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
@@ -1,13 +1,16 @@
1
- import { subFeatureRowHeight } from '../../constants.ts'
1
+ import { domainUnderlineHeight, subFeatureRowHeight } from '../../constants.ts'
2
+ import { contrastTextFn } from '../../util.ts'
2
3
  import { getVisibleLeaves } from '../getVisibleLeaves.ts'
4
+ import { drawFeatureSpans, spanRows } from './drawFeatureSpans.ts'
3
5
 
4
- import type { HierarchyNode } from '../../hierarchy.ts'
5
6
  import type { MsaViewModel } from '../../model.ts'
6
- import type { NodeWithIdsAndLength } from '../../types.ts'
7
+ import type { DomainBand } from '../../types.ts'
7
8
  import type { RenderCtx } from '../renderCtx.ts'
9
+ import type { Theme } from '@mui/material'
8
10
 
9
11
  export function renderBoxFeatureCanvasBlock({
10
12
  model,
13
+ theme,
11
14
  offsetX,
12
15
  offsetY,
13
16
  ctx,
@@ -18,144 +21,85 @@ export function renderBoxFeatureCanvasBlock({
18
21
  offsetX: number
19
22
  offsetY: number
20
23
  model: MsaViewModel
24
+ theme: Theme
21
25
  ctx: RenderCtx
22
26
  highResScaleFactorOverride?: number
23
27
  blockSizeXOverride?: number
24
28
  blockSizeYOverride?: number
25
- }) {
26
- const { blockSize, rowHeight, highResScaleFactor, actuallyShowDomains } =
27
- model
28
- if (actuallyShowDomains) {
29
- const k = highResScaleFactorOverride ?? highResScaleFactor
30
- const bx = blockSizeXOverride ?? blockSize
31
- const by = blockSizeYOverride ?? blockSize
32
- ctx.resetTransform()
33
- ctx.scale(k, k)
34
- ctx.translate(-offsetX, rowHeight / 2 - offsetY)
35
-
36
- drawTiles({
37
- model,
38
- ctx,
39
- visibleLeaves: getVisibleLeaves({ model, offsetY, blockSizeY: by }),
40
- offsetX,
41
- blockWidth: bx,
42
- })
43
- }
44
- }
45
-
46
- function drawTiles({
47
- model,
48
- ctx,
49
- visibleLeaves,
50
- offsetX,
51
- blockWidth,
52
- }: {
53
- model: MsaViewModel
54
- ctx: RenderCtx
55
- visibleLeaves: HierarchyNode<NodeWithIdsAndLength>[]
56
- offsetX: number
57
- blockWidth: number
58
29
  }) {
59
30
  const {
31
+ blockSize,
32
+ rowHeight,
33
+ highResScaleFactor,
34
+ actuallyShowDomains,
60
35
  subFeatureRows,
61
36
  colWidth,
62
- rowHeight,
63
- fillPalette,
64
- strokePalette,
37
+ featureColors,
38
+ featureLabels,
65
39
  segmentLabels,
66
40
  showMsaLetters,
41
+ domainUnderline,
67
42
  domainBands,
68
43
  } = model
69
- const h = subFeatureRows ? subFeatureRowHeight : rowHeight
44
+ if (!actuallyShowDomains) {
45
+ return
46
+ }
47
+ const k = highResScaleFactorOverride ?? highResScaleFactor
48
+ const bx = blockSizeXOverride ?? blockSize
49
+ const by = blockSizeYOverride ?? blockSize
50
+ ctx.resetTransform()
51
+ ctx.scale(k, k)
52
+ ctx.translate(-offsetX, rowHeight / 2 - offsetY)
53
+
54
+ // the plain and underline modes give every band the same height; a sub-row
55
+ // band is thinner, and how thin depends on how many lanes its row needs
56
+ const barHeight = domainUnderline
57
+ ? Math.min(domainUnderlineHeight, rowHeight)
58
+ : rowHeight
70
59
  // exon numbers label the bands only when residue letters aren't drawn (zoomed
71
60
  // out); when letters show, the alternating shades alone mark the boundaries
72
- // and a number would collide with the sequence
73
- const drawSegmentLabels = !showMsaLetters && !subFeatureRows && h >= 9
74
- // gene arrow heads stick out one row-height past the band, so pad the cull
75
- // window enough that a band just outside the block still draws its head
76
- const cull = h + colWidth
77
- const xMin = offsetX - cull
78
- const xMax = offsetX + blockWidth + cull
79
-
61
+ // and a number would collide with the sequence. A featureLabel encoding names
62
+ // what every span carries instead
63
+ const drawSegmentLabels = !featureLabels && !showMsaLetters && !subFeatureRows
80
64
  // a segment (exon) number labels its band once per block, on the topmost
81
65
  // visible row carrying that segment, so it reads as a column header for the
82
66
  // whole band. Keyed by accession rather than drawn on row 0, because the rows
83
67
  // carrying the gene model are often not the first ones in the alignment
84
68
  const labelled = drawSegmentLabels ? new Set<string>() : undefined
85
69
 
86
- for (const node of visibleLeaves) {
87
- const y = node.x!
88
- const bands = domainBands.get(node.data.name)
89
-
90
- if (bands) {
91
- for (const { annotation, startCol, endCol, stackIndex } of bands) {
92
- const { accession, strand } = annotation
93
- const x = startCol * colWidth
94
- const lw = colWidth * (endCol - startCol)
95
- if (x + lw >= xMin && x <= xMax) {
96
- const t = y - rowHeight + (subFeatureRows ? stackIndex * h : 0)
97
- ctx.fillStyle = fillPalette[accession]!
98
- ctx.strokeStyle = strokePalette[accession]!
99
- if (strand === undefined) {
100
- ctx.fillRect(x, t, lw, h)
101
- ctx.strokeRect(x, t, lw, h)
102
- const label =
103
- labelled && !labelled.has(accession)
104
- ? segmentLabels.get(accession)
105
- : undefined
106
- if (label !== undefined) {
107
- const fontSize = Math.min(h - 2, 11)
108
- ctx.font = `${fontSize}px sans-serif`
109
- if (ctx.measureText(label).width + 2 <= lw) {
110
- labelled!.add(accession)
111
- ctx.fillStyle = '#222'
112
- ctx.textAlign = 'center'
113
- ctx.textBaseline = 'middle'
114
- ctx.fillText(label, x + lw / 2, t + h / 2)
115
- }
116
- }
117
- } else {
118
- drawGeneArrow({ ctx, x, t, w: lw, h, strand })
119
- }
120
- }
121
- }
122
- }
123
- }
124
- }
125
-
126
- // A gene arrow: a full-width rectangular body spanning the feature's
127
- // start..end columns, plus a triangular head that points *beyond* that end in
128
- // the strand direction (right for +, left for -). Keeping the body aligned to
129
- // the exact start/end columns means + and - strand features read as having the
130
- // same boundaries; the arrow lives outside them purely to show transcription
131
- // direction, preserving the column-by-column homology down the rows.
132
- function drawGeneArrow({
133
- ctx,
134
- x,
135
- t,
136
- w,
137
- h,
138
- strand,
139
- }: {
140
- ctx: RenderCtx
141
- x: number
142
- t: number
143
- w: number
144
- h: number
145
- strand: number
146
- }) {
147
- // body spans bodyStart..bodyEnd (the exact columns); the head extends one
148
- // row-height past bodyEnd in the strand direction
149
- const dir = strand > 0 ? 1 : -1
150
- const bodyStart = strand > 0 ? x : x + w
151
- const bodyEnd = strand > 0 ? x + w : x
152
- ctx.beginPath()
153
- ctx.moveTo(bodyStart, t)
154
- ctx.lineTo(bodyEnd, t)
155
- ctx.lineTo(bodyEnd + dir * h, t + h / 2)
156
- ctx.lineTo(bodyEnd, t + h)
157
- ctx.lineTo(bodyStart, t + h)
158
- ctx.closePath()
159
- ctx.fill()
160
- ctx.stroke()
70
+ drawFeatureSpans<DomainBand>({
71
+ ctx,
72
+ rows: spanRows(
73
+ getVisibleLeaves({ model, offsetY, blockSizeY: by }),
74
+ domainBands,
75
+ ),
76
+ layout: {
77
+ // sub-rows are thin, but a row whose lanes would spill onto the row below
78
+ // shares out the row height between them instead
79
+ height: laneCount =>
80
+ subFeatureRows
81
+ ? Math.min(subFeatureRowHeight, rowHeight / laneCount)
82
+ : barHeight,
83
+ top: (y, lane, h) =>
84
+ y -
85
+ rowHeight +
86
+ (subFeatureRows ? lane * h : domainUnderline ? rowHeight - h : 0),
87
+ headLength: h => (domainUnderline ? rowHeight / 2 : h),
88
+ // a head only as tall as an underline bar tapers over ten pixels of
89
+ // three, which reads as nothing; it takes the lower two fifths of the
90
+ // row instead, leaving the letters above it
91
+ headRise: h => (domainUnderline ? Math.max(0, rowHeight * 0.4 - h) : 0),
92
+ },
93
+ xOf: band => [band.startCol * colWidth, band.endCol * colWidth],
94
+ colors: featureColors,
95
+ labelOf: band =>
96
+ featureLabels?.get(band.annotation) ??
97
+ (labelled && !labelled.has(band.annotation.accession)
98
+ ? segmentLabels.get(band.annotation.accession)
99
+ : undefined),
100
+ labelDrawn: band => labelled?.add(band.annotation.accession),
101
+ contrastText: contrastTextFn(theme),
102
+ xMin: offsetX,
103
+ xMax: offsetX + bx,
104
+ })
161
105
  }
@@ -5,17 +5,16 @@ import type { MsaViewModel } from '../../model.ts'
5
5
  import type { RenderCtx } from '../renderCtx.ts'
6
6
  import type { Theme } from '@mui/material'
7
7
 
8
- // a stronger fill plus a solid border, so a domain/motif band reads clearly over
9
- // the colored alignment cells (the faint hover-style wash alone is invisible
10
- // against clustalx coloring)
8
+ // a fill plus a solid border; the hover wash alone is invisible over clustalx
9
+ // coloring
11
10
  export const highlightFill = 'rgba(255,140,0,0.28)'
12
11
  export const highlightBorder = 'rgba(210,90,0,0.95)'
13
12
  export const highlightRowFill = 'rgba(255,140,0,0.18)'
14
13
 
15
14
  export const labelFontSize = 11
16
15
  const labelPad = 3
17
- // the label box a highlight draws above its band, and the reason a tree block
18
- // has to consider rows a little outside itself
16
+ // the label box above a highlight band; tree blocks also check rows this far
17
+ // outside themselves
19
18
  export const highlightLabelHeight = labelFontSize + labelPad * 2
20
19
 
21
20
  export interface LabelBox {
@@ -29,11 +28,9 @@ export interface LabelBox {
29
28
  * A highlight's label, over its band where it fits and beside it where it does
30
29
  * not, kept inside `bounds` and out of the way of the labels already placed.
31
30
  *
32
- * Two single-residue highlights a column apart both want the same few pixels to
33
- * the right of their bands, and a band scrolled off the left edge wants its
34
- * label off-frame with it: the first overdrew two labels into an unreadable
35
- * smudge, the second left the visible band untitled. Pass `placed` and each
36
- * later label steps down a row instead of over the one before.
31
+ * `bounds` keeps the label of a band scrolled partly off the left edge visible.
32
+ * With `placed`, a label that would overlap an earlier one, as two
33
+ * single-residue highlights a column apart do, steps down a row.
37
34
  */
38
35
  export function drawHighlightLabel({
39
36
  ctx,
@@ -81,11 +78,16 @@ export function drawHighlightLabel({
81
78
  }
82
79
 
83
80
  /**
84
- * The persistent `highlights` overlay on the alignment: row tints, bordered
85
- * column bands, and a label above each band. `offsetX`/`offsetY` are the
86
- * content coordinates at the canvas origin, the convention renderMSABlock
81
+ * The persistent overlay on the alignment: the band behind each clade of
82
+ * `clades`, the `rowTint` encoding's wash, the row tints and bordered column
83
+ * bands of `highlights`, and a label above each band. `offsetX`/`offsetY` are
84
+ * the content coordinates at the canvas origin, the convention renderMSABlock
87
85
  * uses, so the live overlay passes -scrollX/-scrollY and the export passes its
88
86
  * layout offsets.
87
+ *
88
+ * Rows are culled to the ones this block covers. A tint over every row of a
89
+ * large alignment would otherwise draw the whole column of rects in each block,
90
+ * and again on every pointer move through renderMouseover.
89
91
  */
90
92
  export function renderHighlights({
91
93
  ctx,
@@ -104,13 +106,45 @@ export function renderHighlights({
104
106
  width: number
105
107
  height: number
106
108
  }) {
107
- const { resolvedHighlights, colWidth, rowHeight } = model
109
+ const { resolvedClades, resolvedHighlights, rowTints, colWidth, rowHeight } =
110
+ model
108
111
  const placed: LabelBox[] = []
109
112
  ctx.lineWidth = 2
113
+ const firstRow = Math.max(0, Math.floor(offsetY / rowHeight))
114
+ const lastRow = Math.ceil((offsetY + height) / rowHeight)
115
+ const fillRow = (index: number) => {
116
+ ctx.fillRect(0, index * rowHeight - offsetY, width, rowHeight)
117
+ }
118
+ for (const { rows, color, mark } of resolvedClades) {
119
+ if (mark === 'highlight' && rows[1] >= firstRow && rows[0] <= lastRow) {
120
+ ctx.fillStyle = color
121
+ ctx.fillRect(
122
+ 0,
123
+ rows[0] * rowHeight - offsetY,
124
+ width,
125
+ (rows[1] - rows[0] + 1) * rowHeight,
126
+ )
127
+ }
128
+ }
129
+ if (rowTints) {
130
+ for (
131
+ let index = firstRow;
132
+ index <= Math.min(lastRow, rowTints.length - 1);
133
+ index++
134
+ ) {
135
+ const color = rowTints[index]
136
+ if (color) {
137
+ ctx.fillStyle = color
138
+ fillRow(index)
139
+ }
140
+ }
141
+ }
110
142
  for (const { rowIndices, color } of resolvedHighlights) {
111
143
  ctx.fillStyle = color ?? highlightRowFill
112
144
  for (const index of rowIndices) {
113
- ctx.fillRect(0, index * rowHeight - offsetY, width, rowHeight)
145
+ if (index >= firstRow && index <= lastRow) {
146
+ fillRow(index)
147
+ }
114
148
  }
115
149
  }
116
150
  for (const { startCol, endCol, label, color } of resolvedHighlights) {
@@ -139,11 +173,10 @@ export function renderHighlights({
139
173
  }
140
174
 
141
175
  /**
142
- * Everything the alignment overlay owes to the document rather than to the
143
- * mouse: the reference row's tint, a bordered band per run of highlighted
144
- * columns, and the `highlights` layer. The live overlay canvas draws these
145
- * under its hover and click bands, and the SVG export draws them and stops --
146
- * a shared link that opens with columns highlighted exports with them too.
176
+ * The overlay parts that come from the snapshot: the reference row's tint, a
177
+ * bordered band per run of highlighted columns, and the `highlights` layer. The
178
+ * live overlay draws these under its hover and click bands; the SVG export
179
+ * draws only these.
147
180
  */
148
181
  export function renderPersistentHighlights({
149
182
  ctx,
@@ -113,18 +113,18 @@ function drawTilesAndText({
113
113
  columns,
114
114
  colWidth,
115
115
  rowHeight,
116
+ fontSize,
116
117
  relativeTo,
117
118
  showMsaLetters,
118
119
  subFeatureRows,
120
+ domainUnderline,
119
121
  domainBandsByStart,
122
+ featureColors,
120
123
  } = model
121
124
 
122
125
  const tiles = drawTiles && bgColor
123
126
  const paintTiles = tiles && !rasterTiles
124
127
  if (paintTiles || showMsaLetters) {
125
- // a letter takes its color from the cell it lands on, so the scheme lookup
126
- // is only waste when nothing draws letters
127
- const needsColor = paintTiles || showMsaLetters
128
128
  const tileColor = tileColorFn(model)
129
129
  // Domain fills come from a categorical palette with no fixed lightness, and
130
130
  // a dynamic scheme colors a cell from its column, so a readable letter color
@@ -132,12 +132,15 @@ function drawTilesAndText({
132
132
  const contrastText = contrastTextFn(theme)
133
133
  const offsetXAligned = xStart * colWidth
134
134
  const halfColWidth = colWidth / 2
135
- // note: -rowHeight/4 matches +rowHeight/4 in tree
136
- const quarterRowHeight = rowHeight / 4
135
+ // the context is translated by rowHeight/2, so `y` is the row's bottom
136
+ // edge and the baseline lands fontSize/4 below the row center, where the
137
+ // tree puts its tip labels
138
+ const baselineOffset = rowHeight / 2 - fontSize / 4
137
139
  // with the tiles coming from the domain overlay instead, letters sit either
138
140
  // on a domain box or on the plain background; sub-row layout stacks the boxes
139
- // above the letters, so those rows are all plain background
140
- const overDomains = !drawTiles && !subFeatureRows
141
+ // above the letters and letter-color mode shrinks them to a bar under the
142
+ // row, so neither leaves anything behind a letter
143
+ const overDomains = !drawTiles && !subFeatureRows && !domainUnderline
141
144
 
142
145
  for (let i = 0, l1 = visibleLeaves.length; i < l1; i++) {
143
146
  const node = visibleLeaves[i]!
@@ -146,7 +149,7 @@ function drawTilesAndText({
146
149
  const str = columns.get(name)?.slice(xStart, xEnd)
147
150
  if (str) {
148
151
  const tileY = y - rowHeight
149
- const textY = y - quarterRowHeight
152
+ const textY = y - baselineOffset
150
153
  const bandAt = domainBandCursor(
151
154
  overDomains ? domainBandsByStart.get(name) : undefined,
152
155
  )
@@ -157,7 +160,7 @@ function drawTilesAndText({
157
160
  const x = j * colWidth + offsetXAligned
158
161
  const isMatchingReference =
159
162
  referenceSeq && name !== relativeTo && letter === referenceSeq[j]
160
- const color = needsColor ? tileColor(col, letter) : undefined
163
+ const color = tileColor(col, letter)
161
164
 
162
165
  if (paintTiles) {
163
166
  ctx.fillStyle = isMatchingReference
@@ -170,7 +173,7 @@ function drawTilesAndText({
170
173
  const covering = bandAt(col)
171
174
  ctx.fillStyle = covering
172
175
  ? // on top of a domain box: contrast against the box fill
173
- contrastText(model.fillPalette[covering.annotation.accession])
176
+ contrastText(featureColors.get(covering.annotation)?.fill)
174
177
  : isMatchingReference
175
178
  ? // the dot sits on the faint reference-match wash, which is
176
179
  // the theme's background with a little of its text color in it
@@ -179,7 +182,7 @@ function drawTilesAndText({
179
182
  ? // on top of a colored tile, or on the background where the
180
183
  // scheme gives that cell no color
181
184
  contrastText(color)
182
- : !drawTiles || !color
185
+ : bgColor || !color
183
186
  ? // plain background, uncolored letters
184
187
  theme.palette.text.primary
185
188
  : // letter-color mode: darken/lighten to stay readable
@@ -1,10 +1,8 @@
1
1
  import type { MsaViewModel } from '../../model.ts'
2
2
 
3
- // Per-cell tile color for the active scheme. The scheme is fixed for the whole
4
- // block, so resolve which rule applies once rather than re-testing the scheme
5
- // name inside the innermost loop. Reading only the column table the active
6
- // scheme needs also keeps the other one -- a per-column map for every column of
7
- // the alignment -- from being computed at all.
3
+ // Per-cell tile color for the active scheme, resolved once per block. Reading
4
+ // only the active scheme's column table leaves the other per-column table
5
+ // uncomputed.
8
6
  export function tileColorFn(model: MsaViewModel) {
9
7
  const { colorSchemeName } = model
10
8
  if (colorSchemeName === 'clustalx_protein_dynamic') {
@@ -23,8 +21,7 @@ export function tileColorFn(model: MsaViewModel) {
23
21
  }
24
22
 
25
23
  // The column table the active scheme colors from, or undefined for the static
26
- // schemes. Identity of this plus the scheme name is what a cache of rendered
27
- // colors has to key on.
24
+ // schemes. A cache of rendered colors keys on its identity plus the scheme name.
28
25
  export function tileColorTable(model: MsaViewModel) {
29
26
  const { colorSchemeName } = model
30
27
  if (colorSchemeName === 'clustalx_protein_dynamic') {
@@ -29,9 +29,11 @@ function eventToColRow({
29
29
  * Translates pointer events over one MSA block into model column/row hover and
30
30
  * click state, and reports where a tooltip should be anchored.
31
31
  *
32
- * The anchor is only tracked when there is something to show (an insertion, a
33
- * domain under the cursor, or column stats), so plain mouse movement over the
34
- * alignment updates the canvas overlay without re-rendering this component.
32
+ * The anchor is only tracked when there is something to show (an insertion or a
33
+ * domain under the cursor), so plain mouse movement over the alignment updates
34
+ * the canvas overlay without re-rendering this component. Each track tooltips
35
+ * its own column statistics, and the header reads out the hovered row, residue
36
+ * and position.
35
37
  */
36
38
  export function useMsaBlockMouse({
37
39
  model,
@@ -62,9 +64,7 @@ export function useMsaBlockMouse({
62
64
  model.setMousePos(hit ? pos.col : undefined, hit ? pos.row : undefined)
63
65
 
64
66
  const hasTooltip =
65
- !!model.hoveredInsertion ||
66
- model.mouseOverDomains.length > 0 ||
67
- (model.showColumnStats && !!model.mouseOverColumnStats)
67
+ !!model.hoveredInsertion || model.mouseOverDomains.length > 0
68
68
  setTooltipPoint(
69
69
  hasTooltip ? { x: event.clientX, y: event.clientY } : undefined,
70
70
  )
@@ -0,0 +1,139 @@
1
+ import React, { useState } from 'react'
2
+
3
+ import { useTheme } from '@mui/material'
4
+ import { observer } from 'mobx-react'
5
+
6
+ import { featureField, featureName } from '../../featureFields.ts'
7
+ import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
8
+ import PortalTooltip from '../PortalTooltip.tsx'
9
+ import { renderRowPanel } from './renderRowPanel.ts'
10
+
11
+ import type { MsaViewModel } from '../../model.ts'
12
+ import type { ResolvedRowPanel } from '../../types.ts'
13
+
14
+ interface Hover {
15
+ name: string
16
+ lines: string[]
17
+ clientX: number
18
+ clientY: number
19
+ }
20
+
21
+ // what the cell or span under the pointer reads, which is the field a strip
22
+ // colors by, and a span's name, its coordinates and its encoded field
23
+ function hoverLines({
24
+ model,
25
+ panel,
26
+ name,
27
+ x,
28
+ }: {
29
+ model: MsaViewModel
30
+ panel: ResolvedRowPanel
31
+ name: string
32
+ x: number
33
+ }) {
34
+ if (panel.kind === 'strip') {
35
+ return [
36
+ `${panel.header}: ${model.rowDataOf(name)?.[panel.field] ?? '(none)'}`,
37
+ ]
38
+ }
39
+ const span = panel.spans
40
+ .get(name)
41
+ ?.findLast(s => x >= s.xStart && x <= s.xEnd)
42
+ if (!span) {
43
+ return []
44
+ }
45
+ const { annotation } = span
46
+ const strand =
47
+ annotation.strand === undefined
48
+ ? ''
49
+ : annotation.strand < 0
50
+ ? ' (-)'
51
+ : ' (+)'
52
+ const value = panel.field ? featureField(annotation, panel.field) : undefined
53
+ return [
54
+ featureName(annotation) ?? annotation.accession,
55
+ `${annotation.start}-${annotation.end}${strand}`,
56
+ ...(value === undefined ? [] : [`${panel.field}: ${value}`]),
57
+ ]
58
+ }
59
+
60
+ const RowPanelBlock = observer(function ({
61
+ model,
62
+ panel,
63
+ offsetY,
64
+ }: {
65
+ model: MsaViewModel
66
+ panel: ResolvedRowPanel
67
+ offsetY: number
68
+ }) {
69
+ const [hovered, setHovered] = useState<Hover>()
70
+ const theme = useTheme()
71
+ const { blockSize, highResScaleFactor, rowHeight } = model
72
+ const width = panel.width
73
+ const height = blockSize
74
+ const canvasWidth = width * highResScaleFactor
75
+ const canvasHeight = height * highResScaleFactor
76
+
77
+ const ref = useCanvasAutorun({
78
+ draw: ctx => {
79
+ ctx.resetTransform()
80
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight)
81
+ renderRowPanel({ ctx, model, theme, panel, x: 0, offsetY })
82
+ },
83
+ width: canvasWidth,
84
+ height: canvasHeight,
85
+ deps: [model, theme, panel, offsetY],
86
+ })
87
+
88
+ return (
89
+ <>
90
+ <canvas
91
+ ref={ref}
92
+ width={canvasWidth}
93
+ height={canvasHeight}
94
+ style={{ position: 'absolute', top: offsetY, left: 0, width, height }}
95
+ onMouseMove={event => {
96
+ // the blocks are laid out in row space and scrolled by a transform
97
+ // on their container, so the pointer's row comes off the canvas box
98
+ const { left, top } = event.currentTarget.getBoundingClientRect()
99
+ const row = Math.floor((event.clientY - top + offsetY) / rowHeight)
100
+ const name = model.leaves[row]?.data.name
101
+ const lines =
102
+ name === undefined
103
+ ? []
104
+ : hoverLines({
105
+ model,
106
+ panel,
107
+ name,
108
+ x: event.clientX - left,
109
+ })
110
+ setHovered(
111
+ name === undefined || lines.length === 0
112
+ ? undefined
113
+ : {
114
+ name,
115
+ lines,
116
+ clientX: event.clientX,
117
+ clientY: event.clientY,
118
+ },
119
+ )
120
+ model.setMousePos(undefined, name === undefined ? undefined : row)
121
+ }}
122
+ onMouseLeave={() => {
123
+ setHovered(undefined)
124
+ model.setMousePos(undefined, undefined)
125
+ }}
126
+ />
127
+ {hovered ? (
128
+ <PortalTooltip clientX={hovered.clientX} clientY={hovered.clientY}>
129
+ <div>{hovered.name}</div>
130
+ {hovered.lines.map(line => (
131
+ <div key={line}>{line}</div>
132
+ ))}
133
+ </PortalTooltip>
134
+ ) : null}
135
+ </>
136
+ )
137
+ })
138
+
139
+ export default RowPanelBlock
@@ -0,0 +1,57 @@
1
+ import React from 'react'
2
+
3
+ import { observer } from 'mobx-react'
4
+
5
+ import { headerRunsAcross } from './headerLayout.ts'
6
+
7
+ import type { MsaViewModel } from '../../model.ts'
8
+
9
+ /**
10
+ * The row panel headers, in a band above the panels beside the tree ruler and
11
+ * the minimap. A narrow strip's name is DOM text turned on its side, since the
12
+ * canvas layer has no rotation, and the SVG export draws it with a
13
+ * `rotate(-90)`. A panel wider than the band is tall writes its name across.
14
+ */
15
+ const RowPanelHeaders = observer(function ({ model }: { model: MsaViewModel }) {
16
+ const { resolvedRowPanels, rowPanelsHeaderHeight, fontSize } = model
17
+ return resolvedRowPanels.length > 0 ? (
18
+ <div
19
+ style={{
20
+ display: 'flex',
21
+ flexShrink: 0,
22
+ height: rowPanelsHeaderHeight,
23
+ alignItems: 'flex-end',
24
+ }}
25
+ >
26
+ {resolvedRowPanels.map(panel => {
27
+ const across = headerRunsAcross(panel.width)
28
+ return (
29
+ <div
30
+ key={panel.id}
31
+ title={panel.header}
32
+ style={{
33
+ width: panel.width,
34
+ overflow: 'hidden',
35
+ textOverflow: 'ellipsis',
36
+ whiteSpace: 'nowrap',
37
+ ...(across
38
+ ? { fontSize, textAlign: 'center' }
39
+ : {
40
+ height: rowPanelsHeaderHeight,
41
+ fontSize: Math.min(fontSize, panel.width),
42
+ // vertical-rl reads top to bottom, and the half turn makes
43
+ // it read upwards from the strip, as rotate(-90) exports
44
+ writingMode: 'vertical-rl',
45
+ transform: 'rotate(180deg)',
46
+ }),
47
+ }}
48
+ >
49
+ {panel.header}
50
+ </div>
51
+ )
52
+ })}
53
+ </div>
54
+ ) : null
55
+ })
56
+
57
+ export default RowPanelHeaders