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,178 @@
1
+ import { existsSync, readFileSync } from 'fs'
2
+
3
+ import { walkRows } from './walkRows'
4
+ import { parseGFA } from '../../gfa-core/index'
5
+ import { convertGFAToGraph } from '../gfa/gfaConverter'
6
+ import { anchorGraph, pathOrigin } from '../pathAnchoring'
7
+
8
+ function pggbGraph() {
9
+ const gfa = readFileSync(
10
+ require.resolve('../../../test_data/ecoli_pggb_subgraph.gfa'),
11
+ 'utf8',
12
+ )
13
+ return anchorGraph(convertGFAToGraph(parseGFA(gfa), 'pggb'), 'K12')
14
+ }
15
+
16
+ const KIV2 = '.test-jbrowse-variants/test_data/graphgenomeview/kiv2_eight.gfa'
17
+
18
+ test('one row per non-reference path, longest first, bp summed over its nodes', () => {
19
+ const graph = pggbGraph()
20
+ const result = walkRows(graph)!
21
+ expect(result.reference.label).toBe('K12')
22
+ expect(result.rows.map(r => r.label).sort()).toEqual([
23
+ 'CFT073',
24
+ 'IAI39',
25
+ 'NCTC86',
26
+ 'Sakai',
27
+ ])
28
+ for (let i = 1; i < result.rows.length; i++) {
29
+ expect(result.rows[i - 1]!.bp).toBeGreaterThanOrEqual(result.rows[i]!.bp)
30
+ }
31
+ const byId = new Map(graph.nodes.map(n => [n.id, n.length]))
32
+ for (const row of [result.reference, ...result.rows]) {
33
+ const path = graph.paths!.find(p => p.name === row.name)!
34
+ const total = path.nodeIds.reduce((s, id) => s + byId.get(id)!, 0)
35
+ expect(row.bp).toBe(total)
36
+ expect(row.runs.reduce((s, r) => s + r.bp, 0)).toBe(total)
37
+ expect(row.complete).toBe(true)
38
+ }
39
+ expect(result.reference.offReferenceBp).toBe(0)
40
+ })
41
+
42
+ test('runs alternate and cover the row without gaps', () => {
43
+ const { rows } = walkRows(pggbGraph())!
44
+ for (const row of rows) {
45
+ let pos = 0
46
+ row.runs.forEach((run, i) => {
47
+ expect(run.start).toBe(pos)
48
+ if (i > 0) {
49
+ expect(run.onReference).not.toBe(row.runs[i - 1]!.onReference)
50
+ }
51
+ pos += run.bp
52
+ })
53
+ }
54
+ })
55
+
56
+ test('a region measures between its flanking reference nodes', () => {
57
+ const graph = pggbGraph()
58
+ const byId = new Map(graph.nodes.map(n => [n.id, n.length]))
59
+ const reference = graph.paths!.find(
60
+ p => pathOrigin(p.name).name === graph.referencePath,
61
+ )!
62
+ const start = graph.anchorPaths!.find(
63
+ p => p.name === graph.referencePath,
64
+ )!.start
65
+ const ends = [start]
66
+ for (const id of reference.nodeIds) {
67
+ ends.push(ends.at(-1)! + byId.get(id)!)
68
+ }
69
+ const n = reference.nodeIds.length
70
+ // flanks are the 2nd node and the 2nd-to-last, so the slice is nodes 2..n-3
71
+ const region = { start: ends[2]!, end: ends[n - 2]! }
72
+ const cut = walkRows(graph, region)!
73
+ expect(cut.origin).toBe(region.start)
74
+ expect(cut.reference.bp).toBe(ends[n - 2]! - ends[2]!)
75
+ expect(cut.reference.complete).toBe(true)
76
+ const whole = walkRows(graph)!
77
+ expect(cut.reference.bp).toBeLessThan(whole.reference.bp)
78
+ })
79
+
80
+ test('a walk that skips the flanking node is cut at the next one it visits', () => {
81
+ const graph = pggbGraph()
82
+ const byId = new Map(graph.nodes.map(n => [n.id, n.length]))
83
+ const reference = graph.paths!.find(
84
+ p => pathOrigin(p.name).name === graph.referencePath,
85
+ )!
86
+ const start = graph.anchorPaths!.find(
87
+ p => p.name === graph.referencePath,
88
+ )!.start
89
+ const ends = [start]
90
+ for (const id of reference.nodeIds) {
91
+ ends.push(ends.at(-1)! + byId.get(id)!)
92
+ }
93
+ const n = reference.nodeIds.length
94
+ const region = { start: ends[3]!, end: ends[n - 3]! }
95
+ const skipper = {
96
+ ...reference,
97
+ name: 'skipper',
98
+ nodeIds: reference.nodeIds.filter((_, i) => i !== 2 && i !== n - 3),
99
+ }
100
+ const cut = walkRows({ ...graph, paths: [...graph.paths!, skipper] }, region)!
101
+ const row = cut.rows.find(r => r.name === 'skipper')!
102
+ expect(row.complete).toBe(true)
103
+ expect(row.bp).toBe(cut.reference.bp)
104
+ })
105
+
106
+ // The numbers scripts/layout-lab/copycount.mjs reports for the hosted
107
+ // eight-haplotype KIV-2 cut: GRCh38 about 33 kb through the window, HG00133
108
+ // about 149 kb, so HG00133 carries roughly 21 more units of 5,548 bp.
109
+ test.skipIf(!existsSync(KIV2))(
110
+ 'KIV-2: HG00133 carries ~116 kb GRCh38 does not',
111
+ () => {
112
+ const graph = anchorGraph(
113
+ convertGFAToGraph(parseGFA(readFileSync(KIV2, 'utf8')), 'kiv2'),
114
+ 'GRCh38',
115
+ )
116
+ const result = walkRows(graph)!
117
+ expect(result.reference.label).toBe('GRCh38#0')
118
+ expect(result.reference.bp).toBeGreaterThan(32_000)
119
+ expect(result.reference.bp).toBeLessThan(34_000)
120
+ expect(result.rows[0]!.label).toBe('HG00133#1')
121
+ expect(result.rows[0]!.bp).toBeGreaterThan(145_000)
122
+ expect(result.rows[0]!.offReferenceBp / 5548).toBeGreaterThan(19)
123
+ expect(result.rows.map(r => r.label)).toHaveLength(8)
124
+ },
125
+ )
126
+
127
+ test('a cut that stops at the window leaves whole walks, not partial ones', () => {
128
+ const graph = pggbGraph()
129
+ const byId = new Map(graph.nodes.map(n => [n.id, n.length]))
130
+ const reference = graph.paths!.find(
131
+ p => pathOrigin(p.name).name === graph.referencePath,
132
+ )!
133
+ const start = graph.anchorPaths!.find(
134
+ p => p.name === graph.referencePath,
135
+ )!.start
136
+ let end = start
137
+ for (const id of reference.nodeIds) {
138
+ end += byId.get(id)!
139
+ }
140
+ // the region IS the reference walk, so no node lies outside it
141
+ const rows = walkRows(graph, { start, end })!
142
+ expect(rows.rows.every(r => r.complete)).toBe(true)
143
+ expect(rows.reference.complete).toBe(true)
144
+ })
145
+
146
+ test('a walk that crosses the window backwards reads in reference direction', () => {
147
+ const graph = pggbGraph()
148
+ const byId = new Map(graph.nodes.map(n => [n.id, n.length]))
149
+ const reference = graph.paths!.find(
150
+ p => pathOrigin(p.name).name === graph.referencePath,
151
+ )!
152
+ const start = graph.anchorPaths!.find(
153
+ p => p.name === graph.referencePath,
154
+ )!.start
155
+ const ends = [start]
156
+ for (const id of reference.nodeIds) {
157
+ ends.push(ends.at(-1)! + byId.get(id)!)
158
+ }
159
+ const n = reference.nodeIds.length
160
+ const region = { start: ends[2]!, end: ends[n - 2]! }
161
+ const forward = walkRows(graph, region)!.rows.find(
162
+ r => r.complete && r.runs.length > 1,
163
+ )!
164
+ expect(forward).toBeDefined()
165
+ const path = graph.paths!.find(p => p.name === forward.name)!
166
+ const backwards = {
167
+ ...path,
168
+ name: 'backwards',
169
+ nodeIds: [...path.nodeIds].reverse(),
170
+ }
171
+ const cut = walkRows(
172
+ { ...graph, paths: [...graph.paths!, backwards] },
173
+ region,
174
+ )!
175
+ const row = cut.rows.find(r => r.name === 'backwards')!
176
+ expect(row.complete).toBe(true)
177
+ expect(row.runs).toEqual(forward.runs)
178
+ })
@@ -0,0 +1,177 @@
1
+ import { panSNSample } from '../../pansn'
2
+ import { pathOrigin } from '../pathAnchoring'
3
+
4
+ import type { Graph, GraphPath } from '../types'
5
+
6
+ // Each haplotype walk on its own bp axis, so what a walk carries through the
7
+ // window is its bar length. Reference-anchored rows cannot state that: a
8
+ // haplotype's private copies of a repeat consume no reference and collapse to
9
+ // a mark, which is why the KIV-2 array reads as a knot in every node layout
10
+ // and as a ladder here.
11
+ //
12
+ // A base-level graph does not revisit reference nodes through a repeat array,
13
+ // so a copy count is not a visit count; it is the sequence a walk spends
14
+ // between the reference nodes flanking the window, divided by the unit. Runs
15
+ // distinguish sequence the reference walk also carries from sequence it does
16
+ // not, so an expansion is the purple stretch of a row.
17
+
18
+ export interface WalkRun {
19
+ // bp offset from the start of this walk's slice
20
+ start: number
21
+ bp: number
22
+ onReference: boolean
23
+ }
24
+
25
+ export interface WalkRow {
26
+ name: string
27
+ label: string
28
+ sample: string
29
+ bp: number
30
+ offReferenceBp: number
31
+ // false when the walk does not reach both flanking reference nodes, in which
32
+ // case the whole walk is measured and the bar says so
33
+ complete: boolean
34
+ runs: WalkRun[]
35
+ }
36
+
37
+ export interface WalkRows {
38
+ // reference bp the rows' bars start at, so a row's x is origin + run.start
39
+ origin: number
40
+ // repeat unit in bp when a repeat annotation supplied one; the bars tile by it
41
+ unit?: number
42
+ reference: WalkRow
43
+ // every other walk, longest first
44
+ rows: WalkRow[]
45
+ }
46
+
47
+ function sampleOf(path: GraphPath) {
48
+ return path.sample ?? panSNSample(path.name)
49
+ }
50
+
51
+ function labelOf(path: GraphPath) {
52
+ return path.haplotype !== undefined && path.sample !== undefined
53
+ ? `${path.sample}#${path.haplotype}`
54
+ : sampleOf(path)
55
+ }
56
+
57
+ // Each walk is cut at the nearest reference nodes IT visits on either side of
58
+ // the region, so a walk that skips one flanking node at a SNP is still measured
59
+ // between flanks rather than whole.
60
+ function sliceBetween(
61
+ nodeIds: string[],
62
+ span: Map<string, { start: number; end: number }>,
63
+ region: { start: number; end: number } | undefined,
64
+ flanked = true,
65
+ ) {
66
+ // A cut that stops at the window carries no flanking reference for anyone,
67
+ // so every walk is whole and the slice is the walk.
68
+ if (!region || !flanked) {
69
+ return { ids: nodeIds, complete: true }
70
+ }
71
+ let i0 = -1
72
+ let i1 = -1
73
+ let bestEnd = -Infinity
74
+ let bestStart = Infinity
75
+ nodeIds.forEach((id, i) => {
76
+ const s = span.get(id)
77
+ if (s) {
78
+ if (s.end <= region.start && s.end > bestEnd) {
79
+ bestEnd = s.end
80
+ i0 = i
81
+ }
82
+ if (s.start >= region.end && s.start < bestStart) {
83
+ bestStart = s.start
84
+ i1 = i
85
+ }
86
+ }
87
+ })
88
+ if (i0 < 0 || i1 < 0) {
89
+ return { ids: nodeIds, complete: false }
90
+ }
91
+ const ids = nodeIds.slice(Math.min(i0, i1) + 1, Math.max(i0, i1))
92
+ return { ids: i0 < i1 ? ids : ids.reverse(), complete: true }
93
+ }
94
+
95
+ export function walkRows(
96
+ graph: Graph,
97
+ region?: { start: number; end: number },
98
+ unit?: number,
99
+ ): WalkRows | undefined {
100
+ const paths = graph.paths ?? []
101
+ // `referencePath` is the anchor name, which pathOrigin has already stripped
102
+ // of the range suffix odgi leaves on a P record's name
103
+ const reference =
104
+ paths.find(p => pathOrigin(p.name).name === graph.referencePath) ?? paths[0]
105
+ if (!reference || paths.length < 2) {
106
+ return undefined
107
+ }
108
+ const lengthOf = new Map(graph.nodes.map(n => [n.id, n.length]))
109
+ const onReference = new Set(reference.nodeIds)
110
+ const referenceStart =
111
+ graph.anchorPaths?.find(p => p.name === pathOrigin(reference.name).name)
112
+ ?.start ?? 0
113
+
114
+ const cut = region && region.end > region.start ? region : undefined
115
+ const span = new Map<string, { start: number; end: number }>()
116
+ let pos = referenceStart
117
+ for (const id of reference.nodeIds) {
118
+ const len = lengthOf.get(id) ?? 0
119
+ if (!span.has(id)) {
120
+ span.set(id, { start: pos, end: pos + len })
121
+ }
122
+ pos += len
123
+ }
124
+
125
+ // Whether the reference reaches past the region on both sides, i.e. whether
126
+ // a flanking node exists for any walk to be cut at.
127
+ const flanked =
128
+ cut === undefined ||
129
+ ([...span.values()].some(s => s.end <= cut.start) &&
130
+ [...span.values()].some(s => s.start >= cut.end))
131
+
132
+ const rowOf = (path: GraphPath): WalkRow => {
133
+ const { ids, complete } = sliceBetween(path.nodeIds, span, cut, flanked)
134
+ const runs: WalkRun[] = []
135
+ let bp = 0
136
+ let offReferenceBp = 0
137
+ for (const id of ids) {
138
+ const len = lengthOf.get(id) ?? 0
139
+ const shared = onReference.has(id)
140
+ const last = runs.at(-1)
141
+ if (last?.onReference === shared) {
142
+ last.bp += len
143
+ } else {
144
+ runs.push({ start: bp, bp: len, onReference: shared })
145
+ }
146
+ bp += len
147
+ if (!shared) {
148
+ offReferenceBp += len
149
+ }
150
+ }
151
+ return {
152
+ name: path.name,
153
+ label: labelOf(path),
154
+ sample: sampleOf(path),
155
+ bp,
156
+ offReferenceBp,
157
+ complete,
158
+ runs,
159
+ }
160
+ }
161
+
162
+ const origin = cut ? cut.start : referenceStart
163
+ return {
164
+ origin,
165
+ unit,
166
+ reference: rowOf(reference),
167
+ rows: paths
168
+ .filter(p => p !== reference)
169
+ .map(rowOf)
170
+ .sort(
171
+ (a, b) =>
172
+ Number(b.complete) - Number(a.complete) ||
173
+ b.bp - a.bp ||
174
+ a.label.localeCompare(b.label),
175
+ ),
176
+ }
177
+ }
@@ -0,0 +1,131 @@
1
+ import { isBackbone, isOffReference } from './anchoredNodes'
2
+ import { anchoredLayout } from './layout/anchoredLayout'
3
+ import { orderedLayout } from './layout/orderedLayout'
4
+ import { sampleRowLayout } from './layout/sampleRowLayout'
5
+ import { variantMapLayout } from './layout/variantMapLayout'
6
+ import { walkRowLayout } from './layout/walkRowLayout'
7
+
8
+ import type { Graph, LayoutResult } from './types'
9
+
10
+ // The force layout's menu label. jbrowse-components' check-menu-labels scans
11
+ // this src/ for string literals, so a documented **Layout → …** path is held
12
+ // to what the dropdown renders.
13
+ export const FORCE_LAYOUT_LABEL = 'Force-directed layout'
14
+
15
+ // One table drives the persisted enum, the toolbar dropdown, and the dispatch in
16
+ // the model, so adding a layout is one entry rather than several edits that can
17
+ // disagree.
18
+ //
19
+ // `run` returning undefined means "this mode can't draw this graph" and hands
20
+ // off to the remote Bandage FMMM engine. 'auto' uses it when a GFA has no
21
+ // rank-0 backbone — neither declared in rGFA tags nor derivable from P/W lines
22
+ // (pathAnchoring.ts) — and 'force' is simply the mode that always takes it.
23
+ export interface LayoutMode {
24
+ value: string
25
+ label: string
26
+ description: string
27
+ // `region` is the interval the cut was made for, absent for a whole-file
28
+ // import. The reference-anchored modes scale their rows and their allele
29
+ // floor against it rather than against the backbone they were handed, which
30
+ // a long-range allele's far anchor stretches well past the window
31
+ // (referenceSpan).
32
+ run: (
33
+ graph: Graph,
34
+ region?: { start: number; end: number },
35
+ ) => LayoutResult | undefined
36
+ // whether this mode can draw this graph at all; the dropdown greys out the
37
+ // rest rather than hiding them, so the reason a mode is unavailable stays
38
+ // visible instead of the menu silently changing shape between graphs
39
+ available: (graph: Graph) => boolean
40
+ // whether `run` draws this graph itself, as opposed to handing off to the
41
+ // engine. Not the same question as `available` and not derivable from it:
42
+ // 'force' is available for every graph and draws none of them itself. The two
43
+ // coincide for the anchored modes, which is stated by sharing the predicate
44
+ // rather than by restating it.
45
+ drawsLocally: (graph: Graph) => boolean
46
+ }
47
+
48
+ const hasBackbone = (graph: Graph) => graph.nodes.some(isBackbone)
49
+ const hasWalks = (graph: Graph) =>
50
+ hasBackbone(graph) && (graph.paths?.length ?? 0) > 1
51
+ const hasAlleles = (graph: Graph) =>
52
+ hasBackbone(graph) && graph.nodes.some(isOffReference)
53
+
54
+ // `satisfies` rather than a `: LayoutMode[]` annotation: the annotation widened
55
+ // every `value` to string, which collapsed LayoutModeValue to string and left the
56
+ // persisted enum unable to state which values it accepts.
57
+ export const LAYOUT_MODES = [
58
+ {
59
+ value: 'auto',
60
+ label: 'Anchored',
61
+ description:
62
+ 'x is reference bp, one row per stable rank. Needs rGFA tags or a reference path.',
63
+ run: anchoredLayout,
64
+ available: hasBackbone,
65
+ drawsLocally: hasBackbone,
66
+ },
67
+ {
68
+ value: 'samplerows',
69
+ label: 'Sample rows',
70
+ description:
71
+ 'x is reference bp, one row per contributing assembly. Needs rGFA tags or a reference path.',
72
+ run: sampleRowLayout,
73
+ available: hasAlleles,
74
+ drawsLocally: hasAlleles,
75
+ },
76
+ {
77
+ value: 'walkrows',
78
+ label: 'Walk rows',
79
+ description:
80
+ "x is each walk's own bp: one bar per haplotype, sequence the reference also carries in blue and sequence it does not in purple, so a repeat expansion reads as bar length. Needs W or P lines.",
81
+ run: walkRowLayout,
82
+ available: hasWalks,
83
+ drawsLocally: hasWalks,
84
+ },
85
+ {
86
+ value: 'ordered',
87
+ label: 'Ordered',
88
+ description:
89
+ 'x is reference order, not bp: every node gets room, bubbles read as lenses. Needs rGFA tags or a reference path.',
90
+ run: orderedLayout,
91
+ available: hasBackbone,
92
+ drawsLocally: hasBackbone,
93
+ },
94
+ {
95
+ value: 'variants',
96
+ label: 'Variant map',
97
+ description:
98
+ 'The reference as a line with one typed glyph per bubble, from the bubble index or from the graph itself: SNP, indel, deletion, inversion, repeat array. Click a glyph to open the graph inside it.',
99
+ run: variantMapLayout,
100
+ available: hasBackbone,
101
+ drawsLocally: hasBackbone,
102
+ },
103
+ {
104
+ value: 'force',
105
+ label: FORCE_LAYOUT_LABEL,
106
+ description: 'OGDF FMMM, via the external Bandage engine.',
107
+ run: () => undefined,
108
+ available: () => true,
109
+ drawsLocally: () => false,
110
+ },
111
+ ] as const satisfies readonly LayoutMode[]
112
+
113
+ export type LayoutModeValue = (typeof LAYOUT_MODES)[number]['value']
114
+
115
+ export const LAYOUT_MODE_VALUES = LAYOUT_MODES.map(m => m.value)
116
+
117
+ // Whether the drawing a mode produces for a graph comes from the FMMM engine.
118
+ // The settings that only the engine reads — layout quality, bubble spread —
119
+ // are shown against this, so a control that cannot do anything to the drawing
120
+ // on screen says so instead of appearing to work.
121
+ export function modeUsesLayoutEngine(value: string, graph: Graph) {
122
+ return !layoutModeByValue(value).drawsLocally(graph)
123
+ }
124
+
125
+ export function layoutModeByValue(value: string) {
126
+ // An unknown value can only come from a snapshot written by a build that had
127
+ // a mode this one doesn't. It falls back to 'auto' rather than to the model's
128
+ // own default of 'force', because 'auto' declines any graph it cannot draw and
129
+ // hands off to force anyway, so this is the fallback that covers both.
130
+ return LAYOUT_MODES.find(m => m.value === value) ?? LAYOUT_MODES[0]
131
+ }