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,7 +5,7 @@ import { observer } from 'mobx-react'
5
5
  import DragHandle from './DragHandle.tsx'
6
6
 
7
7
  import type { MsaViewModel } from '../model.ts'
8
- import type { BasicTrack, TrackKind } from '../types.ts'
8
+ import type { BasicTrack } from '../types.ts'
9
9
 
10
10
  export const VerticalResizeHandle = observer(function ({
11
11
  model,
@@ -51,25 +51,6 @@ export const HorizontalResizeHandle = observer(function ({
51
51
  )
52
52
  })
53
53
 
54
- // The volatile behind each resizable kind of computed track. Both bar tracks
55
- // write one height so conservation and property conservation stay directly
56
- // comparable; the logo keeps its own, being taller by default. Text tracks are
57
- // absent: they are one alignment row tall and follow rowHeight, so the zoom
58
- // controls already size them.
59
- const setTrackHeight: Partial<
60
- Record<TrackKind, (model: MsaViewModel, height: number) => void>
61
- > = {
62
- bar: (model, height) => {
63
- model.setConservationTrackHeight(height)
64
- },
65
- logo: (model, height) => {
66
- model.setSequenceLogoTrackHeight(height)
67
- },
68
- arc: (model, height) => {
69
- model.setArcTrackHeight(height)
70
- },
71
- }
72
-
73
54
  export const TrackResizeHandle = observer(function ({
74
55
  model,
75
56
  track,
@@ -77,23 +58,27 @@ export const TrackResizeHandle = observer(function ({
77
58
  model: MsaViewModel
78
59
  track: BasicTrack
79
60
  }) {
80
- const { id, kind, height } = track.model
81
- const setHeight = setTrackHeight[kind]
82
- // a data track carries its own height, so its handle resizes that track --
83
- // dragging one used to resize every track of its kind instead
84
- const ownHeight = model.columnTracks.some(t => t.id === id)
61
+ const { id, heightKey, height } = track.model
62
+ // tracks sharing a key resize together, so only the last turned-on one of
63
+ // them carries the divider: a second between conservation and property
64
+ // conservation would move the pair this one already moves. The drag divides
65
+ // across the group, so its bottom edge follows the cursor rather than half
66
+ const siblings = model.turnedOnTracks.filter(
67
+ t => t.model.heightKey === heightKey,
68
+ )
69
+ const count = siblings.length
85
70
  const onDrag = useCallback(
86
71
  (delta: number, startHeight: number) => {
87
- const next = Math.max(10, startHeight + delta)
88
- if (ownHeight) {
89
- model.setColumnTrackHeight(id, next)
90
- } else {
91
- setTrackHeight[kind]?.(model, next)
72
+ if (heightKey) {
73
+ model.setTrackHeight(
74
+ heightKey,
75
+ Math.max(10, startHeight + delta / count),
76
+ )
92
77
  }
93
78
  },
94
- [model, id, kind, ownHeight],
79
+ [model, heightKey, count],
95
80
  )
96
- return setHeight ? (
81
+ return heightKey && siblings.at(-1)?.model.id === id ? (
97
82
  <DragHandle
98
83
  axis="y"
99
84
  variant="resizer"
@@ -7,17 +7,17 @@ import { getUngappedSequence } from 'msa-parsers'
7
7
  import Checkbox2 from './Checkbox2.tsx'
8
8
  import CopyButton from './CopyButton.tsx'
9
9
 
10
- const useStyles = makeStyles()({
10
+ const useStyles = makeStyles()(theme => ({
11
11
  textAreaFont: {
12
12
  fontFamily: 'Courier New',
13
13
  wordWrap: 'break-word',
14
14
  },
15
15
  dialogContent: {
16
- background: 'lightgrey',
16
+ background: theme.palette.action.selected,
17
17
  margin: 4,
18
18
  minWidth: '80em',
19
19
  },
20
- })
20
+ }))
21
21
 
22
22
  export default function SequenceTextArea({ str }: { str: [string, string][] }) {
23
23
  const { classes } = useStyles()
@@ -1,22 +1,36 @@
1
1
  import React, { lazy, useCallback, useRef, useState } from 'react'
2
2
 
3
+ import BaseTooltip from '@jbrowse/core/ui/BaseTooltip'
3
4
  import { makeStyles } from '@jbrowse/core/util/tss-react'
4
5
  import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'
5
6
  import { IconButton, Menu, MenuItem } from '@mui/material'
6
7
  import { observer } from 'mobx-react'
7
8
 
8
9
  import { useWheelScroll } from '../useWheelScroll.ts'
10
+ import TrackTooltipContent from './tracks/TrackTooltipContent.tsx'
11
+ import { useTrackHover } from './tracks/useTrackHover.ts'
9
12
 
10
13
  import type { MsaViewModel } from '../model.ts'
11
14
  import type { BasicTrack } from '../types.ts'
12
15
 
13
16
  const TrackInfoDialog = lazy(() => import('./dialogs/TrackInfoDialog.tsx'))
14
17
 
15
- const useStyles = makeStyles()({
18
+ const useStyles = makeStyles()(theme => ({
16
19
  button: {
17
20
  padding: 0,
18
21
  },
19
- })
22
+ // a hairline under every track but the last, across the labels as well as
23
+ // the alignment. The divider a track's resize handle draws covers only the
24
+ // alignment, and tracks sharing a height have no handle between them. Inset,
25
+ // so the line costs no layout height: totalTrackAreaHeight sums the model's
26
+ // heights, and the column indicator is drawn against that sum
27
+ row: {
28
+ display: 'flex',
29
+ '&:not(:last-child)': {
30
+ boxShadow: `inset 0 -1px 0 ${theme.palette.divider}`,
31
+ },
32
+ },
33
+ }))
20
34
 
21
35
  const TrackLabel = observer(function TrackLabel({
22
36
  model,
@@ -37,21 +51,37 @@ const TrackLabel = observer(function TrackLabel({
37
51
  const trackLabelHeight = Math.min(height, fontSize)
38
52
 
39
53
  return (
54
+ // a flex row, not inline text: zooming in grows fontSize against a tree
55
+ // area the leaf labels sized, and a name that wrapped took its menu button
56
+ // onto a second line, under the next track's button
40
57
  <div
41
58
  style={{
42
59
  width,
43
60
  height,
44
61
  flexShrink: 0,
45
- textAlign: 'right',
62
+ display: 'flex',
63
+ alignItems: 'flex-start',
64
+ justifyContent: 'flex-end',
65
+ gap: 2,
46
66
  fontSize: trackLabelHeight,
47
67
  }}
48
68
  >
49
- {drawLabels ? name : ''}{' '}
69
+ <span
70
+ title={name}
71
+ style={{
72
+ overflow: 'hidden',
73
+ textOverflow: 'ellipsis',
74
+ whiteSpace: 'nowrap',
75
+ }}
76
+ >
77
+ {drawLabels ? name : ''}
78
+ </span>
50
79
  <IconButton
51
80
  className={classes.button}
52
81
  style={{
53
82
  width: trackLabelHeight,
54
83
  height: trackLabelHeight,
84
+ flexShrink: 0,
55
85
  }}
56
86
  onClick={event => {
57
87
  setAnchorEl(event.currentTarget)
@@ -102,7 +132,7 @@ const Track = observer(function ({
102
132
  model: MsaViewModel
103
133
  track: BasicTrack
104
134
  }) {
105
- const { resizeHandleWidth, colWidth, scrollX, numColumns } = model
135
+ const { resizeHandleWidth, rowPanelsWidth, showColumnStats } = model
106
136
  const {
107
137
  model: { height },
108
138
  } = track
@@ -114,31 +144,38 @@ const Track = observer(function ({
114
144
  [model],
115
145
  )
116
146
  useWheelScroll({ ref, onScrollX })
147
+ const { classes } = useStyles()
148
+ const { hover, onMouseMove, onMouseLeave } = useTrackHover({
149
+ model,
150
+ tooltip: showColumnStats,
151
+ })
117
152
 
118
153
  return (
119
- <div style={{ display: 'flex', height }}>
154
+ <div className={classes.row} style={{ height }}>
120
155
  <TrackLabel model={model} track={track} />
121
- <div style={{ width: resizeHandleWidth, flexShrink: 0 }} />
156
+ {/* the row panels are row space, so a track leaves their column empty
157
+ and starts where the alignment canvas does */}
158
+ <div
159
+ style={{ width: rowPanelsWidth + resizeHandleWidth, flexShrink: 0 }}
160
+ />
122
161
  <div
123
162
  ref={ref}
163
+ data-testid={`track_${track.model.id}`}
124
164
  onMouseMove={event => {
125
- if (!ref.current) {
126
- return
127
- }
128
- const { left } = ref.current.getBoundingClientRect()
129
- const mouseX = event.clientX - left - scrollX
130
- const col = Math.floor(mouseX / colWidth)
131
- if (col >= 0 && col < numColumns) {
132
- model.setMousePos(col, undefined)
133
- } else {
134
- model.setMousePos(undefined, undefined)
165
+ if (ref.current) {
166
+ onMouseMove(event, ref.current)
135
167
  }
136
168
  }}
137
169
  onMouseLeave={() => {
138
- model.setMousePos(undefined, undefined)
170
+ onMouseLeave()
139
171
  }}
140
172
  >
141
173
  <track.ReactComponent model={model} track={track} />
174
+ {hover ? (
175
+ <BaseTooltip clientPoint={{ x: hover.x, y: hover.y + 15 }}>
176
+ <TrackTooltipContent model={model} track={track} col={hover.col} />
177
+ </BaseTooltip>
178
+ ) : null}
142
179
  </div>
143
180
  </div>
144
181
  )
@@ -49,15 +49,20 @@ export default function ExportSVGDialog({
49
49
  totalWidth,
50
50
  totalHeight,
51
51
  treeAreaWidth,
52
+ rowPanelsWidth,
52
53
  turnedOnTracks,
53
54
  totalTrackAreaHeight,
54
55
  numColumns,
55
56
  leaves,
56
57
  showMsaLetters,
58
+ treeOverviewHeight,
57
59
  } = model
58
60
  const hasTracks = turnedOnTracks.length > 0
59
- const entireWidth = totalWidth + treeAreaWidth
60
- const entireHeight = totalHeight + (includeTracks ? totalTrackAreaHeight : 0)
61
+ const entireWidth = totalWidth + treeAreaWidth + rowPanelsWidth
62
+ const entireHeight =
63
+ totalHeight +
64
+ (includeTracks ? totalTrackAreaHeight : 0) +
65
+ treeOverviewHeight
61
66
  // the background is one raster image whatever its size, so what the figure
62
67
  // actually costs is its residue letters: each is an svg element of its own.
63
68
  // Past roughly this many the export runs to tens of seconds and can exhaust
@@ -13,7 +13,7 @@ const useStyles = makeStyles()(theme => ({
13
13
  textArea: {
14
14
  padding: theme.spacing(2),
15
15
  overflow: 'auto',
16
- background: '#ddd',
16
+ background: theme.palette.action.selected,
17
17
  wordBreak: 'break-word',
18
18
  },
19
19
  }))
@@ -23,18 +23,13 @@ const GappynessSlider = observer(function GappynessSlider({
23
23
  Hide columns w/ &ge;{allowedGappyness}% gaps
24
24
  </Typography>
25
25
  <Slider
26
- // Named, because the class alone is not enough to find it from outside.
27
- // An MSA is normally embedded beside other MUI Sliders (in JBrowse, the
28
- // linear view's own zoom control), so a `.MuiSlider-thumb` selector in a
29
- // test or a screenshot spec picks whichever comes first in the document
30
- // and drives that instead — which reads as this slider ignoring its
31
- // keyboard. Target `[data-testid="gappyness_slider"] input` to reach the
32
- // hidden range input MUI renders in the thumb, which is the node that
33
- // takes focus and receives arrow/PageUp/PageDown.
26
+ // In JBrowse other MUI Sliders share the page, so `.MuiSlider-thumb`
27
+ // can match the wrong one. Tests and screenshot specs target
28
+ // `[data-testid="gappyness_slider"] input`, the hidden range input that
29
+ // takes focus and arrow/PageUp/PageDown.
34
30
  //
35
- // Deliberately NOT `slotProps={{ input: ... }}`: passing slotProps here
36
- // widens the Slider's generic so `onChange`'s `val` infers as
37
- // `number | number[]` and no longer assigns to setAllowedGappyness.
31
+ // Not `slotProps={{ input: ... }}`: that widens `onChange`'s `val` to
32
+ // `number | number[]`, which setAllowedGappyness rejects.
38
33
  data-testid="gappyness_slider"
39
34
  style={{ width: 100 }}
40
35
  min={1}
@@ -27,6 +27,12 @@ const Header = observer(function ({ model }: { model: MsaViewModel }) {
27
27
  useEffect(() => {
28
28
  model.setHeaderHeight(height ?? 0)
29
29
  }, [model, height])
30
+ useEffect(
31
+ () => () => {
32
+ model.setHeaderHeight(0)
33
+ },
34
+ [model],
35
+ )
30
36
  return (
31
37
  <div ref={ref} style={{ display: 'flex' }}>
32
38
  <FileMenu model={model} />
@@ -20,10 +20,9 @@ const useStyles = makeStyles()(theme => ({
20
20
  },
21
21
  }))
22
22
 
23
- // An optional layer that failed -- a 404 on the annotations, a row-metadata
24
- // file that is not JSON -- used to go to `error`, which takes dataInitialized
25
- // down with it and replaces a perfectly good alignment with an error screen.
26
- // It says so here instead, and the reader dismisses it.
23
+ // A failed optional layer, such as a 404 on the annotations or row metadata
24
+ // that is not JSON, shows here as a dismissible warning. Setting `error` would
25
+ // clear dataInitialized and hide the loaded alignment.
27
26
  const LoadWarnings = observer(function ({ model }: { model: MsaViewModel }) {
28
27
  const { warnings } = model
29
28
  const { classes } = useStyles()
@@ -0,0 +1,52 @@
1
+ import React from 'react'
2
+
3
+ import SvgIcon from '@mui/material/SvgIcon'
4
+
5
+ import type { SvgIconProps } from '@mui/material/SvgIcon'
6
+
7
+ // Mouse glyphs with outward arrows on the axes a wheel zoom scales.
8
+ // @mui/icons-material ships only the plain `Mouse`, and three variants that
9
+ // differ only in their arrows have to share one body to stay apart at 24px.
10
+ // The body's inner subpath runs the opposite sweep, which punches the hole the
11
+ // wheel bar sits in.
12
+ const BODY =
13
+ 'M12 5a4 4 0 0 0-4 4v6a4 4 0 0 0 8 0V9a4 4 0 0 0-4-4zm0 2a2 2 0 0 1 2 2v6a2 2 0 0 1-4 0V9a2 2 0 0 1 2-2z'
14
+ const WHEEL = 'M11.2 8.6h1.6v3.2h-1.6z'
15
+ const LEFT = 'M.4 12 5 15V9z'
16
+ const RIGHT = 'M23.6 12 19 9v6z'
17
+ const UP = 'M12 .4 15 4H9z'
18
+ const DOWN = 'M12 23.6 9 20h6z'
19
+
20
+ function MouseZoom({
21
+ arrows,
22
+ ...rest
23
+ }: SvgIconProps & { arrows: readonly string[] }) {
24
+ return (
25
+ <SvgIcon {...rest}>
26
+ <path d={BODY} />
27
+ <path d={WHEEL} />
28
+ {arrows.map(d => (
29
+ <path key={d} d={d} />
30
+ ))}
31
+ </SvgIcon>
32
+ )
33
+ }
34
+
35
+ // the bare body, for a wheel that scrolls. @mui/icons-material's own `Mouse` is
36
+ // a solid silhouette, which would jump to a different drawing when the menu
37
+ // changes the mode.
38
+ export function MouseScroll(props: SvgIconProps) {
39
+ return <MouseZoom {...props} arrows={[]} />
40
+ }
41
+
42
+ export function MouseZoomBoth(props: SvgIconProps) {
43
+ return <MouseZoom {...props} arrows={[LEFT, RIGHT, UP, DOWN]} />
44
+ }
45
+
46
+ export function MouseZoomHorizontal(props: SvgIconProps) {
47
+ return <MouseZoom {...props} arrows={[LEFT, RIGHT]} />
48
+ }
49
+
50
+ export function MouseZoomVertical(props: SvgIconProps) {
51
+ return <MouseZoom {...props} arrows={[UP, DOWN]} />
52
+ }
@@ -32,9 +32,7 @@ const TreeSettingsMenu = observer(function ({
32
32
  type: 'subMenu' as const,
33
33
  subMenu: [
34
34
  {
35
- // the cap shows in the label rather than arriving as an
36
- // error after the click, since the click is what would
37
- // freeze the tab
35
+ // the label shows the cap before a click would freeze the tab
38
36
  label: tooManyRows
39
37
  ? `Calculate neighbor joining tree (over ${maxNeighborJoiningRows} rows, use FastTree)`
40
38
  : 'Calculate neighbor joining tree (BLOSUM62)',
@@ -26,10 +26,8 @@ function size(bytes: number) {
26
26
  : `${(bytes / 1_000_000).toFixed(1)} MB`
27
27
  }
28
28
 
29
- // The viewer holds documents its own snapshot does not carry, and until this
30
- // existed it said nothing about it: the view worked, the URL quietly lost the
31
- // data, and whoever copied the link found out from the person who opened it.
32
- // See `unshareableData` on the model.
29
+ // Lists loaded documents the snapshot, and so a shared URL, omits. See
30
+ // `unshareableData` on the model.
33
31
  const UnshareableDataWarning = observer(function ({
34
32
  model,
35
33
  }: {
@@ -1,18 +1,67 @@
1
1
  import React from 'react'
2
2
 
3
- import Mouse from '@mui/icons-material/Mouse'
3
+ import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton'
4
4
  import ZoomIn from '@mui/icons-material/ZoomIn'
5
5
  import ZoomOut from '@mui/icons-material/ZoomOut'
6
- import { IconButton, ToggleButton, Tooltip } from '@mui/material'
6
+ import { IconButton, Tooltip } from '@mui/material'
7
7
  import { observer } from 'mobx-react'
8
8
 
9
+ import {
10
+ MouseScroll,
11
+ MouseZoomBoth,
12
+ MouseZoomHorizontal,
13
+ MouseZoomVertical,
14
+ } from './ScrollZoomIcons.tsx'
15
+
16
+ import type { ScrollZoomAxis } from '../../constants.ts'
9
17
  import type { MsaViewModel } from '../../model.ts'
10
18
 
19
+ interface WheelMode {
20
+ label: string
21
+ // undefined is the wheel scrolling the alignment, which is the default
22
+ axis?: ScrollZoomAxis
23
+ Icon: typeof MouseScroll
24
+ helpText?: string
25
+ }
26
+
27
+ const scrollMode: WheelMode = {
28
+ label: 'Scrolls the alignment',
29
+ Icon: MouseScroll,
30
+ helpText: 'Ctrl+wheel zooms both axes at the cursor.',
31
+ }
32
+
33
+ const wheelModes: WheelMode[] = [
34
+ scrollMode,
35
+ {
36
+ label: 'Zooms both axes',
37
+ axis: 'both',
38
+ Icon: MouseZoomBoth,
39
+ helpText: 'Hold shift to pan while the wheel zooms.',
40
+ },
41
+ {
42
+ label: 'Zooms columns only',
43
+ axis: 'horizontal',
44
+ Icon: MouseZoomHorizontal,
45
+ helpText:
46
+ 'Holds the row height, so the labels and letters stay at their size while the alignment compresses.',
47
+ },
48
+ {
49
+ label: 'Zooms rows only',
50
+ axis: 'vertical',
51
+ Icon: MouseZoomVertical,
52
+ helpText:
53
+ 'Holds the column width, so the alignment stays as wide while the rows grow or shrink.',
54
+ },
55
+ ]
56
+
11
57
  const ZoomControls = observer(function ZoomControls({
12
58
  model,
13
59
  }: {
14
60
  model: MsaViewModel
15
61
  }) {
62
+ const { scrollZoom, scrollZoomAxis } = model
63
+ const activeAxis = scrollZoom ? scrollZoomAxis : undefined
64
+ const current = wheelModes.find(m => m.axis === activeAxis) ?? scrollMode
16
65
  return (
17
66
  <>
18
67
  <Tooltip title="Zoom in">
@@ -35,19 +84,30 @@ const ZoomControls = observer(function ZoomControls({
35
84
  <ZoomOut />
36
85
  </IconButton>
37
86
  </Tooltip>
38
- <ToggleButton
39
- value="scrollZoom"
40
- selected={model.scrollZoom}
41
- aria-label="Toggle scroll-to-zoom"
42
- title="Toggle scroll-to-zoom (zoom on plain mouse wheel; ctrl+wheel always zooms)"
43
- size="small"
44
- sx={{ border: 'none' }}
45
- onChange={() => {
46
- model.setScrollZoom(!model.scrollZoom)
47
- }}
87
+ <CascadingMenuButton
88
+ data-testid="scroll_zoom_menu"
89
+ tooltip={`Mouse wheel: ${current.label.toLowerCase()} (ctrl+wheel always zooms)`}
90
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
91
+ transformOrigin={{ vertical: 'top', horizontal: 'left' }}
92
+ menuItems={[
93
+ { type: 'subHeader', label: 'Mouse wheel' },
94
+ ...wheelModes.map(({ label, axis, Icon, helpText }) => ({
95
+ label,
96
+ icon: Icon,
97
+ helpText,
98
+ type: 'radio' as const,
99
+ checked: current.label === label,
100
+ onClick: () => {
101
+ model.setScrollZoom(axis !== undefined)
102
+ if (axis !== undefined) {
103
+ model.setScrollZoomAxis(axis)
104
+ }
105
+ },
106
+ })),
107
+ ]}
48
108
  >
49
- <Mouse />
50
- </ToggleButton>
109
+ <current.Icon />
110
+ </CascadingMenuButton>
51
111
  </>
52
112
  )
53
113
  })
@@ -32,12 +32,9 @@ export function getAnnotationsMenu({ model }: { model: MsaViewModel }) {
32
32
  ])
33
33
  },
34
34
  },
35
- // The viewer used to submit the whole alignment to the EBI iprscan5 queue
36
- // and poll it for about fifteen minutes, capped at 140 rows and under a
37
- // hardcoded email address. `react-msaview-cli interpro` answers the same
38
- // question from InterPro's precomputed matches in seconds, and
39
- // `interproscan` covers rows InterPro has not seen. Neither belongs in a
40
- // browser tab, so this points at the page that walks through them.
35
+ // `react-msaview-cli interpro` reads InterPro's precomputed matches in
36
+ // seconds, and `interproscan` covers rows InterPro has not seen; this links
37
+ // the tutorial for both
41
38
  {
42
39
  label: 'How to get a domain file...',
43
40
  icon: MenuBook,
@@ -12,9 +12,7 @@ function toggle(label: string, checked: boolean, set: (arg: boolean) => void) {
12
12
  }
13
13
  }
14
14
 
15
- // Every track the alignment can show, with its current state. Without this the
16
- // "Close" item on a track's own dropdown is a one-way door -- there is nowhere
17
- // else in the UI to turn a track back on.
15
+ // the only place in the UI to turn a closed track back on
18
16
  function tracksSubMenu(model: MsaViewModel): MenuItem[] {
19
17
  const shown = new Set(model.turnedOnTracks.map(t => t.model.id))
20
18
  return model.tracks.map(({ model: { id, name } }) =>
@@ -33,14 +31,13 @@ export function msaSettingsMenuItems(model: MsaViewModel): MenuItem[] {
33
31
  subMenu: tracksSubMenu(model),
34
32
  },
35
33
  { type: 'divider' as const },
36
- toggle('Show column statistics on hover', showColumnStats, arg => {
34
+ toggle('Show track values on hover', showColumnStats, arg => {
37
35
  model.setShowColumnStats(arg)
38
36
  }),
39
37
  toggle('Draw letters', drawMsaLetters, arg => {
40
38
  model.setDrawMsaLetters(arg)
41
39
  }),
42
- // the checkbox reads as the inverse of the property it sets: bgColor draws
43
- // the tile, and turning it off is what leaves the letter itself colored
40
+ // inverted: bgColor colors the tile, and off colors the letter
44
41
  toggle('Color letters instead of background of tiles', !bgColor, arg => {
45
42
  model.setBgColor(!arg)
46
43
  }),
@@ -56,7 +53,9 @@ export function treeSettingsMenuItems(model: MsaViewModel): MenuItem[] {
56
53
  showBranchLen,
57
54
  labelsAlignRight,
58
55
  drawNodeBubbles,
56
+ drawNodeLabels,
59
57
  drawLabels,
58
+ showTreeOverview,
60
59
  } = model
61
60
  return [
62
61
  toggle('Show branch length', showBranchLen, arg => {
@@ -68,6 +67,12 @@ export function treeSettingsMenuItems(model: MsaViewModel): MenuItem[] {
68
67
  toggle('Draw bubbles on tree branches', drawNodeBubbles, arg => {
69
68
  model.setDrawNodeBubbles(arg)
70
69
  }),
70
+ toggle('Draw internal node labels', drawNodeLabels, arg => {
71
+ model.setDrawNodeLabels(arg)
72
+ }),
73
+ toggle('Show tree overview', showTreeOverview, arg => {
74
+ model.setShowTreeOverview(arg)
75
+ }),
71
76
  toggle('Tree labels align right', labelsAlignRight, arg => {
72
77
  model.setLabelsAlignRight(arg)
73
78
  }),
@@ -11,8 +11,8 @@ import type { MsaViewModel } from '../../model.ts'
11
11
  const BASE = 'https://jbrowse.org/genomes/multiple_sequence_alignments'
12
12
  const TREES = 'https://jbrowse.org/genomes/newick_trees'
13
13
 
14
- // each example either points at remote files (msa/tree/gff urls) or carries its
15
- // data inline, which is what the two bundled small ones do
14
+ // each example points at remote files (msa/tree/gff urls) or carries its data
15
+ // inline, as the two small bundled ones do
16
16
  interface Example {
17
17
  label: string
18
18
  msa?: string
@@ -16,8 +16,7 @@ export function load(
16
16
  }
17
17
  if (gffFile) {
18
18
  model.setGFFFilehandle(gffFile)
19
- // asked for by hand, so drawn -- even if the reader hid the overlay
20
- // earlier in this view
19
+ // a file opened by hand shows even if the overlay was hidden earlier
21
20
  model.setShowDomains(true)
22
21
  }
23
22
  }
@@ -15,8 +15,7 @@ const Minimap = observer(function ({ model }: { model: MsaViewModel }) {
15
15
  const theme = useTheme()
16
16
  const { unit, s, w, polygonHeight, polygonPoints } = getMinimapLayout(model)
17
17
 
18
- // the whole alignment, sampled down to the bar: it says where the conserved
19
- // blocks and the gappy stretches are, which is what a scroll aims at
18
+ // the whole alignment, sampled down to the bar
20
19
  const barWidth = Math.round(msaCanvasWidth * highResScaleFactor)
21
20
  const barHeight = Math.round(MINIMAP_BAR_HEIGHT * highResScaleFactor)
22
21
  const ref = useCanvasAutorun({