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
@@ -0,0 +1,58 @@
1
+ import type { ColumnCounts } from './columnCounts.ts'
2
+
3
+ export interface ColumnStats {
4
+ col: number
5
+ total: number
6
+ gaps: number
7
+ gapFraction: number
8
+ conservation: number
9
+ propertyConservation: number | undefined
10
+ consensusLetter: string
11
+ consensusCount: number
12
+ consensusFraction: number
13
+ distribution: [string, number][]
14
+ }
15
+
16
+ /**
17
+ * Summarize one column: consensus residue and its identity fraction, the two
18
+ * conservation scores, gap fraction, and the non-gap residue distribution
19
+ * sorted by count. undefined past the end of the alignment or for a column with
20
+ * no sequences.
21
+ *
22
+ * A module-level function: a getter that reached the method through `this` made
23
+ * TypeScript infer the whole views object to type it, and fell back to `any`.
24
+ */
25
+ export function columnStats({
26
+ col,
27
+ colStats,
28
+ conservation,
29
+ propertyConservation,
30
+ }: {
31
+ col: number
32
+ colStats: ColumnCounts
33
+ conservation: number[]
34
+ propertyConservation: number[]
35
+ }): ColumnStats | undefined {
36
+ if (col >= colStats.numColumns) {
37
+ return undefined
38
+ }
39
+ const total = colStats.total(col)
40
+ if (!total) {
41
+ return undefined
42
+ }
43
+ const gaps = colStats.gapCount(col)
44
+ const distribution = colStats.residueEntries(col).sort((a, b) => b[1] - a[1])
45
+ const consensus = distribution[0]
46
+ return {
47
+ col,
48
+ total,
49
+ gaps,
50
+ gapFraction: gaps / total,
51
+ conservation: conservation[col] ?? 0,
52
+ propertyConservation: propertyConservation[col],
53
+ consensusLetter: consensus?.[0] ?? '',
54
+ consensusCount: consensus?.[1] ?? 0,
55
+ consensusFraction: consensus ? consensus[1] / total : 0,
56
+ distribution,
57
+ }
58
+ }
@@ -1,7 +1,11 @@
1
1
  import React, { useState } from 'react'
2
2
 
3
+ import { useTheme } from '@mui/material'
4
+
3
5
  import { useDragScroll } from '../useDragScroll.ts'
4
6
 
7
+ import type { Theme } from '@mui/material'
8
+
5
9
  export const scrollbarThumbFill = 'rgba(66,119,127,0.3)'
6
10
 
7
11
  // The scroll thumbs (minimap, vertical scrollbar) and the resize dividers are
@@ -11,23 +15,29 @@ export const scrollbarThumbFill = 'rgba(66,119,127,0.3)'
11
15
  const variants: Record<
12
16
  'thumb' | 'resizer',
13
17
  {
14
- fill: string
15
- fillHovered: string
18
+ fill: (theme: Theme) => string
19
+ fillHovered: (theme: Theme) => string
16
20
  cursor: (axis: 'x' | 'y') => string
17
21
  position: React.CSSProperties['position']
18
22
  zIndex?: number
19
23
  }
20
24
  > = {
21
25
  thumb: {
22
- fill: scrollbarThumbFill,
23
- fillHovered: 'rgba(66,119,127,0.6)',
26
+ fill: () => scrollbarThumbFill,
27
+ fillHovered: () => 'rgba(66,119,127,0.6)',
24
28
  cursor: () => 'pointer',
25
29
  position: 'absolute',
26
30
  zIndex: 100,
27
31
  },
28
32
  resizer: {
29
- fill: 'rgba(200,200,200)',
30
- fillHovered: 'rgba(150,150,150)',
33
+ fill: theme =>
34
+ theme.palette.mode === 'dark'
35
+ ? theme.palette.grey[800]
36
+ : 'rgba(200,200,200)',
37
+ fillHovered: theme =>
38
+ theme.palette.mode === 'dark'
39
+ ? theme.palette.grey[600]
40
+ : 'rgba(150,150,150)',
31
41
  cursor: axis => (axis === 'x' ? 'ew-resize' : 'ns-resize'),
32
42
  position: 'relative',
33
43
  },
@@ -54,6 +64,7 @@ export default function DragHandle({
54
64
  onDrag: (delta: number, startValue: number) => void
55
65
  style: React.CSSProperties
56
66
  }) {
67
+ const theme = useTheme()
57
68
  const [hovered, setHovered] = useState(false)
58
69
  const { startDrag } = useDragScroll(axis, onDrag)
59
70
  const { fill, fillHovered, cursor, position, zIndex } = variants[variant]
@@ -73,7 +84,7 @@ export default function DragHandle({
73
84
  position,
74
85
  zIndex,
75
86
  cursor: cursor(axis),
76
- background: hovered ? fillHovered : fill,
87
+ background: hovered ? fillHovered(theme) : fill(theme),
77
88
  ...style,
78
89
  }}
79
90
  />
@@ -109,12 +109,8 @@ const Loading = observer(function ({ model }: { model: MsaViewModel }) {
109
109
  <MSAView model={model} />
110
110
  )
111
111
  ) : error ? (
112
- // Ahead of the spinner, because a failed load KEEPS its filehandle --
113
- // only an abort clears it. So hasPendingFilehandle stays true, this
114
- // fell to the spinner below, and the ImportForm that renders
115
- // `model.error` was never reached: the reader got "Downloading file"
116
- // and a Cancel button for as long as they were willing to watch it.
117
- // Found on AlphaFold's files/msa/ prefix answering 403 to every key.
112
+ // checked before the spinner: a failed load keeps its filehandle
113
+ // (only an abort clears it), so hasPendingFilehandle stays true
118
114
  <Reset model={model} error={error} />
119
115
  ) : hasPendingFilehandle || isLoading ? (
120
116
  <LoadingSpinner model={model} />
@@ -12,16 +12,27 @@ import Header from './header/Header.tsx'
12
12
  import Minimap from './minimap/Minimap.tsx'
13
13
  import MSAPanel from './msa/MSAPanel.tsx'
14
14
  import { clickColor, hoverColor } from './overlayColors.ts'
15
+ import RowPanelHeaders from './rowpanels/RowPanelHeaders.tsx'
16
+ import RowPanels from './rowpanels/RowPanels.tsx'
17
+ import TreeOverview from './tree/TreeOverview.tsx'
15
18
  import TreePanel from './tree/TreePanel.tsx'
16
19
  import TreeRuler from './tree/TreeRuler.tsx'
17
20
 
18
21
  import type { MsaViewModel } from '../model.ts'
19
22
 
23
+ // The band above the panels: the tree overview and scale bar, the strip
24
+ // headers, and the minimap over the alignment. The spacer stands in for the
25
+ // resize handle, so the minimap starts exactly where the alignment canvas does.
20
26
  const TopArea = observer(function ({ model }: { model: MsaViewModel }) {
21
- const { showHorizontalScrollbar } = model
27
+ const { showHorizontalScrollbar, showTreeOverview, resizeHandleWidth } = model
22
28
  return (
23
29
  <div style={{ display: 'flex' }}>
24
- <TreeRuler model={model} />
30
+ <div style={{ flexShrink: 0 }}>
31
+ {showTreeOverview ? <TreeOverview model={model} /> : null}
32
+ <TreeRuler model={model} />
33
+ </div>
34
+ <RowPanelHeaders model={model} />
35
+ <div style={{ width: resizeHandleWidth, flexShrink: 0 }} />
25
36
  {showHorizontalScrollbar ? <Minimap model={model} /> : null}
26
37
  </div>
27
38
  )
@@ -38,6 +49,7 @@ const TrackColumnIndicator = observer(function ({
38
49
  colWidth,
39
50
  scrollX,
40
51
  treeAreaWidth,
52
+ rowPanelsWidth,
41
53
  resizeHandleWidth,
42
54
  totalTrackAreaHeight,
43
55
  msaCanvasWidth,
@@ -53,7 +65,7 @@ const TrackColumnIndicator = observer(function ({
53
65
  <div
54
66
  style={{
55
67
  position: 'absolute',
56
- left: treeAreaWidth + resizeHandleWidth,
68
+ left: treeAreaWidth + rowPanelsWidth + resizeHandleWidth,
57
69
  top: 0,
58
70
  width: msaCanvasWidth,
59
71
  height: totalTrackAreaHeight,
@@ -102,6 +114,7 @@ const MainArea = observer(function ({ model }: { model: MsaViewModel }) {
102
114
  return (
103
115
  <div style={{ display: 'flex' }}>
104
116
  <TreePanel model={model} />
117
+ <RowPanels model={model} />
105
118
  <VerticalResizeHandle model={model} />
106
119
  <MSAPanel model={model} />
107
120
  {showVerticalScrollbar ? <VerticalScrollbar model={model} /> : null}
@@ -120,14 +133,15 @@ const View = observer(function ({ model }: { model: MsaViewModel }) {
120
133
  })
121
134
 
122
135
  const MSAView = observer(function ({ model }: { model: MsaViewModel }) {
123
- const { height, viewInitialized, DialogComponent, DialogProps } = model
136
+ const { height, hideHeader, viewInitialized, DialogComponent, DialogProps } =
137
+ model
124
138
 
125
139
  return (
126
140
  <div>
127
141
  {viewInitialized ? (
128
142
  <>
129
143
  <div style={{ height, overflow: 'hidden' }}>
130
- <Header model={model} />
144
+ {hideHeader ? null : <Header model={model} />}
131
145
  <View model={model} />
132
146
  </div>
133
147
  <HorizontalResizeHandle model={model} />
@@ -4,16 +4,38 @@ import { createJBrowseTheme } from '@jbrowse/core/ui/theme'
4
4
  import useMeasure from '@jbrowse/core/util/useMeasure'
5
5
  import { destroy, isAlive } from '@jbrowse/mobx-state-tree'
6
6
  import { ThemeProvider } from '@mui/material/styles'
7
+ import { compareStructural, reaction, when } from 'mobx'
7
8
 
8
9
  import MSAModelF from '../model.ts'
9
10
  import Loading from './Loading.tsx'
10
11
 
11
- import type { ColumnTrackSpec, Highlight, ResidueMapping } from '../types.ts'
12
+ import type { MsaViewModel } from '../model.ts'
13
+ import type {
14
+ Cell,
15
+ Clade,
16
+ ColumnTrackSpec,
17
+ Encoding,
18
+ Highlight,
19
+ Region,
20
+ ResidueEncoding,
21
+ ResidueMapping,
22
+ RowPanelSpec,
23
+ Viewport,
24
+ } from '../types.ts'
12
25
  import type { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
26
+ import type { ThemeOptions } from '@mui/material/styles'
13
27
 
14
- const theme = createJBrowseTheme()
28
+ function resolveTheme(theme: MSAViewerProps['theme']) {
29
+ return createJBrowseTheme(
30
+ theme === 'dark'
31
+ ? { palette: { mode: 'dark' } }
32
+ : typeof theme === 'object'
33
+ ? theme
34
+ : undefined,
35
+ )
36
+ }
15
37
 
16
- interface MSAViewerProps {
38
+ export interface MSAViewerProps {
17
39
  msa?: string
18
40
  tree?: string
19
41
  gff?: string
@@ -27,9 +49,7 @@ interface MSAViewerProps {
27
49
  /** initial per-row pixel height */
28
50
  rowHeight?: number
29
51
  /**
30
- * hide columns at least this percent gaps (default 100, i.e. hide nothing).
31
- * A deep family's alignment is mostly insertions carried by a few members;
32
- * dropping those columns is what makes the rest readable
52
+ * hide columns at least this percent gaps (default 100, i.e. hide nothing)
33
53
  */
34
54
  allowedGappyness?: number
35
55
  /** alignment columns (0-based) to highlight with a persistent overlay */
@@ -40,21 +60,136 @@ interface MSAViewerProps {
40
60
  * for whole rows, each with an optional `label` and `color`
41
61
  */
42
62
  highlights?: Highlight[]
63
+ /**
64
+ * clades of the tree with a mark drawn over them: `{mrca, tips, mark}` or
65
+ * `{range, tips, mark}`, where `mark` is `highlight`, `bracket`, `collapse`
66
+ * or `focus` (see docs/layers.md)
67
+ */
68
+ clades?: Clade[]
43
69
  /** row name to diff every other row against (matches render as ".") */
44
70
  relativeTo?: string
45
71
  /** tracks supplied as data: per-column bar values or a text row (see docs/layers.md) */
46
72
  columnTracks?: ColumnTrackSpec[]
47
73
  /** which residue of which structure each row's residues are (see docs/layers.md) */
48
74
  residueMappings?: ResidueMapping[]
75
+ /** extra fields per row name, such as a lineage or a host (see docs/layers.md) */
76
+ rowData?: Record<string, Record<string, string>>
77
+ /**
78
+ * what the viewer's marks read from a table: `{channel, field, scale?}`,
79
+ * where `channel` is `tipLabel`, `rowTint` or `branch` over `rowData`, or
80
+ * `featureFill` or `featureLabel` over the annotations (see docs/layers.md)
81
+ */
82
+ encodings?: Encoding[]
83
+ /**
84
+ * panels between the tree and the alignment, one cell per row:
85
+ * `{kind: 'strip', field, scale?, width?, header?}` colors each row by a
86
+ * `rowData` field (see docs/layers.md)
87
+ */
88
+ rowPanels?: RowPanelSpec[]
49
89
  /** draw the phylogenetic tree (default true); false leaves a labels-only gutter */
50
90
  drawTree?: boolean
51
91
  /** fixed width (px) of the tree/label area */
52
92
  treeAreaWidth?: number
53
93
  /** auto-size the tree/label area to the labels (used when drawTree is false) */
54
94
  autoTreeAreaWidth?: boolean
95
+ /** draw branch lengths (default true); false draws a cladogram */
96
+ showBranchLen?: boolean
97
+ /**
98
+ * which channel `colorScheme` paints: `fill` (default) colors the background
99
+ * of the cell a residue sits in, `color` colors the letter itself and leaves
100
+ * the background plain, which lets a domain overlay mark each span with a bar
101
+ * under the row and the residue colors show through
102
+ */
103
+ residueEncoding?: ResidueEncoding
104
+ /**
105
+ * a span to zoom and scroll to once the alignment loads, and again whenever
106
+ * it changes: `{row, start, end}` in residues of that row, or `{start, end}`
107
+ * in columns, 1-based and inclusive
108
+ */
109
+ region?: Region
110
+ /** leave out the toolbar, for a page drawing its own controls */
111
+ hideHeader?: boolean
112
+ /**
113
+ * 'light' (default), 'dark', or MUI theme options (palette, typography)
114
+ * merged over the JBrowse theme
115
+ */
116
+ theme?: 'light' | 'dark' | ThemeOptions
117
+ /** the cell under the pointer, or undefined when it leaves the alignment */
118
+ onCellHover?: (cell: Cell | undefined) => void
119
+ /** the cell a click pinned, or undefined when a click clears it */
120
+ onCellClick?: (cell: Cell | undefined) => void
121
+ /** the alignment columns on screen, after every scroll, zoom and resize */
122
+ onViewportChange?: (viewport: Viewport | undefined) => void
123
+ /**
124
+ * the model the viewer built, for the model API; called again with the new
125
+ * model when msa, tree, gff or a filehandle changes
126
+ */
127
+ onModel?: (model: MsaViewModel) => void
128
+ }
129
+
130
+ type DataSource = Pick<
131
+ MSAViewerProps,
132
+ 'msa' | 'tree' | 'gff' | 'msaFilehandle' | 'treeFilehandle' | 'gffFilehandle'
133
+ >
134
+
135
+ function sameSource(a: DataSource, b: DataSource) {
136
+ return (
137
+ a.msa === b.msa &&
138
+ a.tree === b.tree &&
139
+ a.gff === b.gff &&
140
+ JSON.stringify([a.msaFilehandle, a.treeFilehandle, a.gffFilehandle]) ===
141
+ JSON.stringify([b.msaFilehandle, b.treeFilehandle, b.gffFilehandle])
142
+ )
143
+ }
144
+
145
+ /**
146
+ * A new alignment, tree or annotation file needs a new model, so the viewer
147
+ * remounts when one of those props changes and keeps its model across every
148
+ * other change.
149
+ */
150
+ export default function MSAViewer(props: MSAViewerProps) {
151
+ const { msa, tree, gff, msaFilehandle, treeFilehandle, gffFilehandle } = props
152
+ const source = {
153
+ msa,
154
+ tree,
155
+ gff,
156
+ msaFilehandle,
157
+ treeFilehandle,
158
+ gffFilehandle,
159
+ }
160
+ const [shown, setShown] = useState({ source, generation: 0 })
161
+ if (!sameSource(shown.source, source)) {
162
+ setShown({ source, generation: shown.generation + 1 })
163
+ }
164
+ return <Viewer key={shown.generation} {...props} />
55
165
  }
56
166
 
57
- export default function MSAViewer({
167
+ function useModelReaction<K extends 'hoveredCell' | 'clickedCell' | 'viewport'>(
168
+ model: MsaViewModel,
169
+ key: K,
170
+ handler: ((value: MsaViewModel[K]) => void) | undefined,
171
+ ) {
172
+ const latest = useRef(handler)
173
+ useEffect(() => {
174
+ latest.current = handler
175
+ })
176
+ const listening = !!handler
177
+ useEffect(
178
+ () =>
179
+ listening
180
+ ? reaction(
181
+ () => model[key],
182
+ value => {
183
+ latest.current?.(value)
184
+ },
185
+ { equals: compareStructural },
186
+ )
187
+ : undefined,
188
+ [model, key, listening],
189
+ )
190
+ }
191
+
192
+ function Viewer({
58
193
  msa,
59
194
  tree,
60
195
  gff,
@@ -68,20 +203,40 @@ export default function MSAViewer({
68
203
  allowedGappyness,
69
204
  highlightColumns,
70
205
  highlights,
206
+ clades,
71
207
  relativeTo,
72
208
  columnTracks,
73
209
  residueMappings,
210
+ rowData,
211
+ encodings,
212
+ rowPanels,
74
213
  drawTree,
214
+ residueEncoding,
75
215
  treeAreaWidth,
76
216
  autoTreeAreaWidth,
217
+ showBranchLen,
218
+ region,
219
+ hideHeader,
220
+ theme,
221
+ onCellHover,
222
+ onCellClick,
223
+ onViewportChange,
224
+ onModel,
77
225
  }: MSAViewerProps) {
78
- // lazy initializer: the model is created exactly once from the initial props
79
- // (a stable MST instance — not a value safe to recompute, so not useMemo)
80
226
  const [model] = useState(() =>
81
227
  MSAModelF().create({
82
228
  type: 'MsaView',
83
- ...(msa || tree || gff
84
- ? { data: { msa: msa ?? '', tree: tree ?? '', gff } }
229
+ ...(msa || tree || gff || rowData
230
+ ? {
231
+ data: {
232
+ msa: msa ?? '',
233
+ tree: tree ?? '',
234
+ gff,
235
+ // the row table is stored as this JSON string, which is what a
236
+ // shared URL and the size limit already cover
237
+ ...(rowData ? { treeMetadata: JSON.stringify(rowData) } : {}),
238
+ },
239
+ }
85
240
  : {}),
86
241
  ...(msaFilehandle ? { msaFilehandle } : {}),
87
242
  ...(treeFilehandle ? { treeFilehandle } : {}),
@@ -93,12 +248,19 @@ export default function MSAViewer({
93
248
  ...(allowedGappyness !== undefined ? { allowedGappyness } : {}),
94
249
  ...(highlightColumns ? { highlightColumns } : {}),
95
250
  ...(highlights ? { highlights } : {}),
251
+ ...(clades ? { clades } : {}),
96
252
  ...(relativeTo ? { relativeTo } : {}),
97
253
  ...(columnTracks ? { columnTracks } : {}),
98
254
  ...(residueMappings ? { residueMappings } : {}),
255
+ ...(encodings ? { encodings } : {}),
256
+ ...(rowPanels ? { rowPanels } : {}),
99
257
  ...(drawTree !== undefined ? { drawTree } : {}),
100
258
  ...(treeAreaWidth ? { treeAreaWidth } : {}),
101
259
  ...(autoTreeAreaWidth ? { autoTreeAreaWidth } : {}),
260
+ ...(showBranchLen !== undefined ? { showBranchLen } : {}),
261
+ ...(residueEncoding !== undefined
262
+ ? { bgColor: residueEncoding === 'fill' }
263
+ : {}),
102
264
  }),
103
265
  )
104
266
 
@@ -113,14 +275,8 @@ export default function MSAViewer({
113
275
  }
114
276
  }, [model, width])
115
277
 
116
- // The props below stay live after mount: change one and it drives the model,
117
- // so a host can put a control on the viewer -- an expand button, a diff
118
- // toggle, a color-scheme picker -- without remounting it and re-fetching its
119
- // alignment. Each effect depends only on its own prop, so a change the user
120
- // makes inside the viewer (picking a scheme from the menu, dragging a row
121
- // taller) survives the host's next render rather than being snapped back.
122
- // Data props are not among them: new msa/tree/gff is a different alignment,
123
- // which is a new model, which React spells `key`.
278
+ // Each effect depends only on its own prop, so a change made inside the
279
+ // viewer survives the host's next render.
124
280
  useEffect(() => {
125
281
  if (height !== undefined) {
126
282
  model.setHeight(height)
@@ -156,18 +312,34 @@ export default function MSAViewer({
156
312
  model.setTreeAreaWidth(treeAreaWidth)
157
313
  }
158
314
  }, [model, treeAreaWidth])
159
- // unguarded: dropping the prop is how a host turns the reference diff back off
315
+ useEffect(() => {
316
+ if (showBranchLen !== undefined) {
317
+ model.setShowBranchLen(showBranchLen)
318
+ }
319
+ }, [model, showBranchLen])
320
+ useEffect(() => {
321
+ if (residueEncoding !== undefined) {
322
+ model.setBgColor(residueEncoding === 'fill')
323
+ }
324
+ }, [model, residueEncoding])
325
+ useEffect(() => {
326
+ model.setHideHeader(!!hideHeader)
327
+ }, [model, hideHeader])
328
+ // unguarded, so removing the prop turns the reference diff off
160
329
  useEffect(() => {
161
330
  model.drawRelativeTo(relativeTo)
162
331
  }, [model, relativeTo])
163
332
 
164
- // the data layers, keyed by content: a host that computes one inline hands a
165
- // new array on every render, and replacing the model's copy each time would
166
- // redraw the overlay for nothing
333
+ // keyed by content, since a host computing a layer inline passes a new array
334
+ // on every render
167
335
  const highlightsKey = JSON.stringify(highlights ?? [])
168
336
  useEffect(() => {
169
337
  model.setHighlights(JSON.parse(highlightsKey))
170
338
  }, [model, highlightsKey])
339
+ const cladesKey = JSON.stringify(clades ?? [])
340
+ useEffect(() => {
341
+ model.setClades(JSON.parse(cladesKey))
342
+ }, [model, cladesKey])
171
343
  const columnTracksKey = JSON.stringify(columnTracks ?? [])
172
344
  useEffect(() => {
173
345
  model.setColumnTracks(JSON.parse(columnTracksKey))
@@ -176,15 +348,55 @@ export default function MSAViewer({
176
348
  useEffect(() => {
177
349
  model.setResidueMappings(JSON.parse(residueMappingsKey))
178
350
  }, [model, residueMappingsKey])
351
+ const rowDataKey = JSON.stringify(rowData ?? {})
352
+ useEffect(() => {
353
+ const table = JSON.parse(rowDataKey)
354
+ // an empty table leaves whatever the snapshot or a filehandle loaded
355
+ if (Object.keys(table).length) {
356
+ model.setRowData(table)
357
+ }
358
+ }, [model, rowDataKey])
359
+ const encodingsKey = JSON.stringify(encodings ?? [])
360
+ useEffect(() => {
361
+ model.setEncodings(JSON.parse(encodingsKey))
362
+ }, [model, encodingsKey])
363
+ const rowPanelsKey = JSON.stringify(rowPanels ?? [])
364
+ useEffect(() => {
365
+ model.setRowPanels(JSON.parse(rowPanelsKey))
366
+ }, [model, rowPanelsKey])
179
367
  const highlightColumnsKey = JSON.stringify(highlightColumns ?? null)
180
368
  useEffect(() => {
181
369
  model.setHighlightedColumns(JSON.parse(highlightColumnsKey) ?? undefined)
182
370
  }, [model, highlightColumnsKey])
183
371
 
184
- // the model owns a matchMedia listener and whatever fetches its filehandles
185
- // started, so an unmounted viewer that keeps its model keeps those too. The
186
- // destroy is deferred by a tick because React's StrictMode mounts, unmounts
187
- // and remounts with the same state: the remount's effect cancels it
372
+ const regionKey = JSON.stringify(region ?? null)
373
+ useEffect(() => {
374
+ const target = JSON.parse(regionKey) as Region | null
375
+ return target
376
+ ? when(
377
+ () => model.viewInitialized && model.numColumns > 0,
378
+ () => {
379
+ model.zoomToRegion(target)
380
+ },
381
+ )
382
+ : undefined
383
+ }, [model, regionKey])
384
+
385
+ useModelReaction(model, 'hoveredCell', onCellHover)
386
+ useModelReaction(model, 'clickedCell', onCellClick)
387
+ useModelReaction(model, 'viewport', onViewportChange)
388
+
389
+ const latestOnModel = useRef(onModel)
390
+ useEffect(() => {
391
+ latestOnModel.current = onModel
392
+ })
393
+ useEffect(() => {
394
+ latestOnModel.current?.(model)
395
+ }, [model])
396
+
397
+ // destroy releases the model's matchMedia listener and fetches. It waits a
398
+ // tick because StrictMode remounts with the same state, and the remount's
399
+ // effect cancels it.
188
400
  useEffect(() => {
189
401
  if (destroyTimer.current !== undefined) {
190
402
  clearTimeout(destroyTimer.current)
@@ -201,7 +413,7 @@ export default function MSAViewer({
201
413
  }, [model])
202
414
 
203
415
  return (
204
- <ThemeProvider theme={theme}>
416
+ <ThemeProvider theme={resolveTheme(theme)}>
205
417
  <div ref={ref}>
206
418
  <Loading model={model} />
207
419
  </div>
@@ -0,0 +1,47 @@
1
+ import React from 'react'
2
+
3
+ import { makeStyles } from '@jbrowse/core/util/tss-react'
4
+ import { createPortal } from 'react-dom'
5
+
6
+ const useStyles = makeStyles()(theme => ({
7
+ tooltip: {
8
+ position: 'fixed',
9
+ pointerEvents: 'none',
10
+ zIndex: 10000,
11
+ backgroundColor: theme.palette.grey[700],
12
+ color: theme.palette.common.white,
13
+ padding: '4px 8px',
14
+ borderRadius: 4,
15
+ fontSize: 12,
16
+ whiteSpace: 'nowrap',
17
+ maxWidth: 300,
18
+ overflow: 'hidden',
19
+ textOverflow: 'ellipsis',
20
+ },
21
+ }))
22
+
23
+ /**
24
+ * A tooltip beside the pointer, portaled to the body. A panel's scroll
25
+ * transform is a containing block, so a fixed tooltip left inside it would
26
+ * position against the scrolled content instead of the window.
27
+ */
28
+ export default function PortalTooltip({
29
+ clientX,
30
+ clientY,
31
+ children,
32
+ }: {
33
+ clientX: number
34
+ clientY: number
35
+ children: React.ReactNode
36
+ }) {
37
+ const { classes } = useStyles()
38
+ return createPortal(
39
+ <div
40
+ className={classes.tooltip}
41
+ style={{ left: clientX + 12, top: clientY + 12 }}
42
+ >
43
+ {children}
44
+ </div>,
45
+ document.body,
46
+ )
47
+ }