jbrowse-plugin-graphgenomeviewer 7.4.3 → 7.5.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/README.md +10 -7
- package/dist/chunks/{AddTrackWorkflow-LK25EC3H.js → AddTrackWorkflow-NLSZYK56.js} +2 -2
- package/dist/chunks/{GbzBaseSyntenyAdapter-EKLUEECW.js → GbzBaseSyntenyAdapter-4KVWVUN6.js} +2 -2
- package/dist/chunks/{GbzBaseSyntenyAdapter-EKLUEECW.js.map → GbzBaseSyntenyAdapter-4KVWVUN6.js.map} +1 -1
- package/dist/chunks/{GraphGenomeView-P3XMSKUQ.js → GraphGenomeView-KI4FQ5A7.js} +2 -2
- package/dist/chunks/GraphTrackSettingsDialog-UQNMDXTE.js +4 -0
- package/dist/chunks/GraphTrackSettingsDialog-UQNMDXTE.js.map +7 -0
- package/dist/chunks/{HighlightColorDialog-G6AFRYUD.js → HighlightColorDialog-D4RCUCUF.js} +2 -2
- package/dist/chunks/LinearGraphDisplay-RPZUZSM2.js +2 -0
- package/dist/chunks/{LinearGraphDisplay-O4SQSXJJ.js.map → LinearGraphDisplay-RPZUZSM2.js.map} +2 -2
- package/dist/chunks/MinigraphBubbleAdapter-TJYI7KEN.js +2 -0
- package/dist/chunks/{MinigraphBubbleAdapter-3NJ2VLN2.js.map → MinigraphBubbleAdapter-TJYI7KEN.js.map} +1 -1
- package/dist/chunks/RgfaTabixAdapter-5ZMJLFZP.js +2 -0
- package/dist/chunks/RgfaTabixAdapter-5ZMJLFZP.js.map +7 -0
- package/dist/chunks/WalkFormsAdapter-CAJOWOEX.js +2 -0
- package/dist/chunks/WalkFormsAdapter-CAJOWOEX.js.map +7 -0
- package/dist/chunks/WalkTabixSyntenyAdapter-VI3KA2GX.js +2 -0
- package/dist/chunks/{WalkTabixSyntenyAdapter-NHNSZZBX.js.map → WalkTabixSyntenyAdapter-VI3KA2GX.js.map} +2 -2
- package/dist/chunks/{chunk-MCE6STZR.js → chunk-22ANI2KL.js} +2 -2
- package/dist/chunks/{chunk-MCE6STZR.js.map → chunk-22ANI2KL.js.map} +1 -1
- package/dist/chunks/chunk-42KD4AVY.js +2 -0
- package/dist/chunks/{chunk-OKRGO7XA.js.map → chunk-42KD4AVY.js.map} +4 -4
- package/dist/chunks/chunk-4NIWMRKH.js +2 -0
- package/dist/chunks/chunk-4NIWMRKH.js.map +7 -0
- package/dist/chunks/chunk-BCOXRUNQ.js +3 -0
- package/dist/chunks/chunk-BCOXRUNQ.js.map +7 -0
- package/dist/chunks/chunk-DAPI7M4M.js +4 -0
- package/dist/chunks/chunk-DAPI7M4M.js.map +7 -0
- package/dist/chunks/chunk-DEWZOJSC.js +2 -0
- package/dist/chunks/{chunk-6OXXT2YP.js.map → chunk-DEWZOJSC.js.map} +4 -4
- package/dist/chunks/{chunk-K4TFAHGC.js → chunk-FCMXEFJA.js} +1 -1
- package/dist/chunks/{chunk-K4TFAHGC.js.map → chunk-FCMXEFJA.js.map} +1 -1
- package/dist/chunks/{chunk-UOFWYS6T.js → chunk-GYY6LF5L.js} +1 -1
- package/dist/chunks/{chunk-UOFWYS6T.js.map → chunk-GYY6LF5L.js.map} +1 -1
- package/dist/chunks/{chunk-F2DMEJEK.js → chunk-HHMJNAA3.js} +2 -2
- package/dist/chunks/{chunk-NPV7SRJD.js → chunk-HTTONSO4.js} +2 -2
- package/dist/chunks/{chunk-NPV7SRJD.js.map → chunk-HTTONSO4.js.map} +1 -1
- package/dist/chunks/{chunk-MVVXRZXV.js → chunk-JRJHXLGP.js} +2 -2
- package/dist/chunks/{chunk-MVVXRZXV.js.map → chunk-JRJHXLGP.js.map} +2 -2
- package/dist/chunks/chunk-K4JL57NR.js +2 -0
- package/dist/chunks/chunk-K4JL57NR.js.map +7 -0
- package/dist/chunks/chunk-KOC23YNN.js +4 -0
- package/dist/chunks/chunk-KOC23YNN.js.map +7 -0
- package/dist/chunks/{chunk-MNCPMOHJ.js → chunk-LTNFVL2F.js} +2 -2
- package/dist/chunks/chunk-OZEKEYT3.js +2 -0
- package/dist/chunks/{chunk-ZAYC5OMQ.js.map → chunk-OZEKEYT3.js.map} +4 -4
- package/dist/chunks/{chunk-DUPBXBYE.js → chunk-POUV7OPY.js} +2 -2
- package/dist/chunks/{chunk-DUPBXBYE.js.map → chunk-POUV7OPY.js.map} +1 -1
- package/dist/chunks/{chunk-CNNZDE6I.js → chunk-SPL3UO2D.js} +2 -2
- package/dist/chunks/{chunk-CNNZDE6I.js.map → chunk-SPL3UO2D.js.map} +1 -1
- package/dist/chunks/{chunk-QJ43NDYS.js → chunk-TYV6UXJA.js} +2 -2
- package/dist/chunks/chunk-UR2MJJUP.js +2 -0
- package/dist/chunks/chunk-VTMJQFN5.js +2 -0
- package/dist/chunks/chunk-VTMJQFN5.js.map +7 -0
- package/dist/chunks/{chunk-CXOMX2I6.js → chunk-XZGYJ3JC.js} +1 -1
- package/dist/chunks/{chunk-CXOMX2I6.js.map → chunk-XZGYJ3JC.js.map} +1 -1
- package/dist/chunks/{chunk-TGSDXZXI.js → chunk-Z3IKZAYC.js} +2 -2
- package/dist/chunks/{chunk-TGSDXZXI.js.map → chunk-Z3IKZAYC.js.map} +1 -1
- package/dist/chunks/chunk-ZV5FNOHS.js +3 -0
- package/dist/chunks/chunk-ZV5FNOHS.js.map +7 -0
- package/dist/chunks/{renderSvg-7G6NWBXF.js → renderSvg-3PPAEMBT.js} +2 -2
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +4 -4
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
- package/package.json +3 -3
- 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 +294 -113
- package/src/GraphGenomeView/model.ts +3 -10
- package/src/GraphGenomeView/nodeSize.test.ts +21 -5
- package/src/GraphGenomeView/nodeSize.ts +24 -8
- package/src/GraphGenomeView/pane/fitViews.ts +5 -110
- package/src/GraphGenomeView/pane/graphViews.ts +96 -20
- package/src/GraphGenomeView/pane/launchActions.ts +55 -33
- 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 -7
- package/src/GraphGenomeView/pane/renderingActions.ts +61 -2
- package/src/GraphGenomeView/pane/settingActions.ts +62 -4
- package/src/GraphGenomeView/pane/walkRowViews.ts +17 -6
- package/src/GraphGenomeView/tubeMapPanels.ts +2 -1
- package/src/GraphGenomeView/viewModel.ts +4 -9
- package/src/GraphGenomeView/walkRowBlocks.ts +50 -0
- package/src/LinearGraphDisplay/LinearGraphDisplay.test.ts +35 -5
- 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 +4 -3
- package/src/RgfaTabixAdapter/RgfaTabixAdapter.ts +2 -9
- package/src/RgfaTabixAdapter/configSchema.ts +3 -3
- package/src/RgfaTabixAdapter/walkFiles.test.ts +6 -6
- package/src/WalkFormsAdapter/WalkFormsAdapter.test.ts +204 -0
- package/src/WalkFormsAdapter/WalkFormsAdapter.ts +232 -0
- package/src/WalkFormsAdapter/configSchema.ts +127 -0
- package/src/WalkFormsAdapter/formRows.test.ts +258 -0
- package/src/WalkFormsAdapter/formRows.ts +231 -0
- package/src/WalkFormsAdapter/formsMenu.test.ts +237 -0
- package/src/WalkFormsAdapter/formsMenu.ts +202 -0
- package/src/WalkFormsAdapter/index.ts +21 -0
- package/src/WalkFormsAdapter/pinned.ts +33 -0
- package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.test.ts +5 -5
- package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.ts +3 -6
- package/src/WalkTabixSyntenyAdapter/configSchema.ts +2 -2
- 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/index.ts +4 -0
- package/src/launchFromGraph/contributors.test.ts +52 -0
- package/src/launchFromGraph/contributors.ts +105 -1
- package/src/launchFromGraph/launchFromGraph.ts +2 -2
- package/src/panSNAliases/panSNAliases.test.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-UTYH6HEC.js +0 -6
- package/dist/chunks/GraphTrackSettingsDialog-UTYH6HEC.js.map +0 -7
- package/dist/chunks/LinearGraphDisplay-O4SQSXJJ.js +0 -2
- package/dist/chunks/MinigraphBubbleAdapter-3NJ2VLN2.js +0 -2
- package/dist/chunks/RgfaTabixAdapter-55DGDID2.js +0 -2
- package/dist/chunks/RgfaTabixAdapter-55DGDID2.js.map +0 -7
- package/dist/chunks/WalkTabixSyntenyAdapter-NHNSZZBX.js +0 -2
- package/dist/chunks/chunk-4I3K4RHE.js +0 -2
- package/dist/chunks/chunk-4I3K4RHE.js.map +0 -7
- package/dist/chunks/chunk-6OXXT2YP.js +0 -2
- package/dist/chunks/chunk-NFH2U74B.js +0 -2
- package/dist/chunks/chunk-OKRGO7XA.js +0 -2
- package/dist/chunks/chunk-P7VMOTUM.js +0 -3
- package/dist/chunks/chunk-P7VMOTUM.js.map +0 -7
- package/dist/chunks/chunk-RU4WSKBO.js +0 -3
- package/dist/chunks/chunk-RU4WSKBO.js.map +0 -7
- package/dist/chunks/chunk-WGTLNVST.js +0 -2
- package/dist/chunks/chunk-WGTLNVST.js.map +0 -7
- package/dist/chunks/chunk-XQFBLJU5.js +0 -4
- package/dist/chunks/chunk-XQFBLJU5.js.map +0 -7
- package/dist/chunks/chunk-Y5DNBFDY.js +0 -3
- package/dist/chunks/chunk-Y5DNBFDY.js.map +0 -7
- package/dist/chunks/chunk-ZAYC5OMQ.js +0 -2
- package/src/launchFromGraph/launchTracks.test.ts +0 -95
- package/src/launchFromGraph/launchTracks.ts +0 -49
- /package/dist/chunks/{AddTrackWorkflow-LK25EC3H.js.map → AddTrackWorkflow-NLSZYK56.js.map} +0 -0
- /package/dist/chunks/{GraphGenomeView-P3XMSKUQ.js.map → GraphGenomeView-KI4FQ5A7.js.map} +0 -0
- /package/dist/chunks/{HighlightColorDialog-G6AFRYUD.js.map → HighlightColorDialog-D4RCUCUF.js.map} +0 -0
- /package/dist/chunks/{chunk-F2DMEJEK.js.map → chunk-HHMJNAA3.js.map} +0 -0
- /package/dist/chunks/{chunk-MNCPMOHJ.js.map → chunk-LTNFVL2F.js.map} +0 -0
- /package/dist/chunks/{chunk-QJ43NDYS.js.map → chunk-TYV6UXJA.js.map} +0 -0
- /package/dist/chunks/{chunk-NFH2U74B.js.map → chunk-UR2MJJUP.js.map} +0 -0
- /package/dist/chunks/{renderSvg-7G6NWBXF.js.map → renderSvg-3PPAEMBT.js.map} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_follow.GRCh38.walks.bed.gz → walks5_follow.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_follow.GRCh38.walks.bed.gz.tbi → walks5_follow.GRCh38.walks.bed.gz.tbi} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_header.GRCh38.walks.bed.gz → walks5_header.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_header.GRCh38.walks.bed.gz.tbi → walks5_header.GRCh38.walks.bed.gz.tbi} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_inversion.GRCh38.walks.bed.gz → walks5_inversion.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_inversion.GRCh38.walks.bed.gz.tbi → walks5_inversion.GRCh38.walks.bed.gz.tbi} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_lanepairs.GRCh38.walks.bed.gz → walks5_lanepairs.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_lanepairs.GRCh38.walks.bed.gz.tbi → walks5_lanepairs.GRCh38.walks.bed.gz.tbi} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_maxnode.GRCh38.walks.bed.gz → walks5_maxnode.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_maxnode.GRCh38.walks.bed.gz.tbi → walks5_maxnode.GRCh38.walks.bed.gz.tbi} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_rejoin.GRCh38.walks.bed.gz → walks5_rejoin.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_rejoin.GRCh38.walks.bed.gz.tbi → walks5_rejoin.GRCh38.walks.bed.gz.tbi} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_twopart.GRCh38.walks.bed.gz → walks5_twopart.GRCh38.walks.bed.gz} +0 -0
- /package/src/RgfaTabixAdapter/test_data/{walks4_twopart.GRCh38.walks.bed.gz.tbi → walks5_twopart.GRCh38.walks.bed.gz.tbi} +0 -0
|
@@ -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
|
+
})
|
|
@@ -314,7 +314,8 @@ export function motionAllowed() {
|
|
|
314
314
|
}
|
|
315
315
|
|
|
316
316
|
// Calls `blend` with the eased progress once a frame for MORPH_MS while
|
|
317
|
-
// `isLive` holds, then `onEnd`. `finish` jumps to the last frame
|
|
317
|
+
// `isLive` holds, then `onEnd`. `finish` jumps to the last frame, as does a
|
|
318
|
+
// morph cut short, since the layout cache keeps the positions it blends.
|
|
318
319
|
export function runMorph(
|
|
319
320
|
blend: (t: number) => void,
|
|
320
321
|
isLive: () => boolean,
|
|
@@ -330,6 +331,7 @@ export function runMorph(
|
|
|
330
331
|
let last = performance.now()
|
|
331
332
|
const step = () => {
|
|
332
333
|
if (!isLive()) {
|
|
334
|
+
blend(1)
|
|
333
335
|
onEnd()
|
|
334
336
|
return
|
|
335
337
|
}
|
|
@@ -95,6 +95,8 @@ export const VIEWPORT_DEBOUNCE_MS = 150
|
|
|
95
95
|
export const VIEWPORT_PANES_BUILT = 1
|
|
96
96
|
// How many walk rows read their haplotype's genes, the first rows down
|
|
97
97
|
export const WALK_GENE_ROWS = 40
|
|
98
|
+
// How many off-reference contigs read their haplotype's genes
|
|
99
|
+
export const OWN_GENE_CONTIGS = 40
|
|
98
100
|
|
|
99
101
|
// What the canvas draws under the tube map, whose ink is all TubeMapOverlay's
|
|
100
102
|
export type TubeMapRoutes = 'each' | 'grouped' | 'bundled'
|
|
@@ -177,13 +179,6 @@ export function fileName(location: FileLocation) {
|
|
|
177
179
|
return path.split(/[\\/]/).at(-1) ?? ''
|
|
178
180
|
}
|
|
179
181
|
|
|
180
|
-
// A url bandage-figure can read again, or undefined for a local file
|
|
181
|
-
export function uriOf(location: FileLocation | undefined) {
|
|
182
|
-
return location && 'uri' in location && location.uri
|
|
183
|
-
? new URL(location.uri, location.baseUri ?? window.location.href).href
|
|
184
|
-
: undefined
|
|
185
|
-
}
|
|
186
|
-
|
|
187
182
|
type ViewportOwner = 'fit' | 'user' | 'host'
|
|
188
183
|
|
|
189
184
|
interface BuiltViewport {
|
|
@@ -366,6 +361,9 @@ export const paneBase = types
|
|
|
366
361
|
// each walk row's genes, read from its haplotype's own assembly, by walk
|
|
367
362
|
// name
|
|
368
363
|
walkGeneFeatures: undefined as Map<string, GeneModel[]> | undefined,
|
|
364
|
+
// each loaded haplotype's genes on its own nodes off the reference, under
|
|
365
|
+
// those nodes' stable names (ownGeneReads)
|
|
366
|
+
ownGeneFeatures: undefined as GeneModel[] | undefined,
|
|
369
367
|
// the source track's samples TSV, which walk rows group by
|
|
370
368
|
walkRowSampleTable: undefined as SampleRow[] | undefined,
|
|
371
369
|
// the tandem repeat arrays over the cut window, from the repeat track
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sameBubble } from '@jbrowse/bandage-core/bubbles/bubbleLine'
|
|
1
2
|
import { contains } from '@jbrowse/bandage-core/viewport'
|
|
2
3
|
import { getSession } from '@jbrowse/core/util'
|
|
3
4
|
import { addDisposer } from '@jbrowse/mobx-state-tree'
|
|
@@ -11,11 +12,16 @@ import {
|
|
|
11
12
|
paneViewportOf,
|
|
12
13
|
} from './paneBase'
|
|
13
14
|
import {
|
|
15
|
+
bubbleForLgvHover,
|
|
16
|
+
hoverFromPair,
|
|
14
17
|
hoverInRegion,
|
|
15
18
|
nodeForLgvHover,
|
|
16
19
|
readLgvHover,
|
|
20
|
+
readSelectedFeature,
|
|
17
21
|
} from '../../hoverSync/lgvHover'
|
|
18
22
|
import { walksForRow } from '../../hoverSync/rowWalks'
|
|
23
|
+
import { isRecord } from '../../isRecord'
|
|
24
|
+
import { withRows } from '../../launchFromGraph/linearViewTarget'
|
|
19
25
|
import { trackedDpr } from '../trackedDpr'
|
|
20
26
|
|
|
21
27
|
import type { LayoutEngineKind } from '@jbrowse/bandage-core/layoutEngines'
|
|
@@ -88,6 +94,23 @@ export const withRenderingActions = withLoadActions
|
|
|
88
94
|
},
|
|
89
95
|
),
|
|
90
96
|
)
|
|
97
|
+
// and the haplotypes' own genes on their nodes off the reference
|
|
98
|
+
addDisposer(
|
|
99
|
+
self,
|
|
100
|
+
reaction(
|
|
101
|
+
() => ({
|
|
102
|
+
graph: self.graph,
|
|
103
|
+
reads: JSON.stringify(self.ownGeneReads),
|
|
104
|
+
}),
|
|
105
|
+
() => {
|
|
106
|
+
void self.loadOwnGenes()
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
fireImmediately: true,
|
|
110
|
+
equals: (a, b) => a.graph === b.graph && a.reads === b.reads,
|
|
111
|
+
},
|
|
112
|
+
),
|
|
113
|
+
)
|
|
91
114
|
// Walk rows and tube map panels read the source track's samples
|
|
92
115
|
// TSV, to group by its columns, again for a graph after a dropped one
|
|
93
116
|
addDisposer(
|
|
@@ -145,8 +168,16 @@ export const withRenderingActions = withLoadActions
|
|
|
145
168
|
untracked(() => {
|
|
146
169
|
const region = self.graphRegion
|
|
147
170
|
const graph = self.graph
|
|
171
|
+
const liveViewIds = new Set(
|
|
172
|
+
withRows([...getSession(self).views]).flatMap(v =>
|
|
173
|
+
isRecord(v) && typeof v.id === 'string' ? [v.id] : [],
|
|
174
|
+
),
|
|
175
|
+
)
|
|
148
176
|
const inRegion =
|
|
149
|
-
hover &&
|
|
177
|
+
hover &&
|
|
178
|
+
region &&
|
|
179
|
+
hoverInRegion(hover, region) &&
|
|
180
|
+
hoverFromPair(hover, self.pairedViewId, liveViewIds)
|
|
150
181
|
? hover
|
|
151
182
|
: undefined
|
|
152
183
|
// a row naming a walk is the hover, not the node at its bp,
|
|
@@ -155,9 +186,14 @@ export const withRenderingActions = withLoadActions
|
|
|
155
186
|
inRegion?.featureName && graph?.paths
|
|
156
187
|
? walksForRow(inRegion.featureName, graph.paths)
|
|
157
188
|
: []
|
|
189
|
+
// a bubble track's feature, or a graph-derived VCF record,
|
|
190
|
+
// names a whole bubble, which lights whatever the setting
|
|
191
|
+
const bubble =
|
|
192
|
+
inRegion && bubbleForLgvHover(inRegion, self.bubbles)
|
|
158
193
|
if (region && graph && !self.pointerInPane) {
|
|
194
|
+
self.setHoveredBubble(bubble ?? null)
|
|
159
195
|
self.setHoveredNode(
|
|
160
|
-
inRegion && lightsNode && rowWalks.length === 0
|
|
196
|
+
inRegion && lightsNode && rowWalks.length === 0 && !bubble
|
|
161
197
|
? nodeForLgvHover({ hover: inRegion, nodes: graph.nodes })
|
|
162
198
|
: null,
|
|
163
199
|
)
|
|
@@ -167,6 +203,29 @@ export const withRenderingActions = withLoadActions
|
|
|
167
203
|
}),
|
|
168
204
|
)
|
|
169
205
|
|
|
206
|
+
// A click on a bubble feature or graph-derived variant in a linear
|
|
207
|
+
// view opens its bubble here, as clicking its label would
|
|
208
|
+
addDisposer(
|
|
209
|
+
self,
|
|
210
|
+
reaction(
|
|
211
|
+
() => readSelectedFeature(getSession(self).selection),
|
|
212
|
+
selected => {
|
|
213
|
+
const region = self.graphRegion
|
|
214
|
+
const bubble =
|
|
215
|
+
selected &&
|
|
216
|
+
region &&
|
|
217
|
+
hoverInRegion(selected, region) &&
|
|
218
|
+
bubbleForLgvHover(selected, self.bubbles)
|
|
219
|
+
if (
|
|
220
|
+
bubble &&
|
|
221
|
+
!self.openBubbles.some(open => sameBubble(open, bubble))
|
|
222
|
+
) {
|
|
223
|
+
self.toggleBubble(bubble)
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
),
|
|
227
|
+
)
|
|
228
|
+
|
|
170
229
|
// Reaction: drop a hover the pointer cannot still be over.
|
|
171
230
|
//
|
|
172
231
|
// A hover is set by a mousemove on the canvas, and here the drawing
|
|
@@ -20,7 +20,7 @@ import { nodeOwnLocation } from '../../launchFromGraph/contributors'
|
|
|
20
20
|
import { withRows } from '../../launchFromGraph/linearViewTarget'
|
|
21
21
|
import { withLayer } from '../graphLayers'
|
|
22
22
|
import { colorOfScheme } from '../nodeColor'
|
|
23
|
-
import {
|
|
23
|
+
import { sizeOf } from '../nodeSize'
|
|
24
24
|
|
|
25
25
|
import type { GraphGrammar } from './graphViews'
|
|
26
26
|
import type { ScreenTransform } from './keepInView'
|
|
@@ -32,6 +32,7 @@ import type { WalkRowGroupBy } from '../walkRowGroups'
|
|
|
32
32
|
import type { BubbleSpread } from '@jbrowse/bandage-core/bubbleSpreads'
|
|
33
33
|
import type { MinigraphBubble } from '@jbrowse/bandage-core/bubbles/bubbleLine'
|
|
34
34
|
import type { ColorScheme } from '@jbrowse/bandage-core/colorSchemes'
|
|
35
|
+
import type { EdgeWidth } from '@jbrowse/bandage-core/edgeWidths'
|
|
35
36
|
import type { FacetSetting } from '@jbrowse/bandage-core/facetGrid'
|
|
36
37
|
import type { LayoutEngineKind } from '@jbrowse/bandage-core/layoutEngines'
|
|
37
38
|
import type { LayoutModeValue } from '@jbrowse/bandage-core/layoutModes'
|
|
@@ -44,6 +45,8 @@ import type {
|
|
|
44
45
|
WalkLayer,
|
|
45
46
|
} from '@jbrowse/bandage-core/walkEncoding'
|
|
46
47
|
|
|
48
|
+
const ROW_HOVER_SETTLE_MS = 80
|
|
49
|
+
|
|
47
50
|
export const withSettingActions = withTubeMapViews
|
|
48
51
|
.actions(() => ({
|
|
49
52
|
// each host overrides: a view assigns its props, a track display writes
|
|
@@ -166,7 +169,20 @@ export const withSettingActions = withTubeMapViews
|
|
|
166
169
|
},
|
|
167
170
|
setNodeWidth(width: NodeWidth) {
|
|
168
171
|
self.writeGrammar({
|
|
169
|
-
size:
|
|
172
|
+
size: sizeOf({
|
|
173
|
+
width,
|
|
174
|
+
px: self.contigThickness,
|
|
175
|
+
links: self.linkWidth,
|
|
176
|
+
}),
|
|
177
|
+
})
|
|
178
|
+
},
|
|
179
|
+
setLinkWidth(links: EdgeWidth) {
|
|
180
|
+
self.writeGrammar({
|
|
181
|
+
size: sizeOf({
|
|
182
|
+
width: self.nodeWidth,
|
|
183
|
+
px: self.contigThickness,
|
|
184
|
+
links,
|
|
185
|
+
}),
|
|
170
186
|
})
|
|
171
187
|
},
|
|
172
188
|
setTubeMapFold(bp: number) {
|
|
@@ -237,7 +253,19 @@ export const withSettingActions = withTubeMapViews
|
|
|
237
253
|
)
|
|
238
254
|
},
|
|
239
255
|
setWalkRowSamples(samples: string[] | undefined) {
|
|
240
|
-
|
|
256
|
+
const order = self.walkRowOrder
|
|
257
|
+
self.writeGrammar({
|
|
258
|
+
rows: {
|
|
259
|
+
...(samples ? { kept: samples } : {}),
|
|
260
|
+
...(order ? { order } : {}),
|
|
261
|
+
},
|
|
262
|
+
})
|
|
263
|
+
},
|
|
264
|
+
setWalkRowOrder(order: 'shared' | undefined) {
|
|
265
|
+
const kept = self.walkRowSamples
|
|
266
|
+
self.writeGrammar({
|
|
267
|
+
rows: { ...(kept ? { kept } : {}), ...(order ? { order } : {}) },
|
|
268
|
+
})
|
|
241
269
|
},
|
|
242
270
|
setWalkRowGroupBy(groupBy: WalkRowGroupBy | undefined) {
|
|
243
271
|
if (groupBy || self.walkRowGroupBy) {
|
|
@@ -276,7 +304,7 @@ export const withSettingActions = withTubeMapViews
|
|
|
276
304
|
self.hoveredTube = tube
|
|
277
305
|
}
|
|
278
306
|
},
|
|
279
|
-
|
|
307
|
+
applyHoveredRowWalks(walks: string[]) {
|
|
280
308
|
if (walks.join('\n') !== self.hoveredRowWalks.join('\n')) {
|
|
281
309
|
self.hoveredRowWalks = walks
|
|
282
310
|
}
|
|
@@ -469,6 +497,36 @@ export const withSettingActions = withTubeMapViews
|
|
|
469
497
|
self.builtViewport = undefined
|
|
470
498
|
},
|
|
471
499
|
}))
|
|
500
|
+
.actions(self => {
|
|
501
|
+
let settle: ReturnType<typeof setTimeout> | undefined
|
|
502
|
+
let pending: string | undefined
|
|
503
|
+
return {
|
|
504
|
+
// A lane row's lift rebuilds the drawing (43 ms at 10k nodes), and a
|
|
505
|
+
// sweep down a lane crosses a row every few px, so a move from one row
|
|
506
|
+
// to the next lifts once the pointer has stayed on a row a moment.
|
|
507
|
+
// Entering and leaving the rows apply at once.
|
|
508
|
+
setHoveredRowWalks(walks: string[]) {
|
|
509
|
+
const key = walks.join('\n')
|
|
510
|
+
if (key === pending) {
|
|
511
|
+
return
|
|
512
|
+
}
|
|
513
|
+
clearTimeout(settle)
|
|
514
|
+
pending = undefined
|
|
515
|
+
if (walks.length > 0 && self.hoveredRowWalks.length > 0) {
|
|
516
|
+
pending = key
|
|
517
|
+
settle = setTimeout(() => {
|
|
518
|
+
pending = undefined
|
|
519
|
+
// a hover cleared meanwhile, by a pan, stays cleared
|
|
520
|
+
if (isAlive(self) && self.hoveredRowWalks.length > 0) {
|
|
521
|
+
self.applyHoveredRowWalks(walks)
|
|
522
|
+
}
|
|
523
|
+
}, ROW_HOVER_SETTLE_MS)
|
|
524
|
+
} else {
|
|
525
|
+
self.applyHoveredRowWalks(walks)
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
}
|
|
529
|
+
})
|
|
472
530
|
.actions(self => ({
|
|
473
531
|
// The frame clock. Unclamped, since clampZoom's floor is there to keep a
|
|
474
532
|
// scale finite and 1 / bpPerPx already is; clamped, a whole-chromosome
|
|
@@ -18,6 +18,7 @@ import { resolveLocationAssembly } from '../../launchFromGraph/contributors'
|
|
|
18
18
|
|
|
19
19
|
import type { SubgraphRegion } from '../../GetSubgraph'
|
|
20
20
|
import type { WalkAxis, WalkRow } from '@jbrowse/bandage-core/layout/walkRows'
|
|
21
|
+
import type { LayoutModeValue } from '@jbrowse/bandage-core/layoutModes'
|
|
21
22
|
|
|
22
23
|
export const withWalkRowViews = withLaunchViews
|
|
23
24
|
.views(self => ({
|
|
@@ -47,13 +48,23 @@ export const withWalkRowViews = withLaunchViews
|
|
|
47
48
|
}
|
|
48
49
|
return walkStripFrame(bars, { width: self.paneWidth })
|
|
49
50
|
},
|
|
50
|
-
// Whether a cut must follow every snarl a walk leaves the
|
|
51
|
-
// walks come back whole: walk rows measure them, and so does
|
|
51
|
+
// Whether a cut under `mode` must follow every snarl a walk leaves the
|
|
52
|
+
// window by, so walks come back whole: walk rows measure them, and so does
|
|
53
|
+
// the strip under a layout that draws nodes. Only a GBZ cut follows
|
|
54
|
+
// snarls; a walk file cuts the same either way, so asking would re-fetch
|
|
55
|
+
// an identical graph.
|
|
56
|
+
cutsWholeWalksIn(mode: LayoutModeValue, walkStrip = self.walkStrip) {
|
|
57
|
+
const m = layoutModeByValue(mode)
|
|
58
|
+
return (
|
|
59
|
+
self.sourceAdapter?.type === 'GbzBaseSyntenyAdapter' &&
|
|
60
|
+
wholeWalksRule(m, {
|
|
61
|
+
walkStrip: walkStrip && m.drawsNodes,
|
|
62
|
+
host: !!self.host,
|
|
63
|
+
})
|
|
64
|
+
)
|
|
65
|
+
},
|
|
52
66
|
get cutsWholeWalks() {
|
|
53
|
-
return
|
|
54
|
-
walkStrip: self.walkStrip,
|
|
55
|
-
host: !!self.host,
|
|
56
|
-
})
|
|
67
|
+
return this.cutsWholeWalksIn(self.chosenLayoutMode)
|
|
57
68
|
},
|
|
58
69
|
// Where each walk passes the hovered node, or the selected one
|
|
59
70
|
get walkStripMarks() {
|
|
@@ -215,9 +215,10 @@ export function walksThrough(graph: Graph, box: string) {
|
|
|
215
215
|
for (const p of graph.paths ?? []) {
|
|
216
216
|
const walk = walkOf(p.name)
|
|
217
217
|
if (walk !== graph.referencePath) {
|
|
218
|
+
const through = p.nodeIds.includes(box)
|
|
218
219
|
for (const member of p.members ?? [walk]) {
|
|
219
220
|
all.add(member)
|
|
220
|
-
if (
|
|
221
|
+
if (through) {
|
|
221
222
|
here.add(member)
|
|
222
223
|
}
|
|
223
224
|
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
LAYOUT_MODE_VALUES,
|
|
3
|
-
layoutModeByValue,
|
|
4
|
-
} from '@jbrowse/bandage-core/layoutModes'
|
|
1
|
+
import { LAYOUT_MODE_VALUES } from '@jbrowse/bandage-core/layoutModes'
|
|
5
2
|
import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes/models'
|
|
6
3
|
import { getSession } from '@jbrowse/core/util'
|
|
7
4
|
import { addDisposer, isAlive, types } from '@jbrowse/mobx-state-tree'
|
|
@@ -93,9 +90,6 @@ export default function stateModelFactory() {
|
|
|
93
90
|
get paneMeasured() {
|
|
94
91
|
return self.widthMeasured
|
|
95
92
|
},
|
|
96
|
-
get sourceGfaLocation() {
|
|
97
|
-
return self.gfaLocation
|
|
98
|
-
},
|
|
99
93
|
get sourceTrack(): AnyConfigurationModel | undefined {
|
|
100
94
|
return self.loadedTrackId
|
|
101
95
|
? self.sessionTrack(self.loadedTrackId)
|
|
@@ -225,10 +219,11 @@ export default function stateModelFactory() {
|
|
|
225
219
|
self,
|
|
226
220
|
reaction(
|
|
227
221
|
() => self.walkStrip,
|
|
228
|
-
|
|
222
|
+
on => {
|
|
229
223
|
if (
|
|
230
224
|
self.loadedTrackId &&
|
|
231
|
-
|
|
225
|
+
self.cutsWholeWalks !==
|
|
226
|
+
self.cutsWholeWalksIn(self.chosenLayoutMode, !on)
|
|
232
227
|
) {
|
|
233
228
|
void self.load()
|
|
234
229
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { WalkRowBlock } from '@jbrowse/bandage-core/layout/walkRowLinks'
|
|
2
|
+
|
|
3
|
+
// ggbandage's: the three biggest blocks in colour, the rest a pale grey that
|
|
4
|
+
// still carries its letter
|
|
5
|
+
const BLOCK_COLORS = ['#d4735e', '#d8a94a', '#c98aa4']
|
|
6
|
+
const OTHER_BLOCK = '#e6e6e6'
|
|
7
|
+
const BLOCK_EDGE = '#555'
|
|
8
|
+
const LETTER_FONT = 'bold 11px sans-serif'
|
|
9
|
+
const MIN_LETTER_PX = 12
|
|
10
|
+
const MIN_LETTER_BAR_PX = 9
|
|
11
|
+
|
|
12
|
+
export const blockFill = (block: number) => BLOCK_COLORS[block] ?? OTHER_BLOCK
|
|
13
|
+
|
|
14
|
+
// A, B, ... Z, then AA, AB, ...
|
|
15
|
+
export function blockLetter(block: number): string {
|
|
16
|
+
const letter = String.fromCharCode(65 + (block % 26))
|
|
17
|
+
return block < 26 ? letter : blockLetter(Math.floor(block / 26) - 1) + letter
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// How many blocks get a colour of their own, for the key
|
|
21
|
+
export const COLORED_BLOCKS = BLOCK_COLORS.length
|
|
22
|
+
|
|
23
|
+
// Each stretch as a box over its row's bar, lettered where it is wide enough
|
|
24
|
+
export function paintWalkRowBlocks(
|
|
25
|
+
ctx: CanvasRenderingContext2D,
|
|
26
|
+
blocks: WalkRowBlock[],
|
|
27
|
+
f: {
|
|
28
|
+
x: (offset: number) => number
|
|
29
|
+
rowCentre: (row: number) => number
|
|
30
|
+
barPx: number
|
|
31
|
+
},
|
|
32
|
+
) {
|
|
33
|
+
ctx.font = LETTER_FONT
|
|
34
|
+
ctx.textAlign = 'center'
|
|
35
|
+
ctx.textBaseline = 'middle'
|
|
36
|
+
for (const { row, start, bp, block } of blocks) {
|
|
37
|
+
const left = f.x(start)
|
|
38
|
+
const width = f.x(start + bp) - left
|
|
39
|
+
const top = f.rowCentre(row) - f.barPx / 2
|
|
40
|
+
ctx.fillStyle = blockFill(block)
|
|
41
|
+
ctx.fillRect(left, top, width, f.barPx)
|
|
42
|
+
ctx.strokeStyle = BLOCK_EDGE
|
|
43
|
+
ctx.lineWidth = 1
|
|
44
|
+
ctx.strokeRect(left + 0.5, top + 0.5, width - 1, f.barPx - 1)
|
|
45
|
+
if (width >= MIN_LETTER_PX && f.barPx >= MIN_LETTER_BAR_PX) {
|
|
46
|
+
ctx.fillStyle = '#111'
|
|
47
|
+
ctx.fillText(blockLetter(block), left + width / 2, f.rowCentre(row))
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|