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,110 @@
1
+ import { orientToReference } from './orientToReference'
2
+
3
+ import type { Graph, NodeSegment } from '../types'
4
+
5
+ const stable = (start: number, rank: number) => ({
6
+ refName: rank === 0 ? 'chr1' : 'alt',
7
+ start,
8
+ rank,
9
+ })
10
+
11
+ const graph: Graph = {
12
+ name: 'g',
13
+ nodes: [
14
+ { id: 'b0+', name: 'b0', length: 10, depth: 1, stable: stable(0, 0) },
15
+ { id: 'b1+', name: 'b1', length: 10, depth: 1, stable: stable(10, 0) },
16
+ { id: 'b2+', name: 'b2', length: 10, depth: 1, stable: stable(20, 0) },
17
+ { id: 'b3+', name: 'b3', length: 10, depth: 1, stable: stable(30, 0) },
18
+ { id: 'a0+', name: 'a0', length: 4, depth: 1, stable: stable(10, 1) },
19
+ ],
20
+ edges: [],
21
+ }
22
+
23
+ const segment = (x: number, y: number): NodeSegment[] => [
24
+ { x, y },
25
+ { x: x + 10, y },
26
+ ]
27
+
28
+ // backbone left to right on y = 0, the allele below it
29
+ const oriented: Record<string, NodeSegment[]> = {
30
+ 'b0+': segment(0, 0),
31
+ 'b1+': segment(20, 0),
32
+ 'b2+': segment(40, 0),
33
+ 'b3+': segment(60, 0),
34
+ 'a0+': segment(25, 12),
35
+ }
36
+
37
+ function transform(
38
+ positions: Record<string, NodeSegment[]>,
39
+ angle: number,
40
+ reflect: boolean,
41
+ dx: number,
42
+ dy: number,
43
+ ) {
44
+ const c = Math.cos(angle)
45
+ const s = Math.sin(angle)
46
+ return Object.fromEntries(
47
+ Object.entries(positions).map(([id, segs]) => [
48
+ id,
49
+ segs.map(p => {
50
+ const y = reflect ? -p.y : p.y
51
+ return { x: c * p.x - s * y + dx, y: s * p.x + c * y + dy }
52
+ }),
53
+ ]),
54
+ )
55
+ }
56
+
57
+ const mid = (segs: NodeSegment[]) => segs[Math.floor(segs.length / 2)]!
58
+
59
+ function distance(a: NodeSegment, b: NodeSegment) {
60
+ return Math.hypot(a.x - b.x, a.y - b.y)
61
+ }
62
+
63
+ test('a rotated and reflected drawing comes back reading left to right', () => {
64
+ const scrambled = transform(oriented, 2.3, true, 100, -50)
65
+ const out = orientToReference(graph, scrambled)
66
+ const xs = ['b0+', 'b1+', 'b2+', 'b3+'].map(id => mid(out[id]!).x)
67
+ for (let i = 1; i < xs.length; i++) {
68
+ expect(xs[i]!).toBeGreaterThan(xs[i - 1]!)
69
+ }
70
+ const backboneY = mid(out['b1+']!).y
71
+ for (const p of out['a0+']!) {
72
+ expect(p.y).toBeGreaterThan(backboneY)
73
+ }
74
+ })
75
+
76
+ test('the transform is rigid: pairwise distances survive', () => {
77
+ const scrambled = transform(oriented, -1.1, false, -30, 7)
78
+ const out = orientToReference(graph, scrambled)
79
+ const ids = Object.keys(oriented)
80
+ for (const a of ids) {
81
+ for (const b of ids) {
82
+ expect(distance(out[a]![0]!, out[b]![1]!)).toBeCloseTo(
83
+ distance(scrambled[a]![0]!, scrambled[b]![1]!),
84
+ 9,
85
+ )
86
+ }
87
+ }
88
+ })
89
+
90
+ test('an oriented drawing is returned where it was', () => {
91
+ const out = orientToReference(graph, oriented)
92
+ for (const [id, segs] of Object.entries(oriented)) {
93
+ segs.forEach((p, i) => {
94
+ expect(out[id]![i]!.x).toBeCloseTo(p.x, 9)
95
+ expect(out[id]![i]!.y).toBeCloseTo(p.y, 9)
96
+ })
97
+ }
98
+ })
99
+
100
+ test('alleles above the axis are reflected below it', () => {
101
+ const above = transform(oriented, 0, true, 0, 0)
102
+ const out = orientToReference(graph, above)
103
+ expect(mid(out['a0+']!).y).toBeGreaterThan(mid(out['b1+']!).y)
104
+ expect(mid(out['b3+']!).x).toBeGreaterThan(mid(out['b0+']!).x)
105
+ })
106
+
107
+ test('fewer than two placed backbone nodes leaves the drawing alone', () => {
108
+ const positions = { 'b0+': segment(3, 4), 'a0+': segment(9, -2) }
109
+ expect(orientToReference(graph, positions)).toBe(positions)
110
+ })
@@ -0,0 +1,79 @@
1
+ import { isBackbone } from '../anchoredNodes'
2
+
3
+ import type { Graph, NodeSegment } from '../types'
4
+
5
+ type Positions = Record<string, NodeSegment[]>
6
+
7
+ // Turn a finished force layout so the reference runs left to right: the
8
+ // rotation that best maps backbone midpoints onto their reference coordinate
9
+ // (a one-dimensional orthogonal Procrustes fit, weighted by segment length),
10
+ // then a reflection so the off-reference mass sits below the axis. A rigid
11
+ // transform, so the drawing FMMM made is unchanged; only its frame moves.
12
+ //
13
+ // About the length-weighted centroid, and translated back, so a drawing that is
14
+ // already oriented comes back where it was rather than recentred on the origin.
15
+ export function orientToReference(graph: Graph, positions: Positions) {
16
+ const backbone = graph.nodes.filter(
17
+ node => isBackbone(node) && (positions[node.id]?.length ?? 0) > 0,
18
+ )
19
+ if (backbone.length < 2) {
20
+ return positions
21
+ }
22
+ const points = backbone.map(node => {
23
+ const segments = positions[node.id]!
24
+ const mid = segments[Math.floor(segments.length / 2)]!
25
+ return {
26
+ x: mid.x,
27
+ y: mid.y,
28
+ r: node.stable!.start + node.length / 2,
29
+ w: node.length,
30
+ }
31
+ })
32
+ const weight = points.reduce((sum, p) => sum + p.w, 0)
33
+ const cx = points.reduce((sum, p) => sum + p.x * p.w, 0) / weight
34
+ const cy = points.reduce((sum, p) => sum + p.y * p.w, 0) / weight
35
+ const cr = points.reduce((sum, p) => sum + p.r * p.w, 0) / weight
36
+ // cross-covariance of (x, y) against (r, 0); only the first row is non-zero
37
+ let sxr = 0
38
+ let syr = 0
39
+ for (const p of points) {
40
+ sxr += p.w * (p.x - cx) * (p.r - cr)
41
+ syr += p.w * (p.y - cy) * (p.r - cr)
42
+ }
43
+ // the rotation taking the direction (sxr, syr) onto +x
44
+ const angle = -Math.atan2(syr, sxr)
45
+ const c = Math.cos(angle)
46
+ const s = Math.sin(angle)
47
+
48
+ const out: Positions = {}
49
+ for (const [id, segments] of Object.entries(positions)) {
50
+ out[id] = segments.map(p => {
51
+ const x = p.x - cx
52
+ const y = p.y - cy
53
+ return { x: cx + c * x - s * y, y: cy + s * x + c * y }
54
+ })
55
+ }
56
+
57
+ let below = 0
58
+ let above = 0
59
+ for (const node of graph.nodes) {
60
+ if (isBackbone(node)) {
61
+ continue
62
+ }
63
+ for (const p of out[node.id] ?? []) {
64
+ if (p.y > cy) {
65
+ below++
66
+ } else {
67
+ above++
68
+ }
69
+ }
70
+ }
71
+ if (above > below) {
72
+ for (const segments of Object.values(out)) {
73
+ for (const p of segments) {
74
+ p.y = 2 * cy - p.y
75
+ }
76
+ }
77
+ }
78
+ return out
79
+ }
@@ -0,0 +1,147 @@
1
+ import { projectAlleles } from '../../alleleProjection/projectAlleles'
2
+ import { buildNeighbors } from '../referenceSpan'
3
+
4
+ import type { AlleleDeletion, Graph, NodeSegment } from '../types'
5
+
6
+ // Where off-reference segments go on a reference x axis. Shared by the two
7
+ // row-structured layouts, which differ only in what a row means (stable rank
8
+ // vs contributing assembly); how far along the reference a segment reaches is
9
+ // the same question in both, and answering it twice is how they drifted.
10
+ //
11
+ // The rule: a segment occupies the reference its allele replaces, never its own
12
+ // sequence length. x is reference bp — the axis the backbone declares and the
13
+ // linear view beside it is drawn on — so an insertion, which consumes no
14
+ // reference, must not advance along it. Both layouts used to advance by
15
+ // sequence length, which made every off-reference x a false statement about
16
+ // reference position and, on a 1 kb window holding a 100 kb allele, drew 101x
17
+ // the window so that zoom-to-fit rendered the reference at 1% of the frame.
18
+ //
19
+ // Magnitude is the cost. An axis of reference coordinates has no way to state
20
+ // how much sequence an insertion adds, so it is not stated here: it is in the
21
+ // node tooltip, and drawn to scale in the allele-inventory track, where a CIGAR
22
+ // gives the alignments display a channel for length that is not the x axis.
23
+
24
+ // Reference bp an allele has to skip before the drawing calls it a deletion:
25
+ // the 1 bp floor `deletionEdges` uses, a guard against abutting anchors' zero
26
+ // gap rather than a biological threshold. Whether a small one is drawn is
27
+ // decided by its label's fit in graphLabels.
28
+ const MIN_DELETION_BP = 1
29
+
30
+ // Floor on an off-reference node's drawn span, as a fraction of the window.
31
+ // Node thickness is a constant number of screen pixels, so a segment on a
32
+ // sub-percent slice of the window draws wider than it is long and reads as a
33
+ // dot on a stalk, and a pure insertion occupies no reference at all. Rank-0
34
+ // nodes keep their exact offsets, so the reference axis is untouched.
35
+ const MIN_ALLELE_SPAN_FRACTION = 0.015
36
+
37
+ interface PlaceArgs {
38
+ graph: Graph
39
+ // the reference interval the drawing is scaled against (referenceSpan)
40
+ span: number
41
+ // y for a segment, which is the whole of what the two layouts disagree about
42
+ rowY: (node: Graph['nodes'][number]) => number
43
+ // the backbone, already placed at its declared offsets. Mutated in place, and
44
+ // read for the branch points the unanchored walk below starts from.
45
+ positions: Record<string, NodeSegment[]>
46
+ }
47
+
48
+ export function placeOffReference({
49
+ graph,
50
+ span,
51
+ rowY,
52
+ positions,
53
+ }: PlaceArgs): AlleleDeletion[] {
54
+ const minSpan = span * MIN_ALLELE_SPAN_FRACTION
55
+ const alleles = projectAlleles(graph)
56
+ const byId = new Map(graph.nodes.map(n => [n.id, n]))
57
+ // Alleles standing in for more reference than they carry. Collected here
58
+ // rather than derived by the label pass, because the reason they need saying
59
+ // is the placement done in this function: a run drawn over the reference it
60
+ // replaces has a width that is not its length, and nothing downstream can
61
+ // tell that from a node drawn at its own scale. Same measure the deletion
62
+ // arcs use — reference bp a haplotype does not carry — so the two kinds of
63
+ // deletion in a drawing are stated in the same units and the same words.
64
+ const alleleDeletions: AlleleDeletion[] = []
65
+
66
+ for (const allele of alleles) {
67
+ const skipped = allele.refSpan - allele.pathLength
68
+ if (skipped >= MIN_DELETION_BP) {
69
+ alleleDeletions.push({ nodeIds: allele.nodeIds, bp: skipped })
70
+ }
71
+ const drawn = Math.max(allele.refSpan, minSpan)
72
+ // A node sits where it sits *within* the allele: its bp offset from the
73
+ // entry, scaled so the run's longest path fills the reference the allele
74
+ // replaces. So a bubble path occupies the reference exactly once however
75
+ // many segments it is built from, and two parallel branches share the x
76
+ // range rather than queue up after each other — they are alternatives over
77
+ // the same interval, not consecutive sequence.
78
+ //
79
+ // Offsets are measured over the graph rather than over nodeIds' order (see
80
+ // runOffsets). Concatenating in traversal order put graph neighbours 26 kb
81
+ // apart on a 27 kb allele, and the edges spanning those gaps crossed as a
82
+ // long X. A run of all zero-length segments has no distances to scale by;
83
+ // splitting evenly beats 0/0, which wrote NaN into every position after it.
84
+ //
85
+ // Where the floor widens a run past the reference it replaces, the overhang
86
+ // hangs off the right, so the run can end past the rank-0 segment it
87
+ // rejoins and its exit edge runs backwards. That is handled where it shows,
88
+ // in computeEdgeCurves, which sizes each control point by how far the other
89
+ // endpoint lies along its own tangent — a backwards edge gets a straight
90
+ // leader rather than the loop that read as a crossed bowtie.
91
+ const scale = allele.pathLength > 0 ? drawn / allele.pathLength : 0
92
+ allele.nodeIds.forEach((nodeId, i) => {
93
+ const node = byId.get(nodeId)
94
+ if (node) {
95
+ const offset = allele.nodeOffsets[i]!
96
+ const even = drawn / allele.nodeIds.length
97
+ // both ends from the offset rather than start-plus-width, so two nodes
98
+ // the sweep placed end to end land on exactly the same x instead of on
99
+ // two roundings of it
100
+ const at = (bp: number) => allele.start + bp * scale
101
+ const y = rowY(node)
102
+ positions[nodeId] = scale
103
+ ? [
104
+ { x: at(offset), y },
105
+ { x: at(offset + node.length), y },
106
+ ]
107
+ : [
108
+ { x: allele.start + i * even, y },
109
+ { x: allele.start + (i + 1) * even, y },
110
+ ]
111
+ }
112
+ })
113
+ }
114
+
115
+ // A run that reaches the edge of the fetched subgraph has only one backbone
116
+ // anchor, so no reference span can be stated for it. That is the normal case
117
+ // at a window boundary, not an error, and dropping it is indistinguishable
118
+ // from the layout being broken — the sample-rows layout did drop them.
119
+ //
120
+ // They get the floor each, chained from wherever they branch: the only honest
121
+ // claim available is "attaches here and continues past the edge", and a floor
122
+ // keeps a chain of them bounded by the window instead of by their sequence.
123
+ // The same undirected adjacency the reference walk builds, from the one
124
+ // function that builds it. The copy here rebuilt each list with a spread per
125
+ // edge, which is quadratic in a node's degree and is the shape a collapsed
126
+ // repeat's hub node is worst at.
127
+ const neighbors = buildNeighbors(graph)
128
+ // Read forward while appending, so this is a BFS with an O(1) dequeue rather
129
+ // than a quadratic shift() over what ends up being every node in the run.
130
+ const queue = Object.keys(positions)
131
+ for (const fromId of queue) {
132
+ const fromEnd = positions[fromId]!.at(-1)!.x
133
+ for (const nextId of neighbors.get(fromId) ?? []) {
134
+ const next = byId.get(nextId)
135
+ if (next && !(nextId in positions)) {
136
+ const y = rowY(next)
137
+ positions[nextId] = [
138
+ { x: fromEnd, y },
139
+ { x: fromEnd + minSpan, y },
140
+ ]
141
+ queue.push(nextId)
142
+ }
143
+ }
144
+ }
145
+
146
+ return alleleDeletions
147
+ }
@@ -0,0 +1,79 @@
1
+ import { drawnNodeLength, layoutScaling } from './drawnScale'
2
+ import { referenceSeeds, seededNodes } from './referenceSeeds'
3
+ import { parseGFA } from '../../gfa-core/index'
4
+ import { spreadFor } from '../bubbleSpreads'
5
+ import { convertGFAToGraph } from '../gfa/gfaConverter'
6
+
7
+ // chr1 runs v1->v2->v3->v4; a1 replaces v3 and hangs a2 off itself, which no
8
+ // backbone node touches; lone is linked to nothing. Lengths span 1 bp to 7 kb so
9
+ // a compressing spread moves the spacing by more than rounding.
10
+ const GFA = `S\tv1\t${'A'.repeat(5000)}\tSN:Z:chr1\tSO:i:0\tSR:i:0
11
+ S\tv2\tCCC\tSN:Z:chr1\tSO:i:5000\tSR:i:0
12
+ S\tv3\tGG\tSN:Z:chr1\tSO:i:5003\tSR:i:0
13
+ S\tv4\t${'T'.repeat(7000)}\tSN:Z:chr1\tSO:i:5005\tSR:i:0
14
+ S\ta1\tAC\tSN:Z:foo\tSO:i:8\tSR:i:1
15
+ S\ta2\tT\tSN:Z:bar\tSO:i:8\tSR:i:2
16
+ S\tlone\tGATTACA\tSN:Z:baz\tSO:i:0\tSR:i:1
17
+ L\tv1\t+\tv2\t+\t0M
18
+ L\tv2\t+\tv3\t+\t0M
19
+ L\tv3\t+\tv4\t+\t0M
20
+ L\tv2\t+\ta1\t+\t0M
21
+ L\ta1\t+\tv4\t+\t0M
22
+ L\ta1\t+\ta2\t+\t0M`
23
+
24
+ const graph = convertGFAToGraph(parseGFA(GFA))
25
+ const BACKBONE = ['v1+', 'v2+', 'v3+', 'v4+']
26
+
27
+ test('the backbone runs along x, each node one drawn length and one edge on', () => {
28
+ const scaling = layoutScaling(graph)
29
+ const seeds = referenceSeeds(graph, scaling)
30
+ let x = 0
31
+ for (const id of BACKBONE) {
32
+ expect(seeds.get(id)).toEqual({ x, y: 0 })
33
+ const node = graph.nodes.find(n => n.id === id)!
34
+ x += drawnNodeLength(scaling.opts, node.length) + scaling.opts.edgeLength
35
+ }
36
+ })
37
+
38
+ test('an allele sits at its anchor midpoint one lane down, a lane more per step', () => {
39
+ const scaling = layoutScaling(graph)
40
+ const seeds = referenceSeeds(graph, scaling)
41
+ const v2 = graph.nodes.find(n => n.id === 'v2+')!
42
+ const a1 = graph.nodes.find(n => n.id === 'a1+')!
43
+ const anchor = seeds.get('v2+')!
44
+ expect(seeds.get('a1+')).toEqual({
45
+ x: anchor.x + drawnNodeLength(scaling.opts, v2.length) / 2,
46
+ y: 30,
47
+ })
48
+ expect(seeds.get('a2+')).toEqual({
49
+ x: seeds.get('a1+')!.x + drawnNodeLength(scaling.opts, a1.length) / 2,
50
+ y: 60,
51
+ })
52
+ })
53
+
54
+ test('a node no backbone reaches goes below everything', () => {
55
+ const seeds = referenceSeeds(graph, layoutScaling(graph))
56
+ expect(seeds.get('lone+')).toEqual({ x: 0, y: 90 })
57
+ expect(seeds.size).toBe(graph.nodes.length)
58
+ })
59
+
60
+ test('spacing follows the scaled lengths the engine is handed, not bp', () => {
61
+ const compressed = layoutScaling(graph, spreadFor('compress'))
62
+ const seeds = referenceSeeds(graph, compressed)
63
+ const v1 = compressed.nodes.find(n => n.id === 'v1+')!
64
+ expect(v1.length).not.toBe(5000)
65
+ expect(seeds.get('v2+')!.x).toBe(
66
+ drawnNodeLength(compressed.opts, v1.length) + compressed.opts.edgeLength,
67
+ )
68
+ const proportional = referenceSeeds(graph, layoutScaling(graph))
69
+ expect(seeds.get('v2+')!.x).not.toBe(proportional.get('v2+')!.x)
70
+ })
71
+
72
+ test('seededNodes carries every scaled node with its seed', () => {
73
+ const scaling = layoutScaling(graph, spreadFor('compress'))
74
+ const nodes = seededNodes(graph, scaling)
75
+ expect(nodes.map(n => n.length)).toEqual(scaling.nodes.map(n => n.length))
76
+ expect(nodes.every(n => Number.isFinite(n.x) && Number.isFinite(n.y))).toBe(
77
+ true,
78
+ )
79
+ })
@@ -0,0 +1,89 @@
1
+ import { isBackbone } from '../anchoredNodes'
2
+ import { buildNeighbors } from '../referenceSpan'
3
+ import { drawnNodeLength } from './drawnScale'
4
+
5
+ import type { LayoutScaling } from './drawnScale'
6
+ import type { Graph, GraphNode } from '../types'
7
+
8
+ // A node as the engine takes it: an optional `x`/`y` is where FMMM starts the
9
+ // node's chain (`addToOgdfGraph`, graphlayout.cpp).
10
+ export type LayoutNode = GraphNode & { x?: number; y?: number }
11
+
12
+ export interface Seed {
13
+ x: number
14
+ y: number
15
+ }
16
+
17
+ // y per BFS step off the backbone. FMMM keeps only the coarse shape of these
18
+ // (OGDF applies the initial placement at its coarsest multilevel graph), so
19
+ // the gap needs to say "below the line", not to place a lane.
20
+ const LANE_GAP = 30
21
+
22
+ // Initial positions for a reference-anchored graph: the backbone end to end
23
+ // along x at drawn length, each off-reference node at the midpoint of the node
24
+ // a BFS from the backbone reached it from, a lane lower per step. Two alleles
25
+ // off one anchor share a seed; FMMM separates them. Nodes no backbone reaches
26
+ // spread along x below everything rather than piling on one point.
27
+ //
28
+ // Lengths come from `scaling.nodes`, not the graph's bp: under a compressing
29
+ // spread a node's `length` crosses the RPC as drawn units times 1000 with a
30
+ // matching `nodeLengthPerMegabase`, and a seed from bp would misplace every
31
+ // chain against the one the engine builds.
32
+ export function referenceSeeds(
33
+ graph: Graph,
34
+ scaling: LayoutScaling,
35
+ ): Map<string, Seed> {
36
+ const { opts } = scaling
37
+ const drawn = new Map(
38
+ scaling.nodes.map(node => [node.id, drawnNodeLength(opts, node.length)]),
39
+ )
40
+ const drawnOf = (id: string) => drawn.get(id) ?? opts.minimumNodeLength
41
+ const backbone = graph.nodes
42
+ .filter(isBackbone)
43
+ .sort((a, b) => a.stable.start - b.stable.start)
44
+
45
+ const seeds = new Map<string, Seed>()
46
+ let x = 0
47
+ for (const node of backbone) {
48
+ seeds.set(node.id, { x, y: 0 })
49
+ x += drawnOf(node.id) + opts.edgeLength
50
+ }
51
+
52
+ const adjacent = buildNeighbors(graph)
53
+
54
+ const depth = new Map(backbone.map(node => [node.id, 0]))
55
+ // for-of over an array that grows as it goes: the iterator reads the live
56
+ // length, so nodes pushed here are visited
57
+ const queue = backbone.map(node => node.id)
58
+ for (const id of queue) {
59
+ const at = seeds.get(id)!
60
+ const next = depth.get(id)! + 1
61
+ for (const neighbour of adjacent.get(id) ?? []) {
62
+ if (depth.has(neighbour)) {
63
+ continue
64
+ }
65
+ depth.set(neighbour, next)
66
+ seeds.set(neighbour, { x: at.x + drawnOf(id) / 2, y: LANE_GAP * next })
67
+ queue.push(neighbour)
68
+ }
69
+ }
70
+
71
+ let unreached = 0
72
+ for (const node of graph.nodes) {
73
+ if (!seeds.has(node.id)) {
74
+ seeds.set(node.id, {
75
+ x: (unreached++ * x) / graph.nodes.length,
76
+ y: LANE_GAP * 3,
77
+ })
78
+ }
79
+ }
80
+ return seeds
81
+ }
82
+
83
+ export function seededNodes(
84
+ graph: Graph,
85
+ scaling: LayoutScaling,
86
+ ): LayoutNode[] {
87
+ const seeds = referenceSeeds(graph, scaling)
88
+ return scaling.nodes.map(node => ({ ...node, ...seeds.get(node.id) }))
89
+ }
@@ -0,0 +1,5 @@
1
+ // Row pitch of the row layouts, in screen pixels rather than bp: a row holds a
2
+ // 10 px tube and an 11 px label whatever the window spans, so y is never
3
+ // scaled (LayoutResult.pixelRows) and rows past the pane are reached by panning.
4
+ // 20 px is the floor at which labels stop touching.
5
+ export const ROW_HEIGHT_PX = 20