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,245 @@
1
+ import stateModelFactory from './model'
2
+
3
+ const mockRpcCall = vi.fn()
4
+ const mockSession = {
5
+ tracks: [] as Record<string, unknown>[],
6
+ rpcManager: { call: mockRpcCall },
7
+ }
8
+
9
+ vi.mock('@jbrowse/core/util', () => ({
10
+ getSession: () => mockSession,
11
+ isSessionModelWithWidgets: () => false,
12
+ parseLocString: () => ({}),
13
+ getEnv: () => ({}),
14
+ useWidthSetter: () => {},
15
+ measureText: () => 0,
16
+ IntervalTree: class {},
17
+ getSnapshot: () => ({}),
18
+ applySnapshot: () => {},
19
+ objectHash: () => '',
20
+ }))
21
+
22
+ vi.mock(import('@jbrowse/core/configuration'), async importOriginal => ({
23
+ ...(await importOriginal()),
24
+ readConfObject: vi.fn(
25
+ (obj: Record<string, unknown>, key: string) => obj[key],
26
+ ),
27
+ }))
28
+
29
+ // pangenome_hprc's segments track once NA20809 haplotype 2 is loaded
30
+ const TRACK = {
31
+ trackId: 'segments',
32
+ assemblyNames: ['hg38', 'NA20809.2'],
33
+ adapter: { type: 'RgfaTabixAdapter' },
34
+ }
35
+
36
+ // A GRCh38 backbone s1, s3 and one NA20809#2 allele between them, which is what
37
+ // the adapter returns for a window on either assembly.
38
+ const GFA = [
39
+ 'H\tVN:Z:1.0',
40
+ 'S\ts1\t*\tLN:i:1000\tSN:Z:GRCh38#0#chr6\tSO:i:32000000\tSR:i:0',
41
+ 'S\ts3\t*\tLN:i:900\tSN:Z:GRCh38#0#chr6\tSO:i:32001100\tSR:i:0',
42
+ 'S\th1\t*\tLN:i:200\tSN:Z:NA20809#2#CM094351.1\tSO:i:31901000\tSR:i:1',
43
+ 'L\ts1\t+\th1\t+\t0M',
44
+ 'L\th1\t+\ts3\t+\t0M',
45
+ ].join('\n')
46
+
47
+ const ON_HG38 = {
48
+ refName: 'chr6',
49
+ assemblyName: 'hg38',
50
+ start: 32_000_000,
51
+ end: 32_002_000,
52
+ }
53
+
54
+ const ON_NA20809 = {
55
+ refName: 'CM094351.1',
56
+ assemblyName: 'NA20809.2',
57
+ start: 31_900_900,
58
+ end: 31_901_300,
59
+ }
60
+
61
+ function createView(snapshot: Record<string, unknown> = {}) {
62
+ return stateModelFactory().create({
63
+ type: 'GraphGenomeView',
64
+ layoutMode: 'auto',
65
+ ...snapshot,
66
+ } as never)
67
+ }
68
+
69
+ async function cut(region: typeof ON_HG38, gfa = GFA) {
70
+ mockRpcCall.mockImplementation((_sid: unknown, method: string) =>
71
+ method === 'GetSubgraph'
72
+ ? Promise.resolve(gfa)
73
+ : Promise.reject(new Error(`Unexpected RPC: ${method}`)),
74
+ )
75
+ const model = createView()
76
+ await model.loadFromTabixSubgraph(TRACK.adapter, region, {
77
+ trackId: TRACK.trackId,
78
+ })
79
+ return model
80
+ }
81
+
82
+ beforeEach(() => {
83
+ mockRpcCall.mockReset()
84
+ mockSession.tracks = [TRACK]
85
+ })
86
+
87
+ test('a cut on the reference draws', async () => {
88
+ const model = await cut(ON_HG38)
89
+ expect(model.error).toBeUndefined()
90
+ expect(model.nodeCount).toBe(3)
91
+ })
92
+
93
+ // Framed on NA20809.2's contig, the anchored layouts, the colour ramp and every
94
+ // highlight would put GRCh38 coordinates on CM094351.1.
95
+ test('a cut on another assembly the track names is refused, naming the reference', async () => {
96
+ const model = await cut(ON_NA20809)
97
+ expect(String(model.error)).toMatch(/cut on its reference, hg38/)
98
+ expect(model.hasGraph).toBe(false)
99
+ expect(mockRpcCall).not.toHaveBeenCalled()
100
+ })
101
+
102
+ test('a cut that finds no segments is an error, not an empty graph', async () => {
103
+ const model = await cut(ON_HG38, 'H\tVN:Z:1.0')
104
+ expect(String(model.error)).toMatch(/No graph segments/)
105
+ expect(model.hasGraph).toBe(false)
106
+ })
107
+
108
+ describe('overlapping cuts', () => {
109
+ const TWO_NODES = [
110
+ 'H\tVN:Z:1.0',
111
+ 'S\ta1\t*\tLN:i:10\tSN:Z:GRCh38#0#chr6\tSO:i:100\tSR:i:0',
112
+ 'S\ta2\t*\tLN:i:10\tSN:Z:GRCh38#0#chr6\tSO:i:110\tSR:i:0',
113
+ 'L\ta1\t+\ta2\t+\t0M',
114
+ ].join('\n')
115
+ const THREE_NODES = [
116
+ 'H\tVN:Z:1.0',
117
+ 'S\tb1\t*\tLN:i:10\tSN:Z:GRCh38#0#chr6\tSO:i:100\tSR:i:0',
118
+ 'S\tb2\t*\tLN:i:10\tSN:Z:GRCh38#0#chr6\tSO:i:110\tSR:i:0',
119
+ 'S\tb3\t*\tLN:i:10\tSN:Z:GRCh38#0#chr6\tSO:i:120\tSR:i:0',
120
+ 'L\tb1\t+\tb2\t+\t0M',
121
+ 'L\tb2\t+\tb3\t+\t0M',
122
+ ].join('\n')
123
+
124
+ // Two re-cuts in flight at once, the way two quick changes in the settings
125
+ // dialog leave them, each answered when the test says.
126
+ function twoCuts() {
127
+ const pending: ((gfa: string) => void)[] = []
128
+ mockRpcCall.mockImplementation((_sid: unknown, method: string) =>
129
+ method === 'GetSubgraph'
130
+ ? new Promise<string>(resolve => {
131
+ pending.push(resolve)
132
+ })
133
+ : Promise.reject(new Error(`Unexpected RPC: ${method}`)),
134
+ )
135
+ const model = createView({
136
+ loadedTrackId: TRACK.trackId,
137
+ loadedRegion: { ...ON_HG38, start: 0, end: 200 },
138
+ })
139
+ const first = model.reloadSubgraph()
140
+ const second = model.reloadSubgraph()
141
+ return { model, pending, first, second }
142
+ }
143
+
144
+ test('the later cut wins when the earlier one finishes last', async () => {
145
+ const { model, pending, first, second } = twoCuts()
146
+ pending[1]!(THREE_NODES)
147
+ await second
148
+ pending[0]!(TWO_NODES)
149
+ await first
150
+ expect(model.nodeCount).toBe(3)
151
+ })
152
+
153
+ test('the spinner stays up until the live cut lands', async () => {
154
+ const { model, pending, first, second } = twoCuts()
155
+ pending[0]!(TWO_NODES)
156
+ await first
157
+ expect(model.isLoading).toBe(true)
158
+ pending[1]!(THREE_NODES)
159
+ await second
160
+ expect(model.isLoading).toBe(false)
161
+ expect(model.nodeCount).toBe(3)
162
+ })
163
+ })
164
+
165
+ describe('canceling and retrying', () => {
166
+ function pendingCuts() {
167
+ const pending: ((gfa: string) => void)[] = []
168
+ mockRpcCall.mockImplementation((_sid: unknown, method: string) =>
169
+ method === 'GetSubgraph'
170
+ ? new Promise<string>(resolve => {
171
+ pending.push(resolve)
172
+ })
173
+ : Promise.reject(new Error(`Unexpected RPC: ${method}`)),
174
+ )
175
+ return pending
176
+ }
177
+
178
+ function cutSignal(index: number) {
179
+ return (mockRpcCall.mock.calls[index]![2] as { signal: AbortSignal }).signal
180
+ }
181
+
182
+ function launchedView() {
183
+ return createView({ loadedTrackId: TRACK.trackId, loadedRegion: ON_HG38 })
184
+ }
185
+
186
+ test('canceling a first cut aborts it, and its late answer never lands', async () => {
187
+ const pending = pendingCuts()
188
+ const model = launchedView()
189
+ const load = model.reloadSubgraph()
190
+ expect(model.canCancelLoad).toBe(true)
191
+
192
+ model.cancelLoad()
193
+ expect(cutSignal(0).aborted).toBe(true)
194
+ expect(model.isLoading).toBe(false)
195
+ expect(model.loadCanceled).toBe(true)
196
+
197
+ pending[0]!(GFA)
198
+ await load
199
+ expect(model.hasGraph).toBe(false)
200
+ expect(model.error).toBeUndefined()
201
+ })
202
+
203
+ test('retry cuts the same region again', async () => {
204
+ pendingCuts()
205
+ const model = launchedView()
206
+ void model.reloadSubgraph()
207
+ model.cancelLoad()
208
+
209
+ mockRpcCall.mockResolvedValue(GFA)
210
+ model.retryLoad()
211
+ expect(model.loadCanceled).toBe(false)
212
+ await vi.waitFor(() => {
213
+ expect(model.nodeCount).toBe(3)
214
+ })
215
+ })
216
+
217
+ test('a newer cut aborts the one it replaces', () => {
218
+ pendingCuts()
219
+ const model = launchedView()
220
+ void model.reloadSubgraph()
221
+ void model.reloadSubgraph()
222
+ expect(cutSignal(0).aborted).toBe(true)
223
+ expect(cutSignal(1).aborted).toBe(false)
224
+ })
225
+
226
+ test('a reload over a drawn graph cannot be canceled', async () => {
227
+ const model = await cut(ON_HG38)
228
+ pendingCuts()
229
+ void model.reloadSubgraph()
230
+ expect(model.canCancelLoad).toBe(false)
231
+
232
+ model.cancelLoad()
233
+ expect(model.isLoading).toBe(true)
234
+ expect(model.nodeCount).toBe(3)
235
+ })
236
+
237
+ test('a stored track the session no longer has is reported', async () => {
238
+ mockSession.tracks = []
239
+ const model = launchedView()
240
+ await model.refetchIfNeeded()
241
+ expect(String(model.error)).toMatch(/"segments", is not in this session/)
242
+ expect(model.canRetryLoad).toBe(true)
243
+ expect(mockRpcCall).not.toHaveBeenCalled()
244
+ })
245
+ })
@@ -0,0 +1,141 @@
1
+ import type { StableCoordinate } from '../gfa-core/index'
2
+
3
+ export interface GraphNode {
4
+ id: string
5
+ name: string
6
+ length: number
7
+ depth: number
8
+ // where this segment sits on a stable sequence: stated per-segment by rGFA's
9
+ // SN/SO/SR, derived from the P/W lines otherwise (pathAnchoring.ts)
10
+ stable?: StableCoordinate
11
+ // every assembly whose P/W record visits this segment, sorted. This is
12
+ // carriage, and only a path GFA can state it: rGFA's SR is build order, so
13
+ // there the most a segment says is which assembly first contributed it.
14
+ samples?: string[]
15
+ }
16
+
17
+ export interface GraphEdge {
18
+ from: string
19
+ to: string
20
+ // How the link reads each segment, as the file states it: it leaves the END
21
+ // of `from` read this way and arrives at the START of `to`. `from` and `to`
22
+ // alone cannot say which side of a node a link is on, and that is the whole
23
+ // difference between a deletion, a duplication and an inversion. Absent on a
24
+ // graph built without them.
25
+ fromStrand?: '+' | '-'
26
+ toStrand?: '+' | '-'
27
+ pathIds?: string[]
28
+ }
29
+
30
+ export interface GraphPath {
31
+ name: string
32
+ nodeIds: string[]
33
+ // set for W records, which state the assembly they walk; P records do not
34
+ sample?: string
35
+ haplotype?: number
36
+ contig?: string
37
+ }
38
+
39
+ // One P or W record, named the way a coordinate is named: the range suffix
40
+ // `odgi extract` appends (`K12#1#chr:1004500-1004961`) split off into `start`,
41
+ // so `name` is a stable sequence and `start` is where this path begins on it.
42
+ export interface PathOrigin {
43
+ name: string
44
+ sample: string
45
+ start: number
46
+ // bp the path covers, summed over its steps
47
+ length: number
48
+ }
49
+
50
+ // One visit a path makes to one segment. A path may visit the same segment more
51
+ // than once (a collapsed repeat), so this is a list per segment, not a value.
52
+ export interface PathVisit {
53
+ path: string
54
+ sample: string
55
+ // offset on `path`'s own sequence, from walking the path's steps
56
+ start: number
57
+ // how this path reads the segment, which is not how another path may read it
58
+ strand: '+' | '-'
59
+ }
60
+
61
+ export interface Graph {
62
+ name: string
63
+ nodes: GraphNode[]
64
+ edges: GraphEdge[]
65
+ paths?: GraphPath[]
66
+ // The paths that could put this graph on a reference axis, in file order, and
67
+ // every segment visit they make. Absent for a GFA with no P/W records — an
68
+ // rGFA, where the segments carry their own coordinates instead.
69
+ anchorPaths?: PathOrigin[]
70
+ pathVisits?: Map<string, PathVisit[]>
71
+ // Where the nodes' `stable` came from. 'tags' is rGFA's SN/SO/SR; 'paths' is
72
+ // derived, and the only one that can be re-derived against a different
73
+ // reference path. Absent when nothing anchors the graph at all.
74
+ anchoredBy?: 'tags' | 'paths'
75
+ // the path `anchoredBy: 'paths'` put on x
76
+ referencePath?: string
77
+ }
78
+
79
+ export interface NodeSegment {
80
+ x: number
81
+ y: number
82
+ }
83
+
84
+ // A row a layout drew, named, in the same graph y coordinates as
85
+ // `nodePositions`. Emitted by the layout that positioned the rows rather than
86
+ // recomputed by the renderer, so a label cannot end up naming a row the layout
87
+ // put somewhere else. Absent for layouts with no row structure (FMMM).
88
+ export interface RowLabel {
89
+ label: string
90
+ y: number
91
+ }
92
+
93
+ // An allele that stands in for MORE reference than it carries sequence, as the
94
+ // row-structured layouts drew it. Those layouts give an off-reference node the
95
+ // slice of reference its allele replaces rather than its own length
96
+ // (placeOffReference), so on this axis a 93 bp segment bridging two backbone
97
+ // anchors 7.1 kb apart is drawn 7.1 kb wide — and labelling it `93 bp`, which is
98
+ // what a node label says everywhere else, describes the sequence while the
99
+ // reader is looking at the reference. It is a deletion, and the drawing already
100
+ // names deletions when they are bare edges; this is the same event with a
101
+ // segment in the middle of it, which `deletionEdges` cannot see.
102
+ //
103
+ // Emitted by the layout, because only the layout knows whether x is reference bp
104
+ // — under FMMM a node IS drawn at its own scale and `93 bp` is the true label.
105
+ export interface AlleleDeletion {
106
+ // the run's nodes, so a label can be placed over the extent they were drawn at
107
+ nodeIds: string[]
108
+ // reference bp the allele does not carry: the span between its anchors less
109
+ // the longest path through the run. Not `-delta`, which sums a bubble's
110
+ // parallel branches and so understates what any one haplotype skips.
111
+ bp: number
112
+ }
113
+
114
+ export interface LayoutResult {
115
+ nodePositions: Record<string, NodeSegment[]>
116
+ rowLabels?: RowLabel[]
117
+ // Deletions carried by a SEGMENT rather than by a bare edge, which only a
118
+ // reference-bp layout can state — see AlleleDeletion.
119
+ alleleDeletions?: AlleleDeletion[]
120
+ // Set by the layouts whose x is reference bp, so the fit can be taken from
121
+ // the region the cut was made for rather than from how far the drawing
122
+ // happens to reach. An allele anchored far outside the window is a fact
123
+ // about the graph, not a reason to redraw the window at 6% of the frame —
124
+ // see layoutBounds.
125
+ referenceAxis?: boolean
126
+ // Set by the layouts that state y in SCREEN PIXELS rather than in the same
127
+ // units as x. A row layout's y is a row pitch, which is a property of what a
128
+ // row has to hold (a tube, a label) and not of the window's span, so the two
129
+ // axes are no longer one number — see rowSpacing.ts and the model's
130
+ // scaleX/scaleY. A layout that leaves this unset (FMMM) is isotropic, and
131
+ // every unit of it is drawn at one scale, exactly as before.
132
+ //
133
+ // Separate from `referenceAxis` on purpose: that one says what x MEANS, this
134
+ // one says what y is MEASURED IN, and a future layout could set either
135
+ // without the other.
136
+ pixelRows?: boolean
137
+ // How far a drawing reaches past its nodes, when an overlay draws rows the
138
+ // renderer has no node for (walk rows). The fit and the pane height take the
139
+ // wider of this and the nodes.
140
+ extent?: { maxX: number; maxY: number }
141
+ }
@@ -0,0 +1,73 @@
1
+ import { SpatialIndex } from './SpatialIndex'
2
+
3
+ const positions = {
4
+ 'A+': [
5
+ { x: 0, y: 0 },
6
+ { x: 100, y: 0 },
7
+ ],
8
+ 'B+': [
9
+ { x: 200, y: 200 },
10
+ { x: 300, y: 200 },
11
+ ],
12
+ 'C+': [
13
+ { x: 0, y: 0 },
14
+ { x: 0, y: 100 },
15
+ ],
16
+ }
17
+
18
+ test('finds nearby segments', () => {
19
+ const index = new SpatialIndex(positions, 50)
20
+ const results = index.query(50, 0, 10)
21
+ const nodeIds = results.map(r => r.nodeId)
22
+ expect(nodeIds).toContain('A+')
23
+ expect(nodeIds).not.toContain('B+')
24
+ })
25
+
26
+ test('finds nothing when far from all segments', () => {
27
+ const index = new SpatialIndex(positions, 50)
28
+ const results = index.query(500, 500, 10)
29
+ expect(results).toHaveLength(0)
30
+ })
31
+
32
+ test('finds segments near intersection', () => {
33
+ const index = new SpatialIndex(positions, 50)
34
+ const results = index.query(0, 0, 10)
35
+ const nodeIds = new Set(results.map(r => r.nodeId))
36
+ expect(nodeIds.has('A+')).toBe(true)
37
+ expect(nodeIds.has('C+')).toBe(true)
38
+ })
39
+
40
+ test('larger radius finds more candidates', () => {
41
+ const index = new SpatialIndex(positions, 50)
42
+ const small = index.query(150, 100, 10)
43
+ const large = index.query(150, 100, 200)
44
+ expect(large.length).toBeGreaterThanOrEqual(small.length)
45
+ })
46
+
47
+ test('deduplicates entries across cells', () => {
48
+ const index = new SpatialIndex(positions, 10)
49
+ const results = index.query(50, 0, 20)
50
+ const keys = results.map(r => `${r.nodeId}:${r.segmentIdx}`)
51
+ const unique = new Set(keys)
52
+ expect(keys.length).toBe(unique.size)
53
+ })
54
+
55
+ // A row layout puts bp on x and a 20 px row pitch on y, so one cell size taken
56
+ // from the two summed is the x size, and applied to y it swallows every row into
57
+ // one cell: the grid stops discriminating on y and each query hands the distance
58
+ // loop every row in the band. HPRC's rGFA is exactly this shape, at ~7 kb a
59
+ // segment.
60
+ test('a row layout is bucketed on both axes, not just on x', () => {
61
+ const rows: Record<string, { x: number; y: number }[]> = {}
62
+ for (let row = 0; row < 20; row++) {
63
+ rows[`r${row}`] = [
64
+ { x: 0, y: row * 20 },
65
+ { x: 7000, y: row * 20 },
66
+ ]
67
+ }
68
+ const index = new SpatialIndex(rows)
69
+
70
+ // 5 screen px of slack on y, which is a quarter of a row
71
+ const hits = index.query(3500, 0, 7000 * 0.001, 5)
72
+ expect(hits.map(h => h.nodeId)).toEqual(['r0'])
73
+ })