jbrowse-plugin-graphgenomeviewer 7.3.2 → 7.4.1
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-3MELVQRM.js → GbzBaseSyntenyAdapter-FZWSXPAE.js} +2 -2
- package/dist/chunks/{GbzBaseSyntenyAdapter-3MELVQRM.js.map → GbzBaseSyntenyAdapter-FZWSXPAE.js.map} +2 -2
- package/dist/chunks/{GraphGenomeView-6EDKLBOJ.js → GraphGenomeView-52OPGZER.js} +2 -2
- package/dist/chunks/{GraphTrackSettingsDialog-KYEZWI4Z.js → GraphTrackSettingsDialog-JHIP2F5N.js} +2 -2
- package/dist/chunks/{HighlightColorDialog-IMLYI64R.js → HighlightColorDialog-S2WPOSDF.js} +2 -2
- package/dist/chunks/{LinearGraphDisplay-CRIZLJDE.js → LinearGraphDisplay-EF2GE7X7.js} +2 -2
- package/dist/chunks/{MinigraphBubbleAdapter-2HPTGZ5N.js → MinigraphBubbleAdapter-XC75BCG3.js} +2 -2
- package/dist/chunks/{RgfaTabixAdapter-GNH3QCPZ.js → RgfaTabixAdapter-56X2U424.js} +2 -2
- package/dist/chunks/{WalkTabixSyntenyAdapter-CUPHY36S.js → WalkTabixSyntenyAdapter-ECW557IS.js} +2 -2
- package/dist/chunks/{WalkTabixSyntenyAdapter-CUPHY36S.js.map → WalkTabixSyntenyAdapter-ECW557IS.js.map} +3 -3
- package/dist/chunks/chunk-2BTNRN57.js +2 -0
- package/dist/chunks/chunk-2BTNRN57.js.map +7 -0
- package/dist/chunks/{chunk-MNL7IPTH.js → chunk-2Q4D5RHL.js} +2 -2
- package/dist/chunks/{chunk-MNL7IPTH.js.map → chunk-2Q4D5RHL.js.map} +1 -1
- package/dist/chunks/{chunk-ULLEUORS.js → chunk-4AAW5P6N.js} +2 -2
- package/dist/chunks/{chunk-ULLEUORS.js.map → chunk-4AAW5P6N.js.map} +1 -1
- package/dist/chunks/{chunk-NGR473EL.js → chunk-4XM5OBD5.js} +1 -1
- package/dist/chunks/{chunk-NGR473EL.js.map → chunk-4XM5OBD5.js.map} +1 -1
- package/dist/chunks/chunk-ADZBAET3.js +3 -0
- package/dist/chunks/chunk-ADZBAET3.js.map +7 -0
- package/dist/chunks/{chunk-B5SVBPXN.js → chunk-D5PRXUSS.js} +2 -2
- package/dist/chunks/{chunk-B5SVBPXN.js.map → chunk-D5PRXUSS.js.map} +1 -1
- package/dist/chunks/{chunk-PUFH6DMJ.js → chunk-JXOLZ5IZ.js} +2 -2
- package/dist/chunks/{chunk-PUFH6DMJ.js.map → chunk-JXOLZ5IZ.js.map} +1 -1
- package/dist/chunks/chunk-KPP5H27W.js +2 -0
- package/dist/chunks/chunk-KPP5H27W.js.map +7 -0
- package/dist/chunks/{chunk-Q55PHRHV.js → chunk-LTN6XZR5.js} +1 -1
- package/dist/chunks/{chunk-Q55PHRHV.js.map → chunk-LTN6XZR5.js.map} +1 -1
- package/dist/chunks/{chunk-QQCTNB2Z.js → chunk-MLVBPMBU.js} +2 -2
- package/dist/chunks/{chunk-QQCTNB2Z.js.map → chunk-MLVBPMBU.js.map} +1 -1
- package/dist/chunks/chunk-OUHKJSKZ.js +3 -0
- package/dist/chunks/chunk-OUHKJSKZ.js.map +7 -0
- package/dist/chunks/{chunk-HOSCIP4U.js → chunk-PLTIGGGS.js} +2 -2
- package/dist/chunks/{chunk-HOSCIP4U.js.map → chunk-PLTIGGGS.js.map} +2 -2
- package/dist/chunks/chunk-QASFRHPG.js +2 -0
- package/dist/chunks/{chunk-GN4HSVLB.js → chunk-SI64QXFH.js} +2 -2
- package/dist/chunks/{chunk-GN4HSVLB.js.map → chunk-SI64QXFH.js.map} +2 -2
- package/dist/chunks/{chunk-4EKOQHUX.js → chunk-UW7INEQF.js} +1 -1
- package/dist/chunks/{chunk-4EKOQHUX.js.map → chunk-UW7INEQF.js.map} +1 -1
- package/dist/chunks/{chunk-TQKSF3NI.js → chunk-VIVPTP5Z.js} +1 -1
- package/dist/chunks/{chunk-TQKSF3NI.js.map → chunk-VIVPTP5Z.js.map} +1 -1
- package/dist/chunks/{chunk-6DRBCLWY.js → chunk-XLSVRKFJ.js} +2 -2
- package/dist/chunks/chunk-XLSVRKFJ.js.map +7 -0
- package/dist/chunks/{chunk-CJMBJJLO.js → chunk-XT4U7I3D.js} +3 -3
- package/dist/chunks/{chunk-CJMBJJLO.js.map → chunk-XT4U7I3D.js.map} +4 -4
- package/dist/chunks/chunk-YBZA7LPQ.js +2 -0
- package/dist/chunks/chunk-YBZA7LPQ.js.map +7 -0
- package/dist/chunks/{chunk-IFBKAC2H.js → chunk-Z4YNEFPC.js} +2 -2
- package/dist/chunks/renderSvg-IXWB7R54.js +2 -0
- package/dist/chunks/renderSvg-IXWB7R54.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 +3 -3
- package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.test.ts +10 -4
- package/src/GbzBaseSyntenyAdapter/companion.test.ts +4 -2
- package/src/GetSubgraph.ts +2 -0
- package/src/GraphGenomeView/components/ElTree.tsx +1 -1
- package/src/GraphGenomeView/components/FacetPanels.tsx +4 -1
- package/src/GraphGenomeView/components/PaneContextMenu.tsx +14 -0
- package/src/GraphGenomeView/components/PaneSvg.tsx +2 -1
- package/src/GraphGenomeView/legendSections.ts +3 -3
- package/src/GraphGenomeView/model.test.ts +19 -21
- package/src/GraphGenomeView/model.ts +3 -0
- package/src/GraphGenomeView/pane/fitViews.ts +2 -2
- package/src/GraphGenomeView/pane/paneBase.ts +2 -0
- package/src/GraphGenomeView/pane/renderingActions.ts +9 -0
- package/src/GraphGenomeView/repeats/walkCalls.test.ts +2 -2
- package/src/GraphGenomeView/subgraphLoad.test.ts +18 -2
- package/src/GraphGenomeView/tubeMapPanels.test.ts +4 -2
- package/src/GraphGenomeView/tubeMapPanels.ts +2 -2
- package/src/RgfaTabixAdapter/configSchema.ts +1 -1
- package/src/RgfaTabixAdapter/test_data/walks4_follow.GRCh38.walks.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/walks4_header.GRCh38.walks.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/walks4_inversion.GRCh38.walks.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/walks4_lanepairs.GRCh38.walks.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/walks4_maxnode.GRCh38.walks.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/walks4_rejoin.GRCh38.walks.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/walks4_twopart.GRCh38.walks.bed.gz +0 -0
- package/src/RgfaTabixAdapter/walkFiles.test.ts +15 -3
- package/src/RgfaTabixAdapter/walkReader.ts +4 -1
- package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.ts +7 -2
- package/src/WalkTabixSyntenyAdapter/configSchema.ts +1 -1
- package/src/WalkTabixSyntenyAdapter/gbzParity.test.ts +2 -3
- package/src/version.ts +1 -1
- package/dist/chunks/chunk-6DRBCLWY.js.map +0 -7
- package/dist/chunks/chunk-F4T7JI7S.js +0 -2
- package/dist/chunks/chunk-F4T7JI7S.js.map +0 -7
- package/dist/chunks/chunk-HTSG4T32.js +0 -3
- package/dist/chunks/chunk-HTSG4T32.js.map +0 -7
- package/dist/chunks/chunk-N5VWQHTK.js +0 -2
- package/dist/chunks/chunk-N5VWQHTK.js.map +0 -7
- package/dist/chunks/chunk-ORHUPW3G.js +0 -3
- package/dist/chunks/chunk-ORHUPW3G.js.map +0 -7
- package/dist/chunks/chunk-R25GOTED.js +0 -2
- package/dist/chunks/chunk-R25GOTED.js.map +0 -7
- package/dist/chunks/chunk-VW4PPXH6.js +0 -2
- package/dist/chunks/renderSvg-HEEQJD5B.js +0 -2
- package/dist/chunks/renderSvg-HEEQJD5B.js.map +0 -7
- /package/dist/chunks/{GraphGenomeView-6EDKLBOJ.js.map → GraphGenomeView-52OPGZER.js.map} +0 -0
- /package/dist/chunks/{GraphTrackSettingsDialog-KYEZWI4Z.js.map → GraphTrackSettingsDialog-JHIP2F5N.js.map} +0 -0
- /package/dist/chunks/{HighlightColorDialog-IMLYI64R.js.map → HighlightColorDialog-S2WPOSDF.js.map} +0 -0
- /package/dist/chunks/{LinearGraphDisplay-CRIZLJDE.js.map → LinearGraphDisplay-EF2GE7X7.js.map} +0 -0
- /package/dist/chunks/{MinigraphBubbleAdapter-2HPTGZ5N.js.map → MinigraphBubbleAdapter-XC75BCG3.js.map} +0 -0
- /package/dist/chunks/{RgfaTabixAdapter-GNH3QCPZ.js.map → RgfaTabixAdapter-56X2U424.js.map} +0 -0
- /package/dist/chunks/{chunk-VW4PPXH6.js.map → chunk-QASFRHPG.js.map} +0 -0
- /package/dist/chunks/{chunk-IFBKAC2H.js.map → chunk-Z4YNEFPC.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jbrowse-plugin-graphgenomeviewer",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.4.1",
|
|
4
4
|
"description": "JBrowse 2 graph genome (pangenome) view: rGFA reference-anchored layouts, the Bandage force-directed engine, and haplotype lanes read from a gbz-base database",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"repository": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@jbrowse/core": "5.0.0-beta.11",
|
|
33
33
|
"@jbrowse/display-kit": "5.0.0-beta.11",
|
|
34
34
|
"@jbrowse/display-ui": "5.0.0-beta.11",
|
|
35
|
-
"@jbrowse/graph-walks": "^
|
|
35
|
+
"@jbrowse/graph-walks": "^4.0.0",
|
|
36
36
|
"@jbrowse/render-core": "5.0.0-beta.11",
|
|
37
37
|
"@jbrowse/synteny-core": "5.0.0-beta.11"
|
|
38
38
|
},
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"@emotion/react": "^11.14.0",
|
|
41
41
|
"@emotion/styled": "^11.14.1",
|
|
42
42
|
"@eslint/js": "^10.0.1",
|
|
43
|
-
"@jbrowse/bandage-core": "^11.
|
|
43
|
+
"@jbrowse/bandage-core": "^11.7.0",
|
|
44
44
|
"@jbrowse/cli": "^4.3.0",
|
|
45
45
|
"@jbrowse/mobx-state-tree": "^6.9.0",
|
|
46
46
|
"@jbrowse/plugin-linear-genome-view": "5.0.0-beta.11",
|
|
@@ -518,7 +518,10 @@ function cigarSpans(cigar: string) {
|
|
|
518
518
|
}
|
|
519
519
|
|
|
520
520
|
const largestGap = (cigar: string) =>
|
|
521
|
-
|
|
521
|
+
[...cigar.matchAll(/(\d+)[ID]/g)].reduce(
|
|
522
|
+
(max, [, n]) => Math.max(max, Number(n)),
|
|
523
|
+
0,
|
|
524
|
+
)
|
|
522
525
|
|
|
523
526
|
// HG01361#2 and HG02145#2 each carry a ~170 bp insertion against GRCh38 at
|
|
524
527
|
// 31,498,602, which a band composed through GRCh38 draws as nothing
|
|
@@ -539,8 +542,8 @@ const anchoredAdapter = (conf: Record<string, unknown> = {}) =>
|
|
|
539
542
|
})
|
|
540
543
|
|
|
541
544
|
const spanOf = (intervals: { start: number; end: number }[]) => ({
|
|
542
|
-
start:
|
|
543
|
-
end:
|
|
545
|
+
start: intervals.reduce((min, i) => Math.min(min, i.start), Infinity),
|
|
546
|
+
end: intervals.reduce((max, i) => Math.max(max, i.end), -Infinity),
|
|
544
547
|
})
|
|
545
548
|
|
|
546
549
|
test('the adapter type declares that a window of its anchor answers any lane pair', () => {
|
|
@@ -683,7 +686,10 @@ test('sequence both lanes carry and GRCh38 lacks aligns inside the pair record',
|
|
|
683
686
|
targetAssemblyName: 'HG01361#2',
|
|
684
687
|
})
|
|
685
688
|
expect(
|
|
686
|
-
|
|
689
|
+
againstReference.reduce(
|
|
690
|
+
(max, f) => Math.max(max, largestGap(cigarOf(f))),
|
|
691
|
+
0,
|
|
692
|
+
),
|
|
687
693
|
).toBeGreaterThan(150)
|
|
688
694
|
const records = await feats(adapter, insertionWindow, pair)
|
|
689
695
|
for (const f of records) {
|
|
@@ -10,10 +10,12 @@ vi.mock('@jbrowse/core/util/io', async importOriginal => {
|
|
|
10
10
|
const io = await importOriginal<typeof IO>()
|
|
11
11
|
return {
|
|
12
12
|
...io,
|
|
13
|
-
openLocation: (
|
|
13
|
+
openLocation: (
|
|
14
|
+
...[location, pluginManager]: Parameters<typeof io.openLocation>
|
|
15
|
+
) => {
|
|
14
16
|
const error = statError.current
|
|
15
17
|
return error === undefined
|
|
16
|
-
? io.openLocation(
|
|
18
|
+
? io.openLocation(location, pluginManager)
|
|
17
19
|
: { stat: () => Promise.reject(error) }
|
|
18
20
|
},
|
|
19
21
|
}
|
package/src/GetSubgraph.ts
CHANGED
|
@@ -31,6 +31,8 @@ export interface SubgraphCutOptions {
|
|
|
31
31
|
// a cut for walk rows: its runs come with the tables, and the
|
|
32
32
|
// step budget, which guards a Graph of every step, does not apply
|
|
33
33
|
walkRows?: boolean
|
|
34
|
+
// abridged walks, `sample#haplotype#contig`, a walk-file cut reads in full
|
|
35
|
+
expand?: string[]
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
// What an adapter's cut is handed: the payload, plus the call's own signal so
|
|
@@ -24,7 +24,7 @@ function toReact(node: El | string, key: number): ReactNode {
|
|
|
24
24
|
props[propName(name)] = value
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
-
return createElement(node.tag, props,
|
|
27
|
+
return createElement(node.tag, props, node.children.map(toReact))
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
// A useId fit for an SVG id and for url(#…)
|
|
@@ -198,7 +198,10 @@ const FacetPanels = observer(function FacetPanels({
|
|
|
198
198
|
<div
|
|
199
199
|
style={{
|
|
200
200
|
...keyStyle,
|
|
201
|
-
gridRow:
|
|
201
|
+
gridRow:
|
|
202
|
+
Math.floor(
|
|
203
|
+
place.cells.reduce((a, b) => Math.max(a, b), -Infinity) / columns,
|
|
204
|
+
) + 2,
|
|
202
205
|
}}
|
|
203
206
|
data-testid="graph-facet-key"
|
|
204
207
|
>
|
|
@@ -46,6 +46,10 @@ function nodeMenuItems(model: GraphPaneModel, nodeId: string): MenuItem[] {
|
|
|
46
46
|
]
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function formatSteps(steps: number) {
|
|
50
|
+
return steps >= 1000 ? `${Math.round(steps / 1000)} k` : `${steps}`
|
|
51
|
+
}
|
|
52
|
+
|
|
49
53
|
// A walk row's bar stands for a span of its haplotype's own contig, so the
|
|
50
54
|
// question a right-click asks is answered by a linear view on that assembly,
|
|
51
55
|
// with its genes
|
|
@@ -80,6 +84,16 @@ function walkRowMenuItems(
|
|
|
80
84
|
: 'This walk states no contig coordinates',
|
|
81
85
|
onClick: () => {},
|
|
82
86
|
},
|
|
87
|
+
...(walk?.abridgedSteps
|
|
88
|
+
? [
|
|
89
|
+
{
|
|
90
|
+
label: `Load the ${formatSteps(walk.abridgedSteps)} abridged steps`,
|
|
91
|
+
onClick: () => {
|
|
92
|
+
model.expandWalk(walk.name)
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
]
|
|
96
|
+
: []),
|
|
83
97
|
...(walk && model.liftsWalks && model.modeDrawsNodes
|
|
84
98
|
? [
|
|
85
99
|
{
|
|
@@ -143,7 +143,8 @@ function Keys({
|
|
|
143
143
|
}
|
|
144
144
|
const pad = 6
|
|
145
145
|
const rows = sections.flat()
|
|
146
|
-
const boxWidth =
|
|
146
|
+
const boxWidth =
|
|
147
|
+
rows.reduce((max, r) => Math.max(max, r.width), -Infinity) + pad * 2
|
|
147
148
|
const boxHeight =
|
|
148
149
|
rows.reduce((sum, r) => sum + r.height, 0) + sections.length * pad
|
|
149
150
|
const right = model.paneWidth - LEGEND_INSET_PX
|
|
@@ -301,10 +301,10 @@ export function tubeMapRows(model: GraphPaneModel): LegendRow[] {
|
|
|
301
301
|
if (values) {
|
|
302
302
|
rows.push(
|
|
303
303
|
tubeRow('value-reference', flat(PANEL_REFERENCE_TUBE), 'reference'),
|
|
304
|
-
...values.map(({ value, color }) =>
|
|
305
|
-
tubeRow(`value-${value}`, flat(color), value),
|
|
306
|
-
),
|
|
307
304
|
)
|
|
305
|
+
for (const { value, color } of values) {
|
|
306
|
+
rows.push(tubeRow(`value-${value}`, flat(color), value))
|
|
307
|
+
}
|
|
308
308
|
}
|
|
309
309
|
if (roles) {
|
|
310
310
|
rows.push(
|
|
@@ -50,6 +50,9 @@ import type { LayoutModeValue } from '@jbrowse/bandage-core/layoutModes'
|
|
|
50
50
|
import type { Renderer } from '@jbrowse/bandage-core/renderer/types'
|
|
51
51
|
import type { Graph } from '@jbrowse/bandage-core/types'
|
|
52
52
|
|
|
53
|
+
const minOf = (v: number[]) => v.reduce((a, b) => Math.min(a, b))
|
|
54
|
+
const maxOf = (v: number[]) => v.reduce((a, b) => Math.max(a, b))
|
|
55
|
+
|
|
53
56
|
const mockRpcCall = vi.fn()
|
|
54
57
|
// The canonical assembly a name or alias resolves to, which is what
|
|
55
58
|
// assemblyManager.assemblyNameMap does: it is keyed by [name, ...aliases].
|
|
@@ -1575,8 +1578,8 @@ describe('canvas height follows the drawing', () => {
|
|
|
1575
1578
|
const ys = points.map(p => p.y)
|
|
1576
1579
|
const xs = points.map(p => p.x)
|
|
1577
1580
|
return {
|
|
1578
|
-
w:
|
|
1579
|
-
h:
|
|
1581
|
+
w: maxOf(xs) - minOf(xs),
|
|
1582
|
+
h: maxOf(ys) - minOf(ys),
|
|
1580
1583
|
}
|
|
1581
1584
|
}
|
|
1582
1585
|
|
|
@@ -2498,8 +2501,7 @@ describe('bubbleSpread', () => {
|
|
|
2498
2501
|
})
|
|
2499
2502
|
|
|
2500
2503
|
test('compressing shrinks the drawn length range', () => {
|
|
2501
|
-
const range = (lengths: number[]) =>
|
|
2502
|
-
Math.max(...lengths) / Math.min(...lengths)
|
|
2504
|
+
const range = (lengths: number[]) => maxOf(lengths) / minOf(lengths)
|
|
2503
2505
|
// the rope: on the real cut the longest node draws tens of times the
|
|
2504
2506
|
// shortest, so zoom-to-fit frames the long one and the alleles vanish
|
|
2505
2507
|
expect(range(lengthsUnder('auto'))).toBeGreaterThan(20)
|
|
@@ -2608,7 +2610,7 @@ describe('layoutBounds on a reference axis', () => {
|
|
|
2608
2610
|
const drawnX = Object.values(model.nodePositions!)
|
|
2609
2611
|
.flat()
|
|
2610
2612
|
.map(s => s.x)
|
|
2611
|
-
expect(
|
|
2613
|
+
expect(minOf(drawnX)).toBeLessThan(FAR_REGION.start)
|
|
2612
2614
|
|
|
2613
2615
|
expect(model.layoutBounds!.minX).toBe(FAR_REGION.start)
|
|
2614
2616
|
expect(model.layoutBounds!.w).toBe(FAR_REGION.end - FAR_REGION.start)
|
|
@@ -2639,9 +2641,7 @@ describe('layoutBounds on a reference axis', () => {
|
|
|
2639
2641
|
const drawnY = Object.values(model.nodePositions!)
|
|
2640
2642
|
.flat()
|
|
2641
2643
|
.map(s => s.y)
|
|
2642
|
-
expect(model.layoutBounds!.h).toBe(
|
|
2643
|
-
Math.max(...drawnY) - Math.min(...drawnY),
|
|
2644
|
-
)
|
|
2644
|
+
expect(model.layoutBounds!.h).toBe(maxOf(drawnY) - minOf(drawnY))
|
|
2645
2645
|
})
|
|
2646
2646
|
|
|
2647
2647
|
test('a force layout keeps the extent it measured', async () => {
|
|
@@ -2659,10 +2659,8 @@ describe('layoutBounds on a reference axis', () => {
|
|
|
2659
2659
|
const drawnX = Object.values(model.nodePositions!)
|
|
2660
2660
|
.flat()
|
|
2661
2661
|
.map(s => s.x)
|
|
2662
|
-
expect(model.layoutBounds!.minX).toBe(
|
|
2663
|
-
expect(model.layoutBounds!.w).toBe(
|
|
2664
|
-
Math.max(...drawnX) - Math.min(...drawnX),
|
|
2665
|
-
)
|
|
2662
|
+
expect(model.layoutBounds!.minX).toBe(minOf(drawnX))
|
|
2663
|
+
expect(model.layoutBounds!.w).toBe(maxOf(drawnX) - minOf(drawnX))
|
|
2666
2664
|
})
|
|
2667
2665
|
})
|
|
2668
2666
|
|
|
@@ -2749,7 +2747,7 @@ describe('a row layout draws y in pixels', () => {
|
|
|
2749
2747
|
const xs = Object.values(model.nodePositions!)
|
|
2750
2748
|
.flat()
|
|
2751
2749
|
.map(p => p.x * model.scaleX + model.translateX)
|
|
2752
|
-
expect(
|
|
2750
|
+
expect(maxOf(xs) - minOf(xs)).toBeCloseTo(
|
|
2753
2751
|
model.width - model.fitPadLeft - 40,
|
|
2754
2752
|
5,
|
|
2755
2753
|
)
|
|
@@ -2847,7 +2845,7 @@ describe('what the row axis draws, in pixels', () => {
|
|
|
2847
2845
|
return model
|
|
2848
2846
|
}
|
|
2849
2847
|
|
|
2850
|
-
const spread = (v: number[]) =>
|
|
2848
|
+
const spread = (v: number[]) => maxOf(v) - minOf(v)
|
|
2851
2849
|
|
|
2852
2850
|
test('the rows are one pitch apart and the pane holds them', async () => {
|
|
2853
2851
|
const model = await fitted()
|
|
@@ -2861,8 +2859,8 @@ describe('what the row axis draws, in pixels', () => {
|
|
|
2861
2859
|
|
|
2862
2860
|
// three row gaps, and each tube half a thickness either side of its row
|
|
2863
2861
|
expect(spread(rowYs)).toBeCloseTo(3 * ROW_HEIGHT_PX, 5)
|
|
2864
|
-
expect(
|
|
2865
|
-
expect(
|
|
2862
|
+
expect(minOf(rowYs) - model.contigThickness / 2).toBeGreaterThan(0)
|
|
2863
|
+
expect(maxOf(rowYs) + model.contigThickness / 2).toBeLessThan(
|
|
2866
2864
|
model.canvasHeight,
|
|
2867
2865
|
)
|
|
2868
2866
|
})
|
|
@@ -2877,8 +2875,8 @@ describe('what the row axis draws, in pixels', () => {
|
|
|
2877
2875
|
expect(model.deletions).toHaveLength(1)
|
|
2878
2876
|
const points = render(model)
|
|
2879
2877
|
|
|
2880
|
-
const top =
|
|
2881
|
-
const bottom =
|
|
2878
|
+
const top = minOf(points.map(p => p.y))
|
|
2879
|
+
const bottom = maxOf(points.map(p => p.y))
|
|
2882
2880
|
// it does bow past the rows — a flat arc would mean the bow was dropped,
|
|
2883
2881
|
// not converted; the rows themselves span three pitches
|
|
2884
2882
|
expect(bottom - top).toBeGreaterThanOrEqual(3 * ROW_HEIGHT_PX)
|
|
@@ -2917,11 +2915,11 @@ describe('what the row axis draws, in pixels', () => {
|
|
|
2917
2915
|
|
|
2918
2916
|
test('the fit starts the drawing right of the row labels', async () => {
|
|
2919
2917
|
const model = await fitted()
|
|
2920
|
-
const labelsEnd =
|
|
2921
|
-
|
|
2918
|
+
const labelsEnd = maxOf(
|
|
2919
|
+
model.drawnRowLabels.map(r => rowLabelBox(r.label, 0).x1),
|
|
2922
2920
|
)
|
|
2923
2921
|
expect(labelsEnd).toBeGreaterThan(40)
|
|
2924
|
-
expect(
|
|
2922
|
+
expect(minOf(render(model).map(p => p.x))).toBeGreaterThan(labelsEnd)
|
|
2925
2923
|
})
|
|
2926
2924
|
|
|
2927
2925
|
// Zoom is an x-only gesture on this axis, so afterwards the rows are drawn in
|
|
@@ -67,6 +67,9 @@ export const GraphPane = withLaunchActions
|
|
|
67
67
|
haplotypes: self.cutHaplotypes,
|
|
68
68
|
...(self.chosenLayoutMode === 'walkrows' ? { walkRows: true } : {}),
|
|
69
69
|
...(self.cutsWholeWalks ? { snarls: 'overlapping' as const } : {}),
|
|
70
|
+
...(self.expandedWalks.length > 0
|
|
71
|
+
? { expand: self.expandedWalks }
|
|
72
|
+
: {}),
|
|
70
73
|
}
|
|
71
74
|
},
|
|
72
75
|
}))
|
|
@@ -155,9 +155,9 @@ export const withFitViews = withHostViews
|
|
|
155
155
|
// The row labels are pinned to the pane's left edge, so the fit starts
|
|
156
156
|
// the drawing past the widest one.
|
|
157
157
|
get fitPadLeft() {
|
|
158
|
-
return
|
|
158
|
+
return this.drawnRowLabels.reduce(
|
|
159
|
+
(pad, r) => Math.max(pad, rowLabelBox(r.label, 0).x1 + 6),
|
|
159
160
|
FIT_PADDING,
|
|
160
|
-
...this.drawnRowLabels.map(r => rowLabelBox(r.label, 0).x1 + 6),
|
|
161
161
|
)
|
|
162
162
|
},
|
|
163
163
|
// Extent of the drawing in layout units, shared by the pane height and
|
|
@@ -332,6 +332,8 @@ export const paneBase = types
|
|
|
332
332
|
// the haplotypes a GBZ or walk-indexed cut is for; unset is
|
|
333
333
|
// trackHaplotypes
|
|
334
334
|
subgraphHaplotypes: types.maybe(types.frozen<string[]>()),
|
|
335
|
+
// abridged walks the user asked to load in full
|
|
336
|
+
expandedWalks: types.optional(types.frozen<string[]>(), []),
|
|
335
337
|
}),
|
|
336
338
|
)
|
|
337
339
|
.volatile(() => ({
|
|
@@ -424,3 +424,12 @@ export const withRenderingActions = withLoadActions
|
|
|
424
424
|
: self.switchLayout('force')
|
|
425
425
|
},
|
|
426
426
|
}))
|
|
427
|
+
.actions(self => ({
|
|
428
|
+
// reads the abridged walk `name` in full with the next cut
|
|
429
|
+
expandWalk(name: string) {
|
|
430
|
+
if (!self.expandedWalks.includes(name)) {
|
|
431
|
+
self.expandedWalks = self.expandedWalks.concat(name)
|
|
432
|
+
self.retryLoad()
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
}))
|
|
@@ -107,8 +107,8 @@ test('the threshold falls in a gap the ABCA7 cohort leaves empty', () => {
|
|
|
107
107
|
const scored = rows.filter(r => r.call?.agrees !== undefined)
|
|
108
108
|
const near = scored.filter(r => r.call!.agrees)
|
|
109
109
|
const far = scored.filter(r => !r.call!.agrees)
|
|
110
|
-
expect(
|
|
111
|
-
expect(
|
|
110
|
+
expect(near.map(apart).reduce((a, b) => Math.max(a, b))).toBeLessThan(0.08)
|
|
111
|
+
expect(far.map(apart).reduce((a, b) => Math.min(a, b))).toBeGreaterThan(0.14)
|
|
112
112
|
expect(CALL_TOLERANCE).toBeGreaterThan(0.08)
|
|
113
113
|
expect(CALL_TOLERANCE).toBeLessThan(0.14)
|
|
114
114
|
expect({
|
|
@@ -305,6 +305,20 @@ describe('canceling and retrying', () => {
|
|
|
305
305
|
})
|
|
306
306
|
})
|
|
307
307
|
|
|
308
|
+
test('expanding a walk cuts again with its name', async () => {
|
|
309
|
+
const model = await cut(ON_HG38)
|
|
310
|
+
mockRpcCall.mockClear()
|
|
311
|
+
model.expandWalk('HG002#1#chr1')
|
|
312
|
+
model.expandWalk('HG002#1#chr1')
|
|
313
|
+
expect(model.expandedWalks).toEqual(['HG002#1#chr1'])
|
|
314
|
+
await vi.waitFor(() => {
|
|
315
|
+
expect(mockRpcCall).toHaveBeenCalledTimes(1)
|
|
316
|
+
})
|
|
317
|
+
expect(mockRpcCall.mock.calls[0]?.[2]).toMatchObject({
|
|
318
|
+
opts: { expand: ['HG002#1#chr1'] },
|
|
319
|
+
})
|
|
320
|
+
})
|
|
321
|
+
|
|
308
322
|
test('a newer cut aborts the one it replaces', () => {
|
|
309
323
|
pendingCuts()
|
|
310
324
|
const model = launchedView()
|
|
@@ -621,7 +635,8 @@ describe('reads', () => {
|
|
|
621
635
|
[x((r.x0 + r.x1) / 2), y((r.y0 + r.y1) / 2)] as const
|
|
622
636
|
const [haplotypes, reads] = picture.layers
|
|
623
637
|
|
|
624
|
-
|
|
638
|
+
const [readX, readY] = onScreen(reads!.rects[0]!)
|
|
639
|
+
model.setHoveredTube(model.tubeAt(readX, readY))
|
|
625
640
|
expect(model.hoveredTubeText).toEqual({
|
|
626
641
|
label: 'r1',
|
|
627
642
|
readout: 'MAPQ 60, forward strand',
|
|
@@ -636,7 +651,8 @@ describe('reads', () => {
|
|
|
636
651
|
)
|
|
637
652
|
|
|
638
653
|
const alt = haplotypes!.rects.find(r => r.id === 1)!
|
|
639
|
-
|
|
654
|
+
const [altX, altY] = onScreen(alt)
|
|
655
|
+
model.setHoveredTube(model.tubeAt(altX, altY))
|
|
640
656
|
expect(model.hoveredTubeText).toEqual({
|
|
641
657
|
label: 'alt',
|
|
642
658
|
readout: 'haplotype',
|
|
@@ -93,8 +93,10 @@ test('panels stack down the pane, a title gap above each', () => {
|
|
|
93
93
|
expect(a!.result.tubeMap.layout.tracks).toHaveLength(3)
|
|
94
94
|
expect(b!.result.tubeMap.layout.tracks).toHaveLength(2)
|
|
95
95
|
const ys = Object.values(split.nodePositions).flatMap(s => s.map(p => p.y))
|
|
96
|
-
expect(Math.min(
|
|
97
|
-
expect(Math.max(
|
|
96
|
+
expect(ys.reduce((a, b) => Math.min(a, b))).toBeGreaterThanOrEqual(PANEL_GAP)
|
|
97
|
+
expect(ys.reduce((a, b) => Math.max(a, b))).toBeLessThanOrEqual(
|
|
98
|
+
split.extent!.maxY!,
|
|
99
|
+
)
|
|
98
100
|
})
|
|
99
101
|
|
|
100
102
|
test('one group draws the whole map, unsplit', () => {
|
|
@@ -198,8 +198,8 @@ export function memberAt(
|
|
|
198
198
|
if (spans.length === 0) {
|
|
199
199
|
return undefined
|
|
200
200
|
}
|
|
201
|
-
const start =
|
|
202
|
-
const end =
|
|
201
|
+
const start = spans.reduce((min, s) => Math.min(min, s.start), Infinity)
|
|
202
|
+
const end = spans.reduce((max, s) => Math.max(max, s.end), -Infinity)
|
|
203
203
|
const share = right > left ? (sx - left) / (right - left) : 0
|
|
204
204
|
const bp = start + Math.min(1, Math.max(0, share)) * (end - start)
|
|
205
205
|
const distance = (s: { start: number; end: number }) =>
|
|
@@ -48,7 +48,7 @@ import type { Instance } from '@jbrowse/mobx-state-tree'
|
|
|
48
48
|
* ```js
|
|
49
49
|
* {
|
|
50
50
|
* type: 'RgfaTabixAdapter',
|
|
51
|
-
* walksUri: 'https://jbrowse.org/demos/hprc/hprc-v2.1-mc-grch38-
|
|
51
|
+
* walksUri: 'https://jbrowse.org/demos/hprc/hprc-v2.1-mc-grch38-f5.GRCh38',
|
|
52
52
|
* assemblyNameToPanSN: { hg38: 'GRCh38' },
|
|
53
53
|
* defaultHaplotypes: ['HG002', 'HG00733', 'HG02257', 'NA19240'],
|
|
54
54
|
* }
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from '@jbrowse/bandage-core/gfa/graphTables'
|
|
8
8
|
import { walkRowsOf } from '@jbrowse/bandage-core/layout/walkRowRuns'
|
|
9
9
|
import { readConfObject } from '@jbrowse/core/configuration'
|
|
10
|
+
import { WalkReader } from '@jbrowse/graph-walks'
|
|
10
11
|
import { firstValueFrom } from 'rxjs'
|
|
11
12
|
import { toArray } from 'rxjs/operators'
|
|
12
13
|
|
|
@@ -17,9 +18,9 @@ import type { WalkCut } from './walkCut.ts'
|
|
|
17
18
|
import type { GraphTables } from '@jbrowse/bandage-core/gfa/graphTables'
|
|
18
19
|
|
|
19
20
|
// chr22 of HPRC v2.1 at base level, one walk file per reference in walk
|
|
20
|
-
// format
|
|
21
|
+
// format 5, built under ~/work/scratch/walks-20261008/f5. Skipped where the
|
|
21
22
|
// files are absent.
|
|
22
|
-
const chr22 = '/home/cdiesh/work/scratch/walks-20261008/
|
|
23
|
+
const chr22 = '/home/cdiesh/work/scratch/walks-20261008/f5/chr22'
|
|
23
24
|
const present = ['GRCh38', 'CHM13'].every(sample =>
|
|
24
25
|
existsSync(`${chr22}.${sample}.walks.bed.gz`),
|
|
25
26
|
)
|
|
@@ -201,6 +202,17 @@ test("the haplotypes a walk file's header names", async () => {
|
|
|
201
202
|
expect(gfa).not.toMatch(/^S\t8\t/m)
|
|
202
203
|
})
|
|
203
204
|
|
|
205
|
+
test('a cut asked to expand walks hands the reader their names', async () => {
|
|
206
|
+
const cut = vi.spyOn(WalkReader.prototype, 'cut')
|
|
207
|
+
await makeAdapter(headerPrefix).getSubgraph(chr1(1000, 2600), {
|
|
208
|
+
expand: ['HG002#1#chr1'],
|
|
209
|
+
})
|
|
210
|
+
const expand = cut.mock.calls[0]?.[2]?.expand
|
|
211
|
+
expect(expand?.('HG002#1#chr1')).toBe(true)
|
|
212
|
+
expect(expand?.('HG002#2#chr1')).toBe(false)
|
|
213
|
+
cut.mockRestore()
|
|
214
|
+
})
|
|
215
|
+
|
|
204
216
|
test('a cut for some haplotypes keeps a reference walk named without a haplotype', async () => {
|
|
205
217
|
const cut = (await makeAdapter(twoPartPrefix).getSubgraph(chr1(1000, 2600), {
|
|
206
218
|
haplotypes: ['HG002#1'],
|
|
@@ -215,7 +227,7 @@ test('a cut for some haplotypes keeps a reference walk named without a haplotype
|
|
|
215
227
|
|
|
216
228
|
test('a format 1 walk file is refused, with what to rebuild it with', async () => {
|
|
217
229
|
const cut = makeAdapter(fixture('walks1_header')).getSubgraph(chr1(0, 600))
|
|
218
|
-
await expect(cut).rejects.toThrow(/rebuild it with gfa-to-tabix 1.
|
|
230
|
+
await expect(cut).rejects.toThrow(/rebuild it with gfa-to-tabix 1.4/)
|
|
219
231
|
})
|
|
220
232
|
|
|
221
233
|
test('a cut over walkByteBudget reads no row', async () => {
|
|
@@ -57,7 +57,7 @@ export class WalkIndex {
|
|
|
57
57
|
async subgraph(
|
|
58
58
|
refName: string,
|
|
59
59
|
region: { start: number; end: number },
|
|
60
|
-
{ signal, haplotypes, walkRows }: SubgraphAdapterOptions,
|
|
60
|
+
{ signal, haplotypes, walkRows, expand }: SubgraphAdapterOptions,
|
|
61
61
|
) {
|
|
62
62
|
const budgets = this.budgets()
|
|
63
63
|
const { graph, kept, fragments } = await this.reader.cut(refName, region, {
|
|
@@ -68,6 +68,9 @@ export class WalkIndex {
|
|
|
68
68
|
haplotypes?.map(lane => resolvePanSNPrefix(this.adapter, lane)),
|
|
69
69
|
refName,
|
|
70
70
|
),
|
|
71
|
+
...(expand?.length
|
|
72
|
+
? { expand: (name: string) => expand.includes(name) }
|
|
73
|
+
: {}),
|
|
71
74
|
})
|
|
72
75
|
return walkCutFor(graph.tables(kept, fragments), region, walkRows)
|
|
73
76
|
}
|
|
@@ -111,7 +111,10 @@ export default class WalkTabixSyntenyAdapter extends ComparativeAdapterBase<Walk
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
// The window's walks for the haplotypes named and the reference, with no
|
|
114
|
-
// link read; a window past a budget fails as a zoom-in notice for lanes
|
|
114
|
+
// link read; a window past a budget fails as a zoom-in notice for lanes.
|
|
115
|
+
// Named lanes come back in full where format 5 abridged them: an abridged
|
|
116
|
+
// walk is two reference steps, which drew 15 of the HPRC MHC launch's 32
|
|
117
|
+
// lanes empty.
|
|
115
118
|
private async laneWalks(
|
|
116
119
|
refName: string,
|
|
117
120
|
region: Region,
|
|
@@ -119,12 +122,14 @@ export default class WalkTabixSyntenyAdapter extends ComparativeAdapterBase<Walk
|
|
|
119
122
|
opts: LaneFeatureOptions,
|
|
120
123
|
status: string,
|
|
121
124
|
) {
|
|
125
|
+
const keep = walksKept(prefixes, refName)
|
|
122
126
|
return updateStatus(status, opts.statusCallback, () =>
|
|
123
127
|
this.walks.reader
|
|
124
128
|
.cut(refName, region, {
|
|
125
129
|
...this.walks.budgets(),
|
|
126
130
|
signal: opts.signal,
|
|
127
|
-
keep
|
|
131
|
+
keep,
|
|
132
|
+
expand: keep,
|
|
128
133
|
links: false,
|
|
129
134
|
})
|
|
130
135
|
.catch((error: unknown) => {
|
|
@@ -32,7 +32,7 @@ import type { Instance } from '@jbrowse/mobx-state-tree'
|
|
|
32
32
|
* ```js
|
|
33
33
|
* {
|
|
34
34
|
* type: 'WalkTabixSyntenyAdapter',
|
|
35
|
-
* walksUri: 'https://jbrowse.org/demos/hprc/hprc-v2.1-mc-grch38-
|
|
35
|
+
* walksUri: 'https://jbrowse.org/demos/hprc/hprc-v2.1-mc-grch38-f5.GRCh38',
|
|
36
36
|
* assemblyNames: ['hg38'],
|
|
37
37
|
* assemblyNameToPanSN: { hg38: 'GRCh38' },
|
|
38
38
|
* }
|
|
@@ -12,10 +12,9 @@ import type { Feature } from '@jbrowse/core/util'
|
|
|
12
12
|
import type { SyntenyMate } from '@jbrowse/synteny-core'
|
|
13
13
|
|
|
14
14
|
// chr22 of HPRC v2.1 as a gbz-base database and as gfa-to-tabix's GRCh38
|
|
15
|
-
// walk file in walk format
|
|
16
|
-
// the files are absent.
|
|
15
|
+
// walk file in walk format 5. Skipped where the files are absent.
|
|
17
16
|
const dir = '/home/cdiesh/work/scratch/walks-20261008'
|
|
18
|
-
const walkPrefix = `${dir}/
|
|
17
|
+
const walkPrefix = `${dir}/f5/chr22.GRCh38`
|
|
19
18
|
const present =
|
|
20
19
|
existsSync(`${walkPrefix}.walks.bed.gz`) && existsSync(`${dir}/chr22.gbz.db`)
|
|
21
20
|
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '7.
|
|
1
|
+
export const version = '7.4.1'
|