react-msaview 6.5.0 → 8.0.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 (414) hide show
  1. package/README.md +13 -11
  2. package/bundle/index.js +122 -112
  3. package/bundle/index.js.map +4 -4
  4. package/dist/colorSchemes.js +1 -2
  5. package/dist/colorSchemes.js.map +1 -1
  6. package/dist/columnStats.d.ts +28 -0
  7. package/dist/columnStats.js +34 -0
  8. package/dist/columnStats.js.map +1 -0
  9. package/dist/components/DragHandle.d.ts +3 -2
  10. package/dist/components/DragHandle.js +11 -5
  11. package/dist/components/DragHandle.js.map +1 -1
  12. package/dist/components/Loading.js +2 -2
  13. package/dist/components/Loading.js.map +1 -1
  14. package/dist/components/MSAView.js +17 -6
  15. package/dist/components/MSAView.js.map +1 -1
  16. package/dist/components/MSAViewer.d.ts +69 -4
  17. package/dist/components/MSAViewer.js +201 -8
  18. package/dist/components/MSAViewer.js.map +1 -1
  19. package/dist/components/PortalTooltip.d.ts +11 -0
  20. package/dist/components/PortalTooltip.js +29 -0
  21. package/dist/components/PortalTooltip.js.map +1 -0
  22. package/dist/components/ResizeHandles.d.ts +3 -3
  23. package/dist/components/ResizeHandles.js +13 -24
  24. package/dist/components/ResizeHandles.js.map +1 -1
  25. package/dist/components/SequenceTextArea.js +3 -3
  26. package/dist/components/SequenceTextArea.js.map +1 -1
  27. package/dist/components/Track.js +41 -22
  28. package/dist/components/Track.js.map +1 -1
  29. package/dist/components/dialogs/{InterProScanDialog.d.ts → AnnotationFileDialog.d.ts} +2 -2
  30. package/dist/components/dialogs/AnnotationFileDialog.js +92 -0
  31. package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -0
  32. package/dist/components/dialogs/ExportSVGDialog.js +5 -3
  33. package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
  34. package/dist/components/dialogs/FeatureDialog.js +13 -9
  35. package/dist/components/dialogs/FeatureDialog.js.map +1 -1
  36. package/dist/components/dialogs/TrackInfoDialog.js +1 -1
  37. package/dist/components/dialogs/TrackInfoDialog.js.map +1 -1
  38. package/dist/components/header/GappynessSlider.js +6 -11
  39. package/dist/components/header/GappynessSlider.js.map +1 -1
  40. package/dist/components/header/Header.js +7 -0
  41. package/dist/components/header/Header.js.map +1 -1
  42. package/dist/components/header/HeaderStatusArea.js +0 -2
  43. package/dist/components/header/HeaderStatusArea.js.map +1 -1
  44. package/dist/components/header/LoadWarnings.d.ts +6 -0
  45. package/dist/components/header/LoadWarnings.js +35 -0
  46. package/dist/components/header/LoadWarnings.js.map +1 -0
  47. package/dist/components/header/MultiAlignmentSelector.js +0 -0
  48. package/dist/components/header/MultiAlignmentSelector.js.map +1 -1
  49. package/dist/components/header/ScrollZoomIcons.d.ts +6 -0
  50. package/dist/components/header/ScrollZoomIcons.js +35 -0
  51. package/dist/components/header/ScrollZoomIcons.js.map +1 -0
  52. package/dist/components/header/TreeSettingsMenu.js +7 -1
  53. package/dist/components/header/TreeSettingsMenu.js.map +1 -1
  54. package/dist/components/header/UnshareableDataWarning.d.ts +6 -0
  55. package/dist/components/header/UnshareableDataWarning.js +40 -0
  56. package/dist/components/header/UnshareableDataWarning.js.map +1 -0
  57. package/dist/components/header/ZoomControls.js +49 -6
  58. package/dist/components/header/ZoomControls.js.map +1 -1
  59. package/dist/components/header/getAnnotationsMenu.js +11 -14
  60. package/dist/components/header/getAnnotationsMenu.js.map +1 -1
  61. package/dist/components/header/settingsMenuItems.js +10 -7
  62. package/dist/components/header/settingsMenuItems.js.map +1 -1
  63. package/dist/components/import/ImportForm.js +1 -1
  64. package/dist/components/import/ImportForm.js.map +1 -1
  65. package/dist/components/import/ImportFormExamples.js +2 -6
  66. package/dist/components/import/ImportFormExamples.js.map +1 -1
  67. package/dist/components/import/util.js +2 -0
  68. package/dist/components/import/util.js.map +1 -1
  69. package/dist/components/minimap/Minimap.js +1 -2
  70. package/dist/components/minimap/Minimap.js.map +1 -1
  71. package/dist/components/msa/AnnotationLegend.js +27 -19
  72. package/dist/components/msa/AnnotationLegend.js.map +1 -1
  73. package/dist/components/msa/MSACanvas.js +1 -1
  74. package/dist/components/msa/MSACanvas.js.map +1 -1
  75. package/dist/components/msa/MSACanvasBlock.js +17 -20
  76. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  77. package/dist/components/msa/MSAMouseoverCanvas.js +2 -1
  78. package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
  79. package/dist/components/msa/domainBandCursor.d.ts +7 -11
  80. package/dist/components/msa/domainBandCursor.js +7 -11
  81. package/dist/components/msa/domainBandCursor.js.map +1 -1
  82. package/dist/components/msa/drawFeatureSpans.d.ts +53 -0
  83. package/dist/components/msa/drawFeatureSpans.js +78 -0
  84. package/dist/components/msa/drawFeatureSpans.js.map +1 -0
  85. package/dist/components/msa/legendRows.d.ts +8 -0
  86. package/dist/components/msa/legendRows.js +16 -0
  87. package/dist/components/msa/legendRows.js.map +1 -0
  88. package/dist/components/msa/msaRaster.d.ts +30 -27
  89. package/dist/components/msa/msaRaster.js +193 -90
  90. package/dist/components/msa/msaRaster.js.map +1 -1
  91. package/dist/components/msa/packDomainLanes.d.ts +20 -0
  92. package/dist/components/msa/packDomainLanes.js +33 -0
  93. package/dist/components/msa/packDomainLanes.js.map +1 -0
  94. package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +3 -1
  95. package/dist/components/msa/renderBoxFeatureCanvasBlock.js +51 -87
  96. package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
  97. package/dist/components/msa/renderHighlights.d.ts +44 -4
  98. package/dist/components/msa/renderHighlights.js +99 -11
  99. package/dist/components/msa/renderHighlights.js.map +1 -1
  100. package/dist/components/msa/renderMSABlock.d.ts +1 -2
  101. package/dist/components/msa/renderMSABlock.js +28 -31
  102. package/dist/components/msa/renderMSABlock.js.map +1 -1
  103. package/dist/components/msa/renderMSAMouseover.js +9 -30
  104. package/dist/components/msa/renderMSAMouseover.js.map +1 -1
  105. package/dist/components/msa/tileColor.js +4 -7
  106. package/dist/components/msa/tileColor.js.map +1 -1
  107. package/dist/components/msa/useMsaBlockMouse.d.ts +5 -3
  108. package/dist/components/msa/useMsaBlockMouse.js +6 -6
  109. package/dist/components/msa/useMsaBlockMouse.js.map +1 -1
  110. package/dist/components/renderCtx.d.ts +1 -1
  111. package/dist/components/rowpanels/RowPanelBlock.d.ts +9 -0
  112. package/dist/components/rowpanels/RowPanelBlock.js +86 -0
  113. package/dist/components/rowpanels/RowPanelBlock.js.map +1 -0
  114. package/dist/components/rowpanels/RowPanelHeaders.d.ts +12 -0
  115. package/dist/components/rowpanels/RowPanelHeaders.js +38 -0
  116. package/dist/components/rowpanels/RowPanelHeaders.js.map +1 -0
  117. package/dist/components/{msa/ColumnStats.d.ts → rowpanels/RowPanels.d.ts} +2 -2
  118. package/dist/components/rowpanels/RowPanels.js +32 -0
  119. package/dist/components/rowpanels/RowPanels.js.map +1 -0
  120. package/dist/components/rowpanels/headerLayout.d.ts +1 -0
  121. package/dist/components/rowpanels/headerLayout.js +5 -0
  122. package/dist/components/rowpanels/headerLayout.js.map +1 -0
  123. package/dist/components/rowpanels/renderFeaturePanel.d.ts +21 -0
  124. package/dist/components/rowpanels/renderFeaturePanel.js +37 -0
  125. package/dist/components/rowpanels/renderFeaturePanel.js.map +1 -0
  126. package/dist/components/rowpanels/renderRowPanel.d.ts +23 -0
  127. package/dist/components/rowpanels/renderRowPanel.js +22 -0
  128. package/dist/components/rowpanels/renderRowPanel.js.map +1 -0
  129. package/dist/components/rowpanels/renderStrip.d.ts +20 -0
  130. package/dist/components/rowpanels/renderStrip.js +26 -0
  131. package/dist/components/rowpanels/renderStrip.js.map +1 -0
  132. package/dist/components/tracks/TrackBlocks.js +13 -16
  133. package/dist/components/tracks/TrackBlocks.js.map +1 -1
  134. package/dist/components/tracks/TrackTooltipContent.d.ts +15 -0
  135. package/dist/components/tracks/TrackTooltipContent.js +109 -0
  136. package/dist/components/tracks/TrackTooltipContent.js.map +1 -0
  137. package/dist/components/tracks/drawTracks.d.ts +30 -12
  138. package/dist/components/tracks/drawTracks.js +127 -34
  139. package/dist/components/tracks/drawTracks.js.map +1 -1
  140. package/dist/components/tracks/useTrackHover.d.ts +22 -0
  141. package/dist/components/tracks/useTrackHover.js +30 -0
  142. package/dist/components/tracks/useTrackHover.js.map +1 -0
  143. package/dist/components/tree/CladeLabels.d.ts +12 -0
  144. package/dist/components/tree/CladeLabels.js +48 -0
  145. package/dist/components/tree/CladeLabels.js.map +1 -0
  146. package/dist/components/tree/TreeCanvas.js +6 -4
  147. package/dist/components/tree/TreeCanvas.js.map +1 -1
  148. package/dist/components/tree/TreeCanvasBlock.js +6 -21
  149. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  150. package/dist/components/tree/TreeOverview.d.ts +14 -0
  151. package/dist/components/tree/TreeOverview.js +75 -0
  152. package/dist/components/tree/TreeOverview.js.map +1 -0
  153. package/dist/components/tree/TreeRuler.js +11 -5
  154. package/dist/components/tree/TreeRuler.js.map +1 -1
  155. package/dist/components/tree/cladeBrackets.d.ts +25 -0
  156. package/dist/components/tree/cladeBrackets.js +51 -0
  157. package/dist/components/tree/cladeBrackets.js.map +1 -0
  158. package/dist/components/tree/dialogs/TreeNodeInfoDialog.js +2 -2
  159. package/dist/components/tree/dialogs/TreeNodeInfoDialog.js.map +1 -1
  160. package/dist/components/tree/renderTreeCanvas.d.ts +0 -1
  161. package/dist/components/tree/renderTreeCanvas.js +221 -47
  162. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  163. package/dist/components/tree/renderTreeOverview.d.ts +52 -0
  164. package/dist/components/tree/renderTreeOverview.js +138 -0
  165. package/dist/components/tree/renderTreeOverview.js.map +1 -0
  166. package/dist/components/tree/scaleBar.d.ts +10 -0
  167. package/dist/components/tree/scaleBar.js +24 -0
  168. package/dist/components/tree/scaleBar.js.map +1 -0
  169. package/dist/components/tree/useTreeHover.js +2 -4
  170. package/dist/components/tree/useTreeHover.js.map +1 -1
  171. package/dist/constants.d.ts +18 -1
  172. package/dist/constants.js +51 -8
  173. package/dist/constants.js.map +1 -1
  174. package/dist/createPaletteMap.d.ts +7 -6
  175. package/dist/createPaletteMap.js +10 -9
  176. package/dist/createPaletteMap.js.map +1 -1
  177. package/dist/element.d.ts +13 -0
  178. package/dist/element.js +269 -0
  179. package/dist/element.js.map +1 -0
  180. package/dist/featureFields.d.ts +11 -0
  181. package/dist/featureFields.js +17 -0
  182. package/dist/featureFields.js.map +1 -0
  183. package/dist/fetchUtils.d.ts +7 -7
  184. package/dist/fetchUtils.js +4 -32
  185. package/dist/fetchUtils.js.map +1 -1
  186. package/dist/flatToTree.js +1 -2
  187. package/dist/flatToTree.js.map +1 -1
  188. package/dist/ggplotPalettes.d.ts +10 -0
  189. package/dist/ggplotPalettes.js +58 -0
  190. package/dist/ggplotPalettes.js.map +1 -1
  191. package/dist/hierarchy.d.ts +29 -1
  192. package/dist/hierarchy.js +82 -19
  193. package/dist/hierarchy.js.map +1 -1
  194. package/dist/index.d.ts +10 -1
  195. package/dist/index.js +10 -2
  196. package/dist/index.js.map +1 -1
  197. package/dist/model/DataModel.js +4 -4
  198. package/dist/model/DataModel.js.map +1 -1
  199. package/dist/model/msaModel.d.ts +3 -2
  200. package/dist/model/msaModel.js +3 -2
  201. package/dist/model/msaModel.js.map +1 -1
  202. package/dist/model/treeModel.d.ts +30 -0
  203. package/dist/model/treeModel.js +37 -1
  204. package/dist/model/treeModel.js.map +1 -1
  205. package/dist/model.d.ts +792 -259
  206. package/dist/model.js +2092 -675
  207. package/dist/model.js.map +1 -1
  208. package/dist/mount.d.ts +11 -0
  209. package/dist/mount.js +22 -0
  210. package/dist/mount.js.map +1 -0
  211. package/dist/neighborJoining.js +8 -32
  212. package/dist/neighborJoining.js.map +1 -1
  213. package/dist/renderToStaticMarkup.d.ts +15 -13
  214. package/dist/renderToStaticMarkup.js +37 -25
  215. package/dist/renderToStaticMarkup.js.map +1 -1
  216. package/dist/renderToSvg.js +198 -68
  217. package/dist/renderToSvg.js.map +1 -1
  218. package/dist/rowCoordinateCalculations.d.ts +26 -28
  219. package/dist/rowCoordinateCalculations.js +13 -39
  220. package/dist/rowCoordinateCalculations.js.map +1 -1
  221. package/dist/scales.d.ts +22 -0
  222. package/dist/scales.js +30 -0
  223. package/dist/scales.js.map +1 -0
  224. package/dist/seqPosToGlobalCol.d.ts +2 -3
  225. package/dist/seqPosToGlobalCol.js +5 -4
  226. package/dist/seqPosToGlobalCol.js.map +1 -1
  227. package/dist/sequenceLogo.d.ts +6 -11
  228. package/dist/sequenceLogo.js +6 -11
  229. package/dist/sequenceLogo.js.map +1 -1
  230. package/dist/stripDefault.d.ts +7 -14
  231. package/dist/stripDefault.js +7 -14
  232. package/dist/stripDefault.js.map +1 -1
  233. package/dist/svgTestUtil.d.ts +306 -124
  234. package/dist/types.d.ts +295 -3
  235. package/dist/umd.d.ts +2 -0
  236. package/dist/umd.js +4 -2
  237. package/dist/umd.js.map +1 -1
  238. package/dist/useCanvasAutorun.d.ts +5 -10
  239. package/dist/useCanvasAutorun.js +5 -10
  240. package/dist/useCanvasAutorun.js.map +1 -1
  241. package/dist/useMsaSvgFigure.d.ts +20 -0
  242. package/dist/useMsaSvgFigure.js +46 -0
  243. package/dist/useMsaSvgFigure.js.map +1 -0
  244. package/dist/util.d.ts +10 -4
  245. package/dist/util.js +37 -10
  246. package/dist/util.js.map +1 -1
  247. package/dist/version.d.ts +1 -1
  248. package/dist/version.js +1 -1
  249. package/dist/wuss.d.ts +14 -0
  250. package/dist/wuss.js +55 -0
  251. package/dist/wuss.js.map +1 -0
  252. package/package.json +23 -14
  253. package/src/colorSchemes.ts +1 -2
  254. package/src/columnStats.ts +58 -0
  255. package/src/components/DragHandle.tsx +18 -7
  256. package/src/components/Loading.tsx +14 -8
  257. package/src/components/MSAView.tsx +19 -5
  258. package/src/components/MSAViewer.tsx +326 -10
  259. package/src/components/PortalTooltip.tsx +47 -0
  260. package/src/components/ResizeHandles.tsx +21 -36
  261. package/src/components/SequenceTextArea.tsx +3 -3
  262. package/src/components/Track.tsx +55 -18
  263. package/src/components/dialogs/AnnotationFileDialog.tsx +162 -0
  264. package/src/components/dialogs/ExportSVGDialog.tsx +7 -2
  265. package/src/components/dialogs/FeatureDialog.tsx +13 -9
  266. package/src/components/dialogs/TrackInfoDialog.tsx +1 -1
  267. package/src/components/header/GappynessSlider.tsx +6 -11
  268. package/src/components/header/Header.tsx +10 -0
  269. package/src/components/header/HeaderStatusArea.tsx +0 -5
  270. package/src/components/header/LoadWarnings.tsx +48 -0
  271. package/src/components/header/MultiAlignmentSelector.tsx +0 -0
  272. package/src/components/header/ScrollZoomIcons.tsx +52 -0
  273. package/src/components/header/TreeSettingsMenu.tsx +7 -1
  274. package/src/components/header/UnshareableDataWarning.tsx +56 -0
  275. package/src/components/header/ZoomControls.tsx +74 -14
  276. package/src/components/header/getAnnotationsMenu.ts +13 -17
  277. package/src/components/header/settingsMenuItems.ts +11 -6
  278. package/src/components/import/ImportForm.tsx +2 -2
  279. package/src/components/import/ImportFormExamples.tsx +4 -8
  280. package/src/components/import/util.ts +2 -0
  281. package/src/components/minimap/Minimap.tsx +1 -2
  282. package/src/components/msa/AnnotationLegend.tsx +48 -38
  283. package/src/components/msa/MSACanvas.tsx +3 -5
  284. package/src/components/msa/MSACanvasBlock.tsx +17 -21
  285. package/src/components/msa/MSAMouseoverCanvas.tsx +2 -1
  286. package/src/components/msa/domainBandCursor.ts +7 -11
  287. package/src/components/msa/drawFeatureSpans.ts +150 -0
  288. package/src/components/msa/legendRows.ts +24 -0
  289. package/src/components/msa/msaRaster.ts +236 -88
  290. package/src/components/msa/packDomainLanes.ts +36 -0
  291. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +69 -123
  292. package/src/components/msa/renderHighlights.ts +141 -10
  293. package/src/components/msa/renderMSABlock.ts +29 -38
  294. package/src/components/msa/renderMSAMouseover.ts +9 -39
  295. package/src/components/msa/tileColor.ts +4 -7
  296. package/src/components/msa/useMsaBlockMouse.ts +6 -6
  297. package/src/components/renderCtx.ts +1 -0
  298. package/src/components/rowpanels/RowPanelBlock.tsx +139 -0
  299. package/src/components/rowpanels/RowPanelHeaders.tsx +57 -0
  300. package/src/components/rowpanels/RowPanels.tsx +77 -0
  301. package/src/components/rowpanels/headerLayout.ts +5 -0
  302. package/src/components/rowpanels/renderFeaturePanel.ts +66 -0
  303. package/src/components/rowpanels/renderRowPanel.ts +41 -0
  304. package/src/components/rowpanels/renderStrip.ts +46 -0
  305. package/src/components/tracks/TrackBlocks.tsx +13 -18
  306. package/src/components/tracks/TrackTooltipContent.tsx +156 -0
  307. package/src/components/tracks/drawTracks.ts +169 -38
  308. package/src/components/tracks/useTrackHover.ts +42 -0
  309. package/src/components/tree/CladeLabels.tsx +77 -0
  310. package/src/components/tree/TreeCanvas.tsx +4 -3
  311. package/src/components/tree/TreeCanvasBlock.tsx +10 -32
  312. package/src/components/tree/TreeOverview.tsx +106 -0
  313. package/src/components/tree/TreeRuler.tsx +29 -5
  314. package/src/components/tree/cladeBrackets.ts +73 -0
  315. package/src/components/tree/dialogs/TreeNodeInfoDialog.tsx +2 -2
  316. package/src/components/tree/renderTreeCanvas.ts +312 -48
  317. package/src/components/tree/renderTreeOverview.ts +212 -0
  318. package/src/components/tree/scaleBar.ts +23 -0
  319. package/src/components/tree/useTreeHover.ts +2 -4
  320. package/src/constants.ts +61 -8
  321. package/src/createPaletteMap.ts +10 -9
  322. package/src/element.ts +321 -0
  323. package/src/featureFields.ts +19 -0
  324. package/src/fetchUtils.ts +7 -40
  325. package/src/flatToTree.ts +1 -2
  326. package/src/ggplotPalettes.ts +60 -0
  327. package/src/hierarchy.ts +97 -20
  328. package/src/index.ts +46 -6
  329. package/src/model/DataModel.ts +5 -4
  330. package/src/model/msaModel.ts +3 -2
  331. package/src/model/treeModel.ts +42 -0
  332. package/src/model.ts +2492 -793
  333. package/src/mount.tsx +32 -0
  334. package/src/neighborJoining.ts +8 -32
  335. package/src/rawImports.d.ts +9 -0
  336. package/src/renderToStaticMarkup.ts +41 -25
  337. package/src/renderToSvg.tsx +365 -92
  338. package/src/rowCoordinateCalculations.ts +16 -62
  339. package/src/scales.ts +47 -0
  340. package/src/seqPosToGlobalCol.ts +5 -4
  341. package/src/sequenceLogo.ts +6 -11
  342. package/src/stripDefault.ts +7 -14
  343. package/src/types.ts +352 -12
  344. package/src/umd.ts +4 -2
  345. package/src/useCanvasAutorun.ts +5 -10
  346. package/src/useMsaSvgFigure.ts +72 -0
  347. package/src/util.ts +40 -14
  348. package/src/version.ts +1 -1
  349. package/src/wuss.ts +62 -0
  350. package/dist/components/dialogs/InterProScanDialog.js +0 -137
  351. package/dist/components/dialogs/InterProScanDialog.js.map +0 -1
  352. package/dist/components/dialogs/InterProScanFileDialog.d.ts +0 -7
  353. package/dist/components/dialogs/InterProScanFileDialog.js +0 -64
  354. package/dist/components/dialogs/InterProScanFileDialog.js.map +0 -1
  355. package/dist/components/msa/ColumnStats.js +0 -42
  356. package/dist/components/msa/ColumnStats.js.map +0 -1
  357. package/dist/launchInterProScan.d.ts +0 -13
  358. package/dist/launchInterProScan.js +0 -110
  359. package/dist/launchInterProScan.js.map +0 -1
  360. package/dist/useColorContrast.d.ts +0 -10
  361. package/dist/useColorContrast.js +0 -13
  362. package/dist/useColorContrast.js.map +0 -1
  363. package/src/clustalX.test.ts +0 -74
  364. package/src/colSpace.test.ts +0 -67
  365. package/src/collapseLogic.test.ts +0 -115
  366. package/src/colorSchemes.docs.test.ts +0 -57
  367. package/src/columnCounts.test.ts +0 -73
  368. package/src/columnStats.test.ts +0 -94
  369. package/src/columnTracks.test.ts +0 -97
  370. package/src/components/dialogs/InterProScanDialog.tsx +0 -214
  371. package/src/components/dialogs/InterProScanFileDialog.tsx +0 -138
  372. package/src/components/minimap/minimapLayout.test.ts +0 -71
  373. package/src/components/msa/ColumnStats.tsx +0 -54
  374. package/src/components/msa/msaRaster.test.ts +0 -145
  375. package/src/components/msa/renderBoxFeatureCanvasBlock.test.ts +0 -51
  376. package/src/components/tree/renderTreeCanvas.test.ts +0 -296
  377. package/src/dataInitialized.test.ts +0 -42
  378. package/src/domainBands.test.ts +0 -122
  379. package/src/domainLetterColor.test.ts +0 -80
  380. package/src/exportFileName.test.ts +0 -40
  381. package/src/fetchUtils.test.ts +0 -83
  382. package/src/flatToTree.test.ts +0 -51
  383. package/src/hierarchy.test.ts +0 -228
  384. package/src/highlights.test.ts +0 -70
  385. package/src/impgRender.test.tsx +0 -68
  386. package/src/largeTree.test.ts +0 -67
  387. package/src/launchInterProScan.ts +0 -167
  388. package/src/loadErrorDisplay.test.tsx +0 -102
  389. package/src/modelFilehandleLoaders.test.ts +0 -137
  390. package/src/modelReset.test.ts +0 -120
  391. package/src/neighborJoining.test.ts +0 -164
  392. package/src/parseAsn1.test.ts +0 -36
  393. package/src/propertyConservation.test.ts +0 -36
  394. package/src/renderColumnTracksSvg.test.tsx +0 -108
  395. package/src/renderDomainsSvg.test.tsx +0 -118
  396. package/src/renderHighlightsSvg.test.tsx +0 -85
  397. package/src/renderMinimapSvg.test.tsx +0 -62
  398. package/src/renderRasterSvg.test.tsx +0 -144
  399. package/src/renderSequenceLogoSvg.test.tsx +0 -165
  400. package/src/renderToSvg.test.tsx +0 -65
  401. package/src/reset.test.ts +0 -61
  402. package/src/rowCoordinateCalculations.test.ts +0 -211
  403. package/src/rowSpace.test.ts +0 -46
  404. package/src/segmentOverlay.test.ts +0 -70
  405. package/src/seqPosToGlobalCol.test.ts +0 -59
  406. package/src/sequenceLogo.test.ts +0 -88
  407. package/src/setFontSize.test.ts +0 -33
  408. package/src/stripDefault.test.ts +0 -48
  409. package/src/stripDefaultSnapshot.test.ts +0 -87
  410. package/src/treeMetadata.test.ts +0 -39
  411. package/src/useCanvasAutorun.test.tsx +0 -98
  412. package/src/useColorContrast.ts +0 -18
  413. package/src/viewChrome.test.tsx +0 -134
  414. package/src/wheelZoom.test.ts +0 -46
@@ -5,16 +5,30 @@ import { when } from 'mobx'
5
5
 
6
6
  import { visibleRowRange } from './components/getVisibleLeaves.ts'
7
7
  import MinimapSVG from './components/minimap/MinimapSVG.tsx'
8
+ import { legendRows } from './components/msa/legendRows.ts'
8
9
  import { rasterImageHref, rasterSupported } from './components/msa/msaRaster.ts'
9
10
  import { renderBoxFeatureCanvasBlock } from './components/msa/renderBoxFeatureCanvasBlock.ts'
10
- import { renderHighlights } from './components/msa/renderHighlights.ts'
11
+ import { renderPersistentHighlights } from './components/msa/renderHighlights.ts'
11
12
  import { renderMSABlock } from './components/msa/renderMSABlock.ts'
12
13
  import { visibleColRange } from './components/msa/visibleColRange.ts'
14
+ import { headerRunsAcross } from './components/rowpanels/headerLayout.ts'
15
+ import { renderRowPanels } from './components/rowpanels/renderRowPanel.ts'
13
16
  import { renderAllTracks } from './components/tracks/drawTracks.ts'
17
+ import {
18
+ bracketGap,
19
+ cladeHeight,
20
+ cladeInGutter,
21
+ cladeLabelHorizontal,
22
+ cladeLabelOffset,
23
+ } from './components/tree/cladeBrackets.ts'
14
24
  import { renderTreeCanvas } from './components/tree/renderTreeCanvas.ts'
15
- import { renderToStaticMarkup } from './renderToStaticMarkup.ts'
16
- import { colorContrast } from './util.ts'
25
+ import { renderTreeOverview } from './components/tree/renderTreeOverview.ts'
26
+ import { treeScaleBarHeight } from './constants.ts'
27
+ import { measureTextCanvas } from './measureTextCanvas.ts'
28
+ import { renderToStaticMarkup, svgSafeColors } from './renderToStaticMarkup.ts'
29
+ import { outlineColor } from './util.ts'
17
30
 
31
+ import type { LegendRow } from './components/msa/legendRows.ts'
18
32
  import type { MsaViewModel } from './model.ts'
19
33
  import type { Context as ContextType } from '@jbrowse/svgcanvas'
20
34
  import type { Theme } from '@mui/material'
@@ -26,28 +40,29 @@ export interface ExportSvgOptions {
26
40
  exportType: 'entire' | 'viewport'
27
41
  }
28
42
 
29
- // domain-legend geometry, shared by the width calculation and the renderer so
30
- // the reserved column on the right exactly fits the drawn legend
43
+ // legend geometry, shared by the width calculation and the renderer so the
44
+ // reserved column on the right exactly fits the drawn legends
31
45
  const LEGEND_PAD = 8
32
46
  const LEGEND_ROW_H = 16
33
47
  const LEGEND_SWATCH = 12
34
48
  const LEGEND_FONT = 12
35
49
  const LEGEND_CHAR_W = 7
36
50
 
51
+ // the width of a character as a fraction of the font size, close enough for a
52
+ // sans-serif average
53
+ const CHAR_WIDTH = 0.6
54
+
37
55
  const LEGEND_MAX_W = 360
38
56
  const LEGEND_TEXT_X = LEGEND_PAD + LEGEND_SWATCH + 6
39
57
 
40
- function getLegendWidth(model: MsaViewModel) {
41
- const maxLen = model.visibleDomainTypes.reduce(
42
- (a, d) => Math.max(a, d.name.length),
43
- 0,
44
- )
58
+ function getLegendWidth(rows: LegendRow[]) {
59
+ const maxLen = rows.reduce((a, r) => Math.max(a, r.label.length), 0)
45
60
  const w = LEGEND_TEXT_X + LEGEND_PAD + maxLen * LEGEND_CHAR_W
46
61
  return Math.min(LEGEND_MAX_W, Math.max(120, w))
47
62
  }
48
63
 
49
- function legendHeight(domainTypes: unknown[]) {
50
- return LEGEND_PAD * 2 + domainTypes.length * LEGEND_ROW_H
64
+ function legendHeight(rows: LegendRow[]) {
65
+ return LEGEND_PAD * 2 + rows.length * LEGEND_ROW_H
51
66
  }
52
67
 
53
68
  // resolved sizes/offsets (in svg user units) for the chosen export, shared by
@@ -59,6 +74,9 @@ interface Layout {
59
74
  height: number
60
75
  contentHeight: number
61
76
  trackHeight: number
77
+ // the band the minimap, the row panel headers and the tree overview stacked
78
+ // on the scale bar share across the top
79
+ topHeight: number
62
80
  offsetX: number
63
81
  offsetY: number
64
82
  includeMinimap: boolean
@@ -72,11 +90,15 @@ function getLayout(model: MsaViewModel, opts: ExportSvgOptions): Layout {
72
90
  totalWidth,
73
91
  totalHeight,
74
92
  treeAreaWidth,
93
+ rowPanelsWidth,
94
+ rowPanelsHeaderHeight,
75
95
  totalTrackAreaHeight,
76
96
  minimapHeight,
77
97
  msaAreaHeight,
78
98
  msaCanvasWidth,
79
99
  showHorizontalScrollbar,
100
+ treeOverviewHeight,
101
+ treeScaleBar,
80
102
  } = model
81
103
  const trackHeight = opts.includeTracks ? totalTrackAreaHeight : 0
82
104
  // the minimap reflects the live viewport scroll position, so it's only
@@ -87,10 +109,14 @@ function getLayout(model: MsaViewModel, opts: ExportSvgOptions): Layout {
87
109
  opts.exportType === 'viewport' &&
88
110
  !!opts.includeMinimap &&
89
111
  showHorizontalScrollbar
90
- const legendWidth =
91
- model.actuallyShowDomains && model.visibleDomainTypes.length > 0
92
- ? getLegendWidth(model)
93
- : 0
112
+ const rows = legendRows(model.legends)
113
+ const legendWidth = rows.length > 0 ? getLegendWidth(rows) : 0
114
+ const topHeight = Math.max(
115
+ includeMinimap ? minimapHeight : 0,
116
+ rowPanelsHeaderHeight,
117
+ treeOverviewHeight + (treeScaleBar ? treeScaleBarHeight : 0),
118
+ )
119
+ const gutterWidth = treeAreaWidth + rowPanelsWidth
94
120
 
95
121
  // width stays content-only; the legend occupies an extra column added at the
96
122
  // svg root in MsaSvg. The viewport export takes the alignment canvas's own
@@ -99,23 +125,24 @@ function getLayout(model: MsaViewModel, opts: ExportSvgOptions): Layout {
99
125
  // columns the scrollbars hide on screen
100
126
  return opts.exportType === 'entire'
101
127
  ? {
102
- width: totalWidth + treeAreaWidth,
128
+ width: totalWidth + gutterWidth,
103
129
  msaAreaWidth: totalWidth,
104
- height: totalHeight + trackHeight,
130
+ height: totalHeight + trackHeight + topHeight,
105
131
  contentHeight: totalHeight,
106
132
  trackHeight,
133
+ topHeight,
107
134
  offsetX: 0,
108
135
  offsetY: 0,
109
136
  includeMinimap,
110
137
  legendWidth,
111
138
  }
112
139
  : {
113
- width: msaCanvasWidth + treeAreaWidth,
140
+ width: msaCanvasWidth + gutterWidth,
114
141
  msaAreaWidth: msaCanvasWidth,
115
- height:
116
- msaAreaHeight + (includeMinimap ? minimapHeight : 0) + trackHeight,
142
+ height: msaAreaHeight + topHeight + trackHeight,
117
143
  contentHeight: msaAreaHeight,
118
144
  trackHeight,
145
+ topHeight,
119
146
  offsetX: -scrollX,
120
147
  offsetY: -scrollY,
121
148
  includeMinimap,
@@ -131,14 +158,26 @@ export async function renderToSvg(model: MsaViewModel, opts: ExportSvgOptions) {
131
158
  throw model.error
132
159
  }
133
160
  const { Context } = await import('@jbrowse/svgcanvas')
134
- return renderToStaticMarkup(
161
+ // Each svgcanvas layer is already serialized SVG, and React would have to
162
+ // parse it into a second DOM only for the export to serialize it back out.
163
+ // The layers register themselves here instead, and their markup is spliced
164
+ // into the page as strings.
165
+ const layers = new Map<string, ContextType>()
166
+ const markup = renderToStaticMarkup(
135
167
  <MsaSvg
136
168
  model={model}
137
169
  theme={opts.theme}
138
170
  Context={Context}
139
171
  layout={getLayout(model, opts)}
172
+ layers={layers}
140
173
  />,
141
174
  )
175
+ return svgSafeColors(
176
+ markup.replaceAll(/<g data-layer="([^"]+)"><\/g>/g, (match, id: string) => {
177
+ const ctx = layers.get(id)
178
+ return ctx ? `<g id="${id}">${ctx.getSvg().innerHTML}</g>` : match
179
+ }),
180
+ )
142
181
  }
143
182
 
144
183
  function MsaSvg({
@@ -146,24 +185,28 @@ function MsaSvg({
146
185
  theme,
147
186
  Context,
148
187
  layout,
188
+ layers,
149
189
  }: {
150
190
  model: MsaViewModel
151
191
  theme: Theme
152
192
  Context: typeof ContextType
153
193
  layout: Layout
194
+ layers: LayerMap
154
195
  }) {
155
- const { width, trackHeight, includeMinimap, legendWidth } = layout
156
- const { treeAreaWidth, minimapHeight, visibleDomainTypes } = model
196
+ const { width, trackHeight, includeMinimap, legendWidth, topHeight } = layout
197
+ const { treeAreaWidth, rowPanelsWidth } = model
157
198
  const totalWidth = width + legendWidth
158
- const legendTop = includeMinimap ? minimapHeight : 0
159
- // a short alignment with many domain types has a key taller than its rows,
199
+ const legendTop = topHeight
200
+ // a short alignment with many legend entries has a key taller than its rows,
160
201
  // and the page has to hold the whole key
161
202
  const height =
162
203
  legendWidth > 0
163
- ? Math.max(layout.height, legendTop + legendHeight(visibleDomainTypes))
204
+ ? Math.max(
205
+ layout.height,
206
+ legendTop + legendHeight(legendRows(model.legends)),
207
+ )
164
208
  : layout.height
165
- const contrastScheme = colorContrast(model.colorScheme, theme)
166
- const props = { Context, model, theme, layout, contrastScheme }
209
+ const props = { Context, model, theme, layout, layers }
167
210
 
168
211
  const body = (
169
212
  <>
@@ -192,18 +235,37 @@ function MsaSvg({
192
235
  height="100%"
193
236
  fill={theme.palette.background.default}
194
237
  />
195
- {includeMinimap ? (
238
+ {topHeight > 0 ? (
196
239
  <>
197
- <g transform={`translate(${treeAreaWidth} 0)`}>
198
- <MinimapSVG model={model} theme={theme} />
199
- </g>
200
- <g transform={`translate(0 ${minimapHeight})`}>{body}</g>
240
+ {includeMinimap ? (
241
+ <g
242
+ id="minimap-panel"
243
+ transform={`translate(${treeAreaWidth + rowPanelsWidth} 0)`}
244
+ >
245
+ <MinimapSVG model={model} theme={theme} />
246
+ </g>
247
+ ) : null}
248
+ <RowPanelHeadersSVG
249
+ model={model}
250
+ theme={theme}
251
+ bandHeight={topHeight}
252
+ />
253
+ {model.showTreeOverview ? (
254
+ <TreeOverviewSVG
255
+ model={model}
256
+ theme={theme}
257
+ Context={Context}
258
+ layers={layers}
259
+ />
260
+ ) : null}
261
+ <TreeScaleBarSVG model={model} theme={theme} />
262
+ <g transform={`translate(0 ${topHeight})`}>{body}</g>
201
263
  </>
202
264
  ) : (
203
265
  body
204
266
  )}
205
267
  {legendWidth > 0 ? (
206
- <g transform={`translate(${width} ${legendTop})`}>
268
+ <g id="legend-panel" transform={`translate(${width} ${legendTop})`}>
207
269
  <LegendSVG model={model} theme={theme} width={legendWidth} />
208
270
  </g>
209
271
  ) : null}
@@ -211,8 +273,185 @@ function MsaSvg({
211
273
  )
212
274
  }
213
275
 
214
- // the domain color key drawn as a reserved column to the right of the
215
- // alignment, mirroring the on-screen AnnotationLegend overlay
276
+ // The tree overview in the top band, over the tree column, where the live view
277
+ // puts it. It is part of the figure rather than chrome: the inset is what says
278
+ // where the focused clade sits in the whole tree.
279
+ function TreeOverviewSVG({
280
+ model,
281
+ theme,
282
+ Context,
283
+ layers,
284
+ }: {
285
+ model: MsaViewModel
286
+ theme: Theme
287
+ Context: typeof ContextType
288
+ layers: LayerMap
289
+ }) {
290
+ const { treeAreaWidth, overviewHeight, id } = model
291
+ const ctx = new Context(treeAreaWidth, overviewHeight)
292
+ renderTreeOverview({
293
+ model,
294
+ ctx,
295
+ theme,
296
+ width: treeAreaWidth,
297
+ height: overviewHeight,
298
+ })
299
+ return (
300
+ <ClipGroup
301
+ panelId="tree-overview"
302
+ clipId={`tree-overview-${id}`}
303
+ width={treeAreaWidth}
304
+ height={overviewHeight}
305
+ ctx={ctx}
306
+ layers={layers}
307
+ />
308
+ )
309
+ }
310
+
311
+ // The branch-length scale bar under the tree overview, where the live view puts
312
+ // it (see TreeRuler). A phylogram whose branch lengths are drawn to a scale
313
+ // nothing states is a figure a reader cannot measure, so the export carries the
314
+ // bar even though it is chrome on screen.
315
+ function TreeScaleBarSVG({
316
+ model,
317
+ theme,
318
+ }: {
319
+ model: MsaViewModel
320
+ theme: Theme
321
+ }) {
322
+ const { marginLeft, treeScaleBar: bar, treeOverviewHeight } = model
323
+ if (!bar) {
324
+ return null
325
+ }
326
+ const y = treeOverviewHeight + treeScaleBarHeight
327
+ const color = theme.palette.text.primary
328
+ return (
329
+ <g id="tree-scalebar">
330
+ <text x={marginLeft} y={y - 9} fontSize={10} fill={color}>
331
+ {bar.label}
332
+ </text>
333
+ <path
334
+ d={`M${marginLeft} ${y - 7} v6 h${bar.px} v-6`}
335
+ fill="none"
336
+ stroke={color}
337
+ />
338
+ </g>
339
+ )
340
+ }
341
+
342
+ // Each strip's header over the column it names, turned on its side, where the
343
+ // live view puts it (see RowPanelHeaders). The baseline runs up the figure, so
344
+ // the name sits to the left of it, the way TrackLabelsSVG offsets a horizontal
345
+ // baseline by a third of the font size.
346
+ function RowPanelHeadersSVG({
347
+ model,
348
+ theme,
349
+ bandHeight,
350
+ }: {
351
+ model: MsaViewModel
352
+ theme: Theme
353
+ bandHeight: number
354
+ }) {
355
+ const { resolvedRowPanels, treeAreaWidth, fontSize } = model
356
+ const y = bandHeight - 4
357
+ return resolvedRowPanels.length === 0 ? null : (
358
+ <g id="rowpanel-headers">
359
+ {resolvedRowPanels.map(panel => {
360
+ const across = headerRunsAcross(panel.width)
361
+ const size = across ? fontSize : Math.min(fontSize, panel.width)
362
+ const center = treeAreaWidth + panel.offsetX + panel.width / 2
363
+ const x = across ? center : center + size / 3
364
+ const room = across ? panel.width : y - LEGEND_PAD
365
+ const maxChars = Math.floor(room / (size * CHAR_WIDTH))
366
+ return size < 5 ? null : (
367
+ <text
368
+ key={panel.id}
369
+ x={x}
370
+ y={y}
371
+ {...(across
372
+ ? { textAnchor: 'middle' }
373
+ : { transform: `rotate(-90 ${x} ${y})` })}
374
+ fontSize={size}
375
+ fill={theme.palette.text.primary}
376
+ >
377
+ {panel.header.length > maxChars
378
+ ? `${panel.header.slice(0, Math.max(1, maxChars - 1))}…`
379
+ : panel.header}
380
+ </text>
381
+ )
382
+ })}
383
+ </g>
384
+ )
385
+ }
386
+
387
+ // A label past the room it has, cut to what fits with an ellipsis. The measure
388
+ // is the one cladeGutterWidth sizes the gutter with, so a label the gutter
389
+ // holds keeps every character.
390
+ function clipCladeLabel(label: string, room: number, fontSize: number) {
391
+ const width = measureTextCanvas(label, fontSize)
392
+ if (width <= room) {
393
+ return label
394
+ }
395
+ const fits = Math.floor((label.length * room) / width)
396
+ return `${label.slice(0, Math.max(1, fits - 1))}…`
397
+ }
398
+
399
+ // Each bracket's label in the gutter beside its bar, where the live view puts
400
+ // it (see CladeLabels). The canvas layer draws the bar; a label longer than the
401
+ // gutter or the clade is clipped, since text that overruns runs off the figure.
402
+ function CladeLabelsSVG({
403
+ model,
404
+ theme,
405
+ offsetY,
406
+ }: {
407
+ model: MsaViewModel
408
+ theme: Theme
409
+ offsetY: number
410
+ }) {
411
+ const {
412
+ resolvedClades,
413
+ cladeGutterWidth,
414
+ rowHeight,
415
+ treeAreaWidth,
416
+ fontSize,
417
+ } = model
418
+ if (cladeGutterWidth === 0) {
419
+ return null
420
+ }
421
+ const gutterLeft = treeAreaWidth - cladeGutterWidth
422
+ return (
423
+ <g id="clade-labels">
424
+ {resolvedClades.map((clade, index) => {
425
+ const { label, markColor, rows } = clade
426
+ if (!label || !cladeInGutter(clade)) {
427
+ return null
428
+ }
429
+ const left = gutterLeft + cladeLabelOffset(clade)
430
+ const top = rows[0] * rowHeight - offsetY
431
+ const height = cladeHeight(clade, rowHeight)
432
+ const horizontal = cladeLabelHorizontal(clade, rowHeight, fontSize)
433
+ const room = horizontal ? treeAreaWidth - left - bracketGap : height
434
+ const x = horizontal ? left : left + fontSize
435
+ const y = horizontal ? top + height / 2 + fontSize / 3 : top + height
436
+ return (
437
+ <text
438
+ key={`${rows[0]}-${rows[1]}-${index}`}
439
+ x={x}
440
+ y={y}
441
+ transform={horizontal ? undefined : `rotate(-90 ${x} ${y})`}
442
+ fontSize={fontSize}
443
+ fill={markColor ?? theme.palette.text.primary}
444
+ >
445
+ {clipCladeLabel(label, room, fontSize)}
446
+ </text>
447
+ )
448
+ })}
449
+ </g>
450
+ )
451
+ }
452
+
453
+ // the color keys drawn as a reserved column to the right of the alignment,
454
+ // mirroring the on-screen AnnotationLegend overlay
216
455
  function LegendSVG({
217
456
  model,
218
457
  theme,
@@ -222,8 +461,8 @@ function LegendSVG({
222
461
  theme: Theme
223
462
  width: number
224
463
  }) {
225
- const { visibleDomainTypes, fillPalette, strokePalette } = model
226
- const boxHeight = legendHeight(visibleDomainTypes)
464
+ const rows = legendRows(model.legends)
465
+ const boxHeight = legendHeight(rows)
227
466
  // the column is capped at LEGEND_MAX_W, so a name too long for it has to be
228
467
  // clipped here -- text that overruns the reserved width runs off the figure
229
468
  const maxChars = Math.floor(
@@ -240,27 +479,30 @@ function LegendSVG({
240
479
  stroke={theme.palette.divider}
241
480
  rx={2}
242
481
  />
243
- {visibleDomainTypes.map((d, i) => {
482
+ {rows.map((row, i) => {
244
483
  const y = LEGEND_PAD + i * LEGEND_ROW_H
245
484
  return (
246
- <g key={d.accession}>
247
- <rect
248
- x={LEGEND_PAD}
249
- y={y}
250
- width={LEGEND_SWATCH}
251
- height={LEGEND_SWATCH}
252
- fill={fillPalette[d.accession]}
253
- stroke={strokePalette[d.accession]}
254
- />
485
+ <g key={row.key}>
486
+ {row.color ? (
487
+ <rect
488
+ x={LEGEND_PAD}
489
+ y={y}
490
+ width={LEGEND_SWATCH}
491
+ height={LEGEND_SWATCH}
492
+ fill={row.color}
493
+ stroke={outlineColor(row.color)}
494
+ />
495
+ ) : null}
255
496
  <text
256
- x={LEGEND_TEXT_X}
497
+ x={row.color ? LEGEND_TEXT_X : LEGEND_PAD}
257
498
  y={y + LEGEND_SWATCH - 1}
258
499
  fontSize={LEGEND_FONT}
500
+ fontWeight={row.color ? undefined : 'bold'}
259
501
  fill={theme.palette.text.primary}
260
502
  >
261
- {d.name.length > maxChars
262
- ? `${d.name.slice(0, Math.max(1, maxChars - 1))}…`
263
- : d.name}
503
+ {row.label.length > maxChars
504
+ ? `${row.label.slice(0, Math.max(1, maxChars - 1))}…`
505
+ : row.label}
264
506
  </text>
265
507
  </g>
266
508
  )
@@ -269,23 +511,19 @@ function LegendSVG({
269
511
  )
270
512
  }
271
513
 
514
+ type LayerMap = Map<string, ContextType>
515
+
272
516
  interface LayerProps {
273
517
  model: MsaViewModel
274
518
  theme: Theme
275
519
  layout: Layout
276
520
  Context: typeof ContextType
277
- contrastScheme: Record<string, string>
521
+ layers: LayerMap
278
522
  }
279
523
 
280
- function CoreRendering({
281
- model,
282
- theme,
283
- layout,
284
- Context,
285
- contrastScheme,
286
- }: LayerProps) {
524
+ function CoreRendering({ model, theme, layout, Context, layers }: LayerProps) {
287
525
  const { contentHeight, offsetX, offsetY, msaAreaWidth } = layout
288
- const { treeAreaWidth, id } = model
526
+ const { treeAreaWidth, rowPanelsWidth, id } = model
289
527
 
290
528
  const treeCtx = new Context(treeAreaWidth, contentHeight)
291
529
  renderTreeCanvas({
@@ -297,11 +535,26 @@ function CoreRendering({
297
535
  highResScaleFactorOverride: 1,
298
536
  })
299
537
 
538
+ const rowPanelsCtx = rowPanelsWidth
539
+ ? new Context(rowPanelsWidth, contentHeight)
540
+ : undefined
541
+ if (rowPanelsCtx) {
542
+ renderRowPanels({
543
+ model,
544
+ theme,
545
+ ctx: rowPanelsCtx,
546
+ offsetY,
547
+ blockSizeYOverride: contentHeight,
548
+ highResScaleFactorOverride: 1,
549
+ })
550
+ }
551
+
300
552
  const raster = rasterBackground({ model, theme, layout })
301
553
 
302
554
  const msaCtx = new Context(msaAreaWidth, contentHeight)
303
555
  renderBoxFeatureCanvasBlock({
304
556
  model,
557
+ theme,
305
558
  ctx: msaCtx,
306
559
  offsetX,
307
560
  offsetY,
@@ -315,7 +568,6 @@ function CoreRendering({
315
568
  model,
316
569
  theme,
317
570
  ctx: msaCtx,
318
- contrastScheme,
319
571
  offsetX,
320
572
  offsetY,
321
573
  blockSizeXOverride: msaAreaWidth,
@@ -324,7 +576,7 @@ function CoreRendering({
324
576
  rasterTiles: !!raster,
325
577
  })
326
578
  msaCtx.resetTransform()
327
- renderHighlights({
579
+ renderPersistentHighlights({
328
580
  ctx: msaCtx,
329
581
  model,
330
582
  theme,
@@ -337,17 +589,33 @@ function CoreRendering({
337
589
  return (
338
590
  <>
339
591
  <ClipGroup
592
+ panelId="tree-panel"
340
593
  clipId={`tree-${id}`}
341
594
  width={treeAreaWidth}
342
595
  height={contentHeight}
343
596
  ctx={treeCtx}
597
+ layers={layers}
344
598
  />
599
+ <CladeLabelsSVG model={model} theme={theme} offsetY={offsetY} />
600
+ {rowPanelsCtx ? (
601
+ <ClipGroup
602
+ panelId="rowpanels-panel"
603
+ clipId={`rowpanels-${id}`}
604
+ width={rowPanelsWidth}
605
+ height={contentHeight}
606
+ transform={`translate(${treeAreaWidth} 0)`}
607
+ ctx={rowPanelsCtx}
608
+ layers={layers}
609
+ />
610
+ ) : null}
345
611
  <ClipGroup
612
+ panelId="msa-panel"
346
613
  clipId={`msa-${id}`}
347
614
  width={msaAreaWidth}
348
615
  height={contentHeight}
349
- transform={`translate(${treeAreaWidth} 0)`}
616
+ transform={`translate(${treeAreaWidth + rowPanelsWidth} 0)`}
350
617
  ctx={msaCtx}
618
+ layers={layers}
351
619
  underlay={raster}
352
620
  />
353
621
  </>
@@ -355,15 +623,13 @@ function CoreRendering({
355
623
  }
356
624
 
357
625
  /**
358
- * The alignment background as a single <image>, on the same terms the live
359
- * canvas uses it: no domain overlay painting its own boxes, background coloring
360
- * on, and a canvas we can actually read back.
626
+ * The alignment background as a single <image>, under the live canvas's
627
+ * conditions: no domain overlay boxes, background coloring on, and a canvas
628
+ * that reads back.
361
629
  *
362
- * One node replaces the <rect>-per-cell the vector path emits, which is what
363
- * lets an export of a real alignment finish at all. The image is one pixel per
364
- * cell drawn across the cells' own rectangle, so it upscales by whole cells --
365
- * image-rendering keeps those edges hard rather than smearing them, making it
366
- * pixel-for-pixel what the rects drew.
630
+ * The vector path's <rect> per cell exhausts the heap on a real alignment. The
631
+ * image is one pixel per cell, and image-rendering keeps the upscaled cell
632
+ * edges hard, so it matches the rects pixel for pixel.
367
633
  */
368
634
  function rasterBackground({
369
635
  model,
@@ -412,21 +678,14 @@ function rasterBackground({
412
678
  ) : undefined
413
679
  }
414
680
 
415
- function TrackRendering({
416
- model,
417
- theme,
418
- layout,
419
- Context,
420
- contrastScheme,
421
- }: LayerProps) {
681
+ function TrackRendering({ model, theme, layout, Context, layers }: LayerProps) {
422
682
  const { trackHeight, offsetX, msaAreaWidth } = layout
423
- const { treeAreaWidth, id } = model
683
+ const { treeAreaWidth, rowPanelsWidth, id } = model
424
684
 
425
685
  const ctx = new Context(msaAreaWidth, trackHeight)
426
686
  renderAllTracks({
427
687
  model,
428
688
  ctx,
429
- contrastScheme,
430
689
  theme,
431
690
  offsetX,
432
691
  blockSizeXOverride: msaAreaWidth,
@@ -436,12 +695,14 @@ function TrackRendering({
436
695
  return (
437
696
  <>
438
697
  <TrackLabelsSVG model={model} theme={theme} />
439
- <g transform={`translate(${treeAreaWidth} 0)`}>
698
+ <g transform={`translate(${treeAreaWidth + rowPanelsWidth} 0)`}>
440
699
  <ClipGroup
700
+ panelId="tracks-panel"
441
701
  clipId={`tracks-${id}`}
442
702
  width={msaAreaWidth}
443
703
  height={trackHeight}
444
704
  ctx={ctx}
705
+ layers={layers}
445
706
  />
446
707
  </g>
447
708
  </>
@@ -462,14 +723,17 @@ function TrackLabelsSVG({
462
723
  if (!drawLabels) {
463
724
  return null
464
725
  }
465
- let y = 0
726
+ const labels = turnedOnTracks.map((track, i) => {
727
+ const { id, name, height } = track.model
728
+ const size = Math.min(height, fontSize)
729
+ const top = turnedOnTracks
730
+ .slice(0, i)
731
+ .reduce((acc, t) => acc + t.model.height, 0)
732
+ return { id, name, size, baseline: top + height / 2 + size / 3 }
733
+ })
466
734
  return (
467
735
  <>
468
- {turnedOnTracks.map(track => {
469
- const { id, name, height } = track.model
470
- const size = Math.min(height, fontSize)
471
- const baseline = y + height / 2 + size / 3
472
- y += height
736
+ {labels.map(({ id, name, size, baseline }) => {
473
737
  return size < 5 ? null : (
474
738
  <text
475
739
  key={id}
@@ -487,24 +751,33 @@ function TrackLabelsSVG({
487
751
  )
488
752
  }
489
753
 
490
- // Clips a svgcanvas Context to its box and injects its markup verbatim (it is
491
- // already serialized SVG, not React). `underlay` draws beneath that markup,
754
+ // Clips a svgcanvas Context to its box and leaves a placeholder for renderToSvg
755
+ // to splice the layer's own markup into. `underlay` draws beneath that markup,
492
756
  // inside the same clip, for the parts of a layer React emits directly.
757
+ //
758
+ // `panelId` names the spliced group in the output, the same name in every
759
+ // export, so a figure can be taken apart in Illustrator or svgutils. `clipId`
760
+ // carries the model id, which keeps two viewers on one page from colliding.
493
761
  function ClipGroup({
762
+ panelId,
494
763
  clipId,
495
764
  width,
496
765
  height,
497
766
  transform,
498
767
  ctx,
768
+ layers,
499
769
  underlay,
500
770
  }: {
771
+ panelId: string
501
772
  clipId: string
502
773
  width: number
503
774
  height: number
504
775
  transform?: string
505
776
  ctx: ContextType
777
+ layers: LayerMap
506
778
  underlay?: React.ReactNode
507
779
  }) {
780
+ layers.set(panelId, ctx)
508
781
  return (
509
782
  <>
510
783
  <defs>
@@ -514,7 +787,7 @@ function ClipGroup({
514
787
  </defs>
515
788
  <g clipPath={`url(#${clipId})`} transform={transform}>
516
789
  {underlay}
517
- <g dangerouslySetInnerHTML={{ __html: ctx.getSvg().innerHTML }} />
790
+ <g data-layer={panelId} />
518
791
  </g>
519
792
  </>
520
793
  )