jbrowse-plugin-graphgenomeviewer 7.1.0 → 7.1.2

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 (233) hide show
  1. package/README.md +17 -8
  2. package/dist/chunks/AddTrackWorkflow-RPH4ZFSX.js +2 -0
  3. package/dist/chunks/{AddTrackWorkflow-Q6RY23LF.js.map → AddTrackWorkflow-RPH4ZFSX.js.map} +2 -2
  4. package/dist/chunks/{ChooseSamplesDialog-BNVYBGJQ.js → ChooseSamplesDialog-JRN4YLBY.js} +2 -2
  5. package/dist/chunks/{ChooseWalksDialog-EN3C6AQA.js → ChooseWalksDialog-UXLFZTZJ.js} +2 -2
  6. package/dist/chunks/GbzBaseSyntenyAdapter-LRECKL74.js +2 -0
  7. package/dist/chunks/GbzBaseSyntenyAdapter-LRECKL74.js.map +7 -0
  8. package/dist/chunks/{GraphGenomeView-Z2WZKEEO.js → GraphGenomeView-NJ5XCREB.js} +2 -2
  9. package/dist/chunks/GraphTrackSettingsDialog-47PQ6LM7.js +6 -0
  10. package/dist/chunks/GraphTrackSettingsDialog-47PQ6LM7.js.map +7 -0
  11. package/dist/chunks/{HighlightColorDialog-EYBKFSJU.js → HighlightColorDialog-UK5VDDV6.js} +2 -2
  12. package/dist/chunks/{LinearGraphDisplay-WIBVWRQE.js → LinearGraphDisplay-TXTKAACH.js} +2 -2
  13. package/dist/chunks/MinigraphBubbleAdapter-HW7XQMAI.js +2 -0
  14. package/dist/chunks/MinigraphBubbleAdapter-HW7XQMAI.js.map +7 -0
  15. package/dist/chunks/RgfaTabixAdapter-PSKYWD2X.js +3 -0
  16. package/dist/chunks/RgfaTabixAdapter-PSKYWD2X.js.map +7 -0
  17. package/dist/chunks/{TrackWarningsDialog-QT4YEXGD.js → TrackWarningsDialog-TRYBOIHX.js} +2 -2
  18. package/dist/chunks/WalkTabixSyntenyAdapter-U53JLYVU.js +2 -0
  19. package/dist/chunks/WalkTabixSyntenyAdapter-U53JLYVU.js.map +7 -0
  20. package/dist/chunks/chunk-2MCOY3YG.js +3 -0
  21. package/dist/chunks/chunk-2MCOY3YG.js.map +7 -0
  22. package/dist/chunks/{chunk-EWFJBQDV.js → chunk-5O6YRJHL.js} +2 -2
  23. package/dist/chunks/{chunk-EWFJBQDV.js.map → chunk-5O6YRJHL.js.map} +1 -1
  24. package/dist/chunks/chunk-BUY2PV7D.js +2 -0
  25. package/dist/chunks/chunk-BUY2PV7D.js.map +7 -0
  26. package/dist/chunks/chunk-DVCZ3BOC.js +2 -0
  27. package/dist/chunks/chunk-DVCZ3BOC.js.map +7 -0
  28. package/dist/chunks/{chunk-SAMJE6GH.js → chunk-EGDLEQ47.js} +3 -3
  29. package/dist/chunks/{chunk-SAMJE6GH.js.map → chunk-EGDLEQ47.js.map} +4 -4
  30. package/dist/chunks/chunk-FHZXW7IG.js +2 -0
  31. package/dist/chunks/chunk-FHZXW7IG.js.map +7 -0
  32. package/dist/chunks/{chunk-DZ4OUKQV.js → chunk-FITPB5X6.js} +2 -2
  33. package/dist/chunks/{chunk-DZ4OUKQV.js.map → chunk-FITPB5X6.js.map} +1 -1
  34. package/dist/chunks/chunk-FVK2THE4.js +2 -0
  35. package/dist/chunks/chunk-FVK2THE4.js.map +7 -0
  36. package/dist/chunks/{chunk-AHFQHYJI.js → chunk-IBWORBGY.js} +2 -2
  37. package/dist/chunks/chunk-MND7GAOG.js +2 -0
  38. package/dist/chunks/chunk-MND7GAOG.js.map +7 -0
  39. package/dist/chunks/chunk-MURN3JPK.js +2 -0
  40. package/dist/chunks/chunk-MURN3JPK.js.map +7 -0
  41. package/dist/chunks/{chunk-3Q4RBDCL.js → chunk-N6PQZSAX.js} +1 -1
  42. package/dist/chunks/{chunk-3Q4RBDCL.js.map → chunk-N6PQZSAX.js.map} +1 -1
  43. package/dist/chunks/chunk-NWJYLP6Z.js +4 -0
  44. package/dist/chunks/chunk-NWJYLP6Z.js.map +7 -0
  45. package/dist/chunks/{chunk-YJHYBDYF.js → chunk-S75W27NT.js} +2 -2
  46. package/dist/chunks/{chunk-YJHYBDYF.js.map → chunk-S75W27NT.js.map} +1 -1
  47. package/dist/chunks/{chunk-IXQK7JAW.js → chunk-T4HOC6O3.js} +1 -1
  48. package/dist/chunks/{chunk-IXQK7JAW.js.map → chunk-T4HOC6O3.js.map} +1 -1
  49. package/dist/chunks/{chunk-AU73K7LX.js → chunk-TUKJRVBS.js} +1 -1
  50. package/dist/chunks/{chunk-AU73K7LX.js.map → chunk-TUKJRVBS.js.map} +1 -1
  51. package/dist/chunks/chunk-UGCNPRKC.js +2 -0
  52. package/dist/chunks/{chunk-E3WYW7Z6.js.map → chunk-UGCNPRKC.js.map} +2 -2
  53. package/dist/chunks/{chunk-YSTMDXXX.js → chunk-VJ3SO4CH.js} +1 -1
  54. package/dist/chunks/{chunk-YSTMDXXX.js.map → chunk-VJ3SO4CH.js.map} +1 -1
  55. package/dist/chunks/{chunk-XT766CZ5.js → chunk-W6EQLQ6H.js} +1 -1
  56. package/dist/chunks/{chunk-XT766CZ5.js.map → chunk-W6EQLQ6H.js.map} +1 -1
  57. package/dist/chunks/chunk-WAMBI7OX.js +2 -0
  58. package/dist/chunks/chunk-WAMBI7OX.js.map +7 -0
  59. package/dist/chunks/chunk-WCGVUZWP.js +2 -0
  60. package/dist/chunks/chunk-WCGVUZWP.js.map +7 -0
  61. package/dist/chunks/chunk-WEGWTC52.js +2 -0
  62. package/dist/chunks/chunk-WEGWTC52.js.map +7 -0
  63. package/dist/chunks/chunk-YU7T5WOQ.js +2 -0
  64. package/dist/chunks/{chunk-6OPB7FOJ.js.map → chunk-YU7T5WOQ.js.map} +2 -2
  65. package/dist/chunks/{chunk-446RWIA3.js → chunk-ZMOMFU2B.js} +1 -1
  66. package/dist/chunks/{chunk-446RWIA3.js.map → chunk-ZMOMFU2B.js.map} +1 -1
  67. package/dist/chunks/renderSvg-KSQI5DH6.js +2 -0
  68. package/dist/chunks/renderSvg-KSQI5DH6.js.map +7 -0
  69. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +4 -4
  70. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
  71. package/package.json +8 -9
  72. package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.test.ts +12 -10
  73. package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.ts +31 -39
  74. package/src/GbzBaseSyntenyAdapter/configSchema.ts +5 -29
  75. package/src/GetGraphHaplotypes.ts +6 -15
  76. package/src/GetGraphReads.ts +7 -14
  77. package/src/GetSubgraph.ts +11 -20
  78. package/src/GraphAddTrackWorkflow/AddTrackWorkflow.test.tsx +73 -0
  79. package/src/GraphAddTrackWorkflow/AddTrackWorkflow.tsx +1 -0
  80. package/src/GraphAddTrackWorkflow/buildTrackConfig.test.ts +3 -2
  81. package/src/GraphAddTrackWorkflow/buildTrackConfig.ts +2 -11
  82. package/src/GraphAddTrackWorkflow/graphManifest.ts +15 -3
  83. package/src/GraphGenomeView/components/BubbleHalos.tsx +5 -24
  84. package/src/GraphGenomeView/components/ElTree.tsx +6 -1
  85. package/src/GraphGenomeView/components/FacetPanels.test.tsx +65 -0
  86. package/src/GraphGenomeView/components/FacetPanels.tsx +17 -12
  87. package/src/GraphGenomeView/components/GenePins.tsx +5 -22
  88. package/src/GraphGenomeView/components/GraphCanvas.tsx +40 -234
  89. package/src/GraphGenomeView/components/GraphSettingsDialog.tsx +102 -108
  90. package/src/GraphGenomeView/components/GraphToolbar.tsx +5 -6
  91. package/src/GraphGenomeView/components/HoverLayer.tsx +3 -7
  92. package/src/GraphGenomeView/components/HoverTooltip.test.tsx +39 -0
  93. package/src/GraphGenomeView/components/HoverTooltip.tsx +122 -0
  94. package/src/GraphGenomeView/components/LabelChip.tsx +3 -5
  95. package/src/GraphGenomeView/components/LabelLayer.tsx +4 -17
  96. package/src/GraphGenomeView/components/LabelledSelect.tsx +1 -1
  97. package/src/GraphGenomeView/components/Legend.tsx +14 -23
  98. package/src/GraphGenomeView/components/PaneContextMenu.test.tsx +48 -0
  99. package/src/GraphGenomeView/components/PaneContextMenu.tsx +144 -0
  100. package/src/GraphGenomeView/components/PaneLayer.tsx +91 -0
  101. package/src/GraphGenomeView/components/PaneSvg.tsx +32 -50
  102. package/src/GraphGenomeView/components/ReferenceStripOverlay.test.tsx +48 -0
  103. package/src/GraphGenomeView/components/ReferenceStripOverlay.tsx +13 -42
  104. package/src/GraphGenomeView/components/SettingsMenu.test.tsx +29 -0
  105. package/src/GraphGenomeView/components/SettingsMenu.tsx +56 -65
  106. package/src/GraphGenomeView/components/TubeMapOverlay.tsx +15 -46
  107. package/src/GraphGenomeView/components/WalkRowsOverlay.tsx +58 -132
  108. package/src/GraphGenomeView/components/WalkStrip.tsx +17 -20
  109. package/src/GraphGenomeView/components/WalkStripLocator.tsx +8 -15
  110. package/src/GraphGenomeView/components/usePaneGestures.test.tsx +35 -0
  111. package/src/GraphGenomeView/components/usePaneGestures.ts +88 -91
  112. package/src/GraphGenomeView/genes/geneFeatures.ts +27 -45
  113. package/src/GraphGenomeView/graphLayers.ts +4 -4
  114. package/src/GraphGenomeView/host.ts +1 -1
  115. package/src/GraphGenomeView/legendSections.test.ts +23 -1
  116. package/src/GraphGenomeView/legendSections.ts +68 -74
  117. package/src/GraphGenomeView/model.test.ts +373 -39
  118. package/src/GraphGenomeView/model.ts +67 -11
  119. package/src/GraphGenomeView/pane/fitViews.ts +57 -514
  120. package/src/GraphGenomeView/pane/graphViews.ts +219 -160
  121. package/src/GraphGenomeView/pane/hostViews.ts +2 -10
  122. package/src/GraphGenomeView/pane/keepInView.ts +5 -4
  123. package/src/GraphGenomeView/pane/launchActions.ts +117 -139
  124. package/src/GraphGenomeView/pane/launchViews.ts +8 -27
  125. package/src/GraphGenomeView/pane/loadActions.ts +208 -187
  126. package/src/GraphGenomeView/pane/morph.ts +3 -9
  127. package/src/GraphGenomeView/pane/paneBase.ts +22 -11
  128. package/src/GraphGenomeView/pane/renderingActions.ts +65 -11
  129. package/src/GraphGenomeView/pane/settingActions.ts +17 -42
  130. package/src/GraphGenomeView/pane/tubeMapViews.ts +445 -0
  131. package/src/GraphGenomeView/pane/walkRowViews.ts +29 -37
  132. package/src/GraphGenomeView/renderPipeline.test.ts +1 -1
  133. package/src/GraphGenomeView/repeats/repeatFeatures.test.ts +23 -0
  134. package/src/GraphGenomeView/repeats/repeatFeatures.ts +16 -23
  135. package/src/GraphGenomeView/repeats/walkCalls.test.ts +18 -0
  136. package/src/GraphGenomeView/repeats/walkCalls.ts +46 -38
  137. package/src/GraphGenomeView/subgraphLoad.test.ts +67 -3
  138. package/src/GraphGenomeView/trackFeatures.ts +24 -0
  139. package/src/GraphGenomeView/tubeMapPanels.ts +28 -36
  140. package/src/GraphGenomeView/viewModel.ts +8 -56
  141. package/src/GraphGenomeView/walkRowGroups.ts +104 -30
  142. package/src/GraphGenomeView/walkRowSpans.test.ts +6 -0
  143. package/src/GraphGenomeView/walkRowSpans.ts +22 -31
  144. package/src/GraphGenomeView/walkRowsCut.ts +8 -17
  145. package/src/LinearGraphDisplay/LinearGraphDisplay.test.ts +173 -26
  146. package/src/LinearGraphDisplay/components/SubgraphContextSelect.test.tsx +1 -1
  147. package/src/LinearGraphDisplay/components/SubgraphContextSelect.tsx +3 -3
  148. package/src/LinearGraphDisplay/components/SubgraphHaplotypesField.test.tsx +3 -3
  149. package/src/LinearGraphDisplay/components/SubgraphHaplotypesField.tsx +7 -10
  150. package/src/LinearGraphDisplay/configSchema.ts +0 -3
  151. package/src/LinearGraphDisplay/denseWindow.ts +8 -11
  152. package/src/LinearGraphDisplay/model.ts +89 -82
  153. package/src/MinigraphBubbleAdapter/MinigraphBubbleAdapter.test.ts +21 -0
  154. package/src/MinigraphBubbleAdapter/MinigraphBubbleAdapter.ts +6 -14
  155. package/src/MinigraphBubbleAdapter/configSchema.ts +3 -9
  156. package/src/MinigraphBubbleAdapter/index.ts +1 -2
  157. package/src/RgfaTabixAdapter/RgfaTabixAdapter.test.ts +45 -1
  158. package/src/RgfaTabixAdapter/RgfaTabixAdapter.ts +111 -276
  159. package/src/RgfaTabixAdapter/configSchema.ts +15 -49
  160. package/src/RgfaTabixAdapter/index.ts +1 -2
  161. package/src/RgfaTabixAdapter/rgfaBed.ts +25 -15
  162. package/src/RgfaTabixAdapter/rgfaCut.ts +139 -0
  163. package/src/RgfaTabixAdapter/test_data/walks2_twopart.GRCh38.walks.bed.gz +0 -0
  164. package/src/RgfaTabixAdapter/test_data/walks2_twopart.GRCh38.walks.bed.gz.tbi +0 -0
  165. package/src/RgfaTabixAdapter/walkFiles.test.ts +33 -0
  166. package/src/RgfaTabixAdapter/walkReader.ts +103 -27
  167. package/src/RgfaTabixAdapter/walkRowRuns.test.ts +53 -0
  168. package/src/RgfaTabixAdapter/walkRowRuns.ts +42 -24
  169. package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.test.ts +37 -6
  170. package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.ts +23 -45
  171. package/src/WalkTabixSyntenyAdapter/configSchema.ts +11 -24
  172. package/src/WalkTabixSyntenyAdapter/index.ts +1 -2
  173. package/src/WalkTabixSyntenyAdapter/walkLanes.ts +35 -43
  174. package/src/adapterImplementing.ts +27 -0
  175. package/src/graphTrackDefaults/index.ts +14 -20
  176. package/src/hoverSync/lgvHover.ts +2 -4
  177. package/src/launchFromGraph/contributors.ts +1 -5
  178. package/src/launchFromGraph/graphMenuItems.ts +3 -4
  179. package/src/launchFromGraph/launchFromGraph.test.ts +0 -1
  180. package/src/launchFromGraph/launchFromGraph.ts +8 -13
  181. package/src/panSNTabix.test.ts +1 -26
  182. package/src/panSNTabix.ts +54 -51
  183. package/src/synteny/lanePairs.ts +8 -0
  184. package/src/synteny/matchFeature.ts +52 -0
  185. package/src/tabixRange.ts +13 -10
  186. package/src/version.ts +1 -1
  187. package/dist/chunks/AddTrackWorkflow-Q6RY23LF.js +0 -2
  188. package/dist/chunks/GbzBaseSyntenyAdapter-TIXR4Y3J.js +0 -2
  189. package/dist/chunks/GbzBaseSyntenyAdapter-TIXR4Y3J.js.map +0 -7
  190. package/dist/chunks/GraphTrackSettingsDialog-TAM6PMXD.js +0 -6
  191. package/dist/chunks/GraphTrackSettingsDialog-TAM6PMXD.js.map +0 -7
  192. package/dist/chunks/MinigraphBubbleAdapter-AGI2UYQR.js +0 -2
  193. package/dist/chunks/MinigraphBubbleAdapter-AGI2UYQR.js.map +0 -7
  194. package/dist/chunks/RgfaTabixAdapter-2OL5X3HF.js +0 -3
  195. package/dist/chunks/RgfaTabixAdapter-2OL5X3HF.js.map +0 -7
  196. package/dist/chunks/WalkTabixSyntenyAdapter-UKFHCFOF.js +0 -2
  197. package/dist/chunks/WalkTabixSyntenyAdapter-UKFHCFOF.js.map +0 -7
  198. package/dist/chunks/chunk-5SFGHDQA.js +0 -2
  199. package/dist/chunks/chunk-5SFGHDQA.js.map +0 -7
  200. package/dist/chunks/chunk-6OPB7FOJ.js +0 -2
  201. package/dist/chunks/chunk-AMFIBQVR.js +0 -2
  202. package/dist/chunks/chunk-AMFIBQVR.js.map +0 -7
  203. package/dist/chunks/chunk-BJMRBVG3.js +0 -2
  204. package/dist/chunks/chunk-BJMRBVG3.js.map +0 -7
  205. package/dist/chunks/chunk-E3WYW7Z6.js +0 -2
  206. package/dist/chunks/chunk-EHCF6AXH.js +0 -2
  207. package/dist/chunks/chunk-EHCF6AXH.js.map +0 -7
  208. package/dist/chunks/chunk-GOFS7BXK.js +0 -3
  209. package/dist/chunks/chunk-GOFS7BXK.js.map +0 -7
  210. package/dist/chunks/chunk-IJQTSJGT.js +0 -4
  211. package/dist/chunks/chunk-IJQTSJGT.js.map +0 -7
  212. package/dist/chunks/chunk-J6NZIAZF.js +0 -2
  213. package/dist/chunks/chunk-J6NZIAZF.js.map +0 -7
  214. package/dist/chunks/chunk-LUTMKRXY.js +0 -2
  215. package/dist/chunks/chunk-LUTMKRXY.js.map +0 -7
  216. package/dist/chunks/chunk-MHPJB22Z.js +0 -2
  217. package/dist/chunks/chunk-MHPJB22Z.js.map +0 -7
  218. package/dist/chunks/chunk-TXI43PEN.js +0 -2
  219. package/dist/chunks/chunk-TXI43PEN.js.map +0 -7
  220. package/dist/chunks/chunk-WRH2EJKX.js +0 -2
  221. package/dist/chunks/chunk-WRH2EJKX.js.map +0 -7
  222. package/dist/chunks/chunk-WX26KTZM.js +0 -2
  223. package/dist/chunks/chunk-WX26KTZM.js.map +0 -7
  224. package/dist/chunks/renderSvg-ST2GBBOC.js +0 -2
  225. package/dist/chunks/renderSvg-ST2GBBOC.js.map +0 -7
  226. package/src/GraphGenomeView/components/WalkRowContextMenu.tsx +0 -105
  227. /package/dist/chunks/{ChooseSamplesDialog-BNVYBGJQ.js.map → ChooseSamplesDialog-JRN4YLBY.js.map} +0 -0
  228. /package/dist/chunks/{ChooseWalksDialog-EN3C6AQA.js.map → ChooseWalksDialog-UXLFZTZJ.js.map} +0 -0
  229. /package/dist/chunks/{GraphGenomeView-Z2WZKEEO.js.map → GraphGenomeView-NJ5XCREB.js.map} +0 -0
  230. /package/dist/chunks/{HighlightColorDialog-EYBKFSJU.js.map → HighlightColorDialog-UK5VDDV6.js.map} +0 -0
  231. /package/dist/chunks/{LinearGraphDisplay-WIBVWRQE.js.map → LinearGraphDisplay-TXTKAACH.js.map} +0 -0
  232. /package/dist/chunks/{TrackWarningsDialog-QT4YEXGD.js.map → TrackWarningsDialog-TRYBOIHX.js.map} +0 -0
  233. /package/dist/chunks/{chunk-AHFQHYJI.js.map → chunk-IBWORBGY.js.map} +0 -0
@@ -0,0 +1,445 @@
1
+ import { formatBp } from '@jbrowse/bandage-core/graphLabels'
2
+ import { getDpr } from '@jbrowse/bandage-core/renderer/canvas'
3
+ import { referenceBoxes, rulerBoxes } from '@jbrowse/bandage-core/tubeMap/axis'
4
+ import {
5
+ connectorAt,
6
+ drawLitConnector,
7
+ tubeMapConnectors,
8
+ } from '@jbrowse/bandage-core/tubeMap/connectors'
9
+ import { deviationMarks } from '@jbrowse/bandage-core/tubeMap/deviations'
10
+ import {
11
+ drawTubeMapHighlight,
12
+ mismatchOnScreen,
13
+ mismatchesLegible,
14
+ tubeMapMismatchAt,
15
+ tubeMapPicture,
16
+ tubeMapTrackAt,
17
+ } from '@jbrowse/bandage-core/tubeMap/draw'
18
+ import {
19
+ tubeMapFrame,
20
+ tubeMapNodeAt,
21
+ } from '@jbrowse/bandage-core/tubeMap/frame'
22
+ import { tubeMapNodeColors } from '@jbrowse/bandage-core/tubeMap/nodeColors'
23
+ import { computed } from 'mobx'
24
+
25
+ import {
26
+ NO_VALUE_TUBE,
27
+ PANEL_GAP,
28
+ PANEL_TUBE,
29
+ TINTED_TUBES,
30
+ boxOf,
31
+ memberAt,
32
+ paintPanelHover,
33
+ walksThrough,
34
+ } from '../tubeMapPanels'
35
+ import { withFitViews } from './fitViews'
36
+ import { HOVER_BRIGHTEN } from './paneBase'
37
+
38
+ import type { HoveredTube } from './paneBase'
39
+ import type { Canvas2DRenderer } from '@jbrowse/bandage-core/renderer/Canvas2DRenderer'
40
+ import type { RenderBatch } from '@jbrowse/bandage-core/renderer/types'
41
+ import type { TubeMapPicture } from '@jbrowse/bandage-core/tubeMap/draw'
42
+ import type { TubeMapTransform } from '@jbrowse/bandage-core/tubeMap/frame'
43
+ import type { TubeMapMismatch } from '@jbrowse/bandage-core/tubeMap/mismatches'
44
+
45
+ const MAX_SHOWN_BASES = 20
46
+ // a squeezed stack of panels thins no further, as one tube map does
47
+ const MIN_PANEL_Y_SCALE = 0.05
48
+
49
+ const MAX_LISTED = 6
50
+
51
+ function listed(names: string[]) {
52
+ const more = names.length - MAX_LISTED
53
+ return more > 0
54
+ ? `${names.slice(0, MAX_LISTED).join(', ')} and ${more} more`
55
+ : names.join(', ')
56
+ }
57
+
58
+ function mismatchText(m: TubeMapMismatch) {
59
+ if (m.kind === 'deletion') {
60
+ return `deletion of ${formatBp(m.length)}`
61
+ }
62
+ if (m.kind === 'substitution') {
63
+ return `substitution ${m.seq}`
64
+ }
65
+ const seq = m.seq ?? ''
66
+ const shown =
67
+ seq.length > MAX_SHOWN_BASES ? `${seq.slice(0, MAX_SHOWN_BASES)}…` : seq
68
+ return `insertion of ${formatBp(seq.length)} ${shown}`.trim()
69
+ }
70
+
71
+ // Which kinds of read mark are on screen, stopping once all three are found
72
+ function mismatchKindsShown(
73
+ picture: TubeMapPicture | undefined,
74
+ frame: TubeMapTransform | undefined,
75
+ width: number,
76
+ ) {
77
+ const kinds = { substitution: false, insertion: false, deletion: false }
78
+ if (picture && frame && mismatchesLegible(frame.yScale)) {
79
+ let missing = 3
80
+ for (const m of picture.mismatches) {
81
+ if (!kinds[m.kind] && mismatchOnScreen(m, { x: frame.x, width })) {
82
+ kinds[m.kind] = true
83
+ if (--missing === 0) {
84
+ break
85
+ }
86
+ }
87
+ }
88
+ }
89
+ return kinds
90
+ }
91
+
92
+ export const withTubeMapViews = withFitViews
93
+ .views(self => ({
94
+ get tubeMapFrame() {
95
+ const drawing = self.layoutResult?.tubeMap
96
+ return drawing
97
+ ? tubeMapFrame(drawing, {
98
+ ...self.screenTransform,
99
+ usableHeight: self.usableHeight,
100
+ })
101
+ : undefined
102
+ },
103
+ // the reference ruler under the tubes, unless the linear view's ruler
104
+ // is already the axis
105
+ get tubeMapRulerBoxes() {
106
+ const reference = self.tubeMapReference
107
+ return reference && !self.hostPlacesX && !self.layoutResult?.tubeMapPanels
108
+ ? rulerBoxes(reference)
109
+ : undefined
110
+ },
111
+ // Split on the reference axis, the panels share x, so one ruler under
112
+ // the last reads for them all; on the own axis each panel has its own
113
+ get tubeMapPanelRulerBoxes() {
114
+ const { layoutResult } = self
115
+ const last = layoutResult?.tubeMapPanels?.at(-1)
116
+ return last && layoutResult?.referenceAxis && !self.hostPlacesX
117
+ ? rulerBoxes(referenceBoxes(last.result.tubeMap))
118
+ : undefined
119
+ },
120
+ // The connectors run from the top of the pane down to the tubes' top
121
+ get connectorZoneBottom() {
122
+ const picture = self.tubeMapPicture
123
+ const frame = this.tubeMapFrame
124
+ return picture && frame ? frame.y(picture.bounds.minY) - 2 : 0
125
+ },
126
+ // The bands' tops follow every frame of a pan in the linear view and
127
+ // their bottoms every pan of the tubes
128
+ get tubeMapConnectors() {
129
+ const boxes = self.tubeMapConnectedBoxes
130
+ const frame = this.tubeMapFrame
131
+ const bp = boxes && frame ? self.hostBpFrame : undefined
132
+ return boxes && frame && bp
133
+ ? tubeMapConnectors(
134
+ boxes,
135
+ b => b * bp.scale + bp.translateX,
136
+ frame.x,
137
+ self.paneWidth,
138
+ )
139
+ : []
140
+ },
141
+ // What each panel of a split tube map draws, which no pan or hover
142
+ // changes: its picture, its tubes, its boxes' tints and its folded
143
+ // variants
144
+ get tubeMapPanelPictures() {
145
+ const panels = self.layoutResult?.tubeMapPanels
146
+ if (!panels) {
147
+ return undefined
148
+ }
149
+ const { field } = self.facetSetting
150
+ return panels.map(panel => {
151
+ const drawing = panel.result.tubeMap
152
+ const { graph, coarse } = drawing
153
+ return {
154
+ panel,
155
+ label:
156
+ panel.label ??
157
+ (field === 'walk' ? self.walkLabel(panel.paths[0]!) : panel.key),
158
+ picture: tubeMapPicture(drawing),
159
+ tubeColors: self.tubeColorsOf(graph, drawing.pathColors),
160
+ nodeColors: self.tubeMapNodeColors
161
+ ? tubeMapNodeColors(
162
+ graph,
163
+ self.effectiveColorScheme,
164
+ self.referenceRamp,
165
+ )
166
+ : undefined,
167
+ deviations: coarse ? deviationMarks(drawing, coarse.deviations) : [],
168
+ }
169
+ })
170
+ },
171
+ }))
172
+ .views(self => ({
173
+ // The node the pointer is on: inside a merged run, the member at the
174
+ // pointer's bp, so a run spanning the window counts what is under it
175
+ get hoveredMember() {
176
+ const id = self.hoveredNode
177
+ const members =
178
+ id === null ? undefined : self.hoverDrawing?.members.get(id)
179
+ if (!members) {
180
+ return id
181
+ }
182
+ const at = self.hoveredAt
183
+ const i = self.hoveredPanel
184
+ const under = i === null ? undefined : this.tubeMapPanelViews?.[i]
185
+ // sparse, so forEach, which skips its holes, as tubeMapNodeAt does
186
+ let box: { x: number; pixelWidth: number } | undefined
187
+ under?.panel.result.tubeMap.layout.nodes.forEach(n => {
188
+ if (n.name === id) {
189
+ box = n
190
+ }
191
+ })
192
+ const member =
193
+ at && under && box
194
+ ? memberAt(
195
+ members,
196
+ m => self.nodeSpan(m),
197
+ under.frame.x(box.x),
198
+ under.frame.x(box.x + box.pixelWidth),
199
+ at.x,
200
+ )
201
+ : undefined
202
+ return member ?? members[0]
203
+ },
204
+ // For the hovered box, the box each panel draws it in and how many of
205
+ // that panel's haplotypes pass through it: a frequency per group
206
+ get tubeMapPanelHover() {
207
+ const id = self.hoveredNode
208
+ const pictures = self.tubeMapPanelPictures
209
+ if (id === null || !pictures) {
210
+ return undefined
211
+ }
212
+ const node = this.hoveredMember ?? id
213
+ return pictures.map(({ panel }) => {
214
+ const drawing = panel.result.tubeMap
215
+ const box = boxOf(drawing, node)
216
+ return box ? { box, ...walksThrough(drawing.graph, box) } : undefined
217
+ })
218
+ },
219
+ // apart from the pictures, so a lane row's hover redraws no panel
220
+ get tubeMapPanelTubeColors() {
221
+ return self.tubeMapPanelPictures?.map(({ panel, tubeColors }) => {
222
+ const { graph } = panel.result.tubeMap
223
+ return self.fadeUnhovered(
224
+ tubeColors,
225
+ graph.paths ?? [],
226
+ graph.referencePath,
227
+ )
228
+ })
229
+ },
230
+ // Each panel through the pane's transform at its place down the stack.
231
+ // On the reference axis the stack squeezes into the pane as one map does.
232
+ get tubeMapPanelViews() {
233
+ const pictures = self.tubeMapPanelPictures
234
+ const tubeColors = this.tubeMapPanelTubeColors
235
+ const layout = self.layoutResult
236
+ if (!pictures || !tubeColors || !layout) {
237
+ return undefined
238
+ }
239
+ // the titles keep their px and the drawings squeeze into the rest
240
+ const titles = pictures.length * PANEL_GAP
241
+ const drawings = pictures.reduce((h, p) => h + p.panel.height, 0)
242
+ const usable = self.usableHeight - titles
243
+ const yScale =
244
+ self.scaleY *
245
+ (layout.referenceAxis && drawings > 0
246
+ ? Math.max(MIN_PANEL_Y_SCALE, Math.min(1, usable / drawings))
247
+ : 1)
248
+ let y = self.translateY
249
+ return pictures.map((p, i) => {
250
+ const { panel } = p
251
+ const minY = panel.result.extent?.minY ?? 0
252
+ const titleTop = y
253
+ const top = titleTop + PANEL_GAP
254
+ y = top + panel.height * yScale
255
+ return {
256
+ ...p,
257
+ tubeColors: tubeColors[i]!,
258
+ frame: tubeMapFrame(panel.result.tubeMap, {
259
+ scaleX: self.scaleX,
260
+ translateX: self.translateX,
261
+ scaleY: yScale,
262
+ translateY: top - minY * yScale,
263
+ usableHeight: Infinity,
264
+ }),
265
+ titleTop,
266
+ top,
267
+ bottom: y,
268
+ }
269
+ })
270
+ },
271
+ // the panel a pane y falls in, its title's gap included
272
+ tubeMapPanelAt(sy: number) {
273
+ const i =
274
+ this.tubeMapPanelViews?.findIndex(
275
+ v => v.titleTop <= sy && sy <= v.bottom,
276
+ ) ?? -1
277
+ return i < 0 ? null : i
278
+ },
279
+ }))
280
+ .views(self => ({
281
+ // The hover alone, onto a transparent canvas over the drawing, so a
282
+ // pointer move repaints what it lights rather than every stroke under
283
+ // it. `batch` is what the canvas below holds.
284
+ paintHover(r: Canvas2DRenderer, batch: RenderBatch) {
285
+ const picture = self.tubeMapPicture
286
+ const frame = self.tubeMapFrame
287
+ const node = self.hoverLayerNode
288
+ if (picture && frame) {
289
+ const { ctx, canvas } = r
290
+ const dpr = getDpr()
291
+ ctx.setTransform(1, 0, 0, 1, 0, 0)
292
+ ctx.clearRect(0, 0, canvas.width, canvas.height)
293
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
294
+ const panels = self.tubeMapPanelViews
295
+ if (panels) {
296
+ paintPanelHover(ctx, panels, self.tubeMapPanelHover, {
297
+ width: self.paneWidth,
298
+ lit: node !== null,
299
+ })
300
+ } else {
301
+ drawTubeMapHighlight(ctx, picture, {
302
+ ...frame,
303
+ width: self.paneWidth,
304
+ highlightNode: node,
305
+ })
306
+ }
307
+ drawLitConnector(
308
+ ctx,
309
+ self.tubeMapConnectors,
310
+ self.connectorZoneBottom,
311
+ node,
312
+ )
313
+ ctx.setTransform(1, 0, 0, 1, 0, 0)
314
+ } else {
315
+ r.uploadGeometry(batch)
316
+ r.setNodeHighlights(
317
+ node === null ? new Map() : new Map([[node, HOVER_BRIGHTEN]]),
318
+ )
319
+ r.setEdgeHighlight(self.hoveredEdge, HOVER_BRIGHTEN)
320
+ r.updateTransform(self.paintTransform)
321
+ r.renderHighlights()
322
+ }
323
+ },
324
+ }))
325
+ .views(self => {
326
+ // Compared by value: the marks in view are read off the transform, and
327
+ // the legend should redraw when a kind comes or goes, not every pan frame
328
+ const keys = computed(
329
+ () => {
330
+ const layout = self.layoutResult
331
+ const split = layout?.tubeMapPanels !== undefined
332
+ const reads = split ? [] : (layout?.tubeMap?.layout.reads ?? [])
333
+ const paths = self.drawnGraph?.paths ?? []
334
+ const coloring = self.tubeMapColoring
335
+ const kind = self.tubeColorKind
336
+ return {
337
+ bundled: paths.some(p => p.members),
338
+ // tubes in a column's hues, keyed value by value
339
+ values:
340
+ kind === 'value' && coloring
341
+ ? [
342
+ ...[...coloring.palette].map(([value, color]) => ({
343
+ value,
344
+ color,
345
+ })),
346
+ ...(coloring.unvalued
347
+ ? [
348
+ {
349
+ value: `${coloring.field}: none`,
350
+ color: NO_VALUE_TUBE,
351
+ },
352
+ ]
353
+ : []),
354
+ ]
355
+ : undefined,
356
+ // tubes coloured reference and haplotype rather than per walk
357
+ roles: kind === 'role' || kind === 'tinted',
358
+ haplotypeTube: kind === 'tinted' ? TINTED_TUBES[0]! : PANEL_TUBE,
359
+ logWidths: layout?.tubeMap !== undefined && !layout.referenceAxis,
360
+ foldBp:
361
+ self.tubeMapDeviations.length > 0 ? self.tubeMapFold : undefined,
362
+ forwardReads: reads.some(r => !r.is_reverse),
363
+ reverseReads: reads.some(r => r.is_reverse),
364
+ ...mismatchKindsShown(
365
+ split ? undefined : self.tubeMapPicture,
366
+ self.tubeMapFrame,
367
+ self.paneWidth,
368
+ ),
369
+ }
370
+ },
371
+ { equals: (a, b) => JSON.stringify(a) === JSON.stringify(b) },
372
+ )
373
+ return {
374
+ // What the tube map's legend has to explain: that on the own axis a
375
+ // box is as wide as the log of its length, the fold its walks' ticks
376
+ // stand for, the reads' strands, and the marks on the reads in view
377
+ get tubeMapKeys() {
378
+ return keys.get()
379
+ },
380
+ }
381
+ })
382
+ .views(self => ({
383
+ // The drawing under a pane y: its panel's while the map is split
384
+ tubeMapUnder(sy: number) {
385
+ const views = self.tubeMapPanelViews
386
+ if (views) {
387
+ const i = self.tubeMapPanelAt(sy)
388
+ const v = i === null ? undefined : views[i]
389
+ return v && { drawing: v.panel.result.tubeMap, ...v }
390
+ }
391
+ const drawing = self.layoutResult?.tubeMap
392
+ const picture = self.tubeMapPicture
393
+ const frame = self.tubeMapFrame
394
+ return drawing && picture && frame
395
+ ? { drawing, picture, frame }
396
+ : undefined
397
+ },
398
+ tubeMapNodeAt(sx: number, sy: number) {
399
+ const under = this.tubeMapUnder(sy)
400
+ return (
401
+ (under ? tubeMapNodeAt(under.drawing, under.frame, sx, sy) : null) ??
402
+ connectorAt(self.tubeMapConnectors, self.connectorZoneBottom, sx, sy) ??
403
+ null
404
+ )
405
+ },
406
+ // The tube under a pane point, and the mismatch mark on it if any
407
+ tubeAt(sx: number, sy: number): HoveredTube | null {
408
+ const under = this.tubeMapUnder(sy)
409
+ if (!under) {
410
+ return null
411
+ }
412
+ const { picture, frame } = under
413
+ const mismatch = tubeMapMismatchAt(picture, frame, sx, sy)
414
+ const track = mismatch?.readId ?? tubeMapTrackAt(picture, frame, sx, sy)
415
+ return track === undefined ? null : { track, mismatch }
416
+ },
417
+ // A tube names its haplotype; a read its MAPQ and strand, and the
418
+ // mismatch under the pointer
419
+ get hoveredTubeText() {
420
+ const tube = self.hoveredTube
421
+ const layout = self.layoutResult?.tubeMap?.layout
422
+ const read = tube && layout?.reads.find(r => r.id === tube.track)
423
+ if (tube && read) {
424
+ const mapq = read.mapping_quality
425
+ return {
426
+ label: read.name ?? 'read',
427
+ readout: [
428
+ `MAPQ ${mapq ?? 'unknown'}`,
429
+ `${read.is_reverse ? 'reverse' : 'forward'} strand`,
430
+ ...(tube.mismatch ? [mismatchText(tube.mismatch)] : []),
431
+ ].join(', '),
432
+ }
433
+ }
434
+ const path = tube ? self.hoverGraph?.paths?.[tube.track] : undefined
435
+ const members = path?.members
436
+ return members
437
+ ? {
438
+ label: `${members.length} haplotypes`,
439
+ readout: `one route: ${listed(members.map(m => self.walkLabel(m)))}`,
440
+ }
441
+ : path
442
+ ? { label: self.walkLabel(path.name), readout: 'haplotype' }
443
+ : undefined
444
+ },
445
+ }))
@@ -17,6 +17,7 @@ import { WALK_GENE_ROWS } from './paneBase'
17
17
  import { resolveLocationAssembly } from '../../launchFromGraph/contributors'
18
18
 
19
19
  import type { SubgraphRegion } from '../../GetSubgraph'
20
+ import type { WalkAxis, WalkRow } from '@jbrowse/bandage-core/layout/walkRows'
20
21
 
21
22
  export const withWalkRowViews = withLaunchViews
22
23
  .views(self => ({
@@ -63,16 +64,29 @@ export const withWalkRowViews = withLaunchViews
63
64
  ? stripMarks(self.graph, [bars.reference, ...bars.rows], node)
64
65
  : []
65
66
  },
67
+ // Where a row's bar lies on its contig, and the session assembly its
68
+ // haplotype (`HG00097#1`) or else its sample names
69
+ rowLocation(row: WalkRow, axis: WalkAxis) {
70
+ const named = {
71
+ sample: row.sample,
72
+ haplotype:
73
+ row.haplotype === undefined
74
+ ? undefined
75
+ : `${row.sample}#${row.haplotype}`,
76
+ }
77
+ return {
78
+ ...named,
79
+ assembly: resolveLocationAssembly(self.assemblyResolver, named),
80
+ span: { refName: axis.contig, ...rowSpan(axis, row.bp) },
81
+ }
82
+ },
66
83
  // Where the drawing has the node a strip point lights, for the ring
67
84
  // that finds it in a hairball
68
85
  get walkStripLocator() {
69
86
  const id = self.stripHover ? self.hoveredNode : null
70
87
  return id === null
71
88
  ? undefined
72
- : nodeAnchor(self.nodePositions?.[id], p => ({
73
- x: p.x * self.scaleX + self.translateX,
74
- y: p.y * self.scaleY + self.translateY,
75
- }))
89
+ : nodeAnchor(self.nodePositions?.[id], self.toScreen)
76
90
  },
77
91
  }))
78
92
  .views(self => ({
@@ -97,28 +111,18 @@ export const withWalkRowViews = withLaunchViews
97
111
  unplaced++
98
112
  continue
99
113
  }
100
- const assemblyName = resolveLocationAssembly(self.assemblyResolver, {
101
- sample: row.sample,
102
- haplotype:
103
- row.haplotype === undefined
104
- ? undefined
105
- : `${row.sample}#${row.haplotype}`,
106
- })
107
- const track = assemblyName
108
- ? self.geneTracksByAssembly.get(assemblyName)
114
+ const { assembly, span } = self.rowLocation(row, row.axis)
115
+ const track = assembly
116
+ ? self.geneTracksByAssembly.get(assembly)
109
117
  : undefined
110
- if (!assemblyName || !track) {
118
+ if (!assembly || !track) {
111
119
  untrackedRows++
112
120
  continue
113
121
  }
114
122
  reads.push({
115
123
  row: row.name,
116
124
  trackId: track.trackId,
117
- region: {
118
- assemblyName,
119
- refName: row.axis.contig,
120
- ...rowSpan(row.axis, row.bp),
121
- },
125
+ region: { assemblyName: assembly, ...span },
122
126
  })
123
127
  }
124
128
  return {
@@ -142,26 +146,14 @@ export const withWalkRowViews = withLaunchViews
142
146
  if (!row?.axis) {
143
147
  return undefined
144
148
  }
145
- const haplotype =
146
- row.haplotype === undefined
147
- ? undefined
148
- : `${row.sample}#${row.haplotype}`
149
- const assembly =
150
- index === 0
151
- ? self.graphRegion?.assemblyName
152
- : resolveLocationAssembly(self.assemblyResolver, {
153
- sample: row.sample,
154
- haplotype,
155
- })
149
+ const { sample, haplotype, assembly, span } = self.rowLocation(
150
+ row,
151
+ row.axis,
152
+ )
156
153
  return {
157
154
  label: row.label,
158
- assembly,
159
- location: {
160
- sample: row.sample,
161
- haplotype,
162
- refName: row.axis.contig,
163
- ...rowSpan(row.axis, row.bp),
164
- },
155
+ assembly: index === 0 ? self.graphRegion?.assemblyName : assembly,
156
+ location: { sample, haplotype, ...span },
165
157
  }
166
158
  },
167
159
  // Each row's genes as offsets along its bar: the reference row's from
@@ -225,7 +225,7 @@ async function pathModel(count: number) {
225
225
  )
226
226
  const model = stateModelFactory().create({ type: 'GraphGenomeView' })
227
227
  await model.loadGFA(pathGFA(count), 'paths')
228
- model.setDrawPaths(true)
228
+ model.setLayer('paths', true)
229
229
  return model
230
230
  }
231
231
 
@@ -68,6 +68,18 @@ test('vamos motifs and an unnamed array fall back to a motif or locus name', ()
68
68
  )
69
69
  })
70
70
 
71
+ test("an unnamed array's locus name reads the same in every locale", () => {
72
+ const format = Number.prototype.toLocaleString
73
+ const german = vi
74
+ .spyOn(Number.prototype, 'toLocaleString')
75
+ .mockImplementation(function (this: number, locales, options) {
76
+ return format.call(this, locales ?? 'de-DE', options)
77
+ })
78
+ const [array] = repeatArraysFrom([{ ...base, period: 5548 }])
79
+ german.mockRestore()
80
+ expect(array!.name).toBe('chr6:160,616,003-160,646,753')
81
+ })
82
+
71
83
  test('a feature stating no unit is left out, and rows sort by start', () => {
72
84
  const arrays = repeatArraysFrom([
73
85
  { ...base, start: 5000, end: 6000, period: 2 },
@@ -150,6 +162,17 @@ test("VCF 4.5 <CNV:TR>: each allele's length is its runs' RB", () => {
150
162
  })
151
163
  })
152
164
 
165
+ // a haploid chrX genotype is written '.', and a sample may have no GT at all
166
+ test('a missing genotype calls no allele', () => {
167
+ const [array] = repeatArraysFrom([
168
+ {
169
+ ...tandem,
170
+ samples: { HG00097: { GT: ['.'] }, HG00099: {}, HG00128: { GT: ['1'] } },
171
+ },
172
+ ])
173
+ expect(array!.calls).toEqual({ HG00128: [{ bp: 49 }] })
174
+ })
175
+
153
176
  test('a run stating only RUL and RUC is their product, and a summary record states no calls', () => {
154
177
  const [array] = repeatArraysFrom([
155
178
  {
@@ -1,4 +1,6 @@
1
- import type { Feature } from '@jbrowse/core/util'
1
+ import { namedTrack, ownField, trackLabel } from '../trackFeatures'
2
+
3
+ import type { FeatureLike, TrackChoice } from '../trackFeatures'
2
4
 
3
5
  export interface RepeatCall {
4
6
  bp: number
@@ -46,24 +48,15 @@ export const REPEAT_ADAPTER_TYPES = new Set([
46
48
  export const REPEAT_TRACK_HINT =
47
49
  /repeat|tandem|\bstr\b|vntr|trf|trgt|vamos|expansion|microsat/i
48
50
 
49
- export function pickRepeatTrack<
50
- T extends { trackId: string; name?: string; adapterType: string },
51
- >(tracks: T[], named: string) {
51
+ export function pickRepeatTrack<T extends TrackChoice>(
52
+ tracks: T[],
53
+ named: string,
54
+ ) {
52
55
  if (named) {
53
- return tracks.find(t => t.trackId === named)
56
+ return namedTrack(tracks, named)
54
57
  }
55
58
  const candidates = tracks.filter(t => REPEAT_ADAPTER_TYPES.has(t.adapterType))
56
- return candidates.find(t =>
57
- REPEAT_TRACK_HINT.test(`${t.trackId} ${t.name ?? ''}`),
58
- )
59
- }
60
-
61
- type FeatureLike = Feature | Record<string, unknown>
62
-
63
- function own(f: FeatureLike, name: string): unknown {
64
- return typeof (f as Feature).get === 'function'
65
- ? (f as Feature).get(name)
66
- : (f as Record<string, unknown>)[name]
59
+ return candidates.find(t => REPEAT_TRACK_HINT.test(trackLabel(t)))
67
60
  }
68
61
 
69
62
  // A TRGT repeat catalogue is a BED whose name column packs the record:
@@ -72,7 +65,7 @@ function own(f: FeatureLike, name: string): unknown {
72
65
  const PACKED_NAME = /^\w+=[^;]*(;\w+=[^;]*)*$/
73
66
 
74
67
  function packedName(f: FeatureLike) {
75
- const name = own(f, 'name')
68
+ const name = ownField(f, 'name')
76
69
  if (typeof name !== 'string' || !PACKED_NAME.test(name)) {
77
70
  return undefined
78
71
  }
@@ -87,11 +80,11 @@ function packedName(f: FeatureLike) {
87
80
  // A field at the feature's top level, in a VCF record's INFO, or in a packed
88
81
  // name column, in that order.
89
82
  function field(f: FeatureLike, name: string): unknown {
90
- const top = own(f, name)
83
+ const top = ownField(f, name)
91
84
  if (top !== undefined) {
92
85
  return top
93
86
  }
94
- const info = own(f, 'INFO') as Record<string, unknown> | undefined
87
+ const info = ownField(f, 'INFO') as Record<string, unknown> | undefined
95
88
  return info?.[name] ?? packedName(f)?.[name]
96
89
  }
97
90
 
@@ -173,7 +166,7 @@ const TANDEM_REPEAT = '<CNV:TR>'
173
166
  // its runs' RB, or unit length times count where RB is missing. An allele that
174
167
  // is not <CNV:TR>, or has a run stating neither, has none.
175
168
  function tandemAlleleBp(f: FeatureLike) {
176
- const alts = strings(own(f, 'ALT'))
169
+ const alts = strings(ownField(f, 'ALT'))
177
170
  if (!alts.includes(TANDEM_REPEAT)) {
178
171
  return undefined
179
172
  }
@@ -216,7 +209,7 @@ function genotyped(
216
209
  ) {
217
210
  const phased = gt?.includes('|')
218
211
  return (gt ?? '').split(/[/|]/).flatMap((index, k): RepeatCall[] => {
219
- const i = Number(index)
212
+ const i = index === '' ? NaN : Number(index)
220
213
  const bp = i > 0 ? alleleBp[i - 1] : referenceBp
221
214
  if (!Number.isInteger(i) || bp === undefined) {
222
215
  return []
@@ -230,7 +223,7 @@ function callsOf(
230
223
  alleleBp: (number | undefined)[] | undefined,
231
224
  referenceBp: number,
232
225
  ) {
233
- const samples = own(f, 'samples') as
226
+ const samples = ownField(f, 'samples') as
234
227
  Record<string, Record<string, unknown>> | undefined
235
228
  const called: Record<string, RepeatCall[]> = {}
236
229
  for (const [sample, fields] of Object.entries(samples ?? {})) {
@@ -280,7 +273,7 @@ export function repeatArraysFrom(features: FeatureLike[]): RepeatArray[] {
280
273
  (stated && !PACKED_NAME.test(stated) ? stated : undefined) ??
281
274
  (motif && motif.length <= 12
282
275
  ? `(${motif})n`
283
- : `${refName}:${(start + 1).toLocaleString()}-${end.toLocaleString()}`),
276
+ : `${refName}:${(start + 1).toLocaleString('en-US')}-${end.toLocaleString('en-US')}`),
284
277
  refName,
285
278
  start,
286
279
  end,