jbrowse-plugin-graphgenomeviewer 4.0.11 → 4.0.13
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/README.md +22 -4
- package/dist/chunks/GbzBaseSyntenyAdapter-Y5NBXK7P.js +8 -0
- package/dist/chunks/{GbzBaseSyntenyAdapter-URQ4Z2JB.js.map → GbzBaseSyntenyAdapter-Y5NBXK7P.js.map} +4 -4
- package/dist/chunks/{GraphGenomeView-44GJL4RA.js → GraphGenomeView-TTQEJJQD.js} +2 -2
- package/dist/chunks/{GraphGenomeView-44GJL4RA.js.map → GraphGenomeView-TTQEJJQD.js.map} +1 -1
- package/dist/chunks/GraphTrackSettingsDialog-3A2PKZRI.js +4 -0
- package/dist/chunks/{GraphTrackSettingsDialog-LZITZ3VD.js.map → GraphTrackSettingsDialog-3A2PKZRI.js.map} +1 -1
- package/dist/chunks/LinearGraphDisplay-MJ5XZFE5.js +2 -0
- package/dist/chunks/{LinearGraphDisplay-BWBXVSEQ.js.map → LinearGraphDisplay-MJ5XZFE5.js.map} +1 -1
- package/dist/chunks/{MinigraphBubbleAdapter-QTXGK7DW.js → MinigraphBubbleAdapter-APC7JYOM.js} +2 -2
- package/dist/chunks/{RgfaTabixAdapter-O3ERR2GY.js → RgfaTabixAdapter-4E365PXO.js} +2 -2
- package/dist/chunks/{chunk-LGGAXAS6.js → chunk-5DP7IXD3.js} +2 -2
- package/dist/chunks/chunk-CJ56O2VG.js +2 -0
- package/dist/chunks/chunk-CJ56O2VG.js.map +7 -0
- package/dist/chunks/chunk-EFVAQ2KM.js +2 -0
- package/dist/chunks/chunk-EFVAQ2KM.js.map +7 -0
- package/dist/chunks/chunk-IFRDWMQ2.js +2 -0
- package/dist/chunks/chunk-IFRDWMQ2.js.map +7 -0
- package/dist/chunks/chunk-JAQ2RQAE.js +2 -0
- package/dist/chunks/{chunk-QRVREF7H.js.map → chunk-JAQ2RQAE.js.map} +4 -4
- package/dist/chunks/{chunk-WETM2CD7.js → chunk-LA7MQS33.js} +2 -2
- package/dist/chunks/chunk-M4YEAXSR.js +2 -0
- package/dist/chunks/chunk-M4YEAXSR.js.map +7 -0
- package/dist/chunks/chunk-XDOIZTY6.js +4 -0
- package/dist/chunks/chunk-XDOIZTY6.js.map +7 -0
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +2 -2
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
- package/package.json +4 -4
- package/src/GbzBaseSyntenyAdapter/configSchema.ts +3 -4
- package/src/GraphGenomeView/components/GraphCanvas.tsx +4 -11
- package/src/GraphGenomeView/components/TubeMapOverlay.tsx +117 -4
- package/src/GraphGenomeView/components/WalkRowsOverlay.tsx +261 -44
- package/src/GraphGenomeView/components/referenceRampCss.test.ts +27 -0
- package/src/GraphGenomeView/components/referenceRampCss.ts +39 -0
- package/src/GraphGenomeView/genes/genes.test.ts +9 -2
- package/src/GraphGenomeView/host.test.ts +25 -2
- package/src/GraphGenomeView/host.ts +9 -7
- package/src/GraphGenomeView/model.test.ts +383 -4
- package/src/GraphGenomeView/model.ts +413 -88
- package/src/GraphGenomeView/repeats/repeatFeatures.test.ts +75 -0
- package/src/GraphGenomeView/repeats/repeatFeatures.ts +156 -8
- package/src/GraphGenomeView/repeats/walkCalls.test.ts +18 -0
- package/src/GraphGenomeView/repeats/walkCalls.ts +35 -18
- package/src/GraphGenomeView/subgraphLoad.test.ts +1 -0
- package/src/LinearGraphDisplay/LinearGraphDisplay.test.ts +165 -6
- package/src/LinearGraphDisplay/model.ts +61 -1
- package/src/gaf/gafFile.test.ts +18 -0
- package/src/version.ts +1 -1
- package/dist/chunks/GbzBaseSyntenyAdapter-URQ4Z2JB.js +0 -8
- package/dist/chunks/GraphTrackSettingsDialog-LZITZ3VD.js +0 -4
- package/dist/chunks/LinearGraphDisplay-BWBXVSEQ.js +0 -2
- package/dist/chunks/chunk-ADEBM7AA.js +0 -2
- package/dist/chunks/chunk-ADEBM7AA.js.map +0 -7
- package/dist/chunks/chunk-MWSRAF5B.js +0 -4
- package/dist/chunks/chunk-MWSRAF5B.js.map +0 -7
- package/dist/chunks/chunk-QRVREF7H.js +0 -2
- package/dist/chunks/chunk-ZYQJBNS5.js +0 -2
- package/dist/chunks/chunk-ZYQJBNS5.js.map +0 -7
- /package/dist/chunks/{MinigraphBubbleAdapter-QTXGK7DW.js.map → MinigraphBubbleAdapter-APC7JYOM.js.map} +0 -0
- /package/dist/chunks/{RgfaTabixAdapter-O3ERR2GY.js.map → RgfaTabixAdapter-4E365PXO.js.map} +0 -0
- /package/dist/chunks/{chunk-LGGAXAS6.js.map → chunk-5DP7IXD3.js.map} +0 -0
- /package/dist/chunks/{chunk-WETM2CD7.js.map → chunk-LA7MQS33.js.map} +0 -0
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from '@jbrowse/bandage-core/colorSchemes'
|
|
19
19
|
import { deletionEdges } from '@jbrowse/bandage-core/deletionEdges'
|
|
20
20
|
import { genePins } from '@jbrowse/bandage-core/genes/genePins'
|
|
21
|
+
import { rowLabelBox } from '@jbrowse/bandage-core/graphLabels'
|
|
21
22
|
import { layoutLabels } from '@jbrowse/bandage-core/labelLayout'
|
|
22
23
|
import { ROW_HEIGHT_PX } from '@jbrowse/bandage-core/layout/rowSpacing'
|
|
23
24
|
import { walkRowsExtent } from '@jbrowse/bandage-core/layout/walkRowLayout'
|
|
@@ -29,7 +30,10 @@ import {
|
|
|
29
30
|
modeUsesLayoutEngine,
|
|
30
31
|
} from '@jbrowse/bandage-core/layoutModes'
|
|
31
32
|
import { NODE_WIDTH_VALUES, nodeInk } from '@jbrowse/bandage-core/nodeWidths'
|
|
32
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
anchorFromPaths,
|
|
35
|
+
chooseReferencePath,
|
|
36
|
+
} from '@jbrowse/bandage-core/pathAnchoring'
|
|
33
37
|
import { pathColorsLegible, pathLegend } from '@jbrowse/bandage-core/pathColors'
|
|
34
38
|
import {
|
|
35
39
|
FIT_PADDING,
|
|
@@ -41,6 +45,12 @@ import {
|
|
|
41
45
|
forceLayout,
|
|
42
46
|
loadGraph,
|
|
43
47
|
} from '@jbrowse/bandage-core/pipeline'
|
|
48
|
+
import {
|
|
49
|
+
assemblyWalk,
|
|
50
|
+
backboneAssembly,
|
|
51
|
+
featuresOnBackbone,
|
|
52
|
+
graphBackbone,
|
|
53
|
+
} from '@jbrowse/bandage-core/reference'
|
|
44
54
|
import {
|
|
45
55
|
buildNeighbors,
|
|
46
56
|
nodeReferenceSpan,
|
|
@@ -49,11 +59,28 @@ import {
|
|
|
49
59
|
buildGeometry,
|
|
50
60
|
computeReferenceRamp,
|
|
51
61
|
} from '@jbrowse/bandage-core/renderer/GeometryBuilder'
|
|
62
|
+
import {
|
|
63
|
+
referenceBoxes,
|
|
64
|
+
rulerBoxes,
|
|
65
|
+
rulerMarks,
|
|
66
|
+
} from '@jbrowse/bandage-core/tubeMap/axis'
|
|
67
|
+
import { coarsenTubeMap } from '@jbrowse/bandage-core/tubeMap/coarsen'
|
|
68
|
+
import {
|
|
69
|
+
connectorAt,
|
|
70
|
+
referenceNodes,
|
|
71
|
+
tubeMapConnectors,
|
|
72
|
+
} from '@jbrowse/bandage-core/tubeMap/connectors'
|
|
73
|
+
import { deviationMarks } from '@jbrowse/bandage-core/tubeMap/deviations'
|
|
52
74
|
import { tubeMapPicture } from '@jbrowse/bandage-core/tubeMap/draw'
|
|
53
75
|
import {
|
|
54
76
|
tubeMapFrame,
|
|
55
77
|
tubeMapNodeAt,
|
|
56
78
|
} from '@jbrowse/bandage-core/tubeMap/frame'
|
|
79
|
+
import {
|
|
80
|
+
GENE_ROW_PX,
|
|
81
|
+
tubeMapGeneRows,
|
|
82
|
+
tubeMapGenes,
|
|
83
|
+
} from '@jbrowse/bandage-core/tubeMap/genes'
|
|
57
84
|
import {
|
|
58
85
|
axisScaleOf,
|
|
59
86
|
contains,
|
|
@@ -130,6 +157,7 @@ import type { GeneModel } from '@jbrowse/bandage-core/genes/genePins'
|
|
|
130
157
|
import type { LayoutModeValue } from '@jbrowse/bandage-core/layoutModes'
|
|
131
158
|
import type { NodeWidth } from '@jbrowse/bandage-core/nodeWidths'
|
|
132
159
|
import type { Bounds, EngineRequest } from '@jbrowse/bandage-core/pipeline'
|
|
160
|
+
import type { AssemblyNames } from '@jbrowse/bandage-core/reference'
|
|
133
161
|
import type {
|
|
134
162
|
RenderBatch,
|
|
135
163
|
Renderer,
|
|
@@ -155,6 +183,21 @@ const MAX_CANVAS_HEIGHT = 600
|
|
|
155
183
|
// leaves room to hover a node and read its tooltip.
|
|
156
184
|
const MIN_CANVAS_HEIGHT = 160
|
|
157
185
|
const VARIANT_MAP_HEIGHT = 340
|
|
186
|
+
// The thinnest a fit draws a tube map's tubes
|
|
187
|
+
const MIN_FIT_TUBE_PX = 5
|
|
188
|
+
|
|
189
|
+
const TUBE_MAP_MODES = new Set<string>(['tubemap', 'tubemapref'])
|
|
190
|
+
|
|
191
|
+
// The sizes a tube map folds variants under. MICB's 22 kb cut draws 475
|
|
192
|
+
// columns whole, 34 under 3 bp and one under 50, where only its structural
|
|
193
|
+
// variants would stand: human windows are mostly SNPs.
|
|
194
|
+
const TUBE_MAP_FOLDS = [
|
|
195
|
+
{ bp: 0, label: 'None' },
|
|
196
|
+
{ bp: 3, label: 'Under 3 bp' },
|
|
197
|
+
{ bp: 10, label: 'Under 10 bp' },
|
|
198
|
+
{ bp: 50, label: 'Under 50 bp, leaving structural variants' },
|
|
199
|
+
{ bp: 1000, label: 'Under 1 kb' },
|
|
200
|
+
]
|
|
158
201
|
|
|
159
202
|
const SEGMENTS_SUFFIX = '.segs.bed.gz'
|
|
160
203
|
|
|
@@ -374,6 +417,10 @@ export function GraphPaneMixin() {
|
|
|
374
417
|
// The session's genes drawn onto the backbone: exons along the nodes
|
|
375
418
|
// that carry them, names pinned at their midpoints. See genes/.
|
|
376
419
|
showGenes: types.optional(types.boolean, true),
|
|
420
|
+
// Variants under this many bp fold into the reference before a tube map
|
|
421
|
+
// is laid out, each kept as a tick on its walk's tube (coarsen.ts); 0
|
|
422
|
+
// draws every one
|
|
423
|
+
tubeMapFold: types.optional(types.number, 0),
|
|
377
424
|
// Which track the genes come from; empty picks the assembly's
|
|
378
425
|
// annotation track (pickGeneTrack).
|
|
379
426
|
geneTrackId: types.optional(types.string, ''),
|
|
@@ -441,6 +488,11 @@ export function GraphPaneMixin() {
|
|
|
441
488
|
graph: undefined as Graph | undefined,
|
|
442
489
|
// the reference window the graph on screen was cut for, set with it
|
|
443
490
|
graphRegion: undefined as SubgraphRegion | undefined,
|
|
491
|
+
// The walk the track's graph lies on for the region's assembly, which
|
|
492
|
+
// the track's config declares; x starts along it unless the user chose
|
|
493
|
+
// another. Undefined for an rGFA, whose backbone is fixed, and for a
|
|
494
|
+
// graph with no region.
|
|
495
|
+
loadedReferencePath: undefined as string | undefined,
|
|
444
496
|
layoutResult: undefined as LayoutResult | undefined,
|
|
445
497
|
// what the legends in the pane's top-right corner measure, so no label
|
|
446
498
|
// is placed under them
|
|
@@ -552,10 +604,18 @@ export function GraphPaneMixin() {
|
|
|
552
604
|
get paneWidth() {
|
|
553
605
|
return getContainingView(self).width
|
|
554
606
|
},
|
|
607
|
+
// The graph the drawing's node ids address: a folded tube map's coarse
|
|
608
|
+
// graph, the cut otherwise. Hover, details and labels read it, so a
|
|
609
|
+
// merged node reports its own span and length.
|
|
610
|
+
get drawnGraph() {
|
|
611
|
+
return self.layoutResult?.tubeMap?.coarse?.graph ?? self.graph
|
|
612
|
+
},
|
|
613
|
+
}))
|
|
614
|
+
.views(self => ({
|
|
555
615
|
get nodeById() {
|
|
556
|
-
if (self.
|
|
616
|
+
if (self.drawnGraph) {
|
|
557
617
|
const m = new Map<string, GraphNode>()
|
|
558
|
-
for (const n of self.
|
|
618
|
+
for (const n of self.drawnGraph.nodes) {
|
|
559
619
|
m.set(n.id, n)
|
|
560
620
|
}
|
|
561
621
|
return m
|
|
@@ -568,7 +628,7 @@ export function GraphPaneMixin() {
|
|
|
568
628
|
// allocation on a 30k-node cut).
|
|
569
629
|
get nodeLengths() {
|
|
570
630
|
const m = new Map<string, number>()
|
|
571
|
-
for (const n of self.
|
|
631
|
+
for (const n of self.drawnGraph?.nodes ?? []) {
|
|
572
632
|
m.set(n.id, n.length)
|
|
573
633
|
}
|
|
574
634
|
return m
|
|
@@ -805,14 +865,71 @@ export function GraphPaneMixin() {
|
|
|
805
865
|
get selectedRepeat() {
|
|
806
866
|
return self.repeatChoices.find(r => r.key === self.repeatKey)
|
|
807
867
|
},
|
|
868
|
+
get backbone() {
|
|
869
|
+
return self.graph ? graphBackbone(self.graph) : undefined
|
|
870
|
+
},
|
|
871
|
+
// An assembly by each name a backbone may spell it with: the session's
|
|
872
|
+
// name and aliases, and the PanSN prefix the source track maps each of
|
|
873
|
+
// those to.
|
|
874
|
+
assemblySpellings(assemblyName: string): AssemblyNames {
|
|
875
|
+
const { assemblyManager } = getSession(self)
|
|
876
|
+
const assembly = assemblyManager.has(assemblyName)
|
|
877
|
+
? assemblyManager.get(assemblyName)
|
|
878
|
+
: undefined
|
|
879
|
+
const panSN = (self.sourceAdapter?.assemblyNameToPanSN ?? {}) as Record<
|
|
880
|
+
string,
|
|
881
|
+
string
|
|
882
|
+
>
|
|
883
|
+
const names = [
|
|
884
|
+
assemblyName,
|
|
885
|
+
...(assembly ? [assembly.name, ...assembly.aliases] : []),
|
|
886
|
+
]
|
|
887
|
+
return {
|
|
888
|
+
name: assemblyName,
|
|
889
|
+
aliases: [...names, ...names.flatMap(n => panSN[n] ?? [])],
|
|
890
|
+
}
|
|
891
|
+
},
|
|
892
|
+
// Whether x lies along the reference the track's graph came with, not a
|
|
893
|
+
// walk "Draw x along" picked. An rGFA's backbone is fixed.
|
|
894
|
+
get drawsLoadedReference() {
|
|
895
|
+
const graph = self.graph
|
|
896
|
+
return (
|
|
897
|
+
graph?.anchoredBy === 'tags' ||
|
|
898
|
+
(graph?.referencePath !== undefined &&
|
|
899
|
+
graph.referencePath === self.loadedReferencePath)
|
|
900
|
+
)
|
|
901
|
+
},
|
|
902
|
+
}))
|
|
903
|
+
.views(self => ({
|
|
904
|
+
// the assembly the genes were read for
|
|
905
|
+
get geneAssembly() {
|
|
906
|
+
const region = self.graphRegion
|
|
907
|
+
return region ? self.assemblySpellings(region.assemblyName) : undefined
|
|
908
|
+
},
|
|
909
|
+
}))
|
|
910
|
+
.views(self => ({
|
|
911
|
+
// The genes on the drawn backbone under its own refNames. The track's
|
|
912
|
+
// config puts its reference on the cut's assembly, whatever the walk's
|
|
913
|
+
// name. After "Draw x along" another walk, the backbone takes the genes
|
|
914
|
+
// only where its PanSN prefix names their assembly.
|
|
915
|
+
get backboneGenes() {
|
|
916
|
+
const { backbone, geneAssembly, geneFeatures } = self
|
|
917
|
+
const binds =
|
|
918
|
+
self.drawsLoadedReference ||
|
|
919
|
+
(geneAssembly !== undefined &&
|
|
920
|
+
backboneAssembly(backbone, [geneAssembly]) !== undefined)
|
|
921
|
+
return backbone && geneFeatures && binds
|
|
922
|
+
? featuresOnBackbone(geneFeatures, backbone)
|
|
923
|
+
: undefined
|
|
924
|
+
},
|
|
808
925
|
}))
|
|
809
926
|
.views(self => ({
|
|
810
927
|
get nodeNeighbors() {
|
|
811
|
-
return self.
|
|
928
|
+
return self.drawnGraph ? buildNeighbors(self.drawnGraph) : undefined
|
|
812
929
|
},
|
|
813
930
|
get nodeInk(): NodeInk {
|
|
814
931
|
return nodeInk(
|
|
815
|
-
self.
|
|
932
|
+
self.drawnGraph,
|
|
816
933
|
self.nodeById,
|
|
817
934
|
self.contigThickness,
|
|
818
935
|
self.nodeWidth,
|
|
@@ -853,7 +970,20 @@ export function GraphPaneMixin() {
|
|
|
853
970
|
a.label.localeCompare(b.label),
|
|
854
971
|
)
|
|
855
972
|
: bars.rows
|
|
856
|
-
|
|
973
|
+
const [reference, ...paired] = withCalls(
|
|
974
|
+
[bars.reference, ...rows],
|
|
975
|
+
repeat?.calls,
|
|
976
|
+
)
|
|
977
|
+
return { ...bars, reference: reference!, rows: paired }
|
|
978
|
+
},
|
|
979
|
+
// The units walk rows paint every bar by, once the selected repeat's
|
|
980
|
+
// record states a drawn walk's runs.
|
|
981
|
+
get walkRowUnits() {
|
|
982
|
+
const bars = this.walkRowBars
|
|
983
|
+
return bars &&
|
|
984
|
+
[bars.reference, ...bars.rows].some(row => row.call?.sequences)
|
|
985
|
+
? self.selectedRepeat?.units
|
|
986
|
+
: undefined
|
|
857
987
|
},
|
|
858
988
|
// The labels drawn beside the rows. Walk rows label from the bars
|
|
859
989
|
// themselves, which follow the selected repeat and sample filter that
|
|
@@ -867,6 +997,14 @@ export function GraphPaneMixin() {
|
|
|
867
997
|
}))
|
|
868
998
|
: self.rowLabels
|
|
869
999
|
},
|
|
1000
|
+
// The row labels are pinned to the pane's left edge, so the fit starts
|
|
1001
|
+
// the drawing past the widest one.
|
|
1002
|
+
get fitPadLeft() {
|
|
1003
|
+
return Math.max(
|
|
1004
|
+
FIT_PADDING,
|
|
1005
|
+
...this.drawnRowLabels.map(r => rowLabelBox(r.label, 0).x1 + 6),
|
|
1006
|
+
)
|
|
1007
|
+
},
|
|
870
1008
|
// Each bubble in the window with what it is, for the variant map's
|
|
871
1009
|
// glyphs on the reference line.
|
|
872
1010
|
get bubbleGlyphs() {
|
|
@@ -886,9 +1024,9 @@ export function GraphPaneMixin() {
|
|
|
886
1024
|
self.chosenLayoutMode !== 'walkrows' &&
|
|
887
1025
|
!self.layoutResult?.tubeMap &&
|
|
888
1026
|
self.graph &&
|
|
889
|
-
self.
|
|
1027
|
+
self.backboneGenes &&
|
|
890
1028
|
positions
|
|
891
|
-
? genePins(self.graph, self.
|
|
1029
|
+
? genePins(self.graph, self.backboneGenes, positions)
|
|
892
1030
|
: []
|
|
893
1031
|
},
|
|
894
1032
|
// The same bubbles over every other layout, as halos along their nodes.
|
|
@@ -990,54 +1128,6 @@ export function GraphPaneMixin() {
|
|
|
990
1128
|
})
|
|
991
1129
|
: undefined
|
|
992
1130
|
},
|
|
993
|
-
// The pane is as tall as the drawing, rather than a fixed box the drawing
|
|
994
|
-
// floats in the middle of.
|
|
995
|
-
//
|
|
996
|
-
// A row layout says how tall it is outright: rows are px, so the drawing's
|
|
997
|
-
// height is the row count times the pitch and this is a sum, not a
|
|
998
|
-
// derivation. It used to be derived from the drawing's ASPECT RATIO
|
|
999
|
-
// against the x-fit scale, because y was in bp and a height in px did not
|
|
1000
|
-
// exist until a scale was chosen — which is also why a ceiling had to be
|
|
1001
|
-
// put on the row pitch to stop tall graphs from binding the fit on the
|
|
1002
|
-
// wrong axis. Both are gone with the unit.
|
|
1003
|
-
//
|
|
1004
|
-
// An isotropic layout still has no height of its own and keeps the aspect
|
|
1005
|
-
// derivation. It reads neither `scale` nor the height it is replacing, so
|
|
1006
|
-
// zoomToFit consumes this without feeding back into it.
|
|
1007
|
-
get canvasHeight() {
|
|
1008
|
-
const bounds = this.layoutBounds
|
|
1009
|
-
const usableWidth = self.paneWidth - FIT_PADDING * 2
|
|
1010
|
-
// `paneHeight` replaces the built-in ceiling rather than adding a
|
|
1011
|
-
// second clamp under it, and the floor still wins: a pane shorter than
|
|
1012
|
-
// MIN_CANVAS_HEIGHT leaves no room to hover a node and read its
|
|
1013
|
-
// tooltip, which is the reason that floor exists.
|
|
1014
|
-
const ceiling = Math.max(
|
|
1015
|
-
MIN_CANVAS_HEIGHT,
|
|
1016
|
-
self.paneHeight ?? MAX_CANVAS_HEIGHT,
|
|
1017
|
-
)
|
|
1018
|
-
if (!bounds) {
|
|
1019
|
-
return ceiling
|
|
1020
|
-
}
|
|
1021
|
-
if (self.pixelRows) {
|
|
1022
|
-
// The variant map's drawing is one line; its glyphs and labels are
|
|
1023
|
-
// painted above it by the overlay and need the room a row layout
|
|
1024
|
-
// would give to rows.
|
|
1025
|
-
const floor =
|
|
1026
|
-
self.chosenLayoutMode === 'variants'
|
|
1027
|
-
? VARIANT_MAP_HEIGHT
|
|
1028
|
-
: MIN_CANVAS_HEIGHT
|
|
1029
|
-
return Math.min(ceiling, Math.max(floor, bounds.h + FIT_PADDING * 2))
|
|
1030
|
-
}
|
|
1031
|
-
return bounds.w > 0 && usableWidth > 0
|
|
1032
|
-
? Math.min(
|
|
1033
|
-
ceiling,
|
|
1034
|
-
Math.max(
|
|
1035
|
-
MIN_CANVAS_HEIGHT,
|
|
1036
|
-
(bounds.h * usableWidth) / bounds.w + FIT_PADDING * 2,
|
|
1037
|
-
),
|
|
1038
|
-
)
|
|
1039
|
-
: ceiling
|
|
1040
|
-
},
|
|
1041
1131
|
}))
|
|
1042
1132
|
.views(self => ({
|
|
1043
1133
|
get geneTrack() {
|
|
@@ -1067,7 +1157,7 @@ export function GraphPaneMixin() {
|
|
|
1067
1157
|
// neighbour walk to say what the first one already worked out.
|
|
1068
1158
|
get referenceRampDomain() {
|
|
1069
1159
|
const ramp = self.referenceRamp
|
|
1070
|
-
return ramp
|
|
1160
|
+
return ramp && !self.walkRowUnits
|
|
1071
1161
|
? { start: ramp.start, end: ramp.start + ramp.span }
|
|
1072
1162
|
: undefined
|
|
1073
1163
|
},
|
|
@@ -1235,18 +1325,29 @@ export function GraphPaneMixin() {
|
|
|
1235
1325
|
return isLinearHost(view) ? view : undefined
|
|
1236
1326
|
},
|
|
1237
1327
|
}))
|
|
1328
|
+
.views(self => ({
|
|
1329
|
+
// Whether the drawing's x is reference bp a host can place. Walk rows
|
|
1330
|
+
// put the backbone at its bp too, but their bars are lengths, so the
|
|
1331
|
+
// mode's `cutMargins` is what says the drawing is a picture.
|
|
1332
|
+
get xIsReferenceBp() {
|
|
1333
|
+
return (
|
|
1334
|
+
self.layoutResult?.referenceAxis === true &&
|
|
1335
|
+
layoutModeByValue(self.chosenLayoutMode).cutMargins
|
|
1336
|
+
)
|
|
1337
|
+
},
|
|
1338
|
+
}))
|
|
1238
1339
|
.views(self => ({
|
|
1239
1340
|
// Whether the host places x. Only a layout whose x is reference bp can
|
|
1240
1341
|
// take the window's transform; force, ordered and walk rows draw in
|
|
1241
1342
|
// their own coordinates inside the track, and a popped bubble is a
|
|
1242
1343
|
// picture of its own.
|
|
1243
1344
|
get hostPlacesX() {
|
|
1244
|
-
const { host, graphRegion: region
|
|
1345
|
+
const { host, graphRegion: region } = self
|
|
1245
1346
|
return (
|
|
1246
1347
|
host !== undefined &&
|
|
1247
1348
|
host.initialized &&
|
|
1248
1349
|
region !== undefined &&
|
|
1249
|
-
|
|
1350
|
+
self.xIsReferenceBp &&
|
|
1250
1351
|
self.popStack.length === 0 &&
|
|
1251
1352
|
!host.dynamicBlocks.contentBlocks.some(
|
|
1252
1353
|
b => b.refName === region.refName && b.reversed,
|
|
@@ -1255,9 +1356,6 @@ export function GraphPaneMixin() {
|
|
|
1255
1356
|
},
|
|
1256
1357
|
}))
|
|
1257
1358
|
.views(self => ({
|
|
1258
|
-
get overlayLabels() {
|
|
1259
|
-
return layoutLabels(self)
|
|
1260
|
-
},
|
|
1261
1359
|
get hostFrame() {
|
|
1262
1360
|
const { host, graphRegion } = self
|
|
1263
1361
|
return self.hostPlacesX && host && graphRegion
|
|
@@ -1268,6 +1366,127 @@ export function GraphPaneMixin() {
|
|
|
1268
1366
|
const drawing = self.layoutResult?.tubeMap
|
|
1269
1367
|
return drawing ? tubeMapPicture(drawing.layout) : undefined
|
|
1270
1368
|
},
|
|
1369
|
+
get tubeMapReference() {
|
|
1370
|
+
const drawing = self.layoutResult?.tubeMap
|
|
1371
|
+
return drawing && self.drawnGraph
|
|
1372
|
+
? referenceBoxes(self.drawnGraph, drawing.layout)
|
|
1373
|
+
: undefined
|
|
1374
|
+
},
|
|
1375
|
+
// the folded variants, as ticks on the tubes of the walks carrying them
|
|
1376
|
+
get tubeMapDeviations() {
|
|
1377
|
+
const coarse = self.layoutResult?.tubeMap?.coarse
|
|
1378
|
+
const layout = self.layoutResult?.tubeMap?.layout
|
|
1379
|
+
return coarse && layout
|
|
1380
|
+
? deviationMarks(coarse.graph, layout, coarse.deviations)
|
|
1381
|
+
: []
|
|
1382
|
+
},
|
|
1383
|
+
// the reference boxes a linear view's connectors tie to its bp, which
|
|
1384
|
+
// the reference axis already puts under that bp
|
|
1385
|
+
get tubeMapReferenceNodes() {
|
|
1386
|
+
const layout = self.layoutResult
|
|
1387
|
+
return layout?.tubeMap && !layout.referenceAxis && self.drawnGraph
|
|
1388
|
+
? referenceNodes(self.drawnGraph, layout.tubeMap.layout)
|
|
1389
|
+
: undefined
|
|
1390
|
+
},
|
|
1391
|
+
}))
|
|
1392
|
+
.views(self => ({
|
|
1393
|
+
// A linear view has the genes in a track of their own, at their bp
|
|
1394
|
+
get tubeMapGenes() {
|
|
1395
|
+
const reference = self.tubeMapReference
|
|
1396
|
+
return self.showGenes && !self.host && reference && self.backboneGenes
|
|
1397
|
+
? tubeMapGenes(reference, self.backboneGenes)
|
|
1398
|
+
: []
|
|
1399
|
+
},
|
|
1400
|
+
}))
|
|
1401
|
+
.views(self => ({
|
|
1402
|
+
// `paneHeight` replaces the built-in ceiling rather than adding a second
|
|
1403
|
+
// clamp under it, and the floor still wins: a pane shorter than
|
|
1404
|
+
// MIN_CANVAS_HEIGHT leaves no room to hover a node and read its tooltip,
|
|
1405
|
+
// which is the reason that floor exists.
|
|
1406
|
+
get paneCeiling() {
|
|
1407
|
+
return Math.max(MIN_CANVAS_HEIGHT, self.paneHeight ?? MAX_CANVAS_HEIGHT)
|
|
1408
|
+
},
|
|
1409
|
+
// Room over a tube map for the rows its genes need, one inside the
|
|
1410
|
+
// padding and one more for each further gene that overlaps it
|
|
1411
|
+
get fitPadTop() {
|
|
1412
|
+
const rows = tubeMapGeneRows(self.tubeMapGenes)
|
|
1413
|
+
return FIT_PADDING + Math.max(0, rows - 1) * GENE_ROW_PX
|
|
1414
|
+
},
|
|
1415
|
+
// A tube map on its own axis reads by panning along it, as in
|
|
1416
|
+
// sequenceTubeMap. Rather than shrink a long cut to a strip, the fit
|
|
1417
|
+
// stops where its tubes are MIN_FIT_TUBE_PX wide, or at whatever fits
|
|
1418
|
+
// the tallest pane, with the cut's left end on screen.
|
|
1419
|
+
get minFitScale() {
|
|
1420
|
+
const bounds = self.layoutBounds
|
|
1421
|
+
const layout = self.layoutResult
|
|
1422
|
+
const tubePx = layout?.tubeMap?.layout.tracks[0]?.width
|
|
1423
|
+
return bounds && bounds.h > 0 && tubePx && !layout.referenceAxis
|
|
1424
|
+
? Math.min(
|
|
1425
|
+
MIN_FIT_TUBE_PX / tubePx,
|
|
1426
|
+
(this.paneCeiling - this.fitPadTop - FIT_PADDING) / bounds.h,
|
|
1427
|
+
)
|
|
1428
|
+
: 0
|
|
1429
|
+
},
|
|
1430
|
+
// The pane is as tall as the drawing, rather than a fixed box the drawing
|
|
1431
|
+
// floats in. A row layout's rows are px, so its height is a sum; an
|
|
1432
|
+
// isotropic layout has no height of its own and takes its aspect ratio
|
|
1433
|
+
// at the width's fit. Neither reads `scale`, so the fit reads this
|
|
1434
|
+
// without feeding back into it.
|
|
1435
|
+
get canvasHeight() {
|
|
1436
|
+
const bounds = self.layoutBounds
|
|
1437
|
+
const usableWidth = self.paneWidth - FIT_PADDING * 2
|
|
1438
|
+
const ceiling = this.paneCeiling
|
|
1439
|
+
if (!bounds) {
|
|
1440
|
+
return ceiling
|
|
1441
|
+
}
|
|
1442
|
+
if (self.pixelRows) {
|
|
1443
|
+
// The variant map's drawing is one line; its glyphs and labels are
|
|
1444
|
+
// painted above it by the overlay and need the room a row layout
|
|
1445
|
+
// would give to rows.
|
|
1446
|
+
const floor =
|
|
1447
|
+
self.chosenLayoutMode === 'variants'
|
|
1448
|
+
? VARIANT_MAP_HEIGHT
|
|
1449
|
+
: MIN_CANVAS_HEIGHT
|
|
1450
|
+
return Math.min(
|
|
1451
|
+
ceiling,
|
|
1452
|
+
Math.max(floor, bounds.h + this.fitPadTop + FIT_PADDING),
|
|
1453
|
+
)
|
|
1454
|
+
}
|
|
1455
|
+
return bounds.w > 0 && usableWidth > 0
|
|
1456
|
+
? Math.min(
|
|
1457
|
+
ceiling,
|
|
1458
|
+
Math.max(
|
|
1459
|
+
MIN_CANVAS_HEIGHT,
|
|
1460
|
+
bounds.h * Math.max(usableWidth / bounds.w, this.minFitScale) +
|
|
1461
|
+
this.fitPadTop +
|
|
1462
|
+
FIT_PADDING,
|
|
1463
|
+
),
|
|
1464
|
+
)
|
|
1465
|
+
: ceiling
|
|
1466
|
+
},
|
|
1467
|
+
// Where the fit puts the drawing, or undefined until there is a layout
|
|
1468
|
+
// and a measured canvas to fit it into
|
|
1469
|
+
get fittedTransform() {
|
|
1470
|
+
const bounds = self.layoutBounds
|
|
1471
|
+
return bounds
|
|
1472
|
+
? fitTransform(
|
|
1473
|
+
bounds,
|
|
1474
|
+
self.paneWidth,
|
|
1475
|
+
this.canvasHeight,
|
|
1476
|
+
self.pixelRows,
|
|
1477
|
+
{
|
|
1478
|
+
minScale: this.minFitScale,
|
|
1479
|
+
padLeft: self.fitPadLeft,
|
|
1480
|
+
padTop: this.fitPadTop,
|
|
1481
|
+
},
|
|
1482
|
+
)
|
|
1483
|
+
: undefined
|
|
1484
|
+
},
|
|
1485
|
+
}))
|
|
1486
|
+
.views(self => ({
|
|
1487
|
+
get overlayLabels() {
|
|
1488
|
+
return layoutLabels(self)
|
|
1489
|
+
},
|
|
1271
1490
|
get tubeMapFrame() {
|
|
1272
1491
|
const drawing = self.layoutResult?.tubeMap
|
|
1273
1492
|
return drawing
|
|
@@ -1276,16 +1495,75 @@ export function GraphPaneMixin() {
|
|
|
1276
1495
|
translateX: self.translateX,
|
|
1277
1496
|
scaleY: self.scaleY,
|
|
1278
1497
|
translateY: self.translateY,
|
|
1279
|
-
usableHeight: self.canvasHeight -
|
|
1498
|
+
usableHeight: self.canvasHeight - self.fitPadTop - FIT_PADDING,
|
|
1280
1499
|
})
|
|
1281
1500
|
: undefined
|
|
1282
1501
|
},
|
|
1283
1502
|
}))
|
|
1284
1503
|
.views(self => ({
|
|
1504
|
+
// the reference ruler under the tubes, unless the linear view's ruler
|
|
1505
|
+
// is already the axis
|
|
1506
|
+
get tubeMapRulerBoxes() {
|
|
1507
|
+
const reference = self.tubeMapReference
|
|
1508
|
+
return reference && !self.hostPlacesX
|
|
1509
|
+
? rulerBoxes(reference)
|
|
1510
|
+
: undefined
|
|
1511
|
+
},
|
|
1512
|
+
// The connectors run from the top of the pane down to the tubes' top
|
|
1513
|
+
get connectorZoneBottom() {
|
|
1514
|
+
const picture = self.tubeMapPicture
|
|
1515
|
+
const frame = self.tubeMapFrame
|
|
1516
|
+
return picture && frame ? frame.y(picture.bounds.minY) - 2 : 0
|
|
1517
|
+
},
|
|
1518
|
+
// Read off the live blocks, so the bands' tops follow every frame of a
|
|
1519
|
+
// pan in the linear view and their bottoms every pan of the tubes
|
|
1520
|
+
get tubeMapConnectors() {
|
|
1521
|
+
const nodes = self.tubeMapReferenceNodes
|
|
1522
|
+
const frame = self.tubeMapFrame
|
|
1523
|
+
const { host, graphRegion } = self
|
|
1524
|
+
const bp =
|
|
1525
|
+
nodes && frame && host?.initialized && graphRegion
|
|
1526
|
+
? hostFrame(host, graphRegion)
|
|
1527
|
+
: undefined
|
|
1528
|
+
return nodes && frame && bp
|
|
1529
|
+
? tubeMapConnectors(
|
|
1530
|
+
nodes,
|
|
1531
|
+
b => b * bp.scale + bp.translateX,
|
|
1532
|
+
frame.x,
|
|
1533
|
+
self.paneWidth,
|
|
1534
|
+
)
|
|
1535
|
+
: []
|
|
1536
|
+
},
|
|
1537
|
+
}))
|
|
1538
|
+
.views(self => ({
|
|
1539
|
+
// What the tube map's legend has to explain: a zigzag where the ruler
|
|
1540
|
+
// squeezes a box on screen, and the fold its walks' ticks stand for
|
|
1541
|
+
get tubeMapKeys() {
|
|
1542
|
+
const boxes = self.tubeMapRulerBoxes
|
|
1543
|
+
const frame = self.tubeMapFrame
|
|
1544
|
+
const marks =
|
|
1545
|
+
boxes && frame
|
|
1546
|
+
? rulerMarks(boxes, frame.x, self.paneWidth, () => 0)
|
|
1547
|
+
: undefined
|
|
1548
|
+
return {
|
|
1549
|
+
squeezed: (marks?.squeezed.length ?? 0) > 0,
|
|
1550
|
+
foldBp:
|
|
1551
|
+
self.tubeMapDeviations.length > 0 ? self.tubeMapFold : undefined,
|
|
1552
|
+
}
|
|
1553
|
+
},
|
|
1285
1554
|
tubeMapNodeAt(sx: number, sy: number) {
|
|
1286
1555
|
const drawing = self.layoutResult?.tubeMap
|
|
1287
1556
|
const frame = self.tubeMapFrame
|
|
1288
|
-
return
|
|
1557
|
+
return (
|
|
1558
|
+
(drawing && frame ? tubeMapNodeAt(drawing, frame, sx, sy) : null) ??
|
|
1559
|
+
connectorAt(
|
|
1560
|
+
self.tubeMapConnectors,
|
|
1561
|
+
self.connectorZoneBottom,
|
|
1562
|
+
sx,
|
|
1563
|
+
sy,
|
|
1564
|
+
) ??
|
|
1565
|
+
null
|
|
1566
|
+
)
|
|
1289
1567
|
},
|
|
1290
1568
|
}))
|
|
1291
1569
|
.actions(self => ({
|
|
@@ -1366,6 +1644,9 @@ export function GraphPaneMixin() {
|
|
|
1366
1644
|
setShowGenes(show: boolean) {
|
|
1367
1645
|
self.showGenes = show
|
|
1368
1646
|
},
|
|
1647
|
+
setTubeMapFold(bp: number) {
|
|
1648
|
+
self.tubeMapFold = bp
|
|
1649
|
+
},
|
|
1369
1650
|
setGeneTrackId(trackId: string) {
|
|
1370
1651
|
self.geneTrackId = trackId
|
|
1371
1652
|
},
|
|
@@ -1523,10 +1804,10 @@ export function GraphPaneMixin() {
|
|
|
1523
1804
|
self.positionsVersion++
|
|
1524
1805
|
},
|
|
1525
1806
|
zoomToFit() {
|
|
1526
|
-
const bounds = self.layoutBounds
|
|
1527
|
-
const usableHeight = self.canvasHeight - FIT_PADDING * 2
|
|
1528
1807
|
// A host owns x, so a fit while hosted places the rows only.
|
|
1529
1808
|
if (self.viewportOwner === 'host') {
|
|
1809
|
+
const bounds = self.layoutBounds
|
|
1810
|
+
const usableHeight = self.canvasHeight - FIT_PADDING * 2
|
|
1530
1811
|
if (bounds && usableHeight > 0) {
|
|
1531
1812
|
self.translateY = fittedTranslateY(
|
|
1532
1813
|
bounds,
|
|
@@ -1536,17 +1817,10 @@ export function GraphPaneMixin() {
|
|
|
1536
1817
|
}
|
|
1537
1818
|
return
|
|
1538
1819
|
}
|
|
1539
|
-
// Nothing to fit into before the canvas is measured. The autorun
|
|
1540
|
-
// once width lands, so skipping beats persisting a negative
|
|
1541
|
-
// the session snapshot.
|
|
1542
|
-
const fit =
|
|
1543
|
-
? fitTransform(
|
|
1544
|
-
bounds,
|
|
1545
|
-
self.paneWidth,
|
|
1546
|
-
self.canvasHeight,
|
|
1547
|
-
self.pixelRows,
|
|
1548
|
-
)
|
|
1549
|
-
: undefined
|
|
1820
|
+
// Nothing to fit into before the canvas is measured. The fit autorun
|
|
1821
|
+
// runs again once width lands, so skipping beats persisting a negative
|
|
1822
|
+
// scale into the session snapshot.
|
|
1823
|
+
const fit = self.fittedTransform
|
|
1550
1824
|
if (fit) {
|
|
1551
1825
|
self.scale = fit.scale
|
|
1552
1826
|
self.translateX = fit.translateX
|
|
@@ -1580,7 +1854,7 @@ export function GraphPaneMixin() {
|
|
|
1580
1854
|
// whose x no longer means bp is refit.
|
|
1581
1855
|
releaseHost() {
|
|
1582
1856
|
if (self.viewportOwner === 'host') {
|
|
1583
|
-
if (self.
|
|
1857
|
+
if (self.xIsReferenceBp) {
|
|
1584
1858
|
self.viewportOwner = 'user'
|
|
1585
1859
|
} else {
|
|
1586
1860
|
self.viewportOwner = 'fit'
|
|
@@ -1688,13 +1962,24 @@ export function GraphPaneMixin() {
|
|
|
1688
1962
|
// 'force' is expressed. See LAYOUT_MODES.
|
|
1689
1963
|
function* computeLayout(graph: Graph) {
|
|
1690
1964
|
const start = performance.now()
|
|
1965
|
+
// reads are placed by the cut's segment names, which a fold renames
|
|
1966
|
+
const coarse =
|
|
1967
|
+
TUBE_MAP_MODES.has(self.chosenLayoutMode) &&
|
|
1968
|
+
self.tubeMapFold > 0 &&
|
|
1969
|
+
!graph.reads
|
|
1970
|
+
? coarsenTubeMap(graph, self.tubeMapFold)
|
|
1971
|
+
: undefined
|
|
1691
1972
|
const local = layoutModeByValue(self.chosenLayoutMode).run(
|
|
1692
|
-
graph,
|
|
1973
|
+
coarse?.graph ?? graph,
|
|
1693
1974
|
self.graphRegion,
|
|
1694
1975
|
self.host ? self.layoutResult?.sampleRows : undefined,
|
|
1695
1976
|
)
|
|
1696
1977
|
if (local) {
|
|
1697
|
-
|
|
1978
|
+
const result =
|
|
1979
|
+
coarse && local.tubeMap
|
|
1980
|
+
? { ...local, tubeMap: { ...local.tubeMap, coarse } }
|
|
1981
|
+
: local
|
|
1982
|
+
return { result, duration: performance.now() - start }
|
|
1698
1983
|
}
|
|
1699
1984
|
const cache = forceLayoutsOf(graph)
|
|
1700
1985
|
const key = forceLayoutKey(graph)
|
|
@@ -1761,6 +2046,20 @@ export function GraphPaneMixin() {
|
|
|
1761
2046
|
return live
|
|
1762
2047
|
}
|
|
1763
2048
|
|
|
2049
|
+
// The walk a path graph's track puts on the region's assembly: the one
|
|
2050
|
+
// that names it, else the one chooseReferencePath infers
|
|
2051
|
+
function loadedReference(graph: Graph, region: SubgraphRegion) {
|
|
2052
|
+
const paths = graph.anchorPaths
|
|
2053
|
+
return graph.anchoredBy === 'paths' && paths
|
|
2054
|
+
? (
|
|
2055
|
+
assemblyWalk(
|
|
2056
|
+
graph,
|
|
2057
|
+
self.assemblySpellings(region.assemblyName),
|
|
2058
|
+
) ?? chooseReferencePath(paths, region.assemblyName)
|
|
2059
|
+
)?.name
|
|
2060
|
+
: undefined
|
|
2061
|
+
}
|
|
2062
|
+
|
|
1764
2063
|
// `keepSelection` for a re-cut of the same source: node ids survive one
|
|
1765
2064
|
// where edge indexes do not, so the selection is found again by id.
|
|
1766
2065
|
// `readsOf` fetches the reads over the parsed graph before its one
|
|
@@ -1774,10 +2073,17 @@ export function GraphPaneMixin() {
|
|
|
1774
2073
|
) {
|
|
1775
2074
|
const signal = loadController?.signal
|
|
1776
2075
|
self.setStatusMessage('Parsing GFA')
|
|
1777
|
-
const
|
|
2076
|
+
const parsed = loadGraph(text, name, {
|
|
1778
2077
|
referencePath: self.referencePath || region?.assemblyName,
|
|
1779
2078
|
maxNodes: self.maxGraphNodes,
|
|
1780
2079
|
})
|
|
2080
|
+
const loaded = region ? loadedReference(parsed, region) : undefined
|
|
2081
|
+
const graph =
|
|
2082
|
+
!self.referencePath &&
|
|
2083
|
+
loaded !== undefined &&
|
|
2084
|
+
loaded !== parsed.referencePath
|
|
2085
|
+
? anchorFromPaths(parsed, loaded)
|
|
2086
|
+
: parsed
|
|
1781
2087
|
if (readsOf) {
|
|
1782
2088
|
self.setStatusMessage('Reading alignments')
|
|
1783
2089
|
try {
|
|
@@ -1800,6 +2106,7 @@ export function GraphPaneMixin() {
|
|
|
1800
2106
|
const selected = keepSelection ? self.selectedNode : null
|
|
1801
2107
|
self.graph = graph
|
|
1802
2108
|
self.graphRegion = region
|
|
2109
|
+
self.loadedReferencePath = loaded
|
|
1803
2110
|
self.indexBubbles = undefined
|
|
1804
2111
|
self.geneFeatures = undefined
|
|
1805
2112
|
self.repeatArrays = undefined
|
|
@@ -1987,6 +2294,7 @@ export function GraphPaneMixin() {
|
|
|
1987
2294
|
abortLoad()
|
|
1988
2295
|
self.graph = undefined
|
|
1989
2296
|
self.graphRegion = undefined
|
|
2297
|
+
self.loadedReferencePath = undefined
|
|
1990
2298
|
self.layoutResult = undefined
|
|
1991
2299
|
self.indexBubbles = undefined
|
|
1992
2300
|
self.geneFeatures = undefined
|
|
@@ -2225,16 +2533,16 @@ export function GraphPaneMixin() {
|
|
|
2225
2533
|
startRenderingBackend(backend: Renderer) {
|
|
2226
2534
|
if (!self.autorunsInstalled) {
|
|
2227
2535
|
// Autorun: keep the view fitted to the graph until the user moves it.
|
|
2228
|
-
//
|
|
2229
|
-
//
|
|
2230
|
-
//
|
|
2231
|
-
//
|
|
2232
|
-
//
|
|
2536
|
+
// Reading fittedTransform tracks everything the fit depends on, so
|
|
2537
|
+
// it re-fits as the layout arrives, the canvas is measured and the
|
|
2538
|
+
// genes over a tube map claim their rows. A manual pan/zoom (or a
|
|
2539
|
+
// restored-session transform) makes the viewport the user's, and a
|
|
2540
|
+
// host makes it the linear view's.
|
|
2233
2541
|
addDisposer(
|
|
2234
2542
|
self,
|
|
2235
2543
|
autorun(() => {
|
|
2236
2544
|
if (
|
|
2237
|
-
self.
|
|
2545
|
+
self.fittedTransform &&
|
|
2238
2546
|
untracked(() => self.viewportOwner === 'fit')
|
|
2239
2547
|
) {
|
|
2240
2548
|
self.zoomToFit()
|
|
@@ -2595,6 +2903,23 @@ export function GraphPaneMixin() {
|
|
|
2595
2903
|
},
|
|
2596
2904
|
]
|
|
2597
2905
|
: []),
|
|
2906
|
+
...(TUBE_MAP_MODES.has(self.chosenLayoutMode)
|
|
2907
|
+
? [
|
|
2908
|
+
{
|
|
2909
|
+
label: 'Fold variants',
|
|
2910
|
+
subMenu: TUBE_MAP_FOLDS.map(({ bp, label }) => ({
|
|
2911
|
+
type: 'radio' as const,
|
|
2912
|
+
label,
|
|
2913
|
+
checked: self.tubeMapFold === bp,
|
|
2914
|
+
disabled: bp > 0 && self.graph?.reads !== undefined,
|
|
2915
|
+
onClick: () => {
|
|
2916
|
+
self.setTubeMapFold(bp)
|
|
2917
|
+
void self.recomputeLayout()
|
|
2918
|
+
},
|
|
2919
|
+
})),
|
|
2920
|
+
},
|
|
2921
|
+
]
|
|
2922
|
+
: []),
|
|
2598
2923
|
...(self.chosenLayoutMode === 'walkrows' &&
|
|
2599
2924
|
self.repeatChoices.length > 0
|
|
2600
2925
|
? [
|