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.
Files changed (106) hide show
  1. package/dist/chunks/{GbzBaseSyntenyAdapter-3MELVQRM.js → GbzBaseSyntenyAdapter-FZWSXPAE.js} +2 -2
  2. package/dist/chunks/{GbzBaseSyntenyAdapter-3MELVQRM.js.map → GbzBaseSyntenyAdapter-FZWSXPAE.js.map} +2 -2
  3. package/dist/chunks/{GraphGenomeView-6EDKLBOJ.js → GraphGenomeView-52OPGZER.js} +2 -2
  4. package/dist/chunks/{GraphTrackSettingsDialog-KYEZWI4Z.js → GraphTrackSettingsDialog-JHIP2F5N.js} +2 -2
  5. package/dist/chunks/{HighlightColorDialog-IMLYI64R.js → HighlightColorDialog-S2WPOSDF.js} +2 -2
  6. package/dist/chunks/{LinearGraphDisplay-CRIZLJDE.js → LinearGraphDisplay-EF2GE7X7.js} +2 -2
  7. package/dist/chunks/{MinigraphBubbleAdapter-2HPTGZ5N.js → MinigraphBubbleAdapter-XC75BCG3.js} +2 -2
  8. package/dist/chunks/{RgfaTabixAdapter-GNH3QCPZ.js → RgfaTabixAdapter-56X2U424.js} +2 -2
  9. package/dist/chunks/{WalkTabixSyntenyAdapter-CUPHY36S.js → WalkTabixSyntenyAdapter-ECW557IS.js} +2 -2
  10. package/dist/chunks/{WalkTabixSyntenyAdapter-CUPHY36S.js.map → WalkTabixSyntenyAdapter-ECW557IS.js.map} +3 -3
  11. package/dist/chunks/chunk-2BTNRN57.js +2 -0
  12. package/dist/chunks/chunk-2BTNRN57.js.map +7 -0
  13. package/dist/chunks/{chunk-MNL7IPTH.js → chunk-2Q4D5RHL.js} +2 -2
  14. package/dist/chunks/{chunk-MNL7IPTH.js.map → chunk-2Q4D5RHL.js.map} +1 -1
  15. package/dist/chunks/{chunk-ULLEUORS.js → chunk-4AAW5P6N.js} +2 -2
  16. package/dist/chunks/{chunk-ULLEUORS.js.map → chunk-4AAW5P6N.js.map} +1 -1
  17. package/dist/chunks/{chunk-NGR473EL.js → chunk-4XM5OBD5.js} +1 -1
  18. package/dist/chunks/{chunk-NGR473EL.js.map → chunk-4XM5OBD5.js.map} +1 -1
  19. package/dist/chunks/chunk-ADZBAET3.js +3 -0
  20. package/dist/chunks/chunk-ADZBAET3.js.map +7 -0
  21. package/dist/chunks/{chunk-B5SVBPXN.js → chunk-D5PRXUSS.js} +2 -2
  22. package/dist/chunks/{chunk-B5SVBPXN.js.map → chunk-D5PRXUSS.js.map} +1 -1
  23. package/dist/chunks/{chunk-PUFH6DMJ.js → chunk-JXOLZ5IZ.js} +2 -2
  24. package/dist/chunks/{chunk-PUFH6DMJ.js.map → chunk-JXOLZ5IZ.js.map} +1 -1
  25. package/dist/chunks/chunk-KPP5H27W.js +2 -0
  26. package/dist/chunks/chunk-KPP5H27W.js.map +7 -0
  27. package/dist/chunks/{chunk-Q55PHRHV.js → chunk-LTN6XZR5.js} +1 -1
  28. package/dist/chunks/{chunk-Q55PHRHV.js.map → chunk-LTN6XZR5.js.map} +1 -1
  29. package/dist/chunks/{chunk-QQCTNB2Z.js → chunk-MLVBPMBU.js} +2 -2
  30. package/dist/chunks/{chunk-QQCTNB2Z.js.map → chunk-MLVBPMBU.js.map} +1 -1
  31. package/dist/chunks/chunk-OUHKJSKZ.js +3 -0
  32. package/dist/chunks/chunk-OUHKJSKZ.js.map +7 -0
  33. package/dist/chunks/{chunk-HOSCIP4U.js → chunk-PLTIGGGS.js} +2 -2
  34. package/dist/chunks/{chunk-HOSCIP4U.js.map → chunk-PLTIGGGS.js.map} +2 -2
  35. package/dist/chunks/chunk-QASFRHPG.js +2 -0
  36. package/dist/chunks/{chunk-GN4HSVLB.js → chunk-SI64QXFH.js} +2 -2
  37. package/dist/chunks/{chunk-GN4HSVLB.js.map → chunk-SI64QXFH.js.map} +2 -2
  38. package/dist/chunks/{chunk-4EKOQHUX.js → chunk-UW7INEQF.js} +1 -1
  39. package/dist/chunks/{chunk-4EKOQHUX.js.map → chunk-UW7INEQF.js.map} +1 -1
  40. package/dist/chunks/{chunk-TQKSF3NI.js → chunk-VIVPTP5Z.js} +1 -1
  41. package/dist/chunks/{chunk-TQKSF3NI.js.map → chunk-VIVPTP5Z.js.map} +1 -1
  42. package/dist/chunks/{chunk-6DRBCLWY.js → chunk-XLSVRKFJ.js} +2 -2
  43. package/dist/chunks/chunk-XLSVRKFJ.js.map +7 -0
  44. package/dist/chunks/{chunk-CJMBJJLO.js → chunk-XT4U7I3D.js} +3 -3
  45. package/dist/chunks/{chunk-CJMBJJLO.js.map → chunk-XT4U7I3D.js.map} +4 -4
  46. package/dist/chunks/chunk-YBZA7LPQ.js +2 -0
  47. package/dist/chunks/chunk-YBZA7LPQ.js.map +7 -0
  48. package/dist/chunks/{chunk-IFBKAC2H.js → chunk-Z4YNEFPC.js} +2 -2
  49. package/dist/chunks/renderSvg-IXWB7R54.js +2 -0
  50. package/dist/chunks/renderSvg-IXWB7R54.js.map +7 -0
  51. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +4 -4
  52. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
  53. package/package.json +3 -3
  54. package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.test.ts +10 -4
  55. package/src/GbzBaseSyntenyAdapter/companion.test.ts +4 -2
  56. package/src/GetSubgraph.ts +2 -0
  57. package/src/GraphGenomeView/components/ElTree.tsx +1 -1
  58. package/src/GraphGenomeView/components/FacetPanels.tsx +4 -1
  59. package/src/GraphGenomeView/components/PaneContextMenu.tsx +14 -0
  60. package/src/GraphGenomeView/components/PaneSvg.tsx +2 -1
  61. package/src/GraphGenomeView/legendSections.ts +3 -3
  62. package/src/GraphGenomeView/model.test.ts +19 -21
  63. package/src/GraphGenomeView/model.ts +3 -0
  64. package/src/GraphGenomeView/pane/fitViews.ts +2 -2
  65. package/src/GraphGenomeView/pane/paneBase.ts +2 -0
  66. package/src/GraphGenomeView/pane/renderingActions.ts +9 -0
  67. package/src/GraphGenomeView/repeats/walkCalls.test.ts +2 -2
  68. package/src/GraphGenomeView/subgraphLoad.test.ts +18 -2
  69. package/src/GraphGenomeView/tubeMapPanels.test.ts +4 -2
  70. package/src/GraphGenomeView/tubeMapPanels.ts +2 -2
  71. package/src/RgfaTabixAdapter/configSchema.ts +1 -1
  72. package/src/RgfaTabixAdapter/test_data/walks4_follow.GRCh38.walks.bed.gz +0 -0
  73. package/src/RgfaTabixAdapter/test_data/walks4_header.GRCh38.walks.bed.gz +0 -0
  74. package/src/RgfaTabixAdapter/test_data/walks4_inversion.GRCh38.walks.bed.gz +0 -0
  75. package/src/RgfaTabixAdapter/test_data/walks4_lanepairs.GRCh38.walks.bed.gz +0 -0
  76. package/src/RgfaTabixAdapter/test_data/walks4_maxnode.GRCh38.walks.bed.gz +0 -0
  77. package/src/RgfaTabixAdapter/test_data/walks4_rejoin.GRCh38.walks.bed.gz +0 -0
  78. package/src/RgfaTabixAdapter/test_data/walks4_twopart.GRCh38.walks.bed.gz +0 -0
  79. package/src/RgfaTabixAdapter/walkFiles.test.ts +15 -3
  80. package/src/RgfaTabixAdapter/walkReader.ts +4 -1
  81. package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.ts +7 -2
  82. package/src/WalkTabixSyntenyAdapter/configSchema.ts +1 -1
  83. package/src/WalkTabixSyntenyAdapter/gbzParity.test.ts +2 -3
  84. package/src/version.ts +1 -1
  85. package/dist/chunks/chunk-6DRBCLWY.js.map +0 -7
  86. package/dist/chunks/chunk-F4T7JI7S.js +0 -2
  87. package/dist/chunks/chunk-F4T7JI7S.js.map +0 -7
  88. package/dist/chunks/chunk-HTSG4T32.js +0 -3
  89. package/dist/chunks/chunk-HTSG4T32.js.map +0 -7
  90. package/dist/chunks/chunk-N5VWQHTK.js +0 -2
  91. package/dist/chunks/chunk-N5VWQHTK.js.map +0 -7
  92. package/dist/chunks/chunk-ORHUPW3G.js +0 -3
  93. package/dist/chunks/chunk-ORHUPW3G.js.map +0 -7
  94. package/dist/chunks/chunk-R25GOTED.js +0 -2
  95. package/dist/chunks/chunk-R25GOTED.js.map +0 -7
  96. package/dist/chunks/chunk-VW4PPXH6.js +0 -2
  97. package/dist/chunks/renderSvg-HEEQJD5B.js +0 -2
  98. package/dist/chunks/renderSvg-HEEQJD5B.js.map +0 -7
  99. /package/dist/chunks/{GraphGenomeView-6EDKLBOJ.js.map → GraphGenomeView-52OPGZER.js.map} +0 -0
  100. /package/dist/chunks/{GraphTrackSettingsDialog-KYEZWI4Z.js.map → GraphTrackSettingsDialog-JHIP2F5N.js.map} +0 -0
  101. /package/dist/chunks/{HighlightColorDialog-IMLYI64R.js.map → HighlightColorDialog-S2WPOSDF.js.map} +0 -0
  102. /package/dist/chunks/{LinearGraphDisplay-CRIZLJDE.js.map → LinearGraphDisplay-EF2GE7X7.js.map} +0 -0
  103. /package/dist/chunks/{MinigraphBubbleAdapter-2HPTGZ5N.js.map → MinigraphBubbleAdapter-XC75BCG3.js.map} +0 -0
  104. /package/dist/chunks/{RgfaTabixAdapter-GNH3QCPZ.js.map → RgfaTabixAdapter-56X2U424.js.map} +0 -0
  105. /package/dist/chunks/{chunk-VW4PPXH6.js.map → chunk-QASFRHPG.js.map} +0 -0
  106. /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.2",
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": "^3.1.0",
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.6.1",
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
- Math.max(0, ...[...cigar.matchAll(/(\d+)[ID]/g)].map(([, n]) => Number(n)))
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: Math.min(...intervals.map(i => i.start)),
543
- end: Math.max(...intervals.map(i => i.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
- Math.max(...againstReference.map(f => largestGap(cigarOf(f)))),
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: (...args: Parameters<typeof io.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(...args)
18
+ ? io.openLocation(location, pluginManager)
17
19
  : { stat: () => Promise.reject(error) }
18
20
  },
19
21
  }
@@ -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, ...node.children.map(toReact))
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: Math.floor(Math.max(...place.cells) / columns) + 2,
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 = Math.max(...rows.map(r => r.width)) + pad * 2
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: Math.max(...xs) - Math.min(...xs),
1579
- h: Math.max(...ys) - Math.min(...ys),
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(Math.min(...drawnX)).toBeLessThan(FAR_REGION.start)
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(Math.min(...drawnX))
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(Math.max(...xs) - Math.min(...xs)).toBeCloseTo(
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[]) => Math.max(...v) - Math.min(...v)
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(Math.min(...rowYs) - model.contigThickness / 2).toBeGreaterThan(0)
2865
- expect(Math.max(...rowYs) + model.contigThickness / 2).toBeLessThan(
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 = Math.min(...points.map(p => p.y))
2881
- const bottom = Math.max(...points.map(p => p.y))
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 = Math.max(
2921
- ...model.drawnRowLabels.map(r => rowLabelBox(r.label, 0).x1),
2918
+ const labelsEnd = maxOf(
2919
+ model.drawnRowLabels.map(r => rowLabelBox(r.label, 0).x1),
2922
2920
  )
2923
2921
  expect(labelsEnd).toBeGreaterThan(40)
2924
- expect(Math.min(...render(model).map(p => p.x))).toBeGreaterThan(labelsEnd)
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 Math.max(
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(Math.max(...near.map(apart))).toBeLessThan(0.08)
111
- expect(Math.min(...far.map(apart))).toBeGreaterThan(0.14)
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
- model.setHoveredTube(model.tubeAt(...onScreen(reads!.rects[0]!)))
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
- model.setHoveredTube(model.tubeAt(...onScreen(alt)))
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(...ys)).toBeGreaterThanOrEqual(PANEL_GAP)
97
- expect(Math.max(...ys)).toBeLessThanOrEqual(split.extent!.maxY!)
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 = Math.min(...spans.map(s => s.start))
202
- const end = Math.max(...spans.map(s => s.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-f4.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
  * }
@@ -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 4, built under ~/work/scratch/walks-20261008/f4. Skipped where the
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/f4/chr22'
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.2/)
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: walksKept(prefixes, refName),
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-f4.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 4, built 2026-10-08 and 2026-10-20. Skipped where
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}/f4/chr22.GRCh38`
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.3.2'
1
+ export const version = '7.4.1'