react-msaview 5.5.0 → 5.6.1

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 (139) hide show
  1. package/bundle/index.js +173 -162
  2. package/bundle/index.js.map +4 -4
  3. package/dist/components/ConservationTrack.js +20 -26
  4. package/dist/components/ConservationTrack.js.map +1 -1
  5. package/dist/components/MSAViewer.d.ts +7 -1
  6. package/dist/components/MSAViewer.js +4 -1
  7. package/dist/components/MSAViewer.js.map +1 -1
  8. package/dist/components/SequenceTextArea.js +1 -1
  9. package/dist/components/SequenceTextArea.js.map +1 -1
  10. package/dist/components/TextTrack.js +20 -27
  11. package/dist/components/TextTrack.js.map +1 -1
  12. package/dist/components/Track.js +1 -1
  13. package/dist/components/Track.js.map +1 -1
  14. package/dist/components/dialogs/TrackInfoDialog.js +1 -1
  15. package/dist/components/dialogs/TrackInfoDialog.js.map +1 -1
  16. package/dist/components/header/ColorSchemeMenu.js +1 -1
  17. package/dist/components/header/ColorSchemeMenu.js.map +1 -1
  18. package/dist/components/header/FileMenu.js +1 -16
  19. package/dist/components/header/FileMenu.js.map +1 -1
  20. package/dist/components/header/Header.js +6 -5
  21. package/dist/components/header/Header.js.map +1 -1
  22. package/dist/components/header/HeaderInfoArea.js +1 -1
  23. package/dist/components/header/HeaderInfoArea.js.map +1 -1
  24. package/dist/components/header/HeaderStatusArea.js +1 -1
  25. package/dist/components/header/HeaderStatusArea.js.map +1 -1
  26. package/dist/components/header/MSASettingsMenu.js +1 -1
  27. package/dist/components/header/MSASettingsMenu.js.map +1 -1
  28. package/dist/components/header/TreeSettingsMenu.js +1 -1
  29. package/dist/components/header/TreeSettingsMenu.js.map +1 -1
  30. package/dist/components/header/ZoomControls.js +14 -12
  31. package/dist/components/header/ZoomControls.js.map +1 -1
  32. package/dist/components/header/ZoomMenu.js +1 -10
  33. package/dist/components/header/ZoomMenu.js.map +1 -1
  34. package/dist/components/header/settingsMenuItems.js +9 -1
  35. package/dist/components/header/settingsMenuItems.js.map +1 -1
  36. package/dist/components/{header/HeaderMenu.d.ts → msa/ColumnStats.d.ts} +3 -3
  37. package/dist/components/msa/ColumnStats.js +42 -0
  38. package/dist/components/msa/ColumnStats.js.map +1 -0
  39. package/dist/components/msa/DomainLegend.js +1 -1
  40. package/dist/components/msa/DomainLegend.js.map +1 -1
  41. package/dist/components/msa/MSACanvasBlock.js +39 -39
  42. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  43. package/dist/components/msa/MSAMouseoverCanvas.js +6 -12
  44. package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
  45. package/dist/components/msa/renderBoxFeatureCanvasBlock.js +3 -1
  46. package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
  47. package/dist/components/msa/renderMSAMouseover.js +1 -2
  48. package/dist/components/msa/renderMSAMouseover.js.map +1 -1
  49. package/dist/components/overlayColors.d.ts +3 -0
  50. package/dist/components/overlayColors.js +11 -0
  51. package/dist/components/overlayColors.js.map +1 -0
  52. package/dist/components/tracks/renderTracksSvg.d.ts +6 -3
  53. package/dist/components/tracks/renderTracksSvg.js +17 -8
  54. package/dist/components/tracks/renderTracksSvg.js.map +1 -1
  55. package/dist/components/tree/TreeCanvas.js +31 -38
  56. package/dist/components/tree/TreeCanvas.js.map +1 -1
  57. package/dist/components/tree/TreeCanvasBlock.js +12 -47
  58. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  59. package/dist/components/tree/clickMap.d.ts +24 -0
  60. package/dist/components/tree/clickMap.js +33 -0
  61. package/dist/components/tree/clickMap.js.map +1 -0
  62. package/dist/components/tree/renderTreeCanvas.d.ts +1 -21
  63. package/dist/components/tree/renderTreeCanvas.js +8 -7
  64. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  65. package/dist/constants.d.ts +1 -0
  66. package/dist/constants.js +1 -0
  67. package/dist/constants.js.map +1 -1
  68. package/dist/fetchUtils.d.ts +2 -1
  69. package/dist/fetchUtils.js +3 -2
  70. package/dist/fetchUtils.js.map +1 -1
  71. package/dist/index.d.ts +1 -1
  72. package/dist/model/msaModel.d.ts +25 -2
  73. package/dist/model/msaModel.js +15 -3
  74. package/dist/model/msaModel.js.map +1 -1
  75. package/dist/model/treeModel.d.ts +46 -5
  76. package/dist/model/treeModel.js +20 -7
  77. package/dist/model/treeModel.js.map +1 -1
  78. package/dist/model.d.ts +545 -132
  79. package/dist/model.js +101 -75
  80. package/dist/model.js.map +1 -1
  81. package/dist/propertyConservation.d.ts +7 -0
  82. package/dist/propertyConservation.js +66 -0
  83. package/dist/propertyConservation.js.map +1 -0
  84. package/dist/types.d.ts +4 -1
  85. package/dist/useCanvasAutorun.d.ts +2 -0
  86. package/dist/useCanvasAutorun.js +23 -0
  87. package/dist/useCanvasAutorun.js.map +1 -0
  88. package/dist/version.d.ts +1 -1
  89. package/dist/version.js +1 -1
  90. package/package.json +4 -6
  91. package/src/colorSchemes.docs.test.ts +57 -0
  92. package/src/columnStats.test.ts +94 -0
  93. package/src/components/ConservationTrack.tsx +17 -15
  94. package/src/components/MSAViewer.tsx +12 -0
  95. package/src/components/SequenceTextArea.tsx +1 -1
  96. package/src/components/TextTrack.tsx +7 -11
  97. package/src/components/Track.tsx +1 -1
  98. package/src/components/dialogs/TrackInfoDialog.tsx +1 -1
  99. package/src/components/header/ColorSchemeMenu.tsx +1 -0
  100. package/src/components/header/FileMenu.tsx +1 -15
  101. package/src/components/header/Header.tsx +11 -8
  102. package/src/components/header/HeaderInfoArea.tsx +1 -1
  103. package/src/components/header/HeaderStatusArea.tsx +1 -1
  104. package/src/components/header/MSASettingsMenu.tsx +1 -0
  105. package/src/components/header/TreeSettingsMenu.tsx +1 -0
  106. package/src/components/header/ZoomControls.tsx +24 -17
  107. package/src/components/header/ZoomMenu.tsx +1 -10
  108. package/src/components/header/settingsMenuItems.ts +9 -1
  109. package/src/components/msa/ColumnStats.tsx +54 -0
  110. package/src/components/msa/DomainLegend.tsx +5 -1
  111. package/src/components/msa/MSACanvasBlock.tsx +29 -26
  112. package/src/components/msa/MSAMouseoverCanvas.tsx +10 -13
  113. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +3 -1
  114. package/src/components/msa/renderMSAMouseover.ts +2 -2
  115. package/src/components/overlayColors.ts +12 -0
  116. package/src/components/tracks/renderTracksSvg.ts +22 -8
  117. package/src/components/tree/TreeCanvas.tsx +47 -54
  118. package/src/components/tree/TreeCanvasBlock.tsx +10 -68
  119. package/src/components/tree/clickMap.ts +55 -0
  120. package/src/components/tree/renderTreeCanvas.ts +10 -34
  121. package/src/constants.ts +1 -0
  122. package/src/fetchUtils.ts +6 -3
  123. package/src/index.ts +1 -1
  124. package/src/model/msaModel.ts +24 -3
  125. package/src/model/treeModel.ts +27 -7
  126. package/src/model.ts +122 -117
  127. package/src/propertyConservation.test.ts +47 -0
  128. package/src/propertyConservation.ts +73 -0
  129. package/src/stripDefaultSnapshot.test.ts +57 -0
  130. package/src/types.ts +7 -1
  131. package/src/useCanvasAutorun.ts +28 -0
  132. package/src/version.ts +1 -1
  133. package/dist/components/dialogs/SettingsDialog.d.ts +0 -7
  134. package/dist/components/dialogs/SettingsDialog.js +0 -100
  135. package/dist/components/dialogs/SettingsDialog.js.map +0 -1
  136. package/dist/components/header/HeaderMenu.js +0 -27
  137. package/dist/components/header/HeaderMenu.js.map +0 -1
  138. package/src/components/dialogs/SettingsDialog.tsx +0 -234
  139. package/src/components/header/HeaderMenu.tsx +0 -38
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-msaview",
3
3
  "author": "Colin",
4
- "version": "5.5.0",
4
+ "version": "5.6.1",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "repository": {
@@ -35,16 +35,14 @@
35
35
  "react-dom": ">=16.8.0"
36
36
  },
37
37
  "devDependencies": {
38
- "@types/node": "^26.0.1",
38
+ "@types/node": "^26.1.1",
39
39
  "esbuild": "^0.28.1"
40
40
  },
41
41
  "dependencies": {
42
- "@mui/icons-material": "^7.3.11",
43
- "@mui/material": "^7.3.11",
44
42
  "colord": "^2.9.3",
45
43
  "flatbush": "^4.6.2",
46
- "@jbrowse/svgcanvas": "5.5.0",
47
- "msa-parsers": "5.5.0"
44
+ "@jbrowse/svgcanvas": "5.6.1",
45
+ "msa-parsers": "5.6.1"
48
46
  },
49
47
  "scripts": {
50
48
  "clean": "rimraf dist bundle",
@@ -0,0 +1,57 @@
1
+ import { readFileSync } from 'node:fs'
2
+
3
+ import { expect, test } from 'vitest'
4
+
5
+ import colorSchemes from './colorSchemes.ts'
6
+
7
+ // Guard against doc drift: every color-scheme name listed in the JS embedding
8
+ // docs (USAGE.md) must be a real registered scheme, so a rename or removal in
9
+ // colorSchemes.ts can't silently leave a dead name in the docs that users copy.
10
+ // (Subset check only — a registered scheme may intentionally go undocumented.)
11
+ test('color schemes listed in USAGE.md all exist', () => {
12
+ const registered = new Set(Object.keys(colorSchemes))
13
+ const usage = readFileSync(
14
+ new URL('../../../USAGE.md', import.meta.url),
15
+ 'utf8',
16
+ )
17
+
18
+ const section = /## Color schemes\n([\s\S]*?)\n## /.exec(usage)?.[1]
19
+ expect(section, 'could not find the "## Color schemes" section').toBeTruthy()
20
+
21
+ // drop the bold labels (**Protein:**, **Dynamic (per-column):**, …), leaving
22
+ // just the comma-separated scheme names
23
+ const names = section!.replace(/\*\*[^*]+\*\*/g, '').match(/[a-z][a-z0-9_]+/g)
24
+ expect(names?.length).toBeGreaterThan(0)
25
+
26
+ const unknown = names!.filter(name => !registered.has(name))
27
+ expect(
28
+ unknown,
29
+ `documented but not registered: ${unknown.join(', ')}`,
30
+ ).toEqual([])
31
+ })
32
+
33
+ // Same guard for the R package docs, whose color_scheme names pass straight
34
+ // through as colorSchemeName with no remapping — a name that isn't registered
35
+ // silently renders with no coloring.
36
+ test('color schemes listed in the R README all exist', () => {
37
+ const registered = new Set(Object.keys(colorSchemes))
38
+ const readme = readFileSync(
39
+ new URL('../../r-msaview/README.md', import.meta.url),
40
+ 'utf8',
41
+ )
42
+
43
+ const section = /## Color schemes\n([\s\S]*?)\n## /.exec(readme)?.[1]
44
+ expect(section, 'could not find the "## Color schemes" section').toBeTruthy()
45
+
46
+ // names are backtick-quoted, e.g. `jalview_taylor`
47
+ const names = [...section!.matchAll(/`([a-z][a-z0-9_]+)`/g)].flatMap(m =>
48
+ m[1] ? [m[1]] : [],
49
+ )
50
+ expect(names.length).toBeGreaterThan(0)
51
+
52
+ const unknown = names.filter(name => !registered.has(name))
53
+ expect(
54
+ unknown,
55
+ `documented but not registered: ${unknown.join(', ')}`,
56
+ ).toEqual([])
57
+ })
@@ -0,0 +1,94 @@
1
+ // @vitest-environment jsdom
2
+ import { beforeAll, describe, expect, test } from 'vitest'
3
+
4
+ import MSAModelF from './model.ts'
5
+
6
+ beforeAll(() => {
7
+ HTMLCanvasElement.prototype.getContext = function () {
8
+ let font = '10px sans-serif'
9
+ return {
10
+ get font() {
11
+ return font
12
+ },
13
+ set font(v: string) {
14
+ font = v
15
+ },
16
+ measureText(t: string) {
17
+ const size = Number.parseFloat(font) || 10
18
+ return { width: t.length * size * 0.6 } as TextMetrics
19
+ },
20
+ } as unknown as CanvasRenderingContext2D
21
+ } as unknown as typeof HTMLCanvasElement.prototype.getContext
22
+ })
23
+
24
+ // col0 is invariant M; col3 varies V/V/A (all hydrophobic) so property
25
+ // conservation stays high while identity conservation drops
26
+ const protein = `>a
27
+ MKLVIL
28
+ >b
29
+ MRLVIL
30
+ >c
31
+ MKLAIL`
32
+
33
+ const dna = `>a
34
+ ACGTAC
35
+ >b
36
+ ACGTAG
37
+ >c
38
+ ACGAAC`
39
+
40
+ function make(msa: string, id: string) {
41
+ return MSAModelF().create({
42
+ id,
43
+ type: 'MsaView',
44
+ msaFormat: 'fasta',
45
+ data: { msa },
46
+ })
47
+ }
48
+
49
+ describe('property conservation track', () => {
50
+ test('present for protein alignments', () => {
51
+ const ids = make(protein, 'p').tracks.map(t => t.model.id)
52
+ expect(ids).toContain('conservation')
53
+ expect(ids).toContain('property-conservation')
54
+ })
55
+
56
+ test('omitted for nucleotide alignments', () => {
57
+ const ids = make(dna, 'd').tracks.map(t => t.model.id)
58
+ expect(ids).toContain('conservation')
59
+ expect(ids).not.toContain('property-conservation')
60
+ })
61
+
62
+ test('conservative-substitution column: property beats identity', () => {
63
+ const model = make(protein, 'pc')
64
+ // col3 = V/V/A: same property class, different identity
65
+ expect(model.propertyConservation[3]!).toBeGreaterThan(
66
+ model.conservation[3]!,
67
+ )
68
+ })
69
+ })
70
+
71
+ describe('mouseOverColumnStats', () => {
72
+ test('undefined when nothing hovered', () => {
73
+ expect(make(protein, 'm0').mouseOverColumnStats).toBeUndefined()
74
+ })
75
+
76
+ test('reports consensus, gaps and distribution for hovered column', () => {
77
+ const model = make(protein, 'm1')
78
+ model.setMousePos(0, 0)
79
+ const s = model.mouseOverColumnStats
80
+ expect(s?.consensusLetter).toBe('M')
81
+ expect(s?.consensusFraction).toBe(1)
82
+ expect(s?.gapFraction).toBe(0)
83
+ expect(s?.total).toBe(3)
84
+ })
85
+
86
+ test('mixed column reports the majority residue as consensus', () => {
87
+ const model = make(protein, 'm2')
88
+ // col1 = K/R/K -> consensus K at 2/3
89
+ model.setMousePos(1, 0)
90
+ const s = model.mouseOverColumnStats
91
+ expect(s?.consensusLetter).toBe('K')
92
+ expect(s?.consensusCount).toBe(2)
93
+ })
94
+ })
@@ -1,34 +1,33 @@
1
- import React, { useEffect, useRef } from 'react'
1
+ import React from 'react'
2
2
 
3
- import { autorun } from 'mobx'
4
3
  import { observer } from 'mobx-react'
5
4
 
6
5
  import { ConservationTrackResizeHandle } from './ResizeHandles.tsx'
7
- import { drawConservationBars } from './tracks/renderTracksSvg.ts'
6
+ import { useCanvasAutorun } from '../useCanvasAutorun.ts'
7
+ import {
8
+ barTrackValues,
9
+ drawConservationBars,
10
+ } from './tracks/renderTracksSvg.ts'
8
11
 
9
12
  import type { MsaViewModel } from '../model.ts'
10
13
  import type { BasicTrack } from '../types.ts'
11
14
 
12
15
  const ConservationBlock = observer(function ({
13
16
  model,
17
+ track,
14
18
  offsetX,
15
19
  trackHeight,
16
20
  }: {
17
21
  model: MsaViewModel
22
+ track: BasicTrack
18
23
  offsetX: number
19
24
  trackHeight: number
20
25
  }) {
21
26
  const { blockSize, scrollX, highResScaleFactor } = model
22
27
 
23
- const ref = useRef<HTMLCanvasElement>(null)
24
-
25
- useEffect(() => {
26
- const ctx = ref.current?.getContext('2d')
27
- if (!ctx) {
28
- return
29
- }
30
- return autorun(() => {
31
- const { blockSize, colWidth, highResScaleFactor, conservation } = model
28
+ const ref = useCanvasAutorun(
29
+ ctx => {
30
+ const { blockSize, colWidth, highResScaleFactor } = model
32
31
  ctx.resetTransform()
33
32
  ctx.scale(highResScaleFactor, highResScaleFactor)
34
33
  ctx.clearRect(0, 0, blockSize, trackHeight)
@@ -36,14 +35,16 @@ const ConservationBlock = observer(function ({
36
35
 
37
36
  drawConservationBars({
38
37
  ctx,
39
- conservation,
38
+ values: barTrackValues(model, track.model.id),
39
+ color: track.model.barColor ?? 'gray',
40
40
  colWidth,
41
41
  trackHeight,
42
42
  offsetX,
43
43
  blockSize,
44
44
  })
45
- })
46
- }, [model, offsetX, trackHeight])
45
+ },
46
+ [model, track, offsetX, trackHeight],
47
+ )
47
48
 
48
49
  return (
49
50
  <canvas
@@ -83,6 +84,7 @@ const ConservationTrack = observer(function ({
83
84
  <ConservationBlock
84
85
  key={bx}
85
86
  model={model}
87
+ track={track}
86
88
  offsetX={bx}
87
89
  trackHeight={trackHeight}
88
90
  />
@@ -28,6 +28,12 @@ interface MSAViewerProps {
28
28
  highlightColumns?: number[]
29
29
  /** row name to diff every other row against (matches render as ".") */
30
30
  relativeTo?: string
31
+ /** draw the phylogenetic tree (default true); false leaves a labels-only gutter */
32
+ drawTree?: boolean
33
+ /** fixed width (px) of the tree/label area */
34
+ treeAreaWidth?: number
35
+ /** auto-size the tree/label area to the labels (used when drawTree is false) */
36
+ autoTreeAreaWidth?: boolean
31
37
  }
32
38
 
33
39
  export default function MSAViewer({
@@ -43,6 +49,9 @@ export default function MSAViewer({
43
49
  rowHeight,
44
50
  highlightColumns,
45
51
  relativeTo,
52
+ drawTree,
53
+ treeAreaWidth,
54
+ autoTreeAreaWidth,
46
55
  }: MSAViewerProps) {
47
56
  // lazy initializer: the model is created exactly once from the initial props
48
57
  // (a stable MST instance — not a value safe to recompute, so not useMemo)
@@ -61,6 +70,9 @@ export default function MSAViewer({
61
70
  ...(rowHeight ? { rowHeight } : {}),
62
71
  ...(highlightColumns ? { highlightColumns } : {}),
63
72
  ...(relativeTo ? { relativeTo } : {}),
73
+ ...(drawTree !== undefined ? { drawTree } : {}),
74
+ ...(treeAreaWidth ? { treeAreaWidth } : {}),
75
+ ...(autoTreeAreaWidth ? { autoTreeAreaWidth } : {}),
64
76
  }),
65
77
  )
66
78
 
@@ -1,7 +1,7 @@
1
1
  import React, { useState } from 'react'
2
2
 
3
+ import { makeStyles } from '@jbrowse/core/util/tss-react'
3
4
  import { Button, TextField } from '@mui/material'
4
- import { makeStyles } from 'tss-react/mui'
5
5
 
6
6
  import Checkbox2 from './Checkbox2.tsx'
7
7
  import copy from '../vendor/copyToClipboard.ts'
@@ -1,9 +1,9 @@
1
- import React, { useEffect, useRef } from 'react'
1
+ import React from 'react'
2
2
 
3
- import { autorun } from 'mobx'
4
3
  import { observer } from 'mobx-react'
5
4
 
6
5
  import { setFontSize } from '../setFontSize.ts'
6
+ import { useCanvasAutorun } from '../useCanvasAutorun.ts'
7
7
  import { useColorContrast } from '../useColorContrast.ts'
8
8
  import { drawTextTrackContent } from './tracks/renderTracksSvg.ts'
9
9
 
@@ -31,14 +31,9 @@ const AnnotationBlock = observer(function ({
31
31
  } = track
32
32
 
33
33
  const colorScheme = customColorScheme ?? modelColorScheme
34
- const ref = useRef<HTMLCanvasElement>(null)
35
34
  const { contrastScheme } = useColorContrast(colorScheme)
36
- useEffect(() => {
37
- const ctx = ref.current?.getContext('2d')
38
- if (!ctx) {
39
- return
40
- }
41
- return autorun(() => {
35
+ const ref = useCanvasAutorun(
36
+ ctx => {
42
37
  const {
43
38
  blockSize,
44
39
  bgColor,
@@ -65,8 +60,9 @@ const AnnotationBlock = observer(function ({
65
60
  offsetX,
66
61
  blockSize,
67
62
  })
68
- })
69
- }, [model, offsetX, contrastScheme, colorScheme, data])
63
+ },
64
+ [model, offsetX, contrastScheme, colorScheme, data],
65
+ )
70
66
  return (
71
67
  <canvas
72
68
  ref={ref}
@@ -1,9 +1,9 @@
1
1
  import React, { lazy, useCallback, useRef, useState } from 'react'
2
2
 
3
+ import { makeStyles } from '@jbrowse/core/util/tss-react'
3
4
  import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'
4
5
  import { IconButton, Menu, MenuItem } from '@mui/material'
5
6
  import { observer } from 'mobx-react'
6
- import { makeStyles } from 'tss-react/mui'
7
7
 
8
8
  import { useWheelScroll } from '../useWheelScroll.ts'
9
9
 
@@ -1,9 +1,9 @@
1
1
  import React, { useState } from 'react'
2
2
 
3
3
  import { Dialog } from '@jbrowse/core/ui'
4
+ import { makeStyles } from '@jbrowse/core/util/tss-react'
4
5
  import { Button, DialogActions, DialogContent } from '@mui/material'
5
6
  import { observer } from 'mobx-react'
6
- import { makeStyles } from 'tss-react/mui'
7
7
 
8
8
  import copy from '../../vendor/copyToClipboard.ts'
9
9
 
@@ -13,6 +13,7 @@ const ColorSchemeMenu = observer(function ({ model }: { model: MsaViewModel }) {
13
13
  return (
14
14
  <CascadingMenuButton
15
15
  data-testid="color_scheme_menu"
16
+ tooltip="Color scheme"
16
17
  closeAfterItemClick
17
18
  anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
18
19
  transformOrigin={{ vertical: 'top', horizontal: 'left' }}
@@ -5,7 +5,6 @@ import Assignment from '@mui/icons-material/Assignment'
5
5
  import FolderOpen from '@mui/icons-material/FolderOpen'
6
6
  import Menu from '@mui/icons-material/Menu'
7
7
  import PhotoCamera from '@mui/icons-material/PhotoCamera'
8
- import Settings from '@mui/icons-material/Settings'
9
8
  import { observer } from 'mobx-react'
10
9
 
11
10
  import { getDomainMenu } from './getDomainsMenu.ts'
@@ -14,12 +13,12 @@ import type { MsaViewModel } from '../../model.ts'
14
13
 
15
14
  const MetadataDialog = lazy(() => import('../dialogs/MetadataDialog.tsx'))
16
15
  const ExportSVGDialog = lazy(() => import('../dialogs/ExportSVGDialog.tsx'))
17
- const SettingsDialog = lazy(() => import('../dialogs/SettingsDialog.tsx'))
18
16
 
19
17
  const FileMenu = observer(({ model }: { model: MsaViewModel }) => {
20
18
  return (
21
19
  <CascadingMenuButton
22
20
  data-testid="file_menu"
21
+ tooltip="File"
23
22
  menuItems={[
24
23
  {
25
24
  label: 'Return to import form',
@@ -41,19 +40,6 @@ const FileMenu = observer(({ model }: { model: MsaViewModel }) => {
41
40
  ])
42
41
  },
43
42
  },
44
- {
45
- label: 'More settings',
46
- icon: Settings,
47
- onClick: () => {
48
- model.queueDialog(onClose => [
49
- SettingsDialog,
50
- {
51
- model,
52
- onClose,
53
- },
54
- ])
55
- },
56
- },
57
43
  {
58
44
  label: 'Domain settings',
59
45
  type: 'subMenu',
@@ -2,7 +2,7 @@ import React, { lazy, useEffect } from 'react'
2
2
 
3
3
  import useMeasure from '@jbrowse/core/util/useMeasure'
4
4
  import Help from '@mui/icons-material/Help'
5
- import { IconButton } from '@mui/material'
5
+ import { IconButton, Tooltip } from '@mui/material'
6
6
  import { observer } from 'mobx-react'
7
7
 
8
8
  import ColorSchemeMenu from './ColorSchemeMenu.tsx'
@@ -40,13 +40,16 @@ const Header = observer(function ({ model }: { model: MsaViewModel }) {
40
40
  <HeaderInfoArea model={model} />
41
41
  <Spacer />
42
42
  <HeaderStatusArea model={model} />
43
- <IconButton
44
- onClick={() => {
45
- model.queueDialog(onClose => [AboutDialog, { onClose }])
46
- }}
47
- >
48
- <Help />
49
- </IconButton>
43
+ <Tooltip title="About">
44
+ <IconButton
45
+ aria-label="About"
46
+ onClick={() => {
47
+ model.queueDialog(onClose => [AboutDialog, { onClose }])
48
+ }}
49
+ >
50
+ <Help />
51
+ </IconButton>
52
+ </Tooltip>
50
53
  </div>
51
54
  )
52
55
  })
@@ -1,8 +1,8 @@
1
1
  import React from 'react'
2
2
 
3
+ import { makeStyles } from '@jbrowse/core/util/tss-react'
3
4
  import { Typography } from '@mui/material'
4
5
  import { observer } from 'mobx-react'
5
- import { makeStyles } from 'tss-react/mui'
6
6
 
7
7
  import type { MsaViewModel } from '../../model.ts'
8
8
 
@@ -1,9 +1,9 @@
1
1
  import React from 'react'
2
2
 
3
3
  import { LoadingEllipses } from '@jbrowse/core/ui'
4
+ import { makeStyles } from '@jbrowse/core/util/tss-react'
4
5
  import { Button, Typography } from '@mui/material'
5
6
  import { observer } from 'mobx-react'
6
- import { makeStyles } from 'tss-react/mui'
7
7
 
8
8
  import type { MsaViewModel } from '../../model.ts'
9
9
 
@@ -12,6 +12,7 @@ const MSASettingsMenu = observer(function ({ model }: { model: MsaViewModel }) {
12
12
  return (
13
13
  <CascadingMenuButton
14
14
  data-testid="msa_settings_menu"
15
+ tooltip="Alignment settings"
15
16
  closeAfterItemClick={false}
16
17
  anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
17
18
  transformOrigin={{ vertical: 'top', horizontal: 'left' }}
@@ -17,6 +17,7 @@ const TreeSettingsMenu = observer(function ({
17
17
  return (
18
18
  <CascadingMenuButton
19
19
  data-testid="tree_settings_menu"
20
+ tooltip="Tree settings"
20
21
  closeAfterItemClick={false}
21
22
  anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
22
23
  transformOrigin={{ vertical: 'top', horizontal: 'left' }}
@@ -1,9 +1,9 @@
1
1
  import React from 'react'
2
2
 
3
+ import Mouse from '@mui/icons-material/Mouse'
3
4
  import ZoomIn from '@mui/icons-material/ZoomIn'
4
- import ZoomInMap from '@mui/icons-material/ZoomInMap'
5
5
  import ZoomOut from '@mui/icons-material/ZoomOut'
6
- import { IconButton, ToggleButton } from '@mui/material'
6
+ import { IconButton, ToggleButton, Tooltip } from '@mui/material'
7
7
  import { observer } from 'mobx-react'
8
8
 
9
9
  import type { MsaViewModel } from '../../model.ts'
@@ -15,23 +15,30 @@ const ZoomControls = observer(function ZoomControls({
15
15
  }) {
16
16
  return (
17
17
  <>
18
- <IconButton
19
- onClick={() => {
20
- model.zoomIn()
21
- }}
22
- >
23
- <ZoomIn />
24
- </IconButton>
25
- <IconButton
26
- onClick={() => {
27
- model.zoomOut()
28
- }}
29
- >
30
- <ZoomOut />
31
- </IconButton>
18
+ <Tooltip title="Zoom in">
19
+ <IconButton
20
+ aria-label="Zoom in"
21
+ onClick={() => {
22
+ model.zoomIn()
23
+ }}
24
+ >
25
+ <ZoomIn />
26
+ </IconButton>
27
+ </Tooltip>
28
+ <Tooltip title="Zoom out">
29
+ <IconButton
30
+ aria-label="Zoom out"
31
+ onClick={() => {
32
+ model.zoomOut()
33
+ }}
34
+ >
35
+ <ZoomOut />
36
+ </IconButton>
37
+ </Tooltip>
32
38
  <ToggleButton
33
39
  value="scrollZoom"
34
40
  selected={model.scrollZoom}
41
+ aria-label="Toggle scroll-to-zoom"
35
42
  title="Toggle scroll-to-zoom (zoom on plain mouse wheel; ctrl+wheel always zooms)"
36
43
  size="small"
37
44
  sx={{ border: 'none' }}
@@ -39,7 +46,7 @@ const ZoomControls = observer(function ZoomControls({
39
46
  model.setScrollZoom(!model.scrollZoom)
40
47
  }}
41
48
  >
42
- <ZoomInMap />
49
+ <Mouse />
43
50
  </ToggleButton>
44
51
  </>
45
52
  )
@@ -10,6 +10,7 @@ import type { MsaViewModel } from '../../model.ts'
10
10
  const ZoomMenu = observer(function ({ model }: { model: MsaViewModel }) {
11
11
  return (
12
12
  <CascadingMenuButton
13
+ tooltip="Fit / zoom options"
13
14
  menuItems={[
14
15
  {
15
16
  label: 'Fit both vertically/horizontally',
@@ -37,16 +38,6 @@ const ZoomMenu = observer(function ({ model }: { model: MsaViewModel }) {
37
38
  model.resetZoom()
38
39
  },
39
40
  },
40
- {
41
- label: 'Scroll zoom',
42
- type: 'checkbox',
43
- checked: model.scrollZoom,
44
- onClick: () => {
45
- model.setScrollZoom(!model.scrollZoom)
46
- },
47
- helpText:
48
- 'When enabled, scrolling the mouse wheel zooms in and out without holding ctrl. Hold shift to pan instead. Ctrl/⌘+wheel always zooms.',
49
- },
50
41
  ]}
51
42
  >
52
43
  <MoreVert />
@@ -2,8 +2,16 @@ import type { MsaViewModel } from '../../model.ts'
2
2
  import type { MenuItem } from '@jbrowse/core/ui/Menu'
3
3
 
4
4
  export function msaSettingsMenuItems(model: MsaViewModel): MenuItem[] {
5
- const { drawMsaLetters, hideGaps, bgColor } = model
5
+ const { drawMsaLetters, hideGaps, bgColor, showColumnStats } = model
6
6
  return [
7
+ {
8
+ label: 'Show column statistics on hover',
9
+ type: 'checkbox',
10
+ checked: showColumnStats,
11
+ onClick: () => {
12
+ model.setShowColumnStats(!showColumnStats)
13
+ },
14
+ },
7
15
  {
8
16
  label: 'Draw letters',
9
17
  type: 'checkbox',
@@ -0,0 +1,54 @@
1
+ import React from 'react'
2
+
3
+ import { observer } from 'mobx-react'
4
+
5
+ import type { MsaViewModel } from '../../model.ts'
6
+
7
+ const pct = (n: number) => `${Math.round(n * 100)}%`
8
+
9
+ const ColumnStats = observer(function ({ model }: { model: MsaViewModel }) {
10
+ const stats = model.mouseOverColumnStats
11
+ if (!stats) {
12
+ return null
13
+ }
14
+ const {
15
+ col,
16
+ total,
17
+ conservation,
18
+ propertyConservation,
19
+ consensusLetter,
20
+ consensusFraction,
21
+ gapFraction,
22
+ distribution,
23
+ } = stats
24
+ const top = distribution.slice(0, 6)
25
+ return (
26
+ <div>
27
+ <b>Column {col + 1}</b> ({total} seqs)
28
+ <br />
29
+ Consensus: {consensusLetter || '-'} ({pct(consensusFraction)})
30
+ <br />
31
+ Conservation: {conservation.toFixed(2)}
32
+ {propertyConservation === undefined ? null : (
33
+ <>
34
+ <br />
35
+ Property conservation: {propertyConservation.toFixed(2)}
36
+ </>
37
+ )}
38
+ <br />
39
+ Gaps: {pct(gapFraction)}
40
+ {top.length > 0 ? (
41
+ <>
42
+ <br />
43
+ {top.map(([letter, count]) => (
44
+ <span key={letter} style={{ marginRight: 8 }}>
45
+ {letter} {pct(count / total)}
46
+ </span>
47
+ ))}
48
+ </>
49
+ ) : null}
50
+ </div>
51
+ )
52
+ })
53
+
54
+ export default ColumnStats
@@ -9,7 +9,11 @@ import type { MsaViewModel } from '../../model.ts'
9
9
 
10
10
  const DomainLegend = observer(function ({ model }: { model: MsaViewModel }) {
11
11
  const [expanded, setExpanded] = useState(true)
12
- const { actuallyShowDomains, visibleDomainTypes: visible, fillPalette } = model
12
+ const {
13
+ actuallyShowDomains,
14
+ visibleDomainTypes: visible,
15
+ fillPalette,
16
+ } = model
13
17
  const ExpandIcon = expanded ? ExpandLess : ExpandMore
14
18
 
15
19
  return actuallyShowDomains && visible.length > 0 ? (