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,56 @@
1
+ import React, { useCallback, useRef, useState } from 'react'
2
+
3
+ import { Paper, Typography } from '@mui/material'
4
+
5
+ import type { MsaViewModel } from '../../model.ts'
6
+
7
+ const hinted = new WeakSet<MsaViewModel>()
8
+
9
+ const zoomKey =
10
+ typeof navigator !== 'undefined' && navigator.platform.startsWith('Mac')
11
+ ? '⌘'
12
+ : 'Ctrl'
13
+
14
+ export function useWheelZoomHint(model: MsaViewModel) {
15
+ const [visible, setVisible] = useState(false)
16
+ const timer = useRef<ReturnType<typeof setTimeout>>(undefined)
17
+ const onWheelScroll = useCallback(
18
+ (event: WheelEvent) => {
19
+ if (
20
+ !model.scrollZoom &&
21
+ Math.abs(event.deltaY) > Math.abs(event.deltaX) &&
22
+ !hinted.has(model)
23
+ ) {
24
+ hinted.add(model)
25
+ setVisible(true)
26
+ clearTimeout(timer.current)
27
+ timer.current = setTimeout(() => {
28
+ setVisible(false)
29
+ }, 2500)
30
+ }
31
+ },
32
+ [model],
33
+ )
34
+ return { visible, onWheelScroll }
35
+ }
36
+
37
+ export function WheelZoomHint() {
38
+ return (
39
+ <Paper
40
+ elevation={3}
41
+ style={{
42
+ position: 'absolute',
43
+ top: 12,
44
+ left: '50%',
45
+ transform: 'translateX(-50%)',
46
+ padding: '4px 12px',
47
+ pointerEvents: 'none',
48
+ zIndex: 2,
49
+ }}
50
+ >
51
+ <Typography variant="body2">
52
+ {zoomKey} + scroll to zoom, or check Zoom on scroll
53
+ </Typography>
54
+ </Paper>
55
+ )
56
+ }
@@ -27,6 +27,9 @@ export function onMsaKey(
27
27
  case '-':
28
28
  model.zoomOut()
29
29
  return true
30
+ case '0':
31
+ model.resetZoom()
32
+ return true
30
33
  case 'Home':
31
34
  model.setScrollX(0)
32
35
  return true
@@ -6,7 +6,7 @@ import type { Theme } from '@mui/material'
6
6
  // One raster tile holds this many cells on a side. A tile costs ~10ms to build
7
7
  // and ~1MB to hold, and is then drawn at any zoom for ~1ms, so the cache only
8
8
  // has to be large enough to cover a screen without thrashing.
9
- export const rasterTileSize = 512
9
+ const rasterTileSize = 512
10
10
  const maxCachedTiles = 64
11
11
  const maxThumbnailColumns = 2000
12
12
  const maxColorCacheEntries = 4096
@@ -286,15 +286,13 @@ function paint(
286
286
  let supported: boolean | undefined
287
287
 
288
288
  /**
289
- * Whether the alignment background comes from the raster: true with
290
- * background coloring on, the domain overlay off, and a 2d context that reads
291
- * back, which jsdom lacks. renderMSABlock paints each cell otherwise.
289
+ * Whether the alignment background comes from the raster: true while the
290
+ * residue scheme fills the cells, with a 2d context that reads back, which
291
+ * jsdom lacks. renderMSABlock paints each cell otherwise.
292
292
  */
293
- export function rasterApplies(
294
- model: Pick<MsaViewModel, 'actuallyShowDomains' | 'bgColor'>,
295
- ) {
293
+ export function rasterApplies(model: Pick<MsaViewModel, 'cellFill'>) {
296
294
  supported ??= !!makeRasterCanvas(1, 1)
297
- return !model.actuallyShowDomains && model.bgColor && supported
295
+ return model.cellFill === 'residue' && supported
298
296
  }
299
297
 
300
298
  interface RasterCache {
@@ -41,18 +41,41 @@ export function renderBoxFeatureCanvasBlock({
41
41
  ctx.scale(k, k)
42
42
  ctx.translate(-offsetX, rowHeight / 2 - offsetY)
43
43
 
44
+ const source = domainOverlaySource(
45
+ model,
46
+ getVisibleLeaves({ model, offsetY, blockSizeY: by }),
47
+ )
48
+ const contrastText = contrastTextFn(theme)
49
+ if (model.cellFill === 'residue' && model.domainUnderline) {
50
+ // over residue colors a bar in a categorical color blends into the cells
51
+ // beside it, so a strip of the page background parts it from them
52
+ const { height } = source.layout
53
+ const halo = theme.palette.background.default
54
+ drawFeatureSpans<DomainBand>({
55
+ ctx,
56
+ ...source,
57
+ layout: { ...source.layout, height: n => height(n) + barHalo },
58
+ colors: new Map(
59
+ [...source.colors.keys()].map(a => [a, { fill: halo, stroke: halo }]),
60
+ ),
61
+ labelOf: undefined,
62
+ labelDrawn: undefined,
63
+ contrastText,
64
+ xMin: offsetX,
65
+ xMax: offsetX + bx,
66
+ })
67
+ }
44
68
  drawFeatureSpans<DomainBand>({
45
69
  ctx,
46
- ...domainOverlaySource(
47
- model,
48
- getVisibleLeaves({ model, offsetY, blockSizeY: by }),
49
- ),
50
- contrastText: contrastTextFn(theme),
70
+ ...source,
71
+ contrastText,
51
72
  xMin: offsetX,
52
73
  xMax: offsetX + bx,
53
74
  })
54
75
  }
55
76
 
77
+ const barHalo = 1.5
78
+
56
79
  /**
57
80
  * The domain overlay's spans over `leaves`, in alignment pixels: filled rows,
58
81
  * an underline bar, or thin sub-rows, as `domainUnderline` and
@@ -11,11 +11,11 @@ import type { Theme } from '@mui/material'
11
11
 
12
12
  // a fill plus a solid border; the hover wash alone is invisible over clustalx
13
13
  // coloring
14
- export const highlightFill = 'rgba(255,140,0,0.28)'
15
- export const highlightBorder = 'rgba(210,90,0,0.95)'
14
+ const highlightFill = 'rgba(255,140,0,0.28)'
15
+ const highlightBorder = 'rgba(210,90,0,0.95)'
16
16
  export const highlightRowFill = 'rgba(255,140,0,0.18)'
17
17
 
18
- export const labelFontSize = 11
18
+ const labelFontSize = 11
19
19
  const labelPad = 3
20
20
  // the label box above a highlight band; tree blocks also check rows this far
21
21
  // outside themselves
@@ -1,7 +1,9 @@
1
+ import { zoomFrameMaxLetters } from '../../constants.ts'
1
2
  import { setFontSize } from '../../setFontSize.ts'
2
3
  import { adjustColorForContrast, contrastTextFn } from '../../util.ts'
3
4
  import { getVisibleLeaves } from '../getVisibleLeaves.ts'
4
5
  import { domainBandCursor } from './domainBandCursor.ts'
6
+ import { renderBoxFeatureCanvasBlock } from './renderBoxFeatureCanvasBlock.ts'
5
7
  import { tileColorFn } from './tileColor.ts'
6
8
  import { visibleColRange } from './visibleColRange.ts'
7
9
 
@@ -21,6 +23,7 @@ export function renderMSABlock({
21
23
  blockSizeXOverride,
22
24
  blockSizeYOverride,
23
25
  rasterTiles,
26
+ domains,
24
27
  }: {
25
28
  offsetX: number
26
29
  offsetY: number
@@ -31,6 +34,11 @@ export function renderMSABlock({
31
34
  blockSizeXOverride?: number
32
35
  blockSizeYOverride?: number
33
36
  rasterTiles?: boolean
37
+ /**
38
+ * draw the domain overlay too, over the cell colors and under the letters,
39
+ * which is the one order every cell fill reads right in
40
+ */
41
+ domains?: boolean
34
42
  }) {
35
43
  const {
36
44
  colWidth,
@@ -38,18 +46,21 @@ export function renderMSABlock({
38
46
  rowHeight,
39
47
  fontSize,
40
48
  highResScaleFactor,
41
- actuallyShowDomains,
49
+ cellFill,
42
50
  bgColor,
43
51
  } = model
44
52
  const k = highResScaleFactorOverride ?? highResScaleFactor
45
53
  const bx = blockSizeXOverride ?? blockSize
46
54
  const by = blockSizeYOverride ?? blockSize
47
- ctx.resetTransform()
48
- ctx.scale(k, k)
49
- ctx.translate(-offsetX, rowHeight / 2 - offsetY)
50
- ctx.textAlign = 'center'
51
- ctx.textBaseline = 'alphabetic'
52
- setFontSize(ctx, fontSize, !bgColor)
55
+ const setup = () => {
56
+ ctx.resetTransform()
57
+ ctx.scale(k, k)
58
+ ctx.translate(-offsetX, rowHeight / 2 - offsetY)
59
+ ctx.textAlign = 'center'
60
+ ctx.textBaseline = 'alphabetic'
61
+ setFontSize(ctx, fontSize, !bgColor)
62
+ }
63
+ setup()
53
64
 
54
65
  const { xStart, xEnd } = visibleColRange({
55
66
  offsetX,
@@ -62,7 +73,7 @@ export function renderMSABlock({
62
73
  ? columns.get(relativeTo)?.slice(xStart, xEnd)
63
74
  : null
64
75
 
65
- drawTilesAndText({
76
+ const cells = {
66
77
  model,
67
78
  ctx,
68
79
  theme,
@@ -70,13 +81,34 @@ export function renderMSABlock({
70
81
  xEnd,
71
82
  visibleLeaves,
72
83
  referenceSeq,
73
- // when domains are shown the background tiles come from
84
+ // when the domains fill the cells the background comes from
74
85
  // renderBoxFeatureCanvasBlock, so only the letters are drawn here
75
- drawTiles: !actuallyShowDomains,
86
+ drawTiles: cellFill !== 'domain',
76
87
  // ...and when the caller has already laid the raster down, the tiles are
77
88
  // there but nothing here has to paint them
78
89
  rasterTiles,
79
- })
90
+ letters:
91
+ model.showMsaLetters &&
92
+ (!model.wheelZooming ||
93
+ (bx * by) / (colWidth * rowHeight) <= zoomFrameMaxLetters),
94
+ }
95
+ if (domains) {
96
+ drawTilesAndText({ ...cells, pass: 'tiles' })
97
+ renderBoxFeatureCanvasBlock({
98
+ model,
99
+ theme,
100
+ ctx,
101
+ offsetX,
102
+ offsetY,
103
+ highResScaleFactorOverride,
104
+ blockSizeXOverride,
105
+ blockSizeYOverride,
106
+ })
107
+ setup()
108
+ drawTilesAndText({ ...cells, pass: 'letters' })
109
+ } else {
110
+ drawTilesAndText({ ...cells, pass: 'both' })
111
+ }
80
112
  drawInsertionIndicators({
81
113
  model,
82
114
  ctx,
@@ -97,6 +129,8 @@ function drawTilesAndText({
97
129
  referenceSeq,
98
130
  drawTiles,
99
131
  rasterTiles,
132
+ letters: lettersOn,
133
+ pass,
100
134
  }: {
101
135
  model: MsaViewModel
102
136
  theme: Theme
@@ -107,6 +141,8 @@ function drawTilesAndText({
107
141
  referenceSeq: string | null | undefined
108
142
  drawTiles: boolean
109
143
  rasterTiles?: boolean
144
+ letters: boolean
145
+ pass: 'tiles' | 'letters' | 'both'
110
146
  }) {
111
147
  const {
112
148
  bgColor,
@@ -115,7 +151,6 @@ function drawTilesAndText({
115
151
  rowHeight,
116
152
  fontSize,
117
153
  relativeTo,
118
- showMsaLetters,
119
154
  subFeatureRows,
120
155
  domainUnderline,
121
156
  domainBandsByStart,
@@ -123,8 +158,9 @@ function drawTilesAndText({
123
158
  } = model
124
159
 
125
160
  const tiles = drawTiles && bgColor
126
- const paintTiles = tiles && !rasterTiles
127
- if (paintTiles || showMsaLetters) {
161
+ const paintTiles = tiles && !rasterTiles && pass !== 'letters'
162
+ const letters = lettersOn && pass !== 'tiles'
163
+ if (paintTiles || letters) {
128
164
  const tileColor = tileColorFn(model)
129
165
  // Domain fills come from a categorical palette with no fixed lightness, and
130
166
  // a dynamic scheme colors a cell from its column, so a readable letter color
@@ -169,7 +205,7 @@ function drawTilesAndText({
169
205
  ctx.fillRect(x, tileY, colWidth, rowHeight)
170
206
  }
171
207
 
172
- if (showMsaLetters) {
208
+ if (letters) {
173
209
  const covering = bandAt(col)
174
210
  ctx.fillStyle = covering
175
211
  ? // on top of a domain box: contrast against the box fill
@@ -9,6 +9,10 @@ export function tileColorFn(model: MsaViewModel) {
9
9
  const { colClustalX } = model
10
10
  return (col: number, letter: string) => colClustalX[col]![letter]
11
11
  }
12
+ if (dynamicColorSchemeName === 'blosum62_dynamic') {
13
+ const { colSimilarity } = model
14
+ return (col: number, letter: string) => colSimilarity[col]![letter]
15
+ }
12
16
  if (dynamicColorSchemeName === 'percent_identity_dynamic') {
13
17
  const { colConsensus } = model
14
18
  return (col: number, letter: string) => {
@@ -28,6 +32,9 @@ export function tileColorTable(model: MsaViewModel) {
28
32
  if (dynamicColorSchemeName === 'clustalx_protein_dynamic') {
29
33
  return model.colClustalX
30
34
  }
35
+ if (dynamicColorSchemeName === 'blosum62_dynamic') {
36
+ return model.colSimilarity
37
+ }
31
38
  if (dynamicColorSchemeName === 'percent_identity_dynamic') {
32
39
  return model.colConsensus
33
40
  }
@@ -1,9 +1,9 @@
1
1
  import React 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 { featureField, featureName } from '../../featureFields.ts'
8
8
  import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
9
9
  import { useHoverAnchor } from '../useHoverAnchor.ts'
@@ -64,7 +64,7 @@ const RowPanelBlock = observer(function ({
64
64
  name: string
65
65
  lines: string[]
66
66
  }>()
67
- const theme = useTheme()
67
+ const theme = useCanvasTheme()
68
68
  const { blockSize, highResScaleFactor, rowHeight } = model
69
69
  const width = panel.width
70
70
  const height = blockSize
@@ -0,0 +1,225 @@
1
+ import React, { useState } from 'react'
2
+
3
+ import ArrowDropDown from '@mui/icons-material/ArrowDropDown'
4
+ import ArrowRight from '@mui/icons-material/ArrowRight'
5
+ import {
6
+ Box,
7
+ Button,
8
+ Checkbox,
9
+ FormControlLabel,
10
+ MenuItem as MuiMenuItem,
11
+ Radio,
12
+ TextField,
13
+ ToggleButton,
14
+ ToggleButtonGroup,
15
+ Typography,
16
+ } from '@mui/material'
17
+
18
+ import type { MenuItem } from '@jbrowse/core/ui/Menu'
19
+
20
+ type RadioMenuItem = Extract<MenuItem, { type: 'radio' }>
21
+
22
+ /**
23
+ * A submenu of radios may name a compact control to render as: `segmented`
24
+ * for a few short choices, `select` for a longer list
25
+ */
26
+ export type ChoiceDisplay = 'segmented' | 'select'
27
+
28
+ function radios(items: MenuItem[]) {
29
+ return items.filter(
30
+ (i): i is RadioMenuItem => 'type' in i && i.type === 'radio',
31
+ )
32
+ }
33
+
34
+ function Caption({ children }: { children: React.ReactNode }) {
35
+ return (
36
+ <Typography
37
+ variant="caption"
38
+ color="text.secondary"
39
+ sx={{ mt: 1, display: 'block' }}
40
+ >
41
+ {children}
42
+ </Typography>
43
+ )
44
+ }
45
+
46
+ function Segmented({
47
+ label,
48
+ options,
49
+ }: {
50
+ label: string
51
+ options: RadioMenuItem[]
52
+ }) {
53
+ const value = options.findIndex(o => o.checked)
54
+ return (
55
+ <>
56
+ <Caption>{label}</Caption>
57
+ <ToggleButtonGroup
58
+ exclusive
59
+ fullWidth
60
+ size="small"
61
+ value={value}
62
+ aria-label={label}
63
+ onChange={(_event, i: number | null) => {
64
+ if (i !== null) {
65
+ options[i]?.onClick()
66
+ }
67
+ }}
68
+ >
69
+ {options.map((o, i) => (
70
+ <ToggleButton
71
+ key={String(o.label)}
72
+ value={i}
73
+ disabled={o.disabled}
74
+ title={o.helpText}
75
+ sx={{ textTransform: 'none', py: 0.25 }}
76
+ >
77
+ {o.label}
78
+ </ToggleButton>
79
+ ))}
80
+ </ToggleButtonGroup>
81
+ </>
82
+ )
83
+ }
84
+
85
+ // each option fires its own onClick, so picking the checked one again still
86
+ // runs, which reopens a dialog like the custom gap threshold
87
+ function Select({
88
+ label,
89
+ options,
90
+ }: {
91
+ label: string
92
+ options: RadioMenuItem[]
93
+ }) {
94
+ const value = options.findIndex(o => o.checked)
95
+ return (
96
+ <TextField
97
+ select
98
+ size="small"
99
+ fullWidth
100
+ label={label}
101
+ value={value === -1 ? '' : value}
102
+ sx={{ mt: 1.5, '& .MuiInputBase-root': { typography: 'body2' } }}
103
+ >
104
+ {options.map((o, i) => (
105
+ <MuiMenuItem
106
+ key={String(o.label)}
107
+ value={i}
108
+ disabled={o.disabled}
109
+ dense
110
+ onClick={() => {
111
+ o.onClick()
112
+ }}
113
+ >
114
+ {o.label}
115
+ </MuiMenuItem>
116
+ ))}
117
+ </TextField>
118
+ )
119
+ }
120
+
121
+ // a short submenu opens in place under a caption, where every choice shows at
122
+ // once; a long one, like the extra color schemes, folds behind a toggle
123
+ const inlineUpTo = 8
124
+
125
+ function SubSection({ item }: { item: MenuItem }) {
126
+ const [open, setOpen] = useState(false)
127
+ if (!('subMenu' in item) || !Array.isArray(item.subMenu)) {
128
+ return null
129
+ }
130
+ const label = String(item.label)
131
+ const display =
132
+ 'display' in item ? (item.display as ChoiceDisplay) : undefined
133
+ if (display) {
134
+ const options = radios(item.subMenu)
135
+ const rest = item.subMenu.filter(i => !options.includes(i as RadioMenuItem))
136
+ const Control = display === 'segmented' ? Segmented : Select
137
+ return (
138
+ <>
139
+ <Control label={label} options={options} />
140
+ <MenuItemsForm items={rest} />
141
+ </>
142
+ )
143
+ }
144
+ if (item.subMenu.length <= inlineUpTo) {
145
+ return (
146
+ <>
147
+ <Caption>{label}</Caption>
148
+ <MenuItemsForm items={item.subMenu} />
149
+ </>
150
+ )
151
+ }
152
+ return (
153
+ <>
154
+ <Button
155
+ size="small"
156
+ color="inherit"
157
+ startIcon={open ? <ArrowDropDown /> : <ArrowRight />}
158
+ onClick={() => {
159
+ setOpen(!open)
160
+ }}
161
+ sx={{ textTransform: 'none', justifyContent: 'flex-start', px: 0.5 }}
162
+ >
163
+ {label}
164
+ </Button>
165
+ {open ? (
166
+ <Box sx={{ pl: 2 }}>
167
+ <MenuItemsForm items={item.subMenu} />
168
+ </Box>
169
+ ) : null}
170
+ </>
171
+ )
172
+ }
173
+
174
+ export default function MenuItemsForm({ items }: { items: MenuItem[] }) {
175
+ return (
176
+ <Box sx={{ display: 'flex', flexDirection: 'column' }}>
177
+ {items.map((item, i) => {
178
+ const key = 'label' in item ? String(item.label) : String(i)
179
+ if (!('label' in item)) {
180
+ return null
181
+ }
182
+ if ('type' in item && item.type === 'subHeader') {
183
+ return <Caption key={key}>{item.label}</Caption>
184
+ }
185
+ if ('subMenu' in item) {
186
+ return <SubSection key={key} item={item} />
187
+ }
188
+ const onClick = 'onClick' in item ? item.onClick : undefined
189
+ if (
190
+ 'type' in item &&
191
+ (item.type === 'checkbox' || item.type === 'radio')
192
+ ) {
193
+ const Control = item.type === 'checkbox' ? Checkbox : Radio
194
+ return (
195
+ <FormControlLabel
196
+ key={key}
197
+ disabled={item.disabled}
198
+ control={
199
+ <Control
200
+ size="small"
201
+ checked={item.checked}
202
+ onChange={() => onClick?.()}
203
+ sx={{ py: 0.25 }}
204
+ />
205
+ }
206
+ label={item.label}
207
+ slotProps={{ typography: { variant: 'body2' } }}
208
+ />
209
+ )
210
+ }
211
+ return (
212
+ <Button
213
+ key={key}
214
+ size="small"
215
+ disabled={item.disabled}
216
+ onClick={() => onClick?.()}
217
+ sx={{ alignSelf: 'flex-start', textTransform: 'none' }}
218
+ >
219
+ {item.label}
220
+ </Button>
221
+ )
222
+ })}
223
+ </Box>
224
+ )
225
+ }