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,285 @@
|
|
|
1
|
+
import { firstValueFrom } from 'rxjs'
|
|
2
|
+
import { toArray } from 'rxjs/operators'
|
|
3
|
+
|
|
4
|
+
import Adapter from './RgfaTabixAdapter.ts'
|
|
5
|
+
import configSchema from './configSchema.ts'
|
|
6
|
+
|
|
7
|
+
// Built by scripts/build_rgfa_tabix.sh from the minigraph rGFA of four E. coli
|
|
8
|
+
// strains at jbrowse.org/demos/ecoli_pangenome/ecoli_rgfa_slice.gfa (the
|
|
9
|
+
// pangenome tutorial's figure data): 161 segments across four stable sequences,
|
|
10
|
+
// ranks 0-3. Named rgfa_ecoli rather than ecoli_rgfa because the repo's
|
|
11
|
+
// .gitignore ignores `ecoli_*` (the multi-GB pangenome build dirs).
|
|
12
|
+
const prefix = require
|
|
13
|
+
.resolve('./test_data/rgfa_ecoli.segs.bed.gz')
|
|
14
|
+
.replace(/\.segs\.bed\.gz$/, '')
|
|
15
|
+
|
|
16
|
+
function makeAdapter() {
|
|
17
|
+
const local = (path: string) => ({
|
|
18
|
+
localPath: path,
|
|
19
|
+
locationType: 'LocalPathLocation',
|
|
20
|
+
})
|
|
21
|
+
return new Adapter(
|
|
22
|
+
configSchema.create({
|
|
23
|
+
segmentsLocation: local(`${prefix}.segs.bed.gz`),
|
|
24
|
+
segmentsIndex: { location: local(`${prefix}.segs.bed.gz.tbi`) },
|
|
25
|
+
linksLocation: local(`${prefix}.links.bed.gz`),
|
|
26
|
+
linksIndex: { location: local(`${prefix}.links.bed.gz.tbi`) },
|
|
27
|
+
}),
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// The graph names its stable sequences PanSN (`K12#1#chr`); an assembly asking
|
|
32
|
+
// for them names them `chr` in assembly `K12`.
|
|
33
|
+
const k12 = {
|
|
34
|
+
refName: 'chr',
|
|
35
|
+
assemblyName: 'K12',
|
|
36
|
+
start: 993236,
|
|
37
|
+
end: 997574,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
test('getRefNames lists the stable sequences', async () => {
|
|
41
|
+
expect(await makeAdapter().getRefNames()).toEqual([
|
|
42
|
+
'CFT073#1#chr',
|
|
43
|
+
'K12#1#chr',
|
|
44
|
+
'NCTC86#1#chr',
|
|
45
|
+
'Sakai#1#chr',
|
|
46
|
+
])
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('getFeatures returns the segments on a stable sequence', async () => {
|
|
50
|
+
const features = await firstValueFrom(
|
|
51
|
+
makeAdapter().getFeatures(k12).pipe(toArray()),
|
|
52
|
+
)
|
|
53
|
+
expect(
|
|
54
|
+
features.map(f => ({
|
|
55
|
+
name: f.get('name'),
|
|
56
|
+
start: f.get('start'),
|
|
57
|
+
end: f.get('end'),
|
|
58
|
+
rank: f.get('rank'),
|
|
59
|
+
})),
|
|
60
|
+
).toEqual([
|
|
61
|
+
{ name: 's322', start: 993236, end: 993310, rank: 0 },
|
|
62
|
+
{ name: 's323', start: 993310, end: 997574, rank: 0 },
|
|
63
|
+
])
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('getFeatures resolves a PanSN name given unqualified', async () => {
|
|
67
|
+
const qualified = await firstValueFrom(
|
|
68
|
+
makeAdapter()
|
|
69
|
+
.getFeatures({ ...k12, refName: 'K12#1#chr' })
|
|
70
|
+
.pipe(toArray()),
|
|
71
|
+
)
|
|
72
|
+
expect(qualified.map(f => f.get('name'))).toEqual(['s322', 's323'])
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('getFeatures is empty for a sequence the graph does not anchor to', async () => {
|
|
76
|
+
const features = await firstValueFrom(
|
|
77
|
+
makeAdapter()
|
|
78
|
+
.getFeatures({ ...k12, assemblyName: 'volvox', refName: 'ctgA' })
|
|
79
|
+
.pipe(toArray()),
|
|
80
|
+
)
|
|
81
|
+
expect(features).toEqual([])
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
// The point of the two-file index: a region on the reference backbone reaches
|
|
85
|
+
// the rank>0 segments hanging off it, which live at their own coordinates on
|
|
86
|
+
// another stable sequence (here CFT073) and so can never be found by a
|
|
87
|
+
// coordinate query on this region.
|
|
88
|
+
test('getSubgraph reaches off-reference neighbours', async () => {
|
|
89
|
+
const gfa = await makeAdapter().getSubgraph(k12)
|
|
90
|
+
expect(gfa).toBe(
|
|
91
|
+
[
|
|
92
|
+
'H\tVN:Z:1.0',
|
|
93
|
+
'S\ts1727\t*\tLN:i:226\tSN:Z:CFT073#1#chr\tSO:i:1044024\tSR:i:2',
|
|
94
|
+
'S\ts1728\t*\tLN:i:75\tSN:Z:CFT073#1#chr\tSO:i:1048515\tSR:i:2',
|
|
95
|
+
'S\ts322\t*\tLN:i:74\tSN:Z:K12#1#chr\tSO:i:993236\tSR:i:0',
|
|
96
|
+
'S\ts323\t*\tLN:i:4264\tSN:Z:K12#1#chr\tSO:i:993310\tSR:i:0',
|
|
97
|
+
'S\ts324\t*\tLN:i:7093\tSN:Z:K12#1#chr\tSO:i:997574\tSR:i:0',
|
|
98
|
+
'L\ts1727\t+\ts323\t+\t0M',
|
|
99
|
+
'L\ts322\t+\ts323\t+\t0M',
|
|
100
|
+
'L\ts323\t+\ts1728\t+\t0M',
|
|
101
|
+
'L\ts323\t+\ts324\t+\t0M',
|
|
102
|
+
].join('\n'),
|
|
103
|
+
)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('getSubgraph is byte-identical across calls', async () => {
|
|
107
|
+
const adapter = makeAdapter()
|
|
108
|
+
expect(await adapter.getSubgraph(k12)).toBe(await adapter.getSubgraph(k12))
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// context expands by whole link hops, following each newly reached segment from
|
|
112
|
+
// its own stable coordinates — which every link row carries.
|
|
113
|
+
test('getSubgraph context adds another hop', async () => {
|
|
114
|
+
const adapter = makeAdapter()
|
|
115
|
+
const ids = (gfa: string) =>
|
|
116
|
+
gfa
|
|
117
|
+
.split('\n')
|
|
118
|
+
.filter(l => l.startsWith('S'))
|
|
119
|
+
.map(l => l.split('\t')[1]!)
|
|
120
|
+
|
|
121
|
+
const near = ids(await adapter.getSubgraph(k12))
|
|
122
|
+
const far = ids(await adapter.getSubgraph(k12, { hops: 1 }))
|
|
123
|
+
expect(far.length).toBeGreaterThan(near.length)
|
|
124
|
+
expect(far).toEqual(expect.arrayContaining(near))
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// A hop is one query per off-reference segment, so a cut the view has replaced
|
|
128
|
+
// has a lot left to read. The signal reaches every query, and one that gave up
|
|
129
|
+
// before it started reads nothing.
|
|
130
|
+
test('getSubgraph stops for its signal', async () => {
|
|
131
|
+
const gaveUp = new AbortController()
|
|
132
|
+
gaveUp.abort()
|
|
133
|
+
await expect(
|
|
134
|
+
makeAdapter().getSubgraph(k12, { hops: 1, signal: gaveUp.signal }),
|
|
135
|
+
).rejects.toThrow(/abort/i)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('getFeatures stops for its signal', async () => {
|
|
139
|
+
const gaveUp = new AbortController()
|
|
140
|
+
gaveUp.abort()
|
|
141
|
+
await expect(
|
|
142
|
+
firstValueFrom(
|
|
143
|
+
makeAdapter().getFeatures(k12, { signal: gaveUp.signal }).pipe(toArray()),
|
|
144
|
+
),
|
|
145
|
+
).rejects.toThrow(/abort/i)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
// A bare header drew as an empty pane with no error, which is what a missing
|
|
149
|
+
// assemblyNameToPanSN entry (hs1 without CHM13) looked like.
|
|
150
|
+
test('getSubgraph refuses a sequence the index does not hold, saying how to map one', async () => {
|
|
151
|
+
await expect(
|
|
152
|
+
makeAdapter().getSubgraph({
|
|
153
|
+
...k12,
|
|
154
|
+
assemblyName: 'volvox',
|
|
155
|
+
refName: 'ctgA',
|
|
156
|
+
}),
|
|
157
|
+
).rejects.toThrow(
|
|
158
|
+
/volvox ctgA is not in this graph's index.*assemblyNameToPanSN/,
|
|
159
|
+
)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
// The carriage half of the index, which no rGFA has: built by
|
|
163
|
+
// scripts/build_pggb_tabix.sh in jbrowse-components from the same five-strain
|
|
164
|
+
// pggb graph the tutorial uses, sliced to the IS5 element at K12
|
|
165
|
+
// chr:1,299,499-1,300,693. 24 segments spanning every carrier count from 1 to
|
|
166
|
+
// 5, so the fixture covers a strain-private allele and core backbone in one
|
|
167
|
+
// window.
|
|
168
|
+
const pggbPrefix = require
|
|
169
|
+
.resolve('./test_data/pggb_ecoli.segs.bed.gz')
|
|
170
|
+
.replace(/\.segs\.bed\.gz$/, '')
|
|
171
|
+
|
|
172
|
+
function makePggbAdapter() {
|
|
173
|
+
const local = (path: string) => ({
|
|
174
|
+
localPath: path,
|
|
175
|
+
locationType: 'LocalPathLocation',
|
|
176
|
+
})
|
|
177
|
+
return new Adapter(
|
|
178
|
+
configSchema.create({
|
|
179
|
+
segmentsLocation: local(`${pggbPrefix}.segs.bed.gz`),
|
|
180
|
+
segmentsIndex: { location: local(`${pggbPrefix}.segs.bed.gz.tbi`) },
|
|
181
|
+
linksLocation: local(`${pggbPrefix}.links.bed.gz`),
|
|
182
|
+
linksIndex: { location: local(`${pggbPrefix}.links.bed.gz.tbi`) },
|
|
183
|
+
}),
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const is5 = {
|
|
188
|
+
refName: 'chr',
|
|
189
|
+
assemblyName: 'K12',
|
|
190
|
+
start: 1299260,
|
|
191
|
+
end: 1300800,
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// The lane this exists for: color a linear track by how many haplotypes carry
|
|
195
|
+
// each segment. Before this the tag column reached the graph view's node popup
|
|
196
|
+
// and stopped there, so a feature could say its rank but not its carriage.
|
|
197
|
+
test('getFeatures carries SM:Z: onto the feature', async () => {
|
|
198
|
+
const features = await firstValueFrom(
|
|
199
|
+
makePggbAdapter().getFeatures(is5).pipe(toArray()),
|
|
200
|
+
)
|
|
201
|
+
const private1199 = features.find(f => f.get('name') === '196827')!
|
|
202
|
+
expect(private1199.get('end') - private1199.get('start')).toBe(1199)
|
|
203
|
+
expect(private1199.get('samples')).toEqual(['K12.1'])
|
|
204
|
+
expect(private1199.get('carriers')).toBe(1)
|
|
205
|
+
|
|
206
|
+
const core = features.find(f => f.get('name') === '196843')!
|
|
207
|
+
expect(core.get('samples')).toEqual([
|
|
208
|
+
'K12.1',
|
|
209
|
+
'Sakai.1',
|
|
210
|
+
'CFT073.1',
|
|
211
|
+
'NCTC86.1',
|
|
212
|
+
'IAI39.1',
|
|
213
|
+
])
|
|
214
|
+
expect(core.get('carriers')).toBe(5)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
// The other half of the same fixture: the graph route reads carriage off the
|
|
218
|
+
// synthesized S-line, and rgfaBed.test.ts asserts that from a hand-written row.
|
|
219
|
+
// This is the same claim against a real indexed file.
|
|
220
|
+
test('getSubgraph keeps the tag on the S-line it synthesizes', async () => {
|
|
221
|
+
const gfa = await makePggbAdapter().getSubgraph(is5)
|
|
222
|
+
expect(gfa.split('\n')).toContain(
|
|
223
|
+
'S\t196827\t*\tLN:i:1199\tSN:Z:K12#1#chr\tSO:i:1299498\tSR:i:0\tSM:Z:K12.1',
|
|
224
|
+
)
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
// An rGFA has no sixth column, so the attribute is absent rather than 0 — a
|
|
228
|
+
// color expression reading `carriers` on such a track gets undefined, not a
|
|
229
|
+
// claim that nothing carries the segment.
|
|
230
|
+
test('getFeatures omits carriage on a graph that states none', async () => {
|
|
231
|
+
const features = await firstValueFrom(
|
|
232
|
+
makeAdapter().getFeatures(k12).pipe(toArray()),
|
|
233
|
+
)
|
|
234
|
+
expect(features.length).toBeGreaterThan(0)
|
|
235
|
+
expect(features.every(f => f.get('samples') === undefined)).toBe(true)
|
|
236
|
+
expect(features.every(f => f.get('carriers') === undefined)).toBe(true)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
// Minigraph-Cactus writes PanSN stable names, so HPRC release 2 calls the
|
|
240
|
+
// reference `GRCh38#0#chr1` while the JBrowse assembly is `hg38`. This reuses
|
|
241
|
+
// the `assemblyNameToPanSN` slot and helper the all-vs-all PAF adapters use.
|
|
242
|
+
// Matching on the bare contig instead is not an option: that graph also carries
|
|
243
|
+
// `CHM13#0#chr1`, so it would silently resolve to the wrong sample.
|
|
244
|
+
function makeMappedAdapter(assemblyNameToPanSN: Record<string, string>) {
|
|
245
|
+
const local = (path: string) => ({
|
|
246
|
+
localPath: path,
|
|
247
|
+
locationType: 'LocalPathLocation',
|
|
248
|
+
})
|
|
249
|
+
return new Adapter(
|
|
250
|
+
configSchema.create({
|
|
251
|
+
segmentsLocation: local(`${prefix}.segs.bed.gz`),
|
|
252
|
+
segmentsIndex: { location: local(`${prefix}.segs.bed.gz.tbi`) },
|
|
253
|
+
linksLocation: local(`${prefix}.links.bed.gz`),
|
|
254
|
+
linksIndex: { location: local(`${prefix}.links.bed.gz.tbi`) },
|
|
255
|
+
assemblyNameToPanSN,
|
|
256
|
+
}),
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// the fixture's sample is `K12`, so an assembly named anything else needs the map
|
|
261
|
+
const renamed = { ...k12, assemblyName: 'ecoli_k12' }
|
|
262
|
+
|
|
263
|
+
test('maps an assembly name to a differing PanSN sample prefix', async () => {
|
|
264
|
+
const features = await firstValueFrom(
|
|
265
|
+
makeMappedAdapter({ ecoli_k12: 'K12' })
|
|
266
|
+
.getFeatures(renamed)
|
|
267
|
+
.pipe(toArray()),
|
|
268
|
+
)
|
|
269
|
+
expect(features.length).toBeGreaterThan(0)
|
|
270
|
+
expect(features.every(f => f.get('refName') === 'chr')).toBe(true)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
test('without the mapping the same query resolves nothing', async () => {
|
|
274
|
+
const features = await firstValueFrom(
|
|
275
|
+
makeMappedAdapter({}).getFeatures(renamed).pipe(toArray()),
|
|
276
|
+
)
|
|
277
|
+
expect(features).toEqual([])
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
test('getSubgraph resolves through the mapping too', async () => {
|
|
281
|
+
const gfa = await makeMappedAdapter({ ecoli_k12: 'K12' }).getSubgraph(renamed)
|
|
282
|
+
expect(gfa.split('\n').filter(l => l.startsWith('S')).length).toBeGreaterThan(
|
|
283
|
+
0,
|
|
284
|
+
)
|
|
285
|
+
})
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter'
|
|
2
|
+
import { SimpleFeature, updateStatus } from '@jbrowse/core/util'
|
|
3
|
+
import { ObservableCreate } from '@jbrowse/core/util/rxjs'
|
|
4
|
+
|
|
5
|
+
import { PanSNRefNames, openTabixSlot } from '../panSNTabix.ts'
|
|
6
|
+
import {
|
|
7
|
+
formatSubgraph,
|
|
8
|
+
linkKey,
|
|
9
|
+
parseLinkLine,
|
|
10
|
+
parseSegmentLine,
|
|
11
|
+
segmentSamples,
|
|
12
|
+
} from './rgfaBed.ts'
|
|
13
|
+
|
|
14
|
+
import type { RgfaTabixAdapterConfig } from './configSchema.ts'
|
|
15
|
+
import type { SubgraphAdapterOptions } from '../GetSubgraph.ts'
|
|
16
|
+
import type { RgfaLink, RgfaSegment } from './rgfaBed.ts'
|
|
17
|
+
import type PluginManager from '@jbrowse/core/PluginManager'
|
|
18
|
+
import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter'
|
|
19
|
+
import type { getSubAdapterType } from '@jbrowse/core/data_adapters/dataAdapterCache'
|
|
20
|
+
import type { Feature } from '@jbrowse/core/util'
|
|
21
|
+
import type { Region } from '@jbrowse/core/util/types'
|
|
22
|
+
|
|
23
|
+
// What a hop follows: alleles, never the backbone. A rank-0 segment reached
|
|
24
|
+
// through a link is flanking backbone, usually outside the window, so querying
|
|
25
|
+
// its links walks the reference away from the region, brings back more backbone,
|
|
26
|
+
// and closes nothing. Every segment that closes a bubble is an allele interior,
|
|
27
|
+
// and an allele interior is rank > 0 in both index formats (rGFA's SR tag, and
|
|
28
|
+
// pggb's rank 0-or-1 against the anchor path). Reached backbone still becomes a
|
|
29
|
+
// node and its edge still draws; it is only not walked through.
|
|
30
|
+
//
|
|
31
|
+
// This is gfabase's rule, arrived at from the other end. `gfabase sub` expands
|
|
32
|
+
// from a seed either to the whole connected component (`--connected`, which its
|
|
33
|
+
// own README calls overkill) or with `--cutpoints 1`, which takes the biconnected
|
|
34
|
+
// component by *stopping at cutpoints*, the segments every end-to-end walk of
|
|
35
|
+
// the chromosome must traverse. In a reference-anchored pangenome the cutpoints
|
|
36
|
+
// are the backbone, and rank is what the index already states about it, so
|
|
37
|
+
// `rank > 0` is that stopping rule without a connectivity index to build. The
|
|
38
|
+
// third tool is the same shape again: `gfatools view -R` maps the region onto its
|
|
39
|
+
// precomputed bubble decomposition and returns whole overlapping bubbles
|
|
40
|
+
// (gfa_query_by_reg), which is complete for the same reason. Bandage's own
|
|
41
|
+
// `--distance` is a node-step radius, but Bandage holds the entire graph in
|
|
42
|
+
// memory, so its scope is a drawing filter rather than a fetch strategy.
|
|
43
|
+
//
|
|
44
|
+
// Measured with this rule: the E. coli paa window converges at one hop (17
|
|
45
|
+
// segments at 1 and at 2), so the cut closes and stays closed. HPRC's amylase
|
|
46
|
+
// window keeps growing (63 at 0, 78 at 1, 92 at 2) because its alleles have
|
|
47
|
+
// alleles, and wall-clock is flat across all three, since a hop's queries go out
|
|
48
|
+
// together and the remote index dominates.
|
|
49
|
+
//
|
|
50
|
+
// The exact version, when a graph ships a bubble index beside its segments:
|
|
51
|
+
// gfatools' bubble rows carry the member segment ids of every bubble, so the
|
|
52
|
+
// bubbles overlapping the region state which segments a complete cut must
|
|
53
|
+
// contain. That turns the hop count into a termination condition rather than a
|
|
54
|
+
// setting, and MinigraphBubbleAdapter already parses those rows.
|
|
55
|
+
function offReference(segments: RgfaSegment[]) {
|
|
56
|
+
return segments.filter(segment => segment.rank > 0)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default class RgfaTabixAdapter extends BaseFeatureDataAdapter<RgfaTabixAdapterConfig> {
|
|
60
|
+
public static capabilities = ['getFeatures', 'getRefNames']
|
|
61
|
+
|
|
62
|
+
private readonly segments
|
|
63
|
+
private readonly links
|
|
64
|
+
// Off the SEGMENT index: it is the one that names every stable sequence the
|
|
65
|
+
// graph is anchored to, where the link index only names those a link touches.
|
|
66
|
+
private readonly refNames
|
|
67
|
+
|
|
68
|
+
public constructor(
|
|
69
|
+
config: RgfaTabixAdapterConfig,
|
|
70
|
+
getSubAdapter?: getSubAdapterType,
|
|
71
|
+
pluginManager?: PluginManager,
|
|
72
|
+
) {
|
|
73
|
+
super(config, getSubAdapter, pluginManager)
|
|
74
|
+
this.segments = openTabixSlot(this, 'segmentsLocation', 'segmentsIndex')
|
|
75
|
+
this.links = openTabixSlot(this, 'linksLocation', 'linksIndex')
|
|
76
|
+
this.refNames = new PanSNRefNames(this.segments, this)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async getRefNames(opts: BaseOptions = {}) {
|
|
80
|
+
return this.refNames.assemblyRefNames(opts)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public async hasDataForRefName() {
|
|
84
|
+
return true
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getFeatures(query: Region, opts: BaseOptions = {}) {
|
|
88
|
+
const { signal, statusCallback } = opts
|
|
89
|
+
return ObservableCreate<Feature>(async observer => {
|
|
90
|
+
const tabixRefName = await this.refNames.resolve(query, opts)
|
|
91
|
+
if (tabixRefName !== undefined) {
|
|
92
|
+
await updateStatus('Downloading segments', statusCallback, () =>
|
|
93
|
+
this.segments.getLines(tabixRefName, query.start, query.end, {
|
|
94
|
+
signal,
|
|
95
|
+
lineCallback: line => {
|
|
96
|
+
const segment = parseSegmentLine(line)
|
|
97
|
+
// `samples` is who, `carriers` is how many, and the second is not
|
|
98
|
+
// just a convenience: a lane colored by carriage is a jexl
|
|
99
|
+
// expression in a config, where counting a list means relying on
|
|
100
|
+
// jexl resolving `.length` through a member access on an array.
|
|
101
|
+
// The count is the axis the color reads, so the index states it.
|
|
102
|
+
const samples = segmentSamples(segment)
|
|
103
|
+
observer.next(
|
|
104
|
+
new SimpleFeature({
|
|
105
|
+
uniqueId: segment.id,
|
|
106
|
+
refName: query.refName,
|
|
107
|
+
start: segment.start,
|
|
108
|
+
end: segment.end,
|
|
109
|
+
name: segment.id,
|
|
110
|
+
type: 'segment',
|
|
111
|
+
rank: segment.rank,
|
|
112
|
+
stableName: segment.refName,
|
|
113
|
+
...(samples && { samples, carriers: samples.length }),
|
|
114
|
+
}),
|
|
115
|
+
)
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
observer.complete()
|
|
121
|
+
}, signal)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Extract the graph around a region as GFA text, for GraphGenomeView. The
|
|
125
|
+
// region's own segments come from segs.bed.gz; links.bed.gz supplies the edges
|
|
126
|
+
// incident to them *and* the coordinates of the segments on the other end,
|
|
127
|
+
// which typically sit on a different stable sequence (a rank>0 bubble) and so
|
|
128
|
+
// are not reachable by any coordinate query on this region.
|
|
129
|
+
//
|
|
130
|
+
// `hops` is extra rounds of link-following past the region's own segments
|
|
131
|
+
// and their immediate neighbours, each costing one tabix query per
|
|
132
|
+
// off-reference segment newly reached. One round is what closes a bubble, so
|
|
133
|
+
// it is the default the view asks for; see the frontier below. `signal` goes
|
|
134
|
+
// to every one of those queries, so a cut the view has replaced stops.
|
|
135
|
+
async getSubgraph(region: Region, opts: SubgraphAdapterOptions = {}) {
|
|
136
|
+
const { hops = 0, signal } = opts
|
|
137
|
+
const segments = new Map<string, RgfaSegment>()
|
|
138
|
+
const links = new Map<string, RgfaLink>()
|
|
139
|
+
const tabixRefName = await this.refNames.resolve(region, { signal })
|
|
140
|
+
|
|
141
|
+
const addLinksOver = async (
|
|
142
|
+
refName: string,
|
|
143
|
+
start: number,
|
|
144
|
+
end: number,
|
|
145
|
+
) => {
|
|
146
|
+
const reached: RgfaSegment[] = []
|
|
147
|
+
await this.links.getLines(refName, start, end, {
|
|
148
|
+
signal,
|
|
149
|
+
lineCallback: line => {
|
|
150
|
+
const link = parseLinkLine(line)
|
|
151
|
+
links.set(linkKey(link), link)
|
|
152
|
+
for (const segment of [link.sourceSegment, link.targetSegment]) {
|
|
153
|
+
if (!segments.has(segment.id)) {
|
|
154
|
+
segments.set(segment.id, segment)
|
|
155
|
+
reached.push(segment)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
})
|
|
160
|
+
return reached
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (tabixRefName === undefined) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`${region.assemblyName} ${region.refName} is not in this graph's index; a graph with PanSN names (GRCh38#0#chr1) needs ${region.assemblyName} mapped to its prefix in assemblyNameToPanSN`,
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
await this.segments.getLines(tabixRefName, region.start, region.end, {
|
|
169
|
+
signal,
|
|
170
|
+
lineCallback: line => {
|
|
171
|
+
const segment = parseSegmentLine(line)
|
|
172
|
+
segments.set(segment.id, segment)
|
|
173
|
+
},
|
|
174
|
+
})
|
|
175
|
+
let frontier = offReference(
|
|
176
|
+
await addLinksOver(tabixRefName, region.start, region.end),
|
|
177
|
+
)
|
|
178
|
+
for (let hop = 0; hop < hops; hop++) {
|
|
179
|
+
// One hop's queries are independent of each other, so they go out
|
|
180
|
+
// together rather than one round-trip at a time. Their callbacks share
|
|
181
|
+
// the segment and link maps, which is safe because only one of them runs
|
|
182
|
+
// at a time, and the output is sorted at the end regardless of the order
|
|
183
|
+
// they arrive in.
|
|
184
|
+
const reached = await Promise.all(
|
|
185
|
+
frontier.map(segment =>
|
|
186
|
+
addLinksOver(segment.refName, segment.start, segment.end),
|
|
187
|
+
),
|
|
188
|
+
)
|
|
189
|
+
frontier = offReference(reached.flat())
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return formatSubgraph(segments, links)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import Adapter from './RgfaTabixAdapter.ts'
|
|
2
|
+
import configSchema from './configSchema.ts'
|
|
3
|
+
import { parseGFA } from '../gfa-core/index.ts'
|
|
4
|
+
|
|
5
|
+
// A level-of-detail tier: the same two files the adapter always reads, but one
|
|
6
|
+
// node per bubble instead of one per GFA segment. Built by
|
|
7
|
+
// jbrowse-components/scripts/bubbles_to_tier_bed.py from HPRC release 2's
|
|
8
|
+
// hosted `hprc-v2.0-mc-grch38.bubbles.bed.gz`, filtered to chrY at
|
|
9
|
+
// `--min-content 10000`. The whole chromosome is 57 bubbles and 57 backbone
|
|
10
|
+
// nodes in 5.5 kB, which is the point of the tier.
|
|
11
|
+
const prefix = require
|
|
12
|
+
.resolve('./test_data/bubble_tier_chrY.segs.bed.gz')
|
|
13
|
+
.replace(/\.segs\.bed\.gz$/, '')
|
|
14
|
+
|
|
15
|
+
function makeAdapter() {
|
|
16
|
+
const local = (path: string) => ({
|
|
17
|
+
localPath: path,
|
|
18
|
+
locationType: 'LocalPathLocation',
|
|
19
|
+
})
|
|
20
|
+
return new Adapter(
|
|
21
|
+
configSchema.create({
|
|
22
|
+
segmentsLocation: local(`${prefix}.segs.bed.gz`),
|
|
23
|
+
segmentsIndex: { location: local(`${prefix}.segs.bed.gz.tbi`) },
|
|
24
|
+
linksLocation: local(`${prefix}.links.bed.gz`),
|
|
25
|
+
linksIndex: { location: local(`${prefix}.links.bed.gz.tbi`) },
|
|
26
|
+
assemblyNameToPanSN: { hg38: 'GRCh38' },
|
|
27
|
+
}),
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const wholeChromosome = {
|
|
32
|
+
refName: 'chrY',
|
|
33
|
+
assemblyName: 'hg38',
|
|
34
|
+
start: 0,
|
|
35
|
+
end: 57_227_415,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// The claim the tier exists to make: a whole human chromosome is drawable. The
|
|
39
|
+
// measured ceiling is a few hundred nodes (reference/PANGENOME_GRAPHS.md, "Force
|
|
40
|
+
// layout does not get better with more nodes"), and the fine tier over the same
|
|
41
|
+
// span would be six figures of segments.
|
|
42
|
+
test('a whole chromosome collapses to a drawable node count', async () => {
|
|
43
|
+
const graph = parseGFA(await makeAdapter().getSubgraph(wholeChromosome))
|
|
44
|
+
expect(graph.nodes.length).toBe(114)
|
|
45
|
+
expect(graph.nodes.length).toBeLessThan(500)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('the collapse summary reaches GraphNode.tags', async () => {
|
|
49
|
+
const graph = parseGFA(await makeAdapter().getSubgraph(wholeChromosome))
|
|
50
|
+
const bubbles = graph.nodes.filter(n => n.tags.ct === 'bubble')
|
|
51
|
+
const backbone = graph.nodes.filter(n => n.tags.ct === 'backbone')
|
|
52
|
+
expect(bubbles).toHaveLength(57)
|
|
53
|
+
expect(backbone).toHaveLength(57)
|
|
54
|
+
|
|
55
|
+
// Every bubble states what it collapsed, and the numbers arrive typed.
|
|
56
|
+
expect(
|
|
57
|
+
bubbles.every(n => typeof n.tags.cn === 'number' && n.tags.cn >= 1),
|
|
58
|
+
).toBe(true)
|
|
59
|
+
expect(bubbles.every(n => typeof n.tags.cl === 'number')).toBe(true)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// A pure insertion is an alternative to nothing, so it has no reference span.
|
|
63
|
+
// It is drawn 1 bp wide and states its real size in cl, which is why the
|
|
64
|
+
// producer thresholds on content rather than on `end - start`.
|
|
65
|
+
test('a pure insertion survives as a 1 bp node stating its true length', async () => {
|
|
66
|
+
const graph = parseGFA(await makeAdapter().getSubgraph(wholeChromosome))
|
|
67
|
+
const insertions = graph.nodes.filter(
|
|
68
|
+
n => n.tags.ct === 'bubble' && n.length === 1,
|
|
69
|
+
)
|
|
70
|
+
expect(insertions.length).toBeGreaterThan(0)
|
|
71
|
+
expect(insertions.every(n => (n.tags.cl as number) >= 10000)).toBe(true)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// Backbone and bubble strictly alternate, so the tier is one connected chain
|
|
75
|
+
// rather than islands, which is what makes it pan-able as a graph.
|
|
76
|
+
test('the tier is a connected alternating chain', async () => {
|
|
77
|
+
const gfa = await makeAdapter().getSubgraph(wholeChromosome)
|
|
78
|
+
const graph = parseGFA(gfa)
|
|
79
|
+
const rank = new Map(graph.nodes.map(n => [n.id, n.tags.ct]))
|
|
80
|
+
expect(graph.links.length).toBe(113)
|
|
81
|
+
expect(
|
|
82
|
+
graph.links.every(l => rank.get(l.source) !== rank.get(l.target)),
|
|
83
|
+
).toBe(true)
|
|
84
|
+
})
|