react-msaview 9.1.0 → 9.2.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 (278) hide show
  1. package/bundle/index.js +111 -115
  2. package/bundle/index.js.map +4 -4
  3. package/dist/blosum62.d.ts +3 -0
  4. package/dist/blosum62.js +43 -0
  5. package/dist/blosum62.js.map +1 -0
  6. package/dist/colorSchemes.js +1 -0
  7. package/dist/colorSchemes.js.map +1 -1
  8. package/dist/components/Loading.d.ts +3 -2
  9. package/dist/components/Loading.js +2 -2
  10. package/dist/components/Loading.js.map +1 -1
  11. package/dist/components/MSAView.d.ts +3 -2
  12. package/dist/components/MSAView.js +7 -4
  13. package/dist/components/MSAView.js.map +1 -1
  14. package/dist/components/MSAViewer.d.ts +64 -12
  15. package/dist/components/MetadataTable.d.ts +4 -1
  16. package/dist/components/MetadataTable.js +55 -16
  17. package/dist/components/MetadataTable.js.map +1 -1
  18. package/dist/components/Track.js +6 -0
  19. package/dist/components/Track.js.map +1 -1
  20. package/dist/components/controls.js +2 -1
  21. package/dist/components/controls.js.map +1 -1
  22. package/dist/components/dialogs/AnnotationFileDialog.js +7 -2
  23. package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -1
  24. package/dist/components/dialogs/FeatureDialog.js +2 -2
  25. package/dist/components/dialogs/FeatureDialog.js.map +1 -1
  26. package/dist/components/{header/MSASettingsMenu.d.ts → dialogs/GapThresholdDialog.d.ts} +3 -2
  27. package/dist/components/dialogs/GapThresholdDialog.js +15 -0
  28. package/dist/components/dialogs/GapThresholdDialog.js.map +1 -0
  29. package/dist/components/{header → dialogs}/GappynessSlider.d.ts +1 -1
  30. package/dist/components/{header → dialogs}/GappynessSlider.js +3 -10
  31. package/dist/components/dialogs/GappynessSlider.js.map +1 -0
  32. package/dist/components/{header → dialogs}/GoToBox.d.ts +4 -2
  33. package/dist/components/dialogs/GoToBox.js +48 -0
  34. package/dist/components/dialogs/GoToBox.js.map +1 -0
  35. package/dist/components/dialogs/GoToDialog.d.ts +6 -0
  36. package/dist/components/dialogs/GoToDialog.js +10 -0
  37. package/dist/components/dialogs/GoToDialog.js.map +1 -0
  38. package/dist/components/dialogs/MetadataDialog.js +11 -3
  39. package/dist/components/dialogs/MetadataDialog.js.map +1 -1
  40. package/dist/components/dialogs/goTo.d.ts +5 -0
  41. package/dist/components/{header → dialogs}/goTo.js +1 -1
  42. package/dist/components/dialogs/goTo.js.map +1 -0
  43. package/dist/components/dialogs/metadataLabels.d.ts +4 -0
  44. package/dist/components/dialogs/metadataLabels.js +52 -0
  45. package/dist/components/dialogs/metadataLabels.js.map +1 -0
  46. package/dist/components/dialogs/metadataRecord.d.ts +1 -0
  47. package/dist/components/dialogs/metadataRecord.js +38 -0
  48. package/dist/components/dialogs/metadataRecord.js.map +1 -0
  49. package/dist/components/header/Header.d.ts +6 -2
  50. package/dist/components/header/Header.js +30 -32
  51. package/dist/components/header/Header.js.map +1 -1
  52. package/dist/components/header/{TreeSettingsMenu.d.ts → HiddenColumnsChip.d.ts} +2 -2
  53. package/dist/components/header/HiddenColumnsChip.js +15 -0
  54. package/dist/components/header/HiddenColumnsChip.js.map +1 -0
  55. package/dist/components/header/MenuBar.d.ts +6 -0
  56. package/dist/components/header/MenuBar.js +33 -0
  57. package/dist/components/header/MenuBar.js.map +1 -0
  58. package/dist/components/header/ScrollZoomCheckbox.d.ts +7 -0
  59. package/dist/components/header/ScrollZoomCheckbox.js +19 -0
  60. package/dist/components/header/ScrollZoomCheckbox.js.map +1 -0
  61. package/dist/components/header/ZoomControls.js +10 -30
  62. package/dist/components/header/ZoomControls.js.map +1 -1
  63. package/dist/components/header/colorSchemeMenuItems.d.ts +4 -0
  64. package/dist/components/header/colorSchemeMenuItems.js +106 -0
  65. package/dist/components/header/colorSchemeMenuItems.js.map +1 -0
  66. package/dist/components/header/getAnnotationsMenu.d.ts +3 -30
  67. package/dist/components/header/getAnnotationsMenu.js +48 -54
  68. package/dist/components/header/getAnnotationsMenu.js.map +1 -1
  69. package/dist/components/header/menuBarItems.d.ts +11 -0
  70. package/dist/components/header/menuBarItems.js +277 -0
  71. package/dist/components/header/menuBarItems.js.map +1 -0
  72. package/dist/components/import/ImportForm.js +12 -6
  73. package/dist/components/import/ImportForm.js.map +1 -1
  74. package/dist/components/minimap/Minimap.js +7 -3
  75. package/dist/components/minimap/Minimap.js.map +1 -1
  76. package/dist/components/minimap/MinimapSVG.js +2 -1
  77. package/dist/components/minimap/MinimapSVG.js.map +1 -1
  78. package/dist/components/minimap/minimapLayout.d.ts +12 -0
  79. package/dist/components/minimap/minimapLayout.js +20 -0
  80. package/dist/components/minimap/minimapLayout.js.map +1 -1
  81. package/dist/components/msa/MSACanvas.js +26 -9
  82. package/dist/components/msa/MSACanvas.js.map +1 -1
  83. package/dist/components/msa/MSACanvasBlock.js +15 -11
  84. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  85. package/dist/components/msa/MSAMouseoverCanvas.js +2 -2
  86. package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
  87. package/dist/components/msa/WheelZoomHint.d.ts +7 -0
  88. package/dist/components/msa/WheelZoomHint.js +38 -0
  89. package/dist/components/msa/WheelZoomHint.js.map +1 -0
  90. package/dist/components/msa/msaKeys.js +3 -0
  91. package/dist/components/msa/msaKeys.js.map +1 -1
  92. package/dist/components/msa/msaRaster.d.ts +4 -5
  93. package/dist/components/msa/msaRaster.js +5 -5
  94. package/dist/components/msa/msaRaster.js.map +1 -1
  95. package/dist/components/msa/renderBoxFeatureCanvasBlock.js +22 -2
  96. package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
  97. package/dist/components/msa/renderHighlights.d.ts +0 -3
  98. package/dist/components/msa/renderHighlights.js +3 -3
  99. package/dist/components/msa/renderHighlights.js.map +1 -1
  100. package/dist/components/msa/renderMSABlock.d.ts +6 -1
  101. package/dist/components/msa/renderMSABlock.js +44 -17
  102. package/dist/components/msa/renderMSABlock.js.map +1 -1
  103. package/dist/components/msa/tileColor.js +7 -0
  104. package/dist/components/msa/tileColor.js.map +1 -1
  105. package/dist/components/rowpanels/RowPanelBlock.js +2 -2
  106. package/dist/components/rowpanels/RowPanelBlock.js.map +1 -1
  107. package/dist/components/settings/MenuItemsForm.d.ts +10 -0
  108. package/dist/components/settings/MenuItemsForm.js +80 -0
  109. package/dist/components/settings/MenuItemsForm.js.map +1 -0
  110. package/dist/components/settings/SettingsPanel.d.ts +12 -0
  111. package/dist/components/settings/SettingsPanel.js +92 -0
  112. package/dist/components/settings/SettingsPanel.js.map +1 -0
  113. package/dist/components/{header/FileMenu.d.ts → settings/TrackList.d.ts} +2 -2
  114. package/dist/components/settings/TrackList.js +58 -0
  115. package/dist/components/settings/TrackList.js.map +1 -0
  116. package/dist/components/tracks/TrackBlocks.js +2 -2
  117. package/dist/components/tracks/TrackBlocks.js.map +1 -1
  118. package/dist/components/tracks/TrackTooltipContent.js +2 -1
  119. package/dist/components/tracks/TrackTooltipContent.js.map +1 -1
  120. package/dist/components/tracks/drawTracks.js +13 -6
  121. package/dist/components/tracks/drawTracks.js.map +1 -1
  122. package/dist/components/tree/TreeCanvasBlock.js +2 -2
  123. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  124. package/dist/components/tree/TreeMenu.js +4 -6
  125. package/dist/components/tree/TreeMenu.js.map +1 -1
  126. package/dist/components/tree/TreeOverview.js +2 -2
  127. package/dist/components/tree/TreeOverview.js.map +1 -1
  128. package/dist/constants.d.ts +4 -1
  129. package/dist/constants.js +7 -1
  130. package/dist/constants.js.map +1 -1
  131. package/dist/darkReader.d.ts +7 -0
  132. package/dist/darkReader.js +42 -0
  133. package/dist/darkReader.js.map +1 -0
  134. package/dist/element.js +22 -47
  135. package/dist/element.js.map +1 -1
  136. package/dist/featurePanels.d.ts +37 -0
  137. package/dist/featurePanels.js +187 -0
  138. package/dist/featurePanels.js.map +1 -0
  139. package/dist/filehandleLoader.d.ts +41 -0
  140. package/dist/filehandleLoader.js +99 -0
  141. package/dist/filehandleLoader.js.map +1 -0
  142. package/dist/index.d.ts +3 -0
  143. package/dist/index.js +2 -0
  144. package/dist/index.js.map +1 -1
  145. package/dist/model/treeModel.d.ts +9 -0
  146. package/dist/model/treeModel.js +12 -1
  147. package/dist/model/treeModel.js.map +1 -1
  148. package/dist/model.d.ts +148 -32
  149. package/dist/model.js +291 -569
  150. package/dist/model.js.map +1 -1
  151. package/dist/neighborJoining.js +1 -40
  152. package/dist/neighborJoining.js.map +1 -1
  153. package/dist/renderToSvg.js +1 -13
  154. package/dist/renderToSvg.js.map +1 -1
  155. package/dist/rerootTree.d.ts +1 -0
  156. package/dist/rerootTree.js +7 -6
  157. package/dist/rerootTree.js.map +1 -1
  158. package/dist/schemeResidueColor.d.ts +12 -0
  159. package/dist/schemeResidueColor.js +33 -0
  160. package/dist/schemeResidueColor.js.map +1 -0
  161. package/dist/similarityColors.d.ts +11 -0
  162. package/dist/similarityColors.js +90 -0
  163. package/dist/similarityColors.js.map +1 -0
  164. package/dist/svgTestUtil.d.ts +64 -12
  165. package/dist/treeDisplay.d.ts +37 -0
  166. package/dist/treeDisplay.js +209 -0
  167. package/dist/treeDisplay.js.map +1 -0
  168. package/dist/types.d.ts +7 -3
  169. package/dist/useWheelScroll.d.ts +3 -1
  170. package/dist/useWheelScroll.js +36 -17
  171. package/dist/useWheelScroll.js.map +1 -1
  172. package/dist/util.d.ts +2 -0
  173. package/dist/util.js +2 -0
  174. package/dist/util.js.map +1 -1
  175. package/dist/version.d.ts +1 -1
  176. package/dist/version.js +1 -1
  177. package/dist/wheelZoom.d.ts +8 -3
  178. package/dist/wheelZoom.js +44 -32
  179. package/dist/wheelZoom.js.map +1 -1
  180. package/dist/zoomSnapshot.d.ts +29 -0
  181. package/dist/zoomSnapshot.js +47 -0
  182. package/dist/zoomSnapshot.js.map +1 -0
  183. package/package.json +11 -11
  184. package/src/blosum62.ts +45 -0
  185. package/src/colorSchemes.ts +1 -0
  186. package/src/components/Loading.tsx +6 -2
  187. package/src/components/MSAView.tsx +22 -5
  188. package/src/components/MetadataTable.tsx +83 -26
  189. package/src/components/Track.tsx +13 -0
  190. package/src/components/controls.ts +2 -1
  191. package/src/components/dialogs/AnnotationFileDialog.tsx +12 -1
  192. package/src/components/dialogs/FeatureDialog.tsx +2 -5
  193. package/src/components/dialogs/GapThresholdDialog.tsx +36 -0
  194. package/src/components/{header → dialogs}/GappynessSlider.tsx +3 -10
  195. package/src/components/{header → dialogs}/GoToBox.tsx +36 -10
  196. package/src/components/dialogs/GoToDialog.tsx +30 -0
  197. package/src/components/dialogs/MetadataDialog.tsx +32 -4
  198. package/src/components/{header → dialogs}/goTo.ts +1 -1
  199. package/src/components/dialogs/metadataLabels.ts +54 -0
  200. package/src/components/dialogs/metadataRecord.tsx +61 -0
  201. package/src/components/header/Header.tsx +75 -42
  202. package/src/components/header/HiddenColumnsChip.tsx +39 -0
  203. package/src/components/header/MenuBar.tsx +64 -0
  204. package/src/components/header/ScrollZoomCheckbox.tsx +46 -0
  205. package/src/components/header/ZoomControls.tsx +20 -35
  206. package/src/components/header/colorSchemeMenuItems.ts +121 -0
  207. package/src/components/header/getAnnotationsMenu.ts +52 -56
  208. package/src/components/header/menuBarItems.ts +357 -0
  209. package/src/components/import/ImportForm.tsx +19 -7
  210. package/src/components/minimap/Minimap.tsx +11 -3
  211. package/src/components/minimap/MinimapSVG.tsx +17 -1
  212. package/src/components/minimap/minimapLayout.ts +27 -0
  213. package/src/components/msa/MSACanvas.tsx +22 -3
  214. package/src/components/msa/MSACanvasBlock.tsx +18 -11
  215. package/src/components/msa/MSAMouseoverCanvas.tsx +2 -2
  216. package/src/components/msa/WheelZoomHint.tsx +56 -0
  217. package/src/components/msa/msaKeys.ts +3 -0
  218. package/src/components/msa/msaRaster.ts +6 -8
  219. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +28 -5
  220. package/src/components/msa/renderHighlights.ts +3 -3
  221. package/src/components/msa/renderMSABlock.ts +51 -15
  222. package/src/components/msa/tileColor.ts +7 -0
  223. package/src/components/rowpanels/RowPanelBlock.tsx +2 -2
  224. package/src/components/settings/MenuItemsForm.tsx +225 -0
  225. package/src/components/settings/SettingsPanel.tsx +156 -0
  226. package/src/components/settings/TrackList.tsx +124 -0
  227. package/src/components/tracks/TrackBlocks.tsx +2 -2
  228. package/src/components/tracks/TrackTooltipContent.tsx +2 -1
  229. package/src/components/tracks/drawTracks.ts +15 -7
  230. package/src/components/tree/TreeCanvasBlock.tsx +2 -2
  231. package/src/components/tree/TreeMenu.tsx +4 -6
  232. package/src/components/tree/TreeOverview.tsx +2 -2
  233. package/src/constants.ts +9 -1
  234. package/src/darkReader.ts +57 -0
  235. package/src/element.ts +34 -57
  236. package/src/featurePanels.ts +269 -0
  237. package/src/filehandleLoader.ts +136 -0
  238. package/src/index.ts +4 -0
  239. package/src/model/treeModel.ts +13 -0
  240. package/src/model.ts +370 -748
  241. package/src/neighborJoining.ts +1 -42
  242. package/src/renderToSvg.tsx +1 -13
  243. package/src/rerootTree.ts +7 -6
  244. package/src/schemeResidueColor.ts +37 -0
  245. package/src/similarityColors.ts +112 -0
  246. package/src/treeDisplay.ts +278 -0
  247. package/src/types.ts +7 -4
  248. package/src/useWheelScroll.ts +43 -22
  249. package/src/util.ts +3 -0
  250. package/src/version.ts +1 -1
  251. package/src/wheelZoom.ts +55 -33
  252. package/src/zoomSnapshot.ts +80 -0
  253. package/dist/components/header/ColorSchemeMenu.d.ts +0 -12
  254. package/dist/components/header/ColorSchemeMenu.js +0 -37
  255. package/dist/components/header/ColorSchemeMenu.js.map +0 -1
  256. package/dist/components/header/FileMenu.js +0 -92
  257. package/dist/components/header/FileMenu.js.map +0 -1
  258. package/dist/components/header/GappynessSlider.js.map +0 -1
  259. package/dist/components/header/GoToBox.js +0 -31
  260. package/dist/components/header/GoToBox.js.map +0 -1
  261. package/dist/components/header/MSASettingsMenu.js +0 -11
  262. package/dist/components/header/MSASettingsMenu.js.map +0 -1
  263. package/dist/components/header/ScrollZoomIcons.d.ts +0 -6
  264. package/dist/components/header/ScrollZoomIcons.js +0 -35
  265. package/dist/components/header/ScrollZoomIcons.js.map +0 -1
  266. package/dist/components/header/TreeSettingsMenu.js +0 -11
  267. package/dist/components/header/TreeSettingsMenu.js.map +0 -1
  268. package/dist/components/header/goTo.d.ts +0 -20
  269. package/dist/components/header/goTo.js.map +0 -1
  270. package/dist/components/header/settingsMenuItems.d.ts +0 -5
  271. package/dist/components/header/settingsMenuItems.js +0 -171
  272. package/dist/components/header/settingsMenuItems.js.map +0 -1
  273. package/src/components/header/ColorSchemeMenu.tsx +0 -56
  274. package/src/components/header/FileMenu.tsx +0 -107
  275. package/src/components/header/MSASettingsMenu.tsx +0 -26
  276. package/src/components/header/ScrollZoomIcons.tsx +0 -52
  277. package/src/components/header/TreeSettingsMenu.tsx +0 -30
  278. package/src/components/header/settingsMenuItems.ts +0 -216
@@ -0,0 +1,357 @@
1
+ import { lazy } from 'react'
2
+
3
+ import { maxNeighborJoiningRows, treeOrders } from '../../constants.ts'
4
+ import { canGoTo } from '../dialogs/GoToBox.tsx'
5
+ import { scrollZoomLabel } from './ScrollZoomCheckbox.tsx'
6
+ import { colorSchemeMenuItems } from './colorSchemeMenuItems.ts'
7
+ import { openAnnotationFileItem } from './getAnnotationsMenu.ts'
8
+
9
+ import type { MsaViewModel } from '../../model.ts'
10
+ import type { TreeOrder } from '../../types.ts'
11
+ import type { ChoiceDisplay } from '../settings/MenuItemsForm.tsx'
12
+ import type { MenuItem } from '@jbrowse/core/ui/Menu'
13
+
14
+ const AboutDialog = lazy(() => import('../dialogs/AboutDialog.tsx'))
15
+ const ExportSVGDialog = lazy(() => import('../dialogs/ExportSVGDialog.tsx'))
16
+ const GapThresholdDialog = lazy(
17
+ () => import('../dialogs/GapThresholdDialog.tsx'),
18
+ )
19
+ const GoToDialog = lazy(() => import('../dialogs/GoToDialog.tsx'))
20
+ const HelpDialog = lazy(() => import('../dialogs/HelpDialog.tsx'))
21
+ const MetadataDialog = lazy(() => import('../dialogs/MetadataDialog.tsx'))
22
+
23
+ const tutorialsUrl = 'https://gmod.org/JBrowseMSA/tutorials/'
24
+
25
+ function radio(label: string, checked: boolean, onClick: () => void) {
26
+ return { label, type: 'radio' as const, checked, onClick }
27
+ }
28
+
29
+ function toggle(label: string, checked: boolean, set: (arg: boolean) => void) {
30
+ return {
31
+ label,
32
+ type: 'checkbox' as const,
33
+ checked,
34
+ onClick: () => {
35
+ set(!checked)
36
+ },
37
+ }
38
+ }
39
+
40
+ function action(label: string, onClick: () => void, disabled = false) {
41
+ return { label, onClick, disabled }
42
+ }
43
+
44
+ function subMenu(label: string, items: MenuItem[], display?: ChoiceDisplay) {
45
+ return { label, type: 'subMenu' as const, subMenu: items, display }
46
+ }
47
+
48
+ function when<T>(cond: boolean, items: T[]) {
49
+ return cond ? items : []
50
+ }
51
+
52
+ export function fileMenuItems(model: MsaViewModel): MenuItem[] {
53
+ return [
54
+ ...when(!!model.importForm, [
55
+ action('Open alignment…', () => {
56
+ model.reset()
57
+ }),
58
+ ]),
59
+ openAnnotationFileItem(model),
60
+ action('Export as SVG…', () => {
61
+ model.queueDialog(onClose => [ExportSVGDialog, { onClose, model }])
62
+ }),
63
+ ...when(!model.tree.noTree, [
64
+ action('Export tree as Newick', () => {
65
+ model.exportNewick()
66
+ }),
67
+ ]),
68
+ action('Alignment info…', () => {
69
+ model.queueDialog(onClose => [MetadataDialog, { model, onClose }])
70
+ }),
71
+ ]
72
+ }
73
+
74
+ function zoomSubMenu(model: MsaViewModel): MenuItem[] {
75
+ return [
76
+ action('Zoom in', () => {
77
+ model.zoomIn()
78
+ }),
79
+ action('Zoom out', () => {
80
+ model.zoomOut()
81
+ }),
82
+ action('Fit to window', () => {
83
+ model.fit()
84
+ }),
85
+ action('Fit width', () => {
86
+ model.fitHorizontally()
87
+ }),
88
+ action('Fit height', () => {
89
+ model.fitVertically()
90
+ }),
91
+ action('Reset zoom', () => {
92
+ model.resetZoom()
93
+ }),
94
+ toggle(scrollZoomLabel, model.scrollZoom, arg => {
95
+ model.setScrollZoom(arg)
96
+ }),
97
+ ]
98
+ }
99
+
100
+ const gapPresets = [100, 90, 50]
101
+
102
+ export function gapsSubMenu(model: MsaViewModel): MenuItem[] {
103
+ const { hideGaps, allowedGappyness } = model
104
+ const custom = hideGaps && !gapPresets.includes(allowedGappyness)
105
+ return [
106
+ subMenu(
107
+ 'Gappy columns',
108
+ [
109
+ radio('Show all columns', !hideGaps, () => {
110
+ model.setHideGaps(false)
111
+ }),
112
+ ...gapPresets.map(pct =>
113
+ radio(
114
+ pct === 100 ? 'Hide columns of only gaps' : `Hide ≥${pct}% gaps`,
115
+ hideGaps && allowedGappyness === pct,
116
+ () => {
117
+ model.setHideGaps(true)
118
+ model.setAllowedGappyness(pct)
119
+ },
120
+ ),
121
+ ),
122
+ {
123
+ ...radio(
124
+ custom ? `Hide ≥${allowedGappyness}% gaps…` : 'Custom threshold…',
125
+ custom,
126
+ () => {
127
+ model.setHideGaps(true)
128
+ model.queueDialog(onClose => [
129
+ GapThresholdDialog,
130
+ { model, onClose },
131
+ ])
132
+ },
133
+ ),
134
+ keepMenuOpen: false,
135
+ },
136
+ ],
137
+ 'select',
138
+ ),
139
+ ]
140
+ }
141
+
142
+ const treeOrderLabels: Record<TreeOrder, string> = {
143
+ branchLength: 'By branch length',
144
+ input: 'Input order',
145
+ ladderize: 'Ladderize, small clades first',
146
+ ladderizeReverse: 'Ladderize, large clades first',
147
+ }
148
+
149
+ function outgroupLabel(outgroup: string[]) {
150
+ const [first, ...rest] = outgroup
151
+ return rest.length === 0
152
+ ? `Outgroup ${first}`
153
+ : `Outgroup ${first} … ${rest.at(-1)}`
154
+ }
155
+
156
+ export function treeSubMenu(model: MsaViewModel): MenuItem[] {
157
+ const {
158
+ drawTree,
159
+ showBranchLen,
160
+ labelsAlignRight,
161
+ drawNodeLabels,
162
+ drawLabels,
163
+ treeAreaWidth,
164
+ showTreeOverview,
165
+ showTreeScaleBar,
166
+ treeOrder,
167
+ treeRoot,
168
+ rotated,
169
+ } = model
170
+ return [
171
+ subMenu(
172
+ 'Layout',
173
+ [
174
+ radio('Phylogram', drawTree && showBranchLen, () => {
175
+ model.setDrawTree(true)
176
+ model.setShowBranchLen(true)
177
+ }),
178
+ radio('Cladogram', drawTree && !showBranchLen, () => {
179
+ model.setDrawTree(true)
180
+ model.setShowBranchLen(false)
181
+ }),
182
+ radio('Labels only', !drawTree, () => {
183
+ model.setDrawTree(false)
184
+ }),
185
+ ],
186
+ 'segmented',
187
+ ),
188
+ toggle('Tree overview', showTreeOverview, arg => {
189
+ model.setShowTreeOverview(arg)
190
+ }),
191
+ toggle('Scale bar', showTreeScaleBar, arg => {
192
+ model.setShowTreeScaleBar(arg)
193
+ }),
194
+ subMenu(
195
+ 'Order',
196
+ [
197
+ ...treeOrders.map(order =>
198
+ radio(treeOrderLabels[order], treeOrder === order, () => {
199
+ model.setTreeOrder(order)
200
+ }),
201
+ ),
202
+ ...when(rotated.length > 0, [
203
+ action('Undo rotations', () => {
204
+ model.clearRotated()
205
+ }),
206
+ ]),
207
+ ],
208
+ 'select',
209
+ ),
210
+ subMenu(
211
+ 'Root',
212
+ [
213
+ radio('Input root', treeRoot === undefined, () => {
214
+ model.setTreeRoot(undefined)
215
+ }),
216
+ radio('Midpoint', treeRoot === 'midpoint', () => {
217
+ model.setTreeRoot('midpoint')
218
+ }),
219
+ // "Reroot here" in a node's menu sets the outgroup, so this only
220
+ // reports it
221
+ ...when(typeof treeRoot === 'object', [
222
+ {
223
+ ...radio(
224
+ typeof treeRoot === 'object'
225
+ ? outgroupLabel(treeRoot.outgroup)
226
+ : '',
227
+ true,
228
+ () => {},
229
+ ),
230
+ disabled: true,
231
+ },
232
+ ]),
233
+ ],
234
+ 'select',
235
+ ),
236
+ subMenu('Labels', [
237
+ toggle('Tip labels', drawLabels, arg => {
238
+ model.setDrawLabels(arg)
239
+ }),
240
+ toggle('Internal node labels', drawNodeLabels, arg => {
241
+ model.setDrawNodeLabels(arg)
242
+ }),
243
+ toggle('Align right', labelsAlignRight, arg => {
244
+ model.setLabelsAlignRight(arg)
245
+ }),
246
+ ]),
247
+ ...when(treeAreaWidth !== undefined, [
248
+ action('Reset tree width', () => {
249
+ model.setTreeAreaWidth(undefined)
250
+ }),
251
+ ]),
252
+ ]
253
+ }
254
+
255
+ export function viewMenuItems(model: MsaViewModel): MenuItem[] {
256
+ return [
257
+ ...when(canGoTo(model), [
258
+ action('Find row or column…', () => {
259
+ model.queueDialog(onClose => [GoToDialog, { model, onClose }])
260
+ }),
261
+ ]),
262
+ subMenu('Zoom', zoomSubMenu(model)),
263
+ ]
264
+ }
265
+
266
+ export function trackSettingsItems(model: MsaViewModel): MenuItem[] {
267
+ return [
268
+ toggle('Show track values on hover', model.showColumnStats, arg => {
269
+ model.setShowColumnStats(arg)
270
+ }),
271
+ ]
272
+ }
273
+
274
+ // the settings panel's toggles for how the alignment draws
275
+ export function alignmentSettingsItems(model: MsaViewModel): MenuItem[] {
276
+ return [
277
+ toggle('Show letters', model.drawMsaLetters, arg => {
278
+ model.setDrawMsaLetters(arg)
279
+ }),
280
+ ]
281
+ }
282
+
283
+ // who colors the cells comes first, since a scheme picked while the domains
284
+ // fill them would otherwise change nothing on screen
285
+ export function colorSettingsItems(model: MsaViewModel): MenuItem[] {
286
+ const { cellFill, filteredAnnotations } = model
287
+ return [
288
+ subMenu(
289
+ 'Fill cells with',
290
+ [
291
+ radio('Residues', cellFill === 'residue', () => {
292
+ model.setCellFill('residue')
293
+ }),
294
+ ...when(filteredAnnotations.length > 0, [
295
+ radio('Domains', cellFill === 'domain', () => {
296
+ model.setShowDomains(true)
297
+ model.setCellFill('domain')
298
+ }),
299
+ ]),
300
+ {
301
+ ...radio('Nothing', cellFill === 'none', () => {
302
+ model.setCellFill('none')
303
+ }),
304
+ helpText: 'Color the letters instead',
305
+ },
306
+ ],
307
+ 'segmented',
308
+ ),
309
+ {
310
+ type: 'subHeader',
311
+ label:
312
+ cellFill === 'domain'
313
+ ? 'Residue scheme: picking one takes the cells back from the domains'
314
+ : 'Residue scheme',
315
+ },
316
+ ...colorSchemeMenuItems(model),
317
+ ]
318
+ }
319
+
320
+ function neighborJoiningItem(model: MsaViewModel): MenuItem {
321
+ const rows = model.rowMap.size
322
+ const tooManyRows = rows > maxNeighborJoiningRows
323
+ return action(
324
+ tooManyRows
325
+ ? `Build neighbor joining tree (over ${maxNeighborJoiningRows} rows, use FastTree)`
326
+ : model.noTree
327
+ ? 'Build neighbor joining tree'
328
+ : 'Replace with neighbor joining tree',
329
+ () => {
330
+ try {
331
+ model.calculateNeighborJoiningTreeFromMSA()
332
+ } catch (e) {
333
+ console.error('Failed to calculate NJ tree:', e)
334
+ model.addWarning(e instanceof Error ? e.message : String(e))
335
+ }
336
+ },
337
+ tooManyRows || rows < 2,
338
+ )
339
+ }
340
+
341
+ export function toolsMenuItems(model: MsaViewModel): MenuItem[] {
342
+ return [neighborJoiningItem(model)]
343
+ }
344
+
345
+ export function helpMenuItems(model: MsaViewModel): MenuItem[] {
346
+ return [
347
+ action('Mouse & keyboard…', () => {
348
+ model.queueDialog(onClose => [HelpDialog, { model, onClose }])
349
+ }),
350
+ action('Tutorials', () => {
351
+ window.open(tutorialsUrl, '_blank', 'noopener,noreferrer')
352
+ }),
353
+ action('About & credits…', () => {
354
+ model.queueDialog(onClose => [AboutDialog, { onClose }])
355
+ }),
356
+ ]
357
+ }
@@ -19,8 +19,8 @@ const ImportForm = observer(function ({ model }: { model: MsaViewModel }) {
19
19
  const { error } = model
20
20
 
21
21
  return (
22
- <Container>
23
- <Box sx={{ width: '50%', [phone]: { width: '100%' } }}>
22
+ <Container sx={{ pt: 2, pb: 3 }}>
23
+ <Box sx={{ width: '50%', mb: 2, [phone]: { width: '100%' } }}>
24
24
  {error ? <ErrorMessage error={error} /> : null}
25
25
  <Typography>
26
26
  Open an MSA file (FASTA, Stockholm, Clustal, A3M or EMF format) and/or
@@ -45,20 +45,32 @@ const ImportForm = observer(function ({ model }: { model: MsaViewModel }) {
45
45
  },
46
46
  }}
47
47
  >
48
- <div>
48
+ <Box
49
+ sx={{
50
+ width: 400,
51
+ display: 'flex',
52
+ flexDirection: 'column',
53
+ gap: 1.5,
54
+ [phone]: { width: 'auto' },
55
+ }}
56
+ >
49
57
  <div>
50
58
  <Typography>MSA file or URL</Typography>
51
- <FileSelector location={msaFile} setLocation={setMsaFile} />
59
+ <FileSelector inline location={msaFile} setLocation={setMsaFile} />
52
60
  </div>
53
61
  <div>
54
62
  <Typography>Tree file or URL</Typography>
55
- <FileSelector location={treeFile} setLocation={setTreeFile} />
63
+ <FileSelector
64
+ inline
65
+ location={treeFile}
66
+ setLocation={setTreeFile}
67
+ />
56
68
  </div>
57
69
  <div>
58
70
  <Typography>Annotation GFF file or URL (optional)</Typography>
59
- <FileSelector location={gffFile} setLocation={setGffFile} />
71
+ <FileSelector inline location={gffFile} setLocation={setGffFile} />
60
72
  </div>
61
- </div>
73
+ </Box>
62
74
  <div>
63
75
  <Button
64
76
  onClick={() => {
@@ -1,18 +1,22 @@
1
1
  import React, { useCallback } from 'react'
2
2
 
3
- import { useTheme } from '@mui/material'
4
3
  import { observer } from 'mobx-react'
5
4
 
5
+ import { useCanvasTheme } from '../../darkReader.ts'
6
6
  import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
7
7
  import DragHandle, { scrollbarThumbFill } from '../DragHandle.tsx'
8
8
  import { msaThumbnail } from '../msa/msaRaster.ts'
9
- import { MINIMAP_BAR_HEIGHT, getMinimapLayout } from './minimapLayout.ts'
9
+ import {
10
+ MINIMAP_BAR_HEIGHT,
11
+ getMinimapLayout,
12
+ minimapDomainRects,
13
+ } from './minimapLayout.ts'
10
14
 
11
15
  import type { MsaViewModel } from '../../model.ts'
12
16
 
13
17
  const Minimap = observer(function ({ model }: { model: MsaViewModel }) {
14
18
  const { minimapHeight, msaCanvasWidth, highResScaleFactor } = model
15
- const theme = useTheme()
19
+ const theme = useCanvasTheme()
16
20
  const { unit, s, w, polygonHeight, polygonPoints } = getMinimapLayout(model)
17
21
 
18
22
  // the whole alignment, sampled down to the bar
@@ -41,6 +45,10 @@ const Minimap = observer(function ({ model }: { model: MsaViewModel }) {
41
45
  barHeight,
42
46
  )
43
47
  }
48
+ for (const r of minimapDomainRects(model, barWidth, barHeight)) {
49
+ ctx.fillStyle = r.fill
50
+ ctx.fillRect(r.x, r.y, r.w, r.h)
51
+ }
44
52
  },
45
53
  width: barWidth,
46
54
  height: barHeight,
@@ -2,7 +2,11 @@ import React from 'react'
2
2
 
3
3
  import { scrollbarThumbFill } from '../DragHandle.tsx'
4
4
  import { canvasHref, msaThumbnail } from '../msa/msaRaster.ts'
5
- import { MINIMAP_BAR_HEIGHT, getMinimapLayout } from './minimapLayout.ts'
5
+ import {
6
+ MINIMAP_BAR_HEIGHT,
7
+ getMinimapLayout,
8
+ minimapDomainRects,
9
+ } from './minimapLayout.ts'
6
10
 
7
11
  import type { MsaViewModel } from '../../model.ts'
8
12
  import type { Theme } from '@mui/material'
@@ -37,6 +41,18 @@ export default function MinimapSVG({
37
41
  preserveAspectRatio="none"
38
42
  />
39
43
  ) : null}
44
+ {minimapDomainRects(model, msaCanvasWidth, MINIMAP_BAR_HEIGHT).map(
45
+ ({ x, y, w, h, fill }) => (
46
+ <rect
47
+ key={`${x},${y},${w},${fill}`}
48
+ x={x}
49
+ y={y}
50
+ width={w}
51
+ height={h}
52
+ fill={fill}
53
+ />
54
+ ),
55
+ )}
40
56
  <rect
41
57
  x={0}
42
58
  y={0}
@@ -19,3 +19,30 @@ export function getMinimapLayout(model: MsaViewModel) {
19
19
  const polygonPoints = `${s + w},0 ${s},0 0,${polygonHeight} ${msaCanvasWidth},${polygonHeight}`
20
20
  return { unit, s, w, polygonHeight, polygonPoints }
21
21
  }
22
+
23
+ /**
24
+ * The domain overlay's boxes scaled onto the minimap bar, one per row's band,
25
+ * in the colors the alignment draws them, so the overview shows the domains
26
+ * the alignment does. Empty unless the domains fill the cells.
27
+ */
28
+ export function minimapDomainRects(
29
+ model: MsaViewModel,
30
+ width: number,
31
+ height: number,
32
+ ) {
33
+ const { cellFill, domainBands, featureColors, leaves, numColumns } = model
34
+ if (cellFill !== 'domain' || numColumns <= 0 || leaves.length === 0) {
35
+ return []
36
+ }
37
+ const colUnit = width / numColumns
38
+ const rowUnit = height / leaves.length
39
+ return leaves.flatMap((leaf, row) =>
40
+ (domainBands.get(leaf.data.name) ?? []).map(band => ({
41
+ x: band.startCol * colUnit,
42
+ y: row * rowUnit,
43
+ w: Math.max(1, (band.endCol - band.startCol) * colUnit),
44
+ h: Math.max(1, rowUnit),
45
+ fill: featureColors.get(band.annotation)?.fill ?? 'grey',
46
+ })),
47
+ )
48
+ }
@@ -3,9 +3,11 @@ import React, { useCallback, useRef } from 'react'
3
3
  import { observer } from 'mobx-react'
4
4
 
5
5
  import { useWheelScroll } from '../../useWheelScroll.ts'
6
+ import { zoomSnapshotScale } from '../../zoomSnapshot.ts'
6
7
  import { alignmentAriaLabel } from '../controls.ts'
7
8
  import Loading from './Loading.tsx'
8
9
  import MSACanvasBlock from './MSACanvasBlock.tsx'
10
+ import { WheelZoomHint, useWheelZoomHint } from './WheelZoomHint.tsx'
9
11
  import { onMsaKey } from './msaKeys.ts'
10
12
 
11
13
  import type { MsaViewModel } from '../../model.ts'
@@ -20,7 +22,16 @@ const MSACanvas = observer(function ({ model }: { model: MsaViewModel }) {
20
22
  scrollZoom,
21
23
  scrollX,
22
24
  scrollY,
25
+ zoomSnapshot,
23
26
  } = model
27
+ const blocks = zoomSnapshot
28
+ ? zoomSnapshot.blocksY.flatMap(by =>
29
+ zoomSnapshot.blocksX.map(bx => [bx, by] as const),
30
+ )
31
+ : blocks2d
32
+ const scale = zoomSnapshot
33
+ ? zoomSnapshotScale(model, zoomSnapshot)
34
+ : undefined
24
35
  const ref = useRef<HTMLDivElement>(null)
25
36
  const onScrollX = useCallback(
26
37
  (d: number) => {
@@ -36,15 +47,21 @@ const MSACanvas = observer(function ({ model }: { model: MsaViewModel }) {
36
47
  )
37
48
  const onZoom = useCallback(
38
49
  (scaleFactor: number, offsetX: number, offsetY: number) => {
39
- model.zoomToPos(scaleFactor, offsetX, offsetY, model.wheelZoomAxis)
50
+ model.wheelZoomToPos(scaleFactor, offsetX, offsetY)
40
51
  },
41
52
  [model],
42
53
  )
54
+ const onZoomSettle = useCallback(() => {
55
+ model.endWheelZoom()
56
+ }, [model])
57
+ const hint = useWheelZoomHint(model)
43
58
  const { onMouseDown, onMouseUp } = useWheelScroll({
44
59
  ref,
45
60
  onScrollX,
46
61
  onScrollY,
47
62
  onZoom,
63
+ onZoomSettle,
64
+ onWheelScroll: hint.onWheelScroll,
48
65
  scrollZoom,
49
66
  })
50
67
 
@@ -88,11 +105,12 @@ const MSACanvas = observer(function ({ model }: { model: MsaViewModel }) {
88
105
  position: 'absolute',
89
106
  top: 0,
90
107
  left: 0,
91
- transform: `translate(${scrollX}px, ${scrollY}px)`,
108
+ transform: `translate(${scrollX}px, ${scrollY}px)${scale ? ` scale(${scale.sx}, ${scale.sy})` : ''}`,
109
+ transformOrigin: '0 0',
92
110
  willChange: 'transform',
93
111
  }}
94
112
  >
95
- {blocks2d.map(([bx, by]) => (
113
+ {blocks.map(([bx, by]) => (
96
114
  <MSACanvasBlock
97
115
  key={`${bx}_${by}`}
98
116
  model={model}
@@ -104,6 +122,7 @@ const MSACanvas = observer(function ({ model }: { model: MsaViewModel }) {
104
122
  ) : (
105
123
  <Loading />
106
124
  )}
125
+ {hint.visible ? <WheelZoomHint /> : null}
107
126
  </div>
108
127
  )
109
128
  })
@@ -1,16 +1,16 @@
1
- import React from 'react'
1
+ import React, { useRef } from 'react'
2
2
 
3
3
  import BaseTooltip from '@jbrowse/core/ui/BaseTooltip'
4
- import { useTheme } from '@mui/material'
5
4
  import { observer } from 'mobx-react'
6
5
 
6
+ import { useCanvasTheme } from '../../darkReader.ts'
7
7
  import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
8
8
  import { drawMsaRaster, rasterApplies } from './msaRaster.ts'
9
- import { renderBoxFeatureCanvasBlock } from './renderBoxFeatureCanvasBlock.ts'
10
9
  import { renderMSABlock } from './renderMSABlock.ts'
11
10
  import { useMsaBlockMouse } from './useMsaBlockMouse.ts'
12
11
 
13
12
  import type { MsaViewModel } from '../../model.ts'
13
+ import type { ZoomSnapshot } from '../../zoomSnapshot.ts'
14
14
 
15
15
  const MSACanvasBlock = observer(function ({
16
16
  model,
@@ -22,22 +22,28 @@ const MSACanvasBlock = observer(function ({
22
22
  offsetY: number
23
23
  }) {
24
24
  const { blockSize, highResScaleFactor } = model
25
- const theme = useTheme()
25
+ const theme = useCanvasTheme()
26
26
  const { anchor, onMouseMove, onMouseDown, onClick, onMouseLeave } =
27
27
  useMsaBlockMouse({ model, offsetX, offsetY })
28
28
 
29
29
  const canvasSize = blockSize * highResScaleFactor
30
+ const drawnAt = useRef<ZoomSnapshot>(undefined)
30
31
  const ref = useCanvasAutorun({
31
32
  draw: ctx => {
33
+ // during a wheel zoom MSACanvas scales what this block drew at the
34
+ // snapshot, so the block redraws only when a new snapshot is taken
35
+ const snap = model.zoomSnapshot
36
+ if (
37
+ snap &&
38
+ (snap === drawnAt.current ||
39
+ snap.colWidth !== model.colWidth ||
40
+ snap.rowHeight !== model.rowHeight)
41
+ ) {
42
+ return
43
+ }
44
+ drawnAt.current = snap
32
45
  ctx.resetTransform()
33
46
  ctx.clearRect(0, 0, canvasSize, canvasSize)
34
- renderBoxFeatureCanvasBlock({
35
- ctx,
36
- theme,
37
- offsetX,
38
- offsetY,
39
- model,
40
- })
41
47
  const rasterTiles = rasterApplies(model)
42
48
  if (rasterTiles) {
43
49
  drawMsaRaster({ ctx, model, theme, offsetX, offsetY })
@@ -49,6 +55,7 @@ const MSACanvasBlock = observer(function ({
49
55
  offsetY,
50
56
  model,
51
57
  rasterTiles,
58
+ domains: true,
52
59
  })
53
60
  },
54
61
  width: canvasSize,
@@ -1,10 +1,10 @@
1
1
  import React from 'react'
2
2
 
3
3
  import { isAlive } from '@jbrowse/mobx-state-tree'
4
- import { useTheme } from '@mui/material'
5
4
  import { observer } from 'mobx-react'
6
5
 
7
6
  import { msaOverlayZIndex } from '../../constants.ts'
7
+ import { useCanvasTheme } from '../../darkReader.ts'
8
8
  import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
9
9
  import { renderMouseover } from './renderMSAMouseover.ts'
10
10
 
@@ -16,7 +16,7 @@ const MSAMouseoverCanvas = observer(function ({
16
16
  model: MsaViewModel
17
17
  }) {
18
18
  const { height, msaCanvasWidth: width, highResScaleFactor } = model
19
- const theme = useTheme()
19
+ const theme = useCanvasTheme()
20
20
  const canvasWidth = width * highResScaleFactor
21
21
  const canvasHeight = height * highResScaleFactor
22
22
  const ref = useCanvasAutorun({