jbrowse-plugin-graphgenomeviewer 7.4.4 → 7.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{GbzBaseSyntenyAdapter-A5MNPCKX.js → GbzBaseSyntenyAdapter-3PVPTKNW.js} +2 -2
- package/dist/chunks/{GbzBaseSyntenyAdapter-A5MNPCKX.js.map → GbzBaseSyntenyAdapter-3PVPTKNW.js.map} +1 -1
- package/dist/chunks/{GraphGenomeView-SEAPDRXS.js → GraphGenomeView-GQW4YC23.js} +2 -2
- package/dist/chunks/GraphTrackSettingsDialog-ARWCWJW4.js +4 -0
- package/dist/chunks/GraphTrackSettingsDialog-ARWCWJW4.js.map +7 -0
- package/dist/chunks/{HighlightColorDialog-QAYWLO5I.js → HighlightColorDialog-PD3KMF2U.js} +2 -2
- package/dist/chunks/LinearGraphDisplay-QNS4EAL3.js +2 -0
- package/dist/chunks/{LinearGraphDisplay-ZC4BQ27J.js.map → LinearGraphDisplay-QNS4EAL3.js.map} +2 -2
- package/dist/chunks/{MinigraphBubbleAdapter-3QM4XV47.js → MinigraphBubbleAdapter-L6JSQIOL.js} +2 -2
- package/dist/chunks/RgfaTabixAdapter-5HCAJ7QI.js +2 -0
- package/dist/chunks/RgfaTabixAdapter-5HCAJ7QI.js.map +7 -0
- package/dist/chunks/{WalkFormsAdapter-FT2SW4H6.js → WalkFormsAdapter-NX36GP35.js} +2 -2
- package/dist/chunks/WalkTabixSyntenyAdapter-JFNFX3JP.js +2 -0
- package/dist/chunks/{WalkTabixSyntenyAdapter-3WRVNDLB.js.map → WalkTabixSyntenyAdapter-JFNFX3JP.js.map} +2 -2
- package/dist/chunks/{chunk-KIYRMZWP.js → chunk-2CPD5NX2.js} +2 -2
- package/dist/chunks/{chunk-KIYRMZWP.js.map → chunk-2CPD5NX2.js.map} +1 -1
- package/dist/chunks/chunk-3AC33AXB.js +2 -0
- package/dist/chunks/{chunk-HF4H6LQI.js → chunk-4PHM6JNU.js} +2 -2
- package/dist/chunks/chunk-5LFPNQWH.js +2 -0
- package/dist/chunks/{chunk-6VNU7SD4.js.map → chunk-5LFPNQWH.js.map} +4 -4
- package/dist/chunks/{chunk-6WUWQX2P.js → chunk-CNEUOM7X.js} +2 -2
- package/dist/chunks/{chunk-6WUWQX2P.js.map → chunk-CNEUOM7X.js.map} +1 -1
- package/dist/chunks/{chunk-P3FBI5ML.js → chunk-DOC7QIE2.js} +1 -1
- package/dist/chunks/{chunk-P3FBI5ML.js.map → chunk-DOC7QIE2.js.map} +1 -1
- package/dist/chunks/{chunk-G53BI7VK.js → chunk-G4OPM6G7.js} +2 -2
- package/dist/chunks/{chunk-G53BI7VK.js.map → chunk-G4OPM6G7.js.map} +1 -1
- package/dist/chunks/chunk-GRLNDNX4.js +2 -0
- package/dist/chunks/{chunk-S333HOG5.js.map → chunk-GRLNDNX4.js.map} +4 -4
- package/dist/chunks/{chunk-7RJMG6I2.js → chunk-JYYF3U2H.js} +1 -1
- package/dist/chunks/{chunk-7RJMG6I2.js.map → chunk-JYYF3U2H.js.map} +1 -1
- package/dist/chunks/chunk-KDXVLEHS.js +3 -0
- package/dist/chunks/chunk-KDXVLEHS.js.map +7 -0
- package/dist/chunks/chunk-LGV6BTOY.js +2 -0
- package/dist/chunks/{chunk-HCDKP3IP.js.map → chunk-LGV6BTOY.js.map} +4 -4
- package/dist/chunks/{chunk-2KVE7YAS.js → chunk-LJ7O3736.js} +1 -1
- package/dist/chunks/{chunk-2KVE7YAS.js.map → chunk-LJ7O3736.js.map} +1 -1
- package/dist/chunks/{chunk-OJWBLPPH.js → chunk-OQHN2GWX.js} +2 -2
- package/dist/chunks/chunk-PTDOYJFH.js +4 -0
- package/dist/chunks/chunk-PTDOYJFH.js.map +7 -0
- package/dist/chunks/chunk-SGP63UMC.js +2 -0
- package/dist/chunks/chunk-SGP63UMC.js.map +7 -0
- package/dist/chunks/{chunk-XXBE4IFZ.js → chunk-SI5VLKVN.js} +2 -2
- package/dist/chunks/{chunk-XXBE4IFZ.js.map → chunk-SI5VLKVN.js.map} +1 -1
- package/dist/chunks/{chunk-6SWXMRAV.js → chunk-TWPUVVYH.js} +1 -1
- package/dist/chunks/{chunk-6SWXMRAV.js.map → chunk-TWPUVVYH.js.map} +1 -1
- package/dist/chunks/{chunk-FTXDTU27.js → chunk-XF6GU4GK.js} +3 -3
- package/dist/chunks/{chunk-FTXDTU27.js.map → chunk-XF6GU4GK.js.map} +2 -2
- package/dist/chunks/{chunk-MUONTMYZ.js → chunk-XSDGE4RF.js} +2 -2
- package/dist/chunks/{chunk-LE7FZM6Y.js → chunk-ZFJGNGIB.js} +2 -2
- package/dist/chunks/{chunk-LE7FZM6Y.js.map → chunk-ZFJGNGIB.js.map} +1 -1
- package/dist/chunks/chunk-ZI7RFIA2.js +4 -0
- package/dist/chunks/chunk-ZI7RFIA2.js.map +7 -0
- package/dist/chunks/{renderSvg-ZIUOPWJQ.js → renderSvg-NIWFCY3U.js} +2 -2
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +4 -3
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
- package/package.json +2 -2
- package/src/GraphComputeLayout.test.ts +24 -0
- package/src/GraphComputeLayout.ts +4 -2
- package/src/GraphGenomeView/components/GraphSettingsDialog.tsx +16 -0
- package/src/GraphGenomeView/components/WalkRowsOverlay.tsx +83 -0
- package/src/GraphGenomeView/components/usePaneGestures.test.tsx +82 -5
- package/src/GraphGenomeView/components/usePaneGestures.ts +10 -4
- package/src/GraphGenomeView/graphLayers.ts +14 -1
- package/src/GraphGenomeView/legendSections.ts +32 -0
- package/src/GraphGenomeView/model.test.ts +307 -7
- package/src/GraphGenomeView/model.ts +3 -10
- package/src/GraphGenomeView/nodeColor.test.ts +27 -1
- package/src/GraphGenomeView/nodeColor.ts +38 -29
- package/src/GraphGenomeView/nodeSize.test.ts +21 -5
- package/src/GraphGenomeView/nodeSize.ts +24 -8
- package/src/GraphGenomeView/pane/fitViews.ts +5 -1
- package/src/GraphGenomeView/pane/graphViews.ts +102 -20
- package/src/GraphGenomeView/pane/launchActions.ts +55 -11
- package/src/GraphGenomeView/pane/launchViews.ts +46 -0
- package/src/GraphGenomeView/pane/loadActions.ts +85 -25
- package/src/GraphGenomeView/pane/morph.test.ts +25 -1
- package/src/GraphGenomeView/pane/morph.ts +3 -1
- package/src/GraphGenomeView/pane/paneBase.ts +5 -0
- package/src/GraphGenomeView/pane/renderingActions.ts +61 -2
- package/src/GraphGenomeView/pane/settingActions.ts +65 -5
- package/src/GraphGenomeView/pane/walkRowViews.ts +17 -6
- package/src/GraphGenomeView/retiredNodeColor.test.ts +46 -0
- package/src/GraphGenomeView/retiredNodeColor.ts +76 -0
- package/src/GraphGenomeView/tubeMapPanels.ts +2 -1
- package/src/GraphGenomeView/viewModel.ts +16 -9
- package/src/GraphGenomeView/walkRowBlocks.ts +50 -0
- package/src/LinearGraphDisplay/LinearGraphDisplay.test.ts +97 -10
- package/src/LinearGraphDisplay/components/GraphStatusChrome.test.tsx +18 -0
- package/src/LinearGraphDisplay/components/GraphStatusChrome.tsx +4 -2
- package/src/LinearGraphDisplay/components/SubgraphHaplotypesField.test.tsx +41 -1
- package/src/LinearGraphDisplay/components/SubgraphHaplotypesField.tsx +33 -19
- package/src/LinearGraphDisplay/configSchema.ts +18 -10
- package/src/LinearGraphDisplay/model.ts +4 -2
- package/src/RgfaTabixAdapter/RgfaTabixAdapter.ts +2 -9
- package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.ts +3 -6
- package/src/hoverSync/graphViewHighlights.test.ts +53 -0
- package/src/hoverSync/graphViewHighlights.ts +25 -0
- package/src/hoverSync/lgvHover.test.ts +100 -1
- package/src/hoverSync/lgvHover.ts +77 -6
- package/src/launchFromGraph/contributors.test.ts +52 -0
- package/src/launchFromGraph/contributors.ts +105 -1
- package/src/launchFromGraph/launchFromGraph.ts +2 -2
- package/src/panSNTabix.test.ts +28 -0
- package/src/panSNTabix.ts +26 -8
- package/src/version.ts +1 -1
- package/dist/chunks/GraphTrackSettingsDialog-EVIIGC5G.js +0 -6
- package/dist/chunks/GraphTrackSettingsDialog-EVIIGC5G.js.map +0 -7
- package/dist/chunks/LinearGraphDisplay-ZC4BQ27J.js +0 -2
- package/dist/chunks/RgfaTabixAdapter-XSOIEWBK.js +0 -2
- package/dist/chunks/RgfaTabixAdapter-XSOIEWBK.js.map +0 -7
- package/dist/chunks/WalkTabixSyntenyAdapter-3WRVNDLB.js +0 -2
- package/dist/chunks/chunk-6VNU7SD4.js +0 -2
- package/dist/chunks/chunk-GRTN25B6.js +0 -3
- package/dist/chunks/chunk-GRTN25B6.js.map +0 -7
- package/dist/chunks/chunk-HCDKP3IP.js +0 -2
- package/dist/chunks/chunk-LE43I4PT.js +0 -4
- package/dist/chunks/chunk-LE43I4PT.js.map +0 -7
- package/dist/chunks/chunk-QV7SSOMJ.js +0 -2
- package/dist/chunks/chunk-S333HOG5.js +0 -2
- package/dist/chunks/chunk-VZQ6VOUP.js +0 -2
- package/dist/chunks/chunk-VZQ6VOUP.js.map +0 -7
- package/dist/chunks/chunk-WLRRI35A.js +0 -3
- package/dist/chunks/chunk-WLRRI35A.js.map +0 -7
- package/src/launchFromGraph/launchTracks.test.ts +0 -95
- package/src/launchFromGraph/launchTracks.ts +0 -49
- /package/dist/chunks/{GraphGenomeView-SEAPDRXS.js.map → GraphGenomeView-GQW4YC23.js.map} +0 -0
- /package/dist/chunks/{HighlightColorDialog-QAYWLO5I.js.map → HighlightColorDialog-PD3KMF2U.js.map} +0 -0
- /package/dist/chunks/{MinigraphBubbleAdapter-3QM4XV47.js.map → MinigraphBubbleAdapter-L6JSQIOL.js.map} +0 -0
- /package/dist/chunks/{WalkFormsAdapter-FT2SW4H6.js.map → WalkFormsAdapter-NX36GP35.js.map} +0 -0
- /package/dist/chunks/{chunk-QV7SSOMJ.js.map → chunk-3AC33AXB.js.map} +0 -0
- /package/dist/chunks/{chunk-HF4H6LQI.js.map → chunk-4PHM6JNU.js.map} +0 -0
- /package/dist/chunks/{chunk-OJWBLPPH.js.map → chunk-OQHN2GWX.js.map} +0 -0
- /package/dist/chunks/{chunk-MUONTMYZ.js.map → chunk-XSDGE4RF.js.map} +0 -0
- /package/dist/chunks/{renderSvg-ZIUOPWJQ.js.map → renderSvg-NIWFCY3U.js.map} +0 -0
|
@@ -520,6 +520,7 @@ export const withFitViews = withHostViews
|
|
|
520
520
|
connectorThickness: self.connectorThickness,
|
|
521
521
|
drawPaths,
|
|
522
522
|
nodeWidth: self.nodeWidth,
|
|
523
|
+
edgeWidth: self.linkWidth,
|
|
523
524
|
highlight,
|
|
524
525
|
// Untracked, so a zoom does not eagerly rebuild geometry — the
|
|
525
526
|
// debounced viewportDirty bump drives the scale-dependent rebuild
|
|
@@ -573,13 +574,16 @@ export const withFitViews = withHostViews
|
|
|
573
574
|
facet: self.facetSpec,
|
|
574
575
|
colorScheme: self.effectiveColorScheme,
|
|
575
576
|
nodeWidth: self.nodeWidth,
|
|
577
|
+
edgeWidth: self.linkWidth,
|
|
576
578
|
showDeletionEdges: self.showDeletionEdges,
|
|
577
579
|
linearLayout: self.linearLayout,
|
|
578
580
|
contigThickness: self.contigThickness,
|
|
579
581
|
connectorThickness: self.connectorThickness,
|
|
580
582
|
region: self.graphRegion,
|
|
581
583
|
colorDomain: self.statedColorDomain,
|
|
582
|
-
genes: self.showGenes
|
|
584
|
+
genes: self.showGenes
|
|
585
|
+
? [...(self.backboneGenes ?? []), ...(self.ownGeneFeatures ?? [])]
|
|
586
|
+
: undefined,
|
|
583
587
|
walkRows: self.walkRowBars,
|
|
584
588
|
walkRowPitch: self.walkRowPitch,
|
|
585
589
|
rowGenes: self.walkRowGenes,
|
|
@@ -12,6 +12,12 @@ import {
|
|
|
12
12
|
import { facetSettingOf } from '@jbrowse/bandage-core/facetGrid'
|
|
13
13
|
import { genePins } from '@jbrowse/bandage-core/genes/genePins'
|
|
14
14
|
import { formatBp } from '@jbrowse/bandage-core/graphLabels'
|
|
15
|
+
import {
|
|
16
|
+
ribbonJoin,
|
|
17
|
+
sharedRowOrder,
|
|
18
|
+
walkRowBlocks,
|
|
19
|
+
walkRowLinks,
|
|
20
|
+
} from '@jbrowse/bandage-core/layout/walkRowLinks'
|
|
15
21
|
import {
|
|
16
22
|
walkRowRuns,
|
|
17
23
|
walkRowsOf,
|
|
@@ -89,7 +95,7 @@ import type { ScreenTransform } from './keepInView'
|
|
|
89
95
|
import type { WalkCut } from '../../RgfaTabixAdapter/walkCut.ts'
|
|
90
96
|
import type { GraphLayers } from '../graphLayers'
|
|
91
97
|
import type { HoverHighlight } from '../hoverHighlight'
|
|
92
|
-
import type {
|
|
98
|
+
import type { GraphEncoding } from '../nodeColor'
|
|
93
99
|
import type { NodeSize } from '../nodeSize'
|
|
94
100
|
import type { TubeMapPanelSplit } from '../tubeMapPanels'
|
|
95
101
|
import type { WalkRowGroupBy } from '../walkRowGroups'
|
|
@@ -98,6 +104,7 @@ import type {
|
|
|
98
104
|
ColorScheme,
|
|
99
105
|
ResolvedColorScheme,
|
|
100
106
|
} from '@jbrowse/bandage-core/colorSchemes'
|
|
107
|
+
import type { EdgeWidth } from '@jbrowse/bandage-core/edgeWidths'
|
|
101
108
|
import type { FacetInput, FacetSetting } from '@jbrowse/bandage-core/facetGrid'
|
|
102
109
|
import type { LayoutModeValue } from '@jbrowse/bandage-core/layoutModes'
|
|
103
110
|
import type { NodeWidth } from '@jbrowse/bandage-core/nodeWidths'
|
|
@@ -110,12 +117,16 @@ import type { AnyConfigurationModel } from '@jbrowse/core/configuration'
|
|
|
110
117
|
import type { FileLocation } from '@jbrowse/core/util/types'
|
|
111
118
|
|
|
112
119
|
// A repeat pick slices the cut's walks again, by the repeat's own array
|
|
120
|
+
// The rows again from the cut's tables: for a repeat pick, by the repeat's
|
|
121
|
+
// own array, or with each row's visits, which ribbons need and the worker's
|
|
122
|
+
// runs leave out
|
|
113
123
|
function walkRowsFromCut(
|
|
114
124
|
cut: WalkCut,
|
|
115
125
|
region: { start: number; end: number },
|
|
116
126
|
unit?: number,
|
|
127
|
+
visits?: boolean,
|
|
117
128
|
) {
|
|
118
|
-
const runs = walkRowRuns(cut, region)
|
|
129
|
+
const runs = walkRowRuns(cut, region, { visits })
|
|
119
130
|
return runs ? walkRowsOf(runs, unit) : undefined
|
|
120
131
|
}
|
|
121
132
|
|
|
@@ -151,7 +162,7 @@ function spanIn(
|
|
|
151
162
|
// as props, a track display in its config
|
|
152
163
|
export interface GraphGrammar {
|
|
153
164
|
layoutMode?: LayoutModeValue
|
|
154
|
-
|
|
165
|
+
encoding?: GraphEncoding
|
|
155
166
|
size?: NodeSize
|
|
156
167
|
layers?: GraphLayers
|
|
157
168
|
facet?: FacetInput
|
|
@@ -162,9 +173,12 @@ export interface GraphGrammar {
|
|
|
162
173
|
type TubeColorKind = 'value' | 'tinted' | 'role' | 'path'
|
|
163
174
|
|
|
164
175
|
// The rows walk rows draw: `kept` names the samples whose walks show, by the
|
|
165
|
-
// name before the haplotype number; unset shows every walk the cut holds
|
|
176
|
+
// name before the haplotype number; unset shows every walk the cut holds.
|
|
177
|
+
// `order: 'shared'` stacks each row under the one it shares the most sequence
|
|
178
|
+
// with, so ribbons stay short and shared blocks line up.
|
|
166
179
|
export interface GraphRows {
|
|
167
180
|
kept?: string[]
|
|
181
|
+
order?: 'shared'
|
|
168
182
|
}
|
|
169
183
|
|
|
170
184
|
export const withGraphViews = paneBase
|
|
@@ -199,7 +213,9 @@ export const withGraphViews = paneBase
|
|
|
199
213
|
computed(derive, {
|
|
200
214
|
equals: (a, b) => JSON.stringify(a) === JSON.stringify(b),
|
|
201
215
|
})
|
|
202
|
-
const colorDomain = byValue(() =>
|
|
216
|
+
const colorDomain = byValue(() =>
|
|
217
|
+
domainOfColor(self.grammar.encoding?.color),
|
|
218
|
+
)
|
|
203
219
|
const facet = byValue(() => facetSettingOf(self.grammar.facet))
|
|
204
220
|
const groupBy = byValue(() => {
|
|
205
221
|
const g = groupByOf(self.grammar.facet)
|
|
@@ -210,7 +226,7 @@ export const withGraphViews = paneBase
|
|
|
210
226
|
return self.grammar.layoutMode ?? 'force'
|
|
211
227
|
},
|
|
212
228
|
get chosenColorScheme(): ColorScheme {
|
|
213
|
-
return schemeOfColor(self.grammar.color)
|
|
229
|
+
return schemeOfColor(self.grammar.encoding?.color)
|
|
214
230
|
},
|
|
215
231
|
// the reference span the reference-position ramp runs over, for a graph
|
|
216
232
|
// with no region of its own to span it
|
|
@@ -227,6 +243,10 @@ export const withGraphViews = paneBase
|
|
|
227
243
|
get contigThickness() {
|
|
228
244
|
return nodeSizeOf(self.grammar.size).px
|
|
229
245
|
},
|
|
246
|
+
// Link thickness by the walks taking it; see EDGE_WIDTHS
|
|
247
|
+
get linkWidth(): EdgeWidth {
|
|
248
|
+
return nodeSizeOf(self.grammar.size).links
|
|
249
|
+
},
|
|
230
250
|
get layerSet() {
|
|
231
251
|
return layersOf(self.grammar.layers)
|
|
232
252
|
},
|
|
@@ -248,6 +268,9 @@ export const withGraphViews = paneBase
|
|
|
248
268
|
get walkStrip() {
|
|
249
269
|
return this.layerSet.has('walkStrip')
|
|
250
270
|
},
|
|
271
|
+
get walkRibbons() {
|
|
272
|
+
return this.layerSet.has('walkRibbons')
|
|
273
|
+
},
|
|
251
274
|
// One section per value of a field, as a grammar of graphics splits a
|
|
252
275
|
// plot. A node layout draws a panel per `walk` (each lifted walk alone,
|
|
253
276
|
// on the same layout) or per `sample` (a sample's haplotypes in a row);
|
|
@@ -265,6 +288,9 @@ export const withGraphViews = paneBase
|
|
|
265
288
|
const kept = self.grammar.rows?.kept
|
|
266
289
|
return Array.isArray(kept) ? kept : undefined
|
|
267
290
|
},
|
|
291
|
+
get walkRowOrder() {
|
|
292
|
+
return self.grammar.rows?.order === 'shared' ? 'shared' : undefined
|
|
293
|
+
},
|
|
268
294
|
get hoverLightsEverything() {
|
|
269
295
|
return this.chosenHover === 'everything'
|
|
270
296
|
},
|
|
@@ -740,12 +766,26 @@ export const withGraphViews = paneBase
|
|
|
740
766
|
// Each walk sliced to the window, for walk rows and the strip; a repeat
|
|
741
767
|
// pick slices again by its own array
|
|
742
768
|
get cutWalkRows() {
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
769
|
+
const cut = self.walkCut
|
|
770
|
+
const runs = cut?.walkRowRuns
|
|
771
|
+
const visits = this.rowVisitsNeeded
|
|
772
|
+
return cut && runs && visits && self.graphRegion
|
|
773
|
+
? walkRowsFromCut(cut, self.graphRegion, undefined, true)
|
|
774
|
+
: runs
|
|
775
|
+
? walkRowsOf(runs)
|
|
776
|
+
: self.graph
|
|
777
|
+
? walkRows(self.graph, self.graphRegion, undefined, { visits })
|
|
778
|
+
: undefined
|
|
779
|
+
},
|
|
780
|
+
// Ribbons, shared blocks and the shared order compare the rows node by
|
|
781
|
+
// node, so the rows carry their visits
|
|
782
|
+
get rowVisitsNeeded() {
|
|
783
|
+
return (
|
|
784
|
+
self.chosenLayoutMode === 'walkrows' &&
|
|
785
|
+
(self.walkRibbons ||
|
|
786
|
+
self.layerSet.has('walkBlocks') ||
|
|
787
|
+
self.walkRowOrder === 'shared')
|
|
788
|
+
)
|
|
749
789
|
},
|
|
750
790
|
// An assembly by each name a backbone may spell it with: the session's
|
|
751
791
|
// name and aliases, and the PanSN prefix the source track maps each of
|
|
@@ -852,10 +892,11 @@ export const withGraphViews = paneBase
|
|
|
852
892
|
}
|
|
853
893
|
const repeat = self.selectedRepeat
|
|
854
894
|
const cut = self.walkCut
|
|
895
|
+
const visits = self.rowVisitsNeeded
|
|
855
896
|
const bars = repeat
|
|
856
897
|
? cut
|
|
857
|
-
? walkRowsFromCut(cut, repeat, repeat.unit)
|
|
858
|
-
: walkRows(self.graph, repeat, repeat.unit)
|
|
898
|
+
? walkRowsFromCut(cut, repeat, repeat.unit, visits)
|
|
899
|
+
: walkRows(self.graph, repeat, repeat.unit, { visits })
|
|
859
900
|
: self.cutWalkRows
|
|
860
901
|
if (!bars) {
|
|
861
902
|
return undefined
|
|
@@ -929,10 +970,18 @@ export const withGraphViews = paneBase
|
|
|
929
970
|
? { by: 'column', ...grouped.groupBy, table: grouped.table }
|
|
930
971
|
: undefined
|
|
931
972
|
},
|
|
932
|
-
//
|
|
973
|
+
// The cohort's rows the sample filter keeps, in the shared order where
|
|
974
|
+
// asked; a grouping then keeps that order within each section
|
|
933
975
|
get keptCohortRows() {
|
|
934
976
|
const bars = this.cohortWalkRows
|
|
935
|
-
|
|
977
|
+
const rows = bars && filterSamples(bars.rows, self.walkRowSamples)
|
|
978
|
+
if (!bars || !rows || self.walkRowOrder !== 'shared') {
|
|
979
|
+
return rows
|
|
980
|
+
}
|
|
981
|
+
const all = [bars.reference, ...rows]
|
|
982
|
+
return sharedRowOrder(all)
|
|
983
|
+
.filter(i => i > 0)
|
|
984
|
+
.map(i => all[i]!)
|
|
936
985
|
},
|
|
937
986
|
// The kept rows in a section per value of the grouped column, or
|
|
938
987
|
// undefined while ungrouped or the table lacks that column
|
|
@@ -952,6 +1001,27 @@ export const withGraphViews = paneBase
|
|
|
952
1001
|
? { ...bars, rows: this.walkRowGroups?.rows ?? rows }
|
|
953
1002
|
: undefined
|
|
954
1003
|
},
|
|
1004
|
+
// Where each row walks the same nodes as the row below it, as bar
|
|
1005
|
+
// offsets on both, the reference row first
|
|
1006
|
+
get walkRowRibbons() {
|
|
1007
|
+
const bars = this.walkRowBars
|
|
1008
|
+
if (!self.walkRibbons || !self.rowVisitsNeeded || !bars) {
|
|
1009
|
+
return undefined
|
|
1010
|
+
}
|
|
1011
|
+
const rows = [bars.reference, ...bars.rows]
|
|
1012
|
+
return walkRowLinks(rows, ribbonJoin(rows))
|
|
1013
|
+
},
|
|
1014
|
+
// The stretches of each row through a block of nodes off the
|
|
1015
|
+
// reference's path that two or more rows walk, block 0 the one with the
|
|
1016
|
+
// most bp, which the bars letter A, B, C...
|
|
1017
|
+
get sharedBlocks() {
|
|
1018
|
+
const bars = this.walkRowBars
|
|
1019
|
+
if (!self.layerSet.has('walkBlocks') || !self.rowVisitsNeeded || !bars) {
|
|
1020
|
+
return undefined
|
|
1021
|
+
}
|
|
1022
|
+
const rows = [bars.reference, ...bars.rows]
|
|
1023
|
+
return walkRowBlocks(rows, ribbonJoin(rows))
|
|
1024
|
+
},
|
|
955
1025
|
// Whether the canvas draws the graph's nodes. A tube map and walk rows
|
|
956
1026
|
// draw a picture of their own over it, so what sits on nodes (genes,
|
|
957
1027
|
// bubble halos, deletion arcs, the reference strip) has nowhere to go.
|
|
@@ -967,12 +1037,17 @@ export const withGraphViews = paneBase
|
|
|
967
1037
|
get genePins() {
|
|
968
1038
|
dependOn(self.positionsVersion)
|
|
969
1039
|
const positions = self.layoutResult?.nodePositions
|
|
1040
|
+
// each haplotype's own genes ride its nodes off the reference
|
|
1041
|
+
const genes = [
|
|
1042
|
+
...(self.backboneGenes ?? []),
|
|
1043
|
+
...(self.ownGeneFeatures ?? []),
|
|
1044
|
+
]
|
|
970
1045
|
return self.showGenes &&
|
|
971
1046
|
this.drawsNodes &&
|
|
972
1047
|
self.graph &&
|
|
973
|
-
|
|
1048
|
+
genes.length > 0 &&
|
|
974
1049
|
positions
|
|
975
|
-
? genePins(self.graph,
|
|
1050
|
+
? genePins(self.graph, genes, positions)
|
|
976
1051
|
: []
|
|
977
1052
|
},
|
|
978
1053
|
// The bubbles as halos along their nodes.
|
|
@@ -980,10 +1055,17 @@ export const withGraphViews = paneBase
|
|
|
980
1055
|
get bubbleHalos() {
|
|
981
1056
|
dependOn(self.positionsVersion)
|
|
982
1057
|
const positions = self.layoutResult?.nodePositions
|
|
983
|
-
|
|
1058
|
+
// with the layer off, a bubble hovered from a linear view's bubble or
|
|
1059
|
+
// variant track still rings
|
|
1060
|
+
const shown = self.showBubbles
|
|
1061
|
+
? self.bubbles
|
|
1062
|
+
: self.hoveredBubble
|
|
1063
|
+
? [self.hoveredBubble]
|
|
1064
|
+
: []
|
|
1065
|
+
if (shown.length === 0 || !this.drawsNodes || !self.graph || !positions) {
|
|
984
1066
|
return []
|
|
985
1067
|
}
|
|
986
|
-
return bubbleHalos(self.graph,
|
|
1068
|
+
return bubbleHalos(self.graph, shown, positions, self.repeatArrays)
|
|
987
1069
|
},
|
|
988
1070
|
// What `reference-position` paints from, which is also what `auto`
|
|
989
1071
|
// resolves to on any anchored graph
|
|
@@ -22,14 +22,15 @@ import {
|
|
|
22
22
|
import { withRenderingActions } from './renderingActions'
|
|
23
23
|
import { graphLaunchMenuItems } from '../../launchFromGraph/graphMenuItems'
|
|
24
24
|
import {
|
|
25
|
+
canonicalAssemblyName,
|
|
25
26
|
highlightInLinearView,
|
|
26
27
|
launchSyntenyView,
|
|
27
28
|
showInLinearView,
|
|
28
29
|
} from '../../launchFromGraph/launchFromGraph'
|
|
29
|
-
import { launchTracks } from '../../launchFromGraph/launchTracks'
|
|
30
30
|
import { pushLaunchItem } from '../../launchFromGraph/pushLaunchItem'
|
|
31
31
|
import { launchableSyntenyTracks } from '../../launchFromGraph/syntenyTracks'
|
|
32
32
|
import { downloadText } from '../download'
|
|
33
|
+
import { GENE_ADAPTER_TYPES } from '../genes/geneFeatures'
|
|
33
34
|
import { HOVER_HIGHLIGHT_VALUES } from '../hoverHighlight'
|
|
34
35
|
import { TUBE_MAP_PANELS, tubeMapPanelGroups } from '../tubeMapPanels'
|
|
35
36
|
|
|
@@ -67,10 +68,10 @@ function radioItems<T>(
|
|
|
67
68
|
export const withLaunchActions = withRenderingActions
|
|
68
69
|
.actions(self => ({
|
|
69
70
|
// Move the linear view already on screen, or open one if there is none,
|
|
70
|
-
// and pair with whichever it was. A view being created gets the
|
|
71
|
-
//
|
|
72
|
-
// graph
|
|
73
|
-
// assembly
|
|
71
|
+
// and pair with whichever it was. A view being created gets the graph's
|
|
72
|
+
// own track when the launch is on the reference, plus the one gene track
|
|
73
|
+
// the graph reads for that assembly: a hub config can hold hundreds of
|
|
74
|
+
// FeatureTracks per assembly, and a launch is a jump to a locus.
|
|
74
75
|
//
|
|
75
76
|
// Only a view on the reference is paired with. A node's hover band is on
|
|
76
77
|
// that assembly, so paired with a view on another the band fails that
|
|
@@ -79,16 +80,31 @@ export const withLaunchActions = withRenderingActions
|
|
|
79
80
|
showInLinearView(target: { location: GraphLocation; assembly: string }) {
|
|
80
81
|
const session = getSession(self)
|
|
81
82
|
const onReference = target.assembly === self.graphRegion?.assemblyName
|
|
83
|
+
const assembly = canonicalAssemblyName(session, target.assembly)
|
|
84
|
+
// an assembly whose annotation nothing names as genes, a strain's BED,
|
|
85
|
+
// still opens on its first feature track rather than on none
|
|
86
|
+
const geneTrack =
|
|
87
|
+
(onReference
|
|
88
|
+
? self.geneTrack
|
|
89
|
+
: self.geneTracksByAssembly.get(assembly)) ??
|
|
90
|
+
self.sessionTracks.find(
|
|
91
|
+
t =>
|
|
92
|
+
t.assemblies.includes(assembly) &&
|
|
93
|
+
GENE_ADAPTER_TYPES.has(t.adapterType),
|
|
94
|
+
)
|
|
82
95
|
const viewId = showInLinearView({
|
|
83
96
|
session,
|
|
84
97
|
location: target.location,
|
|
85
98
|
assembly: target.assembly,
|
|
86
99
|
connectedViewId: self.pairedViewId,
|
|
87
|
-
tracks:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
100
|
+
tracks: [
|
|
101
|
+
...new Set(
|
|
102
|
+
[
|
|
103
|
+
onReference ? self.sourceTrackId : undefined,
|
|
104
|
+
geneTrack?.trackId,
|
|
105
|
+
].filter(id => id !== undefined),
|
|
106
|
+
),
|
|
107
|
+
],
|
|
92
108
|
})
|
|
93
109
|
if (onReference) {
|
|
94
110
|
self.pairWithLinearView(viewId)
|
|
@@ -282,7 +298,35 @@ export const withLaunchActions = withRenderingActions
|
|
|
282
298
|
]
|
|
283
299
|
: []),
|
|
284
300
|
...(self.chosenLayoutMode === 'walkrows' && self.walkRowBars
|
|
285
|
-
? [
|
|
301
|
+
? [
|
|
302
|
+
{ label: 'Samples', subMenu: this.walkRowSampleMenuItems() },
|
|
303
|
+
{
|
|
304
|
+
type: 'checkbox' as const,
|
|
305
|
+
label: 'Ribbons between rows',
|
|
306
|
+
checked: self.walkRibbons,
|
|
307
|
+
onClick: () => {
|
|
308
|
+
self.setLayer('walkRibbons', !self.walkRibbons)
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
type: 'checkbox' as const,
|
|
313
|
+
label: 'Letter shared blocks',
|
|
314
|
+
checked: self.layerSet.has('walkBlocks'),
|
|
315
|
+
onClick: () => {
|
|
316
|
+
self.setLayer('walkBlocks', !self.layerSet.has('walkBlocks'))
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
type: 'checkbox' as const,
|
|
321
|
+
label: 'Order rows by shared sequence',
|
|
322
|
+
checked: self.walkRowOrder === 'shared',
|
|
323
|
+
onClick: () => {
|
|
324
|
+
self.setWalkRowOrder(
|
|
325
|
+
self.walkRowOrder === 'shared' ? undefined : 'shared',
|
|
326
|
+
)
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
]
|
|
286
330
|
: []),
|
|
287
331
|
...(self.chosenLayoutMode === 'walkrows' &&
|
|
288
332
|
self.walkRowBars &&
|
|
@@ -5,9 +5,12 @@ import {
|
|
|
5
5
|
import { getSession } from '@jbrowse/core/util'
|
|
6
6
|
|
|
7
7
|
import { withGraphViews } from './graphViews'
|
|
8
|
+
import { OWN_GENE_CONTIGS } from './paneBase'
|
|
8
9
|
import {
|
|
9
10
|
contributingAssemblies,
|
|
11
|
+
nodeAssemblySpans,
|
|
10
12
|
nodeOwnLocation,
|
|
13
|
+
offReferenceContigs,
|
|
11
14
|
resolveContributors,
|
|
12
15
|
resolveLocationAssembly,
|
|
13
16
|
} from '../../launchFromGraph/contributors'
|
|
@@ -170,4 +173,47 @@ export const withLaunchViews = withGraphViews
|
|
|
170
173
|
: undefined,
|
|
171
174
|
}
|
|
172
175
|
},
|
|
176
|
+
// The gene reads that put each loaded haplotype's own genes on its nodes
|
|
177
|
+
// off the reference: its assembly's gene track over the span those nodes
|
|
178
|
+
// cover, the first OWN_GENE_CONTIGS contigs of them
|
|
179
|
+
get ownGeneReads() {
|
|
180
|
+
const { graph } = self
|
|
181
|
+
if (!graph || !self.showGenes || !self.modeDrawsNodes) {
|
|
182
|
+
return []
|
|
183
|
+
}
|
|
184
|
+
return offReferenceContigs(
|
|
185
|
+
graph,
|
|
186
|
+
self.assemblyResolver,
|
|
187
|
+
self.graphRegion?.assemblyName,
|
|
188
|
+
)
|
|
189
|
+
.flatMap(({ stableRefName, assemblyName, refName, start, end }) => {
|
|
190
|
+
const track = self.geneTracksByAssembly.get(assemblyName)
|
|
191
|
+
return track
|
|
192
|
+
? [
|
|
193
|
+
{
|
|
194
|
+
stableRefName,
|
|
195
|
+
trackId: track.trackId,
|
|
196
|
+
region: { assemblyName, refName, start, end },
|
|
197
|
+
},
|
|
198
|
+
]
|
|
199
|
+
: []
|
|
200
|
+
})
|
|
201
|
+
.slice(0, OWN_GENE_CONTIGS)
|
|
202
|
+
},
|
|
203
|
+
// The hovered node's own sequence on every loaded assembly but the
|
|
204
|
+
// reference, for linear views of those assemblies to band: an allele off
|
|
205
|
+
// the reference is a few hundred bp "instead of" on GRCh38, and here it
|
|
206
|
+
// is where it actually lives on each haplotype
|
|
207
|
+
get assemblyHoverHighlights() {
|
|
208
|
+
const id = self.hoveredNode
|
|
209
|
+
const node = id === null ? undefined : self.nodeById?.get(id)
|
|
210
|
+
return node && self.graph && self.chosenHover !== 'off'
|
|
211
|
+
? nodeAssemblySpans(
|
|
212
|
+
self.graph,
|
|
213
|
+
node,
|
|
214
|
+
self.assemblyResolver,
|
|
215
|
+
self.graphRegion?.assemblyName,
|
|
216
|
+
)
|
|
217
|
+
: []
|
|
218
|
+
},
|
|
173
219
|
}))
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { walkRowLayoutOf } from '@jbrowse/bandage-core/layout/walkRowLayout'
|
|
10
10
|
import { walkRowsOf } from '@jbrowse/bandage-core/layout/walkRowRuns'
|
|
11
11
|
import { layoutModeByValue } from '@jbrowse/bandage-core/layoutModes'
|
|
12
|
+
import { panSNHaplotype, panSNSample } from '@jbrowse/bandage-core/pansn'
|
|
12
13
|
import {
|
|
13
14
|
anchorFromPaths,
|
|
14
15
|
chooseReferencePath,
|
|
@@ -93,6 +94,15 @@ export const withLoadActions = withEngineSettings.actions(self => {
|
|
|
93
94
|
}) as Promise<{ result: LayoutResult; duration: number }>
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
// the node cap is a drawing's cost
|
|
98
|
+
function assertDrawable(graph: Graph) {
|
|
99
|
+
if (graph.nodes.length > self.maxGraphNodes) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Graph too large to draw: ${graph.nodes.length.toLocaleString()} nodes (limit ${self.maxGraphNodes.toLocaleString()}). Pick Layout → Walk rows, zoom in to a smaller region, or raise the node limit in Graph settings.`,
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
96
106
|
// Single dispatch point for every layout mode. A mode that returns a
|
|
97
107
|
// result computed it locally; one that returns undefined can't draw this
|
|
98
108
|
// graph and hands off to the remote FMMM engine, which is also how
|
|
@@ -150,6 +160,8 @@ export const withLoadActions = withEngineSettings.actions(self => {
|
|
|
150
160
|
mode: self.chosenLayoutMode,
|
|
151
161
|
}
|
|
152
162
|
}
|
|
163
|
+
// walk rows and tube maps fall back here for a graph with no walks
|
|
164
|
+
assertDrawable(graph)
|
|
153
165
|
const settings = self.engineSettings
|
|
154
166
|
const hit = forceLayouts.ready(graph, settings)
|
|
155
167
|
if (hit) {
|
|
@@ -209,11 +221,9 @@ export const withLoadActions = withEngineSettings.actions(self => {
|
|
|
209
221
|
const signal = loadController?.signal
|
|
210
222
|
let computed
|
|
211
223
|
try {
|
|
212
|
-
//
|
|
213
|
-
if (self.modeDrawsNodes
|
|
214
|
-
|
|
215
|
-
`Graph too large to draw: ${graph.nodes.length.toLocaleString()} nodes (limit ${self.maxGraphNodes.toLocaleString()}). Pick Layout → Walk rows, zoom in to a smaller region, or raise the node limit in Graph settings.`,
|
|
216
|
-
)
|
|
224
|
+
// walk rows draw a bar per walk, not a node each
|
|
225
|
+
if (self.modeDrawsNodes) {
|
|
226
|
+
assertDrawable(graph)
|
|
217
227
|
}
|
|
218
228
|
computed = yield* computeLayout(graph)
|
|
219
229
|
} catch (e) {
|
|
@@ -314,6 +324,7 @@ export const withLoadActions = withEngineSettings.actions(self => {
|
|
|
314
324
|
self.indexBubbles = undefined
|
|
315
325
|
self.geneTrackFeatures = undefined
|
|
316
326
|
self.walkGeneFeatures = undefined
|
|
327
|
+
self.ownGeneFeatures = undefined
|
|
317
328
|
walkGeneCache.clear()
|
|
318
329
|
self.repeatArrays = undefined
|
|
319
330
|
self.clearInteractionState()
|
|
@@ -478,11 +489,18 @@ export const withLoadActions = withEngineSettings.actions(self => {
|
|
|
478
489
|
{ adapterConfig, regions: [region], signal },
|
|
479
490
|
)) as Feature[]
|
|
480
491
|
} catch (e) {
|
|
481
|
-
|
|
492
|
+
// a cut the next one replaced aborts its reads, which is not a failure
|
|
493
|
+
if (!signal?.aborted) {
|
|
494
|
+
console.warn(`[GraphGenomeView] no ${what} for this graph`, e)
|
|
495
|
+
}
|
|
482
496
|
return undefined
|
|
483
497
|
}
|
|
484
498
|
}
|
|
485
499
|
|
|
500
|
+
// Bubble indexes a read failed on, so a graph without one, the ordinary
|
|
501
|
+
// case, is not asked again on every re-cut of a pan
|
|
502
|
+
const missingBubbleIndexes = new Set<string>()
|
|
503
|
+
|
|
486
504
|
// The bubble index that the hosted HPRC build keeps beside its segments,
|
|
487
505
|
// `<prefix>.bubbles.bed.gz`, read through the bubble adapter over the same
|
|
488
506
|
// window. A graph whose source has no such file, the ordinary case for a
|
|
@@ -499,13 +517,18 @@ export const withLoadActions = withEngineSettings.actions(self => {
|
|
|
499
517
|
// The track config arrives as written, so the prefix is either the
|
|
500
518
|
// `uri` shorthand or the segments location it expands to.
|
|
501
519
|
const prefix = bubblePrefix(adapterConfig)
|
|
502
|
-
|
|
520
|
+
const uri = `${prefix}.bubbles.bed.gz`
|
|
521
|
+
if (
|
|
522
|
+
adapterConfig.type !== 'RgfaTabixAdapter' ||
|
|
523
|
+
prefix === undefined ||
|
|
524
|
+
missingBubbleIndexes.has(uri)
|
|
525
|
+
) {
|
|
503
526
|
return
|
|
504
527
|
}
|
|
505
528
|
const features = yield* readFeatures(
|
|
506
529
|
{
|
|
507
530
|
type: 'MinigraphBubbleAdapter',
|
|
508
|
-
uri
|
|
531
|
+
uri,
|
|
509
532
|
baseUri: adapterConfig.baseUri,
|
|
510
533
|
assemblyNameToPanSN: adapterConfig.assemblyNameToPanSN,
|
|
511
534
|
},
|
|
@@ -513,6 +536,9 @@ export const withLoadActions = withEngineSettings.actions(self => {
|
|
|
513
536
|
'bubble index',
|
|
514
537
|
signal,
|
|
515
538
|
)
|
|
539
|
+
if (features === undefined && !signal?.aborted) {
|
|
540
|
+
missingBubbleIndexes.add(uri)
|
|
541
|
+
}
|
|
516
542
|
if (features && isLive()) {
|
|
517
543
|
self.indexBubbles = features.flatMap(f => {
|
|
518
544
|
const start = f.get('start')
|
|
@@ -559,6 +585,25 @@ export const withLoadActions = withEngineSettings.actions(self => {
|
|
|
559
585
|
)
|
|
560
586
|
}
|
|
561
587
|
|
|
588
|
+
// A haplotype assembly's genes over a span of its own contig, named for
|
|
589
|
+
// that contig whatever the track calls it, read once per cut
|
|
590
|
+
function* haplotypeGenes(trackId: string, region: SubgraphRegion) {
|
|
591
|
+
const cacheKey = `${trackId} ${region.refName}:${region.start}-${region.end}`
|
|
592
|
+
let genes = walkGeneCache.get(cacheKey)
|
|
593
|
+
if (!genes) {
|
|
594
|
+
const features = yield* trackFeatures(trackId, region, 'haplotype genes')
|
|
595
|
+
if (!features) {
|
|
596
|
+
return undefined
|
|
597
|
+
}
|
|
598
|
+
genes = geneModelsFrom(features).map(g => ({
|
|
599
|
+
...g,
|
|
600
|
+
refName: region.refName,
|
|
601
|
+
}))
|
|
602
|
+
walkGeneCache.set(cacheKey, genes)
|
|
603
|
+
}
|
|
604
|
+
return genes
|
|
605
|
+
}
|
|
606
|
+
|
|
562
607
|
// The annotation tracks read over the cut: genes, which `geneFeatures`
|
|
563
608
|
// makes genes of for the backbone to carry, and tandem repeat arrays, for
|
|
564
609
|
// the walk rows to measure between and tile by
|
|
@@ -860,24 +905,10 @@ export const withLoadActions = withEngineSettings.actions(self => {
|
|
|
860
905
|
yield Promise.all(
|
|
861
906
|
reads.map(({ row, trackId, region }) =>
|
|
862
907
|
flow(function* () {
|
|
863
|
-
const
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
const features = yield* trackFeatures(
|
|
867
|
-
trackId,
|
|
868
|
-
region,
|
|
869
|
-
'haplotype genes',
|
|
870
|
-
)
|
|
871
|
-
if (!features) {
|
|
872
|
-
return
|
|
873
|
-
}
|
|
874
|
-
genes = geneModelsFrom(features).map(g => ({
|
|
875
|
-
...g,
|
|
876
|
-
refName: region.refName,
|
|
877
|
-
}))
|
|
878
|
-
walkGeneCache.set(cacheKey, genes)
|
|
908
|
+
const genes = yield* haplotypeGenes(trackId, region)
|
|
909
|
+
if (genes) {
|
|
910
|
+
fetched.set(row, genes)
|
|
879
911
|
}
|
|
880
|
-
fetched.set(row, genes)
|
|
881
912
|
})(),
|
|
882
913
|
),
|
|
883
914
|
)
|
|
@@ -889,6 +920,35 @@ export const withLoadActions = withEngineSettings.actions(self => {
|
|
|
889
920
|
self.walkGeneFeatures = reads.length ? fetched : undefined
|
|
890
921
|
}
|
|
891
922
|
}),
|
|
923
|
+
// Each loaded haplotype's genes on its own off-reference nodes, renamed
|
|
924
|
+
// to those nodes' stable sequence so genePins lands them there
|
|
925
|
+
loadOwnGenes: flow(function* () {
|
|
926
|
+
const reads = self.ownGeneReads
|
|
927
|
+
const key = JSON.stringify(reads)
|
|
928
|
+
const graph = self.graph
|
|
929
|
+
const fetched = (yield Promise.all(
|
|
930
|
+
reads.map(({ stableRefName, trackId, region }) =>
|
|
931
|
+
flow(function* () {
|
|
932
|
+
const genes = yield* haplotypeGenes(trackId, region)
|
|
933
|
+
// a chip reading LPA twice would not say whose copy each is
|
|
934
|
+
const whose =
|
|
935
|
+
panSNHaplotype(stableRefName) ?? panSNSample(stableRefName)
|
|
936
|
+
return (genes ?? []).map(g => ({
|
|
937
|
+
...g,
|
|
938
|
+
name: `${g.name} · ${whose}`,
|
|
939
|
+
refName: stableRefName,
|
|
940
|
+
}))
|
|
941
|
+
})(),
|
|
942
|
+
),
|
|
943
|
+
)) as GeneModel[][]
|
|
944
|
+
if (
|
|
945
|
+
isAlive(self) &&
|
|
946
|
+
self.graph === graph &&
|
|
947
|
+
JSON.stringify(self.ownGeneReads) === key
|
|
948
|
+
) {
|
|
949
|
+
self.ownGeneFeatures = reads.length ? fetched.flat() : undefined
|
|
950
|
+
}
|
|
951
|
+
}),
|
|
892
952
|
// Open a bubble in place: it keeps its colour, the rest of the drawing
|
|
893
953
|
// greys, and the bubbles inside it get labels of their own. Opening an
|
|
894
954
|
// open one closes it and those inside it.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { abgrAlpha, packAbgr } from '@jbrowse/bandage-core/renderer/colorBits'
|
|
2
|
-
import { expect, test } from 'vitest'
|
|
2
|
+
import { expect, test, vi } from 'vitest'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
blendInto,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
fadeEntering,
|
|
8
8
|
morphStarts,
|
|
9
9
|
routeStarts,
|
|
10
|
+
runMorph,
|
|
10
11
|
} from './morph'
|
|
11
12
|
|
|
12
13
|
import type { RenderBatch } from '@jbrowse/bandage-core/renderer/types'
|
|
@@ -248,3 +249,26 @@ test('a graph past the frame budget snaps', () => {
|
|
|
248
249
|
expect(canMorph(free, free, 3000, false)).toBe(true)
|
|
249
250
|
expect(canMorph(free, free, 3001, false)).toBe(false)
|
|
250
251
|
})
|
|
252
|
+
|
|
253
|
+
// The layout cache holds the positions a morph blends, so a morph abandoned
|
|
254
|
+
// half way would leave a cached layout half blended for the next cache hit
|
|
255
|
+
test('a morph cut short lands its last frame', () => {
|
|
256
|
+
const frames: FrameRequestCallback[] = []
|
|
257
|
+
vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) =>
|
|
258
|
+
frames.push(cb),
|
|
259
|
+
)
|
|
260
|
+
const blends: number[] = []
|
|
261
|
+
let live = true
|
|
262
|
+
const ended = vi.fn()
|
|
263
|
+
runMorph(
|
|
264
|
+
t => blends.push(t),
|
|
265
|
+
() => live,
|
|
266
|
+
ended,
|
|
267
|
+
)
|
|
268
|
+
frames.shift()!(0)
|
|
269
|
+
live = false
|
|
270
|
+
frames.shift()!(0)
|
|
271
|
+
expect(blends.at(-1)).toBe(1)
|
|
272
|
+
expect(ended).toHaveBeenCalledTimes(1)
|
|
273
|
+
vi.unstubAllGlobals()
|
|
274
|
+
})
|