jbrowse-plugin-graphgenomeviewer 1.0.0 → 3.0.3

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 (283) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +202 -24
  3. package/dist/chunks/AddTrackWorkflow-Y3OBHM33.js +2 -0
  4. package/dist/chunks/AddTrackWorkflow-Y3OBHM33.js.map +7 -0
  5. package/dist/chunks/GbzBaseSyntenyAdapter-EFIOI2SN.js +8 -0
  6. package/dist/chunks/GbzBaseSyntenyAdapter-EFIOI2SN.js.map +7 -0
  7. package/dist/chunks/GraphGenomeView-UD5WCV3Q.js +14 -0
  8. package/dist/chunks/GraphGenomeView-UD5WCV3Q.js.map +7 -0
  9. package/dist/chunks/MinigraphBubbleAdapter-B75VQK7T.js +2 -0
  10. package/dist/chunks/MinigraphBubbleAdapter-B75VQK7T.js.map +7 -0
  11. package/dist/chunks/RgfaTabixAdapter-IT7I5RR3.js +3 -0
  12. package/dist/chunks/RgfaTabixAdapter-IT7I5RR3.js.map +7 -0
  13. package/dist/chunks/bandage-layout-FIOZ7B3E.js +1660 -0
  14. package/dist/chunks/bandage-layout-FIOZ7B3E.js.map +7 -0
  15. package/dist/chunks/chunk-D7XFR2IV.js +2 -0
  16. package/dist/chunks/chunk-D7XFR2IV.js.map +7 -0
  17. package/dist/chunks/chunk-F4C3BEPV.js +2 -0
  18. package/dist/chunks/chunk-F4C3BEPV.js.map +7 -0
  19. package/dist/chunks/chunk-FCWXYIFC.js +2 -0
  20. package/dist/chunks/chunk-FCWXYIFC.js.map +7 -0
  21. package/dist/chunks/chunk-FY3BTKY4.js +2 -0
  22. package/dist/chunks/chunk-FY3BTKY4.js.map +7 -0
  23. package/dist/chunks/chunk-J4OLMLS5.js +2 -0
  24. package/dist/chunks/chunk-J4OLMLS5.js.map +7 -0
  25. package/dist/chunks/chunk-JCG7RLMB.js +2 -0
  26. package/dist/chunks/chunk-JCG7RLMB.js.map +7 -0
  27. package/dist/chunks/chunk-KWAXLUPQ.js +2 -0
  28. package/dist/chunks/chunk-KWAXLUPQ.js.map +7 -0
  29. package/dist/chunks/chunk-LGKM2PZH.js +2 -0
  30. package/dist/chunks/chunk-LGKM2PZH.js.map +7 -0
  31. package/dist/chunks/chunk-TN6FK5UD.js +2 -0
  32. package/dist/chunks/chunk-TN6FK5UD.js.map +7 -0
  33. package/dist/chunks/chunk-X3X74OGB.js +2 -0
  34. package/dist/chunks/chunk-X3X74OGB.js.map +7 -0
  35. package/dist/chunks/chunk-XEJ47DLD.js +2 -0
  36. package/dist/chunks/chunk-XEJ47DLD.js.map +7 -0
  37. package/dist/chunks/chunk-ZNGDWZ6Z.js +2 -0
  38. package/dist/chunks/chunk-ZNGDWZ6Z.js.map +7 -0
  39. package/dist/chunks/executeDiagonalize-XMPEDHVZ.js +2 -0
  40. package/dist/chunks/executeDiagonalize-XMPEDHVZ.js.map +7 -0
  41. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +3 -0
  42. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +7 -0
  43. package/package.json +94 -49
  44. package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.test.ts +427 -0
  45. package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.ts +461 -0
  46. package/src/GbzBaseSyntenyAdapter/configSchema.ts +181 -0
  47. package/src/GbzBaseSyntenyAdapter/index.ts +27 -0
  48. package/src/GbzBaseSyntenyAdapter/test_data/micb-kir3dl1.gbz.db +0 -0
  49. package/src/GbzBaseSyntenyAdapter/test_data/micb-kir3dl1.haplotype-index.db +0 -0
  50. package/src/GetSubgraph.test.ts +193 -0
  51. package/src/GetSubgraph.ts +93 -0
  52. package/src/GraphAddTrackWorkflow/AddTrackWorkflow.tsx +167 -0
  53. package/src/GraphAddTrackWorkflow/buildTrackConfig.test.ts +150 -0
  54. package/src/GraphAddTrackWorkflow/buildTrackConfig.ts +114 -0
  55. package/src/GraphAddTrackWorkflow/index.ts +20 -0
  56. package/src/GraphComputeLayout.ts +46 -0
  57. package/src/GraphGenomeView/anchoredNodes.ts +101 -0
  58. package/src/GraphGenomeView/bubbleSpreads.ts +102 -0
  59. package/src/GraphGenomeView/bubbles/bubbleHalos.test.ts +58 -0
  60. package/src/GraphGenomeView/bubbles/bubbleHalos.ts +149 -0
  61. package/src/GraphGenomeView/bubbles/bubbleRoutes.test.ts +141 -0
  62. package/src/GraphGenomeView/bubbles/bubblesFromGraph.test.ts +239 -0
  63. package/src/GraphGenomeView/bubbles/bubblesFromGraph.ts +210 -0
  64. package/src/GraphGenomeView/bubbles/classifyBubble.test.ts +172 -0
  65. package/src/GraphGenomeView/bubbles/classifyBubble.ts +138 -0
  66. package/src/GraphGenomeView/bubbles/popBubble.test.ts +73 -0
  67. package/src/GraphGenomeView/bubbles/popBubble.ts +26 -0
  68. package/src/GraphGenomeView/colorSchemes.ts +36 -0
  69. package/src/GraphGenomeView/components/BubbleHalos.tsx +162 -0
  70. package/src/GraphGenomeView/components/BubbleOverlay.tsx +181 -0
  71. package/src/GraphGenomeView/components/ColorSchemeSelect.tsx +41 -0
  72. package/src/GraphGenomeView/components/GenePins.tsx +107 -0
  73. package/src/GraphGenomeView/components/GraphCanvas.tsx +759 -0
  74. package/src/GraphGenomeView/components/GraphGenomeView.test.tsx +77 -0
  75. package/src/GraphGenomeView/components/GraphGenomeView.tsx +18 -17
  76. package/src/GraphGenomeView/components/GraphLoadStatus.tsx +84 -0
  77. package/src/GraphGenomeView/components/GraphSettingsDialog.tsx +327 -0
  78. package/src/GraphGenomeView/components/GraphStats.tsx +82 -0
  79. package/src/GraphGenomeView/components/GraphToolbar.tsx +84 -0
  80. package/src/GraphGenomeView/components/ImportForm.tsx +124 -0
  81. package/src/GraphGenomeView/components/LabelChip.tsx +65 -0
  82. package/src/GraphGenomeView/components/LayoutSelect.tsx +56 -0
  83. package/src/GraphGenomeView/components/RepeatSelect.tsx +51 -0
  84. package/src/GraphGenomeView/components/SettingsMenu.tsx +82 -0
  85. package/src/GraphGenomeView/components/SubgraphContextSelect.tsx +75 -0
  86. package/src/GraphGenomeView/components/SubgraphHaplotypesField.tsx +86 -0
  87. package/src/GraphGenomeView/components/WalkRowsOverlay.tsx +252 -0
  88. package/src/GraphGenomeView/components/WalkSelect.tsx +51 -0
  89. package/src/GraphGenomeView/components/overlayLabels.test.ts +56 -0
  90. package/src/GraphGenomeView/components/overlayLabels.ts +75 -0
  91. package/src/GraphGenomeView/deletionEdges.test.ts +198 -0
  92. package/src/GraphGenomeView/deletionEdges.ts +217 -0
  93. package/src/GraphGenomeView/genes/geneFeatures.ts +107 -0
  94. package/src/GraphGenomeView/genes/genePins.ts +130 -0
  95. package/src/GraphGenomeView/genes/genes.test.ts +153 -0
  96. package/src/GraphGenomeView/gfa/gfaConverter.test.ts +394 -0
  97. package/src/GraphGenomeView/gfa/gfaConverter.ts +235 -0
  98. package/src/GraphGenomeView/graphLabels.budget.test.ts +36 -0
  99. package/src/GraphGenomeView/graphLabels.test.ts +548 -0
  100. package/src/GraphGenomeView/graphLabels.ts +640 -0
  101. package/src/GraphGenomeView/index.ts +29 -7
  102. package/src/GraphGenomeView/laneRamp.test.ts +97 -0
  103. package/src/GraphGenomeView/laneRamp.ts +87 -0
  104. package/src/GraphGenomeView/launchKeys.test.ts +63 -0
  105. package/src/GraphGenomeView/layout/anchoredLayout.test.ts +213 -0
  106. package/src/GraphGenomeView/layout/anchoredLayout.ts +80 -0
  107. package/src/GraphGenomeView/layout/bubbleCrossing.test.ts +190 -0
  108. package/src/GraphGenomeView/layout/drawnScale.ts +199 -0
  109. package/src/GraphGenomeView/layout/mergeRuns.test.ts +100 -0
  110. package/src/GraphGenomeView/layout/mergeRuns.ts +203 -0
  111. package/src/GraphGenomeView/layout/orderedLayout.test.ts +212 -0
  112. package/src/GraphGenomeView/layout/orderedLayout.ts +396 -0
  113. package/src/GraphGenomeView/layout/orientToReference.test.ts +110 -0
  114. package/src/GraphGenomeView/layout/orientToReference.ts +79 -0
  115. package/src/GraphGenomeView/layout/placeOffReference.ts +147 -0
  116. package/src/GraphGenomeView/layout/referenceSeeds.test.ts +79 -0
  117. package/src/GraphGenomeView/layout/referenceSeeds.ts +89 -0
  118. package/src/GraphGenomeView/layout/rowSpacing.ts +5 -0
  119. package/src/GraphGenomeView/layout/sampleRowLayout.test.ts +318 -0
  120. package/src/GraphGenomeView/layout/sampleRowLayout.ts +134 -0
  121. package/src/GraphGenomeView/layout/variantMapLayout.test.ts +59 -0
  122. package/src/GraphGenomeView/layout/variantMapLayout.ts +20 -0
  123. package/src/GraphGenomeView/layout/walkRowLayout.ts +54 -0
  124. package/src/GraphGenomeView/layout/walkRows.test.ts +178 -0
  125. package/src/GraphGenomeView/layout/walkRows.ts +177 -0
  126. package/src/GraphGenomeView/layoutModes.ts +131 -0
  127. package/src/GraphGenomeView/model.test.ts +2696 -0
  128. package/src/GraphGenomeView/model.ts +2672 -94
  129. package/src/GraphGenomeView/nodeWidths.ts +67 -0
  130. package/src/GraphGenomeView/pathAnchoring.test.ts +252 -0
  131. package/src/GraphGenomeView/pathAnchoring.ts +164 -0
  132. package/src/GraphGenomeView/pathColors.test.ts +83 -0
  133. package/src/GraphGenomeView/pathColors.ts +122 -0
  134. package/src/GraphGenomeView/referenceSpan.test.ts +84 -0
  135. package/src/GraphGenomeView/referenceSpan.ts +133 -0
  136. package/src/GraphGenomeView/renderPasses.bench.ts +172 -0
  137. package/src/GraphGenomeView/renderPipeline.test.ts +226 -0
  138. package/src/GraphGenomeView/renderer/Canvas2DRenderer.test.ts +205 -0
  139. package/src/GraphGenomeView/renderer/Canvas2DRenderer.ts +246 -0
  140. package/src/GraphGenomeView/renderer/GeometryBuilder.test.ts +723 -0
  141. package/src/GraphGenomeView/renderer/GeometryBuilder.ts +882 -0
  142. package/src/GraphGenomeView/renderer/GraphRenderer.ts +13 -0
  143. package/src/GraphGenomeView/renderer/colorBits.ts +43 -0
  144. package/src/GraphGenomeView/renderer/recordingCanvas.ts +64 -0
  145. package/src/GraphGenomeView/renderer/types.ts +84 -0
  146. package/src/GraphGenomeView/renderer/walkFade.test.ts +100 -0
  147. package/src/GraphGenomeView/repeats/repeatFeatures.test.ts +114 -0
  148. package/src/GraphGenomeView/repeats/repeatFeatures.ts +202 -0
  149. package/src/GraphGenomeView/repeats/test_data/abca7_trgt_walks.json +575 -0
  150. package/src/GraphGenomeView/repeats/walkCalls.test.ts +127 -0
  151. package/src/GraphGenomeView/repeats/walkCalls.ts +132 -0
  152. package/src/GraphGenomeView/stress.test.ts +214 -0
  153. package/src/GraphGenomeView/subgraphLoad.test.ts +245 -0
  154. package/src/GraphGenomeView/types.ts +141 -0
  155. package/src/GraphGenomeView/util/SpatialIndex.test.ts +73 -0
  156. package/src/GraphGenomeView/util/SpatialIndex.ts +312 -0
  157. package/src/GraphGenomeView/util/edgeCurves.test.ts +93 -0
  158. package/src/GraphGenomeView/util/edgeCurves.ts +94 -0
  159. package/src/GraphGenomeView/util/geometry.test.ts +447 -0
  160. package/src/GraphGenomeView/util/geometry.ts +656 -0
  161. package/src/GraphGenomeView/util/hitDetection.test.ts +307 -0
  162. package/src/GraphGenomeView/util/hitDetection.ts +330 -0
  163. package/src/GraphGenomeView/util/wheelZoom.test.ts +40 -0
  164. package/src/GraphGenomeView/util/wheelZoom.ts +26 -0
  165. package/src/GraphGenomeView/walkHighlight.test.ts +54 -0
  166. package/src/GraphGenomeView/walkHighlight.ts +56 -0
  167. package/src/LaunchGraphGenomeView.ts +46 -0
  168. package/src/MinigraphBubbleAdapter/MinigraphBubbleAdapter.test.ts +139 -0
  169. package/src/MinigraphBubbleAdapter/MinigraphBubbleAdapter.ts +86 -0
  170. package/src/MinigraphBubbleAdapter/bubbleLine.ts +92 -0
  171. package/src/MinigraphBubbleAdapter/configSchema.ts +109 -0
  172. package/src/MinigraphBubbleAdapter/index.ts +22 -0
  173. package/src/MinigraphBubbleAdapter/test_data/hprc_bubbles_mhc.bed.gz +0 -0
  174. package/src/MinigraphBubbleAdapter/test_data/hprc_bubbles_mhc.bed.gz.tbi +0 -0
  175. package/src/RgfaTabixAdapter/RgfaTabixAdapter.test.ts +285 -0
  176. package/src/RgfaTabixAdapter/RgfaTabixAdapter.ts +194 -0
  177. package/src/RgfaTabixAdapter/bubbleTier.test.ts +84 -0
  178. package/src/RgfaTabixAdapter/configSchema.ts +142 -0
  179. package/src/RgfaTabixAdapter/index.ts +24 -0
  180. package/src/RgfaTabixAdapter/rgfaBed.test.ts +158 -0
  181. package/src/RgfaTabixAdapter/rgfaBed.ts +156 -0
  182. package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.links.bed.gz +0 -0
  183. package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.links.bed.gz.tbi +0 -0
  184. package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.segs.bed.gz +0 -0
  185. package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.segs.bed.gz.tbi +0 -0
  186. package/src/RgfaTabixAdapter/test_data/pggb_ecoli.links.bed.gz +0 -0
  187. package/src/RgfaTabixAdapter/test_data/pggb_ecoli.links.bed.gz.tbi +0 -0
  188. package/src/RgfaTabixAdapter/test_data/pggb_ecoli.segs.bed.gz +0 -0
  189. package/src/RgfaTabixAdapter/test_data/pggb_ecoli.segs.bed.gz.tbi +0 -0
  190. package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.links.bed.gz +0 -0
  191. package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.links.bed.gz.tbi +0 -0
  192. package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.segs.bed.gz +0 -0
  193. package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.segs.bed.gz.tbi +0 -0
  194. package/src/alleleProjection/projectAlleles.test.ts +75 -0
  195. package/src/alleleProjection/projectAlleles.ts +190 -0
  196. package/src/bandage/README.md +121 -0
  197. package/src/bandage/bandage-layout.d.ts +14 -0
  198. package/src/bandage/bandage-layout.js +0 -0
  199. package/src/bandage/native/CMakeLists.txt +45 -0
  200. package/src/bandage/native/include/graph.h +188 -0
  201. package/src/bandage/native/include/graphlayout.h +74 -0
  202. package/src/bandage/native/include/settings.h +86 -0
  203. package/src/bandage/native/include/types.h +56 -0
  204. package/src/bandage/native/src/bindings.cpp +164 -0
  205. package/src/bandage/native/src/graphlayout.cpp +621 -0
  206. package/src/bandage/resizableDecode.test.ts +58 -0
  207. package/src/gfa-core/gfaParser.bench.ts +55 -0
  208. package/src/gfa-core/gfaParser.test.ts +168 -0
  209. package/src/gfa-core/gfaParser.ts +237 -0
  210. package/src/gfa-core/index.ts +10 -0
  211. package/src/hoverSync/GraphNodeHighlight.tsx +62 -0
  212. package/src/hoverSync/graphViewHighlights.test.ts +158 -0
  213. package/src/hoverSync/graphViewHighlights.ts +119 -0
  214. package/src/hoverSync/index.test.tsx +36 -0
  215. package/src/hoverSync/index.tsx +19 -0
  216. package/src/hoverSync/lgvHover.test.ts +202 -0
  217. package/src/hoverSync/lgvHover.ts +136 -0
  218. package/src/index.ts +46 -15
  219. package/src/launchFromGraph/contributors.test.ts +197 -0
  220. package/src/launchFromGraph/contributors.ts +0 -0
  221. package/src/launchFromGraph/graphMenuItems.ts +211 -0
  222. package/src/launchFromGraph/launchFromGraph.test.ts +489 -0
  223. package/src/launchFromGraph/launchFromGraph.ts +265 -0
  224. package/src/launchFromGraph/launchTracks.test.ts +72 -0
  225. package/src/launchFromGraph/launchTracks.ts +50 -0
  226. package/src/launchFromGraph/linearViewTarget.test.ts +82 -0
  227. package/src/launchFromGraph/linearViewTarget.ts +75 -0
  228. package/src/launchFromGraph/syntenyTracks.ts +47 -0
  229. package/src/launchSubgraph/index.ts +152 -0
  230. package/src/launchSubgraph/launchSubgraph.test.ts +264 -0
  231. package/src/launchSubgraph/launchSubgraphView.ts +128 -0
  232. package/src/launchSubgraph/linearViewMenuItems.test.ts +176 -0
  233. package/src/launchSubgraph/linearViewMenuItems.ts +107 -0
  234. package/src/launchSubgraph/multiWayLaunch.test.ts +289 -0
  235. package/src/launchSubgraph/subgraphMenuItems.ts +73 -0
  236. package/src/launchSubgraph/subgraphTracks.test.ts +165 -0
  237. package/src/launchSubgraph/subgraphTracks.ts +151 -0
  238. package/src/launchSubgraph/testEnv.ts +258 -0
  239. package/src/loadBandage.ts +22 -0
  240. package/src/panSNTabix.test.ts +72 -0
  241. package/src/panSNTabix.ts +149 -0
  242. package/src/pansn.ts +106 -0
  243. package/src/pluginName.ts +3 -0
  244. package/src/synteny/ComparativeAdapterBase.ts +77 -0
  245. package/src/synteny/SyntenyFeature.ts +116 -0
  246. package/src/synteny/clipFeatureToRegion.test.ts +329 -0
  247. package/src/synteny/clipFeatureToRegion.ts +167 -0
  248. package/src/version.ts +1 -0
  249. package/dist/GraphGenomeView/components/FeatureDialog.d.ts +0 -5
  250. package/dist/GraphGenomeView/components/FeatureDialog.js +0 -18
  251. package/dist/GraphGenomeView/components/FeatureDialog.js.map +0 -1
  252. package/dist/GraphGenomeView/components/GraphGenomeView.d.ts +0 -6
  253. package/dist/GraphGenomeView/components/GraphGenomeView.js +0 -17
  254. package/dist/GraphGenomeView/components/GraphGenomeView.js.map +0 -1
  255. package/dist/GraphGenomeView/components/GraphPanel.d.ts +0 -6
  256. package/dist/GraphGenomeView/components/GraphPanel.js +0 -17
  257. package/dist/GraphGenomeView/components/GraphPanel.js.map +0 -1
  258. package/dist/GraphGenomeView/components/Header.d.ts +0 -6
  259. package/dist/GraphGenomeView/components/Header.js +0 -59
  260. package/dist/GraphGenomeView/components/Header.js.map +0 -1
  261. package/dist/GraphGenomeView/components/SettingsDialog.d.ts +0 -7
  262. package/dist/GraphGenomeView/components/SettingsDialog.js +0 -24
  263. package/dist/GraphGenomeView/components/SettingsDialog.js.map +0 -1
  264. package/dist/GraphGenomeView/index.d.ts +0 -2
  265. package/dist/GraphGenomeView/index.js +0 -15
  266. package/dist/GraphGenomeView/index.js.map +0 -1
  267. package/dist/GraphGenomeView/model.d.ts +0 -80
  268. package/dist/GraphGenomeView/model.js +0 -96
  269. package/dist/GraphGenomeView/model.js.map +0 -1
  270. package/dist/GraphGenomeView/util.d.ts +0 -1
  271. package/dist/GraphGenomeView/util.js +0 -8
  272. package/dist/GraphGenomeView/util.js.map +0 -1
  273. package/dist/index.d.ts +0 -8
  274. package/dist/index.js +0 -24
  275. package/dist/index.js.map +0 -1
  276. package/dist/jbrowse-plugin-graphgenomeviewer.umd.production.min.js +0 -67
  277. package/dist/jbrowse-plugin-graphgenomeviewer.umd.production.min.js.map +0 -7
  278. package/src/GraphGenomeView/components/FeatureDialog.tsx +0 -51
  279. package/src/GraphGenomeView/components/GraphPanel.tsx +0 -37
  280. package/src/GraphGenomeView/components/Header.tsx +0 -90
  281. package/src/GraphGenomeView/components/SettingsDialog.tsx +0 -71
  282. package/src/GraphGenomeView/util.ts +0 -7
  283. package/src/declare.d.ts +0 -1
@@ -0,0 +1,285 @@
1
+ import { firstValueFrom } from 'rxjs'
2
+ import { toArray } from 'rxjs/operators'
3
+
4
+ import Adapter from './RgfaTabixAdapter.ts'
5
+ import configSchema from './configSchema.ts'
6
+
7
+ // Built by scripts/build_rgfa_tabix.sh from the minigraph rGFA of four E. coli
8
+ // strains at jbrowse.org/demos/ecoli_pangenome/ecoli_rgfa_slice.gfa (the
9
+ // pangenome tutorial's figure data): 161 segments across four stable sequences,
10
+ // ranks 0-3. Named rgfa_ecoli rather than ecoli_rgfa because the repo's
11
+ // .gitignore ignores `ecoli_*` (the multi-GB pangenome build dirs).
12
+ const prefix = require
13
+ .resolve('./test_data/rgfa_ecoli.segs.bed.gz')
14
+ .replace(/\.segs\.bed\.gz$/, '')
15
+
16
+ function makeAdapter() {
17
+ const local = (path: string) => ({
18
+ localPath: path,
19
+ locationType: 'LocalPathLocation',
20
+ })
21
+ return new Adapter(
22
+ configSchema.create({
23
+ segmentsLocation: local(`${prefix}.segs.bed.gz`),
24
+ segmentsIndex: { location: local(`${prefix}.segs.bed.gz.tbi`) },
25
+ linksLocation: local(`${prefix}.links.bed.gz`),
26
+ linksIndex: { location: local(`${prefix}.links.bed.gz.tbi`) },
27
+ }),
28
+ )
29
+ }
30
+
31
+ // The graph names its stable sequences PanSN (`K12#1#chr`); an assembly asking
32
+ // for them names them `chr` in assembly `K12`.
33
+ const k12 = {
34
+ refName: 'chr',
35
+ assemblyName: 'K12',
36
+ start: 993236,
37
+ end: 997574,
38
+ }
39
+
40
+ test('getRefNames lists the stable sequences', async () => {
41
+ expect(await makeAdapter().getRefNames()).toEqual([
42
+ 'CFT073#1#chr',
43
+ 'K12#1#chr',
44
+ 'NCTC86#1#chr',
45
+ 'Sakai#1#chr',
46
+ ])
47
+ })
48
+
49
+ test('getFeatures returns the segments on a stable sequence', async () => {
50
+ const features = await firstValueFrom(
51
+ makeAdapter().getFeatures(k12).pipe(toArray()),
52
+ )
53
+ expect(
54
+ features.map(f => ({
55
+ name: f.get('name'),
56
+ start: f.get('start'),
57
+ end: f.get('end'),
58
+ rank: f.get('rank'),
59
+ })),
60
+ ).toEqual([
61
+ { name: 's322', start: 993236, end: 993310, rank: 0 },
62
+ { name: 's323', start: 993310, end: 997574, rank: 0 },
63
+ ])
64
+ })
65
+
66
+ test('getFeatures resolves a PanSN name given unqualified', async () => {
67
+ const qualified = await firstValueFrom(
68
+ makeAdapter()
69
+ .getFeatures({ ...k12, refName: 'K12#1#chr' })
70
+ .pipe(toArray()),
71
+ )
72
+ expect(qualified.map(f => f.get('name'))).toEqual(['s322', 's323'])
73
+ })
74
+
75
+ test('getFeatures is empty for a sequence the graph does not anchor to', async () => {
76
+ const features = await firstValueFrom(
77
+ makeAdapter()
78
+ .getFeatures({ ...k12, assemblyName: 'volvox', refName: 'ctgA' })
79
+ .pipe(toArray()),
80
+ )
81
+ expect(features).toEqual([])
82
+ })
83
+
84
+ // The point of the two-file index: a region on the reference backbone reaches
85
+ // the rank>0 segments hanging off it, which live at their own coordinates on
86
+ // another stable sequence (here CFT073) and so can never be found by a
87
+ // coordinate query on this region.
88
+ test('getSubgraph reaches off-reference neighbours', async () => {
89
+ const gfa = await makeAdapter().getSubgraph(k12)
90
+ expect(gfa).toBe(
91
+ [
92
+ 'H\tVN:Z:1.0',
93
+ 'S\ts1727\t*\tLN:i:226\tSN:Z:CFT073#1#chr\tSO:i:1044024\tSR:i:2',
94
+ 'S\ts1728\t*\tLN:i:75\tSN:Z:CFT073#1#chr\tSO:i:1048515\tSR:i:2',
95
+ 'S\ts322\t*\tLN:i:74\tSN:Z:K12#1#chr\tSO:i:993236\tSR:i:0',
96
+ 'S\ts323\t*\tLN:i:4264\tSN:Z:K12#1#chr\tSO:i:993310\tSR:i:0',
97
+ 'S\ts324\t*\tLN:i:7093\tSN:Z:K12#1#chr\tSO:i:997574\tSR:i:0',
98
+ 'L\ts1727\t+\ts323\t+\t0M',
99
+ 'L\ts322\t+\ts323\t+\t0M',
100
+ 'L\ts323\t+\ts1728\t+\t0M',
101
+ 'L\ts323\t+\ts324\t+\t0M',
102
+ ].join('\n'),
103
+ )
104
+ })
105
+
106
+ test('getSubgraph is byte-identical across calls', async () => {
107
+ const adapter = makeAdapter()
108
+ expect(await adapter.getSubgraph(k12)).toBe(await adapter.getSubgraph(k12))
109
+ })
110
+
111
+ // context expands by whole link hops, following each newly reached segment from
112
+ // its own stable coordinates — which every link row carries.
113
+ test('getSubgraph context adds another hop', async () => {
114
+ const adapter = makeAdapter()
115
+ const ids = (gfa: string) =>
116
+ gfa
117
+ .split('\n')
118
+ .filter(l => l.startsWith('S'))
119
+ .map(l => l.split('\t')[1]!)
120
+
121
+ const near = ids(await adapter.getSubgraph(k12))
122
+ const far = ids(await adapter.getSubgraph(k12, { hops: 1 }))
123
+ expect(far.length).toBeGreaterThan(near.length)
124
+ expect(far).toEqual(expect.arrayContaining(near))
125
+ })
126
+
127
+ // A hop is one query per off-reference segment, so a cut the view has replaced
128
+ // has a lot left to read. The signal reaches every query, and one that gave up
129
+ // before it started reads nothing.
130
+ test('getSubgraph stops for its signal', async () => {
131
+ const gaveUp = new AbortController()
132
+ gaveUp.abort()
133
+ await expect(
134
+ makeAdapter().getSubgraph(k12, { hops: 1, signal: gaveUp.signal }),
135
+ ).rejects.toThrow(/abort/i)
136
+ })
137
+
138
+ test('getFeatures stops for its signal', async () => {
139
+ const gaveUp = new AbortController()
140
+ gaveUp.abort()
141
+ await expect(
142
+ firstValueFrom(
143
+ makeAdapter().getFeatures(k12, { signal: gaveUp.signal }).pipe(toArray()),
144
+ ),
145
+ ).rejects.toThrow(/abort/i)
146
+ })
147
+
148
+ // A bare header drew as an empty pane with no error, which is what a missing
149
+ // assemblyNameToPanSN entry (hs1 without CHM13) looked like.
150
+ test('getSubgraph refuses a sequence the index does not hold, saying how to map one', async () => {
151
+ await expect(
152
+ makeAdapter().getSubgraph({
153
+ ...k12,
154
+ assemblyName: 'volvox',
155
+ refName: 'ctgA',
156
+ }),
157
+ ).rejects.toThrow(
158
+ /volvox ctgA is not in this graph's index.*assemblyNameToPanSN/,
159
+ )
160
+ })
161
+
162
+ // The carriage half of the index, which no rGFA has: built by
163
+ // scripts/build_pggb_tabix.sh in jbrowse-components from the same five-strain
164
+ // pggb graph the tutorial uses, sliced to the IS5 element at K12
165
+ // chr:1,299,499-1,300,693. 24 segments spanning every carrier count from 1 to
166
+ // 5, so the fixture covers a strain-private allele and core backbone in one
167
+ // window.
168
+ const pggbPrefix = require
169
+ .resolve('./test_data/pggb_ecoli.segs.bed.gz')
170
+ .replace(/\.segs\.bed\.gz$/, '')
171
+
172
+ function makePggbAdapter() {
173
+ const local = (path: string) => ({
174
+ localPath: path,
175
+ locationType: 'LocalPathLocation',
176
+ })
177
+ return new Adapter(
178
+ configSchema.create({
179
+ segmentsLocation: local(`${pggbPrefix}.segs.bed.gz`),
180
+ segmentsIndex: { location: local(`${pggbPrefix}.segs.bed.gz.tbi`) },
181
+ linksLocation: local(`${pggbPrefix}.links.bed.gz`),
182
+ linksIndex: { location: local(`${pggbPrefix}.links.bed.gz.tbi`) },
183
+ }),
184
+ )
185
+ }
186
+
187
+ const is5 = {
188
+ refName: 'chr',
189
+ assemblyName: 'K12',
190
+ start: 1299260,
191
+ end: 1300800,
192
+ }
193
+
194
+ // The lane this exists for: color a linear track by how many haplotypes carry
195
+ // each segment. Before this the tag column reached the graph view's node popup
196
+ // and stopped there, so a feature could say its rank but not its carriage.
197
+ test('getFeatures carries SM:Z: onto the feature', async () => {
198
+ const features = await firstValueFrom(
199
+ makePggbAdapter().getFeatures(is5).pipe(toArray()),
200
+ )
201
+ const private1199 = features.find(f => f.get('name') === '196827')!
202
+ expect(private1199.get('end') - private1199.get('start')).toBe(1199)
203
+ expect(private1199.get('samples')).toEqual(['K12.1'])
204
+ expect(private1199.get('carriers')).toBe(1)
205
+
206
+ const core = features.find(f => f.get('name') === '196843')!
207
+ expect(core.get('samples')).toEqual([
208
+ 'K12.1',
209
+ 'Sakai.1',
210
+ 'CFT073.1',
211
+ 'NCTC86.1',
212
+ 'IAI39.1',
213
+ ])
214
+ expect(core.get('carriers')).toBe(5)
215
+ })
216
+
217
+ // The other half of the same fixture: the graph route reads carriage off the
218
+ // synthesized S-line, and rgfaBed.test.ts asserts that from a hand-written row.
219
+ // This is the same claim against a real indexed file.
220
+ test('getSubgraph keeps the tag on the S-line it synthesizes', async () => {
221
+ const gfa = await makePggbAdapter().getSubgraph(is5)
222
+ expect(gfa.split('\n')).toContain(
223
+ 'S\t196827\t*\tLN:i:1199\tSN:Z:K12#1#chr\tSO:i:1299498\tSR:i:0\tSM:Z:K12.1',
224
+ )
225
+ })
226
+
227
+ // An rGFA has no sixth column, so the attribute is absent rather than 0 — a
228
+ // color expression reading `carriers` on such a track gets undefined, not a
229
+ // claim that nothing carries the segment.
230
+ test('getFeatures omits carriage on a graph that states none', async () => {
231
+ const features = await firstValueFrom(
232
+ makeAdapter().getFeatures(k12).pipe(toArray()),
233
+ )
234
+ expect(features.length).toBeGreaterThan(0)
235
+ expect(features.every(f => f.get('samples') === undefined)).toBe(true)
236
+ expect(features.every(f => f.get('carriers') === undefined)).toBe(true)
237
+ })
238
+
239
+ // Minigraph-Cactus writes PanSN stable names, so HPRC release 2 calls the
240
+ // reference `GRCh38#0#chr1` while the JBrowse assembly is `hg38`. This reuses
241
+ // the `assemblyNameToPanSN` slot and helper the all-vs-all PAF adapters use.
242
+ // Matching on the bare contig instead is not an option: that graph also carries
243
+ // `CHM13#0#chr1`, so it would silently resolve to the wrong sample.
244
+ function makeMappedAdapter(assemblyNameToPanSN: Record<string, string>) {
245
+ const local = (path: string) => ({
246
+ localPath: path,
247
+ locationType: 'LocalPathLocation',
248
+ })
249
+ return new Adapter(
250
+ configSchema.create({
251
+ segmentsLocation: local(`${prefix}.segs.bed.gz`),
252
+ segmentsIndex: { location: local(`${prefix}.segs.bed.gz.tbi`) },
253
+ linksLocation: local(`${prefix}.links.bed.gz`),
254
+ linksIndex: { location: local(`${prefix}.links.bed.gz.tbi`) },
255
+ assemblyNameToPanSN,
256
+ }),
257
+ )
258
+ }
259
+
260
+ // the fixture's sample is `K12`, so an assembly named anything else needs the map
261
+ const renamed = { ...k12, assemblyName: 'ecoli_k12' }
262
+
263
+ test('maps an assembly name to a differing PanSN sample prefix', async () => {
264
+ const features = await firstValueFrom(
265
+ makeMappedAdapter({ ecoli_k12: 'K12' })
266
+ .getFeatures(renamed)
267
+ .pipe(toArray()),
268
+ )
269
+ expect(features.length).toBeGreaterThan(0)
270
+ expect(features.every(f => f.get('refName') === 'chr')).toBe(true)
271
+ })
272
+
273
+ test('without the mapping the same query resolves nothing', async () => {
274
+ const features = await firstValueFrom(
275
+ makeMappedAdapter({}).getFeatures(renamed).pipe(toArray()),
276
+ )
277
+ expect(features).toEqual([])
278
+ })
279
+
280
+ test('getSubgraph resolves through the mapping too', async () => {
281
+ const gfa = await makeMappedAdapter({ ecoli_k12: 'K12' }).getSubgraph(renamed)
282
+ expect(gfa.split('\n').filter(l => l.startsWith('S')).length).toBeGreaterThan(
283
+ 0,
284
+ )
285
+ })
@@ -0,0 +1,194 @@
1
+ import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter'
2
+ import { SimpleFeature, updateStatus } from '@jbrowse/core/util'
3
+ import { ObservableCreate } from '@jbrowse/core/util/rxjs'
4
+
5
+ import { PanSNRefNames, openTabixSlot } from '../panSNTabix.ts'
6
+ import {
7
+ formatSubgraph,
8
+ linkKey,
9
+ parseLinkLine,
10
+ parseSegmentLine,
11
+ segmentSamples,
12
+ } from './rgfaBed.ts'
13
+
14
+ import type { RgfaTabixAdapterConfig } from './configSchema.ts'
15
+ import type { SubgraphAdapterOptions } from '../GetSubgraph.ts'
16
+ import type { RgfaLink, RgfaSegment } from './rgfaBed.ts'
17
+ import type PluginManager from '@jbrowse/core/PluginManager'
18
+ import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter'
19
+ import type { getSubAdapterType } from '@jbrowse/core/data_adapters/dataAdapterCache'
20
+ import type { Feature } from '@jbrowse/core/util'
21
+ import type { Region } from '@jbrowse/core/util/types'
22
+
23
+ // What a hop follows: alleles, never the backbone. A rank-0 segment reached
24
+ // through a link is flanking backbone, usually outside the window, so querying
25
+ // its links walks the reference away from the region, brings back more backbone,
26
+ // and closes nothing. Every segment that closes a bubble is an allele interior,
27
+ // and an allele interior is rank > 0 in both index formats (rGFA's SR tag, and
28
+ // pggb's rank 0-or-1 against the anchor path). Reached backbone still becomes a
29
+ // node and its edge still draws; it is only not walked through.
30
+ //
31
+ // This is gfabase's rule, arrived at from the other end. `gfabase sub` expands
32
+ // from a seed either to the whole connected component (`--connected`, which its
33
+ // own README calls overkill) or with `--cutpoints 1`, which takes the biconnected
34
+ // component by *stopping at cutpoints*, the segments every end-to-end walk of
35
+ // the chromosome must traverse. In a reference-anchored pangenome the cutpoints
36
+ // are the backbone, and rank is what the index already states about it, so
37
+ // `rank > 0` is that stopping rule without a connectivity index to build. The
38
+ // third tool is the same shape again: `gfatools view -R` maps the region onto its
39
+ // precomputed bubble decomposition and returns whole overlapping bubbles
40
+ // (gfa_query_by_reg), which is complete for the same reason. Bandage's own
41
+ // `--distance` is a node-step radius, but Bandage holds the entire graph in
42
+ // memory, so its scope is a drawing filter rather than a fetch strategy.
43
+ //
44
+ // Measured with this rule: the E. coli paa window converges at one hop (17
45
+ // segments at 1 and at 2), so the cut closes and stays closed. HPRC's amylase
46
+ // window keeps growing (63 at 0, 78 at 1, 92 at 2) because its alleles have
47
+ // alleles, and wall-clock is flat across all three, since a hop's queries go out
48
+ // together and the remote index dominates.
49
+ //
50
+ // The exact version, when a graph ships a bubble index beside its segments:
51
+ // gfatools' bubble rows carry the member segment ids of every bubble, so the
52
+ // bubbles overlapping the region state which segments a complete cut must
53
+ // contain. That turns the hop count into a termination condition rather than a
54
+ // setting, and MinigraphBubbleAdapter already parses those rows.
55
+ function offReference(segments: RgfaSegment[]) {
56
+ return segments.filter(segment => segment.rank > 0)
57
+ }
58
+
59
+ export default class RgfaTabixAdapter extends BaseFeatureDataAdapter<RgfaTabixAdapterConfig> {
60
+ public static capabilities = ['getFeatures', 'getRefNames']
61
+
62
+ private readonly segments
63
+ private readonly links
64
+ // Off the SEGMENT index: it is the one that names every stable sequence the
65
+ // graph is anchored to, where the link index only names those a link touches.
66
+ private readonly refNames
67
+
68
+ public constructor(
69
+ config: RgfaTabixAdapterConfig,
70
+ getSubAdapter?: getSubAdapterType,
71
+ pluginManager?: PluginManager,
72
+ ) {
73
+ super(config, getSubAdapter, pluginManager)
74
+ this.segments = openTabixSlot(this, 'segmentsLocation', 'segmentsIndex')
75
+ this.links = openTabixSlot(this, 'linksLocation', 'linksIndex')
76
+ this.refNames = new PanSNRefNames(this.segments, this)
77
+ }
78
+
79
+ async getRefNames(opts: BaseOptions = {}) {
80
+ return this.refNames.assemblyRefNames(opts)
81
+ }
82
+
83
+ public async hasDataForRefName() {
84
+ return true
85
+ }
86
+
87
+ getFeatures(query: Region, opts: BaseOptions = {}) {
88
+ const { signal, statusCallback } = opts
89
+ return ObservableCreate<Feature>(async observer => {
90
+ const tabixRefName = await this.refNames.resolve(query, opts)
91
+ if (tabixRefName !== undefined) {
92
+ await updateStatus('Downloading segments', statusCallback, () =>
93
+ this.segments.getLines(tabixRefName, query.start, query.end, {
94
+ signal,
95
+ lineCallback: line => {
96
+ const segment = parseSegmentLine(line)
97
+ // `samples` is who, `carriers` is how many, and the second is not
98
+ // just a convenience: a lane colored by carriage is a jexl
99
+ // expression in a config, where counting a list means relying on
100
+ // jexl resolving `.length` through a member access on an array.
101
+ // The count is the axis the color reads, so the index states it.
102
+ const samples = segmentSamples(segment)
103
+ observer.next(
104
+ new SimpleFeature({
105
+ uniqueId: segment.id,
106
+ refName: query.refName,
107
+ start: segment.start,
108
+ end: segment.end,
109
+ name: segment.id,
110
+ type: 'segment',
111
+ rank: segment.rank,
112
+ stableName: segment.refName,
113
+ ...(samples && { samples, carriers: samples.length }),
114
+ }),
115
+ )
116
+ },
117
+ }),
118
+ )
119
+ }
120
+ observer.complete()
121
+ }, signal)
122
+ }
123
+
124
+ // Extract the graph around a region as GFA text, for GraphGenomeView. The
125
+ // region's own segments come from segs.bed.gz; links.bed.gz supplies the edges
126
+ // incident to them *and* the coordinates of the segments on the other end,
127
+ // which typically sit on a different stable sequence (a rank>0 bubble) and so
128
+ // are not reachable by any coordinate query on this region.
129
+ //
130
+ // `hops` is extra rounds of link-following past the region's own segments
131
+ // and their immediate neighbours, each costing one tabix query per
132
+ // off-reference segment newly reached. One round is what closes a bubble, so
133
+ // it is the default the view asks for; see the frontier below. `signal` goes
134
+ // to every one of those queries, so a cut the view has replaced stops.
135
+ async getSubgraph(region: Region, opts: SubgraphAdapterOptions = {}) {
136
+ const { hops = 0, signal } = opts
137
+ const segments = new Map<string, RgfaSegment>()
138
+ const links = new Map<string, RgfaLink>()
139
+ const tabixRefName = await this.refNames.resolve(region, { signal })
140
+
141
+ const addLinksOver = async (
142
+ refName: string,
143
+ start: number,
144
+ end: number,
145
+ ) => {
146
+ const reached: RgfaSegment[] = []
147
+ await this.links.getLines(refName, start, end, {
148
+ signal,
149
+ lineCallback: line => {
150
+ const link = parseLinkLine(line)
151
+ links.set(linkKey(link), link)
152
+ for (const segment of [link.sourceSegment, link.targetSegment]) {
153
+ if (!segments.has(segment.id)) {
154
+ segments.set(segment.id, segment)
155
+ reached.push(segment)
156
+ }
157
+ }
158
+ },
159
+ })
160
+ return reached
161
+ }
162
+
163
+ if (tabixRefName === undefined) {
164
+ throw new Error(
165
+ `${region.assemblyName} ${region.refName} is not in this graph's index; a graph with PanSN names (GRCh38#0#chr1) needs ${region.assemblyName} mapped to its prefix in assemblyNameToPanSN`,
166
+ )
167
+ }
168
+ await this.segments.getLines(tabixRefName, region.start, region.end, {
169
+ signal,
170
+ lineCallback: line => {
171
+ const segment = parseSegmentLine(line)
172
+ segments.set(segment.id, segment)
173
+ },
174
+ })
175
+ let frontier = offReference(
176
+ await addLinksOver(tabixRefName, region.start, region.end),
177
+ )
178
+ for (let hop = 0; hop < hops; hop++) {
179
+ // One hop's queries are independent of each other, so they go out
180
+ // together rather than one round-trip at a time. Their callbacks share
181
+ // the segment and link maps, which is safe because only one of them runs
182
+ // at a time, and the output is sorted at the end regardless of the order
183
+ // they arrive in.
184
+ const reached = await Promise.all(
185
+ frontier.map(segment =>
186
+ addLinksOver(segment.refName, segment.start, segment.end),
187
+ ),
188
+ )
189
+ frontier = offReference(reached.flat())
190
+ }
191
+
192
+ return formatSubgraph(segments, links)
193
+ }
194
+ }
@@ -0,0 +1,84 @@
1
+ import Adapter from './RgfaTabixAdapter.ts'
2
+ import configSchema from './configSchema.ts'
3
+ import { parseGFA } from '../gfa-core/index.ts'
4
+
5
+ // A level-of-detail tier: the same two files the adapter always reads, but one
6
+ // node per bubble instead of one per GFA segment. Built by
7
+ // jbrowse-components/scripts/bubbles_to_tier_bed.py from HPRC release 2's
8
+ // hosted `hprc-v2.0-mc-grch38.bubbles.bed.gz`, filtered to chrY at
9
+ // `--min-content 10000`. The whole chromosome is 57 bubbles and 57 backbone
10
+ // nodes in 5.5 kB, which is the point of the tier.
11
+ const prefix = require
12
+ .resolve('./test_data/bubble_tier_chrY.segs.bed.gz')
13
+ .replace(/\.segs\.bed\.gz$/, '')
14
+
15
+ function makeAdapter() {
16
+ const local = (path: string) => ({
17
+ localPath: path,
18
+ locationType: 'LocalPathLocation',
19
+ })
20
+ return new Adapter(
21
+ configSchema.create({
22
+ segmentsLocation: local(`${prefix}.segs.bed.gz`),
23
+ segmentsIndex: { location: local(`${prefix}.segs.bed.gz.tbi`) },
24
+ linksLocation: local(`${prefix}.links.bed.gz`),
25
+ linksIndex: { location: local(`${prefix}.links.bed.gz.tbi`) },
26
+ assemblyNameToPanSN: { hg38: 'GRCh38' },
27
+ }),
28
+ )
29
+ }
30
+
31
+ const wholeChromosome = {
32
+ refName: 'chrY',
33
+ assemblyName: 'hg38',
34
+ start: 0,
35
+ end: 57_227_415,
36
+ }
37
+
38
+ // The claim the tier exists to make: a whole human chromosome is drawable. The
39
+ // measured ceiling is a few hundred nodes (reference/PANGENOME_GRAPHS.md, "Force
40
+ // layout does not get better with more nodes"), and the fine tier over the same
41
+ // span would be six figures of segments.
42
+ test('a whole chromosome collapses to a drawable node count', async () => {
43
+ const graph = parseGFA(await makeAdapter().getSubgraph(wholeChromosome))
44
+ expect(graph.nodes.length).toBe(114)
45
+ expect(graph.nodes.length).toBeLessThan(500)
46
+ })
47
+
48
+ test('the collapse summary reaches GraphNode.tags', async () => {
49
+ const graph = parseGFA(await makeAdapter().getSubgraph(wholeChromosome))
50
+ const bubbles = graph.nodes.filter(n => n.tags.ct === 'bubble')
51
+ const backbone = graph.nodes.filter(n => n.tags.ct === 'backbone')
52
+ expect(bubbles).toHaveLength(57)
53
+ expect(backbone).toHaveLength(57)
54
+
55
+ // Every bubble states what it collapsed, and the numbers arrive typed.
56
+ expect(
57
+ bubbles.every(n => typeof n.tags.cn === 'number' && n.tags.cn >= 1),
58
+ ).toBe(true)
59
+ expect(bubbles.every(n => typeof n.tags.cl === 'number')).toBe(true)
60
+ })
61
+
62
+ // A pure insertion is an alternative to nothing, so it has no reference span.
63
+ // It is drawn 1 bp wide and states its real size in cl, which is why the
64
+ // producer thresholds on content rather than on `end - start`.
65
+ test('a pure insertion survives as a 1 bp node stating its true length', async () => {
66
+ const graph = parseGFA(await makeAdapter().getSubgraph(wholeChromosome))
67
+ const insertions = graph.nodes.filter(
68
+ n => n.tags.ct === 'bubble' && n.length === 1,
69
+ )
70
+ expect(insertions.length).toBeGreaterThan(0)
71
+ expect(insertions.every(n => (n.tags.cl as number) >= 10000)).toBe(true)
72
+ })
73
+
74
+ // Backbone and bubble strictly alternate, so the tier is one connected chain
75
+ // rather than islands, which is what makes it pan-able as a graph.
76
+ test('the tier is a connected alternating chain', async () => {
77
+ const gfa = await makeAdapter().getSubgraph(wholeChromosome)
78
+ const graph = parseGFA(gfa)
79
+ const rank = new Map(graph.nodes.map(n => [n.id, n.tags.ct]))
80
+ expect(graph.links.length).toBe(113)
81
+ expect(
82
+ graph.links.every(l => rank.get(l.source) !== rank.get(l.target)),
83
+ ).toBe(true)
84
+ })