react-msaview 7.0.0 → 8.1.0

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 (335) hide show
  1. package/README.md +11 -10
  2. package/bundle/index.js +122 -111
  3. package/bundle/index.js.map +4 -4
  4. package/dist/calculateBlocks.js +5 -3
  5. package/dist/calculateBlocks.js.map +1 -1
  6. package/dist/colorSchemes.js +1 -2
  7. package/dist/colorSchemes.js.map +1 -1
  8. package/dist/columnStats.d.ts +28 -0
  9. package/dist/columnStats.js +34 -0
  10. package/dist/columnStats.js.map +1 -0
  11. package/dist/components/DragHandle.d.ts +3 -2
  12. package/dist/components/DragHandle.js +11 -5
  13. package/dist/components/DragHandle.js.map +1 -1
  14. package/dist/components/Loading.js +2 -8
  15. package/dist/components/Loading.js.map +1 -1
  16. package/dist/components/MSAView.js +17 -6
  17. package/dist/components/MSAView.js.map +1 -1
  18. package/dist/components/MSAViewer.d.ts +64 -7
  19. package/dist/components/MSAViewer.js +128 -23
  20. package/dist/components/MSAViewer.js.map +1 -1
  21. package/dist/components/PortalTooltip.d.ts +11 -0
  22. package/dist/components/PortalTooltip.js +29 -0
  23. package/dist/components/PortalTooltip.js.map +1 -0
  24. package/dist/components/ResizeHandles.js +11 -29
  25. package/dist/components/ResizeHandles.js.map +1 -1
  26. package/dist/components/SequenceTextArea.js +3 -4
  27. package/dist/components/SequenceTextArea.js.map +1 -1
  28. package/dist/components/Track.js +43 -24
  29. package/dist/components/Track.js.map +1 -1
  30. package/dist/components/dialogs/AboutDialog.js +6 -6
  31. package/dist/components/dialogs/AboutDialog.js.map +1 -1
  32. package/dist/components/dialogs/AnnotationFileDialog.js +5 -2
  33. package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -1
  34. package/dist/components/dialogs/ExportSVGDialog.js +6 -4
  35. package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
  36. package/dist/components/dialogs/FeatureDialog.js +8 -3
  37. package/dist/components/dialogs/FeatureDialog.js.map +1 -1
  38. package/dist/components/dialogs/TrackInfoDialog.js +1 -1
  39. package/dist/components/dialogs/TrackInfoDialog.js.map +1 -1
  40. package/dist/components/header/GappynessSlider.js +6 -11
  41. package/dist/components/header/GappynessSlider.js.map +1 -1
  42. package/dist/components/header/Header.js +4 -1
  43. package/dist/components/header/Header.js.map +1 -1
  44. package/dist/components/header/LoadWarnings.js +6 -23
  45. package/dist/components/header/LoadWarnings.js.map +1 -1
  46. package/dist/components/header/ScrollZoomIcons.d.ts +6 -0
  47. package/dist/components/header/ScrollZoomIcons.js +35 -0
  48. package/dist/components/header/ScrollZoomIcons.js.map +1 -0
  49. package/dist/components/header/TreeSettingsMenu.js +1 -3
  50. package/dist/components/header/TreeSettingsMenu.js.map +1 -1
  51. package/dist/components/header/UnshareableDataWarning.js +2 -4
  52. package/dist/components/header/UnshareableDataWarning.js.map +1 -1
  53. package/dist/components/header/ZoomControls.js +49 -6
  54. package/dist/components/header/ZoomControls.js.map +1 -1
  55. package/dist/components/header/getAnnotationsMenu.js +3 -6
  56. package/dist/components/header/getAnnotationsMenu.js.map +1 -1
  57. package/dist/components/header/settingsMenuItems.js +12 -9
  58. package/dist/components/header/settingsMenuItems.js.map +1 -1
  59. package/dist/components/import/ImportForm.js +1 -1
  60. package/dist/components/import/ImportForm.js.map +1 -1
  61. package/dist/components/import/util.js +1 -2
  62. package/dist/components/import/util.js.map +1 -1
  63. package/dist/components/minimap/Minimap.js +1 -2
  64. package/dist/components/minimap/Minimap.js.map +1 -1
  65. package/dist/components/msa/AnnotationLegend.js +27 -19
  66. package/dist/components/msa/AnnotationLegend.js.map +1 -1
  67. package/dist/components/msa/MSACanvas.js +2 -4
  68. package/dist/components/msa/MSACanvas.js.map +1 -1
  69. package/dist/components/msa/MSACanvasBlock.js +13 -15
  70. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  71. package/dist/components/msa/MSAMouseoverCanvas.js +2 -1
  72. package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
  73. package/dist/components/msa/domainBandCursor.d.ts +7 -11
  74. package/dist/components/msa/domainBandCursor.js +7 -11
  75. package/dist/components/msa/domainBandCursor.js.map +1 -1
  76. package/dist/components/msa/drawFeatureSpans.d.ts +59 -0
  77. package/dist/components/msa/drawFeatureSpans.js +113 -0
  78. package/dist/components/msa/drawFeatureSpans.js.map +1 -0
  79. package/dist/components/msa/legendRows.d.ts +8 -0
  80. package/dist/components/msa/legendRows.js +16 -0
  81. package/dist/components/msa/legendRows.js.map +1 -0
  82. package/dist/components/msa/msaRaster.d.ts +24 -31
  83. package/dist/components/msa/msaRaster.js +30 -39
  84. package/dist/components/msa/msaRaster.js.map +1 -1
  85. package/dist/components/msa/packDomainLanes.d.ts +20 -0
  86. package/dist/components/msa/packDomainLanes.js +33 -0
  87. package/dist/components/msa/packDomainLanes.js.map +1 -0
  88. package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +3 -1
  89. package/dist/components/msa/renderBoxFeatureCanvasBlock.js +50 -87
  90. package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
  91. package/dist/components/msa/renderHighlights.d.ts +15 -13
  92. package/dist/components/msa/renderHighlights.js +43 -20
  93. package/dist/components/msa/renderHighlights.js.map +1 -1
  94. package/dist/components/msa/renderMSABlock.js +12 -12
  95. package/dist/components/msa/renderMSABlock.js.map +1 -1
  96. package/dist/components/msa/tileColor.js +4 -7
  97. package/dist/components/msa/tileColor.js.map +1 -1
  98. package/dist/components/msa/useMsaBlockMouse.d.ts +5 -3
  99. package/dist/components/msa/useMsaBlockMouse.js +6 -6
  100. package/dist/components/msa/useMsaBlockMouse.js.map +1 -1
  101. package/dist/components/rowpanels/RowPanelBlock.d.ts +9 -0
  102. package/dist/components/rowpanels/RowPanelBlock.js +86 -0
  103. package/dist/components/rowpanels/RowPanelBlock.js.map +1 -0
  104. package/dist/components/rowpanels/RowPanelHeaders.d.ts +12 -0
  105. package/dist/components/rowpanels/RowPanelHeaders.js +38 -0
  106. package/dist/components/rowpanels/RowPanelHeaders.js.map +1 -0
  107. package/dist/components/{msa/ColumnStats.d.ts → rowpanels/RowPanels.d.ts} +2 -2
  108. package/dist/components/rowpanels/RowPanels.js +32 -0
  109. package/dist/components/rowpanels/RowPanels.js.map +1 -0
  110. package/dist/components/rowpanels/headerLayout.d.ts +1 -0
  111. package/dist/components/rowpanels/headerLayout.js +5 -0
  112. package/dist/components/rowpanels/headerLayout.js.map +1 -0
  113. package/dist/components/rowpanels/renderFeaturePanel.d.ts +21 -0
  114. package/dist/components/rowpanels/renderFeaturePanel.js +37 -0
  115. package/dist/components/rowpanels/renderFeaturePanel.js.map +1 -0
  116. package/dist/components/rowpanels/renderRowPanel.d.ts +23 -0
  117. package/dist/components/rowpanels/renderRowPanel.js +22 -0
  118. package/dist/components/rowpanels/renderRowPanel.js.map +1 -0
  119. package/dist/components/rowpanels/renderStrip.d.ts +20 -0
  120. package/dist/components/rowpanels/renderStrip.js +26 -0
  121. package/dist/components/rowpanels/renderStrip.js.map +1 -0
  122. package/dist/components/tracks/TrackBlocks.js +3 -5
  123. package/dist/components/tracks/TrackBlocks.js.map +1 -1
  124. package/dist/components/tracks/TrackTooltipContent.d.ts +15 -0
  125. package/dist/components/tracks/TrackTooltipContent.js +109 -0
  126. package/dist/components/tracks/TrackTooltipContent.js.map +1 -0
  127. package/dist/components/tracks/drawTracks.d.ts +4 -4
  128. package/dist/components/tracks/drawTracks.js +25 -39
  129. package/dist/components/tracks/drawTracks.js.map +1 -1
  130. package/dist/components/tracks/useTrackHover.d.ts +22 -0
  131. package/dist/components/tracks/useTrackHover.js +30 -0
  132. package/dist/components/tracks/useTrackHover.js.map +1 -0
  133. package/dist/components/tree/CladeLabels.d.ts +12 -0
  134. package/dist/components/tree/CladeLabels.js +48 -0
  135. package/dist/components/tree/CladeLabels.js.map +1 -0
  136. package/dist/components/tree/TreeBranchMenu.js +1 -1
  137. package/dist/components/tree/TreeBranchMenu.js.map +1 -1
  138. package/dist/components/tree/TreeCanvas.js +5 -4
  139. package/dist/components/tree/TreeCanvas.js.map +1 -1
  140. package/dist/components/tree/TreeCanvasBlock.js +4 -21
  141. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  142. package/dist/components/tree/TreeNodeMenu.js +1 -1
  143. package/dist/components/tree/TreeNodeMenu.js.map +1 -1
  144. package/dist/components/tree/TreeOverview.d.ts +14 -0
  145. package/dist/components/tree/TreeOverview.js +75 -0
  146. package/dist/components/tree/TreeOverview.js.map +1 -0
  147. package/dist/components/tree/TreeRuler.js +9 -13
  148. package/dist/components/tree/TreeRuler.js.map +1 -1
  149. package/dist/components/tree/cladeBrackets.d.ts +25 -0
  150. package/dist/components/tree/cladeBrackets.js +51 -0
  151. package/dist/components/tree/cladeBrackets.js.map +1 -0
  152. package/dist/components/tree/dialogs/TreeNodeInfoDialog.js +2 -2
  153. package/dist/components/tree/dialogs/TreeNodeInfoDialog.js.map +1 -1
  154. package/dist/components/tree/renderTreeCanvas.js +160 -25
  155. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  156. package/dist/components/tree/renderTreeOverview.d.ts +52 -0
  157. package/dist/components/tree/renderTreeOverview.js +138 -0
  158. package/dist/components/tree/renderTreeOverview.js.map +1 -0
  159. package/dist/components/tree/useTreeHover.js +2 -4
  160. package/dist/components/tree/useTreeHover.js.map +1 -1
  161. package/dist/constants.d.ts +16 -0
  162. package/dist/constants.js +52 -22
  163. package/dist/constants.js.map +1 -1
  164. package/dist/createPaletteMap.d.ts +7 -6
  165. package/dist/createPaletteMap.js +10 -9
  166. package/dist/createPaletteMap.js.map +1 -1
  167. package/dist/element.d.ts +13 -0
  168. package/dist/element.js +269 -0
  169. package/dist/element.js.map +1 -0
  170. package/dist/featureFields.d.ts +11 -0
  171. package/dist/featureFields.js +17 -0
  172. package/dist/featureFields.js.map +1 -0
  173. package/dist/flatToTree.js +1 -2
  174. package/dist/flatToTree.js.map +1 -1
  175. package/dist/ggplotPalettes.d.ts +10 -0
  176. package/dist/ggplotPalettes.js +58 -0
  177. package/dist/ggplotPalettes.js.map +1 -1
  178. package/dist/hierarchy.d.ts +27 -0
  179. package/dist/hierarchy.js +66 -9
  180. package/dist/hierarchy.js.map +1 -1
  181. package/dist/index.d.ts +9 -1
  182. package/dist/index.js +5 -3
  183. package/dist/index.js.map +1 -1
  184. package/dist/model/DataModel.js +2 -5
  185. package/dist/model/DataModel.js.map +1 -1
  186. package/dist/model/msaModel.d.ts +3 -2
  187. package/dist/model/msaModel.js +3 -2
  188. package/dist/model/msaModel.js.map +1 -1
  189. package/dist/model/treeModel.d.ts +30 -0
  190. package/dist/model/treeModel.js +37 -1
  191. package/dist/model/treeModel.js.map +1 -1
  192. package/dist/model.d.ts +549 -294
  193. package/dist/model.js +1412 -621
  194. package/dist/model.js.map +1 -1
  195. package/dist/mount.d.ts +11 -0
  196. package/dist/mount.js +22 -0
  197. package/dist/mount.js.map +1 -0
  198. package/dist/neighborJoining.js +8 -32
  199. package/dist/neighborJoining.js.map +1 -1
  200. package/dist/renderToStaticMarkup.d.ts +10 -19
  201. package/dist/renderToStaticMarkup.js +13 -24
  202. package/dist/renderToStaticMarkup.js.map +1 -1
  203. package/dist/renderToSvg.js +180 -56
  204. package/dist/renderToSvg.js.map +1 -1
  205. package/dist/rowCoordinateCalculations.d.ts +4 -6
  206. package/dist/rowCoordinateCalculations.js +4 -6
  207. package/dist/rowCoordinateCalculations.js.map +1 -1
  208. package/dist/scales.d.ts +22 -0
  209. package/dist/scales.js +30 -0
  210. package/dist/scales.js.map +1 -0
  211. package/dist/seqPosToGlobalCol.d.ts +2 -3
  212. package/dist/seqPosToGlobalCol.js +2 -3
  213. package/dist/seqPosToGlobalCol.js.map +1 -1
  214. package/dist/sequenceLogo.d.ts +6 -11
  215. package/dist/sequenceLogo.js +6 -11
  216. package/dist/sequenceLogo.js.map +1 -1
  217. package/dist/stripDefault.d.ts +7 -14
  218. package/dist/stripDefault.js +7 -14
  219. package/dist/stripDefault.js.map +1 -1
  220. package/dist/svgTestUtil.d.ts +186 -70
  221. package/dist/types.d.ts +221 -21
  222. package/dist/umd.d.ts +2 -0
  223. package/dist/umd.js +4 -2
  224. package/dist/umd.js.map +1 -1
  225. package/dist/useCanvasAutorun.d.ts +5 -10
  226. package/dist/useCanvasAutorun.js +5 -10
  227. package/dist/useCanvasAutorun.js.map +1 -1
  228. package/dist/useMsaSvgFigure.d.ts +20 -0
  229. package/dist/useMsaSvgFigure.js +46 -0
  230. package/dist/useMsaSvgFigure.js.map +1 -0
  231. package/dist/useWheelScroll.js +0 -4
  232. package/dist/useWheelScroll.js.map +1 -1
  233. package/dist/util.d.ts +5 -6
  234. package/dist/util.js +13 -6
  235. package/dist/util.js.map +1 -1
  236. package/dist/version.d.ts +1 -1
  237. package/dist/version.js +1 -1
  238. package/package.json +19 -12
  239. package/src/calculateBlocks.ts +5 -3
  240. package/src/colorSchemes.ts +1 -2
  241. package/src/columnStats.ts +58 -0
  242. package/src/components/DragHandle.tsx +18 -7
  243. package/src/components/Loading.tsx +4 -36
  244. package/src/components/MSAView.tsx +19 -5
  245. package/src/components/MSAViewer.tsx +240 -28
  246. package/src/components/PortalTooltip.tsx +47 -0
  247. package/src/components/ResizeHandles.tsx +17 -32
  248. package/src/components/SequenceTextArea.tsx +3 -4
  249. package/src/components/Track.tsx +57 -19
  250. package/src/components/dialogs/AboutDialog.tsx +35 -6
  251. package/src/components/dialogs/AnnotationFileDialog.tsx +10 -1
  252. package/src/components/dialogs/ExportSVGDialog.tsx +8 -3
  253. package/src/components/dialogs/FeatureDialog.tsx +11 -3
  254. package/src/components/dialogs/TrackInfoDialog.tsx +1 -1
  255. package/src/components/header/GappynessSlider.tsx +6 -11
  256. package/src/components/header/Header.tsx +7 -1
  257. package/src/components/header/LoadWarnings.tsx +11 -25
  258. package/src/components/header/ScrollZoomIcons.tsx +52 -0
  259. package/src/components/header/TreeSettingsMenu.tsx +1 -3
  260. package/src/components/header/UnshareableDataWarning.tsx +2 -4
  261. package/src/components/header/ZoomControls.tsx +74 -14
  262. package/src/components/header/getAnnotationsMenu.ts +3 -6
  263. package/src/components/header/settingsMenuItems.ts +13 -8
  264. package/src/components/import/ImportForm.tsx +3 -5
  265. package/src/components/import/ImportFormExamples.tsx +2 -2
  266. package/src/components/import/util.ts +1 -2
  267. package/src/components/minimap/Minimap.tsx +1 -2
  268. package/src/components/msa/AnnotationLegend.tsx +48 -38
  269. package/src/components/msa/MSACanvas.tsx +3 -8
  270. package/src/components/msa/MSACanvasBlock.tsx +13 -16
  271. package/src/components/msa/MSAMouseoverCanvas.tsx +2 -1
  272. package/src/components/msa/domainBandCursor.ts +7 -11
  273. package/src/components/msa/drawFeatureSpans.ts +205 -0
  274. package/src/components/msa/legendRows.ts +24 -0
  275. package/src/components/msa/msaRaster.ts +30 -39
  276. package/src/components/msa/packDomainLanes.ts +36 -0
  277. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +67 -123
  278. package/src/components/msa/renderHighlights.ts +53 -20
  279. package/src/components/msa/renderMSABlock.ts +14 -11
  280. package/src/components/msa/tileColor.ts +4 -7
  281. package/src/components/msa/useMsaBlockMouse.ts +6 -6
  282. package/src/components/rowpanels/RowPanelBlock.tsx +139 -0
  283. package/src/components/rowpanels/RowPanelHeaders.tsx +57 -0
  284. package/src/components/rowpanels/RowPanels.tsx +77 -0
  285. package/src/components/rowpanels/headerLayout.ts +5 -0
  286. package/src/components/rowpanels/renderFeaturePanel.ts +66 -0
  287. package/src/components/rowpanels/renderRowPanel.ts +41 -0
  288. package/src/components/rowpanels/renderStrip.ts +46 -0
  289. package/src/components/tracks/TrackBlocks.tsx +3 -5
  290. package/src/components/tracks/TrackTooltipContent.tsx +156 -0
  291. package/src/components/tracks/drawTracks.ts +25 -50
  292. package/src/components/tracks/useTrackHover.ts +42 -0
  293. package/src/components/tree/CladeLabels.tsx +77 -0
  294. package/src/components/tree/TreeBranchMenu.tsx +1 -2
  295. package/src/components/tree/TreeCanvas.tsx +2 -3
  296. package/src/components/tree/TreeCanvasBlock.tsx +8 -32
  297. package/src/components/tree/TreeNodeMenu.tsx +1 -2
  298. package/src/components/tree/TreeOverview.tsx +106 -0
  299. package/src/components/tree/TreeRuler.tsx +11 -22
  300. package/src/components/tree/cladeBrackets.ts +73 -0
  301. package/src/components/tree/dialogs/TreeNodeInfoDialog.tsx +2 -2
  302. package/src/components/tree/renderTreeCanvas.ts +242 -21
  303. package/src/components/tree/renderTreeOverview.ts +212 -0
  304. package/src/components/tree/useTreeHover.ts +2 -4
  305. package/src/constants.ts +61 -22
  306. package/src/createPaletteMap.ts +10 -9
  307. package/src/element.ts +321 -0
  308. package/src/featureFields.ts +19 -0
  309. package/src/flatToTree.ts +1 -2
  310. package/src/ggplotPalettes.ts +60 -0
  311. package/src/hierarchy.ts +77 -9
  312. package/src/index.ts +31 -7
  313. package/src/model/DataModel.ts +2 -5
  314. package/src/model/msaModel.ts +3 -2
  315. package/src/model/treeModel.ts +42 -0
  316. package/src/model.ts +1677 -643
  317. package/src/mount.tsx +32 -0
  318. package/src/neighborJoining.ts +8 -32
  319. package/src/renderToStaticMarkup.ts +13 -24
  320. package/src/renderToSvg.tsx +335 -68
  321. package/src/rowCoordinateCalculations.ts +4 -6
  322. package/src/scales.ts +47 -0
  323. package/src/seqPosToGlobalCol.ts +2 -3
  324. package/src/sequenceLogo.ts +6 -11
  325. package/src/stripDefault.ts +7 -14
  326. package/src/types.ts +260 -45
  327. package/src/umd.ts +4 -2
  328. package/src/useCanvasAutorun.ts +5 -10
  329. package/src/useMsaSvgFigure.ts +72 -0
  330. package/src/useWheelScroll.ts +0 -4
  331. package/src/util.ts +15 -6
  332. package/src/version.ts +1 -1
  333. package/dist/components/msa/ColumnStats.js +0 -42
  334. package/dist/components/msa/ColumnStats.js.map +0 -1
  335. package/src/components/msa/ColumnStats.tsx +0 -54
package/src/constants.ts CHANGED
@@ -5,22 +5,22 @@ export const defaultHeight = 550
5
5
  export const defaultScrollX = 0
6
6
  export const defaultScrollY = 0
7
7
  export const defaultCurrentAlignment = 0
8
- // Annotations are loaded on purpose -- a gff filehandle, a file picked in the
9
- // dialog -- so they are drawn unless the reader has said otherwise. Since only
10
- // the "no" is stored, a shared link with the overlay hidden reopens hidden, and
11
- // one that never mentions it opens showing whatever annotations it carries.
8
+ // Only a hidden overlay is stored, so a link that never mentions it opens
9
+ // showing its annotations.
12
10
  export const defaultShowDomains = true
13
11
  export const defaultShowDomainLegend = true
14
12
  export const defaultHideGaps = true
15
13
  export const defaultAllowedGappyness = 100
16
14
  export const defaultSubFeatureRows = false
17
- // row height for each stacked feature when subFeatureRows is on
15
+ // height of one sub-row when subFeatureRows is on, unless the row has more
16
+ // lanes than fit at this height (see renderBoxFeatureCanvasBlock)
18
17
  export const subFeatureRowHeight = 4
18
+ // height of the bar the overlay draws under a row in letter-color mode
19
+ export const domainUnderlineHeight = 3
19
20
 
20
- // Feature types that are ordinal *segments* of a single transcript (exons and
21
- // the like) rather than categorical domains. Their identity is their position,
22
- // so the overlay alternates two shades to mark boundaries and labels them by
23
- // number, instead of assigning every one a distinct hue + legend row.
21
+ // Feature types that are ordinal segments of one transcript. The overlay
22
+ // alternates two shades across them and labels them by number, where a domain
23
+ // type gets its own hue and legend row.
24
24
  export const segmentFeatureTypes = new Set([
25
25
  'exon',
26
26
  'CDS',
@@ -35,12 +35,24 @@ export const segmentShades = ['#9fb6d4', '#d4dcea']
35
35
  export const defaultDrawMsaLetters = true
36
36
  export const defaultScrollZoom = false
37
37
 
38
+ // Which cell dimensions a wheel zoom scales. 'horizontal' holds the row height,
39
+ // which fixes the label and letter font size while the columns compress.
40
+ // 'vertical' does the reverse.
41
+ export const scrollZoomAxes = ['both', 'horizontal', 'vertical'] as const
42
+ export type ScrollZoomAxis = (typeof scrollZoomAxes)[number]
43
+ export const defaultScrollZoomAxis: ScrollZoomAxis = 'both'
44
+
38
45
  // Cell size floors for drawing residue letters and tree labels. Below these a
39
- // 500px block holds thousands of glyphs, and fillText -- which no sprite atlas
40
- // beats, measured -- dominates every zoom frame.
46
+ // 500px block holds thousands of glyphs, and fillText dominates every zoom
47
+ // frame. A sprite atlas measured slower.
41
48
  export const minLetterRowHeight = 8
42
49
  export const minLetterColWidth = 5
43
50
 
51
+ // A feature span draws its label at two pixels under its height, capped at 11,
52
+ // so a span shorter than this holds no readable text: an underline bar, a
53
+ // sub-row band or a features panel over small rows
54
+ export const minFeatureLabelHeight = 9
55
+
44
56
  // Tree labels are measured once at this size and scaled to the current font
45
57
  // size, so a vertical zoom never re-measures the tree.
46
58
  export const labelReferenceFontSize = 16
@@ -65,18 +77,45 @@ export const defaultTreeWidth = 300
65
77
  export const defaultShowBranchLen = true
66
78
  export const defaultDrawTree = true
67
79
  export const defaultDrawNodeBubbles = true
80
+ export const defaultDrawNodeLabels = false
81
+ export const defaultShowTreeOverview = false
82
+ export const defaultOverviewHeight = 120
68
83
 
69
- // Neighbor joining refuses above this many rows. The distance matrix is O(n^2*L)
70
- // and the join loop O(n^3), both on the main thread with no progress and no
71
- // cancel: 400 rows measured 1.5s, 800 rows 10s, and 1600 would be about seventy.
72
- // See agent-docs/ideas/neighbor-joining-scaling.md for why the RapidNJ fix is a
73
- // real algorithm change rather than a port, and why capping is the honest answer
74
- // -- NJ on thousands of sequences is the wrong tool however fast it runs.
84
+ // Neighbor joining does not run above this many rows. The distance matrix is
85
+ // O(n^2*L) and the join loop O(n^3), both on the main thread with no progress
86
+ // and no cancel: 400 rows measured 1.5s, 800 rows 10s, and 1600 would be about
87
+ // seventy. See agent-docs/ideas/neighbor-joining-scaling.md.
75
88
  export const maxNeighborJoiningRows = 500
76
89
 
77
- // The largest inline document the snapshot carries. Past this a pasted or
78
- // locally-opened file stays in the live model and leaves the snapshot, since a
79
- // session or a shared URL holding megabytes of sequence is not a link anyone
80
- // can send. `unshareableData` reads the same number, so what the viewer warns
81
- // about and what the snapshot drops cannot drift apart.
90
+ // The opacity a `rowTint` encoding draws its scale's color at, so the residues
91
+ // under the wash stay readable. A color the producer already gave an alpha
92
+ // keeps it.
93
+ export const rowTintAlpha = 0.25
94
+
95
+ // The fill a `clades` record with the `highlight` mark draws behind its rows,
96
+ // and the opacity a producer's own color takes when it carries none.
97
+ export const cladeHighlightColor = '#fff3c4'
98
+ export const cladeHighlightAlpha = 0.6
99
+
100
+ // The largest inline document the snapshot carries. A larger pasted or
101
+ // locally-opened file stays in the live model and out of the snapshot, keeping
102
+ // shared URLs sendable. `unshareableData` warns using the same number.
82
103
  export const maxInlineSnapshotBytes = 50_000
104
+
105
+ // Height of the band the `rowPanels` headers draw in, above the strips in the
106
+ // top area beside the tree ruler and the minimap. A panel wider than the band
107
+ // is tall writes its header across, where it has more room.
108
+ export const rowPanelHeaderHeight = 56
109
+
110
+ // Height of the branch-length scale bar above the tree.
111
+ export const treeScaleBarHeight = 22
112
+
113
+ // Width of a `features` row panel that names none, wide enough for a gene
114
+ // neighborhood of a few arrows to read.
115
+ export const defaultFeaturePanelWidth = 200
116
+
117
+ // The alignment's overlay canvas draws the mouseover, the persisted highlights
118
+ // and the row tints over the residues, and the legend key floats above that,
119
+ // so a tint reaching the top right corner leaves the key readable.
120
+ export const msaOverlayZIndex = 1000
121
+ export const legendZIndex = msaOverlayZIndex + 1
@@ -3,17 +3,18 @@ import { colord } from 'colord'
3
3
  import palettes from './ggplotPalettes.ts'
4
4
 
5
5
  /**
6
- * Creates a map from keys to colors. Up to the largest ggplot palette (8 keys)
7
- * we use the hand-picked ggplot colors; beyond that — e.g. a gene-structure
8
- * overlay with one feature per exon — we generate evenly spaced HSL hues so
9
- * every key still gets a distinct, stable color instead of running off the end
10
- * of the palette.
6
+ * Creates a map from keys to colors. `palette` names the colors to take in
7
+ * order, such as the list a scale asked for by name; without it the keys take
8
+ * the ggplot palette of their own size. Beyond the colors available — a
9
+ * gene-structure overlay with one feature per exon, or nine keys on an
10
+ * eight-color palette — every key gets an evenly spaced HSL hue instead, so no
11
+ * two keys share a color.
11
12
  */
12
- export function createPaletteMap(keys: string[]) {
13
+ export function createPaletteMap(keys: string[], palette?: readonly string[]) {
13
14
  const n = keys.length
14
- if (n <= palettes.length) {
15
- const palette = palettes[n - 1]!
16
- return Object.fromEntries(keys.map((key, i) => [key, palette[i]!]))
15
+ const colors = palette ?? palettes[n - 1]
16
+ if (colors && n <= colors.length) {
17
+ return Object.fromEntries(keys.map((key, i) => [key, colors[i]!]))
17
18
  }
18
19
  return Object.fromEntries(
19
20
  keys.map((key, i) => [
package/src/element.ts ADDED
@@ -0,0 +1,321 @@
1
+ import { when } from 'mobx'
2
+
3
+ import { mount } from './mount.tsx'
4
+
5
+ import type { MSAViewerProps } from './components/MSAViewer.tsx'
6
+ import type { MsaViewModel } from './model.ts'
7
+ import type { MountedViewer } from './mount.tsx'
8
+ import type {
9
+ Cell,
10
+ ColumnTrackSpec,
11
+ Highlight,
12
+ ResidueMapping,
13
+ Viewport,
14
+ } from './types.ts'
15
+
16
+ type ElementData = Pick<
17
+ MSAViewerProps,
18
+ 'msa' | 'tree' | 'gff' | 'highlights' | 'columnTracks' | 'residueMappings'
19
+ >
20
+
21
+ interface NightingaleManager extends HTMLElement {
22
+ register: (element: HTMLElement) => void
23
+ unregister: (element: HTMLElement) => void
24
+ }
25
+
26
+ const ownAttributes = [
27
+ 'msa-url',
28
+ 'tree-url',
29
+ 'gff-url',
30
+ 'color-scheme',
31
+ 'height',
32
+ 'hide-header',
33
+ 'theme',
34
+ 'reference-row',
35
+ 'tree-area-width',
36
+ ] as const
37
+
38
+ // the attributes a <nightingale-manager> writes on the elements it holds
39
+ const nightingaleAttributes = ['display-start', 'display-end', 'highlight']
40
+
41
+ function location(uri: string | null) {
42
+ return uri ? { uri, locationType: 'UriLocation' as const } : undefined
43
+ }
44
+
45
+ function numberAttribute(value: string | null | undefined) {
46
+ const n = value ? Number(value) : Number.NaN
47
+ return Number.isFinite(n) ? n : undefined
48
+ }
49
+
50
+ // Nightingale writes a highlight as "start:end,start:end", 1-based inclusive
51
+ function parseHighlight(value: string | null, row?: string): Highlight[] {
52
+ return (value ?? '').split(',').flatMap(span => {
53
+ const [start, end] = span.split(':').map(numberAttribute)
54
+ return start === undefined ? [] : [{ row, start, end: end ?? start }]
55
+ })
56
+ }
57
+
58
+ function residueAtColumn(model: MsaViewModel, row: string, column: number) {
59
+ const visible = model.globalColToVisibleCol(column - 1)
60
+ return visible === undefined
61
+ ? undefined
62
+ : model.visibleColToSeqPosOneBased(row, visible)
63
+ }
64
+
65
+ // the first and last residues of `row` inside the viewport's columns
66
+ function residueRange(model: MsaViewModel, row: string, viewport: Viewport) {
67
+ let start: number | undefined
68
+ let end: number | undefined
69
+ for (let c = viewport.startColumn; c <= viewport.endColumn && !start; c++) {
70
+ start = residueAtColumn(model, row, c)
71
+ }
72
+ for (let c = viewport.endColumn; c >= viewport.startColumn && !end; c--) {
73
+ end = residueAtColumn(model, row, c)
74
+ }
75
+ return start && end ? { start, end } : undefined
76
+ }
77
+
78
+ /**
79
+ * Registers a custom element that renders MSAViewer, for pages with no React.
80
+ * Attributes: msa-url, tree-url, gff-url, color-scheme, height, hide-header,
81
+ * theme, tree-area-width and reference-row. Properties: msa, tree and gff text, highlights,
82
+ * columnTracks and residueMappings. Events: cell-hover, cell-click and
83
+ * viewport-change, each with the MSAViewer callback's value as detail.
84
+ *
85
+ * Inside a <nightingale-manager>, the element registers with it and speaks its
86
+ * protocol: it follows the display-start, display-end and highlight attributes
87
+ * the manager writes, and reports its own range and hovered residue as change
88
+ * events. Positions are residues of reference-row, or columns without one.
89
+ */
90
+ export function defineMsaElement(tagName = 'jbrowse-msa') {
91
+ const existing = customElements.get(tagName)
92
+ if (existing) {
93
+ return existing
94
+ }
95
+
96
+ class MsaElement extends HTMLElement {
97
+ static observedAttributes = [...ownAttributes, ...nightingaleAttributes]
98
+
99
+ #viewer?: MountedViewer
100
+ #model?: MsaViewModel
101
+ #manager?: NightingaleManager
102
+ #cancelRange?: () => void
103
+ #rangePending = false
104
+ #reported?: { start: number; end: number }
105
+ #data: ElementData = {}
106
+
107
+ get msa() {
108
+ return this.#data.msa
109
+ }
110
+ set msa(value: string | undefined) {
111
+ this.#setData({ msa: value })
112
+ }
113
+ get tree() {
114
+ return this.#data.tree
115
+ }
116
+ set tree(value: string | undefined) {
117
+ this.#setData({ tree: value })
118
+ }
119
+ get gff() {
120
+ return this.#data.gff
121
+ }
122
+ set gff(value: string | undefined) {
123
+ this.#setData({ gff: value })
124
+ }
125
+ get highlights() {
126
+ return this.#data.highlights
127
+ }
128
+ set highlights(value: Highlight[] | undefined) {
129
+ this.#setData({ highlights: value })
130
+ }
131
+ get columnTracks() {
132
+ return this.#data.columnTracks
133
+ }
134
+ set columnTracks(value: ColumnTrackSpec[] | undefined) {
135
+ this.#setData({ columnTracks: value })
136
+ }
137
+ get residueMappings() {
138
+ return this.#data.residueMappings
139
+ }
140
+ set residueMappings(value: ResidueMapping[] | undefined) {
141
+ this.#setData({ residueMappings: value })
142
+ }
143
+
144
+ get #referenceRow() {
145
+ return this.getAttribute('reference-row') ?? undefined
146
+ }
147
+
148
+ #setData(data: ElementData) {
149
+ this.#data = { ...this.#data, ...data }
150
+ this.#viewer?.update(this.#props())
151
+ }
152
+
153
+ #props(): MSAViewerProps {
154
+ const theme = this.getAttribute('theme')
155
+ return {
156
+ ...this.#data,
157
+ msaFilehandle: location(this.getAttribute('msa-url')),
158
+ treeFilehandle: location(this.getAttribute('tree-url')),
159
+ gffFilehandle: location(this.getAttribute('gff-url')),
160
+ colorScheme: this.getAttribute('color-scheme') ?? undefined,
161
+ height: numberAttribute(this.getAttribute('height')),
162
+ treeAreaWidth: numberAttribute(this.getAttribute('tree-area-width')),
163
+ hideHeader: this.hasAttribute('hide-header'),
164
+ theme: theme === 'dark' ? 'dark' : 'light',
165
+ onCellHover: cell => {
166
+ this.#emit('cell-hover', cell)
167
+ this.#reportHover(cell)
168
+ },
169
+ onCellClick: cell => {
170
+ this.#emit('cell-click', cell)
171
+ },
172
+ onViewportChange: viewport => {
173
+ this.#emit('viewport-change', viewport)
174
+ this.#reportRange(viewport)
175
+ },
176
+ onModel: model => {
177
+ this.#model = model
178
+ this.#followRange()
179
+ this.#followHighlight()
180
+ },
181
+ }
182
+ }
183
+
184
+ #emit(type: string, detail: unknown) {
185
+ this.dispatchEvent(new CustomEvent(type, { detail }))
186
+ }
187
+
188
+ #nightingaleChange(detail: Record<string, unknown>) {
189
+ if (this.#manager) {
190
+ this.dispatchEvent(
191
+ new CustomEvent('change', {
192
+ detail,
193
+ bubbles: true,
194
+ cancelable: true,
195
+ }),
196
+ )
197
+ }
198
+ }
199
+
200
+ #reportHover(cell: Cell | undefined) {
201
+ const model = this.#model
202
+ const row = this.#referenceRow
203
+ const position =
204
+ cell && model && row
205
+ ? residueAtColumn(model, row, cell.column)
206
+ : cell?.column
207
+ this.#nightingaleChange({
208
+ type: 'highlight',
209
+ value: position === undefined ? null : `${position}:${position}`,
210
+ })
211
+ }
212
+
213
+ #reportRange(viewport: Viewport | undefined) {
214
+ const model = this.#model
215
+ const row = this.#referenceRow
216
+ if (!viewport || !model) {
217
+ return
218
+ }
219
+ const range = row
220
+ ? residueRange(model, row, viewport)
221
+ : { start: viewport.startColumn, end: viewport.endColumn }
222
+ const shownStart = numberAttribute(this.getAttribute('display-start'))
223
+ const shownEnd = numberAttribute(this.getAttribute('display-end'))
224
+ // a range the manager just wrote comes back within a column of itself,
225
+ // since the viewer shows whole columns
226
+ const echo =
227
+ range &&
228
+ shownStart !== undefined &&
229
+ shownEnd !== undefined &&
230
+ Math.abs(range.start - shownStart) <= 1 &&
231
+ Math.abs(range.end - shownEnd) <= 1
232
+ if (range && !echo) {
233
+ this.#reported = range
234
+ this.#nightingaleChange({
235
+ 'display-start': range.start,
236
+ 'display-end': range.end,
237
+ })
238
+ }
239
+ }
240
+
241
+ #followRange() {
242
+ const model = this.#model
243
+ const start = numberAttribute(this.getAttribute('display-start'))
244
+ const end = numberAttribute(this.getAttribute('display-end'))
245
+ const reported = this.#reported
246
+ // the manager writes the range this element reported back onto it, and
247
+ // zooming to it again would snap the reader's own zoom to whole residues
248
+ if (reported && reported.start === start && reported.end === end) {
249
+ return
250
+ }
251
+ this.#cancelRange?.()
252
+ if (model && start !== undefined && end !== undefined) {
253
+ const row = this.#referenceRow
254
+ this.#cancelRange = when(
255
+ () => model.viewInitialized && model.numColumns > 0,
256
+ () => {
257
+ model.zoomToRegion({ row, start, end })
258
+ },
259
+ )
260
+ }
261
+ }
262
+
263
+ #followHighlight() {
264
+ const spans = parseHighlight(
265
+ this.getAttribute('highlight'),
266
+ this.#referenceRow,
267
+ )
268
+ if (spans.length) {
269
+ this.#model?.applyHighlight('nightingale', spans)
270
+ } else {
271
+ this.#model?.clearHighlight('nightingale')
272
+ }
273
+ }
274
+
275
+ connectedCallback() {
276
+ if (!this.style.display) {
277
+ this.style.display = 'block'
278
+ }
279
+ this.#viewer = mount(this, this.#props())
280
+ const manager = this.closest<NightingaleManager>('nightingale-manager')
281
+ if (manager) {
282
+ void customElements.whenDefined('nightingale-manager').then(() => {
283
+ if (this.isConnected) {
284
+ this.#manager = manager
285
+ manager.register(this)
286
+ }
287
+ })
288
+ }
289
+ }
290
+
291
+ disconnectedCallback() {
292
+ this.#manager?.unregister(this)
293
+ this.#manager = undefined
294
+ this.#cancelRange?.()
295
+ this.#viewer?.destroy()
296
+ this.#viewer = undefined
297
+ this.#model = undefined
298
+ }
299
+
300
+ attributeChangedCallback(name: string) {
301
+ if (name === 'display-start' || name === 'display-end') {
302
+ // the manager writes the two attributes one after the other, and a
303
+ // zoom between them reports a range that overwrites the second
304
+ if (!this.#rangePending) {
305
+ this.#rangePending = true
306
+ queueMicrotask(() => {
307
+ this.#rangePending = false
308
+ this.#followRange()
309
+ })
310
+ }
311
+ } else if (name === 'highlight') {
312
+ this.#followHighlight()
313
+ } else {
314
+ this.#viewer?.update(this.#props())
315
+ }
316
+ }
317
+ }
318
+
319
+ customElements.define(tagName, MsaElement)
320
+ return MsaElement
321
+ }
@@ -0,0 +1,19 @@
1
+ import type { Annotation } from './types.ts'
2
+
3
+ /**
4
+ * The value a feature gives an encoded field: an Annotation property, else one
5
+ * of the GFF attributes the parser kept.
6
+ */
7
+ export function featureField(annotation: Annotation, field: string) {
8
+ const own = (annotation as unknown as Record<string, unknown>)[field]
9
+ const value = own ?? annotation.attributes?.[field]
10
+ return typeof value === 'string' ? value : undefined
11
+ }
12
+
13
+ /**
14
+ * The name a feature answers to in a transform: its GFF `Name` attribute, else
15
+ * the name the parser gave it.
16
+ */
17
+ export function featureName(annotation: Annotation) {
18
+ return featureField(annotation, 'Name') ?? featureField(annotation, 'name')
19
+ }
package/src/flatToTree.ts CHANGED
@@ -22,8 +22,7 @@ export function flatToTree(items: FlatItem[]): TreeNode {
22
22
  item.id,
23
23
  {
24
24
  id: `${item.id}`,
25
- // the label is what NCBI's own tree viewer shows; falling back to the
26
- // node id would render a tree of bare numbers
25
+ // NCBI's tree viewer shows the label
27
26
  name: item.label || `${item.id}`,
28
27
  ...(length !== undefined && Number.isFinite(length)
29
28
  ? { length }
@@ -19,3 +19,63 @@ const palettes = [
19
19
  ]
20
20
 
21
21
  export default palettes
22
+
23
+ /**
24
+ * Fixed categorical palettes a scale names. `ggplot` is not one of them: it is
25
+ * the ramp above, which picks its colors from the number of keys.
26
+ */
27
+ export const namedPalettes: Record<string, readonly string[]> = {
28
+ set1: [
29
+ '#e41a1c',
30
+ '#377eb8',
31
+ '#4daf4a',
32
+ '#984ea3',
33
+ '#ff7f00',
34
+ '#ffff33',
35
+ '#a65628',
36
+ '#f781bf',
37
+ '#999999',
38
+ ],
39
+ dark2: [
40
+ '#1b9e77',
41
+ '#d95f02',
42
+ '#7570b3',
43
+ '#e7298a',
44
+ '#66a61e',
45
+ '#e6ab02',
46
+ '#a6761d',
47
+ '#666666',
48
+ ],
49
+ // Okabe-Ito, which stays distinguishable under the common forms of color
50
+ // blindness
51
+ okabeito: [
52
+ '#e69f00',
53
+ '#56b4e9',
54
+ '#009e73',
55
+ '#f0e442',
56
+ '#0072b2',
57
+ '#d55e00',
58
+ '#cc79a7',
59
+ '#000000',
60
+ ],
61
+ tableau: [
62
+ '#4e79a7',
63
+ '#f28e2b',
64
+ '#e15759',
65
+ '#76b7b2',
66
+ '#59a14f',
67
+ '#edc948',
68
+ '#b07aa1',
69
+ '#ff9da7',
70
+ '#9c755f',
71
+ '#bab0ac',
72
+ ],
73
+ }
74
+
75
+ /**
76
+ * The colors a palette name asks for. `ggplot`, an unknown name, and no name at
77
+ * all give undefined, which `createPaletteMap` answers with the hue ramp.
78
+ */
79
+ export function paletteNamed(name?: string) {
80
+ return name === undefined ? undefined : namedPalettes[name]
81
+ }
package/src/hierarchy.ts CHANGED
@@ -38,10 +38,8 @@ export interface HierarchyNode<T = NodeWithIds> extends CoreHierarchyNode<T> {
38
38
  collapsedTipXFar?: number
39
39
  }
40
40
 
41
- // The generic traversals live in @gmod/newick now, shared with the tree sidebar
42
- // in jbrowse-components. They are iterative there for the reason they were here:
43
- // a phylogenetic tree can be a caterpillar, whose depth equals its leaf count,
44
- // and the recursive form overflows the stack somewhere past 5000 tips.
41
+ // The @gmod/newick traversals are iterative: a caterpillar tree's depth equals
42
+ // its leaf count, and recursion overflows the stack past about 5000 tips.
45
43
  export {
46
44
  descendants,
47
45
  find,
@@ -54,11 +52,8 @@ export {
54
52
  }
55
53
  export type { HierarchyLink } from '@gmod/newick'
56
54
 
57
- // coreHierarchy builds base nodes, and the layout fields above are written onto
58
- // them afterwards by this package. Every one of those fields is optional, so the
59
- // two node types are mutually assignable and this needs no cast -- it exists
60
- // only to declare the wider return type, which is what lets those later
61
- // assignments typecheck.
55
+ // declares the wider return type so the layout code can assign the optional
56
+ // fields above onto coreHierarchy's nodes
62
57
  export function hierarchy<T>(
63
58
  data: T,
64
59
  childrenAccessor: (d: T) => T[] | undefined,
@@ -219,3 +214,76 @@ export function setBrLength(d: HierarchyNode, y0: number, k: number) {
219
214
  }
220
215
  }
221
216
  }
217
+
218
+ /**
219
+ * Every leaf by name, with a name two leaves share mapping to undefined: a tip
220
+ * set naming it cannot say which tip it means.
221
+ */
222
+ export function leafIndex<T extends { name: string }>(root: HierarchyNode<T>) {
223
+ const index = new Map<string, HierarchyNode<T> | undefined>()
224
+ for (const leaf of leaves(root)) {
225
+ const { name } = leaf.data
226
+ index.set(name, index.has(name) ? undefined : leaf)
227
+ }
228
+ return index
229
+ }
230
+
231
+ /**
232
+ * The most recent common ancestor of a set of tip names, or undefined when a
233
+ * name is not a tip of this tree or names two of them.
234
+ *
235
+ * Each name lifts through `parent` to the ancestor the names before it share,
236
+ * so the walk holds two nodes. Recording a root-to-tip path per name instead
237
+ * costs the depth in memory per name, and a caterpillar tree's depth is its tip
238
+ * count. `index` is `leafIndex(root)`, which a caller resolving several tip
239
+ * sets against one tree builds once.
240
+ */
241
+ export function mrca<T extends { name: string }>(
242
+ root: HierarchyNode<T>,
243
+ names: string[],
244
+ index = leafIndex(root),
245
+ ) {
246
+ let node: HierarchyNode<T> | undefined
247
+ for (const name of names) {
248
+ let other = index.get(name)
249
+ if (!other) {
250
+ return undefined
251
+ }
252
+ if (!node) {
253
+ node = other
254
+ continue
255
+ }
256
+ while (node.depth > other.depth) {
257
+ node = node.parent!
258
+ }
259
+ while (other.depth > node.depth) {
260
+ other = other.parent!
261
+ }
262
+ while (node !== other) {
263
+ node = node.parent!
264
+ other = other.parent!
265
+ }
266
+ }
267
+ return node
268
+ }
269
+
270
+ /**
271
+ * The deepest node whose subtree covers a run of rows, given in the row-space
272
+ * coordinates clusterLayout writes to xMin/xMax. A subtree's leaves are one
273
+ * contiguous run, so at most one child per level covers the run and the descent
274
+ * visits the tree's depth rather than its nodes.
275
+ */
276
+ export function nodeCoveringRows<T>(
277
+ root: HierarchyNode<T>,
278
+ top: number,
279
+ bottom: number,
280
+ ) {
281
+ let node = root
282
+ for (;;) {
283
+ const child = node.children?.find(c => c.xMin! <= top && c.xMax! >= bottom)
284
+ if (!child) {
285
+ return node
286
+ }
287
+ node = child
288
+ }
289
+ }