@jbrowse/plugin-linear-genome-view 5.0.0-beta.11 → 5.0.0-beta.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/FeatureTrack/configSchema.d.ts +2 -1
- package/esm/FeatureTrack/configSchema.js +2 -1
- package/esm/LinearGenomeView/afterAttach.js +1 -23
- package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +6 -2
- package/esm/LinearGenomeView/components/ExportSvgDialog.js +31 -7
- package/esm/LinearGenomeView/components/HeaderPanControls.d.ts +3 -3
- package/esm/LinearGenomeView/components/HeaderPanControls.js +4 -2
- package/esm/LinearGenomeView/components/HeaderSearchBoxes.js +4 -1
- package/esm/LinearGenomeView/components/HighlightBand.d.ts +3 -2
- package/esm/LinearGenomeView/components/HighlightBand.js +4 -2
- package/esm/LinearGenomeView/components/HighlightChip.d.ts +3 -2
- package/esm/LinearGenomeView/components/HighlightChip.js +4 -2
- package/esm/LinearGenomeView/components/LinearGenomeView.js +5 -12
- package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +7 -9
- package/esm/LinearGenomeView/components/MiniControls.js +8 -3
- package/esm/LinearGenomeView/components/OverviewHighlightBand.d.ts +3 -2
- package/esm/LinearGenomeView/components/OverviewHighlightBand.js +4 -2
- package/esm/LinearGenomeView/components/RegionWidthEditorDialog.d.ts +3 -2
- package/esm/LinearGenomeView/components/RegionWidthEditorDialog.js +4 -4
- package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +4 -4
- package/esm/LinearGenomeView/components/SearchPanelForm.d.ts +3 -2
- package/esm/LinearGenomeView/components/SearchPanelForm.js +4 -2
- package/esm/LinearGenomeView/components/SearchResultsDialog.d.ts +4 -9
- package/esm/LinearGenomeView/components/SearchResultsDialog.js +7 -4
- package/esm/LinearGenomeView/components/SearchResultsTable.d.ts +3 -8
- package/esm/LinearGenomeView/components/SearchResultsTable.js +4 -33
- package/esm/LinearGenomeView/components/StrandCheckboxes.d.ts +3 -2
- package/esm/LinearGenomeView/components/StrandCheckboxes.js +4 -2
- package/esm/LinearGenomeView/components/TrackContainer.js +1 -1
- package/esm/LinearGenomeView/components/TrackLabel.js +4 -1
- package/esm/LinearGenomeView/components/TrackLabelMenu.js +8 -12
- package/esm/LinearGenomeView/components/useRangeSelect.js +3 -2
- package/esm/LinearGenomeView/components/useSearchBoxPrefs.d.ts +3 -1
- package/esm/LinearGenomeView/components/useSearchBoxPrefs.js +4 -2
- package/esm/LinearGenomeView/components/useSideScroll.js +11 -12
- package/esm/LinearGenomeView/lazyDialogs.d.ts +17 -2
- package/esm/LinearGenomeView/menuItems.js +32 -24
- package/esm/LinearGenomeView/model.d.ts +121 -65
- package/esm/LinearGenomeView/model.js +143 -135
- package/esm/LinearGenomeView/rExport/exportLinearView.d.ts +13 -0
- package/esm/LinearGenomeView/rExport/exportLinearView.js +98 -0
- package/esm/LinearGenomeView/rExport/rScript.d.ts +75 -0
- package/esm/LinearGenomeView/rExport/rScript.js +464 -0
- package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +4 -7
- package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.d.ts +1 -2
- package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.js +2 -2
- package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.d.ts +1 -2
- package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +5 -10
- package/esm/LinearGenomeView/svgcomponents/SVGTracks.d.ts +1 -2
- package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +5 -5
- package/esm/LinearGenomeView/svgcomponents/SVGView.d.ts +1 -2
- package/esm/LinearGenomeView/svgcomponents/SVGView.js +3 -3
- package/esm/LinearGenomeView/svgcomponents/renderViewTracks.d.ts +24 -21
- package/esm/LinearGenomeView/svgcomponents/renderViewTracks.js +42 -29
- package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +32 -17
- package/esm/LinearGenomeView/svgcomponents/util.d.ts +16 -13
- package/esm/LinearGenomeView/svgcomponents/util.js +10 -19
- package/esm/LinearGenomeView/util.d.ts +42 -0
- package/esm/LinearGenomeView/util.js +81 -0
- package/esm/MultiLevelRubberband/MultiLevelRubberband.js +2 -2
- package/esm/displayKitTests/perRegionTestEnv.d.ts +45 -12
- package/esm/index.d.ts +4 -2
- package/esm/index.js +3 -1
- package/esm/searchUtils.js +1 -0
- package/esm/shared/RubberbandSpan.d.ts +3 -2
- package/esm/shared/RubberbandSpan.js +4 -2
- package/esm/shared/coordLabels.d.ts +6 -6
- package/esm/shared/coordLabels.js +7 -6
- package/package.json +10 -10
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { canonicalLocString } from '@jbrowse/core/TextSearch/places';
|
|
2
2
|
import { getConf } from '@jbrowse/core/configuration';
|
|
3
3
|
import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes/models';
|
|
4
4
|
import { exportViewSvg } from '@jbrowse/core/svg/exportViewSvg';
|
|
5
|
-
import { assembleLocString, clamp, getBpDisplayStr,
|
|
5
|
+
import { assembleLocString, clamp, getBpDisplayStr, getNotificationSink, getSession, isSessionModelWithWidgets, localStorageGetBoolean, localStorageGetItem, localStorageSetBoolean, localStorageSetItem, springAnimate, sum, } from '@jbrowse/core/util';
|
|
6
6
|
import { bpToLinearBp, bpToPx, computeMoveToLayout, createOverviewLayout, getLayoutHighlightCoords, getOverviewRegionPxSpan, moveTo, pxToBp, } from '@jbrowse/core/util/Base1DUtils';
|
|
7
7
|
import { scaleBandHeights } from '@jbrowse/core/util/bandHeight';
|
|
8
8
|
import { wholeBaseRegions } from '@jbrowse/core/util/blockTypes';
|
|
9
9
|
import calculateDynamicBlocks from '@jbrowse/core/util/calculateDynamicBlocks';
|
|
10
10
|
import calculateStaticBlocks from '@jbrowse/core/util/calculateStaticBlocks';
|
|
11
|
-
import { tickLabelsWorthDrawing } from '@jbrowse/core/util/tickLabels';
|
|
12
11
|
import { hideTrackGeneric, launchToggleTrackGeneric, launchTrackGeneric, showTrackGeneric, toggleTrackGeneric, } from '@jbrowse/core/util/tracks';
|
|
13
12
|
import { ElementId } from '@jbrowse/core/util/types/mst';
|
|
14
13
|
import { highlightsOnAssemblies } from '@jbrowse/core/util/viewHighlights';
|
|
@@ -18,7 +17,7 @@ import { MIN_DISPLAY_HEIGHT } from '@jbrowse/display-kit/const';
|
|
|
18
17
|
import { contentRightEdgePx } from '@jbrowse/display-kit/regionHost';
|
|
19
18
|
import { cast, getParent, hasParent, isAlive, types, } from '@jbrowse/mobx-state-tree';
|
|
20
19
|
import { observable, when } from 'mobx';
|
|
21
|
-
import { handleSelectedRegion, navToOption } from '../searchUtils.js';
|
|
20
|
+
import { handleSelectedRegion, isOpenInView, navToOption, } from '../searchUtils.js';
|
|
22
21
|
import { doAfterAttach } from './afterAttach.js';
|
|
23
22
|
import { getCytobands, shouldSwapTracks } from './components/util.js';
|
|
24
23
|
import { HEADER_BAR_HEIGHT, HEADER_OVERVIEW_HEIGHT, MIN_BP_PER_PX, MINIMIZED_TRACK_HEIGHT, MINIMUM_BLOCK_WIDTH, RESIZE_ALL_HANDLE_HEIGHT, RESIZE_HANDLE_HEIGHT, SCALE_BAR_HEIGHT, SHOW_ALL_REGIONS_FILL, TRACK_OUTLINE_BORDER, TRACK_TOP_GAP, } from './consts.js';
|
|
@@ -27,13 +26,7 @@ import { setupKeyboardHandler } from './keyboardHandler.js';
|
|
|
27
26
|
import { lgvLaunchKeys } from './launchKeys.js';
|
|
28
27
|
import { buildHighlightsSubMenuItems, buildMenuItems, buildRubberBandMenuItems, buildRubberbandClickMenuItems, } from './menuItems.js';
|
|
29
28
|
import { sharedScaleContainerOf } from './sharedScaleContainer.js';
|
|
30
|
-
import { calculateVisibleLocStrings, cytobandLabelGutterWidth, expandRegion, fitAllRegionsWindow, generateLocations,
|
|
31
|
-
// lazies
|
|
32
|
-
const SearchResultsDialog = lazy(() => import('./components/SearchResultsDialog.js'));
|
|
33
|
-
// The one array `paddingSpans` returns when a view has nothing to mask, so that
|
|
34
|
-
// computed's value repeats by identity. Frozen because every consumer maps over
|
|
35
|
-
// it and a caller that sorted in place would do so for the whole session.
|
|
36
|
-
const NO_PADDING_SPANS = Object.freeze([]);
|
|
29
|
+
import { calculateVisibleLocStrings, cytobandLabelGutterWidth, expandRegion, fitAllRegionsWindow, generateLocations, getGridlineTicks, getPaddingSpans, getScalebarLabels, getScalebarRefNameLabels, regionsOrientation, setDisplayedRegionsKeepingCenter, } from './util.js';
|
|
37
30
|
/**
|
|
38
31
|
* Calculate the offsetPx needed to center content within a viewport.
|
|
39
32
|
* Returns a negative offset when content is smaller than viewport (padding on left).
|
|
@@ -60,13 +53,18 @@ function canonicalHighlight(self, region) {
|
|
|
60
53
|
// bpPerPx deltas smaller than this are treated as no zoom change, avoiding
|
|
61
54
|
// pointless offset re-anchoring on micro-steps
|
|
62
55
|
const BP_PER_PX_EPSILON = 0.000001;
|
|
63
|
-
// px of
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
|
|
56
|
+
// px of blank space a view may scroll past either end of its regions, enough to
|
|
57
|
+
// read as an edge and too little to lose the content
|
|
58
|
+
const OVERSCROLL_PX = 100;
|
|
59
|
+
// The offsets a view can scroll to: the content edge to edge plus a little
|
|
60
|
+
// overscroll. Content narrower than the view collapses the range to the
|
|
61
|
+
// centered offset, which is what `showAllRegions` and `fitAllRegions` ask for.
|
|
62
|
+
function offsetBounds(contentPx, viewportPx) {
|
|
63
|
+
const centered = getCenteredOffsetPx(contentPx, viewportPx);
|
|
64
|
+
return contentPx > viewportPx
|
|
65
|
+
? { min: -OVERSCROLL_PX, max: contentPx - viewportPx + OVERSCROLL_PX }
|
|
66
|
+
: { min: centered, max: centered };
|
|
67
|
+
}
|
|
70
68
|
// whether two BlockSets cover the same blocks, by key and in order
|
|
71
69
|
function sameBlockKeys(a, b) {
|
|
72
70
|
return (a.blocks.length === b.blocks.length &&
|
|
@@ -143,6 +141,8 @@ function isTrackSelectorVisible(self) {
|
|
|
143
141
|
/**
|
|
144
142
|
* #stateModel LinearGenomeView
|
|
145
143
|
* #category view
|
|
144
|
+
* Stacks tracks under a horizontal axis of one or more genomic regions laid
|
|
145
|
+
* end to end, which the user scrolls, zooms and searches.
|
|
146
146
|
*
|
|
147
147
|
* #example
|
|
148
148
|
* A `LinearGenomeView` is what you hand-author under `defaultSession.views`, and
|
|
@@ -267,7 +267,10 @@ export function stateModelFactory(pluginManager) {
|
|
|
267
267
|
trackSelectorType: types.stripDefault(types.enumeration(['hierarchical']), 'hierarchical'),
|
|
268
268
|
/**
|
|
269
269
|
* #property
|
|
270
|
-
* show the "center line"
|
|
270
|
+
* show the "center line". A personal preference: never in a session
|
|
271
|
+
* snapshot. It, showCytobands, trackLabels, colorByCDS, showAminoAcids
|
|
272
|
+
* and showTrackOutlines start from this browser's last choice, which
|
|
273
|
+
* their setters record.
|
|
271
274
|
*/
|
|
272
275
|
showCenterLine: types.optional(types.boolean, () => localStorageGetBoolean('lgv-showCenterLine', false)),
|
|
273
276
|
/**
|
|
@@ -355,6 +358,12 @@ export function stateModelFactory(pluginManager) {
|
|
|
355
358
|
* #volatile
|
|
356
359
|
*/
|
|
357
360
|
draggingTrackId: undefined,
|
|
361
|
+
/**
|
|
362
|
+
* #volatile
|
|
363
|
+
* Set by a search whose hits name more than one place, until the user
|
|
364
|
+
* picks one or closes it.
|
|
365
|
+
*/
|
|
366
|
+
searchPicker: undefined,
|
|
358
367
|
/**
|
|
359
368
|
* #volatile
|
|
360
369
|
*/
|
|
@@ -967,15 +976,13 @@ export function stateModelFactory(pluginManager) {
|
|
|
967
976
|
* #getter
|
|
968
977
|
*/
|
|
969
978
|
get maxOffset() {
|
|
970
|
-
|
|
971
|
-
return this.displayedRegionsTotalPx - MAX_OFFSET_PADDING_PX;
|
|
979
|
+
return offsetBounds(this.displayedRegionsTotalPx, self.width).max;
|
|
972
980
|
},
|
|
973
981
|
/**
|
|
974
982
|
* #getter
|
|
975
983
|
*/
|
|
976
984
|
get minOffset() {
|
|
977
|
-
|
|
978
|
-
return -self.width + MIN_OFFSET_PADDING_PX;
|
|
985
|
+
return offsetBounds(this.displayedRegionsTotalPx, self.width).min;
|
|
979
986
|
},
|
|
980
987
|
/**
|
|
981
988
|
* #getter
|
|
@@ -1020,6 +1027,15 @@ export function stateModelFactory(pluginManager) {
|
|
|
1020
1027
|
getActiveDisplayId(trackId) {
|
|
1021
1028
|
return this.getTrack(trackId)?.activeDisplay.configuration.displayId;
|
|
1022
1029
|
},
|
|
1030
|
+
}))
|
|
1031
|
+
.actions(self => ({
|
|
1032
|
+
/**
|
|
1033
|
+
* #action
|
|
1034
|
+
* Dismiss the search picker without choosing a place.
|
|
1035
|
+
*/
|
|
1036
|
+
closeSearchPicker() {
|
|
1037
|
+
self.searchPicker = undefined;
|
|
1038
|
+
},
|
|
1023
1039
|
}))
|
|
1024
1040
|
.actions(self => ({
|
|
1025
1041
|
/**
|
|
@@ -1027,6 +1043,7 @@ export function stateModelFactory(pluginManager) {
|
|
|
1027
1043
|
*/
|
|
1028
1044
|
setShowTrackOutlines(arg) {
|
|
1029
1045
|
self.showTrackOutlines = arg;
|
|
1046
|
+
localStorageSetBoolean('lgv-showTrackOutlines', arg);
|
|
1030
1047
|
},
|
|
1031
1048
|
/**
|
|
1032
1049
|
* #action
|
|
@@ -1039,18 +1056,21 @@ export function stateModelFactory(pluginManager) {
|
|
|
1039
1056
|
*/
|
|
1040
1057
|
setColorByCDS(flag) {
|
|
1041
1058
|
self.colorByCDS = flag;
|
|
1059
|
+
localStorageSetBoolean('lgv-colorByCDS', flag);
|
|
1042
1060
|
},
|
|
1043
1061
|
/**
|
|
1044
1062
|
* #action
|
|
1045
1063
|
*/
|
|
1046
1064
|
setShowAminoAcids(flag) {
|
|
1047
1065
|
self.showAminoAcids = flag;
|
|
1066
|
+
localStorageSetBoolean('lgv-showAminoAcids', flag);
|
|
1048
1067
|
},
|
|
1049
1068
|
/**
|
|
1050
1069
|
* #action
|
|
1051
1070
|
*/
|
|
1052
1071
|
setShowCytobands(flag) {
|
|
1053
1072
|
self.showCytobands = flag;
|
|
1073
|
+
localStorageSetBoolean('lgv-showCytobands', flag);
|
|
1054
1074
|
},
|
|
1055
1075
|
/**
|
|
1056
1076
|
* #action
|
|
@@ -1236,17 +1256,46 @@ export function stateModelFactory(pluginManager) {
|
|
|
1236
1256
|
model: self,
|
|
1237
1257
|
assemblyName,
|
|
1238
1258
|
})) {
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1259
|
+
const session = getSession(self);
|
|
1260
|
+
const assembly = session.assemblyManager.get(assemblyName);
|
|
1261
|
+
const ordered = [...searchResults].sort((a, b) => Number(isOpenInView(b, self)) -
|
|
1262
|
+
Number(isOpenInView(a, self)));
|
|
1263
|
+
const picker = {
|
|
1264
|
+
query: searchQuery,
|
|
1265
|
+
assemblyName,
|
|
1266
|
+
rows: ordered.map(result => {
|
|
1267
|
+
const location = result.getLocation() ?? '';
|
|
1268
|
+
const trackId = result.getTrackId();
|
|
1269
|
+
return {
|
|
1270
|
+
id: result.getId(),
|
|
1271
|
+
label: result.getLabel(),
|
|
1272
|
+
location: assembly && location
|
|
1273
|
+
? canonicalLocString(location, assembly)
|
|
1274
|
+
: location,
|
|
1275
|
+
trackName: trackId
|
|
1276
|
+
? (session.getTrackById(trackId)?.name ?? '')
|
|
1277
|
+
: '',
|
|
1278
|
+
};
|
|
1279
|
+
}),
|
|
1280
|
+
pick: async (id) => {
|
|
1281
|
+
const picked = ordered.find(result => result.getId() === id);
|
|
1282
|
+
try {
|
|
1283
|
+
if (picked) {
|
|
1284
|
+
await onPick(picked);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
catch (e) {
|
|
1288
|
+
console.error(e);
|
|
1289
|
+
if (isAlive(self)) {
|
|
1290
|
+
getNotificationSink(self).notifyError(`${e}`, e);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
if (isAlive(self) && self.searchPicker === picker) {
|
|
1294
|
+
self.closeSearchPicker();
|
|
1295
|
+
}
|
|
1248
1296
|
},
|
|
1249
|
-
|
|
1297
|
+
};
|
|
1298
|
+
self.searchPicker = picker;
|
|
1250
1299
|
},
|
|
1251
1300
|
/**
|
|
1252
1301
|
* #action
|
|
@@ -1372,12 +1421,14 @@ export function stateModelFactory(pluginManager) {
|
|
|
1372
1421
|
*/
|
|
1373
1422
|
setTrackLabels(setting) {
|
|
1374
1423
|
self.trackLabels = setting;
|
|
1424
|
+
localStorageSetItem('lgv-trackLabels', setting);
|
|
1375
1425
|
},
|
|
1376
1426
|
/**
|
|
1377
1427
|
* #action
|
|
1378
1428
|
*/
|
|
1379
1429
|
setShowCenterLine(b) {
|
|
1380
1430
|
self.showCenterLine = b;
|
|
1431
|
+
localStorageSetBoolean('lgv-showCenterLine', b);
|
|
1381
1432
|
},
|
|
1382
1433
|
/**
|
|
1383
1434
|
* #action
|
|
@@ -1426,7 +1477,8 @@ export function stateModelFactory(pluginManager) {
|
|
|
1426
1477
|
const { bpPerPx, offsetPx: rawOffsetPx } = computeMoveToLayout(layout, leftOffset, rightOffset);
|
|
1427
1478
|
// mirror Base1DView.scrollTo clamping: raw offsetPx can be far outside
|
|
1428
1479
|
// the valid range when both offsets are oob on the same side
|
|
1429
|
-
const
|
|
1480
|
+
const { min, max } = offsetBounds(self.totalBp / bpPerPx, self.width);
|
|
1481
|
+
const offsetPx = clamp(rawOffsetPx, min, max);
|
|
1430
1482
|
return wholeBaseRegions(calculateDynamicBlocks({
|
|
1431
1483
|
...layout,
|
|
1432
1484
|
bpPerPx,
|
|
@@ -1477,16 +1529,6 @@ export function stateModelFactory(pluginManager) {
|
|
|
1477
1529
|
setLaunch(launch) {
|
|
1478
1530
|
self.launch = launch;
|
|
1479
1531
|
},
|
|
1480
|
-
/**
|
|
1481
|
-
* #method
|
|
1482
|
-
* renders the view to SVG markup, which it returns; saves it through
|
|
1483
|
-
* FileSaver unless `save: false`
|
|
1484
|
-
*/
|
|
1485
|
-
// Promise<string> is stated: renderToSvg is typed against this very
|
|
1486
|
-
// model, so an inferred return makes the type reference itself (TS2456)
|
|
1487
|
-
async exportSvg(opts = {}) {
|
|
1488
|
-
return exportViewSvg(self, opts, () => import('./svgcomponents/SVGLinearGenomeView.js'));
|
|
1489
|
-
},
|
|
1490
1532
|
}))
|
|
1491
1533
|
.actions(self => {
|
|
1492
1534
|
let cancelLastSlide = () => { };
|
|
@@ -1790,21 +1832,12 @@ export function stateModelFactory(pluginManager) {
|
|
|
1790
1832
|
* than recomputing the makeTicks loop per component.
|
|
1791
1833
|
*/
|
|
1792
1834
|
get gridlineTicks() {
|
|
1793
|
-
const { bpPerPx } = self;
|
|
1794
1835
|
const { blocks, offsetPx: firstBlockOffset } = this.staticBlocks;
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
const runLeft = run.offsetPx - firstBlockOffset;
|
|
1801
|
-
for (const { type, x } of makeBlockTicks(run, bpPerPx)) {
|
|
1802
|
-
if (x >= 0 && x <= run.widthPx) {
|
|
1803
|
-
ticks.push({ x: runLeft + x, major: type === 'major' });
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
return ticks;
|
|
1836
|
+
return getGridlineTicks({
|
|
1837
|
+
blocks,
|
|
1838
|
+
firstBlockOffset,
|
|
1839
|
+
bpPerPx: self.bpPerPx,
|
|
1840
|
+
});
|
|
1808
1841
|
},
|
|
1809
1842
|
/**
|
|
1810
1843
|
* #getter
|
|
@@ -1822,33 +1855,13 @@ export function stateModelFactory(pluginManager) {
|
|
|
1822
1855
|
* reads as the same number repeated across the row.
|
|
1823
1856
|
*/
|
|
1824
1857
|
get scalebarLabels() {
|
|
1825
|
-
const { bpPerPx } = self;
|
|
1826
1858
|
const { blocks, offsetPx: firstBlockOffset } = this.staticBlocks;
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
for (const { base, x } of makeBlockTicks(run, bpPerPx, false)) {
|
|
1834
|
-
const label = getTickDisplayStr(base + 1, bpPerPx);
|
|
1835
|
-
const w = tickLabelWidth(label);
|
|
1836
|
-
// the bold refName label pinned at the run's left edge takes
|
|
1837
|
-
// precedence, as it does on the overview scalebar
|
|
1838
|
-
if (x - w / 2 >= refNameLabelPx[i] &&
|
|
1839
|
-
labelFitsInBlock(x - w / 2, w, run.widthPx)) {
|
|
1840
|
-
runLabels.push({
|
|
1841
|
-
x: runLeft + x,
|
|
1842
|
-
label,
|
|
1843
|
-
key: `${run.offsetPx}-${base}`,
|
|
1844
|
-
});
|
|
1845
|
-
}
|
|
1846
|
-
}
|
|
1847
|
-
if (tickLabelsWorthDrawing(runLabels.length)) {
|
|
1848
|
-
labels.push(...runLabels);
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
return labels;
|
|
1859
|
+
return getScalebarLabels({
|
|
1860
|
+
blocks,
|
|
1861
|
+
firstBlockOffset,
|
|
1862
|
+
bpPerPx: self.bpPerPx,
|
|
1863
|
+
orientation: self.displayedRegionsOrientation,
|
|
1864
|
+
});
|
|
1852
1865
|
},
|
|
1853
1866
|
/**
|
|
1854
1867
|
* #getter
|
|
@@ -1946,38 +1959,7 @@ export function stateModelFactory(pluginManager) {
|
|
|
1946
1959
|
// cannot drift without failing here.
|
|
1947
1960
|
get paddingSpans() {
|
|
1948
1961
|
const { blocks, offsetPx: firstBlockOffset } = this.staticBlocks;
|
|
1949
|
-
|
|
1950
|
-
for (const block of blocks) {
|
|
1951
|
-
const x = block.offsetPx - firstBlockOffset;
|
|
1952
|
-
if (block.type === 'ContentBlock') {
|
|
1953
|
-
if (block.isRightEndOfDisplayedRegion) {
|
|
1954
|
-
spans.push({
|
|
1955
|
-
key: `${block.key}-sep`,
|
|
1956
|
-
x: x + block.widthPx - 1,
|
|
1957
|
-
width: 3,
|
|
1958
|
-
kind: 'seam',
|
|
1959
|
-
});
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
else {
|
|
1963
|
-
spans.push({
|
|
1964
|
-
key: block.key,
|
|
1965
|
-
x,
|
|
1966
|
-
width: block.widthPx,
|
|
1967
|
-
kind: block.type === 'ElidedBlock'
|
|
1968
|
-
? 'elided'
|
|
1969
|
-
: 'boundary',
|
|
1970
|
-
});
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
1973
|
-
// One shared array when there is nothing to draw, so the computed's
|
|
1974
|
-
// value repeats and MobX's `===` stops the chain here. This is the
|
|
1975
|
-
// COMMON case, not an edge one: a view sitting inside one contig has
|
|
1976
|
-
// no region seam, no elision and no boundary, and that is where a
|
|
1977
|
-
// reader spends nearly all of a session. A fresh `[]` each frame
|
|
1978
|
-
// instead re-rendered a PaddingBlocks per track plus one for the
|
|
1979
|
-
// container, every frame of every gesture, to draw nothing.
|
|
1980
|
-
return spans.length > 0 ? spans : NO_PADDING_SPANS;
|
|
1962
|
+
return getPaddingSpans(blocks, firstBlockOffset);
|
|
1981
1963
|
},
|
|
1982
1964
|
/**
|
|
1983
1965
|
* #getter
|
|
@@ -2050,7 +2032,7 @@ export function stateModelFactory(pluginManager) {
|
|
|
2050
2032
|
/**
|
|
2051
2033
|
* #getter
|
|
2052
2034
|
* Right edge (px, viewport-relative) of the on-screen content, clamped
|
|
2053
|
-
* to the track — where a right-pinned overlay such as a wiggle's
|
|
2035
|
+
* to the track — where a right-pinned overlay such as a wiggle's color
|
|
2054
2036
|
* or score legend belongs, rather than out in the empty gutter the
|
|
2055
2037
|
* regions can leave at whole-genome zoom. `@jbrowse/display-kit`'s
|
|
2056
2038
|
* `contentRightEdgePx` states the rule; an SVG export applies the same
|
|
@@ -2219,6 +2201,42 @@ export function stateModelFactory(pluginManager) {
|
|
|
2219
2201
|
}
|
|
2220
2202
|
},
|
|
2221
2203
|
}))
|
|
2204
|
+
.actions(self => {
|
|
2205
|
+
function layOutAt(width) {
|
|
2206
|
+
self.setWidth(width);
|
|
2207
|
+
self.settleCoarseBlocks();
|
|
2208
|
+
}
|
|
2209
|
+
return {
|
|
2210
|
+
/**
|
|
2211
|
+
* #method
|
|
2212
|
+
* renders the view to SVG markup, which it returns; saves it through
|
|
2213
|
+
* FileSaver unless `save: false`. A `viewWidth` lays the view out at
|
|
2214
|
+
* that many pixels for the export, as a window that wide would, and
|
|
2215
|
+
* puts the on-screen width back after.
|
|
2216
|
+
*/
|
|
2217
|
+
// Promise<string> is stated: renderToSvg is typed against this very
|
|
2218
|
+
// model, so an inferred return makes the type reference itself (TS2456)
|
|
2219
|
+
async exportSvg(opts = {}) {
|
|
2220
|
+
const { width } = self;
|
|
2221
|
+
const { viewWidth = width } = opts;
|
|
2222
|
+
// The on-screen tracks redraw at `viewWidth` until the export ends. A
|
|
2223
|
+
// hidden copy of the view would keep them still, but it needs a
|
|
2224
|
+
// session slot that autosave and undo skip, and new ids for the view,
|
|
2225
|
+
// its tracks and displays, since widgets find them by id.
|
|
2226
|
+
if (viewWidth !== width) {
|
|
2227
|
+
layOutAt(viewWidth);
|
|
2228
|
+
}
|
|
2229
|
+
try {
|
|
2230
|
+
return await exportViewSvg(self, opts, () => import('./svgcomponents/SVGLinearGenomeView.js'));
|
|
2231
|
+
}
|
|
2232
|
+
finally {
|
|
2233
|
+
if (viewWidth !== width) {
|
|
2234
|
+
layOutAt(width);
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
},
|
|
2238
|
+
};
|
|
2239
|
+
})
|
|
2222
2240
|
.actions(self => ({
|
|
2223
2241
|
/**
|
|
2224
2242
|
* #action
|
|
@@ -2498,8 +2516,7 @@ export function stateModelFactory(pluginManager) {
|
|
|
2498
2516
|
// unrelated files lose their parameter types to it.
|
|
2499
2517
|
async navToLocString(input, optAssemblyName, grow, opts) {
|
|
2500
2518
|
const { assemblyNames } = self;
|
|
2501
|
-
const
|
|
2502
|
-
const { assemblyManager } = session;
|
|
2519
|
+
const { assemblyManager } = getSession(self);
|
|
2503
2520
|
const assemblyName = optAssemblyName || assemblyNames[0];
|
|
2504
2521
|
if (assemblyName) {
|
|
2505
2522
|
await assemblyManager.waitForAssembly(assemblyName);
|
|
@@ -2911,22 +2928,13 @@ export function stateModelFactory(pluginManager) {
|
|
|
2911
2928
|
};
|
|
2912
2929
|
})
|
|
2913
2930
|
.postProcessSnapshot(snap => {
|
|
2914
|
-
// showCenterLine is
|
|
2915
|
-
//
|
|
2916
|
-
//
|
|
2917
|
-
//
|
|
2918
|
-
//
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
const { showCenterLine, showCytobands, trackLabels, colorByCDS, showAminoAcids, showTrackOutlines, ...rest } = snap;
|
|
2922
|
-
return {
|
|
2923
|
-
...rest,
|
|
2924
|
-
...(!showCytobands ? { showCytobands } : {}),
|
|
2925
|
-
...(trackLabels ? { trackLabels } : {}),
|
|
2926
|
-
...(colorByCDS ? { colorByCDS } : {}),
|
|
2927
|
-
...(!showAminoAcids ? { showAminoAcids } : {}),
|
|
2928
|
-
...(!showTrackOutlines ? { showTrackOutlines } : {}),
|
|
2929
|
-
};
|
|
2931
|
+
// showCenterLine is a personal preference and stays out of the session.
|
|
2932
|
+
// The other localStorage-seeded settings are always written: the
|
|
2933
|
+
// receiving browser's localStorage fills an absent one, so stripping a
|
|
2934
|
+
// default would show a share link's reader their own setting instead
|
|
2935
|
+
// of the author's.
|
|
2936
|
+
const { showCenterLine, ...rest } = snap;
|
|
2937
|
+
return rest;
|
|
2930
2938
|
});
|
|
2931
2939
|
}
|
|
2932
2940
|
// #endregion
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LinearGenomeViewModel } from '../model.ts';
|
|
2
|
+
/** The R script that redraws the tracks open in the view, over its visible regions. */
|
|
3
|
+
export declare function exportLinearViewToR(view: LinearGenomeViewModel, out?: string): {
|
|
4
|
+
drawn: number;
|
|
5
|
+
script: undefined;
|
|
6
|
+
notes: string[];
|
|
7
|
+
} | {
|
|
8
|
+
drawn: number;
|
|
9
|
+
script: string;
|
|
10
|
+
notes: string[];
|
|
11
|
+
};
|
|
12
|
+
/** Downloads the view's R script, telling the user what it leaves out. */
|
|
13
|
+
export declare function downloadRScript(view: LinearGenomeViewModel): Promise<void>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { fullConfSnapshot, readConfObject } from '@jbrowse/core/configuration';
|
|
2
|
+
import { getSession } from '@jbrowse/core/util';
|
|
3
|
+
import { getSnapshot } from '@jbrowse/mobx-state-tree';
|
|
4
|
+
import { exportTracksToR } from './rScript.js';
|
|
5
|
+
function trackConf(track) {
|
|
6
|
+
return track.configuration;
|
|
7
|
+
}
|
|
8
|
+
/** The regions on screen, each displayed region cut to what the view shows of it. */
|
|
9
|
+
function viewRegions(view) {
|
|
10
|
+
const blocks = view.dynamicBlocks.contentBlocks;
|
|
11
|
+
if (blocks.length === 0) {
|
|
12
|
+
return view.displayedRegions.map(r => ({
|
|
13
|
+
refName: r.refName,
|
|
14
|
+
start: Math.floor(r.start),
|
|
15
|
+
end: Math.ceil(r.end),
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
const byRegion = new Map();
|
|
19
|
+
for (const block of blocks) {
|
|
20
|
+
const key = block.displayedRegionIndex ?? -1;
|
|
21
|
+
const held = byRegion.get(key);
|
|
22
|
+
if (held?.refName === block.refName) {
|
|
23
|
+
held.start = Math.min(held.start, Math.floor(block.start));
|
|
24
|
+
held.end = Math.max(held.end, Math.ceil(block.end));
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
byRegion.set(key, {
|
|
28
|
+
refName: block.refName,
|
|
29
|
+
start: Math.floor(block.start),
|
|
30
|
+
end: Math.ceil(block.end),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return [...byRegion.values()];
|
|
35
|
+
}
|
|
36
|
+
/** The view's assembly config as written. */
|
|
37
|
+
function assemblyOf(view) {
|
|
38
|
+
const name = view.assemblyNames[0];
|
|
39
|
+
const configuration = name
|
|
40
|
+
? getSession(view).assemblyManager.get(name)?.configuration
|
|
41
|
+
: undefined;
|
|
42
|
+
return configuration
|
|
43
|
+
? getSnapshot(configuration)
|
|
44
|
+
: undefined;
|
|
45
|
+
}
|
|
46
|
+
function tracksOf(view) {
|
|
47
|
+
const tracks = [];
|
|
48
|
+
for (const track of view.tracks) {
|
|
49
|
+
const display = track.displays[0];
|
|
50
|
+
if (display) {
|
|
51
|
+
const { type, displayId, ...written } = getSnapshot(display.configuration);
|
|
52
|
+
const { heightMode, height } = fullConfSnapshot(display.configuration);
|
|
53
|
+
tracks.push({
|
|
54
|
+
trackId: track.configuration.trackId,
|
|
55
|
+
trackName: readConfObject(trackConf(track), 'name') ||
|
|
56
|
+
track.configuration.trackId,
|
|
57
|
+
displayType: display.type,
|
|
58
|
+
adapter: readConfObject(trackConf(track), 'adapter'),
|
|
59
|
+
display: written,
|
|
60
|
+
heightMode,
|
|
61
|
+
height,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return tracks;
|
|
66
|
+
}
|
|
67
|
+
/** The R script that redraws the tracks open in the view, over its visible regions. */
|
|
68
|
+
export function exportLinearViewToR(view, out) {
|
|
69
|
+
return exportTracksToR({
|
|
70
|
+
tracks: tracksOf(view),
|
|
71
|
+
regions: viewRegions(view),
|
|
72
|
+
assembly: assemblyOf(view),
|
|
73
|
+
bpPerPx: view.bpPerPx,
|
|
74
|
+
out,
|
|
75
|
+
notes: view.displayedRegions.some(r => r.reversed)
|
|
76
|
+
? ['view: shown right to left, where the figure runs left to right']
|
|
77
|
+
: [],
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/** Downloads the view's R script, telling the user what it leaves out. */
|
|
81
|
+
export async function downloadRScript(view) {
|
|
82
|
+
const session = getSession(view);
|
|
83
|
+
try {
|
|
84
|
+
const { saveAs } = await import('@jbrowse/core/util/FileSaver');
|
|
85
|
+
const { script, notes } = exportLinearViewToR(view);
|
|
86
|
+
if (!script) {
|
|
87
|
+
session.notify(`No open track has an R export. ${notes.join('; ')}`, 'warning');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
saveAs(new Blob([script], { type: 'text/plain;charset=utf-8' }), 'jbrowse_view.R');
|
|
91
|
+
if (notes.length) {
|
|
92
|
+
session.notify(`Exported with ${notes.length} note${notes.length === 1 ? '' : 's'} in the script header`, 'info');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
session.notifyError(`${e}`, e);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** A view's region, 0-based half-open, on the assembly's canonical contig. */
|
|
2
|
+
export interface Region {
|
|
3
|
+
refName: string;
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
}
|
|
7
|
+
export interface RExportTrack {
|
|
8
|
+
trackId: string;
|
|
9
|
+
trackName: string;
|
|
10
|
+
displayType: string;
|
|
11
|
+
adapter: Record<string, unknown>;
|
|
12
|
+
/** The display's config as written, its defaults left out, which the engine fills from the same schema. */
|
|
13
|
+
display: Record<string, unknown>;
|
|
14
|
+
/** The display's resolved sizing, which a feature track exports so the figure drops what the view drops. */
|
|
15
|
+
heightMode?: unknown;
|
|
16
|
+
height?: unknown;
|
|
17
|
+
}
|
|
18
|
+
/** The ggjbrowse release whose readers and `jbrowse_stack` the script calls. */
|
|
19
|
+
export declare const GGJBROWSE_VERSION = "0.10.0";
|
|
20
|
+
/**
|
|
21
|
+
* A resolved config writes an unset slot as `''`, `[]` or `{}`, which a reader
|
|
22
|
+
* would take for a setting. Keeps `false` and `0`, which are values.
|
|
23
|
+
*/
|
|
24
|
+
export declare function withoutUnset(value: unknown): unknown;
|
|
25
|
+
export declare function rStr(s: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* A config value as the R literal ggjbrowse hands the engine: a `list` per
|
|
28
|
+
* object, `c()` per vector of scalars, `list()` per vector of objects and per
|
|
29
|
+
* empty vector, which jsonlite would otherwise write as `{}`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function rValue(value: unknown): string;
|
|
32
|
+
/**
|
|
33
|
+
* The path or URL a JBrowse file location points at, as R opens it: a `uri`
|
|
34
|
+
* resolved against its `baseUri` as the browser's fetch resolved it, a
|
|
35
|
+
* `file://` URL as its path, or a `localPath`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function locationPath(location: unknown): string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* The view's assembly as the R value every reader takes: `jbrowse_assembly()`
|
|
40
|
+
* of its sequence and alias files where ggjbrowse builds the same config from
|
|
41
|
+
* their paths, and otherwise the config itself, its locations paths R opens.
|
|
42
|
+
*/
|
|
43
|
+
export declare function assemblyArgument(snapshot: Record<string, unknown>): {
|
|
44
|
+
value: string;
|
|
45
|
+
} | {
|
|
46
|
+
skipped: string;
|
|
47
|
+
};
|
|
48
|
+
/** A region as JBrowse's location box writes it, 1-based and inclusive. */
|
|
49
|
+
export declare function locusOf(refName: string, start: number, end: number): string;
|
|
50
|
+
/**
|
|
51
|
+
* One R script for a view's tracks over its regions: a ggjbrowse reader per
|
|
52
|
+
* track, reading each as its display does, then `jbrowse_stack()` drawing
|
|
53
|
+
* them as the view stacks them. A track ggjbrowse cannot read lands in the
|
|
54
|
+
* header instead of stopping the figure: a script that quietly differs from
|
|
55
|
+
* its view says so.
|
|
56
|
+
*/
|
|
57
|
+
export declare function exportTracksToR({ tracks, regions, assembly: assemblySnapshot, bpPerPx, out, notes: viewNotes, }: {
|
|
58
|
+
tracks: readonly RExportTrack[];
|
|
59
|
+
regions: readonly Region[];
|
|
60
|
+
/** The view's assembly config as written, which names its sequence and its contigs' aliases. */
|
|
61
|
+
assembly?: Record<string, unknown>;
|
|
62
|
+
/** The view's zoom, which decides what a feature or quantitative display shows. */
|
|
63
|
+
bpPerPx?: number;
|
|
64
|
+
out?: string;
|
|
65
|
+
/** What the caller knows of the view that the figure does not show. */
|
|
66
|
+
notes?: readonly string[];
|
|
67
|
+
}): {
|
|
68
|
+
drawn: number;
|
|
69
|
+
script: undefined;
|
|
70
|
+
notes: string[];
|
|
71
|
+
} | {
|
|
72
|
+
drawn: number;
|
|
73
|
+
script: string;
|
|
74
|
+
notes: string[];
|
|
75
|
+
};
|