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.
- package/bundle/index.js +173 -162
- package/bundle/index.js.map +4 -4
- package/dist/components/ConservationTrack.js +20 -26
- package/dist/components/ConservationTrack.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +7 -1
- package/dist/components/MSAViewer.js +4 -1
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/SequenceTextArea.js +1 -1
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/TextTrack.js +20 -27
- package/dist/components/TextTrack.js.map +1 -1
- package/dist/components/Track.js +1 -1
- package/dist/components/Track.js.map +1 -1
- package/dist/components/dialogs/TrackInfoDialog.js +1 -1
- package/dist/components/dialogs/TrackInfoDialog.js.map +1 -1
- package/dist/components/header/ColorSchemeMenu.js +1 -1
- package/dist/components/header/ColorSchemeMenu.js.map +1 -1
- package/dist/components/header/FileMenu.js +1 -16
- package/dist/components/header/FileMenu.js.map +1 -1
- package/dist/components/header/Header.js +6 -5
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/HeaderInfoArea.js +1 -1
- package/dist/components/header/HeaderInfoArea.js.map +1 -1
- package/dist/components/header/HeaderStatusArea.js +1 -1
- package/dist/components/header/HeaderStatusArea.js.map +1 -1
- package/dist/components/header/MSASettingsMenu.js +1 -1
- package/dist/components/header/MSASettingsMenu.js.map +1 -1
- package/dist/components/header/TreeSettingsMenu.js +1 -1
- package/dist/components/header/TreeSettingsMenu.js.map +1 -1
- package/dist/components/header/ZoomControls.js +14 -12
- package/dist/components/header/ZoomControls.js.map +1 -1
- package/dist/components/header/ZoomMenu.js +1 -10
- package/dist/components/header/ZoomMenu.js.map +1 -1
- package/dist/components/header/settingsMenuItems.js +9 -1
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/{header/HeaderMenu.d.ts → msa/ColumnStats.d.ts} +3 -3
- package/dist/components/msa/ColumnStats.js +42 -0
- package/dist/components/msa/ColumnStats.js.map +1 -0
- package/dist/components/msa/DomainLegend.js +1 -1
- package/dist/components/msa/DomainLegend.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +39 -39
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/MSAMouseoverCanvas.js +6 -12
- package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +3 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +1 -2
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/overlayColors.d.ts +3 -0
- package/dist/components/overlayColors.js +11 -0
- package/dist/components/overlayColors.js.map +1 -0
- package/dist/components/tracks/renderTracksSvg.d.ts +6 -3
- package/dist/components/tracks/renderTracksSvg.js +17 -8
- package/dist/components/tracks/renderTracksSvg.js.map +1 -1
- package/dist/components/tree/TreeCanvas.js +31 -38
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +12 -47
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/clickMap.d.ts +24 -0
- package/dist/components/tree/clickMap.js +33 -0
- package/dist/components/tree/clickMap.js.map +1 -0
- package/dist/components/tree/renderTreeCanvas.d.ts +1 -21
- package/dist/components/tree/renderTreeCanvas.js +8 -7
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/fetchUtils.d.ts +2 -1
- package/dist/fetchUtils.js +3 -2
- package/dist/fetchUtils.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/model/msaModel.d.ts +25 -2
- package/dist/model/msaModel.js +15 -3
- package/dist/model/msaModel.js.map +1 -1
- package/dist/model/treeModel.d.ts +46 -5
- package/dist/model/treeModel.js +20 -7
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +545 -132
- package/dist/model.js +101 -75
- package/dist/model.js.map +1 -1
- package/dist/propertyConservation.d.ts +7 -0
- package/dist/propertyConservation.js +66 -0
- package/dist/propertyConservation.js.map +1 -0
- package/dist/types.d.ts +4 -1
- package/dist/useCanvasAutorun.d.ts +2 -0
- package/dist/useCanvasAutorun.js +23 -0
- package/dist/useCanvasAutorun.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -6
- package/src/colorSchemes.docs.test.ts +57 -0
- package/src/columnStats.test.ts +94 -0
- package/src/components/ConservationTrack.tsx +17 -15
- package/src/components/MSAViewer.tsx +12 -0
- package/src/components/SequenceTextArea.tsx +1 -1
- package/src/components/TextTrack.tsx +7 -11
- package/src/components/Track.tsx +1 -1
- package/src/components/dialogs/TrackInfoDialog.tsx +1 -1
- package/src/components/header/ColorSchemeMenu.tsx +1 -0
- package/src/components/header/FileMenu.tsx +1 -15
- package/src/components/header/Header.tsx +11 -8
- package/src/components/header/HeaderInfoArea.tsx +1 -1
- package/src/components/header/HeaderStatusArea.tsx +1 -1
- package/src/components/header/MSASettingsMenu.tsx +1 -0
- package/src/components/header/TreeSettingsMenu.tsx +1 -0
- package/src/components/header/ZoomControls.tsx +24 -17
- package/src/components/header/ZoomMenu.tsx +1 -10
- package/src/components/header/settingsMenuItems.ts +9 -1
- package/src/components/msa/ColumnStats.tsx +54 -0
- package/src/components/msa/DomainLegend.tsx +5 -1
- package/src/components/msa/MSACanvasBlock.tsx +29 -26
- package/src/components/msa/MSAMouseoverCanvas.tsx +10 -13
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +3 -1
- package/src/components/msa/renderMSAMouseover.ts +2 -2
- package/src/components/overlayColors.ts +12 -0
- package/src/components/tracks/renderTracksSvg.ts +22 -8
- package/src/components/tree/TreeCanvas.tsx +47 -54
- package/src/components/tree/TreeCanvasBlock.tsx +10 -68
- package/src/components/tree/clickMap.ts +55 -0
- package/src/components/tree/renderTreeCanvas.ts +10 -34
- package/src/constants.ts +1 -0
- package/src/fetchUtils.ts +6 -3
- package/src/index.ts +1 -1
- package/src/model/msaModel.ts +24 -3
- package/src/model/treeModel.ts +27 -7
- package/src/model.ts +122 -117
- package/src/propertyConservation.test.ts +47 -0
- package/src/propertyConservation.ts +73 -0
- package/src/stripDefaultSnapshot.test.ts +57 -0
- package/src/types.ts +7 -1
- package/src/useCanvasAutorun.ts +28 -0
- package/src/version.ts +1 -1
- package/dist/components/dialogs/SettingsDialog.d.ts +0 -7
- package/dist/components/dialogs/SettingsDialog.js +0 -100
- package/dist/components/dialogs/SettingsDialog.js.map +0 -1
- package/dist/components/header/HeaderMenu.js +0 -27
- package/dist/components/header/HeaderMenu.js.map +0 -1
- package/src/components/dialogs/SettingsDialog.tsx +0 -234
- 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.
|
|
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.
|
|
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.
|
|
47
|
-
"msa-parsers": "5.
|
|
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
|
|
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 {
|
|
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 =
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
63
|
+
},
|
|
64
|
+
[model, offsetX, contrastScheme, colorScheme, data],
|
|
65
|
+
)
|
|
70
66
|
return (
|
|
71
67
|
<canvas
|
|
72
68
|
ref={ref}
|
package/src/components/Track.tsx
CHANGED
|
@@ -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
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
<
|
|
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 {
|
|
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 ? (
|