react-msaview 7.0.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 (310) 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/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.map +1 -1
  13. package/dist/components/MSAView.js +17 -6
  14. package/dist/components/MSAView.js.map +1 -1
  15. package/dist/components/MSAViewer.d.ts +64 -7
  16. package/dist/components/MSAViewer.js +128 -23
  17. package/dist/components/MSAViewer.js.map +1 -1
  18. package/dist/components/PortalTooltip.d.ts +11 -0
  19. package/dist/components/PortalTooltip.js +29 -0
  20. package/dist/components/PortalTooltip.js.map +1 -0
  21. package/dist/components/ResizeHandles.js +11 -29
  22. package/dist/components/ResizeHandles.js.map +1 -1
  23. package/dist/components/SequenceTextArea.js +3 -3
  24. package/dist/components/SequenceTextArea.js.map +1 -1
  25. package/dist/components/Track.js +41 -22
  26. package/dist/components/Track.js.map +1 -1
  27. package/dist/components/dialogs/ExportSVGDialog.js +5 -3
  28. package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
  29. package/dist/components/dialogs/TrackInfoDialog.js +1 -1
  30. package/dist/components/dialogs/TrackInfoDialog.js.map +1 -1
  31. package/dist/components/header/GappynessSlider.js +6 -11
  32. package/dist/components/header/GappynessSlider.js.map +1 -1
  33. package/dist/components/header/Header.js +3 -0
  34. package/dist/components/header/Header.js.map +1 -1
  35. package/dist/components/header/LoadWarnings.js +3 -4
  36. package/dist/components/header/LoadWarnings.js.map +1 -1
  37. package/dist/components/header/ScrollZoomIcons.d.ts +6 -0
  38. package/dist/components/header/ScrollZoomIcons.js +35 -0
  39. package/dist/components/header/ScrollZoomIcons.js.map +1 -0
  40. package/dist/components/header/TreeSettingsMenu.js +1 -3
  41. package/dist/components/header/TreeSettingsMenu.js.map +1 -1
  42. package/dist/components/header/UnshareableDataWarning.js +2 -4
  43. package/dist/components/header/UnshareableDataWarning.js.map +1 -1
  44. package/dist/components/header/ZoomControls.js +49 -6
  45. package/dist/components/header/ZoomControls.js.map +1 -1
  46. package/dist/components/header/getAnnotationsMenu.js +3 -6
  47. package/dist/components/header/getAnnotationsMenu.js.map +1 -1
  48. package/dist/components/header/settingsMenuItems.js +10 -7
  49. package/dist/components/header/settingsMenuItems.js.map +1 -1
  50. package/dist/components/import/util.js +1 -2
  51. package/dist/components/import/util.js.map +1 -1
  52. package/dist/components/minimap/Minimap.js +1 -2
  53. package/dist/components/minimap/Minimap.js.map +1 -1
  54. package/dist/components/msa/AnnotationLegend.js +27 -19
  55. package/dist/components/msa/AnnotationLegend.js.map +1 -1
  56. package/dist/components/msa/MSACanvas.js +1 -1
  57. package/dist/components/msa/MSACanvas.js.map +1 -1
  58. package/dist/components/msa/MSACanvasBlock.js +13 -15
  59. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  60. package/dist/components/msa/MSAMouseoverCanvas.js +2 -1
  61. package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
  62. package/dist/components/msa/domainBandCursor.d.ts +7 -11
  63. package/dist/components/msa/domainBandCursor.js +7 -11
  64. package/dist/components/msa/domainBandCursor.js.map +1 -1
  65. package/dist/components/msa/drawFeatureSpans.d.ts +53 -0
  66. package/dist/components/msa/drawFeatureSpans.js +78 -0
  67. package/dist/components/msa/drawFeatureSpans.js.map +1 -0
  68. package/dist/components/msa/legendRows.d.ts +8 -0
  69. package/dist/components/msa/legendRows.js +16 -0
  70. package/dist/components/msa/legendRows.js.map +1 -0
  71. package/dist/components/msa/msaRaster.d.ts +24 -31
  72. package/dist/components/msa/msaRaster.js +30 -39
  73. package/dist/components/msa/msaRaster.js.map +1 -1
  74. package/dist/components/msa/packDomainLanes.d.ts +20 -0
  75. package/dist/components/msa/packDomainLanes.js +33 -0
  76. package/dist/components/msa/packDomainLanes.js.map +1 -0
  77. package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +3 -1
  78. package/dist/components/msa/renderBoxFeatureCanvasBlock.js +51 -87
  79. package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
  80. package/dist/components/msa/renderHighlights.d.ts +15 -13
  81. package/dist/components/msa/renderHighlights.js +43 -20
  82. package/dist/components/msa/renderHighlights.js.map +1 -1
  83. package/dist/components/msa/renderMSABlock.js +5 -4
  84. package/dist/components/msa/renderMSABlock.js.map +1 -1
  85. package/dist/components/msa/tileColor.js +4 -7
  86. package/dist/components/msa/tileColor.js.map +1 -1
  87. package/dist/components/msa/useMsaBlockMouse.d.ts +5 -3
  88. package/dist/components/msa/useMsaBlockMouse.js +6 -6
  89. package/dist/components/msa/useMsaBlockMouse.js.map +1 -1
  90. package/dist/components/rowpanels/RowPanelBlock.d.ts +9 -0
  91. package/dist/components/rowpanels/RowPanelBlock.js +86 -0
  92. package/dist/components/rowpanels/RowPanelBlock.js.map +1 -0
  93. package/dist/components/rowpanels/RowPanelHeaders.d.ts +12 -0
  94. package/dist/components/rowpanels/RowPanelHeaders.js +38 -0
  95. package/dist/components/rowpanels/RowPanelHeaders.js.map +1 -0
  96. package/dist/components/{msa/ColumnStats.d.ts → rowpanels/RowPanels.d.ts} +2 -2
  97. package/dist/components/rowpanels/RowPanels.js +32 -0
  98. package/dist/components/rowpanels/RowPanels.js.map +1 -0
  99. package/dist/components/rowpanels/headerLayout.d.ts +1 -0
  100. package/dist/components/rowpanels/headerLayout.js +5 -0
  101. package/dist/components/rowpanels/headerLayout.js.map +1 -0
  102. package/dist/components/rowpanels/renderFeaturePanel.d.ts +21 -0
  103. package/dist/components/rowpanels/renderFeaturePanel.js +37 -0
  104. package/dist/components/rowpanels/renderFeaturePanel.js.map +1 -0
  105. package/dist/components/rowpanels/renderRowPanel.d.ts +23 -0
  106. package/dist/components/rowpanels/renderRowPanel.js +22 -0
  107. package/dist/components/rowpanels/renderRowPanel.js.map +1 -0
  108. package/dist/components/rowpanels/renderStrip.d.ts +20 -0
  109. package/dist/components/rowpanels/renderStrip.js +26 -0
  110. package/dist/components/rowpanels/renderStrip.js.map +1 -0
  111. package/dist/components/tracks/TrackBlocks.js +3 -5
  112. package/dist/components/tracks/TrackBlocks.js.map +1 -1
  113. package/dist/components/tracks/TrackTooltipContent.d.ts +15 -0
  114. package/dist/components/tracks/TrackTooltipContent.js +109 -0
  115. package/dist/components/tracks/TrackTooltipContent.js.map +1 -0
  116. package/dist/components/tracks/drawTracks.d.ts +4 -4
  117. package/dist/components/tracks/drawTracks.js +17 -34
  118. package/dist/components/tracks/drawTracks.js.map +1 -1
  119. package/dist/components/tracks/useTrackHover.d.ts +22 -0
  120. package/dist/components/tracks/useTrackHover.js +30 -0
  121. package/dist/components/tracks/useTrackHover.js.map +1 -0
  122. package/dist/components/tree/CladeLabels.d.ts +12 -0
  123. package/dist/components/tree/CladeLabels.js +48 -0
  124. package/dist/components/tree/CladeLabels.js.map +1 -0
  125. package/dist/components/tree/TreeCanvas.js +4 -1
  126. package/dist/components/tree/TreeCanvas.js.map +1 -1
  127. package/dist/components/tree/TreeCanvasBlock.js +4 -21
  128. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  129. package/dist/components/tree/TreeOverview.d.ts +14 -0
  130. package/dist/components/tree/TreeOverview.js +75 -0
  131. package/dist/components/tree/TreeOverview.js.map +1 -0
  132. package/dist/components/tree/TreeRuler.js +9 -13
  133. package/dist/components/tree/TreeRuler.js.map +1 -1
  134. package/dist/components/tree/cladeBrackets.d.ts +25 -0
  135. package/dist/components/tree/cladeBrackets.js +51 -0
  136. package/dist/components/tree/cladeBrackets.js.map +1 -0
  137. package/dist/components/tree/dialogs/TreeNodeInfoDialog.js +2 -2
  138. package/dist/components/tree/dialogs/TreeNodeInfoDialog.js.map +1 -1
  139. package/dist/components/tree/renderTreeCanvas.js +158 -24
  140. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  141. package/dist/components/tree/renderTreeOverview.d.ts +52 -0
  142. package/dist/components/tree/renderTreeOverview.js +138 -0
  143. package/dist/components/tree/renderTreeOverview.js.map +1 -0
  144. package/dist/components/tree/useTreeHover.js +2 -4
  145. package/dist/components/tree/useTreeHover.js.map +1 -1
  146. package/dist/constants.d.ts +15 -0
  147. package/dist/constants.js +48 -22
  148. package/dist/constants.js.map +1 -1
  149. package/dist/createPaletteMap.d.ts +7 -6
  150. package/dist/createPaletteMap.js +10 -9
  151. package/dist/createPaletteMap.js.map +1 -1
  152. package/dist/element.d.ts +13 -0
  153. package/dist/element.js +269 -0
  154. package/dist/element.js.map +1 -0
  155. package/dist/featureFields.d.ts +11 -0
  156. package/dist/featureFields.js +17 -0
  157. package/dist/featureFields.js.map +1 -0
  158. package/dist/flatToTree.js +1 -2
  159. package/dist/flatToTree.js.map +1 -1
  160. package/dist/ggplotPalettes.d.ts +10 -0
  161. package/dist/ggplotPalettes.js +58 -0
  162. package/dist/ggplotPalettes.js.map +1 -1
  163. package/dist/hierarchy.d.ts +27 -0
  164. package/dist/hierarchy.js +66 -9
  165. package/dist/hierarchy.js.map +1 -1
  166. package/dist/index.d.ts +9 -1
  167. package/dist/index.js +5 -3
  168. package/dist/index.js.map +1 -1
  169. package/dist/model/DataModel.js +2 -5
  170. package/dist/model/DataModel.js.map +1 -1
  171. package/dist/model/msaModel.d.ts +3 -2
  172. package/dist/model/msaModel.js +3 -2
  173. package/dist/model/msaModel.js.map +1 -1
  174. package/dist/model/treeModel.d.ts +30 -0
  175. package/dist/model/treeModel.js +37 -1
  176. package/dist/model/treeModel.js.map +1 -1
  177. package/dist/model.d.ts +531 -275
  178. package/dist/model.js +1295 -547
  179. package/dist/model.js.map +1 -1
  180. package/dist/mount.d.ts +11 -0
  181. package/dist/mount.js +22 -0
  182. package/dist/mount.js.map +1 -0
  183. package/dist/neighborJoining.js +8 -32
  184. package/dist/neighborJoining.js.map +1 -1
  185. package/dist/renderToStaticMarkup.d.ts +10 -19
  186. package/dist/renderToStaticMarkup.js +13 -24
  187. package/dist/renderToStaticMarkup.js.map +1 -1
  188. package/dist/renderToSvg.js +180 -56
  189. package/dist/renderToSvg.js.map +1 -1
  190. package/dist/rowCoordinateCalculations.d.ts +4 -6
  191. package/dist/rowCoordinateCalculations.js +4 -6
  192. package/dist/rowCoordinateCalculations.js.map +1 -1
  193. package/dist/scales.d.ts +22 -0
  194. package/dist/scales.js +30 -0
  195. package/dist/scales.js.map +1 -0
  196. package/dist/seqPosToGlobalCol.d.ts +2 -3
  197. package/dist/seqPosToGlobalCol.js +2 -3
  198. package/dist/seqPosToGlobalCol.js.map +1 -1
  199. package/dist/sequenceLogo.d.ts +6 -11
  200. package/dist/sequenceLogo.js +6 -11
  201. package/dist/sequenceLogo.js.map +1 -1
  202. package/dist/stripDefault.d.ts +7 -14
  203. package/dist/stripDefault.js +7 -14
  204. package/dist/stripDefault.js.map +1 -1
  205. package/dist/svgTestUtil.d.ts +172 -52
  206. package/dist/types.d.ts +219 -21
  207. package/dist/umd.d.ts +2 -0
  208. package/dist/umd.js +4 -2
  209. package/dist/umd.js.map +1 -1
  210. package/dist/useCanvasAutorun.d.ts +5 -10
  211. package/dist/useCanvasAutorun.js +5 -10
  212. package/dist/useCanvasAutorun.js.map +1 -1
  213. package/dist/useMsaSvgFigure.d.ts +20 -0
  214. package/dist/useMsaSvgFigure.js +46 -0
  215. package/dist/useMsaSvgFigure.js.map +1 -0
  216. package/dist/util.d.ts +5 -6
  217. package/dist/util.js +13 -6
  218. package/dist/util.js.map +1 -1
  219. package/dist/version.d.ts +1 -1
  220. package/dist/version.js +1 -1
  221. package/package.json +19 -12
  222. package/src/colorSchemes.ts +1 -2
  223. package/src/columnStats.ts +58 -0
  224. package/src/components/DragHandle.tsx +18 -7
  225. package/src/components/Loading.tsx +2 -6
  226. package/src/components/MSAView.tsx +19 -5
  227. package/src/components/MSAViewer.tsx +240 -28
  228. package/src/components/PortalTooltip.tsx +47 -0
  229. package/src/components/ResizeHandles.tsx +17 -32
  230. package/src/components/SequenceTextArea.tsx +3 -3
  231. package/src/components/Track.tsx +55 -18
  232. package/src/components/dialogs/ExportSVGDialog.tsx +7 -2
  233. package/src/components/dialogs/TrackInfoDialog.tsx +1 -1
  234. package/src/components/header/GappynessSlider.tsx +6 -11
  235. package/src/components/header/Header.tsx +6 -0
  236. package/src/components/header/LoadWarnings.tsx +3 -4
  237. package/src/components/header/ScrollZoomIcons.tsx +52 -0
  238. package/src/components/header/TreeSettingsMenu.tsx +1 -3
  239. package/src/components/header/UnshareableDataWarning.tsx +2 -4
  240. package/src/components/header/ZoomControls.tsx +74 -14
  241. package/src/components/header/getAnnotationsMenu.ts +3 -6
  242. package/src/components/header/settingsMenuItems.ts +11 -6
  243. package/src/components/import/ImportFormExamples.tsx +2 -2
  244. package/src/components/import/util.ts +1 -2
  245. package/src/components/minimap/Minimap.tsx +1 -2
  246. package/src/components/msa/AnnotationLegend.tsx +48 -38
  247. package/src/components/msa/MSACanvas.tsx +3 -5
  248. package/src/components/msa/MSACanvasBlock.tsx +13 -16
  249. package/src/components/msa/MSAMouseoverCanvas.tsx +2 -1
  250. package/src/components/msa/domainBandCursor.ts +7 -11
  251. package/src/components/msa/drawFeatureSpans.ts +150 -0
  252. package/src/components/msa/legendRows.ts +24 -0
  253. package/src/components/msa/msaRaster.ts +30 -39
  254. package/src/components/msa/packDomainLanes.ts +36 -0
  255. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +69 -123
  256. package/src/components/msa/renderHighlights.ts +53 -20
  257. package/src/components/msa/renderMSABlock.ts +5 -3
  258. package/src/components/msa/tileColor.ts +4 -7
  259. package/src/components/msa/useMsaBlockMouse.ts +6 -6
  260. package/src/components/rowpanels/RowPanelBlock.tsx +139 -0
  261. package/src/components/rowpanels/RowPanelHeaders.tsx +57 -0
  262. package/src/components/rowpanels/RowPanels.tsx +77 -0
  263. package/src/components/rowpanels/headerLayout.ts +5 -0
  264. package/src/components/rowpanels/renderFeaturePanel.ts +66 -0
  265. package/src/components/rowpanels/renderRowPanel.ts +41 -0
  266. package/src/components/rowpanels/renderStrip.ts +46 -0
  267. package/src/components/tracks/TrackBlocks.tsx +3 -5
  268. package/src/components/tracks/TrackTooltipContent.tsx +156 -0
  269. package/src/components/tracks/drawTracks.ts +17 -34
  270. package/src/components/tracks/useTrackHover.ts +42 -0
  271. package/src/components/tree/CladeLabels.tsx +77 -0
  272. package/src/components/tree/TreeCanvas.tsx +2 -0
  273. package/src/components/tree/TreeCanvasBlock.tsx +8 -32
  274. package/src/components/tree/TreeOverview.tsx +106 -0
  275. package/src/components/tree/TreeRuler.tsx +11 -22
  276. package/src/components/tree/cladeBrackets.ts +73 -0
  277. package/src/components/tree/dialogs/TreeNodeInfoDialog.tsx +2 -2
  278. package/src/components/tree/renderTreeCanvas.ts +240 -20
  279. package/src/components/tree/renderTreeOverview.ts +212 -0
  280. package/src/components/tree/useTreeHover.ts +2 -4
  281. package/src/constants.ts +56 -22
  282. package/src/createPaletteMap.ts +10 -9
  283. package/src/element.ts +321 -0
  284. package/src/featureFields.ts +19 -0
  285. package/src/flatToTree.ts +1 -2
  286. package/src/ggplotPalettes.ts +60 -0
  287. package/src/hierarchy.ts +77 -9
  288. package/src/index.ts +31 -7
  289. package/src/model/DataModel.ts +2 -5
  290. package/src/model/msaModel.ts +3 -2
  291. package/src/model/treeModel.ts +42 -0
  292. package/src/model.ts +1523 -560
  293. package/src/mount.tsx +32 -0
  294. package/src/neighborJoining.ts +8 -32
  295. package/src/renderToStaticMarkup.ts +13 -24
  296. package/src/renderToSvg.tsx +335 -68
  297. package/src/rowCoordinateCalculations.ts +4 -6
  298. package/src/scales.ts +47 -0
  299. package/src/seqPosToGlobalCol.ts +2 -3
  300. package/src/sequenceLogo.ts +6 -11
  301. package/src/stripDefault.ts +7 -14
  302. package/src/types.ts +258 -45
  303. package/src/umd.ts +4 -2
  304. package/src/useCanvasAutorun.ts +5 -10
  305. package/src/useMsaSvgFigure.ts +72 -0
  306. package/src/util.ts +15 -6
  307. package/src/version.ts +1 -1
  308. package/dist/components/msa/ColumnStats.js +0 -42
  309. package/dist/components/msa/ColumnStats.js.map +0 -1
  310. package/src/components/msa/ColumnStats.tsx +0 -54
@@ -5,6 +5,9 @@ import ExpandMore from '@mui/icons-material/ExpandMore'
5
5
  import { IconButton, Paper, Typography } from '@mui/material'
6
6
  import { observer } from 'mobx-react'
7
7
 
8
+ import { legendZIndex } from '../../constants.ts'
9
+ import { legendRows } from './legendRows.ts'
10
+
8
11
  import type { MsaViewModel } from '../../model.ts'
9
12
 
10
13
  const AnnotationLegend = observer(function ({
@@ -12,65 +15,72 @@ const AnnotationLegend = observer(function ({
12
15
  }: {
13
16
  model: MsaViewModel
14
17
  }) {
15
- const {
16
- actuallyShowDomains,
17
- visibleDomainTypes: visible,
18
- fillPalette,
19
- showDomainLegend: expanded,
20
- } = model
18
+ const { legends, showDomainLegend: expanded } = model
19
+ const rows = legendRows(legends)
21
20
  const ExpandIcon = expanded ? ExpandLess : ExpandMore
22
21
 
23
- return actuallyShowDomains && visible.length > 0 ? (
22
+ return rows.length > 0 ? (
24
23
  <Paper
25
24
  elevation={3}
26
25
  style={{
27
26
  position: 'absolute',
28
27
  top: 4,
29
28
  right: 4,
30
- zIndex: 100,
31
- maxWidth: 260,
29
+ zIndex: legendZIndex,
30
+ maxWidth: 220,
32
31
  maxHeight: '60%',
33
32
  display: 'flex',
34
33
  flexDirection: 'column',
35
34
  opacity: 0.95,
36
35
  }}
37
36
  >
38
- <div
39
- style={{
40
- display: 'flex',
41
- alignItems: 'center',
42
- justifyContent: 'flex-end',
43
- padding: '2px 4px 2px 8px',
37
+ <IconButton
38
+ size="small"
39
+ title={expanded ? 'Collapse key' : 'Expand key'}
40
+ style={
41
+ expanded
42
+ ? { position: 'absolute', top: 0, right: 0, padding: 1 }
43
+ : { padding: 1 }
44
+ }
45
+ onClick={() => {
46
+ model.setShowDomainLegend(!expanded)
44
47
  }}
45
48
  >
46
- <IconButton
47
- size="small"
48
- title={expanded ? 'Collapse key' : 'Expand key'}
49
- onClick={() => {
50
- model.setShowDomainLegend(!expanded)
51
- }}
52
- >
53
- <ExpandIcon fontSize="small" />
54
- </IconButton>
55
- </div>
49
+ <ExpandIcon style={{ fontSize: 14 }} />
50
+ </IconButton>
56
51
  {expanded ? (
57
- <div style={{ overflow: 'auto', padding: '0 8px 6px' }}>
58
- {visible.map(({ accession, name }) => (
52
+ <div style={{ overflow: 'auto', padding: '2px 6px 4px' }}>
53
+ {rows.map((row, i) => (
59
54
  <div
60
- key={accession}
61
- style={{ display: 'flex', alignItems: 'center', gap: 6 }}
62
- title={accession}
55
+ key={row.key}
56
+ style={{
57
+ display: 'flex',
58
+ alignItems: 'center',
59
+ gap: 4,
60
+ marginRight: i === 0 ? 18 : 0,
61
+ }}
62
+ title={row.hint}
63
63
  >
64
- <div
64
+ {row.color ? (
65
+ <div
66
+ style={{
67
+ width: 9,
68
+ height: 9,
69
+ flexShrink: 0,
70
+ background: row.color,
71
+ }}
72
+ />
73
+ ) : null}
74
+ <Typography
75
+ variant="caption"
76
+ noWrap
65
77
  style={{
66
- width: 12,
67
- height: 12,
68
- flexShrink: 0,
69
- background: fillPalette[accession],
78
+ fontSize: 10,
79
+ lineHeight: 1.4,
80
+ fontWeight: row.color ? undefined : 'bold',
70
81
  }}
71
- />
72
- <Typography variant="caption" noWrap>
73
- {name}
82
+ >
83
+ {row.label}
74
84
  </Typography>
75
85
  </div>
76
86
  ))}
@@ -34,7 +34,7 @@ const MSACanvas = observer(function ({ model }: { model: MsaViewModel }) {
34
34
  )
35
35
  const onZoom = useCallback(
36
36
  (scaleFactor: number, offsetX: number, offsetY: number) => {
37
- model.zoomToPos(scaleFactor, offsetX, offsetY)
37
+ model.zoomToPos(scaleFactor, offsetX, offsetY, model.wheelZoomAxis)
38
38
  },
39
39
  [model],
40
40
  )
@@ -49,10 +49,8 @@ const MSACanvas = observer(function ({ model }: { model: MsaViewModel }) {
49
49
  return (
50
50
  <div
51
51
  ref={ref}
52
- // the MSA viewport: its rect is the origin every column/row offset is
53
- // measured from (col*colWidth + scrollX, row*rowHeight + scrollY), which
54
- // is what lets a screenshot callout anchor to an alignment column instead
55
- // of a hand-measured pixel
52
+ // screenshot callouts anchor to columns from this rect's origin:
53
+ // col*colWidth + scrollX, row*rowHeight + scrollY
56
54
  data-testid="msa_canvas"
57
55
  onMouseDown={onMouseDown}
58
56
  onMouseUp={onMouseUp}
@@ -5,7 +5,6 @@ import { useTheme } from '@mui/material'
5
5
  import { observer } from 'mobx-react'
6
6
 
7
7
  import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
8
- import ColumnStats from './ColumnStats.tsx'
9
8
  import { drawMsaRaster, rasterSupported } from './msaRaster.ts'
10
9
  import { renderBoxFeatureCanvasBlock } from './renderBoxFeatureCanvasBlock.ts'
11
10
  import { renderMSABlock } from './renderMSABlock.ts'
@@ -39,6 +38,7 @@ const MSACanvasBlock = observer(function ({
39
38
  ctx.clearRect(0, 0, canvasSize, canvasSize)
40
39
  renderBoxFeatureCanvasBlock({
41
40
  ctx,
41
+ theme,
42
42
  offsetX,
43
43
  offsetY,
44
44
  model,
@@ -65,7 +65,7 @@ const MSACanvasBlock = observer(function ({
65
65
  deps: [model, offsetX, offsetY, theme],
66
66
  })
67
67
 
68
- const { hoveredInsertion, mouseOverDomains, showColumnStats } = model
68
+ const { hoveredInsertion, mouseOverDomains } = model
69
69
 
70
70
  return (
71
71
  <>
@@ -107,20 +107,17 @@ const MSACanvasBlock = observer(function ({
107
107
  : hoveredInsertion.letters}
108
108
  </>
109
109
  ) : (
110
- <>
111
- {mouseOverDomains.map(d => (
112
- <div key={`${d.accession}-${d.start}-${d.end}`}>
113
- <b>{d.name}</b> ({d.accession}) {d.start}-{d.end}
114
- {d.description ? (
115
- <>
116
- <br />
117
- {d.description}
118
- </>
119
- ) : null}
120
- </div>
121
- ))}
122
- {showColumnStats ? <ColumnStats model={model} /> : null}
123
- </>
110
+ mouseOverDomains.map(d => (
111
+ <div key={`${d.accession}-${d.start}-${d.end}`}>
112
+ <b>{d.name}</b> ({d.accession}) {d.start}-{d.end}
113
+ {d.description ? (
114
+ <>
115
+ <br />
116
+ {d.description}
117
+ </>
118
+ ) : null}
119
+ </div>
120
+ ))
124
121
  )}
125
122
  </BaseTooltip>
126
123
  ) : null}
@@ -4,6 +4,7 @@ import { isAlive } from '@jbrowse/mobx-state-tree'
4
4
  import { useTheme } from '@mui/material'
5
5
  import { observer } from 'mobx-react'
6
6
 
7
+ import { msaOverlayZIndex } from '../../constants.ts'
7
8
  import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
8
9
  import { renderMouseover } from './renderMSAMouseover.ts'
9
10
 
@@ -41,7 +42,7 @@ const MSAMouseoverCanvas = observer(function ({
41
42
  left: 0,
42
43
  width,
43
44
  height,
44
- zIndex: 1000,
45
+ zIndex: msaOverlayZIndex,
45
46
  pointerEvents: 'none',
46
47
  }}
47
48
  />
@@ -1,19 +1,15 @@
1
1
  import type { DomainBand } from '../../types.ts'
2
2
 
3
3
  /**
4
- * Left-to-right cursor over one row's domain bands, answering "which band is
5
- * drawn on top at this column" — the fill a letter there has to contrast
6
- * against.
4
+ * Left-to-right cursor over one row's domain bands that returns the band drawn
5
+ * on top at a column, for a letter's contrast color.
7
6
  *
8
- * Not simply the first band covering the column. The overlay paints in
9
- * `domainBands` order, largest first, so where signatures nest — a family
10
- * enclosing a domain, which InterProScan emits routinely — the box a reader
11
- * actually sees is the last one painted over that column. `stackIndex` is that
12
- * paint order.
7
+ * The overlay paints in `domainBands` order, largest first, so where signatures
8
+ * nest (InterProScan often emits a family enclosing a domain) the visible band
9
+ * is the last one painted. `stackIndex` is that paint order.
13
10
  *
14
- * `bands` must be sorted by start column (`domainBandsByStart`), and columns
15
- * must be visited in increasing order, which is what the block renderer does.
16
- * Both keep this to one pass over the bands per row rather than a scan per cell.
11
+ * `bands` must be sorted by start column (`domainBandsByStart`) and columns
12
+ * visited in increasing order, which keeps this to one pass per row.
17
13
  */
18
14
  export function domainBandCursor(bands: DomainBand[] | undefined) {
19
15
  let next = 0
@@ -0,0 +1,150 @@
1
+ import type { HierarchyNode } from '../../hierarchy.ts'
2
+ import type { Annotation, NodeWithIdsAndLength } from '../../types.ts'
3
+ import type { RenderCtx } from '../renderCtx.ts'
4
+
5
+ /** one drawn span: a feature, the lane it sits in, and its row's depth */
6
+ export interface SpanBand {
7
+ annotation: Annotation
8
+ lane: number
9
+ laneCount: number
10
+ }
11
+
12
+ /** one row's spans, at the row baseline `y` the tree layout gives it */
13
+ export interface SpanRow<T extends SpanBand> {
14
+ y: number
15
+ bands: T[]
16
+ }
17
+
18
+ /**
19
+ * Where a band lands inside its row. The alignment overlay fills the row, bars
20
+ * it along the bottom, or stacks thin sub-rows; a `features` row panel divides
21
+ * the row between its lanes.
22
+ */
23
+ export interface SpanLayout {
24
+ /** the band height in a row needing `laneCount` lanes */
25
+ height: (laneCount: number) => number
26
+ /** the band's top edge, from its row baseline, its lane and its height */
27
+ top: (y: number, lane: number, height: number) => number
28
+ /** how far a strand head reaches past the body */
29
+ headLength: (height: number) => number
30
+ }
31
+
32
+ /** the rows of `bandsByRow` that `leaves` covers, in display order */
33
+ export function spanRows<T extends SpanBand>(
34
+ leaves: HierarchyNode<NodeWithIdsAndLength>[],
35
+ bandsByRow: Map<string, T[]>,
36
+ ): SpanRow<T>[] {
37
+ const rows: SpanRow<T>[] = []
38
+ for (const leaf of leaves) {
39
+ const bands = bandsByRow.get(leaf.data.name)
40
+ if (bands && bands.length > 0) {
41
+ rows.push({ y: leaf.x!, bands })
42
+ }
43
+ }
44
+ return rows
45
+ }
46
+
47
+ /**
48
+ * The span mark, in whatever x space the caller maps its bands into: alignment
49
+ * columns for the domain overlay, panel pixels for a `features` row panel. A
50
+ * feature with a strand draws as an arrow, one without as a box, and a label
51
+ * draws inside the span wherever the text fits.
52
+ *
53
+ * `xOf` returns the span's `[xStart, xEnd]` in the context's coordinates, and
54
+ * the context is translated so that `y - rowHeight` is the row's top edge.
55
+ */
56
+ export function drawFeatureSpans<T extends SpanBand>({
57
+ ctx,
58
+ rows,
59
+ layout,
60
+ xOf,
61
+ colors,
62
+ labelOf,
63
+ labelDrawn,
64
+ contrastText,
65
+ xMin,
66
+ xMax,
67
+ }: {
68
+ ctx: RenderCtx
69
+ rows: SpanRow<T>[]
70
+ layout: SpanLayout
71
+ xOf: (band: T) => [number, number]
72
+ colors: Map<Annotation, { fill: string; stroke: string }>
73
+ labelOf?: (band: T) => string | undefined
74
+ labelDrawn?: (band: T) => void
75
+ contrastText: (color: string) => string
76
+ xMin: number
77
+ xMax: number
78
+ }) {
79
+ for (const { y, bands } of rows) {
80
+ const h = layout.height(bands[0]!.laneCount)
81
+ const headLen = layout.headLength(h)
82
+ for (const band of bands) {
83
+ const [xStart, xEnd] = xOf(band)
84
+ const w = xEnd - xStart
85
+ if (xEnd < xMin || xStart > xMax) {
86
+ continue
87
+ }
88
+ const t = layout.top(y, band.lane, h)
89
+ const { fill, stroke } = colors.get(band.annotation)!
90
+ ctx.fillStyle = fill
91
+ ctx.strokeStyle = stroke
92
+ const { strand } = band.annotation
93
+ if (strand === undefined) {
94
+ ctx.fillRect(xStart, t, w, h)
95
+ ctx.strokeRect(xStart, t, w, h)
96
+ } else {
97
+ drawGeneArrow({ ctx, x: xStart, t, w, h, headLen, strand })
98
+ }
99
+ const label = labelOf?.(band)
100
+ if (label !== undefined) {
101
+ const fontSize = Math.min(h - 2, 11)
102
+ ctx.font = `${fontSize}px sans-serif`
103
+ if (ctx.measureText(label).width + 2 <= w) {
104
+ labelDrawn?.(band)
105
+ ctx.fillStyle = contrastText(fill)
106
+ ctx.textAlign = 'center'
107
+ ctx.textBaseline = 'middle'
108
+ ctx.fillText(label, xStart + w / 2, t + h / 2)
109
+ }
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ // A gene arrow: a full-width rectangular body spanning the feature's
116
+ // start..end columns, plus a triangular head that points *beyond* that end in
117
+ // the strand direction (right for +, left for -). Keeping the body aligned to
118
+ // the exact start/end columns means + and - strand features read as having the
119
+ // same boundaries; the arrow lives outside them purely to show transcription
120
+ // direction, preserving the column-by-column homology down the rows.
121
+ function drawGeneArrow({
122
+ ctx,
123
+ x,
124
+ t,
125
+ w,
126
+ h,
127
+ headLen,
128
+ strand,
129
+ }: {
130
+ ctx: RenderCtx
131
+ x: number
132
+ t: number
133
+ w: number
134
+ h: number
135
+ headLen: number
136
+ strand: number
137
+ }) {
138
+ const dir = strand > 0 ? 1 : -1
139
+ const bodyStart = strand > 0 ? x : x + w
140
+ const bodyEnd = strand > 0 ? x + w : x
141
+ ctx.beginPath()
142
+ ctx.moveTo(bodyStart, t)
143
+ ctx.lineTo(bodyEnd, t)
144
+ ctx.lineTo(bodyEnd + dir * headLen, t + h / 2)
145
+ ctx.lineTo(bodyEnd, t + h)
146
+ ctx.lineTo(bodyStart, t + h)
147
+ ctx.closePath()
148
+ ctx.fill()
149
+ ctx.stroke()
150
+ }
@@ -0,0 +1,24 @@
1
+ import type { Legend } from '../../types.ts'
2
+
3
+ export interface LegendRow {
4
+ key: string
5
+ label: string
6
+ color?: string
7
+ hint?: string
8
+ }
9
+
10
+ // the legends flattened into the rows both the overlay and the export stack
11
+ // top to bottom. A title row separates one legend from the next, and a lone
12
+ // legend needs no title.
13
+ export function legendRows(legends: Legend[]): LegendRow[] {
14
+ const titled = legends.length > 1
15
+ return legends.flatMap(legend => [
16
+ ...(titled ? [{ key: legend.id, label: legend.title }] : []),
17
+ ...legend.entries.map(entry => ({
18
+ key: `${legend.id}:${entry.id}`,
19
+ label: entry.label,
20
+ color: entry.color,
21
+ hint: entry.id,
22
+ })),
23
+ ])
24
+ }
@@ -36,9 +36,8 @@ const colorCache = new Map<string, number>()
36
36
 
37
37
  /**
38
38
  * CSS color string -> the packed pixel an ImageData holds for it. Painting a 1x1
39
- * canvas and reading it back handles every form a color scheme can produce --
40
- * hex, rgb(), hsl(), named -- without a parser of our own, and the cache means
41
- * each distinct color is resolved once per session rather than once per cell.
39
+ * canvas and reading it back parses hex, rgb(), hsl() and named colors; the
40
+ * cache resolves each distinct color once per session.
42
41
  */
43
42
  export function cssColorToPixel(css: string) {
44
43
  const hit = colorCache.get(css)
@@ -80,13 +79,12 @@ function cellPixelFn(spec: RasterSpec, toPixel: (css: string) => number) {
80
79
  /**
81
80
  * Packs a rectangle of the alignment into one pixel per cell, in `rowNames`
82
81
  * order, ready to hand to putImageData. Cells past the end of a row, and rows
83
- * with no sequence at all, stay transparent -- what the per-cell tile painter
84
- * also leaves behind.
82
+ * with no sequence, stay transparent, as in the per-cell painter.
85
83
  *
86
- * `colStep`/`rowStep` sample instead of covering, which is how the minimap fits
87
- * a whole alignment into a 12px bar. `colSpan`/`rowSpan` average each block of
88
- * that many cells into one pixel instead, so a zoom below a device pixel per
89
- * column still shows every column's color without blending neighbouring rows.
84
+ * `colStep`/`rowStep` sample every nth cell, which fits a whole alignment into
85
+ * the minimap's 12px bar. `colSpan`/`rowSpan` average each block of that many
86
+ * cells into one pixel, so a zoom below a device pixel per column still shows
87
+ * every column's color without blending neighbouring rows.
90
88
  */
91
89
  export function rasterPixels({
92
90
  spec,
@@ -292,8 +290,8 @@ interface RasterCache {
292
290
 
293
291
  const caches = new WeakMap<object, RasterCache>()
294
292
 
295
- // Everything a cell's color depends on. A change to any of them makes every
296
- // cached tile stale, which a fresh cache expresses without tracking which.
293
+ // Everything a cell's color depends on; a change to any of them starts a fresh
294
+ // cache.
297
295
  function rasterKeys(model: MsaViewModel, theme: Theme) {
298
296
  return [
299
297
  model.columns,
@@ -402,13 +400,12 @@ function getTile({
402
400
  /**
403
401
  * Paints the alignment background of one MSA block from cached
404
402
  * one-pixel-per-cell tiles. The tiles do not depend on the zoom level, so a zoom
405
- * frame costs a handful of drawImage calls instead of a fillRect per visible
406
- * cell -- which ran to 885ms per block at the minimum column width.
403
+ * frame costs a handful of drawImage calls. A fillRect per visible cell measured
404
+ * 885ms per block at the minimum column width.
407
405
  *
408
- * Below a device pixel per cell the tiles average cells together instead, and
409
- * only along the axis that needs it. Leaving the downsampling to the browser's
410
- * image smoothing blurred both axes at once: at fit-to-width, where only the
411
- * columns are narrow, every row boundary turned into a gradient.
406
+ * Below a device pixel per cell the tiles average cells together, per axis.
407
+ * The browser's image smoothing blurs both axes, which turns every row
408
+ * boundary into a gradient at fit-to-width, where only the columns are narrow.
412
409
  */
413
410
  export function drawMsaRaster({
414
411
  ctx,
@@ -467,12 +464,11 @@ export function drawMsaRaster({
467
464
  /**
468
465
  * A raster canvas as a PNG data URI, for embedding in the SVG export.
469
466
  *
470
- * toDataURL is the synchronous read-back. The DOM canvas has it, and so does a
471
- * node canvas standing in for OffscreenCanvas headlessly; a browser
472
- * OffscreenCanvas -- which is what the tile and thumbnail caches hold wherever
473
- * it exists -- gets copied onto a DOM canvas first. Returns undefined wherever
474
- * the read-back is unavailable (jsdom) rather than throwing, which is the
475
- * signal to fall back to drawing the thing in vector.
467
+ * toDataURL is the synchronous read-back. The DOM canvas and a headless node
468
+ * canvas have it; a browser OffscreenCanvas, which the tile and thumbnail
469
+ * caches hold where it exists, is copied onto a DOM canvas first. Returns
470
+ * undefined where read-back is unavailable (jsdom), and the caller then draws
471
+ * in vector.
476
472
  */
477
473
  export function canvasHref(canvas: RasterCanvas | undefined) {
478
474
  if (!canvas) {
@@ -499,12 +495,10 @@ export function canvasHref(canvas: RasterCanvas | undefined) {
499
495
  }
500
496
  }
501
497
 
502
- // A canvas much past this many pixels fails to allocate, and the browser's own
503
- // per-side limit is lower still, so a raster bigger than either averages down,
504
- // per axis: an alignment wide enough to hit the limit on columns alone keeps
505
- // every row.
506
- // The result is still drawn across the same rectangle -- it loses cell-exact
507
- // detail at a size where no figure could show it anyway.
498
+ // A canvas much past this many pixels fails to allocate, and the browser's
499
+ // per-side limit is lower still, so a larger raster averages down per axis and
500
+ // is drawn across the same rectangle. An alignment that hits the limit on
501
+ // columns alone keeps every row.
508
502
  const maxImagePixels = 64e6
509
503
  const maxImageSide = 16384
510
504
 
@@ -512,14 +506,12 @@ const maxImageSide = 16384
512
506
  * The alignment rectangle [col0, col0+numCols) x [row0, row0+numRows) as a PNG
513
507
  * data URI, for the SVG export.
514
508
  *
515
- * SVG has no blit, so the export cannot reuse the tile cache the live canvas
516
- * draws from: one <image> is the whole background instead. That is the
517
- * difference between an export that scales and one that does not -- the vector
518
- * path emits a <rect> per cell, and a 200x500 alignment exhausts the heap
519
- * building them.
509
+ * SVG has no blit, so the export draws the whole background as one <image>. The
510
+ * vector path emits a <rect> per cell, and a 200x500 alignment exhausts the
511
+ * heap building them.
520
512
  *
521
- * Returns undefined wherever a canvas cannot be read back (jsdom, notably),
522
- * which is the signal to keep the per-cell path.
513
+ * Returns undefined where a canvas cannot be read back (jsdom), and the caller
514
+ * then keeps the per-cell path.
523
515
  */
524
516
  export function rasterImageHref({
525
517
  model,
@@ -578,9 +570,8 @@ export function rasterImageHref({
578
570
  }
579
571
 
580
572
  /**
581
- * The whole alignment sampled down to a strip small enough to sit behind the
582
- * minimap thumb, cached alongside the block tiles because it goes stale under
583
- * exactly the same conditions.
573
+ * The whole alignment sampled down to a strip behind the minimap thumb, cached
574
+ * with the block tiles since the same keys invalidate both.
584
575
  */
585
576
  export function msaThumbnail({
586
577
  model,
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Assigns each of one row's bands a lane: the first lane whose last band ends
3
+ * before this one starts, so only bands that actually overlap stack. A row of
4
+ * adjacent exons packs into a single lane; a family with a domain nested inside
5
+ * it takes two.
6
+ *
7
+ * Packing runs left to right, but the result keeps the input's largest-first
8
+ * paint order. `laneCount` is the row's depth, which the renderer divides the
9
+ * row height by so a deep stack stays inside its own row.
10
+ *
11
+ * The coordinates are whatever the caller packs in: alignment columns for the
12
+ * overlay, residue positions for a `features` row panel.
13
+ */
14
+ export function packDomainLanes<T extends { startCol: number; endCol: number }>(
15
+ bands: T[],
16
+ ): (T & { lane: number; laneCount: number })[] {
17
+ const laneEnds: number[] = []
18
+ const lanes = new Array<number>(bands.length)
19
+ const byStart = bands
20
+ .map((_, i) => i)
21
+ .sort((a, b) => bands[a]!.startCol - bands[b]!.startCol)
22
+
23
+ for (const i of byStart) {
24
+ const { startCol, endCol } = bands[i]!
25
+ const found = laneEnds.findIndex(end => end <= startCol)
26
+ const lane = found === -1 ? laneEnds.length : found
27
+ laneEnds[lane] = endCol
28
+ lanes[i] = lane
29
+ }
30
+
31
+ return bands.map((band, i) => ({
32
+ ...band,
33
+ lane: lanes[i]!,
34
+ laneCount: laneEnds.length,
35
+ }))
36
+ }