@jbrowse/plugin-linear-genome-view 5.0.0-beta.8 → 5.0.0-beta.9
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 +140 -48
- package/esm/LinearGenomeView/afterAttach.d.ts +5 -5
- package/esm/LinearGenomeView/afterAttach.js +18 -24
- package/esm/LinearGenomeView/closeUps.d.ts +83 -0
- package/esm/LinearGenomeView/closeUps.js +211 -0
- package/esm/LinearGenomeView/components/AddCloseUpDialog.d.ts +19 -0
- package/esm/LinearGenomeView/components/AddCloseUpDialog.js +29 -0
- package/esm/LinearGenomeView/components/CloseUps.d.ts +5 -0
- package/esm/LinearGenomeView/components/CloseUps.js +75 -0
- package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +2 -1
- package/esm/LinearGenomeView/components/ExportSvgDialog.js +6 -2
- package/esm/LinearGenomeView/components/GetSequenceDialog.js +2 -2
- package/esm/LinearGenomeView/components/Header.js +6 -1
- package/esm/LinearGenomeView/components/HeaderZoomControls.js +4 -34
- package/esm/LinearGenomeView/components/Highlight.js +2 -4
- package/esm/LinearGenomeView/components/HighlightBand.js +2 -2
- package/esm/LinearGenomeView/components/HighlightChip.d.ts +1 -2
- package/esm/LinearGenomeView/components/HighlightChip.js +5 -7
- package/esm/LinearGenomeView/components/ImportForm.js +2 -2
- package/esm/LinearGenomeView/components/LinearGenomeView.js +3 -3
- package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +3 -2
- package/esm/LinearGenomeView/components/OverviewHighlight.js +1 -1
- package/esm/LinearGenomeView/components/OverviewHighlightBand.js +2 -3
- package/esm/LinearGenomeView/components/OverviewScalebarPolygon.d.ts +7 -1
- package/esm/LinearGenomeView/components/OverviewScalebarPolygon.js +22 -7
- package/esm/LinearGenomeView/components/SVGHighlightBand.js +2 -3
- package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +6 -21
- package/esm/LinearGenomeView/components/SearchBox.js +2 -2
- package/esm/LinearGenomeView/components/TrackContainer.js +12 -5
- package/esm/LinearGenomeView/components/TrackLabel.js +1 -1
- package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +3 -1
- package/esm/LinearGenomeView/components/TrackRenderingContainer.js +3 -7
- package/esm/LinearGenomeView/components/TracksContainer.js +7 -15
- package/esm/LinearGenomeView/components/fetchSequence.d.ts +1 -2
- package/esm/LinearGenomeView/components/useSideScroll.js +1 -1
- package/esm/LinearGenomeView/consts.d.ts +2 -0
- package/esm/LinearGenomeView/consts.js +5 -0
- package/esm/LinearGenomeView/headerFit.d.ts +2 -1
- package/esm/LinearGenomeView/headerFit.js +7 -2
- package/esm/LinearGenomeView/index.d.ts +1 -1
- package/esm/LinearGenomeView/keyboardHandler.d.ts +2 -0
- package/esm/LinearGenomeView/keyboardHandler.js +11 -0
- package/esm/LinearGenomeView/launchKeys.d.ts +1 -1
- package/esm/LinearGenomeView/launchKeys.js +15 -5
- package/esm/LinearGenomeView/lazyDialogs.d.ts +7 -0
- package/esm/LinearGenomeView/lazyDialogs.js +2 -0
- package/esm/LinearGenomeView/menuItems.d.ts +28 -16
- package/esm/LinearGenomeView/menuItems.js +213 -85
- package/esm/LinearGenomeView/model.d.ts +367 -199
- package/esm/LinearGenomeView/model.js +302 -143
- package/esm/LinearGenomeView/sharedScaleContainer.d.ts +1 -1
- package/esm/LinearGenomeView/showRegionsWithUndo.js +7 -1
- package/esm/LinearGenomeView/svgcomponents/SVGCloseUpFrame.d.ts +5 -0
- package/esm/LinearGenomeView/svgcomponents/SVGCloseUpFrame.js +8 -0
- package/esm/LinearGenomeView/svgcomponents/SVGHighlights.js +3 -4
- package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.js +3 -3
- package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +95 -24
- package/esm/LinearGenomeView/svgcomponents/SVGRuler.js +4 -5
- package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +13 -4
- package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +4 -4
- package/esm/LinearGenomeView/svgcomponents/renderViewTracks.js +1 -1
- package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +52 -63
- package/esm/LinearGenomeView/svgcomponents/util.d.ts +8 -6
- package/esm/LinearGenomeView/svgcomponents/util.js +37 -16
- package/esm/LinearGenomeView/types.d.ts +5 -4
- package/esm/LinearGenomeView/util.d.ts +20 -37
- package/esm/LinearGenomeView/util.js +58 -141
- package/esm/displayKitTests/perRegionTestEnv.d.ts +64 -45
- package/esm/displayKitTests/perRegionTestEnv.js +6 -6
- package/esm/index.d.ts +9 -67
- package/esm/index.js +6 -22
- package/esm/searchUtils.d.ts +2 -3
- package/esm/searchUtils.js +2 -2
- package/package.json +7 -7
- package/esm/lazyPluginExports.d.ts +0 -23
- package/esm/lazyPluginExports.js +0 -36
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { lazy } from 'react';
|
|
2
2
|
import { getConf } from '@jbrowse/core/configuration';
|
|
3
|
-
import { BaseViewModel
|
|
3
|
+
import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes/models';
|
|
4
|
+
import { exportViewSvg } from '@jbrowse/core/svg/exportViewSvg';
|
|
4
5
|
import { VIEW_HEADER_HEIGHT } from '@jbrowse/core/ui';
|
|
5
|
-
import { assembleLocString, clamp, getBpDisplayStr, getDialogHost, getSession, getTickDisplayStr, isSessionModelWithWidgets, localStorageGetBoolean, localStorageGetItem, springAnimate, sum, } from '@jbrowse/core/util';
|
|
6
|
+
import { assembleLocString, clamp, getBpDisplayStr, getDialogHost, getSession, getTickDisplayStr, isSessionModelWithWidgets, localStorageGetBoolean, localStorageGetItem, scheduleDetachedDestroy, springAnimate, sum, } from '@jbrowse/core/util';
|
|
6
7
|
import { bpToLinearBp, bpToPx, computeMoveToLayout, createOverviewLayout, getLayoutHighlightCoords, getOverviewRegionPxSpan, moveTo, pxToBp, } from '@jbrowse/core/util/Base1DUtils';
|
|
7
8
|
import { wholeBaseRegions } from '@jbrowse/core/util/blockTypes';
|
|
8
9
|
import calculateDynamicBlocks from '@jbrowse/core/util/calculateDynamicBlocks';
|
|
@@ -10,15 +11,17 @@ import calculateStaticBlocks from '@jbrowse/core/util/calculateStaticBlocks';
|
|
|
10
11
|
import { tickLabelsWorthDrawing } from '@jbrowse/core/util/tickLabels';
|
|
11
12
|
import { hideTrackGeneric, launchToggleTrackGeneric, launchTrackGeneric, showTrackGeneric, toggleTrackGeneric, } from '@jbrowse/core/util/tracks';
|
|
12
13
|
import { ElementId } from '@jbrowse/core/util/types/mst';
|
|
13
|
-
import {
|
|
14
|
+
import { highlightsOnAssemblies } from '@jbrowse/core/util/viewHighlights';
|
|
15
|
+
import { assemblyErrorMessage, computeViewStatus, viewLoading, } from '@jbrowse/core/util/viewStatus';
|
|
14
16
|
import { pendingLaunch, withLaunchInput, } from '@jbrowse/core/util/withLaunchInput';
|
|
15
17
|
import { contentRightEdgePx } from '@jbrowse/display-kit/regionHost';
|
|
16
|
-
import { cast, getParent, hasParent, isAlive, types, } from '@jbrowse/mobx-state-tree';
|
|
18
|
+
import { cast, detach, getParent, hasParent, isAlive, types, } from '@jbrowse/mobx-state-tree';
|
|
17
19
|
import { observable, when } from 'mobx';
|
|
18
20
|
import { handleSelectedRegion } from '../searchUtils.js';
|
|
19
21
|
import { doAfterAttach } from './afterAttach.js';
|
|
22
|
+
import { closeUpHost, closeUpType } from './closeUps.js';
|
|
20
23
|
import { shouldSwapTracks } from './components/util.js';
|
|
21
|
-
import { HEADER_BAR_HEIGHT, HEADER_OVERVIEW_HEIGHT, MIN_BP_PER_PX, MINIMIZED_TRACK_HEIGHT, RESIZE_HANDLE_HEIGHT, SCALE_BAR_HEIGHT, SHOW_ALL_REGIONS_FILL, TRACK_OUTLINE_BORDER, TRACK_TOP_GAP, } from './consts.js';
|
|
24
|
+
import { CLOSE_UP_CONNECTOR_HEIGHT, HEADER_BAR_HEIGHT, HEADER_OVERVIEW_HEIGHT, MIN_BP_PER_PX, MIN_CLOSE_UP_CONNECTOR_HEIGHT, MINIMIZED_TRACK_HEIGHT, RESIZE_HANDLE_HEIGHT, SCALE_BAR_HEIGHT, SHOW_ALL_REGIONS_FILL, TRACK_OUTLINE_BORDER, TRACK_TOP_GAP, } from './consts.js';
|
|
22
25
|
import { planFlight } from './flyTo.js';
|
|
23
26
|
import { setupKeyboardHandler } from './keyboardHandler.js';
|
|
24
27
|
import { lgvLaunchKeys } from './launchKeys.js';
|
|
@@ -167,9 +170,8 @@ function isTrackSelectorVisible(self) {
|
|
|
167
170
|
* ```
|
|
168
171
|
* `assembly`, `loc`, `tracks`, `tracklist`, `nav` and `highlight` need
|
|
169
172
|
* on-attach resolution and are captured into the `launch` property below;
|
|
170
|
-
* `colorByCDS`, `showAminoAcids`, `showCenterLine
|
|
171
|
-
*
|
|
172
|
-
* written the same way.
|
|
173
|
+
* `colorByCDS`, `showAminoAcids`, `showCenterLine` and `trackLabels` are plain
|
|
174
|
+
* view props MST restores natively. Both are written the same way.
|
|
173
175
|
* At runtime the same model is driven imperatively — every property and action
|
|
174
176
|
* below is reachable on `viewState.session.views[0]`:
|
|
175
177
|
* ```js
|
|
@@ -181,7 +183,7 @@ function isTrackSelectorVisible(self) {
|
|
|
181
183
|
*/
|
|
182
184
|
export function stateModelFactory(pluginManager) {
|
|
183
185
|
const model = types
|
|
184
|
-
.compose('LinearGenomeView', BaseViewModel,
|
|
186
|
+
.compose('LinearGenomeView', BaseViewModel, types.model({
|
|
185
187
|
/**
|
|
186
188
|
* #property
|
|
187
189
|
*/
|
|
@@ -256,6 +258,23 @@ export function stateModelFactory(pluginManager) {
|
|
|
256
258
|
* keep the header, drop the whole-chromosome overview strip below it
|
|
257
259
|
*/
|
|
258
260
|
hideHeaderOverview: types.stripDefault(types.boolean, false),
|
|
261
|
+
/**
|
|
262
|
+
* #property
|
|
263
|
+
* Closer views of the same locus stacked under the tracks, widest
|
|
264
|
+
* first, so the page zooms in as it reads down. Each is a
|
|
265
|
+
* LinearGenomeView with tracks of its own whose regions, width and
|
|
266
|
+
* centre this view drives; its window width is the one thing it keeps.
|
|
267
|
+
* See `closeUps.ts`.
|
|
268
|
+
*/
|
|
269
|
+
closeUps: types.stripDefault(types.array(closeUpType(pluginManager)), []),
|
|
270
|
+
/**
|
|
271
|
+
* #property
|
|
272
|
+
* Height of the bands the trapezoids between this view's close-ups
|
|
273
|
+
* are drawn in, dragged by any one of them. One number for the stack:
|
|
274
|
+
* the bands are a ladder the eye reads down, and a rung of its own
|
|
275
|
+
* height reads as a difference in the data rather than in the drawing.
|
|
276
|
+
*/
|
|
277
|
+
closeUpConnectorHeight: types.stripDefault(types.number, CLOSE_UP_CONNECTOR_HEIGHT),
|
|
259
278
|
/**
|
|
260
279
|
* #property
|
|
261
280
|
* suppress the "No tracks active" placeholder, for an embed that opens
|
|
@@ -287,7 +306,7 @@ export function stateModelFactory(pluginManager) {
|
|
|
287
306
|
* "hidden", or empty string "" (which results in the
|
|
288
307
|
* LinearGenomeViewPlugin config default being used). the resolved value
|
|
289
308
|
* is the `effectiveTrackLabels` getter. see LinearGenomeViewPlugin
|
|
290
|
-
* https://jbrowse.org/jb2/docs/config/
|
|
309
|
+
* https://jbrowse.org/jb2/docs/config/lineargenomeviewconfigschema/ docs for
|
|
291
310
|
* how conf is used
|
|
292
311
|
*/
|
|
293
312
|
trackLabels: types.optional(types.string, () => localStorageGetItem('lgv-trackLabels') ?? ''),
|
|
@@ -298,7 +317,7 @@ export function stateModelFactory(pluginManager) {
|
|
|
298
317
|
showGridlines: types.stripDefault(types.boolean, true),
|
|
299
318
|
/**
|
|
300
319
|
* #property
|
|
301
|
-
* controls whether highlight
|
|
320
|
+
* controls whether highlight chip labels are shown inline
|
|
302
321
|
*/
|
|
303
322
|
labelsVisible: types.stripDefault(types.boolean, true),
|
|
304
323
|
/**
|
|
@@ -505,6 +524,14 @@ export function stateModelFactory(pluginManager) {
|
|
|
505
524
|
const { assemblyManager } = getSession(self);
|
|
506
525
|
return this.assemblyNames.map(a => assemblyManager.getDisplayName(a));
|
|
507
526
|
},
|
|
527
|
+
/**
|
|
528
|
+
* #getter
|
|
529
|
+
* the session's highlights on this view's assemblies
|
|
530
|
+
*/
|
|
531
|
+
get highlights() {
|
|
532
|
+
const { highlights, assemblyManager } = getSession(self);
|
|
533
|
+
return highlightsOnAssemblies(highlights, this.assemblyNames, assemblyManager);
|
|
534
|
+
},
|
|
508
535
|
/**
|
|
509
536
|
* #getter
|
|
510
537
|
* checking if lgv is a 'top-level' view is used for toggling pin track
|
|
@@ -539,8 +566,38 @@ export function stateModelFactory(pluginManager) {
|
|
|
539
566
|
},
|
|
540
567
|
/**
|
|
541
568
|
* #getter
|
|
542
|
-
*
|
|
543
|
-
*
|
|
569
|
+
* The census entry for this view's close-ups, which are views in
|
|
570
|
+
* their own right.
|
|
571
|
+
*/
|
|
572
|
+
get ownViews() {
|
|
573
|
+
return [...self.closeUps];
|
|
574
|
+
},
|
|
575
|
+
/**
|
|
576
|
+
* #getter
|
|
577
|
+
* The close-ups as what the host reads off them. The array element
|
|
578
|
+
* is a late type back onto this view, so this is where it gets a shape.
|
|
579
|
+
*/
|
|
580
|
+
get closeUpViews() {
|
|
581
|
+
return self.closeUps;
|
|
582
|
+
},
|
|
583
|
+
/**
|
|
584
|
+
* #getter
|
|
585
|
+
* Whether this view is itself a close-up of another
|
|
586
|
+
*/
|
|
587
|
+
get isCloseUp() {
|
|
588
|
+
return !!closeUpHost(self);
|
|
589
|
+
},
|
|
590
|
+
/**
|
|
591
|
+
* #getter
|
|
592
|
+
* See `BaseViewModel.rendersDisplays`.
|
|
593
|
+
*/
|
|
594
|
+
get rendersDisplays() {
|
|
595
|
+
return !self.scalebarOnly;
|
|
596
|
+
},
|
|
597
|
+
/**
|
|
598
|
+
* #getter
|
|
599
|
+
* Assembly name captioning the scalebar ahead of its refName labels, or
|
|
600
|
+
* undefined for none. A container view (e.g. LinearSyntenyView) opts its sub-views in by
|
|
544
601
|
* exposing showAssemblyNameInSubviewScalebar; duck-typed rather than
|
|
545
602
|
* matching a concrete view type so no upward plugin dependency is needed
|
|
546
603
|
* and any container can opt in. A wrong nesting depth simply yields no
|
|
@@ -587,10 +644,23 @@ export function stateModelFactory(pluginManager) {
|
|
|
587
644
|
* and error checks resolve it directly through here.
|
|
588
645
|
*/
|
|
589
646
|
get initAssembly() {
|
|
590
|
-
|
|
591
|
-
|
|
647
|
+
const name = this.launchAssemblyName;
|
|
648
|
+
return self.pendingLaunch && name !== undefined
|
|
649
|
+
? getSession(self).assemblyManager.get(name)
|
|
592
650
|
: undefined;
|
|
593
651
|
},
|
|
652
|
+
/**
|
|
653
|
+
* #getter
|
|
654
|
+
* The assembly a pending launch is for: the one it names, else the one
|
|
655
|
+
* the view already shows. A re-launch of a materialized view (a
|
|
656
|
+
* `jb.setSession` document writing `loc` beside the built state) need
|
|
657
|
+
* not restate it; without the default the readiness gate waited on
|
|
658
|
+
* `assemblyManager.get(undefined)` for good. Undefined on a fresh view
|
|
659
|
+
* whose blob names none, which `error` reports.
|
|
660
|
+
*/
|
|
661
|
+
get launchAssemblyName() {
|
|
662
|
+
return self.pendingLaunch?.assembly ?? self.assemblyNames[0];
|
|
663
|
+
},
|
|
594
664
|
/**
|
|
595
665
|
* #getter
|
|
596
666
|
*/
|
|
@@ -624,35 +694,11 @@ export function stateModelFactory(pluginManager) {
|
|
|
624
694
|
},
|
|
625
695
|
/**
|
|
626
696
|
* #getter
|
|
627
|
-
* What the
|
|
628
|
-
*
|
|
629
|
-
* "Loading" for the part of startup that actually takes time. Falls back
|
|
630
|
-
* once the assembly is loaded and the remaining wait is the init autorun's
|
|
631
|
-
* own navigation, which is local.
|
|
632
|
-
*/
|
|
633
|
-
get loadingMessage() {
|
|
634
|
-
return this.showLoading
|
|
635
|
-
? this.loadingAssembly?.statusMessage || 'Loading'
|
|
636
|
-
: undefined;
|
|
637
|
-
},
|
|
638
|
-
/**
|
|
639
|
-
* #getter
|
|
640
|
-
* Determinate fraction for the spinner's bar, when the assembly load
|
|
641
|
-
* reports one (a whole-file download with a Content-Length)
|
|
697
|
+
* What the loading screen says while `showLoading`, read off the
|
|
698
|
+
* assembly whose load is the wait; undefined otherwise.
|
|
642
699
|
*/
|
|
643
|
-
get
|
|
644
|
-
return this.showLoading
|
|
645
|
-
? this.loadingAssembly?.statusProgress
|
|
646
|
-
: undefined;
|
|
647
|
-
},
|
|
648
|
-
/**
|
|
649
|
-
* #getter
|
|
650
|
-
* The URL the assembly load is currently fetching, when the phase named
|
|
651
|
-
* one. Only the stalled-load notice reads it — see
|
|
652
|
-
* {@link ViewLoadingScreen}.
|
|
653
|
-
*/
|
|
654
|
-
get loadingSource() {
|
|
655
|
-
return this.showLoading ? this.loadingAssembly?.statusSource : undefined;
|
|
700
|
+
get loading() {
|
|
701
|
+
return viewLoading(this.showLoading, () => this.loadingAssembly);
|
|
656
702
|
},
|
|
657
703
|
/**
|
|
658
704
|
* #getter
|
|
@@ -731,12 +777,7 @@ export function stateModelFactory(pluginManager) {
|
|
|
731
777
|
return computeViewStatus({
|
|
732
778
|
error: this.error,
|
|
733
779
|
hasSomethingToShow: this.hasSomethingToShow,
|
|
734
|
-
loading: () => this.
|
|
735
|
-
? {
|
|
736
|
-
message: this.loadingMessage ?? 'Loading',
|
|
737
|
-
progress: this.loadingProgress,
|
|
738
|
-
}
|
|
739
|
-
: undefined,
|
|
780
|
+
loading: () => this.loading,
|
|
740
781
|
});
|
|
741
782
|
},
|
|
742
783
|
/**
|
|
@@ -791,15 +832,15 @@ export function stateModelFactory(pluginManager) {
|
|
|
791
832
|
/**
|
|
792
833
|
* #getter
|
|
793
834
|
* Where the scalebar pins when the view's chrome is sticky: everything
|
|
794
|
-
* stacked above it, which is the view's own title bar
|
|
795
|
-
* header. Expressed from `headerHeight`
|
|
796
|
-
* constants — the two are the same box, and a
|
|
797
|
-
* is free to drift from the first while both
|
|
835
|
+
* stacked above it, which is the view's own title bar, where the session
|
|
836
|
+
* draws one, plus this view's header. Expressed from `headerHeight`
|
|
837
|
+
* rather than re-summing its constants — the two are the same box, and a
|
|
838
|
+
* second spelling of the sum is free to drift from the first while both
|
|
839
|
+
* typecheck.
|
|
798
840
|
*/
|
|
799
841
|
get rubberbandTop() {
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
: 0;
|
|
842
|
+
const titleBar = getSession(self).viewTitleBars === false ? 0 : VIEW_HEADER_HEIGHT;
|
|
843
|
+
return self.stickyViewHeaders ? titleBar + this.headerHeight : 0;
|
|
803
844
|
},
|
|
804
845
|
/**
|
|
805
846
|
* #getter
|
|
@@ -906,16 +947,16 @@ export function stateModelFactory(pluginManager) {
|
|
|
906
947
|
},
|
|
907
948
|
/**
|
|
908
949
|
* #getter
|
|
909
|
-
* The zoom-out limit.
|
|
910
|
-
* it on a shared scale
|
|
911
|
-
* one, drawn short so the two compare by length.
|
|
950
|
+
* The zoom-out limit. Equal to the fit for this view's regions, except
|
|
951
|
+
* while a container holds it on a coarser shared scale, such as a small
|
|
952
|
+
* genome next to a large one, drawn short so the two compare by length.
|
|
912
953
|
*
|
|
913
|
-
*
|
|
914
|
-
*
|
|
915
|
-
*
|
|
916
|
-
*
|
|
917
|
-
*
|
|
918
|
-
*
|
|
954
|
+
* The shared scale raises the limit. A bpPerPx written past the limit
|
|
955
|
+
* would be undone by the first operation that clamps (a wheel tick, a
|
|
956
|
+
* rubberband, a `setDisplayedRegions`), as with the dotplot's locked
|
|
957
|
+
* aspect ratio turning "zoom out" into a zoom in (`axisMaxBpPerPx`).
|
|
958
|
+
* Every zoom clamps against the same ceiling, so full zoom-out stops at
|
|
959
|
+
* the shared scale.
|
|
919
960
|
*/
|
|
920
961
|
get maxBpPerPx() {
|
|
921
962
|
return Math.max(this.fitBpPerPx, this.sharedFitBpPerPx);
|
|
@@ -959,12 +1000,12 @@ export function stateModelFactory(pluginManager) {
|
|
|
959
1000
|
// hand-authored JSON is what fills it, so the type is not a guarantee.
|
|
960
1001
|
// Naming the authoring mistake beats the downstream symptom, which is
|
|
961
1002
|
// the literal string "Assembly undefined not found".
|
|
962
|
-
if (!
|
|
1003
|
+
if (!this.launchAssemblyName) {
|
|
963
1004
|
return 'LinearGenomeView init needs an "assembly"';
|
|
964
1005
|
}
|
|
965
1006
|
const asm = this.initAssembly;
|
|
966
1007
|
if (!asm) {
|
|
967
|
-
return `Assembly ${
|
|
1008
|
+
return `Assembly ${this.launchAssemblyName} not found`;
|
|
968
1009
|
}
|
|
969
1010
|
if (asm.error) {
|
|
970
1011
|
return asm.error;
|
|
@@ -1133,6 +1174,50 @@ export function stateModelFactory(pluginManager) {
|
|
|
1133
1174
|
setHideHeaderOverview(b) {
|
|
1134
1175
|
self.hideHeaderOverview = b;
|
|
1135
1176
|
},
|
|
1177
|
+
/**
|
|
1178
|
+
* #action
|
|
1179
|
+
* Add a close-up showing `trackIds`, spanning `windowWidthBp` bases or a
|
|
1180
|
+
* tenth of the closest close-up there is. The stack stays widest first,
|
|
1181
|
+
* so a span between two existing close-ups lands between them, and one
|
|
1182
|
+
* wider than this view is pulled back in to it by the sync.
|
|
1183
|
+
*
|
|
1184
|
+
* Answers the close-up, which is a LinearGenomeView: its own actions
|
|
1185
|
+
* navigate it, name its tracks and take it away again.
|
|
1186
|
+
*/
|
|
1187
|
+
addCloseUp({ windowWidthBp, trackIds = [], } = {}) {
|
|
1188
|
+
const closest = self.closeUpViews.at(-1);
|
|
1189
|
+
const width = windowWidthBp ?? (closest?.windowWidthBp ?? self.windowWidthBp) / 10;
|
|
1190
|
+
const centerBp = self.windowStartBp + self.windowWidthBp / 2;
|
|
1191
|
+
const at = self.closeUpViews.filter(closeUp => closeUp.windowWidthBp > width).length;
|
|
1192
|
+
self.closeUps.splice(at, 0, {
|
|
1193
|
+
type: 'LinearGenomeView',
|
|
1194
|
+
hideHeader: true,
|
|
1195
|
+
displayedRegions: self.displayedRegions,
|
|
1196
|
+
windowWidthBp: width,
|
|
1197
|
+
windowStartBp: centerBp - width / 2,
|
|
1198
|
+
});
|
|
1199
|
+
const closeUp = self.closeUpViews[at];
|
|
1200
|
+
for (const trackId of trackIds) {
|
|
1201
|
+
closeUp.showTrack(trackId);
|
|
1202
|
+
}
|
|
1203
|
+
return closeUp;
|
|
1204
|
+
},
|
|
1205
|
+
/**
|
|
1206
|
+
* #action
|
|
1207
|
+
*/
|
|
1208
|
+
removeCloseUp(closeUp) {
|
|
1209
|
+
detach(closeUp);
|
|
1210
|
+
scheduleDetachedDestroy(closeUp);
|
|
1211
|
+
},
|
|
1212
|
+
/**
|
|
1213
|
+
* #action
|
|
1214
|
+
* Set the height of every band between this view's close-ups.
|
|
1215
|
+
* Floored at the drag surface's own height rather than at 0: the band IS
|
|
1216
|
+
* the handle, so a band dragged shut could never be dragged open again.
|
|
1217
|
+
*/
|
|
1218
|
+
setCloseUpConnectorHeight(height) {
|
|
1219
|
+
self.closeUpConnectorHeight = Math.max(MIN_CLOSE_UP_CONNECTOR_HEIGHT, Math.round(height));
|
|
1220
|
+
},
|
|
1136
1221
|
/**
|
|
1137
1222
|
* #action
|
|
1138
1223
|
*/
|
|
@@ -1256,6 +1341,34 @@ export function stateModelFactory(pluginManager) {
|
|
|
1256
1341
|
},
|
|
1257
1342
|
}))
|
|
1258
1343
|
.actions(self => ({
|
|
1344
|
+
/**
|
|
1345
|
+
* #action
|
|
1346
|
+
* Open a close-up over a rubberband selection, showing this view's
|
|
1347
|
+
* tracks. The view recentres on the span first, because every close-up
|
|
1348
|
+
* shares its host's centre — a close-up is a closer look at the middle of
|
|
1349
|
+
* this view, and a selection off to one side would otherwise open
|
|
1350
|
+
* somewhere else.
|
|
1351
|
+
*
|
|
1352
|
+
* The span is `computeMoveToLayout`'s, the pure half of the `moveTo` the
|
|
1353
|
+
* same selection's "Zoom to region" runs, so the close-up covers exactly
|
|
1354
|
+
* what zooming would have navigated to.
|
|
1355
|
+
*/
|
|
1356
|
+
addCloseUpForSpan(start, end, { trackIds } = {}) {
|
|
1357
|
+
if (!start || !end) {
|
|
1358
|
+
return undefined;
|
|
1359
|
+
}
|
|
1360
|
+
const { bpPerPx, offsetPx } = computeMoveToLayout(self, start, end);
|
|
1361
|
+
const windowWidthBp = bpPerPx * self.width;
|
|
1362
|
+
const centerBp = offsetPx * bpPerPx + windowWidthBp / 2;
|
|
1363
|
+
self.scrollToBp(centerBp - self.windowWidthBp / 2);
|
|
1364
|
+
return self.addCloseUp({
|
|
1365
|
+
windowWidthBp,
|
|
1366
|
+
// the view's own tracks unless the caller named a list, so a close-up
|
|
1367
|
+
// is a closer look at what is on screen by default and an empty row
|
|
1368
|
+
// when someone asked for one
|
|
1369
|
+
trackIds: trackIds ?? self.tracks.map(track => track.configuration.trackId),
|
|
1370
|
+
});
|
|
1371
|
+
},
|
|
1259
1372
|
/**
|
|
1260
1373
|
* #action
|
|
1261
1374
|
* showTrack for a track whose display state model may be lazily
|
|
@@ -1264,12 +1377,26 @@ export function stateModelFactory(pluginManager) {
|
|
|
1264
1377
|
async launchTrack(trackId, initialSnapshot = {}, displayInitialSnapshot = {}, inlineConf) {
|
|
1265
1378
|
return launchTrackGeneric(self, trackId, initialSnapshot, displayInitialSnapshot, inlineConf);
|
|
1266
1379
|
},
|
|
1380
|
+
}))
|
|
1381
|
+
.views(self => ({
|
|
1382
|
+
/**
|
|
1383
|
+
* #method
|
|
1384
|
+
* The shown track a move action means: the menu passes a track model's
|
|
1385
|
+
* own `id`, an agent passes the `trackId` it knows the track by, and
|
|
1386
|
+
* both name one shown track. Answers the model id, or the argument
|
|
1387
|
+
* unchanged when nothing shown matches, so the move reports it.
|
|
1388
|
+
*/
|
|
1389
|
+
trackModelId(idOrTrackId) {
|
|
1390
|
+
return ((self.tracks.find(t => t.id === idOrTrackId) ??
|
|
1391
|
+
self.tracks.find(t => t.configuration.trackId === idOrTrackId))?.id ?? idOrTrackId);
|
|
1392
|
+
},
|
|
1267
1393
|
}))
|
|
1268
1394
|
.actions(self => ({
|
|
1269
1395
|
/**
|
|
1270
1396
|
* #action
|
|
1271
1397
|
*/
|
|
1272
|
-
moveTrackDown(
|
|
1398
|
+
moveTrackDown(idOrTrackId) {
|
|
1399
|
+
const id = self.trackModelId(idOrTrackId);
|
|
1273
1400
|
const section = self.trackSection(id);
|
|
1274
1401
|
const idx = section.findIndex(t => t.id === id);
|
|
1275
1402
|
if (idx !== -1 && idx < section.length - 1) {
|
|
@@ -1279,7 +1406,8 @@ export function stateModelFactory(pluginManager) {
|
|
|
1279
1406
|
/**
|
|
1280
1407
|
* #action
|
|
1281
1408
|
*/
|
|
1282
|
-
moveTrackUp(
|
|
1409
|
+
moveTrackUp(idOrTrackId) {
|
|
1410
|
+
const id = self.trackModelId(idOrTrackId);
|
|
1283
1411
|
const section = self.trackSection(id);
|
|
1284
1412
|
const idx = section.findIndex(t => t.id === id);
|
|
1285
1413
|
if (idx > 0) {
|
|
@@ -1289,7 +1417,8 @@ export function stateModelFactory(pluginManager) {
|
|
|
1289
1417
|
/**
|
|
1290
1418
|
* #action
|
|
1291
1419
|
*/
|
|
1292
|
-
moveTrackToTop(
|
|
1420
|
+
moveTrackToTop(idOrTrackId) {
|
|
1421
|
+
const id = self.trackModelId(idOrTrackId);
|
|
1293
1422
|
const section = self.trackSection(id);
|
|
1294
1423
|
if (section.length && section[0].id !== id) {
|
|
1295
1424
|
this.moveTrack(id, section[0].id);
|
|
@@ -1298,7 +1427,8 @@ export function stateModelFactory(pluginManager) {
|
|
|
1298
1427
|
/**
|
|
1299
1428
|
* #action
|
|
1300
1429
|
*/
|
|
1301
|
-
moveTrackToBottom(
|
|
1430
|
+
moveTrackToBottom(idOrTrackId) {
|
|
1431
|
+
const id = self.trackModelId(idOrTrackId);
|
|
1302
1432
|
const section = self.trackSection(id);
|
|
1303
1433
|
const last = section[section.length - 1];
|
|
1304
1434
|
if (last && last.id !== id) {
|
|
@@ -1307,15 +1437,19 @@ export function stateModelFactory(pluginManager) {
|
|
|
1307
1437
|
},
|
|
1308
1438
|
/**
|
|
1309
1439
|
* #action
|
|
1440
|
+
* Move one shown track to another's position; each is a track model id
|
|
1441
|
+
* or a trackId.
|
|
1310
1442
|
*/
|
|
1311
|
-
moveTrack(
|
|
1443
|
+
moveTrack(moving, target) {
|
|
1444
|
+
const movingId = self.trackModelId(moving);
|
|
1445
|
+
const targetId = self.trackModelId(target);
|
|
1312
1446
|
const oldIndex = self.tracks.findIndex(track => track.id === movingId);
|
|
1313
1447
|
if (oldIndex === -1) {
|
|
1314
|
-
throw new Error(`
|
|
1448
|
+
throw new Error(`No shown track has id or trackId "${moving}" — the view's tracks are ${self.tracks.map(t => t.configuration.trackId).join(', ') || 'none'}`);
|
|
1315
1449
|
}
|
|
1316
1450
|
const newIndex = self.tracks.findIndex(track => track.id === targetId);
|
|
1317
1451
|
if (newIndex === -1) {
|
|
1318
|
-
throw new Error(`
|
|
1452
|
+
throw new Error(`No shown track has id or trackId "${target}" — the view's tracks are ${self.tracks.map(t => t.configuration.trackId).join(', ') || 'none'}`);
|
|
1319
1453
|
}
|
|
1320
1454
|
// direction-aware placement: filtering out oldIndex shifts the target
|
|
1321
1455
|
// left by one when dragging down (oldIndex < newIndex), so splicing at
|
|
@@ -1460,17 +1594,10 @@ export function stateModelFactory(pluginManager) {
|
|
|
1460
1594
|
* renders the view to SVG markup, which it returns; saves it through
|
|
1461
1595
|
* FileSaver unless `save: false`
|
|
1462
1596
|
*/
|
|
1463
|
-
// Promise<string> is stated
|
|
1464
|
-
//
|
|
1465
|
-
// inferred return makes the model type reference itself (TS2456).
|
|
1597
|
+
// Promise<string> is stated: renderToSvg is typed against this very
|
|
1598
|
+
// model, so an inferred return makes the type reference itself (TS2456)
|
|
1466
1599
|
async exportSvg(opts = {}) {
|
|
1467
|
-
|
|
1468
|
-
const html = await renderToSvg(self, opts);
|
|
1469
|
-
if (opts.save !== false) {
|
|
1470
|
-
const { saveSvgAsImage } = await import('@jbrowse/core/svg/saveSvgAsImage');
|
|
1471
|
-
await saveSvgAsImage(html, opts);
|
|
1472
|
-
}
|
|
1473
|
-
return html;
|
|
1600
|
+
return exportViewSvg(self, opts, () => import('./svgcomponents/SVGLinearGenomeView.js'));
|
|
1474
1601
|
},
|
|
1475
1602
|
}))
|
|
1476
1603
|
.actions(self => {
|
|
@@ -1874,8 +2001,9 @@ export function stateModelFactory(pluginManager) {
|
|
|
1874
2001
|
* into that through `scalebarDisplayPrefix`) and whether it is flipped.
|
|
1875
2002
|
* A ` [rev]` there is a fact about the ROW; the same marker on a
|
|
1876
2003
|
* chromosome name means that one region, which only happens under mixed
|
|
1877
|
-
* orientation. `captionSpanPx` is the width it takes
|
|
1878
|
-
*
|
|
2004
|
+
* orientation. `captionSpanPx` is the width it takes: the sticky label
|
|
2005
|
+
* pins to its right edge rather than the viewport's, and the coordinate
|
|
2006
|
+
* numbers stay out from under it. The SVG export deliberately
|
|
1879
2007
|
* calls `getScalebarRefNameLabels` itself with no prefix rather than
|
|
1880
2008
|
* reading this, since it draws its own assembly name above the ruler.
|
|
1881
2009
|
*/
|
|
@@ -1892,36 +2020,36 @@ export function stateModelFactory(pluginManager) {
|
|
|
1892
2020
|
* Every span along the row that is not track data, as plain geometry in
|
|
1893
2021
|
* the staticBlocks frame — the same frame as gridlineTicks and
|
|
1894
2022
|
* scalebarLabels, so one `translateX(staticBlocksTranslateX)` places all
|
|
1895
|
-
* three.
|
|
1896
|
-
* them:
|
|
2023
|
+
* three. A host drawing its own chrome needs all three kinds:
|
|
1897
2024
|
*
|
|
1898
2025
|
* - `seam`: the 3px bar at a region's right edge. Displayed regions are
|
|
1899
|
-
* laid out **contiguously**
|
|
1900
|
-
* padding only before the first region and after the last
|
|
2026
|
+
* laid out **contiguously** (calculateStaticBlocks emits boundary
|
|
2027
|
+
* padding only before the first region and after the last), so this
|
|
1901
2028
|
* bar is the only thing separating two of them. Without it a
|
|
1902
|
-
* two-region view
|
|
1903
|
-
*
|
|
1904
|
-
*
|
|
1905
|
-
*
|
|
2029
|
+
* two-region view looks like a single region.
|
|
2030
|
+
* `isRightEndOfDisplayedRegion` marks it; the right edge of the
|
|
2031
|
+
* currently *loaded* blocks, which block geometry gives you, is a
|
|
2032
|
+
* different set.
|
|
1906
2033
|
* - `elided`: a region too narrow to draw at this zoom. Whole-genome on
|
|
1907
|
-
* a real assembly is mostly these
|
|
1908
|
-
* but the 24 chromosomes
|
|
1909
|
-
*
|
|
2034
|
+
* a real assembly is mostly these (hg38 has 455 sequences, and all
|
|
2035
|
+
* but the 24 chromosomes are under a pixel wide), so a host that
|
|
2036
|
+
* skips them leaves that part of the row empty.
|
|
1910
2037
|
* - `boundary`: past the start of the first region or the end of the
|
|
1911
|
-
* last. Greying it
|
|
1912
|
-
*
|
|
2038
|
+
* last. Greying it shows the last region's seam as the edge of a
|
|
2039
|
+
* filled area, where it would otherwise look like a line in the
|
|
2040
|
+
* middle of the track.
|
|
1913
2041
|
*
|
|
1914
2042
|
* Elided blocks get no seam even though they carry the flag: at the
|
|
1915
|
-
* zoom where regions elide, one bar per region
|
|
2043
|
+
* zoom where regions elide, one bar per region would fill the row solid
|
|
2044
|
+
* grey.
|
|
1916
2045
|
*
|
|
1917
|
-
* PaddingBlocks is the in-tree consumer. The SVG export
|
|
1918
|
-
*
|
|
1919
|
-
*
|
|
1920
|
-
* interactive row
|
|
1921
|
-
*
|
|
1922
|
-
*
|
|
1923
|
-
*
|
|
1924
|
-
* so it is all that separates two of them.
|
|
2046
|
+
* PaddingBlocks is the in-tree consumer. The SVG export does not use
|
|
2047
|
+
* it: `SVGRegionSeparators` walks dynamicBlocks itself and draws only
|
|
2048
|
+
* the seam, because `elided` and `boundary` mark state for an
|
|
2049
|
+
* interactive row. In a static figure the striped grey would cover most
|
|
2050
|
+
* of the row at whole-genome zoom. The export keeps the seam because
|
|
2051
|
+
* regions lay out contiguously and the seam is all that separates two
|
|
2052
|
+
* of them.
|
|
1925
2053
|
*/
|
|
1926
2054
|
// Annotated with the shape spelled out rather than as `PaddingSpan`,
|
|
1927
2055
|
// because the API doc's type column is where a host drawing its own
|
|
@@ -1993,8 +2121,8 @@ export function stateModelFactory(pluginManager) {
|
|
|
1993
2121
|
* viewport. False both when the view holds no regions at all and when
|
|
1994
2122
|
* it holds some but is scrolled entirely off them; either way there is
|
|
1995
2123
|
* no visible span for the scalebar, ruler and refName labels to
|
|
1996
|
-
* describe
|
|
1997
|
-
*
|
|
2124
|
+
* describe. The SVG export checks it before drawing its header.
|
|
2125
|
+
* Distinct from `hasDisplayedRegions`, which only asks whether the
|
|
1998
2126
|
* view has been given regions, not whether any are on screen.
|
|
1999
2127
|
*/
|
|
2000
2128
|
get hasVisibleContent() {
|
|
@@ -2096,23 +2224,22 @@ export function stateModelFactory(pluginManager) {
|
|
|
2096
2224
|
},
|
|
2097
2225
|
/**
|
|
2098
2226
|
* #getter
|
|
2099
|
-
* **
|
|
2100
|
-
* view has settled at least once, the live ones before that.
|
|
2227
|
+
* **The blocks a debounced consumer clips to**: the coarse blocks once
|
|
2228
|
+
* the view has settled at least once, the live ones before that.
|
|
2101
2229
|
*
|
|
2102
|
-
* The coarse blocks
|
|
2103
|
-
* animation frame during a pan or zoom
|
|
2104
|
-
* alignments coverage scale and MAF's coverage band
|
|
2105
|
-
*
|
|
2106
|
-
*
|
|
2107
|
-
*
|
|
2108
|
-
*
|
|
2109
|
-
*
|
|
2110
|
-
*
|
|
2111
|
-
*
|
|
2112
|
-
* fetch used to be trailing-edge at 600ms, so it never did.
|
|
2230
|
+
* The coarse blocks stop a per-bp scan from recomputing on every
|
|
2231
|
+
* animation frame during a pan or zoom. Wiggle's autoscale domain, the
|
|
2232
|
+
* alignments coverage scale and MAF's coverage band use them. While the
|
|
2233
|
+
* coarse blocks are stale the result is a frame or two old, as the
|
|
2234
|
+
* debounce intends. **Empty coarse blocks are a different case.** A scan
|
|
2235
|
+
* over no blocks yields no entries, and the consumer falls back to the
|
|
2236
|
+
* domain `[0,1]`, which draws a line plot blank and a density plot
|
|
2237
|
+
* saturated. The coarse blocks are empty for the 500ms between a view
|
|
2238
|
+
* initializing and the coarse autorun's first run, and data can load
|
|
2239
|
+
* within that window.
|
|
2113
2240
|
*
|
|
2114
|
-
*
|
|
2115
|
-
*
|
|
2241
|
+
* Falling back to the live blocks costs one extra recompute at that
|
|
2242
|
+
* transition, compared with N per frame without the debounce.
|
|
2116
2243
|
*/
|
|
2117
2244
|
get settledDynamicBlocks() {
|
|
2118
2245
|
return self.coarseDynamicBlocks.length
|
|
@@ -2601,6 +2728,31 @@ export function stateModelFactory(pluginManager) {
|
|
|
2601
2728
|
async navToLocation(parsedLocString, assemblyName, grow) {
|
|
2602
2729
|
return self.navToLocations([parsedLocString], assemblyName, grow);
|
|
2603
2730
|
},
|
|
2731
|
+
/**
|
|
2732
|
+
* #action
|
|
2733
|
+
* adds the region in view to the session's highlights
|
|
2734
|
+
*/
|
|
2735
|
+
highlightCurrentRegion() {
|
|
2736
|
+
const [region] = self.visibleWholeBaseRegions;
|
|
2737
|
+
if (region) {
|
|
2738
|
+
getSession(self).addHighlight(region);
|
|
2739
|
+
}
|
|
2740
|
+
},
|
|
2741
|
+
/**
|
|
2742
|
+
* #action
|
|
2743
|
+
* navigates to the most recently added highlight on this view's
|
|
2744
|
+
* assemblies, zoomed out a little for context
|
|
2745
|
+
*/
|
|
2746
|
+
async navigateNewestHighlight() {
|
|
2747
|
+
const newest = self.highlights.at(-1);
|
|
2748
|
+
if (newest) {
|
|
2749
|
+
const { assemblyName, ...region } = newest;
|
|
2750
|
+
await self.navToLocString(assembleLocString(region), assemblyName, 0.2);
|
|
2751
|
+
}
|
|
2752
|
+
else {
|
|
2753
|
+
getSession(self).notify('There are no highlights to navigate to.', 'info');
|
|
2754
|
+
}
|
|
2755
|
+
},
|
|
2604
2756
|
}))
|
|
2605
2757
|
.views(self => ({
|
|
2606
2758
|
/**
|
|
@@ -2617,7 +2769,7 @@ export function stateModelFactory(pluginManager) {
|
|
|
2617
2769
|
},
|
|
2618
2770
|
/**
|
|
2619
2771
|
* #method
|
|
2620
|
-
* Map a highlight
|
|
2772
|
+
* Map a highlight region to its pixel position+width inside
|
|
2621
2773
|
* the tracks container. Falls back to the raw refName if the region's
|
|
2622
2774
|
* assemblyName is missing or unknown so highlights authored without an
|
|
2623
2775
|
* assembly still render in single-assembly views.
|
|
@@ -2703,21 +2855,21 @@ export function stateModelFactory(pluginManager) {
|
|
|
2703
2855
|
let heading;
|
|
2704
2856
|
/**
|
|
2705
2857
|
* #action
|
|
2706
|
-
*
|
|
2707
|
-
*
|
|
2858
|
+
* Animate to a window along the Van Wijk arc from the current window to
|
|
2859
|
+
* the target, over a duration derived from the arc.
|
|
2708
2860
|
*
|
|
2709
|
-
*
|
|
2710
|
-
* between
|
|
2711
|
-
*
|
|
2861
|
+
* The final state is the one `setWindow` would produce; only the path in
|
|
2862
|
+
* between differs. A caller can therefore offer an Undo, a snackbar or a
|
|
2863
|
+
* follow anchor around this exactly as around the instant move.
|
|
2712
2864
|
*
|
|
2713
|
-
*
|
|
2714
|
-
* wrote and
|
|
2715
|
-
* zoom, a drag, a locstring nav, or the Undo on the
|
|
2716
|
-
* flight was launched with.
|
|
2717
|
-
*
|
|
2718
|
-
* past `maxBpPerPx` reads its
|
|
2719
|
-
*
|
|
2720
|
-
*
|
|
2865
|
+
* The flight stops when anything else moves the view: it reads back what
|
|
2866
|
+
* it wrote and stops as soon as the view holds something else, such as a
|
|
2867
|
+
* wheel zoom, a drag, a locstring nav, or the Undo on the snackbar the
|
|
2868
|
+
* flight was launched with. It compares against the written value, not
|
|
2869
|
+
* the requested one, because the write clamps: an arc that pulls back
|
|
2870
|
+
* past `maxBpPerPx` reads back its clamped result, and treating that as
|
|
2871
|
+
* interference would end the flight after one frame. `springAnimate`
|
|
2872
|
+
* handles the same value the same way.
|
|
2721
2873
|
*/
|
|
2722
2874
|
function flyTo(centerBp, windowWidthBp) {
|
|
2723
2875
|
cancelLastFlight();
|
|
@@ -2798,6 +2950,16 @@ export function stateModelFactory(pluginManager) {
|
|
|
2798
2950
|
flyTo(centerBp, heading ? heading.windowWidthBp : self.windowWidthBp);
|
|
2799
2951
|
}
|
|
2800
2952
|
},
|
|
2953
|
+
/**
|
|
2954
|
+
* #action
|
|
2955
|
+
* A flight to a linear bp centre at the zoom the view is on, or is
|
|
2956
|
+
* already flying to, widened where that is narrower than `fitWidthBp`.
|
|
2957
|
+
* The current width mid-flight is a point on the arc, so a second
|
|
2958
|
+
* flight read from it would land pulled back.
|
|
2959
|
+
*/
|
|
2960
|
+
flyToFit(centerBp, fitWidthBp) {
|
|
2961
|
+
flyTo(centerBp, Math.max(heading ? heading.windowWidthBp : self.windowWidthBp, fitWidthBp));
|
|
2962
|
+
},
|
|
2801
2963
|
};
|
|
2802
2964
|
})
|
|
2803
2965
|
.actions(self => ({
|
|
@@ -2819,12 +2981,9 @@ export function stateModelFactory(pluginManager) {
|
|
|
2819
2981
|
// The cytobands setting has been `showCytobandsSetting` and (briefly)
|
|
2820
2982
|
// `cytobandsVisible`; both now persist as the bare `showCytobands` prop
|
|
2821
2983
|
// (the capability-gated getter is `effectiveShowCytobands`).
|
|
2822
|
-
const {
|
|
2984
|
+
const { showCytobandsSetting, cytobandsVisible, offsetPx, bpPerPx, ...rest } = snap;
|
|
2823
2985
|
const legacyShowCytobands = showCytobandsSetting ?? cytobandsVisible;
|
|
2824
2986
|
return {
|
|
2825
|
-
highlight: Array.isArray(highlight) || highlight === undefined
|
|
2826
|
-
? highlight
|
|
2827
|
-
: [highlight],
|
|
2828
2987
|
...(legacyShowCytobands !== undefined
|
|
2829
2988
|
? { showCytobands: legacyShowCytobands }
|
|
2830
2989
|
: {}),
|