jbrowse-plugin-graphgenomeviewer 8.0.2 → 8.2.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.
Files changed (111) hide show
  1. package/dist/chunks/{AddTrackWorkflow-RJSOQZYQ.js → AddTrackWorkflow-7CXTSSMR.js} +2 -2
  2. package/dist/chunks/GbzBaseSyntenyAdapter-EJIR3HZM.js +2 -0
  3. package/dist/chunks/GbzBaseSyntenyAdapter-EJIR3HZM.js.map +7 -0
  4. package/dist/chunks/{GraphGenomeView-667ZUNDI.js → GraphGenomeView-2AKRM74B.js} +2 -2
  5. package/dist/chunks/{GraphTrackSettingsDialog-7M3Q635V.js → GraphTrackSettingsDialog-7XRZQW6T.js} +2 -2
  6. package/dist/chunks/{HighlightColorDialog-4F34Q4PY.js → HighlightColorDialog-LCBGD2ZA.js} +2 -2
  7. package/dist/chunks/LinearGraphDisplay-WWQ73BO5.js +2 -0
  8. package/dist/chunks/{MinigraphBubbleAdapter-G25V6MZL.js → MinigraphBubbleAdapter-4OOBJ44C.js} +2 -2
  9. package/dist/chunks/RgfaTabixAdapter-CEGFN2QY.js +2 -0
  10. package/dist/chunks/RgfaTabixAdapter-CEGFN2QY.js.map +7 -0
  11. package/dist/chunks/{WalkFormsAdapter-PBN7BJZW.js → WalkFormsAdapter-LTAKACZD.js} +2 -2
  12. package/dist/chunks/{WalkTabixSyntenyAdapter-27WDE5JF.js → WalkTabixSyntenyAdapter-B6V3YNW7.js} +2 -2
  13. package/dist/chunks/chunk-5FNGR33G.js +2 -0
  14. package/dist/chunks/chunk-5FNGR33G.js.map +7 -0
  15. package/dist/chunks/chunk-6VNDP3UB.js +2 -0
  16. package/dist/chunks/chunk-6VNDP3UB.js.map +7 -0
  17. package/dist/chunks/{chunk-CJAV55QB.js → chunk-BJHWGRMP.js} +1 -1
  18. package/dist/chunks/{chunk-CJAV55QB.js.map → chunk-BJHWGRMP.js.map} +1 -1
  19. package/dist/chunks/{chunk-IAXUZMOX.js → chunk-CNCYMJ5E.js} +2 -2
  20. package/dist/chunks/{chunk-IAXUZMOX.js.map → chunk-CNCYMJ5E.js.map} +2 -2
  21. package/dist/chunks/{chunk-2V75YPTT.js → chunk-CVHKXFVS.js} +2 -2
  22. package/dist/chunks/{chunk-2V75YPTT.js.map → chunk-CVHKXFVS.js.map} +2 -2
  23. package/dist/chunks/chunk-CZI7IP5S.js +2 -0
  24. package/dist/chunks/chunk-CZI7IP5S.js.map +7 -0
  25. package/dist/chunks/chunk-CZXJXNZN.js +3 -0
  26. package/dist/chunks/chunk-CZXJXNZN.js.map +7 -0
  27. package/dist/chunks/chunk-HAAABGE7.js +2 -0
  28. package/dist/chunks/{chunk-JMRW3IGK.js → chunk-IK3MQ4ZJ.js} +2 -2
  29. package/dist/chunks/chunk-IK3MQ4ZJ.js.map +7 -0
  30. package/dist/chunks/{chunk-JOYKGTDE.js → chunk-MCBYBZZJ.js} +2 -2
  31. package/dist/chunks/chunk-NOXHIOIM.js +2 -0
  32. package/dist/chunks/chunk-NOXHIOIM.js.map +7 -0
  33. package/dist/chunks/{chunk-FY7X6253.js → chunk-OGXVDZPS.js} +2 -2
  34. package/dist/chunks/{chunk-FY7X6253.js.map → chunk-OGXVDZPS.js.map} +2 -2
  35. package/dist/chunks/{chunk-3EQNVA3F.js → chunk-OLZZDBQJ.js} +2 -2
  36. package/dist/chunks/{chunk-3EQNVA3F.js.map → chunk-OLZZDBQJ.js.map} +2 -2
  37. package/dist/chunks/{chunk-I6F25EXY.js → chunk-PTOKTIQG.js} +2 -2
  38. package/dist/chunks/{chunk-O5FDMVL3.js → chunk-QWG7NASD.js} +2 -2
  39. package/dist/chunks/chunk-SL4JJJQJ.js +3 -0
  40. package/dist/chunks/chunk-SL4JJJQJ.js.map +7 -0
  41. package/dist/chunks/{chunk-XB5KC2EZ.js → chunk-T2VC3EEL.js} +1 -1
  42. package/dist/chunks/{chunk-XB5KC2EZ.js.map → chunk-T2VC3EEL.js.map} +1 -1
  43. package/dist/chunks/{chunk-2PNGGCQJ.js → chunk-UECOGSWM.js} +2 -2
  44. package/dist/chunks/{chunk-2PNGGCQJ.js.map → chunk-UECOGSWM.js.map} +1 -1
  45. package/dist/chunks/chunk-UHV6XIDG.js +2 -0
  46. package/dist/chunks/chunk-UHV6XIDG.js.map +7 -0
  47. package/dist/chunks/{chunk-5RHNR3TH.js → chunk-VKZALU57.js} +1 -1
  48. package/dist/chunks/{chunk-5RHNR3TH.js.map → chunk-VKZALU57.js.map} +1 -1
  49. package/dist/chunks/chunk-W7IHIVSE.js +2 -0
  50. package/dist/chunks/{chunk-XWCLYB5C.js.map → chunk-W7IHIVSE.js.map} +4 -4
  51. package/dist/chunks/{chunk-NLS74JSZ.js → chunk-WUOXZAGN.js} +2 -2
  52. package/dist/chunks/{chunk-NLS74JSZ.js.map → chunk-WUOXZAGN.js.map} +1 -1
  53. package/dist/chunks/{renderSvg-YNA5VK6A.js → renderSvg-E5Q7MLCH.js} +2 -2
  54. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +5 -5
  55. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
  56. package/package.json +4 -4
  57. package/src/GraphAddTrackWorkflow/buildTrackConfig.ts +12 -2
  58. package/src/GraphAddTrackWorkflow/graphManifest.ts +16 -9
  59. package/src/GraphGenomeView/components/HoverTooltip.tsx +8 -0
  60. package/src/GraphGenomeView/pane/paneBase.ts +14 -9
  61. package/src/RgfaTabixAdapter/RgfaTabixAdapter.ts +94 -41
  62. package/src/RgfaTabixAdapter/configSchema.ts +35 -4
  63. package/src/RgfaTabixAdapter/format2.test.ts +128 -0
  64. package/src/RgfaTabixAdapter/test_data/pggb_ecoli_f1.links.bed.gz +0 -0
  65. package/src/RgfaTabixAdapter/test_data/pggb_ecoli_f1.links.bed.gz.tbi +0 -0
  66. package/src/RgfaTabixAdapter/test_data/pggb_ecoli_f1.segs.bed.gz +0 -0
  67. package/src/RgfaTabixAdapter/test_data/pggb_ecoli_f1.segs.bed.gz.tbi +0 -0
  68. package/src/RgfaTabixAdapter/test_data/pggb_ecoli_f2.graph.bed.gz +0 -0
  69. package/src/RgfaTabixAdapter/test_data/pggb_ecoli_f2.graph.bed.gz.tbi +0 -0
  70. package/src/RgfaTabixAdapter/test_data/walks5_skip.GRCh38.walks.bed.gz +0 -0
  71. package/src/RgfaTabixAdapter/test_data/walks5_skip.GRCh38.walks.bed.gz.tbi +0 -0
  72. package/src/RgfaTabixAdapter/walkFiles.test.ts +21 -0
  73. package/src/RgfaTabixAdapter/walkReader.ts +20 -15
  74. package/src/graphTrackDefaults/index.ts +2 -2
  75. package/src/locationName.ts +2 -0
  76. package/src/version.ts +1 -1
  77. package/dist/chunks/GbzBaseSyntenyAdapter-EGNUOKB2.js +0 -2
  78. package/dist/chunks/GbzBaseSyntenyAdapter-EGNUOKB2.js.map +0 -7
  79. package/dist/chunks/LinearGraphDisplay-BAEMS5WG.js +0 -2
  80. package/dist/chunks/RgfaTabixAdapter-JPTUWO2Z.js +0 -2
  81. package/dist/chunks/RgfaTabixAdapter-JPTUWO2Z.js.map +0 -7
  82. package/dist/chunks/chunk-AH5UWTAV.js +0 -2
  83. package/dist/chunks/chunk-AH5UWTAV.js.map +0 -7
  84. package/dist/chunks/chunk-DG3E6BSX.js +0 -3
  85. package/dist/chunks/chunk-DG3E6BSX.js.map +0 -7
  86. package/dist/chunks/chunk-HLAQQI6G.js +0 -2
  87. package/dist/chunks/chunk-HLAQQI6G.js.map +0 -7
  88. package/dist/chunks/chunk-IKJZPPOA.js +0 -2
  89. package/dist/chunks/chunk-IKJZPPOA.js.map +0 -7
  90. package/dist/chunks/chunk-JMRW3IGK.js.map +0 -7
  91. package/dist/chunks/chunk-Q7QC2EQU.js +0 -2
  92. package/dist/chunks/chunk-RHYBJCA4.js +0 -2
  93. package/dist/chunks/chunk-RHYBJCA4.js.map +0 -7
  94. package/dist/chunks/chunk-X7YIGIYW.js +0 -2
  95. package/dist/chunks/chunk-X7YIGIYW.js.map +0 -7
  96. package/dist/chunks/chunk-XWCLYB5C.js +0 -2
  97. package/dist/chunks/chunk-ZGTG74GE.js +0 -3
  98. package/dist/chunks/chunk-ZGTG74GE.js.map +0 -7
  99. /package/dist/chunks/{AddTrackWorkflow-RJSOQZYQ.js.map → AddTrackWorkflow-7CXTSSMR.js.map} +0 -0
  100. /package/dist/chunks/{GraphGenomeView-667ZUNDI.js.map → GraphGenomeView-2AKRM74B.js.map} +0 -0
  101. /package/dist/chunks/{GraphTrackSettingsDialog-7M3Q635V.js.map → GraphTrackSettingsDialog-7XRZQW6T.js.map} +0 -0
  102. /package/dist/chunks/{HighlightColorDialog-4F34Q4PY.js.map → HighlightColorDialog-LCBGD2ZA.js.map} +0 -0
  103. /package/dist/chunks/{LinearGraphDisplay-BAEMS5WG.js.map → LinearGraphDisplay-WWQ73BO5.js.map} +0 -0
  104. /package/dist/chunks/{MinigraphBubbleAdapter-G25V6MZL.js.map → MinigraphBubbleAdapter-4OOBJ44C.js.map} +0 -0
  105. /package/dist/chunks/{WalkFormsAdapter-PBN7BJZW.js.map → WalkFormsAdapter-LTAKACZD.js.map} +0 -0
  106. /package/dist/chunks/{WalkTabixSyntenyAdapter-27WDE5JF.js.map → WalkTabixSyntenyAdapter-B6V3YNW7.js.map} +0 -0
  107. /package/dist/chunks/{chunk-Q7QC2EQU.js.map → chunk-HAAABGE7.js.map} +0 -0
  108. /package/dist/chunks/{chunk-JOYKGTDE.js.map → chunk-MCBYBZZJ.js.map} +0 -0
  109. /package/dist/chunks/{chunk-I6F25EXY.js.map → chunk-PTOKTIQG.js.map} +0 -0
  110. /package/dist/chunks/{chunk-O5FDMVL3.js.map → chunk-QWG7NASD.js.map} +0 -0
  111. /package/dist/chunks/{renderSvg-YNA5VK6A.js.map → renderSvg-E5Q7MLCH.js.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jbrowse-plugin-graphgenomeviewer",
3
- "version": "8.0.2",
3
+ "version": "8.2.0",
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": {
@@ -27,12 +27,12 @@
27
27
  "dependencies": {
28
28
  "@gmod/bgzf-filehandle": "^6.8.0",
29
29
  "@gmod/gbz-base": "^7.4.1",
30
- "@gmod/tabix": "^3.11.0",
30
+ "@gmod/tabix": "^3.13.0",
31
31
  "@jbrowse/cigar-utils": "5.0.0-beta.18",
32
32
  "@jbrowse/core": "5.0.0-beta.18",
33
33
  "@jbrowse/display-kit": "5.0.0-beta.18",
34
34
  "@jbrowse/display-ui": "5.0.0-beta.18",
35
- "@jbrowse/graph-walks": "^4.2.0",
35
+ "@jbrowse/graph-walks": "^4.4.0",
36
36
  "@jbrowse/render-core": "5.0.0-beta.18",
37
37
  "@jbrowse/synteny-core": "5.0.0-beta.18"
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": "^12.0.2",
43
+ "@jbrowse/bandage-core": "^12.1.1",
44
44
  "@jbrowse/cli": "^4.3.0",
45
45
  "@jbrowse/mobx-state-tree": "^6.10.0",
46
46
  "@jbrowse/plugin-linear-genome-view": "5.0.0-beta.18",
@@ -2,6 +2,7 @@ import { makeIndexType } from '@jbrowse/core/util/tracks'
2
2
 
3
3
  import { besideSegments, segmentsSibling } from './graphManifest'
4
4
  import {
5
+ GRAPH_SUFFIX,
5
6
  SEGMENTS_SUFFIX,
6
7
  WALKS_SUFFIX,
7
8
  locationName,
@@ -21,7 +22,7 @@ export type GraphFileChoice =
21
22
  | 'MinigraphBubbleAdapter'
22
23
 
23
24
  export const GRAPH_FILE_LABELS: Record<GraphFileChoice, string> = {
24
- RgfaTabixAdapter: 'rGFA segments (tabix BED pair)',
25
+ RgfaTabixAdapter: 'Indexed GFA (gfa-to-tabix .segs.bed.gz or .graph.bed.gz)',
25
26
  RgfaTabixWalks: 'Haplotype walks (.walks.bed.gz)',
26
27
  GbzBaseSyntenyAdapter: 'GBZ database (.gbz.db)',
27
28
  MinigraphBubbleAdapter: 'Minigraph bubbles (tabix BED)',
@@ -29,7 +30,7 @@ export const GRAPH_FILE_LABELS: Record<GraphFileChoice, string> = {
29
30
 
30
31
  export const GRAPH_FILE_FIELDS: Record<GraphFileChoice, string> = {
31
32
  RgfaTabixAdapter:
32
- 'Path to segments BED (.segs.bed.gz from gfa-to-tabix; the .links.bed.gz, its index and any .graph.json are read beside it)',
33
+ "Path to segments BED (.segs.bed.gz from gfa-to-tabix; the .links.bed.gz, its index and any .graph.json are read beside it), or format 2's .graph.bed.gz",
33
34
  RgfaTabixWalks:
34
35
  'Path to a walk file (<prefix>.<sample>.walks.bed.gz from gfa-to-tabix --walks, for the sample this assembly is)',
35
36
  GbzBaseSyntenyAdapter: 'Path to the .gbz.db written by gbz-base construct',
@@ -187,6 +188,15 @@ export function buildAdapterConfig({
187
188
  ...panSN(assembly, sample),
188
189
  }
189
190
  }
191
+ if (locationName(loc).endsWith(GRAPH_SUFFIX)) {
192
+ return {
193
+ type: 'RgfaTabixAdapter',
194
+ graphLocation: loc,
195
+ graphIndex: tabixIndex(loc, indexLoc),
196
+ ...referencePanSN(assembly, sample, manifest?.reference),
197
+ ...coarseTier(loc, indexLoc, manifest?.tier),
198
+ }
199
+ }
190
200
  const links = linksLocation(loc)
191
201
  return {
192
202
  type: 'RgfaTabixAdapter',
@@ -1,6 +1,7 @@
1
1
  import { openLocation } from '@jbrowse/core/util/io'
2
2
 
3
3
  import {
4
+ GRAPH_SUFFIX,
4
5
  SEGMENTS_SUFFIX,
5
6
  locationName,
6
7
  readsSiblings,
@@ -76,21 +77,27 @@ export function besideSegments(segs: FileLocation, fileName: string) {
76
77
  return renamed(segs, name => name.replace(/[^/\\]*$/, fileName))
77
78
  }
78
79
 
79
- // A segments BED the links BED and the manifest can be found beside
80
- export function isSegmentsLocation(loc: FileLocation) {
81
- return readsSiblings(loc) && locationName(loc).endsWith(SEGMENTS_SUFFIX)
80
+ // The suffix of a gfa-to-tabix index file: format 1's segments BED or format
81
+ // 2's graph BED
82
+ function indexSuffix(loc: FileLocation) {
83
+ const name = locationName(loc)
84
+ return [SEGMENTS_SUFFIX, GRAPH_SUFFIX].find(suffix => name.endsWith(suffix))
82
85
  }
83
86
 
84
- // `<prefix><suffix>` beside `<prefix>.segs.bed.gz`
87
+ // A segments BED, or format 2's graph BED, the rest of the index and the
88
+ // manifest can be found beside
89
+ export function isGraphIndexLocation(loc: FileLocation) {
90
+ return readsSiblings(loc) && indexSuffix(loc) !== undefined
91
+ }
92
+
93
+ // `<prefix><suffix>` beside `<prefix>.segs.bed.gz` or `<prefix>.graph.bed.gz`
85
94
  export function segmentsSibling(segs: FileLocation, suffix: string) {
86
- return renamed(
87
- segs,
88
- name => `${name.slice(0, -SEGMENTS_SUFFIX.length)}${suffix}`,
89
- )
95
+ const own = indexSuffix(segs) ?? SEGMENTS_SUFFIX
96
+ return renamed(segs, name => `${name.slice(0, -own.length)}${suffix}`)
90
97
  }
91
98
 
92
99
  export function manifestLocation(segs: FileLocation) {
93
- return isSegmentsLocation(segs)
100
+ return isGraphIndexLocation(segs)
94
101
  ? segmentsSibling(segs, '.graph.json')
95
102
  : undefined
96
103
  }
@@ -63,6 +63,14 @@ function hoverTip(
63
63
  {own.sample} {regionLabel(own)} (rank {node.stable?.rank})
64
64
  </>
65
65
  ) : null}
66
+ {/* gfa-to-tabix --max-component left its component off the
67
+ reference, so the cut holds only its ends */}
68
+ {node.moved ? (
69
+ <>
70
+ <br />
71
+ End of a component too large to draw here
72
+ </>
73
+ ) : null}
66
74
  </>
67
75
  ),
68
76
  }
@@ -7,7 +7,7 @@ import { viewportOf } from '@jbrowse/bandage-core/viewport'
7
7
  import { types } from '@jbrowse/mobx-state-tree'
8
8
  import { RenderLifecycleMixin } from '@jbrowse/render-core/RenderLifecycleMixin'
9
9
 
10
- import { SEGMENTS_SUFFIX } from '../../locationName.ts'
10
+ import { GRAPH_SUFFIX, SEGMENTS_SUFFIX } from '../../locationName.ts'
11
11
  import { lenientOptionalEnum } from '../lenientEnum'
12
12
 
13
13
  import type { MorphEntering } from './morph'
@@ -76,18 +76,23 @@ export const TUBE_MAP_FOLDS = [
76
76
  { bp: 1000, label: 'Under 1 kb' },
77
77
  ]
78
78
 
79
- // The rGFA index prefix an adapter config names, from either spelling the
80
- // config schema accepts: the `uri` shorthand or an explicit segments location.
79
+ // The rGFA index prefix an adapter config names, from any spelling the config
80
+ // schema accepts: the `uri` shorthand, or an explicit segments location or
81
+ // format 2 graph location.
81
82
  export function bubblePrefix(adapterConfig: Record<string, unknown>) {
82
83
  if (typeof adapterConfig.uri === 'string') {
83
84
  return adapterConfig.uri
84
85
  }
85
- const segments = adapterConfig.segmentsLocation as
86
- { uri?: string } | undefined
87
- const uri = segments?.uri
88
- return typeof uri === 'string' && uri.endsWith(SEGMENTS_SUFFIX)
89
- ? uri.slice(0, -SEGMENTS_SUFFIX.length)
90
- : undefined
86
+ for (const [slot, suffix] of [
87
+ ['segmentsLocation', SEGMENTS_SUFFIX],
88
+ ['graphLocation', GRAPH_SUFFIX],
89
+ ] as const) {
90
+ const uri = (adapterConfig[slot] as { uri?: string } | undefined)?.uri
91
+ if (typeof uri === 'string' && uri.endsWith(suffix)) {
92
+ return uri.slice(0, -suffix.length)
93
+ }
94
+ }
95
+ return undefined
91
96
  }
92
97
  export const HOVER_BRIGHTEN = 1.15
93
98
  export const SELECT_BRIGHTEN = 1.6
@@ -1,7 +1,13 @@
1
1
  import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter'
2
2
  import { SimpleFeature, updateStatus } from '@jbrowse/core/util'
3
3
  import { ObservableCreate } from '@jbrowse/core/util/rxjs'
4
- import { parseSegmentRow, rgfaCut, segmentSamples } from '@jbrowse/graph-walks'
4
+ import {
5
+ parseGraphHeader,
6
+ parseGraphRow,
7
+ parseSegmentRow,
8
+ rgfaCut,
9
+ segmentSamples,
10
+ } from '@jbrowse/graph-walks'
5
11
 
6
12
  import { locationIsSet } from '../locationName.ts'
7
13
  import { PanSNRefNames, openTabixSlot } from '../panSNTabix.ts'
@@ -15,10 +21,13 @@ import type { TabixIndexedFile } from '@gmod/tabix'
15
21
  import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter'
16
22
  import type { Feature } from '@jbrowse/core/util'
17
23
  import type { FileLocation, Region } from '@jbrowse/core/util/types'
24
+ import type { RgfaSegment } from '@jbrowse/graph-walks'
18
25
 
19
- interface GraphIndex {
20
- segments: TabixIndexedFile
21
- links: TabixIndexedFile
26
+ // gfa-to-tabix's format 1 pair, or format 2's one file
27
+ type GraphIndex = (
28
+ | { segments: TabixIndexedFile; links: TabixIndexedFile }
29
+ | { graph: TabixIndexedFile }
30
+ ) & {
22
31
  walks?: WalkIndex
23
32
  refNames: PanSNRefNames
24
33
  }
@@ -31,9 +40,33 @@ export default class RgfaTabixAdapter extends BaseFeatureDataAdapter<RgfaTabixAd
31
40
 
32
41
  // refNames off the SEGMENT index: it is the one that names every stable
33
42
  // sequence the graph is anchored to, where the link index only names those a
34
- // link touches. A walk-indexed graph's walk file names the same sequences,
35
- // and it has no segment file.
43
+ // link touches. Format 2's one file names them all. A walk-indexed graph's
44
+ // walk file names the same sequences, and it has no segment file.
36
45
  private openIndex(under: string[]): GraphIndex {
46
+ const walksLocation: FileLocation = this.getConf('walksLocation')
47
+ const walks =
48
+ under.length === 0 && locationIsSet(walksLocation)
49
+ ? new WalkIndex(
50
+ openTabixSlot(this, ['walksLocation'], ['walksIndex']),
51
+ this,
52
+ )
53
+ : undefined
54
+ const graphLocation: FileLocation =
55
+ under.length === 0
56
+ ? this.getConf('graphLocation')
57
+ : this.getConf(['coarse', 'graphLocation'])
58
+ if (locationIsSet(graphLocation)) {
59
+ const graph = openTabixSlot(
60
+ this,
61
+ [...under, 'graphLocation'],
62
+ [...under, 'graphIndex'],
63
+ )
64
+ return {
65
+ graph,
66
+ walks,
67
+ refNames: new PanSNRefNames(walks?.file ?? graph, this),
68
+ }
69
+ }
37
70
  const segments = openTabixSlot(
38
71
  this,
39
72
  [...under, 'segmentsLocation'],
@@ -44,14 +77,6 @@ export default class RgfaTabixAdapter extends BaseFeatureDataAdapter<RgfaTabixAd
44
77
  [...under, 'linksLocation'],
45
78
  [...under, 'linksIndex'],
46
79
  )
47
- const walksLocation: FileLocation = this.getConf('walksLocation')
48
- const walks =
49
- under.length === 0 && locationIsSet(walksLocation)
50
- ? new WalkIndex(
51
- openTabixSlot(this, ['walksLocation'], ['walksIndex']),
52
- this,
53
- )
54
- : undefined
55
80
  return {
56
81
  segments,
57
82
  links,
@@ -142,41 +167,69 @@ export default class RgfaTabixAdapter extends BaseFeatureDataAdapter<RgfaTabixAd
142
167
  }
143
168
  }
144
169
 
145
- private async segmentFeatures(
170
+ // Each segment row under the window, from the segment file or from format
171
+ // 2's node rows, with whether the index is anchored
172
+ private async segmentRows(
146
173
  tabixRefName: string,
147
174
  query: Region,
148
175
  opts: BaseOptions,
149
- emit: (feature: Feature) => void,
176
+ each: (segment: RgfaSegment, anchored: boolean) => void,
150
177
  ) {
151
- await updateStatus('Downloading segments', opts.statusCallback, () =>
152
- getLines(this.fine.segments, tabixRefName, query.start, query.end, {
178
+ const index = this.fine
179
+ if ('graph' in index) {
180
+ const header = parseGraphHeader(
181
+ await index.graph.getHeaderLines({ signal: opts.signal }),
182
+ )
183
+ return getLines(index.graph, tabixRefName, query.start, query.end, {
153
184
  signal: opts.signal,
154
185
  lineCallback: line => {
155
- const { segment, anchored } = parseSegmentRow(line)
156
- // An anchored index also returns the alleles under the region,
157
- // which lie on other stable sequences and have no place on this one
158
- if (anchored && segment.refName !== tabixRefName) {
159
- return
186
+ const row = parseGraphRow(line, header)
187
+ if (row?.kind === 'N') {
188
+ each(row.segment, header.anchored)
160
189
  }
161
- // `samples` lists the haplotypes whose paths visit the segment and
162
- // `sampleCount` counts them. A lane's color reads the count, and
163
- // a count in jexl would otherwise rely on `.length` resolving
164
- // through a member access on an array.
165
- const samples = segmentSamples(segment)
166
- emit(
167
- new SimpleFeature({
168
- uniqueId: segment.id,
169
- refName: query.refName,
170
- start: segment.start,
171
- end: segment.end,
172
- name: segment.id,
173
- type: 'segment',
174
- rank: segment.rank,
175
- stableName: segment.refName,
176
- ...(samples && { samples, sampleCount: samples.length }),
177
- }),
178
- )
179
190
  },
191
+ })
192
+ }
193
+ return getLines(index.segments, tabixRefName, query.start, query.end, {
194
+ signal: opts.signal,
195
+ lineCallback: line => {
196
+ const { segment, anchored } = parseSegmentRow(line)
197
+ each(segment, anchored)
198
+ },
199
+ })
200
+ }
201
+
202
+ private async segmentFeatures(
203
+ tabixRefName: string,
204
+ query: Region,
205
+ opts: BaseOptions,
206
+ emit: (feature: Feature) => void,
207
+ ) {
208
+ await updateStatus('Downloading segments', opts.statusCallback, () =>
209
+ this.segmentRows(tabixRefName, query, opts, (segment, anchored) => {
210
+ // An anchored index also returns the alleles under the region,
211
+ // which lie on other stable sequences and have no place on this one
212
+ if (anchored && segment.refName !== tabixRefName) {
213
+ return
214
+ }
215
+ // `samples` lists the haplotypes whose paths visit the segment and
216
+ // `sampleCount` counts them. A lane's color reads the count, and
217
+ // a count in jexl would otherwise rely on `.length` resolving
218
+ // through a member access on an array.
219
+ const samples = segmentSamples(segment)
220
+ emit(
221
+ new SimpleFeature({
222
+ uniqueId: segment.id,
223
+ refName: query.refName,
224
+ start: segment.start,
225
+ end: segment.end,
226
+ name: segment.id,
227
+ type: 'segment',
228
+ rank: segment.rank,
229
+ stableName: segment.refName,
230
+ ...(samples && { samples, sampleCount: samples.length }),
231
+ }),
232
+ )
180
233
  }),
181
234
  )
182
235
  }
@@ -1,6 +1,7 @@
1
1
  import { ConfigurationSchema } from '@jbrowse/core/configuration'
2
2
  import { WALK_BUDGETS } from '@jbrowse/graph-walks'
3
3
 
4
+ import { GRAPH_SUFFIX } from '../locationName.ts'
4
5
  import {
5
6
  fileLocationSlot,
6
7
  tabixIndexSchema,
@@ -21,7 +22,10 @@ import type { Instance } from '@jbrowse/mobx-state-tree'
21
22
  * where the adapter follows links onto other contigs.
22
23
  *
23
24
  * The `uri` shorthand takes the prefix the build script was given and resolves
24
- * `<uri>.segs.bed.gz`, `<uri>.links.bed.gz` and their `.tbi` indexes. `coarse`
25
+ * `<uri>.segs.bed.gz`, `<uri>.links.bed.gz` and their `.tbi` indexes. With
26
+ * `format: 2` it resolves gfa-to-tabix's format 2 instead, `<uri>.graph.bed.gz`
27
+ * and its index: nodes and links in one file, a plain GFA's carriers as bits,
28
+ * from `gfa-to-tabix --format 2`. `coarse`
25
29
  * takes the same shorthand for a second pair at one node per bubble, built by
26
30
  * `gfa-to-tabix fold`, plus either the zoom past which a graph view following
27
31
  * a linear view cuts it (`aboveBpPerPx`) or the size it was folded at
@@ -56,16 +60,23 @@ import type { Instance } from '@jbrowse/mobx-state-tree'
56
60
  */
57
61
 
58
62
  function prefixLocations(snap: Record<string, unknown>) {
59
- const { uri, baseUri, csi } = snap
60
- return typeof uri === 'string'
63
+ const { uri, baseUri, csi, format } = snap
64
+ if (typeof uri !== 'string') {
65
+ return snap
66
+ }
67
+ return format === 2
61
68
  ? {
69
+ ...snap,
70
+ graphLocation: { uri: `${uri}${GRAPH_SUFFIX}`, baseUri },
71
+ graphIndex: tbiBeside(`${uri}${GRAPH_SUFFIX}`, baseUri, csi),
72
+ }
73
+ : {
62
74
  ...snap,
63
75
  segmentsLocation: { uri: `${uri}.segs.bed.gz`, baseUri },
64
76
  segmentsIndex: tbiBeside(`${uri}.segs.bed.gz`, baseUri, csi),
65
77
  linksLocation: { uri: `${uri}.links.bed.gz`, baseUri },
66
78
  linksIndex: tbiBeside(`${uri}.links.bed.gz`, baseUri, csi),
67
79
  }
68
- : snap
69
80
  }
70
81
 
71
82
  function normalizeSnapshot(snap: Record<string, unknown>) {
@@ -119,6 +130,15 @@ const RgfaTabixCoarseTier = ConfigurationSchema('RgfaTabixCoarseTier', {
119
130
  'RgfaCoarseLinksIndex',
120
131
  '/path/to/graph.tier.links.bed.gz.tbi',
121
132
  ),
133
+ /**
134
+ * #slot
135
+ * format 2's one file in place of the pair; see the adapter's `graphLocation`
136
+ */
137
+ graphLocation: fileLocationSlot(''),
138
+ /**
139
+ * #slot
140
+ */
141
+ graphIndex: tabixIndexSchema('RgfaCoarseGraphIndex'),
122
142
  })
123
143
 
124
144
  const RgfaTabixAdapter = ConfigurationSchema(
@@ -150,6 +170,17 @@ const RgfaTabixAdapter = ConfigurationSchema(
150
170
  'RgfaLinksIndex',
151
171
  '/path/to/graph.links.bed.gz.tbi',
152
172
  ),
173
+ /**
174
+ * #slot
175
+ * gfa-to-tabix's format 2, `<prefix>.graph.bed.gz`: segments and links in
176
+ * one file, read in place of `segmentsLocation` and `linksLocation`.
177
+ * Unset means the graph is the pair.
178
+ */
179
+ graphLocation: fileLocationSlot(''),
180
+ /**
181
+ * #slot
182
+ */
183
+ graphIndex: tabixIndexSchema('RgfaGraphIndex'),
153
184
  /**
154
185
  * #slot
155
186
  * a `gfa-to-tabix --walks` file in walk format 5: per reference chunk,
@@ -0,0 +1,128 @@
1
+ import { readConfObject } from '@jbrowse/core/configuration'
2
+ import { firstValueFrom } from 'rxjs'
3
+ import { toArray } from 'rxjs/operators'
4
+
5
+ import Adapter from './RgfaTabixAdapter.ts'
6
+ import configSchema from './configSchema.ts'
7
+ import { buildAdapterConfig } from '../GraphAddTrackWorkflow/buildTrackConfig.ts'
8
+ import { bubblePrefix } from '../GraphGenomeView/pane/paneBase.ts'
9
+
10
+ // gfa-to-tabix 1.5's anchored index of test_data/ecoli_pggb_subgraph.gfa on
11
+ // K12, in format 1 (`pggb_ecoli_f1`, a segs/links pair) and format 2
12
+ // (`pggb_ecoli_f2.graph.bed.gz`, one file, carriers as SB:H: bits)
13
+ const DATA = require
14
+ .resolve('./test_data/pggb_ecoli_f2.graph.bed.gz')
15
+ .replace(/pggb_ecoli_f2\.graph\.bed\.gz$/, '')
16
+ const local = (path: string) => ({
17
+ localPath: path,
18
+ locationType: 'LocalPathLocation',
19
+ })
20
+
21
+ function formatOne() {
22
+ const f = `${DATA}pggb_ecoli_f1`
23
+ return new Adapter(
24
+ configSchema.create({
25
+ segmentsLocation: local(`${f}.segs.bed.gz`),
26
+ segmentsIndex: { location: local(`${f}.segs.bed.gz.tbi`) },
27
+ linksLocation: local(`${f}.links.bed.gz`),
28
+ linksIndex: { location: local(`${f}.links.bed.gz.tbi`) },
29
+ }),
30
+ )
31
+ }
32
+
33
+ function formatTwo() {
34
+ const f = `${DATA}pggb_ecoli_f2.graph.bed.gz`
35
+ return new Adapter(
36
+ configSchema.create({
37
+ graphLocation: local(f),
38
+ graphIndex: { location: local(`${f}.tbi`) },
39
+ }),
40
+ )
41
+ }
42
+
43
+ const window = (start: number) => ({
44
+ refName: 'chr',
45
+ assemblyName: 'K12',
46
+ start,
47
+ end: start + 60,
48
+ })
49
+
50
+ // A segment's carriers as a set, since format 2 names them in #samples order
51
+ const sorted = (samples: unknown) =>
52
+ Array.isArray(samples) ? [...samples].sort() : samples
53
+
54
+ test('format 2 names the same reference sequences', async () => {
55
+ expect(await formatTwo().getRefNames()).toEqual(
56
+ await formatOne().getRefNames(),
57
+ )
58
+ })
59
+
60
+ test('format 2 gives the carriage lane the same segments and carriers', async () => {
61
+ const features = (adapter: Adapter, start: number) =>
62
+ firstValueFrom(adapter.getFeatures(window(start)).pipe(toArray()))
63
+ for (let start = 1004500; start < 1004961; start += 41) {
64
+ const one = await features(formatOne(), start)
65
+ const two = await features(formatTwo(), start)
66
+ const shape = (f: (typeof one)[number]) => ({
67
+ name: f.get('name'),
68
+ start: f.get('start'),
69
+ end: f.get('end'),
70
+ rank: f.get('rank'),
71
+ samples: sorted(f.get('samples')),
72
+ sampleCount: f.get('sampleCount'),
73
+ })
74
+ expect(two.map(shape)).toEqual(one.map(shape))
75
+ expect(two.every(f => (f.get('sampleCount') as number) > 0)).toBe(true)
76
+ }
77
+ })
78
+
79
+ test('format 2 cuts the same graph', async () => {
80
+ const lines = (gfa: unknown) =>
81
+ String(gfa)
82
+ .split('\n')
83
+ .map(l => l.replace(/\tSM:Z:\S*/, ''))
84
+ for (let start = 1004500; start < 1004961; start += 41) {
85
+ const one = await formatOne().getSubgraph(window(start))
86
+ const two = await formatTwo().getSubgraph(window(start))
87
+ expect(lines(two)).toEqual(lines(one))
88
+ }
89
+ })
90
+
91
+ test('the uri shorthand with format: 2 resolves the one file', () => {
92
+ const config = configSchema.create({
93
+ uri: 'hprc',
94
+ format: 2,
95
+ baseUri: 'https://example.com/',
96
+ })
97
+ expect(readConfObject(config, 'graphLocation')).toMatchObject({
98
+ uri: 'hprc.graph.bed.gz',
99
+ baseUri: 'https://example.com/',
100
+ })
101
+ expect(readConfObject(config, ['graphIndex', 'location'])).toMatchObject({
102
+ uri: 'hprc.graph.bed.gz.tbi',
103
+ })
104
+ })
105
+
106
+ test('Open track takes a format 2 graph file', () => {
107
+ const loc = {
108
+ uri: 'https://example.com/hprc.graph.bed.gz',
109
+ locationType: 'UriLocation' as const,
110
+ }
111
+ const adapter = buildAdapterConfig({
112
+ choice: 'RgfaTabixAdapter',
113
+ loc,
114
+ indexLoc: undefined,
115
+ assembly: 'hg38',
116
+ sample: 'GRCh38',
117
+ })
118
+ expect(adapter).toMatchObject({
119
+ type: 'RgfaTabixAdapter',
120
+ graphLocation: loc,
121
+ graphIndex: {
122
+ location: { uri: 'https://example.com/hprc.graph.bed.gz.tbi' },
123
+ },
124
+ assemblyNameToPanSN: { hg38: 'GRCh38' },
125
+ })
126
+ expect(adapter).not.toHaveProperty('segmentsLocation')
127
+ expect(bubblePrefix(adapter)).toBe('https://example.com/hprc')
128
+ })
@@ -365,6 +365,27 @@ test('a walk leaving the window is measured to where it rejoins the reference',
365
365
  ])
366
366
  })
367
367
 
368
+ // gfa-to-tabix 1.5 --walks --chunk 10 of ten 10 bp reference nodes:
369
+ // W GRCh38 0 chr1 >1>2>3>4>5>6>7>8>9>10
370
+ // W HG002 1 ctg >1>2>9>10 a deletion of 20-80, whose chunks hold skip rows
371
+ const skipPrefix = fixture('walks5_skip')
372
+
373
+ test('a haplotype that jumps over the window draws as a deletion row', async () => {
374
+ const cut = (await makeAdapter(skipPrefix).getSubgraph(chr1(31, 60), {
375
+ walkRows: true,
376
+ })) as WalkCut
377
+ const rows = walkRowsOf(cut.walkRowRuns!).rows
378
+ expect(
379
+ rows.map(({ label, bp, gapBp, complete }) => ({
380
+ label,
381
+ bp,
382
+ gapBp,
383
+ complete,
384
+ })),
385
+ ).toEqual([{ label: 'HG002#1', bp: 0, gapBp: 29, complete: true }])
386
+ expect(rows[0]!.runs.map(run => run.cause)).toEqual(['deleted'])
387
+ })
388
+
368
389
  test('a walk the read cuts off is read on to the chunk its row names', async () => {
369
390
  const getLines = vi.spyOn(TabixIndexedFile.prototype, 'getLines')
370
391
  const cut = (await makeAdapter(followPrefix).getSubgraph(chr1(1000, 1500), {
@@ -55,9 +55,10 @@ export class WalkIndex {
55
55
 
56
56
  /**
57
57
  * The graph display's cut of a window: the reference walk, the walks of
58
- * `haplotypes` (every walk when unset), and the nodes and links they visit.
59
- * Walk rows draw runs, so the step budget spares them, and read a short
60
- * list of named walks in full where format 5 abridged them.
58
+ * `haplotypes` (every walk when unset), and the nodes and links they visit,
59
+ * with the haplotypes whose deletion spans the window as `skips`, which walk
60
+ * rows draw. Walk rows draw runs, so the step budget spares them, and read a
61
+ * short list of named walks in full where format 5 abridged them.
61
62
  */
62
63
  async subgraph(
63
64
  refName: string,
@@ -76,18 +77,22 @@ export class WalkIndex {
76
77
  const asked = expand?.length
77
78
  ? (name: string) => expand.includes(name)
78
79
  : undefined
79
- const { graph, kept, fragments } = await this.reader.cut(refName, region, {
80
- ...budgets,
81
- stepBudget: walkRows ? Infinity : budgets.stepBudget,
82
- signal,
83
- keep,
84
- ...(named || asked
85
- ? {
86
- expand: (name: string) => !!named?.(name) || !!asked?.(name),
87
- }
88
- : {}),
89
- })
90
- return walkCutFor(graph.tables(kept, fragments), region, walkRows)
80
+ const { graph, kept, fragments, skips } = await this.reader.cut(
81
+ refName,
82
+ region,
83
+ {
84
+ ...budgets,
85
+ stepBudget: walkRows ? Infinity : budgets.stepBudget,
86
+ signal,
87
+ keep,
88
+ ...(named || asked
89
+ ? {
90
+ expand: (name: string) => !!named?.(name) || !!asked?.(name),
91
+ }
92
+ : {}),
93
+ },
94
+ )
95
+ return walkCutFor(graph.tables(kept, fragments, skips), region, walkRows)
91
96
  }
92
97
 
93
98
  /**