jbrowse-plugin-graphgenomeviewer 1.0.0 → 3.0.3
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/LICENSE +674 -0
- package/README.md +202 -24
- package/dist/chunks/AddTrackWorkflow-Y3OBHM33.js +2 -0
- package/dist/chunks/AddTrackWorkflow-Y3OBHM33.js.map +7 -0
- package/dist/chunks/GbzBaseSyntenyAdapter-EFIOI2SN.js +8 -0
- package/dist/chunks/GbzBaseSyntenyAdapter-EFIOI2SN.js.map +7 -0
- package/dist/chunks/GraphGenomeView-UD5WCV3Q.js +14 -0
- package/dist/chunks/GraphGenomeView-UD5WCV3Q.js.map +7 -0
- package/dist/chunks/MinigraphBubbleAdapter-B75VQK7T.js +2 -0
- package/dist/chunks/MinigraphBubbleAdapter-B75VQK7T.js.map +7 -0
- package/dist/chunks/RgfaTabixAdapter-IT7I5RR3.js +3 -0
- package/dist/chunks/RgfaTabixAdapter-IT7I5RR3.js.map +7 -0
- package/dist/chunks/bandage-layout-FIOZ7B3E.js +1660 -0
- package/dist/chunks/bandage-layout-FIOZ7B3E.js.map +7 -0
- package/dist/chunks/chunk-D7XFR2IV.js +2 -0
- package/dist/chunks/chunk-D7XFR2IV.js.map +7 -0
- package/dist/chunks/chunk-F4C3BEPV.js +2 -0
- package/dist/chunks/chunk-F4C3BEPV.js.map +7 -0
- package/dist/chunks/chunk-FCWXYIFC.js +2 -0
- package/dist/chunks/chunk-FCWXYIFC.js.map +7 -0
- package/dist/chunks/chunk-FY3BTKY4.js +2 -0
- package/dist/chunks/chunk-FY3BTKY4.js.map +7 -0
- package/dist/chunks/chunk-J4OLMLS5.js +2 -0
- package/dist/chunks/chunk-J4OLMLS5.js.map +7 -0
- package/dist/chunks/chunk-JCG7RLMB.js +2 -0
- package/dist/chunks/chunk-JCG7RLMB.js.map +7 -0
- package/dist/chunks/chunk-KWAXLUPQ.js +2 -0
- package/dist/chunks/chunk-KWAXLUPQ.js.map +7 -0
- package/dist/chunks/chunk-LGKM2PZH.js +2 -0
- package/dist/chunks/chunk-LGKM2PZH.js.map +7 -0
- package/dist/chunks/chunk-TN6FK5UD.js +2 -0
- package/dist/chunks/chunk-TN6FK5UD.js.map +7 -0
- package/dist/chunks/chunk-X3X74OGB.js +2 -0
- package/dist/chunks/chunk-X3X74OGB.js.map +7 -0
- package/dist/chunks/chunk-XEJ47DLD.js +2 -0
- package/dist/chunks/chunk-XEJ47DLD.js.map +7 -0
- package/dist/chunks/chunk-ZNGDWZ6Z.js +2 -0
- package/dist/chunks/chunk-ZNGDWZ6Z.js.map +7 -0
- package/dist/chunks/executeDiagonalize-XMPEDHVZ.js +2 -0
- package/dist/chunks/executeDiagonalize-XMPEDHVZ.js.map +7 -0
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +3 -0
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +7 -0
- package/package.json +94 -49
- package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.test.ts +427 -0
- package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.ts +461 -0
- package/src/GbzBaseSyntenyAdapter/configSchema.ts +181 -0
- package/src/GbzBaseSyntenyAdapter/index.ts +27 -0
- package/src/GbzBaseSyntenyAdapter/test_data/micb-kir3dl1.gbz.db +0 -0
- package/src/GbzBaseSyntenyAdapter/test_data/micb-kir3dl1.haplotype-index.db +0 -0
- package/src/GetSubgraph.test.ts +193 -0
- package/src/GetSubgraph.ts +93 -0
- package/src/GraphAddTrackWorkflow/AddTrackWorkflow.tsx +167 -0
- package/src/GraphAddTrackWorkflow/buildTrackConfig.test.ts +150 -0
- package/src/GraphAddTrackWorkflow/buildTrackConfig.ts +114 -0
- package/src/GraphAddTrackWorkflow/index.ts +20 -0
- package/src/GraphComputeLayout.ts +46 -0
- package/src/GraphGenomeView/anchoredNodes.ts +101 -0
- package/src/GraphGenomeView/bubbleSpreads.ts +102 -0
- package/src/GraphGenomeView/bubbles/bubbleHalos.test.ts +58 -0
- package/src/GraphGenomeView/bubbles/bubbleHalos.ts +149 -0
- package/src/GraphGenomeView/bubbles/bubbleRoutes.test.ts +141 -0
- package/src/GraphGenomeView/bubbles/bubblesFromGraph.test.ts +239 -0
- package/src/GraphGenomeView/bubbles/bubblesFromGraph.ts +210 -0
- package/src/GraphGenomeView/bubbles/classifyBubble.test.ts +172 -0
- package/src/GraphGenomeView/bubbles/classifyBubble.ts +138 -0
- package/src/GraphGenomeView/bubbles/popBubble.test.ts +73 -0
- package/src/GraphGenomeView/bubbles/popBubble.ts +26 -0
- package/src/GraphGenomeView/colorSchemes.ts +36 -0
- package/src/GraphGenomeView/components/BubbleHalos.tsx +162 -0
- package/src/GraphGenomeView/components/BubbleOverlay.tsx +181 -0
- package/src/GraphGenomeView/components/ColorSchemeSelect.tsx +41 -0
- package/src/GraphGenomeView/components/GenePins.tsx +107 -0
- package/src/GraphGenomeView/components/GraphCanvas.tsx +759 -0
- package/src/GraphGenomeView/components/GraphGenomeView.test.tsx +77 -0
- package/src/GraphGenomeView/components/GraphGenomeView.tsx +18 -17
- package/src/GraphGenomeView/components/GraphLoadStatus.tsx +84 -0
- package/src/GraphGenomeView/components/GraphSettingsDialog.tsx +327 -0
- package/src/GraphGenomeView/components/GraphStats.tsx +82 -0
- package/src/GraphGenomeView/components/GraphToolbar.tsx +84 -0
- package/src/GraphGenomeView/components/ImportForm.tsx +124 -0
- package/src/GraphGenomeView/components/LabelChip.tsx +65 -0
- package/src/GraphGenomeView/components/LayoutSelect.tsx +56 -0
- package/src/GraphGenomeView/components/RepeatSelect.tsx +51 -0
- package/src/GraphGenomeView/components/SettingsMenu.tsx +82 -0
- package/src/GraphGenomeView/components/SubgraphContextSelect.tsx +75 -0
- package/src/GraphGenomeView/components/SubgraphHaplotypesField.tsx +86 -0
- package/src/GraphGenomeView/components/WalkRowsOverlay.tsx +252 -0
- package/src/GraphGenomeView/components/WalkSelect.tsx +51 -0
- package/src/GraphGenomeView/components/overlayLabels.test.ts +56 -0
- package/src/GraphGenomeView/components/overlayLabels.ts +75 -0
- package/src/GraphGenomeView/deletionEdges.test.ts +198 -0
- package/src/GraphGenomeView/deletionEdges.ts +217 -0
- package/src/GraphGenomeView/genes/geneFeatures.ts +107 -0
- package/src/GraphGenomeView/genes/genePins.ts +130 -0
- package/src/GraphGenomeView/genes/genes.test.ts +153 -0
- package/src/GraphGenomeView/gfa/gfaConverter.test.ts +394 -0
- package/src/GraphGenomeView/gfa/gfaConverter.ts +235 -0
- package/src/GraphGenomeView/graphLabels.budget.test.ts +36 -0
- package/src/GraphGenomeView/graphLabels.test.ts +548 -0
- package/src/GraphGenomeView/graphLabels.ts +640 -0
- package/src/GraphGenomeView/index.ts +29 -7
- package/src/GraphGenomeView/laneRamp.test.ts +97 -0
- package/src/GraphGenomeView/laneRamp.ts +87 -0
- package/src/GraphGenomeView/launchKeys.test.ts +63 -0
- package/src/GraphGenomeView/layout/anchoredLayout.test.ts +213 -0
- package/src/GraphGenomeView/layout/anchoredLayout.ts +80 -0
- package/src/GraphGenomeView/layout/bubbleCrossing.test.ts +190 -0
- package/src/GraphGenomeView/layout/drawnScale.ts +199 -0
- package/src/GraphGenomeView/layout/mergeRuns.test.ts +100 -0
- package/src/GraphGenomeView/layout/mergeRuns.ts +203 -0
- package/src/GraphGenomeView/layout/orderedLayout.test.ts +212 -0
- package/src/GraphGenomeView/layout/orderedLayout.ts +396 -0
- package/src/GraphGenomeView/layout/orientToReference.test.ts +110 -0
- package/src/GraphGenomeView/layout/orientToReference.ts +79 -0
- package/src/GraphGenomeView/layout/placeOffReference.ts +147 -0
- package/src/GraphGenomeView/layout/referenceSeeds.test.ts +79 -0
- package/src/GraphGenomeView/layout/referenceSeeds.ts +89 -0
- package/src/GraphGenomeView/layout/rowSpacing.ts +5 -0
- package/src/GraphGenomeView/layout/sampleRowLayout.test.ts +318 -0
- package/src/GraphGenomeView/layout/sampleRowLayout.ts +134 -0
- package/src/GraphGenomeView/layout/variantMapLayout.test.ts +59 -0
- package/src/GraphGenomeView/layout/variantMapLayout.ts +20 -0
- package/src/GraphGenomeView/layout/walkRowLayout.ts +54 -0
- package/src/GraphGenomeView/layout/walkRows.test.ts +178 -0
- package/src/GraphGenomeView/layout/walkRows.ts +177 -0
- package/src/GraphGenomeView/layoutModes.ts +131 -0
- package/src/GraphGenomeView/model.test.ts +2696 -0
- package/src/GraphGenomeView/model.ts +2672 -94
- package/src/GraphGenomeView/nodeWidths.ts +67 -0
- package/src/GraphGenomeView/pathAnchoring.test.ts +252 -0
- package/src/GraphGenomeView/pathAnchoring.ts +164 -0
- package/src/GraphGenomeView/pathColors.test.ts +83 -0
- package/src/GraphGenomeView/pathColors.ts +122 -0
- package/src/GraphGenomeView/referenceSpan.test.ts +84 -0
- package/src/GraphGenomeView/referenceSpan.ts +133 -0
- package/src/GraphGenomeView/renderPasses.bench.ts +172 -0
- package/src/GraphGenomeView/renderPipeline.test.ts +226 -0
- package/src/GraphGenomeView/renderer/Canvas2DRenderer.test.ts +205 -0
- package/src/GraphGenomeView/renderer/Canvas2DRenderer.ts +246 -0
- package/src/GraphGenomeView/renderer/GeometryBuilder.test.ts +723 -0
- package/src/GraphGenomeView/renderer/GeometryBuilder.ts +882 -0
- package/src/GraphGenomeView/renderer/GraphRenderer.ts +13 -0
- package/src/GraphGenomeView/renderer/colorBits.ts +43 -0
- package/src/GraphGenomeView/renderer/recordingCanvas.ts +64 -0
- package/src/GraphGenomeView/renderer/types.ts +84 -0
- package/src/GraphGenomeView/renderer/walkFade.test.ts +100 -0
- package/src/GraphGenomeView/repeats/repeatFeatures.test.ts +114 -0
- package/src/GraphGenomeView/repeats/repeatFeatures.ts +202 -0
- package/src/GraphGenomeView/repeats/test_data/abca7_trgt_walks.json +575 -0
- package/src/GraphGenomeView/repeats/walkCalls.test.ts +127 -0
- package/src/GraphGenomeView/repeats/walkCalls.ts +132 -0
- package/src/GraphGenomeView/stress.test.ts +214 -0
- package/src/GraphGenomeView/subgraphLoad.test.ts +245 -0
- package/src/GraphGenomeView/types.ts +141 -0
- package/src/GraphGenomeView/util/SpatialIndex.test.ts +73 -0
- package/src/GraphGenomeView/util/SpatialIndex.ts +312 -0
- package/src/GraphGenomeView/util/edgeCurves.test.ts +93 -0
- package/src/GraphGenomeView/util/edgeCurves.ts +94 -0
- package/src/GraphGenomeView/util/geometry.test.ts +447 -0
- package/src/GraphGenomeView/util/geometry.ts +656 -0
- package/src/GraphGenomeView/util/hitDetection.test.ts +307 -0
- package/src/GraphGenomeView/util/hitDetection.ts +330 -0
- package/src/GraphGenomeView/util/wheelZoom.test.ts +40 -0
- package/src/GraphGenomeView/util/wheelZoom.ts +26 -0
- package/src/GraphGenomeView/walkHighlight.test.ts +54 -0
- package/src/GraphGenomeView/walkHighlight.ts +56 -0
- package/src/LaunchGraphGenomeView.ts +46 -0
- package/src/MinigraphBubbleAdapter/MinigraphBubbleAdapter.test.ts +139 -0
- package/src/MinigraphBubbleAdapter/MinigraphBubbleAdapter.ts +86 -0
- package/src/MinigraphBubbleAdapter/bubbleLine.ts +92 -0
- package/src/MinigraphBubbleAdapter/configSchema.ts +109 -0
- package/src/MinigraphBubbleAdapter/index.ts +22 -0
- package/src/MinigraphBubbleAdapter/test_data/hprc_bubbles_mhc.bed.gz +0 -0
- package/src/MinigraphBubbleAdapter/test_data/hprc_bubbles_mhc.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/RgfaTabixAdapter.test.ts +285 -0
- package/src/RgfaTabixAdapter/RgfaTabixAdapter.ts +194 -0
- package/src/RgfaTabixAdapter/bubbleTier.test.ts +84 -0
- package/src/RgfaTabixAdapter/configSchema.ts +142 -0
- package/src/RgfaTabixAdapter/index.ts +24 -0
- package/src/RgfaTabixAdapter/rgfaBed.test.ts +158 -0
- package/src/RgfaTabixAdapter/rgfaBed.ts +156 -0
- package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.links.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.links.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.segs.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.segs.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/test_data/pggb_ecoli.links.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/pggb_ecoli.links.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/test_data/pggb_ecoli.segs.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/pggb_ecoli.segs.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.links.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.links.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.segs.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.segs.bed.gz.tbi +0 -0
- package/src/alleleProjection/projectAlleles.test.ts +75 -0
- package/src/alleleProjection/projectAlleles.ts +190 -0
- package/src/bandage/README.md +121 -0
- package/src/bandage/bandage-layout.d.ts +14 -0
- package/src/bandage/bandage-layout.js +0 -0
- package/src/bandage/native/CMakeLists.txt +45 -0
- package/src/bandage/native/include/graph.h +188 -0
- package/src/bandage/native/include/graphlayout.h +74 -0
- package/src/bandage/native/include/settings.h +86 -0
- package/src/bandage/native/include/types.h +56 -0
- package/src/bandage/native/src/bindings.cpp +164 -0
- package/src/bandage/native/src/graphlayout.cpp +621 -0
- package/src/bandage/resizableDecode.test.ts +58 -0
- package/src/gfa-core/gfaParser.bench.ts +55 -0
- package/src/gfa-core/gfaParser.test.ts +168 -0
- package/src/gfa-core/gfaParser.ts +237 -0
- package/src/gfa-core/index.ts +10 -0
- package/src/hoverSync/GraphNodeHighlight.tsx +62 -0
- package/src/hoverSync/graphViewHighlights.test.ts +158 -0
- package/src/hoverSync/graphViewHighlights.ts +119 -0
- package/src/hoverSync/index.test.tsx +36 -0
- package/src/hoverSync/index.tsx +19 -0
- package/src/hoverSync/lgvHover.test.ts +202 -0
- package/src/hoverSync/lgvHover.ts +136 -0
- package/src/index.ts +46 -15
- package/src/launchFromGraph/contributors.test.ts +197 -0
- package/src/launchFromGraph/contributors.ts +0 -0
- package/src/launchFromGraph/graphMenuItems.ts +211 -0
- package/src/launchFromGraph/launchFromGraph.test.ts +489 -0
- package/src/launchFromGraph/launchFromGraph.ts +265 -0
- package/src/launchFromGraph/launchTracks.test.ts +72 -0
- package/src/launchFromGraph/launchTracks.ts +50 -0
- package/src/launchFromGraph/linearViewTarget.test.ts +82 -0
- package/src/launchFromGraph/linearViewTarget.ts +75 -0
- package/src/launchFromGraph/syntenyTracks.ts +47 -0
- package/src/launchSubgraph/index.ts +152 -0
- package/src/launchSubgraph/launchSubgraph.test.ts +264 -0
- package/src/launchSubgraph/launchSubgraphView.ts +128 -0
- package/src/launchSubgraph/linearViewMenuItems.test.ts +176 -0
- package/src/launchSubgraph/linearViewMenuItems.ts +107 -0
- package/src/launchSubgraph/multiWayLaunch.test.ts +289 -0
- package/src/launchSubgraph/subgraphMenuItems.ts +73 -0
- package/src/launchSubgraph/subgraphTracks.test.ts +165 -0
- package/src/launchSubgraph/subgraphTracks.ts +151 -0
- package/src/launchSubgraph/testEnv.ts +258 -0
- package/src/loadBandage.ts +22 -0
- package/src/panSNTabix.test.ts +72 -0
- package/src/panSNTabix.ts +149 -0
- package/src/pansn.ts +106 -0
- package/src/pluginName.ts +3 -0
- package/src/synteny/ComparativeAdapterBase.ts +77 -0
- package/src/synteny/SyntenyFeature.ts +116 -0
- package/src/synteny/clipFeatureToRegion.test.ts +329 -0
- package/src/synteny/clipFeatureToRegion.ts +167 -0
- package/src/version.ts +1 -0
- package/dist/GraphGenomeView/components/FeatureDialog.d.ts +0 -5
- package/dist/GraphGenomeView/components/FeatureDialog.js +0 -18
- package/dist/GraphGenomeView/components/FeatureDialog.js.map +0 -1
- package/dist/GraphGenomeView/components/GraphGenomeView.d.ts +0 -6
- package/dist/GraphGenomeView/components/GraphGenomeView.js +0 -17
- package/dist/GraphGenomeView/components/GraphGenomeView.js.map +0 -1
- package/dist/GraphGenomeView/components/GraphPanel.d.ts +0 -6
- package/dist/GraphGenomeView/components/GraphPanel.js +0 -17
- package/dist/GraphGenomeView/components/GraphPanel.js.map +0 -1
- package/dist/GraphGenomeView/components/Header.d.ts +0 -6
- package/dist/GraphGenomeView/components/Header.js +0 -59
- package/dist/GraphGenomeView/components/Header.js.map +0 -1
- package/dist/GraphGenomeView/components/SettingsDialog.d.ts +0 -7
- package/dist/GraphGenomeView/components/SettingsDialog.js +0 -24
- package/dist/GraphGenomeView/components/SettingsDialog.js.map +0 -1
- package/dist/GraphGenomeView/index.d.ts +0 -2
- package/dist/GraphGenomeView/index.js +0 -15
- package/dist/GraphGenomeView/index.js.map +0 -1
- package/dist/GraphGenomeView/model.d.ts +0 -80
- package/dist/GraphGenomeView/model.js +0 -96
- package/dist/GraphGenomeView/model.js.map +0 -1
- package/dist/GraphGenomeView/util.d.ts +0 -1
- package/dist/GraphGenomeView/util.js +0 -8
- package/dist/GraphGenomeView/util.js.map +0 -1
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -24
- package/dist/index.js.map +0 -1
- package/dist/jbrowse-plugin-graphgenomeviewer.umd.production.min.js +0 -67
- package/dist/jbrowse-plugin-graphgenomeviewer.umd.production.min.js.map +0 -7
- package/src/GraphGenomeView/components/FeatureDialog.tsx +0 -51
- package/src/GraphGenomeView/components/GraphPanel.tsx +0 -37
- package/src/GraphGenomeView/components/Header.tsx +0 -90
- package/src/GraphGenomeView/components/SettingsDialog.tsx +0 -71
- package/src/GraphGenomeView/util.ts +0 -7
- package/src/declare.d.ts +0 -1
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import stateModelFactory from './model'
|
|
2
|
+
|
|
3
|
+
const mockRpcCall = vi.fn()
|
|
4
|
+
const mockSession = {
|
|
5
|
+
tracks: [] as Record<string, unknown>[],
|
|
6
|
+
rpcManager: { call: mockRpcCall },
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
vi.mock('@jbrowse/core/util', () => ({
|
|
10
|
+
getSession: () => mockSession,
|
|
11
|
+
isSessionModelWithWidgets: () => false,
|
|
12
|
+
parseLocString: () => ({}),
|
|
13
|
+
getEnv: () => ({}),
|
|
14
|
+
useWidthSetter: () => {},
|
|
15
|
+
measureText: () => 0,
|
|
16
|
+
IntervalTree: class {},
|
|
17
|
+
getSnapshot: () => ({}),
|
|
18
|
+
applySnapshot: () => {},
|
|
19
|
+
objectHash: () => '',
|
|
20
|
+
}))
|
|
21
|
+
|
|
22
|
+
vi.mock(import('@jbrowse/core/configuration'), async importOriginal => ({
|
|
23
|
+
...(await importOriginal()),
|
|
24
|
+
readConfObject: vi.fn(
|
|
25
|
+
(obj: Record<string, unknown>, key: string) => obj[key],
|
|
26
|
+
),
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
// pangenome_hprc's segments track once NA20809 haplotype 2 is loaded
|
|
30
|
+
const TRACK = {
|
|
31
|
+
trackId: 'segments',
|
|
32
|
+
assemblyNames: ['hg38', 'NA20809.2'],
|
|
33
|
+
adapter: { type: 'RgfaTabixAdapter' },
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// A GRCh38 backbone s1, s3 and one NA20809#2 allele between them, which is what
|
|
37
|
+
// the adapter returns for a window on either assembly.
|
|
38
|
+
const GFA = [
|
|
39
|
+
'H\tVN:Z:1.0',
|
|
40
|
+
'S\ts1\t*\tLN:i:1000\tSN:Z:GRCh38#0#chr6\tSO:i:32000000\tSR:i:0',
|
|
41
|
+
'S\ts3\t*\tLN:i:900\tSN:Z:GRCh38#0#chr6\tSO:i:32001100\tSR:i:0',
|
|
42
|
+
'S\th1\t*\tLN:i:200\tSN:Z:NA20809#2#CM094351.1\tSO:i:31901000\tSR:i:1',
|
|
43
|
+
'L\ts1\t+\th1\t+\t0M',
|
|
44
|
+
'L\th1\t+\ts3\t+\t0M',
|
|
45
|
+
].join('\n')
|
|
46
|
+
|
|
47
|
+
const ON_HG38 = {
|
|
48
|
+
refName: 'chr6',
|
|
49
|
+
assemblyName: 'hg38',
|
|
50
|
+
start: 32_000_000,
|
|
51
|
+
end: 32_002_000,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const ON_NA20809 = {
|
|
55
|
+
refName: 'CM094351.1',
|
|
56
|
+
assemblyName: 'NA20809.2',
|
|
57
|
+
start: 31_900_900,
|
|
58
|
+
end: 31_901_300,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function createView(snapshot: Record<string, unknown> = {}) {
|
|
62
|
+
return stateModelFactory().create({
|
|
63
|
+
type: 'GraphGenomeView',
|
|
64
|
+
layoutMode: 'auto',
|
|
65
|
+
...snapshot,
|
|
66
|
+
} as never)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function cut(region: typeof ON_HG38, gfa = GFA) {
|
|
70
|
+
mockRpcCall.mockImplementation((_sid: unknown, method: string) =>
|
|
71
|
+
method === 'GetSubgraph'
|
|
72
|
+
? Promise.resolve(gfa)
|
|
73
|
+
: Promise.reject(new Error(`Unexpected RPC: ${method}`)),
|
|
74
|
+
)
|
|
75
|
+
const model = createView()
|
|
76
|
+
await model.loadFromTabixSubgraph(TRACK.adapter, region, {
|
|
77
|
+
trackId: TRACK.trackId,
|
|
78
|
+
})
|
|
79
|
+
return model
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
mockRpcCall.mockReset()
|
|
84
|
+
mockSession.tracks = [TRACK]
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('a cut on the reference draws', async () => {
|
|
88
|
+
const model = await cut(ON_HG38)
|
|
89
|
+
expect(model.error).toBeUndefined()
|
|
90
|
+
expect(model.nodeCount).toBe(3)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// Framed on NA20809.2's contig, the anchored layouts, the colour ramp and every
|
|
94
|
+
// highlight would put GRCh38 coordinates on CM094351.1.
|
|
95
|
+
test('a cut on another assembly the track names is refused, naming the reference', async () => {
|
|
96
|
+
const model = await cut(ON_NA20809)
|
|
97
|
+
expect(String(model.error)).toMatch(/cut on its reference, hg38/)
|
|
98
|
+
expect(model.hasGraph).toBe(false)
|
|
99
|
+
expect(mockRpcCall).not.toHaveBeenCalled()
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test('a cut that finds no segments is an error, not an empty graph', async () => {
|
|
103
|
+
const model = await cut(ON_HG38, 'H\tVN:Z:1.0')
|
|
104
|
+
expect(String(model.error)).toMatch(/No graph segments/)
|
|
105
|
+
expect(model.hasGraph).toBe(false)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
describe('overlapping cuts', () => {
|
|
109
|
+
const TWO_NODES = [
|
|
110
|
+
'H\tVN:Z:1.0',
|
|
111
|
+
'S\ta1\t*\tLN:i:10\tSN:Z:GRCh38#0#chr6\tSO:i:100\tSR:i:0',
|
|
112
|
+
'S\ta2\t*\tLN:i:10\tSN:Z:GRCh38#0#chr6\tSO:i:110\tSR:i:0',
|
|
113
|
+
'L\ta1\t+\ta2\t+\t0M',
|
|
114
|
+
].join('\n')
|
|
115
|
+
const THREE_NODES = [
|
|
116
|
+
'H\tVN:Z:1.0',
|
|
117
|
+
'S\tb1\t*\tLN:i:10\tSN:Z:GRCh38#0#chr6\tSO:i:100\tSR:i:0',
|
|
118
|
+
'S\tb2\t*\tLN:i:10\tSN:Z:GRCh38#0#chr6\tSO:i:110\tSR:i:0',
|
|
119
|
+
'S\tb3\t*\tLN:i:10\tSN:Z:GRCh38#0#chr6\tSO:i:120\tSR:i:0',
|
|
120
|
+
'L\tb1\t+\tb2\t+\t0M',
|
|
121
|
+
'L\tb2\t+\tb3\t+\t0M',
|
|
122
|
+
].join('\n')
|
|
123
|
+
|
|
124
|
+
// Two re-cuts in flight at once, the way two quick changes in the settings
|
|
125
|
+
// dialog leave them, each answered when the test says.
|
|
126
|
+
function twoCuts() {
|
|
127
|
+
const pending: ((gfa: string) => void)[] = []
|
|
128
|
+
mockRpcCall.mockImplementation((_sid: unknown, method: string) =>
|
|
129
|
+
method === 'GetSubgraph'
|
|
130
|
+
? new Promise<string>(resolve => {
|
|
131
|
+
pending.push(resolve)
|
|
132
|
+
})
|
|
133
|
+
: Promise.reject(new Error(`Unexpected RPC: ${method}`)),
|
|
134
|
+
)
|
|
135
|
+
const model = createView({
|
|
136
|
+
loadedTrackId: TRACK.trackId,
|
|
137
|
+
loadedRegion: { ...ON_HG38, start: 0, end: 200 },
|
|
138
|
+
})
|
|
139
|
+
const first = model.reloadSubgraph()
|
|
140
|
+
const second = model.reloadSubgraph()
|
|
141
|
+
return { model, pending, first, second }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
test('the later cut wins when the earlier one finishes last', async () => {
|
|
145
|
+
const { model, pending, first, second } = twoCuts()
|
|
146
|
+
pending[1]!(THREE_NODES)
|
|
147
|
+
await second
|
|
148
|
+
pending[0]!(TWO_NODES)
|
|
149
|
+
await first
|
|
150
|
+
expect(model.nodeCount).toBe(3)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
test('the spinner stays up until the live cut lands', async () => {
|
|
154
|
+
const { model, pending, first, second } = twoCuts()
|
|
155
|
+
pending[0]!(TWO_NODES)
|
|
156
|
+
await first
|
|
157
|
+
expect(model.isLoading).toBe(true)
|
|
158
|
+
pending[1]!(THREE_NODES)
|
|
159
|
+
await second
|
|
160
|
+
expect(model.isLoading).toBe(false)
|
|
161
|
+
expect(model.nodeCount).toBe(3)
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
describe('canceling and retrying', () => {
|
|
166
|
+
function pendingCuts() {
|
|
167
|
+
const pending: ((gfa: string) => void)[] = []
|
|
168
|
+
mockRpcCall.mockImplementation((_sid: unknown, method: string) =>
|
|
169
|
+
method === 'GetSubgraph'
|
|
170
|
+
? new Promise<string>(resolve => {
|
|
171
|
+
pending.push(resolve)
|
|
172
|
+
})
|
|
173
|
+
: Promise.reject(new Error(`Unexpected RPC: ${method}`)),
|
|
174
|
+
)
|
|
175
|
+
return pending
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function cutSignal(index: number) {
|
|
179
|
+
return (mockRpcCall.mock.calls[index]![2] as { signal: AbortSignal }).signal
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function launchedView() {
|
|
183
|
+
return createView({ loadedTrackId: TRACK.trackId, loadedRegion: ON_HG38 })
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
test('canceling a first cut aborts it, and its late answer never lands', async () => {
|
|
187
|
+
const pending = pendingCuts()
|
|
188
|
+
const model = launchedView()
|
|
189
|
+
const load = model.reloadSubgraph()
|
|
190
|
+
expect(model.canCancelLoad).toBe(true)
|
|
191
|
+
|
|
192
|
+
model.cancelLoad()
|
|
193
|
+
expect(cutSignal(0).aborted).toBe(true)
|
|
194
|
+
expect(model.isLoading).toBe(false)
|
|
195
|
+
expect(model.loadCanceled).toBe(true)
|
|
196
|
+
|
|
197
|
+
pending[0]!(GFA)
|
|
198
|
+
await load
|
|
199
|
+
expect(model.hasGraph).toBe(false)
|
|
200
|
+
expect(model.error).toBeUndefined()
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
test('retry cuts the same region again', async () => {
|
|
204
|
+
pendingCuts()
|
|
205
|
+
const model = launchedView()
|
|
206
|
+
void model.reloadSubgraph()
|
|
207
|
+
model.cancelLoad()
|
|
208
|
+
|
|
209
|
+
mockRpcCall.mockResolvedValue(GFA)
|
|
210
|
+
model.retryLoad()
|
|
211
|
+
expect(model.loadCanceled).toBe(false)
|
|
212
|
+
await vi.waitFor(() => {
|
|
213
|
+
expect(model.nodeCount).toBe(3)
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
test('a newer cut aborts the one it replaces', () => {
|
|
218
|
+
pendingCuts()
|
|
219
|
+
const model = launchedView()
|
|
220
|
+
void model.reloadSubgraph()
|
|
221
|
+
void model.reloadSubgraph()
|
|
222
|
+
expect(cutSignal(0).aborted).toBe(true)
|
|
223
|
+
expect(cutSignal(1).aborted).toBe(false)
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
test('a reload over a drawn graph cannot be canceled', async () => {
|
|
227
|
+
const model = await cut(ON_HG38)
|
|
228
|
+
pendingCuts()
|
|
229
|
+
void model.reloadSubgraph()
|
|
230
|
+
expect(model.canCancelLoad).toBe(false)
|
|
231
|
+
|
|
232
|
+
model.cancelLoad()
|
|
233
|
+
expect(model.isLoading).toBe(true)
|
|
234
|
+
expect(model.nodeCount).toBe(3)
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
test('a stored track the session no longer has is reported', async () => {
|
|
238
|
+
mockSession.tracks = []
|
|
239
|
+
const model = launchedView()
|
|
240
|
+
await model.refetchIfNeeded()
|
|
241
|
+
expect(String(model.error)).toMatch(/"segments", is not in this session/)
|
|
242
|
+
expect(model.canRetryLoad).toBe(true)
|
|
243
|
+
expect(mockRpcCall).not.toHaveBeenCalled()
|
|
244
|
+
})
|
|
245
|
+
})
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { StableCoordinate } from '../gfa-core/index'
|
|
2
|
+
|
|
3
|
+
export interface GraphNode {
|
|
4
|
+
id: string
|
|
5
|
+
name: string
|
|
6
|
+
length: number
|
|
7
|
+
depth: number
|
|
8
|
+
// where this segment sits on a stable sequence: stated per-segment by rGFA's
|
|
9
|
+
// SN/SO/SR, derived from the P/W lines otherwise (pathAnchoring.ts)
|
|
10
|
+
stable?: StableCoordinate
|
|
11
|
+
// every assembly whose P/W record visits this segment, sorted. This is
|
|
12
|
+
// carriage, and only a path GFA can state it: rGFA's SR is build order, so
|
|
13
|
+
// there the most a segment says is which assembly first contributed it.
|
|
14
|
+
samples?: string[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface GraphEdge {
|
|
18
|
+
from: string
|
|
19
|
+
to: string
|
|
20
|
+
// How the link reads each segment, as the file states it: it leaves the END
|
|
21
|
+
// of `from` read this way and arrives at the START of `to`. `from` and `to`
|
|
22
|
+
// alone cannot say which side of a node a link is on, and that is the whole
|
|
23
|
+
// difference between a deletion, a duplication and an inversion. Absent on a
|
|
24
|
+
// graph built without them.
|
|
25
|
+
fromStrand?: '+' | '-'
|
|
26
|
+
toStrand?: '+' | '-'
|
|
27
|
+
pathIds?: string[]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface GraphPath {
|
|
31
|
+
name: string
|
|
32
|
+
nodeIds: string[]
|
|
33
|
+
// set for W records, which state the assembly they walk; P records do not
|
|
34
|
+
sample?: string
|
|
35
|
+
haplotype?: number
|
|
36
|
+
contig?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// One P or W record, named the way a coordinate is named: the range suffix
|
|
40
|
+
// `odgi extract` appends (`K12#1#chr:1004500-1004961`) split off into `start`,
|
|
41
|
+
// so `name` is a stable sequence and `start` is where this path begins on it.
|
|
42
|
+
export interface PathOrigin {
|
|
43
|
+
name: string
|
|
44
|
+
sample: string
|
|
45
|
+
start: number
|
|
46
|
+
// bp the path covers, summed over its steps
|
|
47
|
+
length: number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// One visit a path makes to one segment. A path may visit the same segment more
|
|
51
|
+
// than once (a collapsed repeat), so this is a list per segment, not a value.
|
|
52
|
+
export interface PathVisit {
|
|
53
|
+
path: string
|
|
54
|
+
sample: string
|
|
55
|
+
// offset on `path`'s own sequence, from walking the path's steps
|
|
56
|
+
start: number
|
|
57
|
+
// how this path reads the segment, which is not how another path may read it
|
|
58
|
+
strand: '+' | '-'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface Graph {
|
|
62
|
+
name: string
|
|
63
|
+
nodes: GraphNode[]
|
|
64
|
+
edges: GraphEdge[]
|
|
65
|
+
paths?: GraphPath[]
|
|
66
|
+
// The paths that could put this graph on a reference axis, in file order, and
|
|
67
|
+
// every segment visit they make. Absent for a GFA with no P/W records — an
|
|
68
|
+
// rGFA, where the segments carry their own coordinates instead.
|
|
69
|
+
anchorPaths?: PathOrigin[]
|
|
70
|
+
pathVisits?: Map<string, PathVisit[]>
|
|
71
|
+
// Where the nodes' `stable` came from. 'tags' is rGFA's SN/SO/SR; 'paths' is
|
|
72
|
+
// derived, and the only one that can be re-derived against a different
|
|
73
|
+
// reference path. Absent when nothing anchors the graph at all.
|
|
74
|
+
anchoredBy?: 'tags' | 'paths'
|
|
75
|
+
// the path `anchoredBy: 'paths'` put on x
|
|
76
|
+
referencePath?: string
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface NodeSegment {
|
|
80
|
+
x: number
|
|
81
|
+
y: number
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// A row a layout drew, named, in the same graph y coordinates as
|
|
85
|
+
// `nodePositions`. Emitted by the layout that positioned the rows rather than
|
|
86
|
+
// recomputed by the renderer, so a label cannot end up naming a row the layout
|
|
87
|
+
// put somewhere else. Absent for layouts with no row structure (FMMM).
|
|
88
|
+
export interface RowLabel {
|
|
89
|
+
label: string
|
|
90
|
+
y: number
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// An allele that stands in for MORE reference than it carries sequence, as the
|
|
94
|
+
// row-structured layouts drew it. Those layouts give an off-reference node the
|
|
95
|
+
// slice of reference its allele replaces rather than its own length
|
|
96
|
+
// (placeOffReference), so on this axis a 93 bp segment bridging two backbone
|
|
97
|
+
// anchors 7.1 kb apart is drawn 7.1 kb wide — and labelling it `93 bp`, which is
|
|
98
|
+
// what a node label says everywhere else, describes the sequence while the
|
|
99
|
+
// reader is looking at the reference. It is a deletion, and the drawing already
|
|
100
|
+
// names deletions when they are bare edges; this is the same event with a
|
|
101
|
+
// segment in the middle of it, which `deletionEdges` cannot see.
|
|
102
|
+
//
|
|
103
|
+
// Emitted by the layout, because only the layout knows whether x is reference bp
|
|
104
|
+
// — under FMMM a node IS drawn at its own scale and `93 bp` is the true label.
|
|
105
|
+
export interface AlleleDeletion {
|
|
106
|
+
// the run's nodes, so a label can be placed over the extent they were drawn at
|
|
107
|
+
nodeIds: string[]
|
|
108
|
+
// reference bp the allele does not carry: the span between its anchors less
|
|
109
|
+
// the longest path through the run. Not `-delta`, which sums a bubble's
|
|
110
|
+
// parallel branches and so understates what any one haplotype skips.
|
|
111
|
+
bp: number
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface LayoutResult {
|
|
115
|
+
nodePositions: Record<string, NodeSegment[]>
|
|
116
|
+
rowLabels?: RowLabel[]
|
|
117
|
+
// Deletions carried by a SEGMENT rather than by a bare edge, which only a
|
|
118
|
+
// reference-bp layout can state — see AlleleDeletion.
|
|
119
|
+
alleleDeletions?: AlleleDeletion[]
|
|
120
|
+
// Set by the layouts whose x is reference bp, so the fit can be taken from
|
|
121
|
+
// the region the cut was made for rather than from how far the drawing
|
|
122
|
+
// happens to reach. An allele anchored far outside the window is a fact
|
|
123
|
+
// about the graph, not a reason to redraw the window at 6% of the frame —
|
|
124
|
+
// see layoutBounds.
|
|
125
|
+
referenceAxis?: boolean
|
|
126
|
+
// Set by the layouts that state y in SCREEN PIXELS rather than in the same
|
|
127
|
+
// units as x. A row layout's y is a row pitch, which is a property of what a
|
|
128
|
+
// row has to hold (a tube, a label) and not of the window's span, so the two
|
|
129
|
+
// axes are no longer one number — see rowSpacing.ts and the model's
|
|
130
|
+
// scaleX/scaleY. A layout that leaves this unset (FMMM) is isotropic, and
|
|
131
|
+
// every unit of it is drawn at one scale, exactly as before.
|
|
132
|
+
//
|
|
133
|
+
// Separate from `referenceAxis` on purpose: that one says what x MEANS, this
|
|
134
|
+
// one says what y is MEASURED IN, and a future layout could set either
|
|
135
|
+
// without the other.
|
|
136
|
+
pixelRows?: boolean
|
|
137
|
+
// How far a drawing reaches past its nodes, when an overlay draws rows the
|
|
138
|
+
// renderer has no node for (walk rows). The fit and the pane height take the
|
|
139
|
+
// wider of this and the nodes.
|
|
140
|
+
extent?: { maxX: number; maxY: number }
|
|
141
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { SpatialIndex } from './SpatialIndex'
|
|
2
|
+
|
|
3
|
+
const positions = {
|
|
4
|
+
'A+': [
|
|
5
|
+
{ x: 0, y: 0 },
|
|
6
|
+
{ x: 100, y: 0 },
|
|
7
|
+
],
|
|
8
|
+
'B+': [
|
|
9
|
+
{ x: 200, y: 200 },
|
|
10
|
+
{ x: 300, y: 200 },
|
|
11
|
+
],
|
|
12
|
+
'C+': [
|
|
13
|
+
{ x: 0, y: 0 },
|
|
14
|
+
{ x: 0, y: 100 },
|
|
15
|
+
],
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test('finds nearby segments', () => {
|
|
19
|
+
const index = new SpatialIndex(positions, 50)
|
|
20
|
+
const results = index.query(50, 0, 10)
|
|
21
|
+
const nodeIds = results.map(r => r.nodeId)
|
|
22
|
+
expect(nodeIds).toContain('A+')
|
|
23
|
+
expect(nodeIds).not.toContain('B+')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('finds nothing when far from all segments', () => {
|
|
27
|
+
const index = new SpatialIndex(positions, 50)
|
|
28
|
+
const results = index.query(500, 500, 10)
|
|
29
|
+
expect(results).toHaveLength(0)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('finds segments near intersection', () => {
|
|
33
|
+
const index = new SpatialIndex(positions, 50)
|
|
34
|
+
const results = index.query(0, 0, 10)
|
|
35
|
+
const nodeIds = new Set(results.map(r => r.nodeId))
|
|
36
|
+
expect(nodeIds.has('A+')).toBe(true)
|
|
37
|
+
expect(nodeIds.has('C+')).toBe(true)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('larger radius finds more candidates', () => {
|
|
41
|
+
const index = new SpatialIndex(positions, 50)
|
|
42
|
+
const small = index.query(150, 100, 10)
|
|
43
|
+
const large = index.query(150, 100, 200)
|
|
44
|
+
expect(large.length).toBeGreaterThanOrEqual(small.length)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('deduplicates entries across cells', () => {
|
|
48
|
+
const index = new SpatialIndex(positions, 10)
|
|
49
|
+
const results = index.query(50, 0, 20)
|
|
50
|
+
const keys = results.map(r => `${r.nodeId}:${r.segmentIdx}`)
|
|
51
|
+
const unique = new Set(keys)
|
|
52
|
+
expect(keys.length).toBe(unique.size)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// A row layout puts bp on x and a 20 px row pitch on y, so one cell size taken
|
|
56
|
+
// from the two summed is the x size, and applied to y it swallows every row into
|
|
57
|
+
// one cell: the grid stops discriminating on y and each query hands the distance
|
|
58
|
+
// loop every row in the band. HPRC's rGFA is exactly this shape, at ~7 kb a
|
|
59
|
+
// segment.
|
|
60
|
+
test('a row layout is bucketed on both axes, not just on x', () => {
|
|
61
|
+
const rows: Record<string, { x: number; y: number }[]> = {}
|
|
62
|
+
for (let row = 0; row < 20; row++) {
|
|
63
|
+
rows[`r${row}`] = [
|
|
64
|
+
{ x: 0, y: row * 20 },
|
|
65
|
+
{ x: 7000, y: row * 20 },
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
const index = new SpatialIndex(rows)
|
|
69
|
+
|
|
70
|
+
// 5 screen px of slack on y, which is a quarter of a row
|
|
71
|
+
const hits = index.query(3500, 0, 7000 * 0.001, 5)
|
|
72
|
+
expect(hits.map(h => h.nodeId)).toEqual(['r0'])
|
|
73
|
+
})
|