@jbrowse/plugin-linear-genome-view 2.0.0 → 2.1.2
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/dist/BaseLinearDisplay/components/BaseLinearDisplay.js +28 -55
- package/dist/BaseLinearDisplay/components/BaseLinearDisplay.js.map +1 -1
- package/dist/BaseLinearDisplay/components/Block.js +17 -28
- package/dist/BaseLinearDisplay/components/Block.js.map +1 -1
- package/dist/BaseLinearDisplay/components/LinearBlocks.js +19 -21
- package/dist/BaseLinearDisplay/components/LinearBlocks.js.map +1 -1
- package/dist/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js +28 -48
- package/dist/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js.map +1 -1
- package/dist/BaseLinearDisplay/components/Tooltip.js +29 -58
- package/dist/BaseLinearDisplay/components/Tooltip.js.map +1 -1
- package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.js +242 -363
- package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.js.map +1 -1
- package/dist/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js +1 -1
- package/dist/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js.map +1 -1
- package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.js +77 -129
- package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.js.map +1 -1
- package/dist/LinearBareDisplay/configSchema.js +2 -2
- package/dist/LinearBareDisplay/configSchema.js.map +1 -1
- package/dist/LinearBareDisplay/model.js +13 -19
- package/dist/LinearBareDisplay/model.js.map +1 -1
- package/dist/LinearBasicDisplay/components/SetMaxHeight.js +14 -31
- package/dist/LinearBasicDisplay/components/SetMaxHeight.js.map +1 -1
- package/dist/LinearBasicDisplay/configSchema.js +3 -3
- package/dist/LinearBasicDisplay/configSchema.js.map +1 -1
- package/dist/LinearBasicDisplay/model.js +119 -147
- package/dist/LinearBasicDisplay/model.js.map +1 -1
- package/dist/LinearGenomeView/components/CenterLine.js +11 -12
- package/dist/LinearGenomeView/components/CenterLine.js.map +1 -1
- package/dist/LinearGenomeView/components/ExportSvgDialog.js +30 -96
- package/dist/LinearGenomeView/components/ExportSvgDialog.js.map +1 -1
- package/dist/LinearGenomeView/components/GetSequenceDialog.d.ts +9 -0
- package/dist/LinearGenomeView/components/GetSequenceDialog.js +172 -0
- package/dist/LinearGenomeView/components/GetSequenceDialog.js.map +1 -0
- package/dist/LinearGenomeView/components/{VerticalGuides.d.ts → Gridlines.d.ts} +0 -0
- package/dist/LinearGenomeView/components/{VerticalGuides.js → Gridlines.js} +19 -21
- package/dist/LinearGenomeView/components/Gridlines.js.map +1 -0
- package/dist/LinearGenomeView/components/Header.js +26 -31
- package/dist/LinearGenomeView/components/Header.js.map +1 -1
- package/dist/LinearGenomeView/components/HelpDialog.js +10 -11
- package/dist/LinearGenomeView/components/HelpDialog.js.map +1 -1
- package/dist/LinearGenomeView/components/ImportForm.js +91 -154
- package/dist/LinearGenomeView/components/ImportForm.js.map +1 -1
- package/dist/LinearGenomeView/components/LinearGenomeView.js +20 -25
- package/dist/LinearGenomeView/components/LinearGenomeView.js.map +1 -1
- package/dist/LinearGenomeView/components/LinearGenomeViewSvg.js +86 -157
- package/dist/LinearGenomeView/components/LinearGenomeViewSvg.js.map +1 -1
- package/dist/LinearGenomeView/components/MiniControls.js +16 -32
- package/dist/LinearGenomeView/components/MiniControls.js.map +1 -1
- package/dist/LinearGenomeView/components/OverviewRubberBand.d.ts +0 -11
- package/dist/LinearGenomeView/components/OverviewRubberBand.js +55 -84
- package/dist/LinearGenomeView/components/OverviewRubberBand.js.map +1 -1
- package/dist/LinearGenomeView/components/OverviewScaleBar.js +94 -117
- package/dist/LinearGenomeView/components/OverviewScaleBar.js.map +1 -1
- package/dist/LinearGenomeView/components/RefNameAutocomplete.js +90 -172
- package/dist/LinearGenomeView/components/RefNameAutocomplete.js.map +1 -1
- package/dist/LinearGenomeView/components/RubberBand.js +51 -72
- package/dist/LinearGenomeView/components/RubberBand.js.map +1 -1
- package/dist/LinearGenomeView/components/Ruler.js +17 -18
- package/dist/LinearGenomeView/components/Ruler.js.map +1 -1
- package/dist/LinearGenomeView/components/ScaleBar.js +37 -58
- package/dist/LinearGenomeView/components/ScaleBar.js.map +1 -1
- package/dist/LinearGenomeView/components/SearchBox.js +69 -133
- package/dist/LinearGenomeView/components/SearchBox.js.map +1 -1
- package/dist/LinearGenomeView/components/SearchResultsDialog.js +32 -33
- package/dist/LinearGenomeView/components/SearchResultsDialog.js.map +1 -1
- package/dist/LinearGenomeView/components/{SequenceDialog.d.ts → SequenceSearchDialog.d.ts} +0 -0
- package/dist/LinearGenomeView/components/SequenceSearchDialog.js +104 -0
- package/dist/LinearGenomeView/components/SequenceSearchDialog.js.map +1 -0
- package/dist/LinearGenomeView/components/TrackContainer.d.ts +2 -1
- package/dist/LinearGenomeView/components/TrackContainer.js +36 -43
- package/dist/LinearGenomeView/components/TrackContainer.js.map +1 -1
- package/dist/LinearGenomeView/components/TrackLabel.js +50 -85
- package/dist/LinearGenomeView/components/TrackLabel.js.map +1 -1
- package/dist/LinearGenomeView/components/TracksContainer.js +33 -50
- package/dist/LinearGenomeView/components/TracksContainer.js.map +1 -1
- package/dist/LinearGenomeView/components/ZoomControls.js +15 -32
- package/dist/LinearGenomeView/components/ZoomControls.js.map +1 -1
- package/dist/LinearGenomeView/components/util.js +14 -87
- package/dist/LinearGenomeView/components/util.js.map +1 -1
- package/dist/LinearGenomeView/index.d.ts +66 -79
- package/dist/LinearGenomeView/index.js +534 -710
- package/dist/LinearGenomeView/index.js.map +1 -1
- package/dist/LinearGenomeView/util.js +17 -36
- package/dist/LinearGenomeView/util.js.map +1 -1
- package/dist/index.js +75 -146
- package/dist/index.js.map +1 -1
- package/esm/BaseLinearDisplay/components/BaseLinearDisplay.js +1 -1
- package/esm/BaseLinearDisplay/components/BaseLinearDisplay.js.map +1 -1
- package/esm/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js +1 -0
- package/esm/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js.map +1 -1
- package/esm/LinearGenomeView/components/GetSequenceDialog.d.ts +9 -0
- package/esm/LinearGenomeView/components/{SequenceDialog.js → GetSequenceDialog.js} +5 -8
- package/esm/LinearGenomeView/components/GetSequenceDialog.js.map +1 -0
- package/esm/LinearGenomeView/components/{VerticalGuides.d.ts → Gridlines.d.ts} +0 -0
- package/esm/LinearGenomeView/components/{VerticalGuides.js → Gridlines.js} +1 -1
- package/esm/LinearGenomeView/components/Gridlines.js.map +1 -0
- package/esm/LinearGenomeView/components/ImportForm.js +1 -0
- package/esm/LinearGenomeView/components/ImportForm.js.map +1 -1
- package/esm/LinearGenomeView/components/LinearGenomeView.js +3 -7
- package/esm/LinearGenomeView/components/LinearGenomeView.js.map +1 -1
- package/esm/LinearGenomeView/components/OverviewRubberBand.d.ts +0 -11
- package/esm/LinearGenomeView/components/OverviewRubberBand.js +16 -21
- package/esm/LinearGenomeView/components/OverviewRubberBand.js.map +1 -1
- package/esm/LinearGenomeView/components/OverviewScaleBar.js +0 -2
- package/esm/LinearGenomeView/components/OverviewScaleBar.js.map +1 -1
- package/esm/LinearGenomeView/components/RubberBand.js +0 -2
- package/esm/LinearGenomeView/components/RubberBand.js.map +1 -1
- package/esm/LinearGenomeView/components/SearchBox.js.map +1 -1
- package/esm/LinearGenomeView/components/{SequenceDialog.d.ts → SequenceSearchDialog.d.ts} +0 -0
- package/esm/LinearGenomeView/components/SequenceSearchDialog.js +76 -0
- package/esm/LinearGenomeView/components/SequenceSearchDialog.js.map +1 -0
- package/esm/LinearGenomeView/components/TrackContainer.d.ts +2 -1
- package/esm/LinearGenomeView/components/TrackContainer.js +15 -20
- package/esm/LinearGenomeView/components/TrackContainer.js.map +1 -1
- package/esm/LinearGenomeView/components/TrackLabel.js +22 -23
- package/esm/LinearGenomeView/components/TrackLabel.js.map +1 -1
- package/esm/LinearGenomeView/components/TracksContainer.js +2 -2
- package/esm/LinearGenomeView/components/TracksContainer.js.map +1 -1
- package/esm/LinearGenomeView/index.d.ts +66 -79
- package/esm/LinearGenomeView/index.js +268 -407
- package/esm/LinearGenomeView/index.js.map +1 -1
- package/package.json +3 -4
- package/src/BaseLinearDisplay/components/BaseLinearDisplay.tsx +0 -1
- package/src/BaseLinearDisplay/components/ServerSideRenderedBlockContent.tsx +2 -1
- package/src/LinearGenomeView/components/{SequenceDialog.tsx → GetSequenceDialog.tsx} +5 -18
- package/src/LinearGenomeView/components/{VerticalGuides.tsx → Gridlines.tsx} +0 -0
- package/src/LinearGenomeView/components/ImportForm.tsx +1 -0
- package/src/LinearGenomeView/components/LinearGenomeView.tsx +4 -8
- package/src/LinearGenomeView/components/OverviewRubberBand.tsx +19 -34
- package/src/LinearGenomeView/components/OverviewScaleBar.tsx +0 -2
- package/src/LinearGenomeView/components/RubberBand.tsx +0 -2
- package/src/LinearGenomeView/components/SearchBox.tsx +1 -1
- package/src/LinearGenomeView/components/SequenceSearchDialog.tsx +165 -0
- package/src/LinearGenomeView/components/TrackContainer.tsx +18 -28
- package/src/LinearGenomeView/components/TrackLabel.tsx +60 -53
- package/src/LinearGenomeView/components/TracksContainer.tsx +2 -2
- package/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.js.snap +14 -17
- package/src/LinearGenomeView/index.test.ts +14 -36
- package/src/LinearGenomeView/index.tsx +389 -541
- package/dist/LinearGenomeView/components/SequenceDialog.js +0 -242
- package/dist/LinearGenomeView/components/SequenceDialog.js.map +0 -1
- package/dist/LinearGenomeView/components/VerticalGuides.js.map +0 -1
- package/esm/LinearGenomeView/components/SequenceDialog.js.map +0 -1
- package/esm/LinearGenomeView/components/VerticalGuides.js.map +0 -1
|
@@ -1,16 +1,18 @@
|
|
|
1
|
+
import { lazy } from 'react';
|
|
1
2
|
import { getConf } from '@jbrowse/core/configuration';
|
|
2
3
|
import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes/models';
|
|
3
4
|
import { ElementId, Region as MUIRegion } from '@jbrowse/core/util/types/mst';
|
|
4
5
|
import { ReturnToImportFormDialog } from '@jbrowse/core/ui';
|
|
5
|
-
import { assembleLocString, clamp, findLastIndex, getContainingView, getSession, isViewContainer, isSessionModelWithWidgets, measureText, parseLocString, springAnimate,
|
|
6
|
+
import { assembleLocString, clamp, findLastIndex, getContainingView, getSession, isViewContainer, isSessionModelWithWidgets, measureText, parseLocString, springAnimate, } from '@jbrowse/core/util';
|
|
6
7
|
import calculateDynamicBlocks from '@jbrowse/core/util/calculateDynamicBlocks';
|
|
7
8
|
import calculateStaticBlocks from '@jbrowse/core/util/calculateStaticBlocks';
|
|
8
9
|
import { getParentRenderProps } from '@jbrowse/core/util/tracks';
|
|
9
10
|
import { transaction, autorun } from 'mobx';
|
|
10
11
|
import { addDisposer, cast, getSnapshot, getRoot, resolveIdentifier, types, } from 'mobx-state-tree';
|
|
11
12
|
import Base1DView from '@jbrowse/core/util/Base1DViewModel';
|
|
12
|
-
import
|
|
13
|
+
import { moveTo, pxToBp, bpToPx } from '@jbrowse/core/util/Base1DUtils';
|
|
13
14
|
import { saveAs } from 'file-saver';
|
|
15
|
+
import clone from 'clone';
|
|
14
16
|
// icons
|
|
15
17
|
import { TrackSelector as TrackSelectorIcon } from '@jbrowse/core/ui/Icons';
|
|
16
18
|
import SyncAltIcon from '@mui/icons-material/SyncAlt';
|
|
@@ -25,6 +27,7 @@ import { renderToSvg } from './components/LinearGenomeViewSvg';
|
|
|
25
27
|
import RefNameAutocomplete from './components/RefNameAutocomplete';
|
|
26
28
|
import SearchBox from './components/SearchBox';
|
|
27
29
|
import ExportSvgDlg from './components/ExportSvgDialog';
|
|
30
|
+
const SequenceSearchDialog = lazy(() => import('./components/SequenceSearchDialog'));
|
|
28
31
|
function calculateVisibleLocStrings(contentBlocks) {
|
|
29
32
|
if (!contentBlocks.length) {
|
|
30
33
|
return '';
|
|
@@ -74,6 +77,7 @@ export function stateModelFactory(pluginManager) {
|
|
|
74
77
|
const setting = localStorageGetItem('lgv-showCytobands');
|
|
75
78
|
return setting !== undefined && setting !== null ? !!+setting : true;
|
|
76
79
|
}),
|
|
80
|
+
showGridlines: true,
|
|
77
81
|
}))
|
|
78
82
|
.volatile(() => ({
|
|
79
83
|
volatileWidth: undefined,
|
|
@@ -197,91 +201,6 @@ export function stateModelFactory(pluginManager) {
|
|
|
197
201
|
tracks: self.tracks,
|
|
198
202
|
};
|
|
199
203
|
},
|
|
200
|
-
bpToPx({ refName, coord, regionNumber, }) {
|
|
201
|
-
return viewBpToPx({ refName, coord, regionNumber, self });
|
|
202
|
-
},
|
|
203
|
-
/**
|
|
204
|
-
*
|
|
205
|
-
* @param px - px in the view area, return value is the displayed regions
|
|
206
|
-
* @returns BpOffset of the displayed region that it lands in
|
|
207
|
-
*/
|
|
208
|
-
pxToBp(px) {
|
|
209
|
-
let bpSoFar = 0;
|
|
210
|
-
const bp = (self.offsetPx + px) * self.bpPerPx;
|
|
211
|
-
const n = self.displayedRegions.length;
|
|
212
|
-
if (bp < 0) {
|
|
213
|
-
const region = self.displayedRegions[0];
|
|
214
|
-
const offset = bp;
|
|
215
|
-
const snap = getSnapshot(region);
|
|
216
|
-
return {
|
|
217
|
-
// xref https://github.com/mobxjs/mobx-state-tree/issues/1524 for Omit
|
|
218
|
-
...snap,
|
|
219
|
-
oob: true,
|
|
220
|
-
coord: region.reversed
|
|
221
|
-
? Math.floor(region.end - offset) + 1
|
|
222
|
-
: Math.floor(region.start + offset) + 1,
|
|
223
|
-
offset,
|
|
224
|
-
index: 0,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
const interRegionPaddingBp = self.interRegionPaddingWidth * self.bpPerPx;
|
|
228
|
-
const minimumBlockBp = self.minimumBlockWidth * self.bpPerPx;
|
|
229
|
-
for (let index = 0; index < self.displayedRegions.length; index += 1) {
|
|
230
|
-
const region = self.displayedRegions[index];
|
|
231
|
-
const len = region.end - region.start;
|
|
232
|
-
const offset = bp - bpSoFar;
|
|
233
|
-
if (len + bpSoFar > bp && bpSoFar <= bp) {
|
|
234
|
-
const snap = getSnapshot(region);
|
|
235
|
-
return {
|
|
236
|
-
// xref https://github.com/mobxjs/mobx-state-tree/issues/1524 for Omit
|
|
237
|
-
...snap,
|
|
238
|
-
oob: false,
|
|
239
|
-
offset,
|
|
240
|
-
coord: region.reversed
|
|
241
|
-
? Math.floor(region.end - offset) + 1
|
|
242
|
-
: Math.floor(region.start + offset) + 1,
|
|
243
|
-
index,
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
// add the interRegionPaddingWidth if the boundary is in the screen
|
|
247
|
-
// e.g. offset>0 && offset<width
|
|
248
|
-
if (region.end - region.start > minimumBlockBp &&
|
|
249
|
-
offset / self.bpPerPx > 0 &&
|
|
250
|
-
offset / self.bpPerPx < self.width) {
|
|
251
|
-
bpSoFar += len + interRegionPaddingBp;
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
bpSoFar += len;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
if (bp >= bpSoFar) {
|
|
258
|
-
const region = self.displayedRegions[n - 1];
|
|
259
|
-
const len = region.end - region.start;
|
|
260
|
-
const offset = bp - bpSoFar + len;
|
|
261
|
-
const snap = getSnapshot(region);
|
|
262
|
-
return {
|
|
263
|
-
// xref https://github.com/mobxjs/mobx-state-tree/issues/1524 for Omit
|
|
264
|
-
...snap,
|
|
265
|
-
oob: true,
|
|
266
|
-
offset,
|
|
267
|
-
coord: region.reversed
|
|
268
|
-
? Math.floor(region.end - offset) + 1
|
|
269
|
-
: Math.floor(region.start + offset) + 1,
|
|
270
|
-
index: n - 1,
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
return {
|
|
274
|
-
coord: 0,
|
|
275
|
-
index: 0,
|
|
276
|
-
refName: '',
|
|
277
|
-
oob: true,
|
|
278
|
-
assemblyName: '',
|
|
279
|
-
offset: 0,
|
|
280
|
-
start: 0,
|
|
281
|
-
end: 0,
|
|
282
|
-
reversed: false,
|
|
283
|
-
};
|
|
284
|
-
},
|
|
285
204
|
getTrack(id) {
|
|
286
205
|
return self.tracks.find(t => t.configuration.trackId === id);
|
|
287
206
|
},
|
|
@@ -326,11 +245,6 @@ export function stateModelFactory(pluginManager) {
|
|
|
326
245
|
});
|
|
327
246
|
return allActions;
|
|
328
247
|
},
|
|
329
|
-
get centerLineInfo() {
|
|
330
|
-
return self.displayedRegions.length
|
|
331
|
-
? this.pxToBp(self.width / 2)
|
|
332
|
-
: undefined;
|
|
333
|
-
},
|
|
334
248
|
}))
|
|
335
249
|
.actions(self => ({
|
|
336
250
|
setShowCytobands(flag) {
|
|
@@ -352,6 +266,9 @@ export function stateModelFactory(pluginManager) {
|
|
|
352
266
|
toggleNoTracksActive() {
|
|
353
267
|
self.hideNoTracksActive = !self.hideNoTracksActive;
|
|
354
268
|
},
|
|
269
|
+
toggleShowGridlines() {
|
|
270
|
+
self.showGridlines = !self.showGridlines;
|
|
271
|
+
},
|
|
355
272
|
scrollTo(offsetPx) {
|
|
356
273
|
const newOffsetPx = clamp(offsetPx, self.minOffset, self.maxOffset);
|
|
357
274
|
self.offsetPx = newOffsetPx;
|
|
@@ -383,7 +300,7 @@ export function stateModelFactory(pluginManager) {
|
|
|
383
300
|
self.searchResults = results;
|
|
384
301
|
self.searchQuery = query;
|
|
385
302
|
},
|
|
386
|
-
|
|
303
|
+
setGetSequenceDialogOpen(open) {
|
|
387
304
|
self.seqDialogDisplayed = open;
|
|
388
305
|
},
|
|
389
306
|
setNewView(bpPerPx, offsetPx) {
|
|
@@ -498,233 +415,6 @@ export function stateModelFactory(pluginManager) {
|
|
|
498
415
|
}
|
|
499
416
|
throw new Error(`invalid track selector type ${self.trackSelectorType}`);
|
|
500
417
|
},
|
|
501
|
-
navToLocString(locString, optAssemblyName) {
|
|
502
|
-
const { assemblyNames } = self;
|
|
503
|
-
const { assemblyManager } = getSession(self);
|
|
504
|
-
const { isValidRefName } = assemblyManager;
|
|
505
|
-
const assemblyName = optAssemblyName || assemblyNames[0];
|
|
506
|
-
let parsedLocStrings;
|
|
507
|
-
const inputs = locString
|
|
508
|
-
.split(/(\s+)/)
|
|
509
|
-
.map(f => f.trim())
|
|
510
|
-
.filter(f => !!f);
|
|
511
|
-
// first try interpreting as a whitespace-separated sequence of
|
|
512
|
-
// multiple locstrings
|
|
513
|
-
try {
|
|
514
|
-
parsedLocStrings = inputs.map(l => parseLocString(l, ref => isValidRefName(ref, assemblyName)));
|
|
515
|
-
}
|
|
516
|
-
catch (e) {
|
|
517
|
-
// if this fails, try interpreting as a whitespace-separated refname,
|
|
518
|
-
// start, end if start and end are integer inputs
|
|
519
|
-
const [refName, start, end] = inputs;
|
|
520
|
-
if (`${e}`.match(/Unknown reference sequence/) &&
|
|
521
|
-
Number.isInteger(+start) &&
|
|
522
|
-
Number.isInteger(+end)) {
|
|
523
|
-
parsedLocStrings = [
|
|
524
|
-
parseLocString(refName + ':' + start + '..' + end, ref => isValidRefName(ref, assemblyName)),
|
|
525
|
-
];
|
|
526
|
-
}
|
|
527
|
-
else {
|
|
528
|
-
throw e;
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
const locations = parsedLocStrings === null || parsedLocStrings === void 0 ? void 0 : parsedLocStrings.map(region => {
|
|
532
|
-
const asmName = region.assemblyName || assemblyName;
|
|
533
|
-
const asm = assemblyManager.get(asmName);
|
|
534
|
-
const { refName } = region;
|
|
535
|
-
if (!asm) {
|
|
536
|
-
throw new Error(`assembly ${asmName} not found`);
|
|
537
|
-
}
|
|
538
|
-
const { regions } = asm;
|
|
539
|
-
if (!regions) {
|
|
540
|
-
throw new Error(`regions not loaded yet for ${asmName}`);
|
|
541
|
-
}
|
|
542
|
-
const canonicalRefName = asm.getCanonicalRefName(region.refName);
|
|
543
|
-
if (!canonicalRefName) {
|
|
544
|
-
throw new Error(`Could not find refName ${refName} in ${asm.name}`);
|
|
545
|
-
}
|
|
546
|
-
const parentRegion = regions.find(region => region.refName === canonicalRefName);
|
|
547
|
-
if (!parentRegion) {
|
|
548
|
-
throw new Error(`Could not find refName ${refName} in ${asmName}`);
|
|
549
|
-
}
|
|
550
|
-
return {
|
|
551
|
-
...region,
|
|
552
|
-
assemblyName: asmName,
|
|
553
|
-
parentRegion,
|
|
554
|
-
};
|
|
555
|
-
});
|
|
556
|
-
if (locations.length === 1) {
|
|
557
|
-
const loc = locations[0];
|
|
558
|
-
this.setDisplayedRegions([
|
|
559
|
-
{ reversed: loc.reversed, ...loc.parentRegion },
|
|
560
|
-
]);
|
|
561
|
-
const { start, end, parentRegion } = loc;
|
|
562
|
-
this.navTo({
|
|
563
|
-
...loc,
|
|
564
|
-
start: clamp(start !== null && start !== void 0 ? start : 0, 0, parentRegion.end),
|
|
565
|
-
end: clamp(end !== null && end !== void 0 ? end : parentRegion.end, 0, parentRegion.end),
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
else {
|
|
569
|
-
this.setDisplayedRegions(
|
|
570
|
-
// @ts-ignore
|
|
571
|
-
locations.map(r => (r.start === undefined ? r.parentRegion : r)));
|
|
572
|
-
this.showAllRegions();
|
|
573
|
-
}
|
|
574
|
-
},
|
|
575
|
-
/**
|
|
576
|
-
* Navigate to a location based on its refName and optionally start, end,
|
|
577
|
-
* and assemblyName. Can handle if there are multiple displayedRegions
|
|
578
|
-
* from same refName. Only navigates to a location if it is entirely
|
|
579
|
-
* within a displayedRegion. Navigates to the first matching location
|
|
580
|
-
* encountered.
|
|
581
|
-
*
|
|
582
|
-
* Throws an error if navigation was unsuccessful
|
|
583
|
-
*
|
|
584
|
-
* @param location - a proposed location to navigate to
|
|
585
|
-
*/
|
|
586
|
-
navTo(query) {
|
|
587
|
-
this.navToMultiple([query]);
|
|
588
|
-
},
|
|
589
|
-
navToMultiple(locations) {
|
|
590
|
-
const firstLocation = locations[0];
|
|
591
|
-
let { refName } = firstLocation;
|
|
592
|
-
const { start, end, assemblyName = self.assemblyNames[0], } = firstLocation;
|
|
593
|
-
if (start !== undefined && end !== undefined && start > end) {
|
|
594
|
-
throw new Error(`start "${start + 1}" is greater than end "${end}"`);
|
|
595
|
-
}
|
|
596
|
-
const session = getSession(self);
|
|
597
|
-
const { assemblyManager } = session;
|
|
598
|
-
const assembly = assemblyManager.get(assemblyName);
|
|
599
|
-
if (assembly) {
|
|
600
|
-
const canonicalRefName = assembly.getCanonicalRefName(refName);
|
|
601
|
-
if (canonicalRefName) {
|
|
602
|
-
refName = canonicalRefName;
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
let s = start;
|
|
606
|
-
let e = end;
|
|
607
|
-
let refNameMatched = false;
|
|
608
|
-
const predicate = (r) => {
|
|
609
|
-
if (refName === r.refName) {
|
|
610
|
-
refNameMatched = true;
|
|
611
|
-
if (s === undefined) {
|
|
612
|
-
s = r.start;
|
|
613
|
-
}
|
|
614
|
-
if (e === undefined) {
|
|
615
|
-
e = r.end;
|
|
616
|
-
}
|
|
617
|
-
if (s >= r.start && s <= r.end && e <= r.end && e >= r.start) {
|
|
618
|
-
return true;
|
|
619
|
-
}
|
|
620
|
-
s = start;
|
|
621
|
-
e = end;
|
|
622
|
-
}
|
|
623
|
-
return false;
|
|
624
|
-
};
|
|
625
|
-
const lastIndex = findLastIndex(self.displayedRegions, predicate);
|
|
626
|
-
let index;
|
|
627
|
-
while (index !== lastIndex) {
|
|
628
|
-
try {
|
|
629
|
-
const previousIndex = index;
|
|
630
|
-
index = self.displayedRegions
|
|
631
|
-
.slice(previousIndex === undefined ? 0 : previousIndex + 1)
|
|
632
|
-
.findIndex(predicate);
|
|
633
|
-
if (previousIndex !== undefined) {
|
|
634
|
-
index += previousIndex + 1;
|
|
635
|
-
}
|
|
636
|
-
if (!refNameMatched) {
|
|
637
|
-
throw new Error(`could not find a region with refName "${refName}"`);
|
|
638
|
-
}
|
|
639
|
-
if (s === undefined) {
|
|
640
|
-
throw new Error(`could not find a region with refName "${refName}" that contained an end position ${e}`);
|
|
641
|
-
}
|
|
642
|
-
if (e === undefined) {
|
|
643
|
-
throw new Error(`could not find a region with refName "${refName}" that contained a start position ${s + 1}`);
|
|
644
|
-
}
|
|
645
|
-
if (index === -1) {
|
|
646
|
-
throw new Error(`could not find a region that completely contained "${assembleLocString(firstLocation)}"`);
|
|
647
|
-
}
|
|
648
|
-
if (locations.length === 1) {
|
|
649
|
-
const f = self.displayedRegions[index];
|
|
650
|
-
this.moveTo({ index, offset: f.reversed ? f.end - e : s - f.start }, { index, offset: f.reversed ? f.end - s : e - f.start });
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
let locationIndex = 0;
|
|
654
|
-
let locationStart = 0;
|
|
655
|
-
let locationEnd = 0;
|
|
656
|
-
for (locationIndex; locationIndex < locations.length; locationIndex++) {
|
|
657
|
-
const location = locations[locationIndex];
|
|
658
|
-
const region = self.displayedRegions[index + locationIndex];
|
|
659
|
-
locationStart = location.start || region.start;
|
|
660
|
-
locationEnd = location.end || region.end;
|
|
661
|
-
if (location.refName !== region.refName) {
|
|
662
|
-
throw new Error(`Entered location ${assembleLocString(location)} does not match with displayed regions`);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
locationIndex -= 1;
|
|
666
|
-
const startDisplayedRegion = self.displayedRegions[index];
|
|
667
|
-
const endDisplayedRegion = self.displayedRegions[index + locationIndex];
|
|
668
|
-
this.moveTo({
|
|
669
|
-
index,
|
|
670
|
-
offset: startDisplayedRegion.reversed
|
|
671
|
-
? startDisplayedRegion.end - e
|
|
672
|
-
: s - startDisplayedRegion.start,
|
|
673
|
-
}, {
|
|
674
|
-
index: index + locationIndex,
|
|
675
|
-
offset: endDisplayedRegion.reversed
|
|
676
|
-
? endDisplayedRegion.end - locationStart
|
|
677
|
-
: locationEnd - endDisplayedRegion.start,
|
|
678
|
-
});
|
|
679
|
-
return;
|
|
680
|
-
}
|
|
681
|
-
catch (error) {
|
|
682
|
-
if (index === lastIndex) {
|
|
683
|
-
throw error;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
},
|
|
688
|
-
/**
|
|
689
|
-
* Navigate to a location based on user clicking and dragging on the
|
|
690
|
-
* overview scale bar to select a region to zoom into.
|
|
691
|
-
* Can handle if there are multiple displayedRegions from same refName.
|
|
692
|
-
* Only navigates to a location if it is entirely within a displayedRegion.
|
|
693
|
-
*
|
|
694
|
-
* @param leftPx- `object as {start, end, index, offset}`, offset = start of user drag
|
|
695
|
-
* @param rightPx- `object as {start, end, index, offset}`, offset = end of user drag
|
|
696
|
-
*/
|
|
697
|
-
zoomToDisplayedRegions(leftPx, rightPx) {
|
|
698
|
-
if (leftPx === undefined || rightPx === undefined) {
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
const singleRefSeq = leftPx.refName === rightPx.refName && leftPx.index === rightPx.index;
|
|
702
|
-
// zooming into one displayed Region
|
|
703
|
-
if ((singleRefSeq && rightPx.offset < leftPx.offset) ||
|
|
704
|
-
leftPx.index > rightPx.index) {
|
|
705
|
-
;
|
|
706
|
-
[leftPx, rightPx] = [rightPx, leftPx];
|
|
707
|
-
}
|
|
708
|
-
const startOffset = {
|
|
709
|
-
start: leftPx.start,
|
|
710
|
-
end: leftPx.end,
|
|
711
|
-
index: leftPx.index,
|
|
712
|
-
offset: leftPx.offset,
|
|
713
|
-
};
|
|
714
|
-
const endOffset = {
|
|
715
|
-
start: rightPx.start,
|
|
716
|
-
end: rightPx.end,
|
|
717
|
-
index: rightPx.index,
|
|
718
|
-
offset: rightPx.offset,
|
|
719
|
-
};
|
|
720
|
-
if (startOffset && endOffset) {
|
|
721
|
-
this.moveTo(startOffset, endOffset);
|
|
722
|
-
}
|
|
723
|
-
else {
|
|
724
|
-
const session = getSession(self);
|
|
725
|
-
session.notify('No regions found to navigate to', 'warning');
|
|
726
|
-
}
|
|
727
|
-
},
|
|
728
418
|
/**
|
|
729
419
|
* Helper method for the fetchSequence.
|
|
730
420
|
* Retrieves the corresponding regions that were selected by the rubberband
|
|
@@ -736,12 +426,11 @@ export function stateModelFactory(pluginManager) {
|
|
|
736
426
|
getSelectedRegions(leftOffset, rightOffset) {
|
|
737
427
|
const snap = getSnapshot(self);
|
|
738
428
|
const simView = Base1DView.create({
|
|
739
|
-
// xref https://github.com/mobxjs/mobx-state-tree/issues/1524 for Omit
|
|
740
429
|
...snap,
|
|
741
430
|
interRegionPaddingWidth: self.interRegionPaddingWidth,
|
|
742
431
|
});
|
|
743
432
|
simView.setVolatileWidth(self.width);
|
|
744
|
-
simView.
|
|
433
|
+
simView.moveTo(leftOffset, rightOffset);
|
|
745
434
|
return simView.dynamicBlocks.contentBlocks.map(region => ({
|
|
746
435
|
...region,
|
|
747
436
|
start: Math.floor(region.start),
|
|
@@ -752,80 +441,16 @@ export function stateModelFactory(pluginManager) {
|
|
|
752
441
|
afterDisplayedRegionsSet(cb) {
|
|
753
442
|
self.afterDisplayedRegionsSetCallbacks.push(cb);
|
|
754
443
|
},
|
|
755
|
-
/**
|
|
756
|
-
* offset is the base-pair-offset in the displayed region, index is the index of the
|
|
757
|
-
* displayed region in the linear genome view
|
|
758
|
-
*
|
|
759
|
-
* @param start - object as `{start, end, offset, index}`
|
|
760
|
-
* @param end - object as `{start, end, offset, index}`
|
|
761
|
-
*/
|
|
762
|
-
moveTo(start, end) {
|
|
763
|
-
// find locations in the modellist
|
|
764
|
-
let bpSoFar = 0;
|
|
765
|
-
if (start.index === end.index) {
|
|
766
|
-
bpSoFar += end.offset - start.offset;
|
|
767
|
-
}
|
|
768
|
-
else {
|
|
769
|
-
const s = self.displayedRegions[start.index];
|
|
770
|
-
bpSoFar += s.end - s.start - start.offset;
|
|
771
|
-
if (end.index - start.index >= 2) {
|
|
772
|
-
for (let i = start.index + 1; i < end.index; i += 1) {
|
|
773
|
-
bpSoFar +=
|
|
774
|
-
self.displayedRegions[i].end - self.displayedRegions[i].start;
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
bpSoFar += end.offset;
|
|
778
|
-
}
|
|
779
|
-
const targetBpPerPx = bpSoFar /
|
|
780
|
-
(self.width -
|
|
781
|
-
self.interRegionPaddingWidth * (end.index - start.index));
|
|
782
|
-
const newBpPerPx = self.zoomTo(targetBpPerPx);
|
|
783
|
-
// If our target bpPerPx was smaller than the allowed minBpPerPx, adjust
|
|
784
|
-
// the scroll so the requested range is in the middle of the screen
|
|
785
|
-
let extraBp = 0;
|
|
786
|
-
if (targetBpPerPx < newBpPerPx) {
|
|
787
|
-
extraBp = ((newBpPerPx - targetBpPerPx) * self.width) / 2;
|
|
788
|
-
}
|
|
789
|
-
let bpToStart = -extraBp;
|
|
790
|
-
for (let i = 0; i < self.displayedRegions.length; i += 1) {
|
|
791
|
-
const region = self.displayedRegions[i];
|
|
792
|
-
if (start.index === i) {
|
|
793
|
-
bpToStart += start.offset;
|
|
794
|
-
break;
|
|
795
|
-
}
|
|
796
|
-
else {
|
|
797
|
-
bpToStart += region.end - region.start;
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
self.scrollTo(Math.round(bpToStart / self.bpPerPx) +
|
|
801
|
-
self.interRegionPaddingWidth * start.index);
|
|
802
|
-
},
|
|
803
444
|
horizontalScroll(distance) {
|
|
804
445
|
const oldOffsetPx = self.offsetPx;
|
|
805
446
|
// newOffsetPx is the actual offset after the scroll is clamped
|
|
806
447
|
const newOffsetPx = self.scrollTo(self.offsetPx + distance);
|
|
807
448
|
return newOffsetPx - oldOffsetPx;
|
|
808
449
|
},
|
|
809
|
-
/**
|
|
810
|
-
* scrolls the view to center on the given bp. if that is not in any
|
|
811
|
-
* of the displayed regions, does nothing
|
|
812
|
-
* @param bp - basepair at which you want to center the view
|
|
813
|
-
* @param refName - refName of the displayedRegion you are centering at
|
|
814
|
-
* @param regionIndex - index of the displayedRegion
|
|
815
|
-
*/
|
|
816
|
-
centerAt(bp, refName, regionIndex) {
|
|
817
|
-
const centerPx = self.bpToPx({
|
|
818
|
-
refName,
|
|
819
|
-
coord: bp,
|
|
820
|
-
regionNumber: regionIndex,
|
|
821
|
-
});
|
|
822
|
-
if (centerPx) {
|
|
823
|
-
self.scrollTo(Math.round(centerPx.offsetPx - self.width / 2));
|
|
824
|
-
}
|
|
825
|
-
},
|
|
826
450
|
center() {
|
|
827
451
|
const centerBp = self.totalBp / 2;
|
|
828
|
-
|
|
452
|
+
const centerPx = centerBp / self.bpPerPx;
|
|
453
|
+
self.scrollTo(Math.round(centerPx - self.width / 2));
|
|
829
454
|
},
|
|
830
455
|
showAllRegions() {
|
|
831
456
|
self.zoomTo(self.maxBpPerPx);
|
|
@@ -926,6 +551,15 @@ export function stateModelFactory(pluginManager) {
|
|
|
926
551
|
},
|
|
927
552
|
icon: FolderOpenIcon,
|
|
928
553
|
},
|
|
554
|
+
{
|
|
555
|
+
label: 'Sequence search',
|
|
556
|
+
onClick: () => {
|
|
557
|
+
getSession(self).queueDialog(handleClose => [
|
|
558
|
+
SequenceSearchDialog,
|
|
559
|
+
{ model: self, handleClose },
|
|
560
|
+
]);
|
|
561
|
+
},
|
|
562
|
+
},
|
|
929
563
|
{
|
|
930
564
|
label: 'Export SVG',
|
|
931
565
|
icon: PhotoCameraIcon,
|
|
@@ -981,6 +615,24 @@ export function stateModelFactory(pluginManager) {
|
|
|
981
615
|
checked: !self.hideNoTracksActive,
|
|
982
616
|
onClick: self.toggleNoTracksActive,
|
|
983
617
|
},
|
|
618
|
+
{
|
|
619
|
+
label: 'Show guidelines',
|
|
620
|
+
icon: VisibilityIcon,
|
|
621
|
+
type: 'checkbox',
|
|
622
|
+
checked: self.showGridlines,
|
|
623
|
+
onClick: self.toggleShowGridlines,
|
|
624
|
+
},
|
|
625
|
+
...(canShowCytobands
|
|
626
|
+
? [
|
|
627
|
+
{
|
|
628
|
+
label: 'Show ideogram',
|
|
629
|
+
icon: VisibilityIcon,
|
|
630
|
+
type: 'checkbox',
|
|
631
|
+
checked: self.showCytobands,
|
|
632
|
+
onClick: () => self.setShowCytobands(!showCytobands),
|
|
633
|
+
},
|
|
634
|
+
]
|
|
635
|
+
: []),
|
|
984
636
|
{
|
|
985
637
|
label: 'Track labels',
|
|
986
638
|
icon: LabelIcon,
|
|
@@ -1008,24 +660,12 @@ export function stateModelFactory(pluginManager) {
|
|
|
1008
660
|
},
|
|
1009
661
|
],
|
|
1010
662
|
},
|
|
1011
|
-
...(canShowCytobands
|
|
1012
|
-
? [
|
|
1013
|
-
{
|
|
1014
|
-
label: showCytobands ? 'Hide ideogram' : 'Show ideograms',
|
|
1015
|
-
onClick: () => {
|
|
1016
|
-
self.setShowCytobands(!showCytobands);
|
|
1017
|
-
},
|
|
1018
|
-
},
|
|
1019
|
-
]
|
|
1020
|
-
: []),
|
|
1021
663
|
];
|
|
1022
664
|
// add track's view level menu options
|
|
1023
665
|
for (const [key, value] of self.trackTypeActions.entries()) {
|
|
1024
666
|
if (value.length) {
|
|
1025
667
|
menuItems.push({ type: 'divider' }, { type: 'subHeader', label: key });
|
|
1026
|
-
value.forEach(action =>
|
|
1027
|
-
menuItems.push(action);
|
|
1028
|
-
});
|
|
668
|
+
value.forEach(action => menuItems.push(action));
|
|
1029
669
|
}
|
|
1030
670
|
}
|
|
1031
671
|
return menuItems;
|
|
@@ -1094,6 +734,203 @@ export function stateModelFactory(pluginManager) {
|
|
|
1094
734
|
const blob = new Blob([html], { type: 'image/svg+xml' });
|
|
1095
735
|
saveAs(blob, opts.filename || 'image.svg');
|
|
1096
736
|
},
|
|
737
|
+
/**
|
|
738
|
+
* offset is the base-pair-offset in the displayed region, index is the index of the
|
|
739
|
+
* displayed region in the linear genome view
|
|
740
|
+
*
|
|
741
|
+
* @param start - object as `{start, end, offset, index}`
|
|
742
|
+
* @param end - object as `{start, end, offset, index}`
|
|
743
|
+
*/
|
|
744
|
+
moveTo(start, end) {
|
|
745
|
+
moveTo(self, start, end);
|
|
746
|
+
},
|
|
747
|
+
navToLocString(locString, optAssemblyName) {
|
|
748
|
+
const { assemblyNames } = self;
|
|
749
|
+
const { assemblyManager } = getSession(self);
|
|
750
|
+
const { isValidRefName } = assemblyManager;
|
|
751
|
+
const assemblyName = optAssemblyName || assemblyNames[0];
|
|
752
|
+
let parsedLocStrings;
|
|
753
|
+
const inputs = locString
|
|
754
|
+
.split(/(\s+)/)
|
|
755
|
+
.map(f => f.trim())
|
|
756
|
+
.filter(f => !!f);
|
|
757
|
+
// first try interpreting as a whitespace-separated sequence of
|
|
758
|
+
// multiple locstrings
|
|
759
|
+
try {
|
|
760
|
+
parsedLocStrings = inputs.map(l => parseLocString(l, ref => isValidRefName(ref, assemblyName)));
|
|
761
|
+
}
|
|
762
|
+
catch (e) {
|
|
763
|
+
// if this fails, try interpreting as a whitespace-separated refname,
|
|
764
|
+
// start, end if start and end are integer inputs
|
|
765
|
+
const [refName, start, end] = inputs;
|
|
766
|
+
if (`${e}`.match(/Unknown reference sequence/) &&
|
|
767
|
+
Number.isInteger(+start) &&
|
|
768
|
+
Number.isInteger(+end)) {
|
|
769
|
+
parsedLocStrings = [
|
|
770
|
+
parseLocString(refName + ':' + start + '..' + end, ref => isValidRefName(ref, assemblyName)),
|
|
771
|
+
];
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
throw e;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
const locations = parsedLocStrings === null || parsedLocStrings === void 0 ? void 0 : parsedLocStrings.map(region => {
|
|
778
|
+
const asmName = region.assemblyName || assemblyName;
|
|
779
|
+
const asm = assemblyManager.get(asmName);
|
|
780
|
+
const { refName } = region;
|
|
781
|
+
if (!asm) {
|
|
782
|
+
throw new Error(`assembly ${asmName} not found`);
|
|
783
|
+
}
|
|
784
|
+
const { regions } = asm;
|
|
785
|
+
if (!regions) {
|
|
786
|
+
throw new Error(`regions not loaded yet for ${asmName}`);
|
|
787
|
+
}
|
|
788
|
+
const canonicalRefName = asm.getCanonicalRefName(region.refName);
|
|
789
|
+
if (!canonicalRefName) {
|
|
790
|
+
throw new Error(`Could not find refName ${refName} in ${asm.name}`);
|
|
791
|
+
}
|
|
792
|
+
const parentRegion = regions.find(region => region.refName === canonicalRefName);
|
|
793
|
+
if (!parentRegion) {
|
|
794
|
+
throw new Error(`Could not find refName ${refName} in ${asmName}`);
|
|
795
|
+
}
|
|
796
|
+
return {
|
|
797
|
+
...region,
|
|
798
|
+
assemblyName: asmName,
|
|
799
|
+
parentRegion,
|
|
800
|
+
};
|
|
801
|
+
});
|
|
802
|
+
if (locations.length === 1) {
|
|
803
|
+
const loc = locations[0];
|
|
804
|
+
self.setDisplayedRegions([
|
|
805
|
+
{ reversed: loc.reversed, ...loc.parentRegion },
|
|
806
|
+
]);
|
|
807
|
+
const { start, end, parentRegion } = loc;
|
|
808
|
+
this.navTo({
|
|
809
|
+
...loc,
|
|
810
|
+
start: clamp(start !== null && start !== void 0 ? start : 0, 0, parentRegion.end),
|
|
811
|
+
end: clamp(end !== null && end !== void 0 ? end : parentRegion.end, 0, parentRegion.end),
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
self.setDisplayedRegions(
|
|
816
|
+
// @ts-ignore
|
|
817
|
+
locations.map(r => (r.start === undefined ? r.parentRegion : r)));
|
|
818
|
+
self.showAllRegions();
|
|
819
|
+
}
|
|
820
|
+
},
|
|
821
|
+
/**
|
|
822
|
+
* Navigate to a location based on its refName and optionally start, end,
|
|
823
|
+
* and assemblyName. Can handle if there are multiple displayedRegions
|
|
824
|
+
* from same refName. Only navigates to a location if it is entirely
|
|
825
|
+
* within a displayedRegion. Navigates to the first matching location
|
|
826
|
+
* encountered.
|
|
827
|
+
*
|
|
828
|
+
* Throws an error if navigation was unsuccessful
|
|
829
|
+
*
|
|
830
|
+
* @param location - a proposed location to navigate to
|
|
831
|
+
*/
|
|
832
|
+
navTo(query) {
|
|
833
|
+
this.navToMultiple([query]);
|
|
834
|
+
},
|
|
835
|
+
navToMultiple(locations) {
|
|
836
|
+
const firstLocation = locations[0];
|
|
837
|
+
let { refName } = firstLocation;
|
|
838
|
+
const { start, end, assemblyName = self.assemblyNames[0], } = firstLocation;
|
|
839
|
+
if (start !== undefined && end !== undefined && start > end) {
|
|
840
|
+
throw new Error(`start "${start + 1}" is greater than end "${end}"`);
|
|
841
|
+
}
|
|
842
|
+
const session = getSession(self);
|
|
843
|
+
const { assemblyManager } = session;
|
|
844
|
+
const assembly = assemblyManager.get(assemblyName);
|
|
845
|
+
if (assembly) {
|
|
846
|
+
const canonicalRefName = assembly.getCanonicalRefName(refName);
|
|
847
|
+
if (canonicalRefName) {
|
|
848
|
+
refName = canonicalRefName;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
let s = start;
|
|
852
|
+
let e = end;
|
|
853
|
+
let refNameMatched = false;
|
|
854
|
+
const predicate = (r) => {
|
|
855
|
+
if (refName === r.refName) {
|
|
856
|
+
refNameMatched = true;
|
|
857
|
+
if (s === undefined) {
|
|
858
|
+
s = r.start;
|
|
859
|
+
}
|
|
860
|
+
if (e === undefined) {
|
|
861
|
+
e = r.end;
|
|
862
|
+
}
|
|
863
|
+
if (s >= r.start && s <= r.end && e <= r.end && e >= r.start) {
|
|
864
|
+
return true;
|
|
865
|
+
}
|
|
866
|
+
s = start;
|
|
867
|
+
e = end;
|
|
868
|
+
}
|
|
869
|
+
return false;
|
|
870
|
+
};
|
|
871
|
+
const lastIndex = findLastIndex(self.displayedRegions, predicate);
|
|
872
|
+
let index;
|
|
873
|
+
while (index !== lastIndex) {
|
|
874
|
+
try {
|
|
875
|
+
const previousIndex = index;
|
|
876
|
+
index = self.displayedRegions
|
|
877
|
+
.slice(previousIndex === undefined ? 0 : previousIndex + 1)
|
|
878
|
+
.findIndex(predicate);
|
|
879
|
+
if (previousIndex !== undefined) {
|
|
880
|
+
index += previousIndex + 1;
|
|
881
|
+
}
|
|
882
|
+
if (!refNameMatched) {
|
|
883
|
+
throw new Error(`could not find a region with refName "${refName}"`);
|
|
884
|
+
}
|
|
885
|
+
if (s === undefined) {
|
|
886
|
+
throw new Error(`could not find a region with refName "${refName}" that contained an end position ${e}`);
|
|
887
|
+
}
|
|
888
|
+
if (e === undefined) {
|
|
889
|
+
throw new Error(`could not find a region with refName "${refName}" that contained a start position ${s + 1}`);
|
|
890
|
+
}
|
|
891
|
+
if (index === -1) {
|
|
892
|
+
throw new Error(`could not find a region that completely contained "${assembleLocString(firstLocation)}"`);
|
|
893
|
+
}
|
|
894
|
+
if (locations.length === 1) {
|
|
895
|
+
const f = self.displayedRegions[index];
|
|
896
|
+
this.moveTo({ index, offset: f.reversed ? f.end - e : s - f.start }, { index, offset: f.reversed ? f.end - s : e - f.start });
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
let locationIndex = 0;
|
|
900
|
+
let locationStart = 0;
|
|
901
|
+
let locationEnd = 0;
|
|
902
|
+
for (locationIndex; locationIndex < locations.length; locationIndex++) {
|
|
903
|
+
const location = locations[locationIndex];
|
|
904
|
+
const region = self.displayedRegions[index + locationIndex];
|
|
905
|
+
locationStart = location.start || region.start;
|
|
906
|
+
locationEnd = location.end || region.end;
|
|
907
|
+
if (location.refName !== region.refName) {
|
|
908
|
+
throw new Error(`Entered location ${assembleLocString(location)} does not match with displayed regions`);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
locationIndex -= 1;
|
|
912
|
+
const startDisplayedRegion = self.displayedRegions[index];
|
|
913
|
+
const endDisplayedRegion = self.displayedRegions[index + locationIndex];
|
|
914
|
+
this.moveTo({
|
|
915
|
+
index,
|
|
916
|
+
offset: startDisplayedRegion.reversed
|
|
917
|
+
? startDisplayedRegion.end - e
|
|
918
|
+
: s - startDisplayedRegion.start,
|
|
919
|
+
}, {
|
|
920
|
+
index: index + locationIndex,
|
|
921
|
+
offset: endDisplayedRegion.reversed
|
|
922
|
+
? endDisplayedRegion.end - locationStart
|
|
923
|
+
: locationEnd - endDisplayedRegion.start,
|
|
924
|
+
});
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
catch (error) {
|
|
928
|
+
if (index === lastIndex) {
|
|
929
|
+
throw error;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
},
|
|
1097
934
|
}))
|
|
1098
935
|
.views(self => ({
|
|
1099
936
|
rubberBandMenuItems() {
|
|
@@ -1103,20 +940,44 @@ export function stateModelFactory(pluginManager) {
|
|
|
1103
940
|
icon: ZoomInIcon,
|
|
1104
941
|
onClick: () => {
|
|
1105
942
|
const { leftOffset, rightOffset } = self;
|
|
1106
|
-
|
|
1107
|
-
self.moveTo(leftOffset, rightOffset);
|
|
1108
|
-
}
|
|
943
|
+
self.moveTo(leftOffset, rightOffset);
|
|
1109
944
|
},
|
|
1110
945
|
},
|
|
1111
946
|
{
|
|
1112
947
|
label: 'Get sequence',
|
|
1113
948
|
icon: MenuOpenIcon,
|
|
1114
|
-
onClick: () =>
|
|
1115
|
-
self.setSequenceDialogOpen(true);
|
|
1116
|
-
},
|
|
949
|
+
onClick: () => self.setGetSequenceDialogOpen(true),
|
|
1117
950
|
},
|
|
1118
951
|
];
|
|
1119
952
|
},
|
|
953
|
+
bpToPx({ refName, coord, regionNumber, }) {
|
|
954
|
+
return bpToPx({ refName, coord, regionNumber, self });
|
|
955
|
+
},
|
|
956
|
+
/**
|
|
957
|
+
* scrolls the view to center on the given bp. if that is not in any
|
|
958
|
+
* of the displayed regions, does nothing
|
|
959
|
+
* @param coord - basepair at which you want to center the view
|
|
960
|
+
* @param refName - refName of the displayedRegion you are centering at
|
|
961
|
+
* @param regionNumber - index of the displayedRegion
|
|
962
|
+
*/
|
|
963
|
+
centerAt(coord, refName, regionNumber) {
|
|
964
|
+
const centerPx = this.bpToPx({
|
|
965
|
+
refName,
|
|
966
|
+
coord,
|
|
967
|
+
regionNumber,
|
|
968
|
+
});
|
|
969
|
+
if (centerPx) {
|
|
970
|
+
self.scrollTo(Math.round(centerPx.offsetPx - self.width / 2));
|
|
971
|
+
}
|
|
972
|
+
},
|
|
973
|
+
pxToBp(px) {
|
|
974
|
+
return pxToBp(self, px);
|
|
975
|
+
},
|
|
976
|
+
get centerLineInfo() {
|
|
977
|
+
return self.displayedRegions.length
|
|
978
|
+
? this.pxToBp(self.width / 2)
|
|
979
|
+
: undefined;
|
|
980
|
+
},
|
|
1120
981
|
}));
|
|
1121
982
|
}
|
|
1122
983
|
export { renderToSvg, RefNameAutocomplete, SearchBox };
|