jbrowse-plugin-graphgenomeviewer 7.1.0 → 7.1.2
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 +17 -8
- package/dist/chunks/AddTrackWorkflow-RPH4ZFSX.js +2 -0
- package/dist/chunks/{AddTrackWorkflow-Q6RY23LF.js.map → AddTrackWorkflow-RPH4ZFSX.js.map} +2 -2
- package/dist/chunks/{ChooseSamplesDialog-BNVYBGJQ.js → ChooseSamplesDialog-JRN4YLBY.js} +2 -2
- package/dist/chunks/{ChooseWalksDialog-EN3C6AQA.js → ChooseWalksDialog-UXLFZTZJ.js} +2 -2
- package/dist/chunks/GbzBaseSyntenyAdapter-LRECKL74.js +2 -0
- package/dist/chunks/GbzBaseSyntenyAdapter-LRECKL74.js.map +7 -0
- package/dist/chunks/{GraphGenomeView-Z2WZKEEO.js → GraphGenomeView-NJ5XCREB.js} +2 -2
- package/dist/chunks/GraphTrackSettingsDialog-47PQ6LM7.js +6 -0
- package/dist/chunks/GraphTrackSettingsDialog-47PQ6LM7.js.map +7 -0
- package/dist/chunks/{HighlightColorDialog-EYBKFSJU.js → HighlightColorDialog-UK5VDDV6.js} +2 -2
- package/dist/chunks/{LinearGraphDisplay-WIBVWRQE.js → LinearGraphDisplay-TXTKAACH.js} +2 -2
- package/dist/chunks/MinigraphBubbleAdapter-HW7XQMAI.js +2 -0
- package/dist/chunks/MinigraphBubbleAdapter-HW7XQMAI.js.map +7 -0
- package/dist/chunks/RgfaTabixAdapter-PSKYWD2X.js +3 -0
- package/dist/chunks/RgfaTabixAdapter-PSKYWD2X.js.map +7 -0
- package/dist/chunks/{TrackWarningsDialog-QT4YEXGD.js → TrackWarningsDialog-TRYBOIHX.js} +2 -2
- package/dist/chunks/WalkTabixSyntenyAdapter-U53JLYVU.js +2 -0
- package/dist/chunks/WalkTabixSyntenyAdapter-U53JLYVU.js.map +7 -0
- package/dist/chunks/chunk-2MCOY3YG.js +3 -0
- package/dist/chunks/chunk-2MCOY3YG.js.map +7 -0
- package/dist/chunks/{chunk-EWFJBQDV.js → chunk-5O6YRJHL.js} +2 -2
- package/dist/chunks/{chunk-EWFJBQDV.js.map → chunk-5O6YRJHL.js.map} +1 -1
- package/dist/chunks/chunk-BUY2PV7D.js +2 -0
- package/dist/chunks/chunk-BUY2PV7D.js.map +7 -0
- package/dist/chunks/chunk-DVCZ3BOC.js +2 -0
- package/dist/chunks/chunk-DVCZ3BOC.js.map +7 -0
- package/dist/chunks/{chunk-SAMJE6GH.js → chunk-EGDLEQ47.js} +3 -3
- package/dist/chunks/{chunk-SAMJE6GH.js.map → chunk-EGDLEQ47.js.map} +4 -4
- package/dist/chunks/chunk-FHZXW7IG.js +2 -0
- package/dist/chunks/chunk-FHZXW7IG.js.map +7 -0
- package/dist/chunks/{chunk-DZ4OUKQV.js → chunk-FITPB5X6.js} +2 -2
- package/dist/chunks/{chunk-DZ4OUKQV.js.map → chunk-FITPB5X6.js.map} +1 -1
- package/dist/chunks/chunk-FVK2THE4.js +2 -0
- package/dist/chunks/chunk-FVK2THE4.js.map +7 -0
- package/dist/chunks/{chunk-AHFQHYJI.js → chunk-IBWORBGY.js} +2 -2
- package/dist/chunks/chunk-MND7GAOG.js +2 -0
- package/dist/chunks/chunk-MND7GAOG.js.map +7 -0
- package/dist/chunks/chunk-MURN3JPK.js +2 -0
- package/dist/chunks/chunk-MURN3JPK.js.map +7 -0
- package/dist/chunks/{chunk-3Q4RBDCL.js → chunk-N6PQZSAX.js} +1 -1
- package/dist/chunks/{chunk-3Q4RBDCL.js.map → chunk-N6PQZSAX.js.map} +1 -1
- package/dist/chunks/chunk-NWJYLP6Z.js +4 -0
- package/dist/chunks/chunk-NWJYLP6Z.js.map +7 -0
- package/dist/chunks/{chunk-YJHYBDYF.js → chunk-S75W27NT.js} +2 -2
- package/dist/chunks/{chunk-YJHYBDYF.js.map → chunk-S75W27NT.js.map} +1 -1
- package/dist/chunks/{chunk-IXQK7JAW.js → chunk-T4HOC6O3.js} +1 -1
- package/dist/chunks/{chunk-IXQK7JAW.js.map → chunk-T4HOC6O3.js.map} +1 -1
- package/dist/chunks/{chunk-AU73K7LX.js → chunk-TUKJRVBS.js} +1 -1
- package/dist/chunks/{chunk-AU73K7LX.js.map → chunk-TUKJRVBS.js.map} +1 -1
- package/dist/chunks/chunk-UGCNPRKC.js +2 -0
- package/dist/chunks/{chunk-E3WYW7Z6.js.map → chunk-UGCNPRKC.js.map} +2 -2
- package/dist/chunks/{chunk-YSTMDXXX.js → chunk-VJ3SO4CH.js} +1 -1
- package/dist/chunks/{chunk-YSTMDXXX.js.map → chunk-VJ3SO4CH.js.map} +1 -1
- package/dist/chunks/{chunk-XT766CZ5.js → chunk-W6EQLQ6H.js} +1 -1
- package/dist/chunks/{chunk-XT766CZ5.js.map → chunk-W6EQLQ6H.js.map} +1 -1
- package/dist/chunks/chunk-WAMBI7OX.js +2 -0
- package/dist/chunks/chunk-WAMBI7OX.js.map +7 -0
- package/dist/chunks/chunk-WCGVUZWP.js +2 -0
- package/dist/chunks/chunk-WCGVUZWP.js.map +7 -0
- package/dist/chunks/chunk-WEGWTC52.js +2 -0
- package/dist/chunks/chunk-WEGWTC52.js.map +7 -0
- package/dist/chunks/chunk-YU7T5WOQ.js +2 -0
- package/dist/chunks/{chunk-6OPB7FOJ.js.map → chunk-YU7T5WOQ.js.map} +2 -2
- package/dist/chunks/{chunk-446RWIA3.js → chunk-ZMOMFU2B.js} +1 -1
- package/dist/chunks/{chunk-446RWIA3.js.map → chunk-ZMOMFU2B.js.map} +1 -1
- package/dist/chunks/renderSvg-KSQI5DH6.js +2 -0
- package/dist/chunks/renderSvg-KSQI5DH6.js.map +7 -0
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +4 -4
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
- package/package.json +8 -9
- package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.test.ts +12 -10
- package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.ts +31 -39
- package/src/GbzBaseSyntenyAdapter/configSchema.ts +5 -29
- package/src/GetGraphHaplotypes.ts +6 -15
- package/src/GetGraphReads.ts +7 -14
- package/src/GetSubgraph.ts +11 -20
- package/src/GraphAddTrackWorkflow/AddTrackWorkflow.test.tsx +73 -0
- package/src/GraphAddTrackWorkflow/AddTrackWorkflow.tsx +1 -0
- package/src/GraphAddTrackWorkflow/buildTrackConfig.test.ts +3 -2
- package/src/GraphAddTrackWorkflow/buildTrackConfig.ts +2 -11
- package/src/GraphAddTrackWorkflow/graphManifest.ts +15 -3
- package/src/GraphGenomeView/components/BubbleHalos.tsx +5 -24
- package/src/GraphGenomeView/components/ElTree.tsx +6 -1
- package/src/GraphGenomeView/components/FacetPanels.test.tsx +65 -0
- package/src/GraphGenomeView/components/FacetPanels.tsx +17 -12
- package/src/GraphGenomeView/components/GenePins.tsx +5 -22
- package/src/GraphGenomeView/components/GraphCanvas.tsx +40 -234
- package/src/GraphGenomeView/components/GraphSettingsDialog.tsx +102 -108
- package/src/GraphGenomeView/components/GraphToolbar.tsx +5 -6
- package/src/GraphGenomeView/components/HoverLayer.tsx +3 -7
- package/src/GraphGenomeView/components/HoverTooltip.test.tsx +39 -0
- package/src/GraphGenomeView/components/HoverTooltip.tsx +122 -0
- package/src/GraphGenomeView/components/LabelChip.tsx +3 -5
- package/src/GraphGenomeView/components/LabelLayer.tsx +4 -17
- package/src/GraphGenomeView/components/LabelledSelect.tsx +1 -1
- package/src/GraphGenomeView/components/Legend.tsx +14 -23
- package/src/GraphGenomeView/components/PaneContextMenu.test.tsx +48 -0
- package/src/GraphGenomeView/components/PaneContextMenu.tsx +144 -0
- package/src/GraphGenomeView/components/PaneLayer.tsx +91 -0
- package/src/GraphGenomeView/components/PaneSvg.tsx +32 -50
- package/src/GraphGenomeView/components/ReferenceStripOverlay.test.tsx +48 -0
- package/src/GraphGenomeView/components/ReferenceStripOverlay.tsx +13 -42
- package/src/GraphGenomeView/components/SettingsMenu.test.tsx +29 -0
- package/src/GraphGenomeView/components/SettingsMenu.tsx +56 -65
- package/src/GraphGenomeView/components/TubeMapOverlay.tsx +15 -46
- package/src/GraphGenomeView/components/WalkRowsOverlay.tsx +58 -132
- package/src/GraphGenomeView/components/WalkStrip.tsx +17 -20
- package/src/GraphGenomeView/components/WalkStripLocator.tsx +8 -15
- package/src/GraphGenomeView/components/usePaneGestures.test.tsx +35 -0
- package/src/GraphGenomeView/components/usePaneGestures.ts +88 -91
- package/src/GraphGenomeView/genes/geneFeatures.ts +27 -45
- package/src/GraphGenomeView/graphLayers.ts +4 -4
- package/src/GraphGenomeView/host.ts +1 -1
- package/src/GraphGenomeView/legendSections.test.ts +23 -1
- package/src/GraphGenomeView/legendSections.ts +68 -74
- package/src/GraphGenomeView/model.test.ts +373 -39
- package/src/GraphGenomeView/model.ts +67 -11
- package/src/GraphGenomeView/pane/fitViews.ts +57 -514
- package/src/GraphGenomeView/pane/graphViews.ts +219 -160
- package/src/GraphGenomeView/pane/hostViews.ts +2 -10
- package/src/GraphGenomeView/pane/keepInView.ts +5 -4
- package/src/GraphGenomeView/pane/launchActions.ts +117 -139
- package/src/GraphGenomeView/pane/launchViews.ts +8 -27
- package/src/GraphGenomeView/pane/loadActions.ts +208 -187
- package/src/GraphGenomeView/pane/morph.ts +3 -9
- package/src/GraphGenomeView/pane/paneBase.ts +22 -11
- package/src/GraphGenomeView/pane/renderingActions.ts +65 -11
- package/src/GraphGenomeView/pane/settingActions.ts +17 -42
- package/src/GraphGenomeView/pane/tubeMapViews.ts +445 -0
- package/src/GraphGenomeView/pane/walkRowViews.ts +29 -37
- package/src/GraphGenomeView/renderPipeline.test.ts +1 -1
- package/src/GraphGenomeView/repeats/repeatFeatures.test.ts +23 -0
- package/src/GraphGenomeView/repeats/repeatFeatures.ts +16 -23
- package/src/GraphGenomeView/repeats/walkCalls.test.ts +18 -0
- package/src/GraphGenomeView/repeats/walkCalls.ts +46 -38
- package/src/GraphGenomeView/subgraphLoad.test.ts +67 -3
- package/src/GraphGenomeView/trackFeatures.ts +24 -0
- package/src/GraphGenomeView/tubeMapPanels.ts +28 -36
- package/src/GraphGenomeView/viewModel.ts +8 -56
- package/src/GraphGenomeView/walkRowGroups.ts +104 -30
- package/src/GraphGenomeView/walkRowSpans.test.ts +6 -0
- package/src/GraphGenomeView/walkRowSpans.ts +22 -31
- package/src/GraphGenomeView/walkRowsCut.ts +8 -17
- package/src/LinearGraphDisplay/LinearGraphDisplay.test.ts +173 -26
- package/src/LinearGraphDisplay/components/SubgraphContextSelect.test.tsx +1 -1
- package/src/LinearGraphDisplay/components/SubgraphContextSelect.tsx +3 -3
- package/src/LinearGraphDisplay/components/SubgraphHaplotypesField.test.tsx +3 -3
- package/src/LinearGraphDisplay/components/SubgraphHaplotypesField.tsx +7 -10
- package/src/LinearGraphDisplay/configSchema.ts +0 -3
- package/src/LinearGraphDisplay/denseWindow.ts +8 -11
- package/src/LinearGraphDisplay/model.ts +89 -82
- package/src/MinigraphBubbleAdapter/MinigraphBubbleAdapter.test.ts +21 -0
- package/src/MinigraphBubbleAdapter/MinigraphBubbleAdapter.ts +6 -14
- package/src/MinigraphBubbleAdapter/configSchema.ts +3 -9
- package/src/MinigraphBubbleAdapter/index.ts +1 -2
- package/src/RgfaTabixAdapter/RgfaTabixAdapter.test.ts +45 -1
- package/src/RgfaTabixAdapter/RgfaTabixAdapter.ts +111 -276
- package/src/RgfaTabixAdapter/configSchema.ts +15 -49
- package/src/RgfaTabixAdapter/index.ts +1 -2
- package/src/RgfaTabixAdapter/rgfaBed.ts +25 -15
- package/src/RgfaTabixAdapter/rgfaCut.ts +139 -0
- package/src/RgfaTabixAdapter/test_data/walks2_twopart.GRCh38.walks.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/walks2_twopart.GRCh38.walks.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/walkFiles.test.ts +33 -0
- package/src/RgfaTabixAdapter/walkReader.ts +103 -27
- package/src/RgfaTabixAdapter/walkRowRuns.test.ts +53 -0
- package/src/RgfaTabixAdapter/walkRowRuns.ts +42 -24
- package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.test.ts +37 -6
- package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.ts +23 -45
- package/src/WalkTabixSyntenyAdapter/configSchema.ts +11 -24
- package/src/WalkTabixSyntenyAdapter/index.ts +1 -2
- package/src/WalkTabixSyntenyAdapter/walkLanes.ts +35 -43
- package/src/adapterImplementing.ts +27 -0
- package/src/graphTrackDefaults/index.ts +14 -20
- package/src/hoverSync/lgvHover.ts +2 -4
- package/src/launchFromGraph/contributors.ts +1 -5
- package/src/launchFromGraph/graphMenuItems.ts +3 -4
- package/src/launchFromGraph/launchFromGraph.test.ts +0 -1
- package/src/launchFromGraph/launchFromGraph.ts +8 -13
- package/src/panSNTabix.test.ts +1 -26
- package/src/panSNTabix.ts +54 -51
- package/src/synteny/lanePairs.ts +8 -0
- package/src/synteny/matchFeature.ts +52 -0
- package/src/tabixRange.ts +13 -10
- package/src/version.ts +1 -1
- package/dist/chunks/AddTrackWorkflow-Q6RY23LF.js +0 -2
- package/dist/chunks/GbzBaseSyntenyAdapter-TIXR4Y3J.js +0 -2
- package/dist/chunks/GbzBaseSyntenyAdapter-TIXR4Y3J.js.map +0 -7
- package/dist/chunks/GraphTrackSettingsDialog-TAM6PMXD.js +0 -6
- package/dist/chunks/GraphTrackSettingsDialog-TAM6PMXD.js.map +0 -7
- package/dist/chunks/MinigraphBubbleAdapter-AGI2UYQR.js +0 -2
- package/dist/chunks/MinigraphBubbleAdapter-AGI2UYQR.js.map +0 -7
- package/dist/chunks/RgfaTabixAdapter-2OL5X3HF.js +0 -3
- package/dist/chunks/RgfaTabixAdapter-2OL5X3HF.js.map +0 -7
- package/dist/chunks/WalkTabixSyntenyAdapter-UKFHCFOF.js +0 -2
- package/dist/chunks/WalkTabixSyntenyAdapter-UKFHCFOF.js.map +0 -7
- package/dist/chunks/chunk-5SFGHDQA.js +0 -2
- package/dist/chunks/chunk-5SFGHDQA.js.map +0 -7
- package/dist/chunks/chunk-6OPB7FOJ.js +0 -2
- package/dist/chunks/chunk-AMFIBQVR.js +0 -2
- package/dist/chunks/chunk-AMFIBQVR.js.map +0 -7
- package/dist/chunks/chunk-BJMRBVG3.js +0 -2
- package/dist/chunks/chunk-BJMRBVG3.js.map +0 -7
- package/dist/chunks/chunk-E3WYW7Z6.js +0 -2
- package/dist/chunks/chunk-EHCF6AXH.js +0 -2
- package/dist/chunks/chunk-EHCF6AXH.js.map +0 -7
- package/dist/chunks/chunk-GOFS7BXK.js +0 -3
- package/dist/chunks/chunk-GOFS7BXK.js.map +0 -7
- package/dist/chunks/chunk-IJQTSJGT.js +0 -4
- package/dist/chunks/chunk-IJQTSJGT.js.map +0 -7
- package/dist/chunks/chunk-J6NZIAZF.js +0 -2
- package/dist/chunks/chunk-J6NZIAZF.js.map +0 -7
- package/dist/chunks/chunk-LUTMKRXY.js +0 -2
- package/dist/chunks/chunk-LUTMKRXY.js.map +0 -7
- package/dist/chunks/chunk-MHPJB22Z.js +0 -2
- package/dist/chunks/chunk-MHPJB22Z.js.map +0 -7
- package/dist/chunks/chunk-TXI43PEN.js +0 -2
- package/dist/chunks/chunk-TXI43PEN.js.map +0 -7
- package/dist/chunks/chunk-WRH2EJKX.js +0 -2
- package/dist/chunks/chunk-WRH2EJKX.js.map +0 -7
- package/dist/chunks/chunk-WX26KTZM.js +0 -2
- package/dist/chunks/chunk-WX26KTZM.js.map +0 -7
- package/dist/chunks/renderSvg-ST2GBBOC.js +0 -2
- package/dist/chunks/renderSvg-ST2GBBOC.js.map +0 -7
- package/src/GraphGenomeView/components/WalkRowContextMenu.tsx +0 -105
- /package/dist/chunks/{ChooseSamplesDialog-BNVYBGJQ.js.map → ChooseSamplesDialog-JRN4YLBY.js.map} +0 -0
- /package/dist/chunks/{ChooseWalksDialog-EN3C6AQA.js.map → ChooseWalksDialog-UXLFZTZJ.js.map} +0 -0
- /package/dist/chunks/{GraphGenomeView-Z2WZKEEO.js.map → GraphGenomeView-NJ5XCREB.js.map} +0 -0
- /package/dist/chunks/{HighlightColorDialog-EYBKFSJU.js.map → HighlightColorDialog-UK5VDDV6.js.map} +0 -0
- /package/dist/chunks/{LinearGraphDisplay-WIBVWRQE.js.map → LinearGraphDisplay-TXTKAACH.js.map} +0 -0
- /package/dist/chunks/{TrackWarningsDialog-QT4YEXGD.js.map → TrackWarningsDialog-TRYBOIHX.js.map} +0 -0
- /package/dist/chunks/{chunk-AHFQHYJI.js.map → chunk-IBWORBGY.js.map} +0 -0
|
@@ -120,6 +120,25 @@ export function linkKey(link: RgfaLink) {
|
|
|
120
120
|
return `${link.source}${link.sourceStrand}${link.target}${link.targetStrand}`
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
// gfa-to-tabix writes both endpoints of a link into every row and files the
|
|
124
|
+
// link under each endpoint's stable sequence, so a read covering both returns
|
|
125
|
+
// it twice; linkKey dedupes it. Returns the endpoints the cut lacked.
|
|
126
|
+
export function addLink(
|
|
127
|
+
segments: Map<string, RgfaSegment>,
|
|
128
|
+
links: Map<string, RgfaLink>,
|
|
129
|
+
link: RgfaLink,
|
|
130
|
+
) {
|
|
131
|
+
links.set(linkKey(link), link)
|
|
132
|
+
const added: RgfaSegment[] = []
|
|
133
|
+
for (const segment of [link.sourceSegment, link.targetSegment]) {
|
|
134
|
+
if (!segments.has(segment.id)) {
|
|
135
|
+
segments.set(segment.id, segment)
|
|
136
|
+
added.push(segment)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return added
|
|
140
|
+
}
|
|
141
|
+
|
|
123
142
|
// The cut an anchored index gives: `under` is what the segment file returned
|
|
124
143
|
// for the window, every segment of every bubble under it, and `found` is what
|
|
125
144
|
// the link file returned. gfa-to-tabix files a link widely enough that `found`
|
|
@@ -148,12 +167,7 @@ export function anchoredCut(
|
|
|
148
167
|
under.has(link.target) ||
|
|
149
168
|
side(link.sourceSegment) * side(link.targetSegment) === -1
|
|
150
169
|
) {
|
|
151
|
-
|
|
152
|
-
for (const segment of [link.sourceSegment, link.targetSegment]) {
|
|
153
|
-
if (!segments.has(segment.id)) {
|
|
154
|
-
segments.set(segment.id, segment)
|
|
155
|
-
}
|
|
156
|
-
}
|
|
170
|
+
addLink(segments, links, link)
|
|
157
171
|
}
|
|
158
172
|
}
|
|
159
173
|
for (const link of found) {
|
|
@@ -164,10 +178,6 @@ export function anchoredCut(
|
|
|
164
178
|
return { segments, links }
|
|
165
179
|
}
|
|
166
180
|
|
|
167
|
-
// Both endpoints of a link are written into every row, so the same link is
|
|
168
|
-
// indexed under each endpoint's stable sequence and arrives twice for a region
|
|
169
|
-
// covering both. Callers dedupe on linkKey.
|
|
170
|
-
|
|
171
181
|
export interface StableSpan {
|
|
172
182
|
refName: string
|
|
173
183
|
start: number
|
|
@@ -195,12 +205,12 @@ export function closingSpans(
|
|
|
195
205
|
return [
|
|
196
206
|
...mergedSpans(
|
|
197
207
|
held.filter(segment => segment.rank > 0),
|
|
198
|
-
unread,
|
|
208
|
+
segment => unread.has(segment.id),
|
|
199
209
|
window.end - window.start,
|
|
200
210
|
),
|
|
201
211
|
...mergedSpans(
|
|
202
212
|
held.filter(segment => segment.rank === 0 && outside(segment)),
|
|
203
|
-
|
|
213
|
+
() => true,
|
|
204
214
|
0,
|
|
205
215
|
),
|
|
206
216
|
]
|
|
@@ -208,7 +218,7 @@ export function closingSpans(
|
|
|
208
218
|
|
|
209
219
|
function mergedSpans(
|
|
210
220
|
segments: RgfaSegment[],
|
|
211
|
-
|
|
221
|
+
isUnread: (segment: RgfaSegment) => boolean,
|
|
212
222
|
maxGap: number,
|
|
213
223
|
) {
|
|
214
224
|
const sorted = [...segments].sort(
|
|
@@ -221,14 +231,14 @@ function mergedSpans(
|
|
|
221
231
|
span?.refName === segment.refName &&
|
|
222
232
|
segment.start - span.end <= maxGap
|
|
223
233
|
) {
|
|
224
|
-
span.needed ||= segment.start > span.end ||
|
|
234
|
+
span.needed ||= segment.start > span.end || isUnread(segment)
|
|
225
235
|
span.end = Math.max(span.end, segment.end)
|
|
226
236
|
} else {
|
|
227
237
|
spans.push({
|
|
228
238
|
refName: segment.refName,
|
|
229
239
|
start: segment.start,
|
|
230
240
|
end: segment.end,
|
|
231
|
-
needed:
|
|
241
|
+
needed: isUnread(segment),
|
|
232
242
|
})
|
|
233
243
|
}
|
|
234
244
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addLink,
|
|
3
|
+
anchoredCut,
|
|
4
|
+
closingLinks,
|
|
5
|
+
closingSpans,
|
|
6
|
+
formatSubgraph,
|
|
7
|
+
parseLinkLine,
|
|
8
|
+
parseSegmentRow,
|
|
9
|
+
} from './rgfaBed.ts'
|
|
10
|
+
import { getLines } from '../tabixRange.ts'
|
|
11
|
+
|
|
12
|
+
import type { RgfaLink, RgfaSegment, StableSpan } from './rgfaBed.ts'
|
|
13
|
+
import type { TabixIndexedFile } from '@gmod/tabix'
|
|
14
|
+
|
|
15
|
+
// What a hop follows: alleles, never the backbone. A rank-0 segment reached
|
|
16
|
+
// through a link is flanking backbone, usually outside the window, so querying
|
|
17
|
+
// its links walks the reference away from the region, brings back more backbone,
|
|
18
|
+
// and closes nothing. Every segment that closes a bubble is an allele interior,
|
|
19
|
+
// and an allele interior is rank > 0 in both index formats (rGFA's SR tag, and
|
|
20
|
+
// pggb's rank 0-or-1 against the anchor path). Reached backbone still becomes a
|
|
21
|
+
// node and its edge still draws; it is only not walked through.
|
|
22
|
+
//
|
|
23
|
+
// This is gfabase's rule, arrived at from the other end. `gfabase sub` expands
|
|
24
|
+
// from a seed either to the whole connected component (`--connected`, which its
|
|
25
|
+
// own README calls overkill) or with `--cutpoints 1`, which takes the biconnected
|
|
26
|
+
// component by *stopping at cutpoints*, the segments every end-to-end walk of
|
|
27
|
+
// the chromosome must traverse. In a reference-anchored pangenome the cutpoints
|
|
28
|
+
// are the backbone, and rank is what the index already states about it, so
|
|
29
|
+
// `rank > 0` is that stopping rule without a connectivity index to build. The
|
|
30
|
+
// third tool is the same shape again: `gfatools view -R` maps the region onto its
|
|
31
|
+
// precomputed bubble decomposition and returns whole overlapping bubbles
|
|
32
|
+
// (gfa_query_by_reg), which is complete for the same reason. Bandage's own
|
|
33
|
+
// `--distance` is a node-step radius, but Bandage holds the entire graph in
|
|
34
|
+
// memory, so its scope is a drawing filter rather than a fetch strategy.
|
|
35
|
+
//
|
|
36
|
+
// Measured with this rule: the E. coli paa window converges at one hop (17
|
|
37
|
+
// segments at 1 and at 2), so the cut closes and stays closed. HPRC's amylase
|
|
38
|
+
// window keeps growing (63 at 0, 78 at 1, 92 at 2) because its alleles have
|
|
39
|
+
// alleles, and wall-clock is flat across all three, since a hop's queries go out
|
|
40
|
+
// together and the remote index dominates.
|
|
41
|
+
//
|
|
42
|
+
// The exact version, when a graph ships a bubble index beside its segments:
|
|
43
|
+
// gfatools' bubble rows carry the member segment ids of every bubble, so the
|
|
44
|
+
// bubbles overlapping the region state which segments a complete cut must
|
|
45
|
+
// contain. That turns the hop count into a termination condition rather than a
|
|
46
|
+
// setting, and MinigraphBubbleAdapter already parses those rows.
|
|
47
|
+
function offReference(segments: RgfaSegment[]) {
|
|
48
|
+
return segments.filter(segment => segment.rank > 0)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface RgfaFiles {
|
|
52
|
+
segments: TabixIndexedFile
|
|
53
|
+
links: TabixIndexedFile
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function linksOver(
|
|
57
|
+
file: TabixIndexedFile,
|
|
58
|
+
span: StableSpan,
|
|
59
|
+
signal?: AbortSignal,
|
|
60
|
+
) {
|
|
61
|
+
const found: RgfaLink[] = []
|
|
62
|
+
await getLines(file, span.refName, span.start, span.end, {
|
|
63
|
+
signal,
|
|
64
|
+
lineCallback: line => {
|
|
65
|
+
found.push(parseLinkLine(line))
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
return found
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The graph around a window of one stable sequence, as GFA text. The window's
|
|
73
|
+
* own segments come from the segment file; the link file supplies the edges
|
|
74
|
+
* incident to them *and* the coordinates of the segments on the other end,
|
|
75
|
+
* which typically sit on a different stable sequence (a rank>0 bubble) and so
|
|
76
|
+
* are not reachable by any coordinate query on this window.
|
|
77
|
+
*
|
|
78
|
+
* `hops` is extra rounds of link-following past the window's own segments and
|
|
79
|
+
* their immediate neighbours, each costing one tabix query per off-reference
|
|
80
|
+
* segment newly reached. After the last round, one more read per stretch of
|
|
81
|
+
* stable sequence the cut holds closes it (closingSpans), so an allele reached
|
|
82
|
+
* from both ends draws whole. The view asks for one hop; a hopless cut stays
|
|
83
|
+
* as cheap as it was. `signal` goes to every one of those queries, so a cut
|
|
84
|
+
* the view has replaced stops.
|
|
85
|
+
*/
|
|
86
|
+
export async function rgfaCut(
|
|
87
|
+
files: RgfaFiles,
|
|
88
|
+
window: StableSpan,
|
|
89
|
+
{ hops = 0, signal }: { hops?: number; signal?: AbortSignal } = {},
|
|
90
|
+
) {
|
|
91
|
+
const segments = new Map<string, RgfaSegment>()
|
|
92
|
+
const links = new Map<string, RgfaLink>()
|
|
93
|
+
const layout = { anchored: false }
|
|
94
|
+
await getLines(files.segments, window.refName, window.start, window.end, {
|
|
95
|
+
signal,
|
|
96
|
+
lineCallback: line => {
|
|
97
|
+
const row = parseSegmentRow(line)
|
|
98
|
+
layout.anchored ||= row.anchored
|
|
99
|
+
segments.set(row.segment.id, row.segment)
|
|
100
|
+
},
|
|
101
|
+
})
|
|
102
|
+
// An anchored index files every allele and every link under the reference
|
|
103
|
+
// interval its bubble hangs from, so one read of each file holds the whole
|
|
104
|
+
// graph under the window and there is nothing to hop to or close.
|
|
105
|
+
if (layout.anchored) {
|
|
106
|
+
const cut = anchoredCut(
|
|
107
|
+
segments,
|
|
108
|
+
await linksOver(files.links, window, signal),
|
|
109
|
+
window,
|
|
110
|
+
)
|
|
111
|
+
return formatSubgraph(cut.segments, cut.links)
|
|
112
|
+
}
|
|
113
|
+
const addLinksOver = async (span: StableSpan) =>
|
|
114
|
+
(await linksOver(files.links, span, signal)).flatMap(link =>
|
|
115
|
+
addLink(segments, links, link),
|
|
116
|
+
)
|
|
117
|
+
let frontier = offReference(await addLinksOver(window))
|
|
118
|
+
for (let hop = 0; hop < hops; hop++) {
|
|
119
|
+
// One hop's queries are independent of each other, so they go out
|
|
120
|
+
// together rather than one round-trip at a time, and the output is sorted
|
|
121
|
+
// at the end regardless of the order they arrive in.
|
|
122
|
+
const reached = await Promise.all(frontier.map(addLinksOver))
|
|
123
|
+
frontier = offReference(reached.flat())
|
|
124
|
+
}
|
|
125
|
+
if (hops > 0) {
|
|
126
|
+
const spans = closingSpans(
|
|
127
|
+
[...segments.values()],
|
|
128
|
+
new Set(frontier.map(segment => segment.id)),
|
|
129
|
+
window,
|
|
130
|
+
)
|
|
131
|
+
const found = await Promise.all(
|
|
132
|
+
spans.map(span => linksOver(files.links, span, signal)),
|
|
133
|
+
)
|
|
134
|
+
for (const link of closingLinks(found.flat(), segments, spans)) {
|
|
135
|
+
addLink(segments, links, link)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return formatSubgraph(segments, links)
|
|
139
|
+
}
|
|
@@ -33,6 +33,10 @@ const fixture = (name: string) =>
|
|
|
33
33
|
// W HG00097 1 chr1 >1>2>3>4>5>6
|
|
34
34
|
const headerPrefix = fixture('walks2_header')
|
|
35
35
|
|
|
36
|
+
// walks2_header's walks with the reference named as pggb names a P-line,
|
|
37
|
+
// GRCh38#chr1
|
|
38
|
+
const twoPartPrefix = fixture('walks2_twopart')
|
|
39
|
+
|
|
36
40
|
// An unchopped graph, node 1 spanning 0-2600 and filed under chunk 0 alone:
|
|
37
41
|
// W GRCh38 0 chr1 >1>2>4
|
|
38
42
|
// W HG002 1 chr1 >1>3>4 3 replaces 2
|
|
@@ -193,6 +197,13 @@ test("the haplotypes a walk file's header names", async () => {
|
|
|
193
197
|
expect(gfa).not.toMatch(/^S\t8\t/m)
|
|
194
198
|
})
|
|
195
199
|
|
|
200
|
+
test('a cut for some haplotypes keeps a reference walk named without a haplotype', async () => {
|
|
201
|
+
const cut = (await makeAdapter(twoPartPrefix).getSubgraph(chr1(1000, 2600), {
|
|
202
|
+
haplotypes: ['HG002#1'],
|
|
203
|
+
})) as GraphTables
|
|
204
|
+
expect(cut.walks.names).toEqual(['GRCh38#chr1', 'HG002#1#chr1'])
|
|
205
|
+
})
|
|
206
|
+
|
|
196
207
|
test('a format 1 walk file is refused, with what to rebuild it with', async () => {
|
|
197
208
|
const cut = makeAdapter(fixture('walks1_header')).getSubgraph(chr1(0, 600))
|
|
198
209
|
await expect(cut).rejects.toThrow(/rebuild it with gfa-to-tabix 1.0/)
|
|
@@ -228,6 +239,9 @@ test('a cut or a feature read over a range that is not finite queries nothing',
|
|
|
228
239
|
await expect(
|
|
229
240
|
firstValueFrom(adapter.getFeatures(region).pipe(toArray())),
|
|
230
241
|
).rejects.toThrow(/not a finite range/)
|
|
242
|
+
await expect(adapter.getRegionByteSize([region])).rejects.toThrow(
|
|
243
|
+
/not a finite range/,
|
|
244
|
+
)
|
|
231
245
|
expect(getLines).not.toHaveBeenCalled()
|
|
232
246
|
expect(bytes).not.toHaveBeenCalled()
|
|
233
247
|
getLines.mockRestore()
|
|
@@ -247,6 +261,25 @@ test("a node longer than a chunk is read back as far as the header's maxnode rea
|
|
|
247
261
|
expect(walkNames(gfa)).toEqual(['GRCh38#0#chr1', 'HG002#1#chr1'])
|
|
248
262
|
})
|
|
249
263
|
|
|
264
|
+
test("a walk-indexed graph's features are the reference nodes overlapping the window", async () => {
|
|
265
|
+
const features = await firstValueFrom(
|
|
266
|
+
makeAdapter(headerPrefix).getFeatures(chr1(1000, 2300)).pipe(toArray()),
|
|
267
|
+
)
|
|
268
|
+
expect(features.map(f => f.get('name'))).toEqual(['2', '3', '4'])
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
test('the byte gate measures a walk file from the chunk before the window', async () => {
|
|
272
|
+
const bytes = vi.spyOn(TabixIndexedFile.prototype, 'bytesForRegions')
|
|
273
|
+
const size = await makeAdapter(headerPrefix).getRegionByteSize([
|
|
274
|
+
chr1(2100, 2600),
|
|
275
|
+
])
|
|
276
|
+
expect(size).toBeGreaterThan(0)
|
|
277
|
+
expect(bytes.mock.calls.map(call => call[0])).toEqual([
|
|
278
|
+
[{ refName: 'GRCh38#0#chr1', start: 1000, end: 2600 }],
|
|
279
|
+
])
|
|
280
|
+
bytes.mockRestore()
|
|
281
|
+
})
|
|
282
|
+
|
|
250
283
|
test('getFeatures reads from the chunk before the window', async () => {
|
|
251
284
|
const getLines = vi.spyOn(TabixIndexedFile.prototype, 'getLines')
|
|
252
285
|
await firstValueFrom(
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { resolvePanSNPrefix } from '@jbrowse/bandage-core/pansn'
|
|
2
|
+
import { WalkReader, referenceNodes, walksKept } from '@jbrowse/graph-walks'
|
|
3
3
|
|
|
4
|
+
import { getLines } from '../tabixRange.ts'
|
|
4
5
|
import { walkCutFor } from './walkRowRuns.ts'
|
|
5
6
|
|
|
6
|
-
import type {
|
|
7
|
+
import type { SubgraphAdapterOptions } from '../GetSubgraph.ts'
|
|
8
|
+
import type { TabixIndexedFile } from '@gmod/tabix'
|
|
9
|
+
import type {
|
|
10
|
+
BaseFeatureDataAdapter,
|
|
11
|
+
BaseOptions,
|
|
12
|
+
} from '@jbrowse/core/data_adapters/BaseAdapter'
|
|
7
13
|
|
|
8
14
|
// The zoom-in notices name the config slots that set the budgets
|
|
9
15
|
export const BUDGET_SLOTS = {
|
|
@@ -12,29 +18,99 @@ export const BUDGET_SLOTS = {
|
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* @jbrowse/graph-walks reads and cuts the window.
|
|
21
|
+
* A walk file and the answers both walk-indexed adapters give from it,
|
|
22
|
+
* RgfaTabixAdapter and WalkTabixSyntenyAdapter, whose schemas declare the
|
|
23
|
+
* budget slots. @jbrowse/graph-walks reads and cuts the file.
|
|
19
24
|
*/
|
|
20
|
-
export
|
|
21
|
-
reader: WalkReader
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
25
|
+
export class WalkIndex {
|
|
26
|
+
readonly reader: WalkReader
|
|
27
|
+
|
|
28
|
+
constructor(
|
|
29
|
+
readonly file: TabixIndexedFile,
|
|
30
|
+
private adapter: BaseFeatureDataAdapter,
|
|
31
|
+
) {
|
|
32
|
+
this.reader = new WalkReader(file)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The haplotypes the header names, `sample#haplotype` in byte order, or
|
|
37
|
+
* undefined when it names none
|
|
38
|
+
*/
|
|
39
|
+
async haplotypeNames(opts: BaseOptions = {}) {
|
|
40
|
+
const { haplotypes } = await this.reader.header(opts)
|
|
41
|
+
return haplotypes.length > 0 ? haplotypes : undefined
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
budgets() {
|
|
45
|
+
return {
|
|
46
|
+
stepBudget: this.adapter.getConf('walkStepBudget') as number,
|
|
47
|
+
byteBudget: this.adapter.getConf('walkByteBudget') as number,
|
|
48
|
+
budgetNames: BUDGET_SLOTS,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The graph display's cut of a window: the reference walk, the walks of
|
|
54
|
+
* `haplotypes` (every walk when unset), and the nodes and links they visit.
|
|
55
|
+
* Walk rows draw runs, so the step budget spares them.
|
|
56
|
+
*/
|
|
57
|
+
async subgraph(
|
|
58
|
+
refName: string,
|
|
59
|
+
region: { start: number; end: number },
|
|
60
|
+
{ signal, haplotypes, walkRows }: SubgraphAdapterOptions,
|
|
61
|
+
) {
|
|
62
|
+
const budgets = this.budgets()
|
|
63
|
+
const { graph, kept, fragments } = await this.reader.cut(refName, region, {
|
|
64
|
+
...budgets,
|
|
65
|
+
stepBudget: walkRows ? Infinity : budgets.stepBudget,
|
|
66
|
+
signal,
|
|
67
|
+
keep: walksKept(
|
|
68
|
+
haplotypes?.map(lane => resolvePanSNPrefix(this.adapter, lane)),
|
|
69
|
+
refName,
|
|
70
|
+
),
|
|
71
|
+
})
|
|
72
|
+
return walkCutFor(graph.tables(kept, fragments), region, walkRows)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The reference nodes overlapping a window, from the ref rows of its
|
|
77
|
+
* chunks. A row's tabix interval is its chunk's first base, and gfa-to-tabix
|
|
78
|
+
* files a node crossing in from a chunk before under that chunk, so the read
|
|
79
|
+
* starts at queryStart.
|
|
80
|
+
*/
|
|
81
|
+
async referenceNodes(
|
|
82
|
+
refName: string,
|
|
83
|
+
region: { start: number; end: number },
|
|
84
|
+
opts: BaseOptions,
|
|
85
|
+
) {
|
|
86
|
+
const refRows: string[][] = []
|
|
87
|
+
await getLines(
|
|
88
|
+
this.file,
|
|
89
|
+
refName,
|
|
90
|
+
await this.reader.queryStart(region.start, opts),
|
|
91
|
+
region.end,
|
|
92
|
+
{
|
|
93
|
+
signal: opts.signal,
|
|
94
|
+
lineCallback: line => {
|
|
95
|
+
const cols = line.split('\t')
|
|
96
|
+
if (cols[3] === 'ref') {
|
|
97
|
+
refRows.push(cols)
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
)
|
|
102
|
+
const seen = new Set<number>()
|
|
103
|
+
return referenceNodes(refRows).filter(node => {
|
|
104
|
+
if (
|
|
105
|
+
node.refName !== refName ||
|
|
106
|
+
node.end <= region.start ||
|
|
107
|
+
node.start >= region.end ||
|
|
108
|
+
seen.has(node.id)
|
|
109
|
+
) {
|
|
110
|
+
return false
|
|
111
|
+
}
|
|
112
|
+
seen.add(node.id)
|
|
113
|
+
return true
|
|
114
|
+
})
|
|
115
|
+
}
|
|
40
116
|
}
|
|
@@ -99,3 +99,56 @@ test('walkRows measures and stops the same walks', () => {
|
|
|
99
99
|
).rows.find(r => r.label === 'E#1')!
|
|
100
100
|
expect(short.stop).toEqual({ contigEnds: true, shortBp: 500 })
|
|
101
101
|
})
|
|
102
|
+
|
|
103
|
+
test('walks named without a haplotype field are rows of their own, on their contigs', () => {
|
|
104
|
+
const twoPart: GraphTables = {
|
|
105
|
+
...tables,
|
|
106
|
+
walks: {
|
|
107
|
+
...tables.walks,
|
|
108
|
+
names: ['GRCh38#0#chr1', 'A#a', 'A#b', 'C#1#c', 'D#1#d', 'E#1#e'],
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
const rows = walkRowsOf(walkRowRuns(twoPart, repeat)!).rows
|
|
112
|
+
expect(
|
|
113
|
+
rows
|
|
114
|
+
.filter(r => r.sample === 'A')
|
|
115
|
+
.map(({ name, label, haplotype, axis }) => ({
|
|
116
|
+
name,
|
|
117
|
+
label,
|
|
118
|
+
haplotype,
|
|
119
|
+
contig: axis?.contig,
|
|
120
|
+
}))
|
|
121
|
+
.sort((x, y) => x.name.localeCompare(y.name)),
|
|
122
|
+
).toEqual([
|
|
123
|
+
{ name: 'A#a', label: 'A', haplotype: undefined, contig: 'a' },
|
|
124
|
+
{ name: 'A#b', label: 'A', haplotype: undefined, contig: 'b' },
|
|
125
|
+
])
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('a reference cut into pieces is one reference row, as walkRows reads it', () => {
|
|
129
|
+
const split: GraphTables = {
|
|
130
|
+
...tables,
|
|
131
|
+
walks: {
|
|
132
|
+
names: ['GRCh38#0#chr1', 'GRCh38#0#chr1', 'A#1#a'],
|
|
133
|
+
starts: Float64Array.of(0, 3000, 0),
|
|
134
|
+
ends: Float64Array.of(2000, 5000, 5000),
|
|
135
|
+
offsets: Int32Array.of(0, 2, 4, 9),
|
|
136
|
+
steps: Int32Array.of(...[0, 1], ...[3, 4], ...[0, 1, 2, 3, 4]),
|
|
137
|
+
reversed: new Uint8Array(9),
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
const region = { start: 2200, end: 2800 }
|
|
141
|
+
const fields = ({ label, bp, offReferenceBp, complete }: WalkRow) => ({
|
|
142
|
+
label,
|
|
143
|
+
bp,
|
|
144
|
+
offReferenceBp,
|
|
145
|
+
complete,
|
|
146
|
+
})
|
|
147
|
+
const ours = walkRowsOf(walkRowRuns(split, region)!)
|
|
148
|
+
const graph = loadGraph(split, 'cut')
|
|
149
|
+
const theirs = walkRows({ ...graph, referencePath: 'GRCh38#0#chr1' }, region)!
|
|
150
|
+
expect(ours.rows.map(fields)).toEqual(theirs.rows.map(fields))
|
|
151
|
+
expect(fields(ours.reference)).toEqual(fields(theirs.reference))
|
|
152
|
+
expect(ours.rows.map(r => r.label)).toEqual(['A#1'])
|
|
153
|
+
expect(ours.rows[0]).toMatchObject({ bp: 1000, complete: true })
|
|
154
|
+
})
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
panSNContig,
|
|
3
|
+
panSNHaplotype,
|
|
4
|
+
panSNSample,
|
|
5
|
+
} from '@jbrowse/bandage-core/pansn'
|
|
6
|
+
|
|
1
7
|
import type { GraphTables } from '@jbrowse/bandage-core/gfa/graphTables'
|
|
2
8
|
import type {
|
|
3
9
|
WalkAxis,
|
|
@@ -44,16 +50,23 @@ export interface WalkRowRuns {
|
|
|
44
50
|
flags: Uint8Array
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
/**
|
|
54
|
+
* A walk's PanSN fields: `HG002#1#chr1`, or a pggb P-line's `chm13#chr1`,
|
|
55
|
+
* which has no haplotype
|
|
56
|
+
*/
|
|
57
|
+
export function walkPath(name: string) {
|
|
58
|
+
const sample = panSNSample(name)
|
|
59
|
+
const haplotype = panSNHaplotype(name)
|
|
60
|
+
return {
|
|
61
|
+
name,
|
|
62
|
+
sample,
|
|
63
|
+
haplotype:
|
|
64
|
+
haplotype === undefined ? undefined : +haplotype.slice(sample.length + 1),
|
|
65
|
+
contig: panSNContig(name),
|
|
66
|
+
}
|
|
50
67
|
}
|
|
51
68
|
|
|
52
|
-
|
|
53
|
-
name: string
|
|
54
|
-
sample: string
|
|
55
|
-
haplotype: number
|
|
56
|
-
contig: string
|
|
69
|
+
type Path = ReturnType<typeof walkPath> & {
|
|
57
70
|
start: number
|
|
58
71
|
from: number
|
|
59
72
|
to: number
|
|
@@ -84,11 +97,13 @@ class RunWriter {
|
|
|
84
97
|
|
|
85
98
|
/**
|
|
86
99
|
* `walkRows(graphFromTables(tables), region)` for a cut whose first walk is
|
|
87
|
-
* the reference, which every walk-file and GBZ cut is
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* cut's context
|
|
100
|
+
* the reference, which every walk-file and GBZ cut is, in as many pieces as
|
|
101
|
+
* the cut breaks it into; a step is a node index. A gap between two pieces of
|
|
102
|
+
* one haplotype is a step of `-bp`. Two differences: a walk reaching only one of the region's flanks measures from
|
|
103
|
+
* that flank, where walkRows takes the whole walk the cut holds, so its length
|
|
104
|
+
* is a lower bound that no longer grows with the cut's context; and a row
|
|
105
|
+
* keeps its walk's name whole, where graphFromTables reads a two-part name
|
|
106
|
+
* (`chm13#chr1`) as a haplotype `chr1` with no contig.
|
|
92
107
|
*/
|
|
93
108
|
export function walkRowRuns(
|
|
94
109
|
{ nodes: n, walks: w }: GraphTables,
|
|
@@ -99,19 +114,19 @@ export function walkRowRuns(
|
|
|
99
114
|
const paths: Path[] = []
|
|
100
115
|
for (let p = 0; p < w.names.length; p++) {
|
|
101
116
|
if (w.offsets[p + 1]! > w.offsets[p]!) {
|
|
102
|
-
const { sample, haplotype, contig } = walkFields(w.names[p]!)
|
|
103
117
|
paths.push({
|
|
104
|
-
|
|
105
|
-
sample,
|
|
106
|
-
haplotype,
|
|
107
|
-
contig,
|
|
118
|
+
...walkPath(w.names[p]!),
|
|
108
119
|
start: w.starts[p]!,
|
|
109
120
|
from: w.offsets[p]!,
|
|
110
121
|
to: w.offsets[p + 1]!,
|
|
111
122
|
})
|
|
112
123
|
}
|
|
113
124
|
}
|
|
114
|
-
const
|
|
125
|
+
const referencePieces = paths
|
|
126
|
+
.filter(p => p.name === paths[0]?.name)
|
|
127
|
+
.sort((a, b) => a.start - b.start)
|
|
128
|
+
const first = referencePieces[0]
|
|
129
|
+
const others = paths.filter(p => p.name !== first?.name)
|
|
115
130
|
if (!first || others.length === 0) {
|
|
116
131
|
return undefined
|
|
117
132
|
}
|
|
@@ -130,9 +145,9 @@ export function walkRowRuns(
|
|
|
130
145
|
let flankedBefore = false
|
|
131
146
|
let flankedAfter = false
|
|
132
147
|
const cut = region && region.end > region.start ? region : undefined
|
|
133
|
-
{
|
|
134
|
-
let pos =
|
|
135
|
-
for (let s =
|
|
148
|
+
for (const piece of referencePieces) {
|
|
149
|
+
let pos = piece.start
|
|
150
|
+
for (let s = piece.from; s < piece.to; s++) {
|
|
136
151
|
const node = w.steps[s]!
|
|
137
152
|
const len = lengthOf(node)
|
|
138
153
|
if (Number.isNaN(spanStart[node]!)) {
|
|
@@ -345,7 +360,10 @@ export function walkRowRuns(
|
|
|
345
360
|
out.endRow()
|
|
346
361
|
return {
|
|
347
362
|
name: path.name,
|
|
348
|
-
label:
|
|
363
|
+
label:
|
|
364
|
+
path.haplotype === undefined
|
|
365
|
+
? path.sample
|
|
366
|
+
: `${path.sample}#${path.haplotype}`,
|
|
349
367
|
sample: path.sample,
|
|
350
368
|
haplotype: path.haplotype,
|
|
351
369
|
bp,
|
|
@@ -357,7 +375,7 @@ export function walkRowRuns(
|
|
|
357
375
|
}
|
|
358
376
|
}
|
|
359
377
|
|
|
360
|
-
const reference = rowOf(
|
|
378
|
+
const reference = rowOf(referencePieces)
|
|
361
379
|
const rowList = [...walks.values()].map(pieces => ({
|
|
362
380
|
meta: rowOf(pieces),
|
|
363
381
|
row: out.offsets.length - 2,
|