jbrowse-plugin-graphgenomeviewer 7.4.4 → 7.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{GbzBaseSyntenyAdapter-A5MNPCKX.js → GbzBaseSyntenyAdapter-3PVPTKNW.js} +2 -2
- package/dist/chunks/{GbzBaseSyntenyAdapter-A5MNPCKX.js.map → GbzBaseSyntenyAdapter-3PVPTKNW.js.map} +1 -1
- package/dist/chunks/{GraphGenomeView-SEAPDRXS.js → GraphGenomeView-GQW4YC23.js} +2 -2
- package/dist/chunks/GraphTrackSettingsDialog-ARWCWJW4.js +4 -0
- package/dist/chunks/GraphTrackSettingsDialog-ARWCWJW4.js.map +7 -0
- package/dist/chunks/{HighlightColorDialog-QAYWLO5I.js → HighlightColorDialog-PD3KMF2U.js} +2 -2
- package/dist/chunks/LinearGraphDisplay-QNS4EAL3.js +2 -0
- package/dist/chunks/{LinearGraphDisplay-ZC4BQ27J.js.map → LinearGraphDisplay-QNS4EAL3.js.map} +2 -2
- package/dist/chunks/{MinigraphBubbleAdapter-3QM4XV47.js → MinigraphBubbleAdapter-L6JSQIOL.js} +2 -2
- package/dist/chunks/RgfaTabixAdapter-5HCAJ7QI.js +2 -0
- package/dist/chunks/RgfaTabixAdapter-5HCAJ7QI.js.map +7 -0
- package/dist/chunks/{WalkFormsAdapter-FT2SW4H6.js → WalkFormsAdapter-NX36GP35.js} +2 -2
- package/dist/chunks/WalkTabixSyntenyAdapter-JFNFX3JP.js +2 -0
- package/dist/chunks/{WalkTabixSyntenyAdapter-3WRVNDLB.js.map → WalkTabixSyntenyAdapter-JFNFX3JP.js.map} +2 -2
- package/dist/chunks/{chunk-KIYRMZWP.js → chunk-2CPD5NX2.js} +2 -2
- package/dist/chunks/{chunk-KIYRMZWP.js.map → chunk-2CPD5NX2.js.map} +1 -1
- package/dist/chunks/chunk-3AC33AXB.js +2 -0
- package/dist/chunks/{chunk-HF4H6LQI.js → chunk-4PHM6JNU.js} +2 -2
- package/dist/chunks/chunk-5LFPNQWH.js +2 -0
- package/dist/chunks/{chunk-6VNU7SD4.js.map → chunk-5LFPNQWH.js.map} +4 -4
- package/dist/chunks/{chunk-6WUWQX2P.js → chunk-CNEUOM7X.js} +2 -2
- package/dist/chunks/{chunk-6WUWQX2P.js.map → chunk-CNEUOM7X.js.map} +1 -1
- package/dist/chunks/{chunk-P3FBI5ML.js → chunk-DOC7QIE2.js} +1 -1
- package/dist/chunks/{chunk-P3FBI5ML.js.map → chunk-DOC7QIE2.js.map} +1 -1
- package/dist/chunks/{chunk-G53BI7VK.js → chunk-G4OPM6G7.js} +2 -2
- package/dist/chunks/{chunk-G53BI7VK.js.map → chunk-G4OPM6G7.js.map} +1 -1
- package/dist/chunks/chunk-GRLNDNX4.js +2 -0
- package/dist/chunks/{chunk-S333HOG5.js.map → chunk-GRLNDNX4.js.map} +4 -4
- package/dist/chunks/{chunk-7RJMG6I2.js → chunk-JYYF3U2H.js} +1 -1
- package/dist/chunks/{chunk-7RJMG6I2.js.map → chunk-JYYF3U2H.js.map} +1 -1
- package/dist/chunks/chunk-KDXVLEHS.js +3 -0
- package/dist/chunks/chunk-KDXVLEHS.js.map +7 -0
- package/dist/chunks/chunk-LGV6BTOY.js +2 -0
- package/dist/chunks/{chunk-HCDKP3IP.js.map → chunk-LGV6BTOY.js.map} +4 -4
- package/dist/chunks/{chunk-2KVE7YAS.js → chunk-LJ7O3736.js} +1 -1
- package/dist/chunks/{chunk-2KVE7YAS.js.map → chunk-LJ7O3736.js.map} +1 -1
- package/dist/chunks/{chunk-OJWBLPPH.js → chunk-OQHN2GWX.js} +2 -2
- package/dist/chunks/chunk-PTDOYJFH.js +4 -0
- package/dist/chunks/chunk-PTDOYJFH.js.map +7 -0
- package/dist/chunks/chunk-SGP63UMC.js +2 -0
- package/dist/chunks/chunk-SGP63UMC.js.map +7 -0
- package/dist/chunks/{chunk-XXBE4IFZ.js → chunk-SI5VLKVN.js} +2 -2
- package/dist/chunks/{chunk-XXBE4IFZ.js.map → chunk-SI5VLKVN.js.map} +1 -1
- package/dist/chunks/{chunk-6SWXMRAV.js → chunk-TWPUVVYH.js} +1 -1
- package/dist/chunks/{chunk-6SWXMRAV.js.map → chunk-TWPUVVYH.js.map} +1 -1
- package/dist/chunks/{chunk-FTXDTU27.js → chunk-XF6GU4GK.js} +3 -3
- package/dist/chunks/{chunk-FTXDTU27.js.map → chunk-XF6GU4GK.js.map} +2 -2
- package/dist/chunks/{chunk-MUONTMYZ.js → chunk-XSDGE4RF.js} +2 -2
- package/dist/chunks/{chunk-LE7FZM6Y.js → chunk-ZFJGNGIB.js} +2 -2
- package/dist/chunks/{chunk-LE7FZM6Y.js.map → chunk-ZFJGNGIB.js.map} +1 -1
- package/dist/chunks/chunk-ZI7RFIA2.js +4 -0
- package/dist/chunks/chunk-ZI7RFIA2.js.map +7 -0
- package/dist/chunks/{renderSvg-ZIUOPWJQ.js → renderSvg-NIWFCY3U.js} +2 -2
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +4 -3
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
- package/package.json +2 -2
- package/src/GraphComputeLayout.test.ts +24 -0
- package/src/GraphComputeLayout.ts +4 -2
- package/src/GraphGenomeView/components/GraphSettingsDialog.tsx +16 -0
- package/src/GraphGenomeView/components/WalkRowsOverlay.tsx +83 -0
- package/src/GraphGenomeView/components/usePaneGestures.test.tsx +82 -5
- package/src/GraphGenomeView/components/usePaneGestures.ts +10 -4
- package/src/GraphGenomeView/graphLayers.ts +14 -1
- package/src/GraphGenomeView/legendSections.ts +32 -0
- package/src/GraphGenomeView/model.test.ts +307 -7
- package/src/GraphGenomeView/model.ts +3 -10
- package/src/GraphGenomeView/nodeColor.test.ts +27 -1
- package/src/GraphGenomeView/nodeColor.ts +38 -29
- package/src/GraphGenomeView/nodeSize.test.ts +21 -5
- package/src/GraphGenomeView/nodeSize.ts +24 -8
- package/src/GraphGenomeView/pane/fitViews.ts +5 -1
- package/src/GraphGenomeView/pane/graphViews.ts +102 -20
- package/src/GraphGenomeView/pane/launchActions.ts +55 -11
- package/src/GraphGenomeView/pane/launchViews.ts +46 -0
- package/src/GraphGenomeView/pane/loadActions.ts +85 -25
- package/src/GraphGenomeView/pane/morph.test.ts +25 -1
- package/src/GraphGenomeView/pane/morph.ts +3 -1
- package/src/GraphGenomeView/pane/paneBase.ts +5 -0
- package/src/GraphGenomeView/pane/renderingActions.ts +61 -2
- package/src/GraphGenomeView/pane/settingActions.ts +65 -5
- package/src/GraphGenomeView/pane/walkRowViews.ts +17 -6
- package/src/GraphGenomeView/retiredNodeColor.test.ts +46 -0
- package/src/GraphGenomeView/retiredNodeColor.ts +76 -0
- package/src/GraphGenomeView/tubeMapPanels.ts +2 -1
- package/src/GraphGenomeView/viewModel.ts +16 -9
- package/src/GraphGenomeView/walkRowBlocks.ts +50 -0
- package/src/LinearGraphDisplay/LinearGraphDisplay.test.ts +97 -10
- package/src/LinearGraphDisplay/components/GraphStatusChrome.test.tsx +18 -0
- package/src/LinearGraphDisplay/components/GraphStatusChrome.tsx +4 -2
- package/src/LinearGraphDisplay/components/SubgraphHaplotypesField.test.tsx +41 -1
- package/src/LinearGraphDisplay/components/SubgraphHaplotypesField.tsx +33 -19
- package/src/LinearGraphDisplay/configSchema.ts +18 -10
- package/src/LinearGraphDisplay/model.ts +4 -2
- package/src/RgfaTabixAdapter/RgfaTabixAdapter.ts +2 -9
- package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.ts +3 -6
- package/src/hoverSync/graphViewHighlights.test.ts +53 -0
- package/src/hoverSync/graphViewHighlights.ts +25 -0
- package/src/hoverSync/lgvHover.test.ts +100 -1
- package/src/hoverSync/lgvHover.ts +77 -6
- package/src/launchFromGraph/contributors.test.ts +52 -0
- package/src/launchFromGraph/contributors.ts +105 -1
- package/src/launchFromGraph/launchFromGraph.ts +2 -2
- package/src/panSNTabix.test.ts +28 -0
- package/src/panSNTabix.ts +26 -8
- package/src/version.ts +1 -1
- package/dist/chunks/GraphTrackSettingsDialog-EVIIGC5G.js +0 -6
- package/dist/chunks/GraphTrackSettingsDialog-EVIIGC5G.js.map +0 -7
- package/dist/chunks/LinearGraphDisplay-ZC4BQ27J.js +0 -2
- package/dist/chunks/RgfaTabixAdapter-XSOIEWBK.js +0 -2
- package/dist/chunks/RgfaTabixAdapter-XSOIEWBK.js.map +0 -7
- package/dist/chunks/WalkTabixSyntenyAdapter-3WRVNDLB.js +0 -2
- package/dist/chunks/chunk-6VNU7SD4.js +0 -2
- package/dist/chunks/chunk-GRTN25B6.js +0 -3
- package/dist/chunks/chunk-GRTN25B6.js.map +0 -7
- package/dist/chunks/chunk-HCDKP3IP.js +0 -2
- package/dist/chunks/chunk-LE43I4PT.js +0 -4
- package/dist/chunks/chunk-LE43I4PT.js.map +0 -7
- package/dist/chunks/chunk-QV7SSOMJ.js +0 -2
- package/dist/chunks/chunk-S333HOG5.js +0 -2
- package/dist/chunks/chunk-VZQ6VOUP.js +0 -2
- package/dist/chunks/chunk-VZQ6VOUP.js.map +0 -7
- package/dist/chunks/chunk-WLRRI35A.js +0 -3
- package/dist/chunks/chunk-WLRRI35A.js.map +0 -7
- package/src/launchFromGraph/launchTracks.test.ts +0 -95
- package/src/launchFromGraph/launchTracks.ts +0 -49
- /package/dist/chunks/{GraphGenomeView-SEAPDRXS.js.map → GraphGenomeView-GQW4YC23.js.map} +0 -0
- /package/dist/chunks/{HighlightColorDialog-QAYWLO5I.js.map → HighlightColorDialog-PD3KMF2U.js.map} +0 -0
- /package/dist/chunks/{MinigraphBubbleAdapter-3QM4XV47.js.map → MinigraphBubbleAdapter-L6JSQIOL.js.map} +0 -0
- /package/dist/chunks/{WalkFormsAdapter-FT2SW4H6.js.map → WalkFormsAdapter-NX36GP35.js.map} +0 -0
- /package/dist/chunks/{chunk-QV7SSOMJ.js.map → chunk-3AC33AXB.js.map} +0 -0
- /package/dist/chunks/{chunk-HF4H6LQI.js.map → chunk-4PHM6JNU.js.map} +0 -0
- /package/dist/chunks/{chunk-OJWBLPPH.js.map → chunk-OQHN2GWX.js.map} +0 -0
- /package/dist/chunks/{chunk-MUONTMYZ.js.map → chunk-XSDGE4RF.js.map} +0 -0
- /package/dist/chunks/{renderSvg-ZIUOPWJQ.js.map → renderSvg-NIWFCY3U.js.map} +0 -0
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
bubbleForLgvHover,
|
|
3
|
+
hoverFromPair,
|
|
4
|
+
hoverInRegion,
|
|
5
|
+
nodeForLgvHover,
|
|
6
|
+
readLgvHover,
|
|
7
|
+
readSelectedFeature,
|
|
8
|
+
} from './lgvHover'
|
|
2
9
|
|
|
10
|
+
import type { MinigraphBubble } from '@jbrowse/bandage-core/bubbles/bubbleLine'
|
|
3
11
|
import type { GraphNode } from '@jbrowse/bandage-core/types'
|
|
4
12
|
|
|
5
13
|
// The shape LinearGenomeViewContainer writes on mousemove: `hoverPosition` is a
|
|
@@ -200,3 +208,94 @@ test('a coordinate past the graph resolves to nothing', () => {
|
|
|
200
208
|
nodeForLgvHover({ hover: { refName: 'chr1', coord: 9999 }, nodes: NODES }),
|
|
201
209
|
).toBeNull()
|
|
202
210
|
})
|
|
211
|
+
|
|
212
|
+
describe('the bubble a linear-view feature names', () => {
|
|
213
|
+
const bubble = (over: Partial<MinigraphBubble>): MinigraphBubble => ({
|
|
214
|
+
refName: 'chr1',
|
|
215
|
+
start: 5,
|
|
216
|
+
end: 8,
|
|
217
|
+
segmentCount: 3,
|
|
218
|
+
pathCount: 2,
|
|
219
|
+
inversion: false,
|
|
220
|
+
shortestAlleleLength: 0,
|
|
221
|
+
longestAlleleLength: 2,
|
|
222
|
+
segments: '',
|
|
223
|
+
shortestAllele: undefined,
|
|
224
|
+
longestAllele: undefined,
|
|
225
|
+
...over,
|
|
226
|
+
})
|
|
227
|
+
const indexed = bubble({ segments: 's10,s11,s12' })
|
|
228
|
+
const derived = bubble({ segments: 's21,s20,s22', key: 's20>s22' })
|
|
229
|
+
const bubbles = [indexed, derived]
|
|
230
|
+
const at = { refName: 'chr1', coord: 6 }
|
|
231
|
+
|
|
232
|
+
test("is the indexed bubble with a bubble feature's segments", () => {
|
|
233
|
+
expect(
|
|
234
|
+
bubbleForLgvHover(
|
|
235
|
+
{ ...at, featureSegments: 's10,s11,s12', featureName: '2 bp' },
|
|
236
|
+
bubbles,
|
|
237
|
+
),
|
|
238
|
+
).toBe(indexed)
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
// vg deconstruct writes the snarl's ends as the record ID
|
|
242
|
+
test('is the bubble a graph-derived VCF record names, either way round', () => {
|
|
243
|
+
expect(bubbleForLgvHover({ ...at, featureName: '>s20>s22' }, bubbles)).toBe(
|
|
244
|
+
derived,
|
|
245
|
+
)
|
|
246
|
+
expect(bubbleForLgvHover({ ...at, featureName: '<s22<s20' }, bubbles)).toBe(
|
|
247
|
+
derived,
|
|
248
|
+
)
|
|
249
|
+
expect(bubbleForLgvHover({ ...at, featureName: '>s10>s12' }, bubbles)).toBe(
|
|
250
|
+
indexed,
|
|
251
|
+
)
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
test('is nothing for a feature that names no bubble', () => {
|
|
255
|
+
expect(bubbleForLgvHover({ ...at, featureName: 'rs123' }, bubbles)).toBe(
|
|
256
|
+
undefined,
|
|
257
|
+
)
|
|
258
|
+
expect(bubbleForLgvHover({ ...at, featureName: '>s20>s99' }, bubbles)).toBe(
|
|
259
|
+
undefined,
|
|
260
|
+
)
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
test('a selected feature reads as a hover at its start', () => {
|
|
264
|
+
const fields: Record<string, unknown> = {
|
|
265
|
+
refName: 'chr1',
|
|
266
|
+
start: 5,
|
|
267
|
+
name: '>s20>s22',
|
|
268
|
+
}
|
|
269
|
+
expect(
|
|
270
|
+
readSelectedFeature({ get: (key: string) => fields[key] }),
|
|
271
|
+
).toMatchObject({ refName: 'chr1', coord: 5, featureName: '>s20>s22' })
|
|
272
|
+
expect(readSelectedFeature(undefined)).toBeUndefined()
|
|
273
|
+
})
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
// Two linear views on one locus: the graph paired with A must not light for
|
|
277
|
+
// a hover in B
|
|
278
|
+
describe('a hover from another linear view', () => {
|
|
279
|
+
const at = (viewId?: string) => ({ refName: 'chr1', coord: 3, viewId })
|
|
280
|
+
const live = new Set(['lgvA', 'lgvB'])
|
|
281
|
+
|
|
282
|
+
test('is read off the hover payload', () => {
|
|
283
|
+
expect(
|
|
284
|
+
readLgvHover({
|
|
285
|
+
hoverPosition: { refName: 'chr1', coord: 4 },
|
|
286
|
+
viewId: 'lgvB',
|
|
287
|
+
})?.viewId,
|
|
288
|
+
).toBe('lgvB')
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
test('is ignored by a graph paired with an open view', () => {
|
|
292
|
+
expect(hoverFromPair(at('lgvA'), 'lgvA', live)).toBe(true)
|
|
293
|
+
expect(hoverFromPair(at('lgvB'), 'lgvA', live)).toBe(false)
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
test('reaches an unpaired graph, one whose pair closed, or from an older host', () => {
|
|
297
|
+
expect(hoverFromPair(at('lgvB'), undefined, live)).toBe(true)
|
|
298
|
+
expect(hoverFromPair(at('lgvB'), 'gone', live)).toBe(true)
|
|
299
|
+
expect(hoverFromPair(at(), 'lgvA', live)).toBe(true)
|
|
300
|
+
})
|
|
301
|
+
})
|
|
@@ -3,6 +3,7 @@ import { isBackbone } from '@jbrowse/bandage-core/anchoredNodes'
|
|
|
3
3
|
import { isRecord } from '../isRecord'
|
|
4
4
|
|
|
5
5
|
import type { SubgraphRegion } from '../GetSubgraph'
|
|
6
|
+
import type { MinigraphBubble } from '@jbrowse/bandage-core/bubbles/bubbleLine'
|
|
6
7
|
import type { GraphNode } from '@jbrowse/bandage-core/types'
|
|
7
8
|
|
|
8
9
|
// What a LinearGenomeView writes to `session.hovered` on every mousemove (see
|
|
@@ -33,6 +34,11 @@ export interface LgvHover {
|
|
|
33
34
|
// hover that states no assembly is taken at its word rather than dropped.
|
|
34
35
|
assemblyName?: string
|
|
35
36
|
featureName?: string
|
|
37
|
+
// a bubble feature's comma-separated segment names (MinigraphBubbleAdapter)
|
|
38
|
+
featureSegments?: string
|
|
39
|
+
// the linear view hovered, which hosts from jbrowse-components b49f05f438
|
|
40
|
+
// state
|
|
41
|
+
viewId?: string
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
interface FeatureLike {
|
|
@@ -46,13 +52,13 @@ function isFeatureLike(value: unknown): value is FeatureLike {
|
|
|
46
52
|
// A display hovers either a Feature, read with `get`, or a plain hit item with
|
|
47
53
|
// a `name` of its own, which is what the canvas feature display hands over.
|
|
48
54
|
// Reading only the first left the exact segment match dead on that display.
|
|
49
|
-
function
|
|
50
|
-
const
|
|
51
|
-
? feature.get(
|
|
55
|
+
function hoveredField(feature: unknown, key: string) {
|
|
56
|
+
const value = isFeatureLike(feature)
|
|
57
|
+
? feature.get(key)
|
|
52
58
|
: isRecord(feature)
|
|
53
|
-
? feature
|
|
59
|
+
? feature[key]
|
|
54
60
|
: undefined
|
|
55
|
-
return typeof
|
|
61
|
+
return typeof value === 'string' ? value : undefined
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
// `hovered` is typed `unknown` on the session by design — it is a shared channel
|
|
@@ -71,13 +77,49 @@ export function readLgvHover(hovered: unknown): LgvHover | undefined {
|
|
|
71
77
|
coord: typeof coord0 === 'number' ? coord0 : coord - 1,
|
|
72
78
|
assemblyName:
|
|
73
79
|
typeof assemblyName === 'string' ? assemblyName : undefined,
|
|
74
|
-
featureName:
|
|
80
|
+
featureName: hoveredField(hovered.hoverFeature, 'name'),
|
|
81
|
+
featureSegments: hoveredField(hovered.hoverFeature, 'segments'),
|
|
82
|
+
viewId: typeof hovered.viewId === 'string' ? hovered.viewId : undefined,
|
|
75
83
|
}
|
|
76
84
|
}
|
|
77
85
|
}
|
|
78
86
|
return result
|
|
79
87
|
}
|
|
80
88
|
|
|
89
|
+
// Whether a hover may reach a graph paired with `paired`: one naming another
|
|
90
|
+
// view does not, while the paired view is still open. A hover naming no view,
|
|
91
|
+
// from an older host, and a graph whose pair has closed take any.
|
|
92
|
+
export function hoverFromPair(
|
|
93
|
+
hover: LgvHover,
|
|
94
|
+
paired: string | undefined,
|
|
95
|
+
liveViewIds: ReadonlySet<string>,
|
|
96
|
+
) {
|
|
97
|
+
return (
|
|
98
|
+
hover.viewId === undefined ||
|
|
99
|
+
paired === undefined ||
|
|
100
|
+
!liveViewIds.has(paired) ||
|
|
101
|
+
hover.viewId === paired
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// The feature a click in a linear view selected (`session.selection`, which
|
|
106
|
+
// core's feature widget sets), read as a hover at its start
|
|
107
|
+
export function readSelectedFeature(selection: unknown): LgvHover | undefined {
|
|
108
|
+
if (!isFeatureLike(selection)) {
|
|
109
|
+
return undefined
|
|
110
|
+
}
|
|
111
|
+
const refName = selection.get('refName')
|
|
112
|
+
const start = selection.get('start')
|
|
113
|
+
return typeof refName === 'string' && typeof start === 'number'
|
|
114
|
+
? {
|
|
115
|
+
refName,
|
|
116
|
+
coord: start,
|
|
117
|
+
featureName: hoveredField(selection, 'name'),
|
|
118
|
+
featureSegments: hoveredField(selection, 'segments'),
|
|
119
|
+
}
|
|
120
|
+
: undefined
|
|
121
|
+
}
|
|
122
|
+
|
|
81
123
|
// Whether a hover is one this graph can answer: the same assembly, the same
|
|
82
124
|
// stable sequence, and inside the window the cut was made for.
|
|
83
125
|
//
|
|
@@ -130,3 +172,32 @@ export function nodeForLgvHover({
|
|
|
130
172
|
// An exact segment match wins over the coordinate it happens to sit at.
|
|
131
173
|
return byName ?? byCoord ?? null
|
|
132
174
|
}
|
|
175
|
+
|
|
176
|
+
// A bubble's two end segments, unordered: a derived bubble's key is
|
|
177
|
+
// `source>sink`, and an indexed one lists its segments source first
|
|
178
|
+
function bubbleEnds(b: Pick<MinigraphBubble, 'key' | 'segments'>) {
|
|
179
|
+
const ids = b.key?.split('>') ?? b.segments.split(',').filter(Boolean)
|
|
180
|
+
return ids.length >= 2 ? [ids[0]!, ids.at(-1)!].sort().join('\t') : undefined
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// The ends a hovered feature names: a bubble feature's segments, or a vg
|
|
184
|
+
// deconstruct record's ID, `>source>sink` (`<sink<source` on the reverse)
|
|
185
|
+
function featureEnds(hover: LgvHover) {
|
|
186
|
+
if (hover.featureSegments) {
|
|
187
|
+
return bubbleEnds({ segments: hover.featureSegments })
|
|
188
|
+
}
|
|
189
|
+
const ids = /^([<>][^<>]+){2}$/.test(hover.featureName ?? '')
|
|
190
|
+
? hover.featureName!.split(/[<>]/).filter(Boolean)
|
|
191
|
+
: []
|
|
192
|
+
return ids.length === 2 ? ids.sort().join('\t') : undefined
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// The graph's bubble a linear-view feature names, so a bubble track or a
|
|
196
|
+
// graph-derived VCF lights the whole bubble rather than the backbone node at
|
|
197
|
+
// the pointer's bp
|
|
198
|
+
export function bubbleForLgvHover(hover: LgvHover, bubbles: MinigraphBubble[]) {
|
|
199
|
+
const ends = featureEnds(hover)
|
|
200
|
+
return ends === undefined
|
|
201
|
+
? undefined
|
|
202
|
+
: bubbles.find(b => bubbleEnds(b) === ends)
|
|
203
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { PathVisits } from '@jbrowse/bandage-core/pathVisits'
|
|
1
2
|
import { expect, test } from 'vitest'
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
contributingAssemblies,
|
|
5
6
|
locString,
|
|
7
|
+
nodeAssemblySpans,
|
|
6
8
|
nodeOwnLocation,
|
|
7
9
|
resolveContributors,
|
|
8
10
|
resolveLocationAssembly,
|
|
@@ -141,6 +143,27 @@ test('a contributor resolves through an assembly alias, under its own name', ()
|
|
|
141
143
|
).toMatchObject({ refName: 'chr17', start: 83899576, end: 84041803 })
|
|
142
144
|
})
|
|
143
145
|
|
|
146
|
+
test('two haplotypes on one contig name do not share a bare-sample assembly', () => {
|
|
147
|
+
const contributors = contributingAssemblies(
|
|
148
|
+
graph([
|
|
149
|
+
node('s1', 'GRCh38#0#chr6', 31000000, 5000, 0),
|
|
150
|
+
node('s2', 'HG002#1#chr6', 31100000, 300, 1),
|
|
151
|
+
node('s3', 'HG002#2#chr6', 31050000, 300, 1),
|
|
152
|
+
]),
|
|
153
|
+
)
|
|
154
|
+
const bySample = (sample: string) =>
|
|
155
|
+
({ HG002: 'HG002', 'HG002#2': 'HG002.2', GRCh38: 'hg38' })[sample]
|
|
156
|
+
expect(
|
|
157
|
+
resolveContributors(contributors, bySample).map(c => [
|
|
158
|
+
c.sample,
|
|
159
|
+
c.haplotype,
|
|
160
|
+
]),
|
|
161
|
+
).toEqual([
|
|
162
|
+
['hg38', 'GRCh38#0'],
|
|
163
|
+
['HG002.2', 'HG002#2'],
|
|
164
|
+
])
|
|
165
|
+
})
|
|
166
|
+
|
|
144
167
|
test('a locstring is 1-based inclusive', () => {
|
|
145
168
|
expect(
|
|
146
169
|
locString({
|
|
@@ -201,3 +224,32 @@ test('a haplotype-named graph contributes per haplotype, resolved at that depth
|
|
|
201
224
|
resolveLocationAssembly(bySample, { sample: 'K12', haplotype: undefined }),
|
|
202
225
|
).toBeUndefined()
|
|
203
226
|
})
|
|
227
|
+
|
|
228
|
+
test("a node's spans are where each walk through it passes it, on loaded assemblies", () => {
|
|
229
|
+
const allele = node('s7', 'HG002#1#CM1', 500, 300, 1)
|
|
230
|
+
const g = {
|
|
231
|
+
...graph([allele]),
|
|
232
|
+
pathVisits: PathVisits.from([
|
|
233
|
+
[
|
|
234
|
+
's7',
|
|
235
|
+
[
|
|
236
|
+
{
|
|
237
|
+
path: 'GRCh38#0#chr6',
|
|
238
|
+
sample: 'GRCh38',
|
|
239
|
+
start: 31000,
|
|
240
|
+
strand: '+',
|
|
241
|
+
},
|
|
242
|
+
{ path: 'HG002#1#CM1', sample: 'HG002', start: 500, strand: '+' },
|
|
243
|
+
{ path: 'HG002#2#CM2', sample: 'HG002', start: 9100, strand: '-' },
|
|
244
|
+
{ path: 'NA1#1#chr6', sample: 'NA1', start: 40, strand: '+' },
|
|
245
|
+
],
|
|
246
|
+
],
|
|
247
|
+
]),
|
|
248
|
+
}
|
|
249
|
+
const resolve = (sample: string) =>
|
|
250
|
+
({ GRCh38: 'hg38', 'HG002#1': 'HG002.1', 'HG002#2': 'HG002.2' })[sample]
|
|
251
|
+
expect(nodeAssemblySpans(g, allele, resolve, 'hg38')).toEqual([
|
|
252
|
+
{ assemblyName: 'HG002.1', refName: 'CM1', start: 500, end: 800 },
|
|
253
|
+
{ assemblyName: 'HG002.2', refName: 'CM2', start: 9100, end: 9400 },
|
|
254
|
+
])
|
|
255
|
+
})
|
|
@@ -44,6 +44,94 @@ function anchoredNodeLocation(node: AnchoredNode): GraphLocation {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
export interface AssemblySpan {
|
|
48
|
+
assemblyName: string
|
|
49
|
+
refName: string
|
|
50
|
+
start: number
|
|
51
|
+
end: number
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Where a node's sequence sits on each loaded assembly: its own rGFA
|
|
55
|
+
// location, and where every walk through it passes it on that walk's contig.
|
|
56
|
+
// `except` is the assembly the reference band is already drawn on.
|
|
57
|
+
export function nodeAssemblySpans(
|
|
58
|
+
graph: Graph,
|
|
59
|
+
node: GraphNode,
|
|
60
|
+
resolve: (sample: string) => string | undefined,
|
|
61
|
+
except?: string,
|
|
62
|
+
) {
|
|
63
|
+
const spans = new Map<string, AssemblySpan>()
|
|
64
|
+
const add = (loc: GraphLocation) => {
|
|
65
|
+
const assemblyName = resolveLocationAssembly(resolve, loc)
|
|
66
|
+
const key = `${assemblyName}\t${loc.refName}\t${loc.start}`
|
|
67
|
+
if (assemblyName !== undefined && assemblyName !== except) {
|
|
68
|
+
spans.set(key, {
|
|
69
|
+
assemblyName,
|
|
70
|
+
refName: loc.refName,
|
|
71
|
+
start: loc.start,
|
|
72
|
+
end: loc.end,
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const own = nodeOwnLocation(node)
|
|
77
|
+
if (own) {
|
|
78
|
+
add(own)
|
|
79
|
+
}
|
|
80
|
+
for (const visit of graph.pathVisits?.get(node.name) ?? []) {
|
|
81
|
+
add({
|
|
82
|
+
sample: panSNSample(visit.path),
|
|
83
|
+
haplotype: panSNHaplotype(visit.path),
|
|
84
|
+
refName: panSNContig(visit.path),
|
|
85
|
+
start: visit.start,
|
|
86
|
+
end: visit.start + node.length,
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
return [...spans.values()]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Each stable sequence off the reference that the cut's nodes lie on, with
|
|
93
|
+
// the loaded assembly holding it and the span its nodes cover there. A gene
|
|
94
|
+
// read over that span, renamed to `stableRefName`, lands on those nodes the
|
|
95
|
+
// way the reference's genes land on the backbone.
|
|
96
|
+
export function offReferenceContigs(
|
|
97
|
+
graph: Graph,
|
|
98
|
+
resolve: (sample: string) => string | undefined,
|
|
99
|
+
except?: string,
|
|
100
|
+
) {
|
|
101
|
+
const contigs = new Map<string, AssemblySpan & { stableRefName: string }>()
|
|
102
|
+
// an HPRC cut names hundreds of haplotypes no session loads
|
|
103
|
+
const unloaded = new Set<string>()
|
|
104
|
+
for (const node of graph.nodes) {
|
|
105
|
+
if (!isAnchored(node) || node.stable.rank === 0) {
|
|
106
|
+
continue
|
|
107
|
+
}
|
|
108
|
+
const stableRefName = node.stable.refName
|
|
109
|
+
if (unloaded.has(stableRefName)) {
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
112
|
+
const loc = anchoredNodeLocation(node)
|
|
113
|
+
const seen = contigs.get(stableRefName)
|
|
114
|
+
if (seen) {
|
|
115
|
+
seen.start = Math.min(seen.start, loc.start)
|
|
116
|
+
seen.end = Math.max(seen.end, loc.end)
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
const assemblyName = resolveLocationAssembly(resolve, loc)
|
|
120
|
+
if (assemblyName === undefined || assemblyName === except) {
|
|
121
|
+
unloaded.add(stableRefName)
|
|
122
|
+
} else {
|
|
123
|
+
contigs.set(stableRefName, {
|
|
124
|
+
stableRefName,
|
|
125
|
+
assemblyName,
|
|
126
|
+
refName: loc.refName,
|
|
127
|
+
start: loc.start,
|
|
128
|
+
end: loc.end,
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return [...contigs.values()]
|
|
133
|
+
}
|
|
134
|
+
|
|
47
135
|
export interface Contributor extends GraphLocation {
|
|
48
136
|
// lowest rank this assembly holds here: 0 means it is the reference backbone
|
|
49
137
|
rank: number
|
|
@@ -188,12 +276,28 @@ export function resolveLocationAssembly(
|
|
|
188
276
|
// among its aliases; matching on names alone leaves that donor unopenable unless
|
|
189
277
|
// the reader renames their assembly after the graph. The resolved (canonical)
|
|
190
278
|
// name replaces the sample, so a launch opens the assembly the session knows.
|
|
279
|
+
//
|
|
280
|
+
// A bare sample stands in for a haplotype unless two haplotypes of it share a
|
|
281
|
+
// contig name. HPRC's GenBank contigs differ per haplotype, so an `NA20809`
|
|
282
|
+
// assembly can hold both; two haplotypes each on `chr6` cannot both be it, and
|
|
283
|
+
// opening hap 2's coordinates on hap 1's sequence lands somewhere plausible
|
|
284
|
+
// and wrong.
|
|
191
285
|
export function resolveContributors(
|
|
192
286
|
contributors: Contributor[],
|
|
193
287
|
resolve: (sample: string) => string | undefined,
|
|
194
288
|
) {
|
|
289
|
+
const haplotypesOn = new Map<string, number>()
|
|
290
|
+
const contig = (c: Contributor) => `${c.sample}\t${c.refName}`
|
|
291
|
+
for (const c of contributors) {
|
|
292
|
+
if (c.haplotype !== undefined) {
|
|
293
|
+
haplotypesOn.set(contig(c), (haplotypesOn.get(contig(c)) ?? 0) + 1)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
195
296
|
return contributors.flatMap(c => {
|
|
196
|
-
const sample =
|
|
297
|
+
const sample =
|
|
298
|
+
(haplotypesOn.get(contig(c)) ?? 0) > 1
|
|
299
|
+
? resolve(c.haplotype!)
|
|
300
|
+
: resolveLocationAssembly(resolve, c)
|
|
197
301
|
return sample === undefined ? [] : [{ ...c, sample }]
|
|
198
302
|
})
|
|
199
303
|
}
|
|
@@ -253,8 +253,8 @@ function assemblyNamesOfTrack(
|
|
|
253
253
|
// point of launching from the graph instead of from a linear view.
|
|
254
254
|
//
|
|
255
255
|
// Panels carry THE GRAPH'S OWN TRACK and nothing else, which is a narrower rule
|
|
256
|
-
// than the single-view launch's (see
|
|
257
|
-
//
|
|
256
|
+
// than the single-view launch's (see showInLinearView, which adds the
|
|
257
|
+
// assembly's gene track). Tried that here: a gene track per panel takes
|
|
258
258
|
// about 160px of a row that is otherwise a ruler, so on five strains the
|
|
259
259
|
// annotation is most of the viewport and the ribbons — the thing the launch
|
|
260
260
|
// exists to draw — are squeezed into the gaps, and `collapseEmptyRows` is here
|
package/src/panSNTabix.test.ts
CHANGED
|
@@ -66,4 +66,32 @@ describe('a region the file holds no name for', () => {
|
|
|
66
66
|
refNamesOf(['chr1', 'HG002#1#chr1']).resolveMapped(chrM),
|
|
67
67
|
).resolves.toBeUndefined()
|
|
68
68
|
})
|
|
69
|
+
|
|
70
|
+
// an assemblyNameToPanSN hint would send the user to fix a config that is
|
|
71
|
+
// already right
|
|
72
|
+
test('fails a cut by saying the graph has nothing there', async () => {
|
|
73
|
+
await expect(
|
|
74
|
+
refNamesOf(['chr1', 'HG002#1#chr1']).resolveForCut(chrM),
|
|
75
|
+
).rejects.toThrow('This graph has nothing on hg38 chrM')
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// A walk file indexes `chr1~851968`-style detail sequences beside its contigs
|
|
80
|
+
test("a walk file's detail sequences are not contigs", async () => {
|
|
81
|
+
const refNames = new PanSNRefNames(
|
|
82
|
+
{
|
|
83
|
+
getReferenceSequenceNames: () =>
|
|
84
|
+
Promise.resolve(['chr1', 'chr1~851968', 'chr2', 'chr2~65536']),
|
|
85
|
+
} as never,
|
|
86
|
+
{ getConf: () => undefined } as never,
|
|
87
|
+
)
|
|
88
|
+
expect(await refNames.assemblyRefNames()).toEqual(['chr1', 'chr2'])
|
|
89
|
+
expect(
|
|
90
|
+
await refNames.resolve({
|
|
91
|
+
assemblyName: 'hg38',
|
|
92
|
+
refName: 'chr1~851968',
|
|
93
|
+
start: 0,
|
|
94
|
+
end: 1,
|
|
95
|
+
}),
|
|
96
|
+
).toBeUndefined()
|
|
69
97
|
})
|
package/src/panSNTabix.ts
CHANGED
|
@@ -96,24 +96,23 @@ export function openTabixSlot(
|
|
|
96
96
|
})
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
export function unmappedRegionMessage(region: Region) {
|
|
100
|
+
return `${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`
|
|
101
|
+
}
|
|
102
|
+
|
|
99
103
|
/**
|
|
100
104
|
* The reference-name half of a tabix graph track, against one indexed file.
|
|
101
105
|
*
|
|
102
106
|
* Constructed with the file the adapter's own slots opened, so this never reads
|
|
103
107
|
* a slot whose name it cannot prove.
|
|
104
108
|
*/
|
|
105
|
-
export function unmappedRegionMessage(region: Region) {
|
|
106
|
-
return `${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`
|
|
107
|
-
}
|
|
108
|
-
|
|
109
109
|
export class PanSNRefNames {
|
|
110
110
|
// Read once and shared by every query, so it takes on no caller's signal: a
|
|
111
111
|
// track fetch the user panned away from once aborted the read a graph cut
|
|
112
112
|
// was waiting on. cachedSetup rejects only the caller that aborts, keeps the
|
|
113
113
|
// read going for the rest, and retries a failed read.
|
|
114
114
|
private lookup = cachedSetup({
|
|
115
|
-
setup: async opts =>
|
|
116
|
-
refNameLookup(await this.file.getReferenceSequenceNames(opts)),
|
|
115
|
+
setup: async opts => refNameLookup(await this.names(opts)),
|
|
117
116
|
})
|
|
118
117
|
|
|
119
118
|
constructor(
|
|
@@ -121,6 +120,13 @@ export class PanSNRefNames {
|
|
|
121
120
|
private adapter: BaseFeatureDataAdapter,
|
|
122
121
|
) {}
|
|
123
122
|
|
|
123
|
+
// A walk file also indexes `<seq>~<chunkStart>` detail sequences, which name
|
|
124
|
+
// no contig
|
|
125
|
+
private async names(opts?: BaseOptions) {
|
|
126
|
+
const names = await this.file.getReferenceSequenceNames(opts)
|
|
127
|
+
return names.filter(n => !n.includes('~'))
|
|
128
|
+
}
|
|
129
|
+
|
|
124
130
|
/**
|
|
125
131
|
* The names the ASSEMBLY uses, not the graph's own.
|
|
126
132
|
*
|
|
@@ -132,7 +138,7 @@ export class PanSNRefNames {
|
|
|
132
138
|
* were spotted.
|
|
133
139
|
*/
|
|
134
140
|
async assemblyRefNames(opts: BaseOptions = {}) {
|
|
135
|
-
const names = await this.
|
|
141
|
+
const names = await this.names(opts)
|
|
136
142
|
const prefix = resolvePanSNPrefix(this.adapter, opts.assemblyName)
|
|
137
143
|
const contigs = names
|
|
138
144
|
.filter(n => panSNMatchesPrefix(n, prefix))
|
|
@@ -167,7 +173,7 @@ export class PanSNRefNames {
|
|
|
167
173
|
async resolveMapped(region: Region, opts?: BaseOptions) {
|
|
168
174
|
const refName = await this.resolve(region, opts)
|
|
169
175
|
if (refName === undefined && region.assemblyName) {
|
|
170
|
-
const names = await this.
|
|
176
|
+
const names = await this.names(opts)
|
|
171
177
|
const prefix = resolvePanSNPrefix(this.adapter, region.assemblyName)
|
|
172
178
|
if (
|
|
173
179
|
names.every(n => n.includes('#')) &&
|
|
@@ -179,6 +185,18 @@ export class PanSNRefNames {
|
|
|
179
185
|
return refName
|
|
180
186
|
}
|
|
181
187
|
|
|
188
|
+
// The name a graph cut reads, failing with what is actually wrong: an
|
|
189
|
+
// unmapped assembly, or a contig such as chrM the graph has nothing on
|
|
190
|
+
async resolveForCut(region: Region, opts?: BaseOptions) {
|
|
191
|
+
const refName = await this.resolveMapped(region, opts)
|
|
192
|
+
if (refName === undefined) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
`This graph has nothing on ${region.assemblyName} ${region.refName}`,
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
return refName
|
|
198
|
+
}
|
|
199
|
+
|
|
182
200
|
/**
|
|
183
201
|
* The compressed bytes a read of `regions` fetches from the file, as its
|
|
184
202
|
* index estimates them, for the display's byte gate. `queryStart` moves a
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '7.
|
|
1
|
+
export const version = '7.6.0'
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import{a as G,b as L}from"./chunk-6VNU7SD4.js";import{b as v,c as x,n as d}from"./chunk-7RJMG6I2.js";import"./chunk-2KVE7YAS.js";import"./chunk-S333HOG5.js";import"./chunk-LE7FZM6Y.js";import"./chunk-JRJHXLGP.js";import"./chunk-6SWXMRAV.js";import"./chunk-IKJZPPOA.js";import{g,h}from"./chunk-YU7T5WOQ.js";import{F as m,H as u,n as C}from"./chunk-UGCNPRKC.js";import{b as r,c as i,d as c}from"./chunk-JMBDTNUR.js";var P=d()({section:{marginBottom:24}}),T=[{value:"0",label:"None"},{value:"1",label:"1 hop"},{value:"2",label:"2 hops"}],w=c(function({model:t}){let{classes:l}=P(),o=t.sourceAdapter;return o?.type==="RgfaTabixAdapter"&&!v(o)?i("div",{className:l.section,children:[r(G,{label:"Graph context",sx:{minWidth:200},value:String(t.subgraphContext),options:T,testId:"graph-context-select",onChange:a=>{t.setSubgraphContext(Number(a)),t.recut()}}),r(u,{variant:"caption",component:"div",color:"text.secondary",children:"How far the cut follows links out of the region. At none, a detour that leaves the window draws as two stubs. No effect on gfa-to-tabix's default anchored index."})]}):null}),k=w;var F=d()({section:{marginBottom:24}});function M(n){return n.split(/[\s,]+/).filter(t=>t!=="")}var I=c(function({model:t}){let[l,o]=h(t.cutHaplotypes?.join(", ")??""),a=()=>{let e=M(l);e.join(`
|
|
2
|
-
`)===(t.cutHaplotypes??[]).join(`
|
|
3
|
-
`)||(t.setSubgraphHaplotypes(e),t.recut())};return r(m,{fullWidth:!0,label:"Haplotypes",placeholder:"every haplotype",value:l,slotProps:{htmlInput:{"data-testid":"graph-haplotypes-field"}},onChange:e=>{o(e.target.value)},onBlur:()=>{a()},onKeyDown:e=>{e.key==="Enter"&&a()}})}),A=c(function({model:t,names:l}){let[o,a]=h(t.cutHaplotypes??[]),e=g(o),f=g(""),[b,S]=h(!1),H=p=>{p.join(`
|
|
4
|
-
`)!==(t.cutHaplotypes??[]).join(`
|
|
5
|
-
`)&&(t.setSubgraphHaplotypes(p),t.recut())};return r(C,{multiple:!0,freeSolo:!0,disableCloseOnSelect:!0,options:l,value:o,open:b,onOpen:()=>{S(!0)},onInputChange:(p,s)=>{f.current=s},onClose:(p,s)=>{S(!1);let y=f.current.trim();s==="blur"&&y&&!e.current.includes(y)&&(e.current=[...e.current,y],a(e.current)),H(e.current)},onChange:(p,s)=>{e.current=s,a(s),b||H(s)},renderInput:p=>r(m,{...p,label:"Haplotypes",placeholder:o.length===0?"every haplotype":void 0,slotProps:{...p.slotProps,htmlInput:{...p.slotProps.htmlInput,"data-testid":"graph-haplotypes-field"}}})})}),D=c(function({model:t}){let{classes:l}=F(),o=t.haplotypeNames,a=t.cutHaplotypes?.join(",")??"",e=t.sourceAdapter;return e&&x(e)?i("div",{className:l.section,children:[o?r(A,{model:t,names:o},a):r(I,{model:t},a),i(u,{variant:"caption",component:"div",color:"text.secondary",children:[o?`Which of the graph's ${o.length.toLocaleString()} haplotypes to cut, or a PanSN prefix (HG002 for both).`:"Which haplotypes to cut, as lane assembly names or PanSN prefixes (HG002#1, or HG002 for both) separated by commas."," ","Empty is every haplotype."]})]}):null}),N=D;function B({model:n,onClose:t}){return i(L,{model:n,onClose:t,children:[r(k,{model:n}),r(N,{model:n})]})}export{B as default};
|
|
6
|
-
//# sourceMappingURL=GraphTrackSettingsDialog-EVIIGC5G.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/LinearGraphDisplay/components/SubgraphContextSelect.tsx", "../../src/LinearGraphDisplay/components/SubgraphHaplotypesField.tsx", "../../src/LinearGraphDisplay/components/GraphTrackSettingsDialog.tsx"],
|
|
4
|
-
"sourcesContent": ["import { Typography } from '@mui/material'\nimport { observer } from 'mobx-react'\nimport { makeStyles } from 'tss-react/mui'\n\nimport LabelledSelect from '../../GraphGenomeView/components/LabelledSelect'\nimport { walkIndexed } from '../../graphTrackConfig'\n\nimport type { LinearGraphCutModel } from '../model'\n\nconst useStyles = makeStyles()({\n section: {\n marginBottom: 24,\n },\n})\n\n// Only an rGFA cut from segment rows reads hops: a GBZ cut widens by its\n// adapter's bp `context`, and a walk-indexed cut follows its walks.\n// Hops past the region's own segments, each costing a tabix query per\n// off-reference segment already reached. One is the default: at none a single\n// detour draws as two unrelated stubs, which is a wrong picture of the graph\n// rather than a cheaper one. Two exists for a graph whose alleles have alleles\n// (HPRC's amylase window keeps growing at 2, the E. coli paa locus does not), and\n// it stops there because a frontier is still not a bubble decomposition. Cut an\n// exact slice with gfatools when that is what is wanted.\nconst SUBGRAPH_CONTEXTS = [\n { value: '0', label: 'None' },\n { value: '1', label: '1 hop' },\n { value: '2', label: '2 hops' },\n]\n\nconst SubgraphContextSelect = observer(function SubgraphContextSelect({\n model,\n}: {\n model: LinearGraphCutModel\n}) {\n const { classes } = useStyles()\n const adapter = model.sourceAdapter\n return adapter?.type === 'RgfaTabixAdapter' && !walkIndexed(adapter) ? (\n <div className={classes.section}>\n <LabelledSelect\n label=\"Graph context\"\n sx={{ minWidth: 200 }}\n value={String(model.subgraphContext)}\n options={SUBGRAPH_CONTEXTS}\n testId=\"graph-context-select\"\n onChange={hops => {\n model.setSubgraphContext(Number(hops))\n model.recut()\n }}\n />\n <Typography variant=\"caption\" component=\"div\" color=\"text.secondary\">\n How far the cut follows links out of the region. At none, a detour that\n leaves the window draws as two stubs. No effect on gfa-to-tabix's\n default anchored index.\n </Typography>\n </div>\n ) : null\n})\n\nexport default SubgraphContextSelect\n", "import { useRef, useState } from 'react'\n\nimport { Autocomplete, TextField, Typography } from '@mui/material'\nimport { observer } from 'mobx-react'\nimport { makeStyles } from 'tss-react/mui'\n\nimport { cutsByHaplotype } from '../../graphTrackConfig'\n\nimport type { LinearGraphCutModel } from '../model'\n\nconst useStyles = makeStyles()({\n section: {\n marginBottom: 24,\n },\n})\n\nfunction parseHaplotypeList(text: string) {\n return text.split(/[\\s,]+/).filter(name => name !== '')\n}\n\nconst HaplotypeListField = observer(function HaplotypeListField({\n model,\n}: {\n model: LinearGraphCutModel\n}) {\n const [draft, setDraft] = useState(model.cutHaplotypes?.join(', ') ?? '')\n const apply = () => {\n const parsed = parseHaplotypeList(draft)\n const unchanged =\n parsed.join('\\n') === (model.cutHaplotypes ?? []).join('\\n')\n if (!unchanged) {\n model.setSubgraphHaplotypes(parsed)\n model.recut()\n }\n }\n return (\n <TextField\n fullWidth\n label=\"Haplotypes\"\n placeholder=\"every haplotype\"\n value={draft}\n slotProps={{ htmlInput: { 'data-testid': 'graph-haplotypes-field' } }}\n onChange={e => {\n setDraft(e.target.value)\n }}\n onBlur={() => {\n apply()\n }}\n onKeyDown={e => {\n if (e.key === 'Enter') {\n apply()\n }\n }}\n />\n )\n})\n\n// The haplotypes a walk-indexed graph's header names, searched as typed. A\n// typed prefix the list lacks (HG002 for both haplotypes) is kept too, on\n// Enter or when the field loses focus. The set applies when the list closes\n// or, with the list closed, as it changes. Not MUI's `autoSelect`: in a\n// multiple select its blur toggles the highlighted option, so a pick made by\n// keyboard was dropped again on Tab.\nconst HaplotypePickField = observer(function HaplotypePickField({\n model,\n names,\n}: {\n model: LinearGraphCutModel\n names: string[]\n}) {\n const [draft, setDraft] = useState(model.cutHaplotypes ?? [])\n const draftRef = useRef(draft)\n const typed = useRef('')\n const [open, setOpen] = useState(false)\n const apply = (haplotypes: string[]) => {\n if (haplotypes.join('\\n') !== (model.cutHaplotypes ?? []).join('\\n')) {\n model.setSubgraphHaplotypes(haplotypes)\n model.recut()\n }\n }\n return (\n <Autocomplete\n multiple\n freeSolo\n disableCloseOnSelect\n options={names}\n value={draft}\n open={open}\n onOpen={() => {\n setOpen(true)\n }}\n onInputChange={(_, text) => {\n typed.current = text\n }}\n onClose={(_, reason) => {\n setOpen(false)\n const text = typed.current.trim()\n if (reason === 'blur' && text && !draftRef.current.includes(text)) {\n draftRef.current = [...draftRef.current, text]\n setDraft(draftRef.current)\n }\n apply(draftRef.current)\n }}\n onChange={(_, value) => {\n draftRef.current = value\n setDraft(value)\n if (!open) {\n apply(value)\n }\n }}\n renderInput={params => (\n <TextField\n {...params}\n label=\"Haplotypes\"\n placeholder={draft.length === 0 ? 'every haplotype' : undefined}\n slotProps={{\n ...params.slotProps,\n htmlInput: {\n ...params.slotProps.htmlInput,\n 'data-testid': 'graph-haplotypes-field',\n },\n }}\n />\n )}\n />\n )\n})\n\n// Only a GBZ cut and a walk-indexed rGFA cut read the set. The inner field is\n// keyed on it so an outside change (a restored session, a launch) resets the\n// draft rather than fighting it.\nconst SubgraphHaplotypesField = observer(function SubgraphHaplotypesField({\n model,\n}: {\n model: LinearGraphCutModel\n}) {\n const { classes } = useStyles()\n const names = model.haplotypeNames\n const key = model.cutHaplotypes?.join(',') ?? ''\n const adapter = model.sourceAdapter\n return adapter && cutsByHaplotype(adapter) ? (\n <div className={classes.section}>\n {names ? (\n <HaplotypePickField key={key} model={model} names={names} />\n ) : (\n <HaplotypeListField key={key} model={model} />\n )}\n <Typography variant=\"caption\" component=\"div\" color=\"text.secondary\">\n {names\n ? `Which of the graph's ${names.length.toLocaleString()} haplotypes to cut, or a PanSN prefix (HG002 for both).`\n : 'Which haplotypes to cut, as lane assembly names or PanSN prefixes (HG002#1, or HG002 for both) separated by commas.'}{' '}\n Empty is every haplotype.\n </Typography>\n </div>\n ) : null\n})\n\nexport default SubgraphHaplotypesField\n", "import SubgraphContextSelect from './SubgraphContextSelect'\nimport SubgraphHaplotypesField from './SubgraphHaplotypesField'\nimport GraphSettingsDialog from '../../GraphGenomeView/components/GraphSettingsDialog'\n\nimport type { LinearGraphCutModel } from '../model'\n\nexport default function GraphTrackSettingsDialog({\n model,\n onClose,\n}: {\n model: LinearGraphCutModel\n onClose: () => void\n}) {\n return (\n <GraphSettingsDialog model={model} onClose={onClose}>\n <SubgraphContextSelect model={model} />\n <SubgraphHaplotypesField model={model} />\n </GraphSettingsDialog>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["useStyles", "makeStyles", "SUBGRAPH_CONTEXTS", "SubgraphContextSelect", "observer", "model", "classes", "adapter", "walkIndexed", "jsxs", "jsx", "LabelledSelect", "hops", "Typography", "SubgraphContextSelect_default", "useStyles", "makeStyles", "parseHaplotypeList", "text", "name", "HaplotypeListField", "observer", "model", "draft", "setDraft", "useState", "apply", "parsed", "jsx", "TextField", "HaplotypePickField", "names", "draftRef", "useRef", "typed", "open", "setOpen", "haplotypes", "Autocomplete", "_", "reason", "value", "params", "SubgraphHaplotypesField", "classes", "key", "adapter", "cutsByHaplotype", "jsxs", "Typography", "SubgraphHaplotypesField_default", "GraphTrackSettingsDialog", "model", "onClose", "jsxs", "GraphSettingsDialog_default", "jsx", "SubgraphContextSelect_default", "SubgraphHaplotypesField_default"]
|
|
7
|
-
}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{a as h}from"./chunk-HCDKP3IP.js";import"./chunk-6VNU7SD4.js";import"./chunk-LE43I4PT.js";import"./chunk-GRTN25B6.js";import"./chunk-7RJMG6I2.js";import"./chunk-WLRRI35A.js";import"./chunk-VZQ6VOUP.js";import"./chunk-2KVE7YAS.js";import"./chunk-S333HOG5.js";import"./chunk-P3FBI5ML.js";import"./chunk-6WUWQX2P.js";import"./chunk-KIYRMZWP.js";import"./chunk-YBLNN5FU.js";import"./chunk-G53BI7VK.js";import"./chunk-LE7FZM6Y.js";import"./chunk-YBLNN5FU.js";import"./chunk-JRJHXLGP.js";import"./chunk-6SWXMRAV.js";import"./chunk-IKJZPPOA.js";import"./chunk-NDRLCGPC.js";import"./chunk-TENPGIPX.js";import{h as T}from"./chunk-YU7T5WOQ.js";import"./chunk-UGCNPRKC.js";import{b as e,c as d,d as i}from"./chunk-JMBDTNUR.js";var n=JBrowseExports["@jbrowse/display-kit/DisplayBackgroundProgress"];var f=n&&n.__esModule?n.default:n;var l=JBrowseExports["@jbrowse/display-kit/DisplayErrorBar"];var v=l&&l.__esModule?l.default:l;var p=JBrowseExports["@jbrowse/display-kit/DisplayLoadingOverlay"];var O=p&&p.__esModule?p.default:p;var c=JBrowseExports["@jbrowse/display-kit/TooLargeMessage"];var L=c&&c.__esModule?c.default:c;var s=JBrowseExports["@jbrowse/display-ui"],u=r=>s!=null&&r in Object(s)?s[r]:Object.assign(function(){throw new Error("This JBrowse does not provide '"+r+"' from '@jbrowse/display-ui' to plugins: GraphGenomeView was built for a different JBrowse version, so update the plugin or JBrowse")},{jbrowseHostMissing:r});var w=u("BottomRightCornerContext");var g=u("TrackOverlayPortal");var C=u("useChromeOverlayOverride");var U=s&&s.__esModule?s.default:s;var k={TooLarge:L,ErrorBar:v,Loading:O,BackgroundProgress:f},m="Zoom in to see features, or force load this track for the rest of the session (may be slow)",M="Force load this track for the rest of the session (may be slow)";function H(r){let o=r.error??r.renderError,t=o instanceof Error&&o.regionTooLarge===!0;return{error:{error:o,onRetry:t?void 0:()=>{r.reload()}},tooLarge:{message:[r.regionTooLargeReason,r.zoomCanReleaseGate?m:M].filter(Boolean).join(". "),onForceLoad:()=>{r.forceLoad()}},loading:{message:r.statusMessage,progress:r.statusProgress,canceled:r.fetchCanceled,onCancel:()=>{r.cancelFetchByUser()},onRetry:()=>{r.reload()}},background:{message:r.statusMessage,progress:r.statusProgress}}}var _=i(function({model:o,phase:t,drawn:x,testid:E,style:B,children:G,...S}){let{TooLarge:b,ErrorBar:P,Loading:A,BackgroundProgress:D}=C()??k,[R,I]=T(null),a=H(o),y={...S,"data-testid":E,"data-display-id":o.configuration.displayId,"data-display-drawn":x,"data-display-phase":t};return t==="tooLarge"?e("div",{style:{display:"contents"},...y,children:e(g,{children:e("div",{style:{pointerEvents:"auto"},children:e(b,{model:o,...a.tooLarge})})})}):d("div",{style:{position:"relative",height:o.height,...B},...y,children:[e(w,{value:R,children:G}),d(g,{children:[t==="error"?e(P,{model:o,visible:!0,...a.error}):null,e(A,{model:o,...a.loading,visible:t==="loading"||t==="canceled",immediate:!x}),e("div",{ref:I,style:{position:"absolute",bottom:2,right:2,display:"flex",flexDirection:"column",alignItems:"flex-end",gap:4,pointerEvents:"none"},children:t==="ready"&&o.statusMessage?e(D,{model:o,visible:!0,...a.background}):null})]})]})});var j=i(function({model:o}){return e(_,{model:o,phase:o.displayPhase,drawn:o.painted&&o.geometryPainted,testid:"linear-graph-display","data-layout":o.chosenLayoutMode,"data-cut-tier":o.cutTier,"data-recuts":o.recuts,"data-node-count":o.hasGraph?o.nodeCount:void 0,"data-loading":o.isLoading?"":void 0,style:{width:o.paneWidth,height:o.height,overflow:"hidden"},children:e(h,{model:o,ownChrome:!1})})}),so=j;export{so as default};
|
|
2
|
-
//# sourceMappingURL=LinearGraphDisplay-ZC4BQ27J.js.map
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{a as b}from"./chunk-OJWBLPPH.js";import"./chunk-QV7SSOMJ.js";import{a as N}from"./chunk-KOB7YIMR.js";import"./chunk-YBLNN5FU.js";import"./chunk-G53BI7VK.js";import"./chunk-LE7FZM6Y.js";import{C as m,D as k,E as x,b as g,u as c,v as u,w as h,x as w}from"./chunk-FTXDTU27.js";import"./chunk-YBLNN5FU.js";import"./chunk-JRJHXLGP.js";import"./chunk-6SWXMRAV.js";import{d}from"./chunk-IKJZPPOA.js";import"./chunk-OGQ6OQDN.js";import"./chunk-NDRLCGPC.js";import{a as p,o as f}from"./chunk-TENPGIPX.js";var l=class extends g{constructor(){super(...arguments);this.fine=this.openIndex([])}static{this.capabilities=["getFeatures","getRefNames"]}openIndex(e){let a=m(this,[...e,"segmentsLocation"],[...e,"segmentsIndex"]),s=m(this,[...e,"linksLocation"],[...e,"linksIndex"]),n=this.getConf("walksLocation"),r=e.length===0&&d(n)?new b(m(this,["walksLocation"],["walksIndex"]),this):void 0;return{segments:a,links:s,walks:r,refNames:new x(r?.file??a,this)}}async getHaplotypeNames(e={}){return this.fine.walks?.haplotypeNames(e)}index(e="fine"){return e==="fine"?this.fine:(this.coarse??=this.openIndex(["coarse"]),this.coarse)}async getRefNames(e={}){return this.fine.refNames.assemblyRefNames(e)}async getRegionByteSize(e,a={}){let{walks:s,refNames:n}=this.fine;return n.regionBytes(e,a,s&&((r,{start:t,end:i})=>s.reader.queryStart(t,{...a,refName:r,end:i})))}async hasDataForRefName(){return!0}getFeatures(e,a={}){return N(async s=>{let n=await this.fine.refNames.resolveMapped(e,a);if(n!==void 0){let{walks:r}=this.fine,t=i=>{s.next(i)};await(r?this.walkFeatures(r,n,e,a,t):this.segmentFeatures(n,e,a,t))}s.complete()},a.signal)}async walkFeatures(e,a,s,n,r){let t=await f("Downloading walks",n.statusCallback,()=>e.referenceNodes(a,s,n));for(let i of t){let o=String(i.id);r(new p({uniqueId:o,refName:s.refName,start:i.start,end:i.end,name:o,type:"segment",rank:0,stableName:i.refName}))}}async segmentFeatures(e,a,s,n){await f("Downloading segments",s.statusCallback,()=>w(this.fine.segments,e,a.start,a.end,{signal:s.signal,lineCallback:r=>{let{segment:t,anchored:i}=c(r);if(i&&t.refName!==e)return;let o=u(t);n(new p({uniqueId:t.id,refName:a.refName,start:t.start,end:t.end,name:t.id,type:"segment",rank:t.rank,stableName:t.refName,...o&&{samples:o,sampleCount:o.length}}))}}))}async getSubgraph(e,a={}){let{hops:s,signal:n,tier:r}=a,t=this.index(r),i=await t.refNames.resolve(e,{signal:n});if(i===void 0)throw new Error(k(e));return t.walks?t.walks.subgraph(i,e,a):h(t,{refName:i,start:e.start,end:e.end},{hops:s,signal:n})}};export{l as default};
|
|
2
|
-
//# sourceMappingURL=RgfaTabixAdapter-XSOIEWBK.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/RgfaTabixAdapter/RgfaTabixAdapter.ts"],
|
|
4
|
-
"sourcesContent": ["import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter'\nimport { SimpleFeature, updateStatus } from '@jbrowse/core/util'\nimport { ObservableCreate } from '@jbrowse/core/util/rxjs'\nimport { parseSegmentRow, rgfaCut, segmentSamples } from '@jbrowse/graph-walks'\n\nimport { locationIsSet } from '../locationName.ts'\nimport {\n PanSNRefNames,\n openTabixSlot,\n unmappedRegionMessage,\n} from '../panSNTabix.ts'\nimport { getLines } from '../tabixRange.ts'\nimport { WalkIndex } from './walkReader.ts'\n\nimport type { RgfaTabixAdapterConfig } from './configSchema.ts'\nimport type { SubgraphAdapterOptions, SubgraphTier } from '../GetSubgraph.ts'\nimport type { WalkCut } from './walkCut.ts'\nimport type { TabixIndexedFile } from '@gmod/tabix'\nimport type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter'\nimport type { Feature } from '@jbrowse/core/util'\nimport type { FileLocation, Region } from '@jbrowse/core/util/types'\n\ninterface GraphIndex {\n segments: TabixIndexedFile\n links: TabixIndexedFile\n walks?: WalkIndex\n refNames: PanSNRefNames\n}\n\nexport default class RgfaTabixAdapter extends BaseFeatureDataAdapter<RgfaTabixAdapterConfig> {\n public static capabilities = ['getFeatures', 'getRefNames']\n\n private readonly fine = this.openIndex([])\n private coarse: GraphIndex | undefined\n\n // refNames off the SEGMENT index: it is the one that names every stable\n // sequence the graph is anchored to, where the link index only names those a\n // link touches. A walk-indexed graph's walk file names the same sequences,\n // and it has no segment file.\n private openIndex(under: string[]): GraphIndex {\n const segments = openTabixSlot(\n this,\n [...under, 'segmentsLocation'],\n [...under, 'segmentsIndex'],\n )\n const links = openTabixSlot(\n this,\n [...under, 'linksLocation'],\n [...under, 'linksIndex'],\n )\n const walksLocation: FileLocation = this.getConf('walksLocation')\n const walks =\n under.length === 0 && locationIsSet(walksLocation)\n ? new WalkIndex(\n openTabixSlot(this, ['walksLocation'], ['walksIndex']),\n this,\n )\n : undefined\n return {\n segments,\n links,\n walks,\n refNames: new PanSNRefNames(walks?.file ?? segments, this),\n }\n }\n\n /**\n * The haplotypes a walk-indexed graph's header names, `sample#haplotype`\n * in byte order, or undefined when it names none\n */\n async getHaplotypeNames(opts: BaseOptions = {}) {\n return this.fine.walks?.haplotypeNames(opts)\n }\n\n private index(tier: SubgraphTier = 'fine') {\n if (tier === 'fine') {\n return this.fine\n }\n this.coarse ??= this.openIndex(['coarse'])\n return this.coarse\n }\n\n async getRefNames(opts: BaseOptions = {}) {\n return this.fine.refNames.assemblyRefNames(opts)\n }\n\n // getFeatures reads a walk file from the chunk before the window\n async getRegionByteSize(regions: Region[], opts: BaseOptions = {}) {\n const { walks, refNames } = this.fine\n return refNames.regionBytes(\n regions,\n opts,\n walks &&\n ((refName, { start, end }) =>\n walks.reader.queryStart(start, { ...opts, refName, end })),\n )\n }\n\n public async hasDataForRefName() {\n return true\n }\n\n getFeatures(query: Region, opts: BaseOptions = {}) {\n return ObservableCreate<Feature>(async observer => {\n const tabixRefName = await this.fine.refNames.resolveMapped(query, opts)\n if (tabixRefName !== undefined) {\n const { walks } = this.fine\n const emit = (feature: Feature) => {\n observer.next(feature)\n }\n await (walks\n ? this.walkFeatures(walks, tabixRefName, query, opts, emit)\n : this.segmentFeatures(tabixRefName, query, opts, emit))\n }\n observer.complete()\n }, opts.signal)\n }\n\n // A walk-indexed graph's reference nodes are its ref rows' steps\n private async walkFeatures(\n walks: WalkIndex,\n tabixRefName: string,\n query: Region,\n opts: BaseOptions,\n emit: (feature: Feature) => void,\n ) {\n const nodes = await updateStatus(\n 'Downloading walks',\n opts.statusCallback,\n () => walks.referenceNodes(tabixRefName, query, opts),\n )\n for (const node of nodes) {\n const id = String(node.id)\n emit(\n new SimpleFeature({\n uniqueId: id,\n refName: query.refName,\n start: node.start,\n end: node.end,\n name: id,\n type: 'segment',\n rank: 0,\n stableName: node.refName,\n }),\n )\n }\n }\n\n private async segmentFeatures(\n tabixRefName: string,\n query: Region,\n opts: BaseOptions,\n emit: (feature: Feature) => void,\n ) {\n await updateStatus('Downloading segments', opts.statusCallback, () =>\n getLines(this.fine.segments, tabixRefName, query.start, query.end, {\n signal: opts.signal,\n lineCallback: line => {\n const { segment, anchored } = parseSegmentRow(line)\n // An anchored index also returns the alleles under the region,\n // which lie on other stable sequences and have no place on this one\n if (anchored && segment.refName !== tabixRefName) {\n return\n }\n // `samples` lists the haplotypes whose paths visit the segment and\n // `sampleCount` counts them. A lane's color reads the count, and\n // a count in jexl would otherwise rely on `.length` resolving\n // through a member access on an array.\n const samples = segmentSamples(segment)\n emit(\n new SimpleFeature({\n uniqueId: segment.id,\n refName: query.refName,\n start: segment.start,\n end: segment.end,\n name: segment.id,\n type: 'segment',\n rank: segment.rank,\n stableName: segment.refName,\n ...(samples && { samples, sampleCount: samples.length }),\n }),\n )\n },\n }),\n )\n }\n\n // The graph around a region for GraphGenomeView: GFA text (rgfaCut), or a\n // walk-indexed graph's tables (WalkIndex.subgraph). `tier: 'coarse'` reads\n // the `coarse` pair instead, the same way.\n async getSubgraph(\n region: Region,\n opts: SubgraphAdapterOptions = {},\n ): Promise<string | WalkCut> {\n const { hops, signal, tier } = opts\n const index = this.index(tier)\n const tabixRefName = await index.refNames.resolve(region, { signal })\n if (tabixRefName === undefined) {\n throw new Error(unmappedRegionMessage(region))\n }\n if (index.walks) {\n return index.walks.subgraph(tabixRefName, region, opts)\n }\n return rgfaCut(\n index,\n { refName: tabixRefName, start: region.start, end: region.end },\n { hops, signal },\n )\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["RgfaTabixAdapter", "BaseFeatureDataAdapter", "under", "segments", "openTabixSlot", "links", "walksLocation", "walks", "locationIsSet", "WalkIndex", "PanSNRefNames", "opts", "tier", "regions", "refNames", "refName", "start", "end", "query", "ObservableCreate", "observer", "tabixRefName", "emit", "feature", "nodes", "updateStatus", "node", "id", "SimpleFeature", "getLines", "line", "segment", "anchored", "parseSegmentRow", "samples", "segmentSamples", "region", "hops", "signal", "index", "unmappedRegionMessage", "rgfaCut"]
|
|
7
|
-
}
|