@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.
Files changed (144) hide show
  1. package/dist/BaseLinearDisplay/components/BaseLinearDisplay.js +28 -55
  2. package/dist/BaseLinearDisplay/components/BaseLinearDisplay.js.map +1 -1
  3. package/dist/BaseLinearDisplay/components/Block.js +17 -28
  4. package/dist/BaseLinearDisplay/components/Block.js.map +1 -1
  5. package/dist/BaseLinearDisplay/components/LinearBlocks.js +19 -21
  6. package/dist/BaseLinearDisplay/components/LinearBlocks.js.map +1 -1
  7. package/dist/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js +28 -48
  8. package/dist/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js.map +1 -1
  9. package/dist/BaseLinearDisplay/components/Tooltip.js +29 -58
  10. package/dist/BaseLinearDisplay/components/Tooltip.js.map +1 -1
  11. package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.js +242 -363
  12. package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.js.map +1 -1
  13. package/dist/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js +1 -1
  14. package/dist/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js.map +1 -1
  15. package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.js +77 -129
  16. package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.js.map +1 -1
  17. package/dist/LinearBareDisplay/configSchema.js +2 -2
  18. package/dist/LinearBareDisplay/configSchema.js.map +1 -1
  19. package/dist/LinearBareDisplay/model.js +13 -19
  20. package/dist/LinearBareDisplay/model.js.map +1 -1
  21. package/dist/LinearBasicDisplay/components/SetMaxHeight.js +14 -31
  22. package/dist/LinearBasicDisplay/components/SetMaxHeight.js.map +1 -1
  23. package/dist/LinearBasicDisplay/configSchema.js +3 -3
  24. package/dist/LinearBasicDisplay/configSchema.js.map +1 -1
  25. package/dist/LinearBasicDisplay/model.js +119 -147
  26. package/dist/LinearBasicDisplay/model.js.map +1 -1
  27. package/dist/LinearGenomeView/components/CenterLine.js +11 -12
  28. package/dist/LinearGenomeView/components/CenterLine.js.map +1 -1
  29. package/dist/LinearGenomeView/components/ExportSvgDialog.js +30 -96
  30. package/dist/LinearGenomeView/components/ExportSvgDialog.js.map +1 -1
  31. package/dist/LinearGenomeView/components/GetSequenceDialog.d.ts +9 -0
  32. package/dist/LinearGenomeView/components/GetSequenceDialog.js +172 -0
  33. package/dist/LinearGenomeView/components/GetSequenceDialog.js.map +1 -0
  34. package/dist/LinearGenomeView/components/{VerticalGuides.d.ts → Gridlines.d.ts} +0 -0
  35. package/dist/LinearGenomeView/components/{VerticalGuides.js → Gridlines.js} +19 -21
  36. package/dist/LinearGenomeView/components/Gridlines.js.map +1 -0
  37. package/dist/LinearGenomeView/components/Header.js +26 -31
  38. package/dist/LinearGenomeView/components/Header.js.map +1 -1
  39. package/dist/LinearGenomeView/components/HelpDialog.js +10 -11
  40. package/dist/LinearGenomeView/components/HelpDialog.js.map +1 -1
  41. package/dist/LinearGenomeView/components/ImportForm.js +91 -154
  42. package/dist/LinearGenomeView/components/ImportForm.js.map +1 -1
  43. package/dist/LinearGenomeView/components/LinearGenomeView.js +20 -25
  44. package/dist/LinearGenomeView/components/LinearGenomeView.js.map +1 -1
  45. package/dist/LinearGenomeView/components/LinearGenomeViewSvg.js +86 -157
  46. package/dist/LinearGenomeView/components/LinearGenomeViewSvg.js.map +1 -1
  47. package/dist/LinearGenomeView/components/MiniControls.js +16 -32
  48. package/dist/LinearGenomeView/components/MiniControls.js.map +1 -1
  49. package/dist/LinearGenomeView/components/OverviewRubberBand.d.ts +0 -11
  50. package/dist/LinearGenomeView/components/OverviewRubberBand.js +55 -84
  51. package/dist/LinearGenomeView/components/OverviewRubberBand.js.map +1 -1
  52. package/dist/LinearGenomeView/components/OverviewScaleBar.js +94 -117
  53. package/dist/LinearGenomeView/components/OverviewScaleBar.js.map +1 -1
  54. package/dist/LinearGenomeView/components/RefNameAutocomplete.js +90 -172
  55. package/dist/LinearGenomeView/components/RefNameAutocomplete.js.map +1 -1
  56. package/dist/LinearGenomeView/components/RubberBand.js +51 -72
  57. package/dist/LinearGenomeView/components/RubberBand.js.map +1 -1
  58. package/dist/LinearGenomeView/components/Ruler.js +17 -18
  59. package/dist/LinearGenomeView/components/Ruler.js.map +1 -1
  60. package/dist/LinearGenomeView/components/ScaleBar.js +37 -58
  61. package/dist/LinearGenomeView/components/ScaleBar.js.map +1 -1
  62. package/dist/LinearGenomeView/components/SearchBox.js +69 -133
  63. package/dist/LinearGenomeView/components/SearchBox.js.map +1 -1
  64. package/dist/LinearGenomeView/components/SearchResultsDialog.js +32 -33
  65. package/dist/LinearGenomeView/components/SearchResultsDialog.js.map +1 -1
  66. package/dist/LinearGenomeView/components/{SequenceDialog.d.ts → SequenceSearchDialog.d.ts} +0 -0
  67. package/dist/LinearGenomeView/components/SequenceSearchDialog.js +104 -0
  68. package/dist/LinearGenomeView/components/SequenceSearchDialog.js.map +1 -0
  69. package/dist/LinearGenomeView/components/TrackContainer.d.ts +2 -1
  70. package/dist/LinearGenomeView/components/TrackContainer.js +36 -43
  71. package/dist/LinearGenomeView/components/TrackContainer.js.map +1 -1
  72. package/dist/LinearGenomeView/components/TrackLabel.js +50 -85
  73. package/dist/LinearGenomeView/components/TrackLabel.js.map +1 -1
  74. package/dist/LinearGenomeView/components/TracksContainer.js +33 -50
  75. package/dist/LinearGenomeView/components/TracksContainer.js.map +1 -1
  76. package/dist/LinearGenomeView/components/ZoomControls.js +15 -32
  77. package/dist/LinearGenomeView/components/ZoomControls.js.map +1 -1
  78. package/dist/LinearGenomeView/components/util.js +14 -87
  79. package/dist/LinearGenomeView/components/util.js.map +1 -1
  80. package/dist/LinearGenomeView/index.d.ts +66 -79
  81. package/dist/LinearGenomeView/index.js +534 -710
  82. package/dist/LinearGenomeView/index.js.map +1 -1
  83. package/dist/LinearGenomeView/util.js +17 -36
  84. package/dist/LinearGenomeView/util.js.map +1 -1
  85. package/dist/index.js +75 -146
  86. package/dist/index.js.map +1 -1
  87. package/esm/BaseLinearDisplay/components/BaseLinearDisplay.js +1 -1
  88. package/esm/BaseLinearDisplay/components/BaseLinearDisplay.js.map +1 -1
  89. package/esm/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js +1 -0
  90. package/esm/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js.map +1 -1
  91. package/esm/LinearGenomeView/components/GetSequenceDialog.d.ts +9 -0
  92. package/esm/LinearGenomeView/components/{SequenceDialog.js → GetSequenceDialog.js} +5 -8
  93. package/esm/LinearGenomeView/components/GetSequenceDialog.js.map +1 -0
  94. package/esm/LinearGenomeView/components/{VerticalGuides.d.ts → Gridlines.d.ts} +0 -0
  95. package/esm/LinearGenomeView/components/{VerticalGuides.js → Gridlines.js} +1 -1
  96. package/esm/LinearGenomeView/components/Gridlines.js.map +1 -0
  97. package/esm/LinearGenomeView/components/ImportForm.js +1 -0
  98. package/esm/LinearGenomeView/components/ImportForm.js.map +1 -1
  99. package/esm/LinearGenomeView/components/LinearGenomeView.js +3 -7
  100. package/esm/LinearGenomeView/components/LinearGenomeView.js.map +1 -1
  101. package/esm/LinearGenomeView/components/OverviewRubberBand.d.ts +0 -11
  102. package/esm/LinearGenomeView/components/OverviewRubberBand.js +16 -21
  103. package/esm/LinearGenomeView/components/OverviewRubberBand.js.map +1 -1
  104. package/esm/LinearGenomeView/components/OverviewScaleBar.js +0 -2
  105. package/esm/LinearGenomeView/components/OverviewScaleBar.js.map +1 -1
  106. package/esm/LinearGenomeView/components/RubberBand.js +0 -2
  107. package/esm/LinearGenomeView/components/RubberBand.js.map +1 -1
  108. package/esm/LinearGenomeView/components/SearchBox.js.map +1 -1
  109. package/esm/LinearGenomeView/components/{SequenceDialog.d.ts → SequenceSearchDialog.d.ts} +0 -0
  110. package/esm/LinearGenomeView/components/SequenceSearchDialog.js +76 -0
  111. package/esm/LinearGenomeView/components/SequenceSearchDialog.js.map +1 -0
  112. package/esm/LinearGenomeView/components/TrackContainer.d.ts +2 -1
  113. package/esm/LinearGenomeView/components/TrackContainer.js +15 -20
  114. package/esm/LinearGenomeView/components/TrackContainer.js.map +1 -1
  115. package/esm/LinearGenomeView/components/TrackLabel.js +22 -23
  116. package/esm/LinearGenomeView/components/TrackLabel.js.map +1 -1
  117. package/esm/LinearGenomeView/components/TracksContainer.js +2 -2
  118. package/esm/LinearGenomeView/components/TracksContainer.js.map +1 -1
  119. package/esm/LinearGenomeView/index.d.ts +66 -79
  120. package/esm/LinearGenomeView/index.js +268 -407
  121. package/esm/LinearGenomeView/index.js.map +1 -1
  122. package/package.json +3 -4
  123. package/src/BaseLinearDisplay/components/BaseLinearDisplay.tsx +0 -1
  124. package/src/BaseLinearDisplay/components/ServerSideRenderedBlockContent.tsx +2 -1
  125. package/src/LinearGenomeView/components/{SequenceDialog.tsx → GetSequenceDialog.tsx} +5 -18
  126. package/src/LinearGenomeView/components/{VerticalGuides.tsx → Gridlines.tsx} +0 -0
  127. package/src/LinearGenomeView/components/ImportForm.tsx +1 -0
  128. package/src/LinearGenomeView/components/LinearGenomeView.tsx +4 -8
  129. package/src/LinearGenomeView/components/OverviewRubberBand.tsx +19 -34
  130. package/src/LinearGenomeView/components/OverviewScaleBar.tsx +0 -2
  131. package/src/LinearGenomeView/components/RubberBand.tsx +0 -2
  132. package/src/LinearGenomeView/components/SearchBox.tsx +1 -1
  133. package/src/LinearGenomeView/components/SequenceSearchDialog.tsx +165 -0
  134. package/src/LinearGenomeView/components/TrackContainer.tsx +18 -28
  135. package/src/LinearGenomeView/components/TrackLabel.tsx +60 -53
  136. package/src/LinearGenomeView/components/TracksContainer.tsx +2 -2
  137. package/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.js.snap +14 -17
  138. package/src/LinearGenomeView/index.test.ts +14 -36
  139. package/src/LinearGenomeView/index.tsx +389 -541
  140. package/dist/LinearGenomeView/components/SequenceDialog.js +0 -242
  141. package/dist/LinearGenomeView/components/SequenceDialog.js.map +0 -1
  142. package/dist/LinearGenomeView/components/VerticalGuides.js.map +0 -1
  143. package/esm/LinearGenomeView/components/SequenceDialog.js.map +0 -1
  144. 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, viewBpToPx, } from '@jbrowse/core/util';
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 clone from 'clone';
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
- setSequenceDialogOpen(open) {
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.zoomToDisplayedRegions(leftOffset, rightOffset);
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
- self.scrollTo(Math.round(centerBp / self.bpPerPx - self.width / 2));
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
- if (leftOffset && rightOffset) {
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 };