react-msaview 8.1.0 → 8.3.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.
- package/bundle/index.js +104 -117
- package/bundle/index.js.map +4 -4
- package/dist/columnCounts.d.ts +4 -3
- package/dist/columnCounts.js +11 -6
- package/dist/columnCounts.js.map +1 -1
- package/dist/components/CopyButton.d.ts +1 -1
- package/dist/components/CopyButton.js +1 -1
- package/dist/components/CopyButton.js.map +1 -1
- package/dist/components/DragHandle.js +14 -2
- package/dist/components/DragHandle.js.map +1 -1
- package/dist/components/Loading.js +10 -11
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +12 -1
- package/dist/components/MSAViewer.js +12 -1
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/SequenceTextArea.d.ts +11 -0
- package/dist/components/SequenceTextArea.js +40 -8
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/StatusMessage.d.ts +12 -0
- package/dist/components/StatusMessage.js +43 -0
- package/dist/components/StatusMessage.js.map +1 -0
- package/dist/components/Track.js +3 -3
- package/dist/components/Track.js.map +1 -1
- package/dist/components/VerticalScrollbar.js +6 -1
- package/dist/components/VerticalScrollbar.js.map +1 -1
- package/dist/components/dialogs/ExportSVGDialog.d.ts +3 -2
- package/dist/components/dialogs/ExportSVGDialog.js +4 -2
- package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
- package/dist/components/dialogs/FeatureDialog.js +15 -10
- package/dist/components/dialogs/FeatureDialog.js.map +1 -1
- package/dist/components/getVisibleLeaves.d.ts +1 -1
- package/dist/components/header/FileMenu.js +9 -0
- package/dist/components/header/FileMenu.js.map +1 -1
- package/dist/components/header/GappynessSlider.js +15 -3
- package/dist/components/header/GappynessSlider.js.map +1 -1
- package/dist/components/header/Header.js +6 -7
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/{HeaderStatusArea.d.ts → RowSearch.d.ts} +2 -2
- package/dist/components/header/RowSearch.js +15 -0
- package/dist/components/header/RowSearch.js.map +1 -0
- package/dist/components/header/revealRow.d.ts +3 -0
- package/dist/components/header/revealRow.js +15 -0
- package/dist/components/header/revealRow.js.map +1 -0
- package/dist/components/header/settingsMenuItems.js +57 -1
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/import/ImportForm.js +10 -4
- package/dist/components/import/ImportForm.js.map +1 -1
- package/dist/components/import/ImportFormExamples.js +19 -27
- package/dist/components/import/ImportFormExamples.js.map +1 -1
- package/dist/components/minimap/Minimap.js +6 -1
- package/dist/components/minimap/Minimap.js.map +1 -1
- package/dist/components/msa/AnnotationLegend.js +11 -5
- package/dist/components/msa/AnnotationLegend.js.map +1 -1
- package/dist/components/msa/MSACanvas.js +6 -1
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +3 -7
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/msaKeys.d.ts +5 -0
- package/dist/components/msa/msaKeys.js +35 -0
- package/dist/components/msa/msaKeys.js.map +1 -0
- package/dist/components/msa/useMsaBlockMouse.d.ts +2 -4
- package/dist/components/msa/useMsaBlockMouse.js +18 -5
- package/dist/components/msa/useMsaBlockMouse.js.map +1 -1
- package/dist/components/rowpanels/RowPanelBlock.js +10 -14
- package/dist/components/rowpanels/RowPanelBlock.js.map +1 -1
- package/dist/components/tracks/drawTracks.d.ts +1 -0
- package/dist/components/tracks/drawTracks.js +14 -2
- package/dist/components/tracks/drawTracks.js.map +1 -1
- package/dist/components/tracks/useTrackHover.d.ts +1 -5
- package/dist/components/tracks/useTrackHover.js +5 -5
- package/dist/components/tracks/useTrackHover.js.map +1 -1
- package/dist/components/tree/ScaleBarSVG.d.ts +14 -0
- package/dist/components/tree/ScaleBarSVG.js +11 -0
- package/dist/components/tree/ScaleBarSVG.js.map +1 -0
- package/dist/components/tree/TreeCanvas.js +8 -26
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +11 -20
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/{TreeBranchMenu.d.ts → TreeMenu.d.ts} +5 -4
- package/dist/components/tree/TreeMenu.js +48 -0
- package/dist/components/tree/TreeMenu.js.map +1 -0
- package/dist/components/tree/TreeRuler.js +3 -3
- package/dist/components/tree/TreeRuler.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.js +19 -9
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/tree/renderTreeMouseover.d.ts +5 -0
- package/dist/components/tree/renderTreeMouseover.js +20 -0
- package/dist/components/tree/renderTreeMouseover.js.map +1 -0
- package/dist/components/tree/useTreeHover.d.ts +3 -8
- package/dist/components/tree/useTreeHover.js +8 -10
- package/dist/components/tree/useTreeHover.js.map +1 -1
- package/dist/components/useHoverAnchor.d.ts +19 -0
- package/dist/components/useHoverAnchor.js +18 -0
- package/dist/components/useHoverAnchor.js.map +1 -0
- package/dist/constants.d.ts +4 -1
- package/dist/constants.js +12 -3
- package/dist/constants.js.map +1 -1
- package/dist/expandSpec.d.ts +46 -0
- package/dist/expandSpec.js +127 -0
- package/dist/expandSpec.js.map +1 -0
- package/dist/exportFileName.d.ts +3 -3
- package/dist/exportFileName.js +4 -4
- package/dist/exportFileName.js.map +1 -1
- package/dist/hierarchy.d.ts +51 -0
- package/dist/hierarchy.js +125 -0
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/model/treeModel.d.ts +12 -0
- package/dist/model/treeModel.js +14 -1
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +188 -47
- package/dist/model.js +482 -192
- package/dist/model.js.map +1 -1
- package/dist/renderToSvg.js +2 -4
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rerootTree.d.ts +33 -0
- package/dist/rerootTree.js +144 -0
- package/dist/rerootTree.js.map +1 -0
- package/dist/svgTestUtil.d.ts +114 -20
- package/dist/types.d.ts +25 -12
- package/dist/useMsaSvgFigure.d.ts +2 -1
- package/dist/useMsaSvgFigure.js +11 -4
- package/dist/useMsaSvgFigure.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/writeNewick.d.ts +8 -0
- package/dist/writeNewick.js +34 -0
- package/dist/writeNewick.js.map +1 -0
- package/package.json +3 -3
- package/src/columnCounts.ts +14 -6
- package/src/components/CopyButton.tsx +2 -2
- package/src/components/DragHandle.tsx +21 -1
- package/src/components/Loading.tsx +14 -30
- package/src/components/MSAViewer.tsx +26 -0
- package/src/components/SequenceTextArea.tsx +54 -8
- package/src/components/StatusMessage.tsx +70 -0
- package/src/components/Track.tsx +8 -4
- package/src/components/VerticalScrollbar.tsx +7 -0
- package/src/components/dialogs/ExportSVGDialog.tsx +5 -2
- package/src/components/dialogs/FeatureDialog.tsx +59 -38
- package/src/components/header/FileMenu.tsx +9 -0
- package/src/components/header/GappynessSlider.tsx +17 -4
- package/src/components/header/Header.tsx +7 -8
- package/src/components/header/RowSearch.tsx +34 -0
- package/src/components/header/revealRow.ts +17 -0
- package/src/components/header/settingsMenuItems.ts +66 -0
- package/src/components/import/ImportForm.tsx +14 -7
- package/src/components/import/ImportFormExamples.tsx +20 -28
- package/src/components/minimap/Minimap.tsx +7 -0
- package/src/components/msa/AnnotationLegend.tsx +12 -6
- package/src/components/msa/MSACanvas.tsx +8 -0
- package/src/components/msa/MSACanvasBlock.tsx +5 -11
- package/src/components/msa/msaKeys.ts +39 -0
- package/src/components/msa/useMsaBlockMouse.ts +23 -7
- package/src/components/rowpanels/RowPanelBlock.tsx +16 -23
- package/src/components/tracks/drawTracks.ts +15 -2
- package/src/components/tracks/useTrackHover.ts +5 -5
- package/src/components/tree/ScaleBarSVG.tsx +30 -0
- package/src/components/tree/TreeCanvas.tsx +9 -40
- package/src/components/tree/TreeCanvasBlock.tsx +19 -39
- package/src/components/tree/TreeMenu.tsx +129 -0
- package/src/components/tree/TreeRuler.tsx +7 -13
- package/src/components/tree/renderTreeCanvas.ts +20 -8
- package/src/components/tree/renderTreeMouseover.ts +41 -0
- package/src/components/tree/useTreeHover.ts +8 -17
- package/src/components/useHoverAnchor.ts +31 -0
- package/src/constants.ts +14 -3
- package/src/expandSpec.ts +196 -0
- package/src/exportFileName.ts +4 -3
- package/src/hierarchy.ts +151 -4
- package/src/index.ts +11 -1
- package/src/model/treeModel.ts +22 -0
- package/src/model.ts +588 -221
- package/src/renderToSvg.tsx +6 -9
- package/src/rerootTree.ts +172 -0
- package/src/types.ts +34 -12
- package/src/useMsaSvgFigure.ts +11 -4
- package/src/version.ts +1 -1
- package/src/writeNewick.ts +38 -0
- package/dist/components/PortalTooltip.d.ts +0 -11
- package/dist/components/PortalTooltip.js +0 -29
- package/dist/components/PortalTooltip.js.map +0 -1
- package/dist/components/header/HeaderStatusArea.js +0 -21
- package/dist/components/header/HeaderStatusArea.js.map +0 -1
- package/dist/components/tree/TreeBranchMenu.js +0 -29
- package/dist/components/tree/TreeBranchMenu.js.map +0 -1
- package/dist/components/tree/TreeNodeMenu.d.ts +0 -13
- package/dist/components/tree/TreeNodeMenu.js +0 -41
- package/dist/components/tree/TreeNodeMenu.js.map +0 -1
- package/src/components/PortalTooltip.tsx +0 -47
- package/src/components/header/HeaderStatusArea.tsx +0 -32
- package/src/components/tree/TreeBranchMenu.tsx +0 -68
- package/src/components/tree/TreeNodeMenu.tsx +0 -95
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
Typography,
|
|
16
16
|
useTheme,
|
|
17
17
|
} from '@mui/material'
|
|
18
|
+
import { observer } from 'mobx-react'
|
|
18
19
|
|
|
19
20
|
import Checkbox2 from '../Checkbox2.tsx'
|
|
20
21
|
|
|
@@ -30,7 +31,7 @@ function afterPaint(fn: () => void) {
|
|
|
30
31
|
})
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
const ExportSVGDialog = observer(function ({
|
|
34
35
|
model,
|
|
35
36
|
onClose,
|
|
36
37
|
}: {
|
|
@@ -181,4 +182,6 @@ export default function ExportSVGDialog({
|
|
|
181
182
|
</DialogActions>
|
|
182
183
|
</Dialog>
|
|
183
184
|
)
|
|
184
|
-
}
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
export default ExportSVGDialog
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
2
|
|
|
3
3
|
import { Dialog } from '@jbrowse/core/ui'
|
|
4
4
|
import {
|
|
@@ -10,18 +10,28 @@ import {
|
|
|
10
10
|
TableCell,
|
|
11
11
|
TableHead,
|
|
12
12
|
TableRow,
|
|
13
|
+
TextField,
|
|
13
14
|
} from '@mui/material'
|
|
14
15
|
import { observer } from 'mobx-react'
|
|
15
16
|
|
|
16
17
|
import type { MsaViewModel } from '../../model.ts'
|
|
18
|
+
import type { Annotation } from '../../types.ts'
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
function matches(type: Annotation, query: string) {
|
|
21
|
+
return [type.accession, type.name, type.description].some(field =>
|
|
22
|
+
field?.toLowerCase().includes(query),
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const Toggles = observer(function ({
|
|
27
|
+
model,
|
|
28
|
+
accessions,
|
|
29
|
+
}: {
|
|
30
|
+
model: MsaViewModel
|
|
31
|
+
accessions: string[]
|
|
32
|
+
}) {
|
|
23
33
|
const setAll = (shown: boolean) => {
|
|
24
|
-
for (const accession of
|
|
34
|
+
for (const accession of accessions) {
|
|
25
35
|
model.setFilter(accession, shown)
|
|
26
36
|
}
|
|
27
37
|
}
|
|
@@ -48,6 +58,11 @@ const Toggles = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
48
58
|
|
|
49
59
|
const FeatureTable = observer(function ({ model }: { model: MsaViewModel }) {
|
|
50
60
|
const { annotationTypes, annotations, featureColors, fillPalette } = model
|
|
61
|
+
const [query, setQuery] = useState('')
|
|
62
|
+
const needle = query.trim().toLowerCase()
|
|
63
|
+
const types = [...annotationTypes.values()].filter(
|
|
64
|
+
type => !needle || matches(type, needle),
|
|
65
|
+
)
|
|
51
66
|
const counts = new Map<string, number>()
|
|
52
67
|
const swatches = new Map<string, string>()
|
|
53
68
|
for (const annot of annotations) {
|
|
@@ -62,7 +77,15 @@ const FeatureTable = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
62
77
|
}
|
|
63
78
|
return (
|
|
64
79
|
<>
|
|
65
|
-
<
|
|
80
|
+
<TextField
|
|
81
|
+
label="Filter"
|
|
82
|
+
size="small"
|
|
83
|
+
value={query}
|
|
84
|
+
onChange={event => {
|
|
85
|
+
setQuery(event.target.value)
|
|
86
|
+
}}
|
|
87
|
+
/>
|
|
88
|
+
<Toggles model={model} accessions={types.map(t => t.accession)} />
|
|
66
89
|
<Table size="small">
|
|
67
90
|
<TableHead>
|
|
68
91
|
<TableRow>
|
|
@@ -75,36 +98,34 @@ const FeatureTable = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
75
98
|
</TableRow>
|
|
76
99
|
</TableHead>
|
|
77
100
|
<TableBody>
|
|
78
|
-
{
|
|
79
|
-
|
|
80
|
-
<
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
),
|
|
107
|
-
)}
|
|
101
|
+
{types.map(({ accession, name, description }) => (
|
|
102
|
+
<TableRow key={accession}>
|
|
103
|
+
<TableCell padding="checkbox">
|
|
104
|
+
<Checkbox
|
|
105
|
+
checked={!model.turnedOffFeatures.get(accession)}
|
|
106
|
+
onChange={() => {
|
|
107
|
+
model.setFilter(
|
|
108
|
+
accession,
|
|
109
|
+
!!model.turnedOffFeatures.get(accession),
|
|
110
|
+
)
|
|
111
|
+
}}
|
|
112
|
+
/>
|
|
113
|
+
</TableCell>
|
|
114
|
+
<TableCell>
|
|
115
|
+
<div
|
|
116
|
+
style={{
|
|
117
|
+
width: 20,
|
|
118
|
+
height: 20,
|
|
119
|
+
background: swatches.get(accession),
|
|
120
|
+
}}
|
|
121
|
+
/>
|
|
122
|
+
</TableCell>
|
|
123
|
+
<TableCell>{accession}</TableCell>
|
|
124
|
+
<TableCell>{name}</TableCell>
|
|
125
|
+
<TableCell>{counts.get(accession)}</TableCell>
|
|
126
|
+
<TableCell>{description}</TableCell>
|
|
127
|
+
</TableRow>
|
|
128
|
+
))}
|
|
108
129
|
</TableBody>
|
|
109
130
|
</Table>
|
|
110
131
|
</>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { lazy } from 'react'
|
|
2
2
|
|
|
3
3
|
import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton'
|
|
4
|
+
import AccountTree from '@mui/icons-material/AccountTree'
|
|
4
5
|
import Assignment from '@mui/icons-material/Assignment'
|
|
5
6
|
import FolderOpen from '@mui/icons-material/FolderOpen'
|
|
6
7
|
import Menu from '@mui/icons-material/Menu'
|
|
@@ -47,6 +48,14 @@ const FileMenu = observer(({ model }: { model: MsaViewModel }) => {
|
|
|
47
48
|
model,
|
|
48
49
|
}),
|
|
49
50
|
},
|
|
51
|
+
{
|
|
52
|
+
label: 'Export tree (Newick)',
|
|
53
|
+
icon: AccountTree,
|
|
54
|
+
disabled: !!model.tree.noTree,
|
|
55
|
+
onClick: () => {
|
|
56
|
+
model.exportNewick()
|
|
57
|
+
},
|
|
58
|
+
},
|
|
50
59
|
{
|
|
51
60
|
label: 'Export SVG',
|
|
52
61
|
icon: PhotoCamera,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
2
|
|
|
3
3
|
import { Slider, Typography } from '@mui/material'
|
|
4
4
|
import { observer } from 'mobx-react'
|
|
@@ -11,6 +11,8 @@ const GappynessSlider = observer(function GappynessSlider({
|
|
|
11
11
|
model: MsaViewModel
|
|
12
12
|
}) {
|
|
13
13
|
const { hideGaps, allowedGappyness } = model
|
|
14
|
+
const [dragValue, setDragValue] = useState<number>()
|
|
15
|
+
const value = dragValue ?? allowedGappyness
|
|
14
16
|
if (!hideGaps) {
|
|
15
17
|
return null
|
|
16
18
|
}
|
|
@@ -20,7 +22,7 @@ const GappynessSlider = observer(function GappynessSlider({
|
|
|
20
22
|
least ceil(allowedGappyness% of the rows), so at 100 it hides the
|
|
21
23
|
columns that are entirely gaps rather than nothing */}
|
|
22
24
|
<Typography style={{ whiteSpace: 'nowrap' }}>
|
|
23
|
-
Hide columns w/ ≥{
|
|
25
|
+
Hide columns w/ ≥{value}% gaps
|
|
24
26
|
</Typography>
|
|
25
27
|
<Slider
|
|
26
28
|
// In JBrowse other MUI Sliders share the page, so `.MuiSlider-thumb`
|
|
@@ -30,13 +32,24 @@ const GappynessSlider = observer(function GappynessSlider({
|
|
|
30
32
|
//
|
|
31
33
|
// Not `slotProps={{ input: ... }}`: that widens `onChange`'s `val` to
|
|
32
34
|
// `number | number[]`, which setAllowedGappyness rejects.
|
|
35
|
+
//
|
|
36
|
+
// The thumb follows local state while dragging and the model takes the
|
|
37
|
+
// value on release, since each write recomputes the gap columns over
|
|
38
|
+
// the whole alignment.
|
|
39
|
+
//
|
|
40
|
+
// The thumb centers on the track's ends, so the margin keeps its
|
|
41
|
+
// overhang off the label and the row search beside it.
|
|
33
42
|
data-testid="gappyness_slider"
|
|
34
|
-
style={{ width: 100 }}
|
|
43
|
+
style={{ width: 100, marginInline: 10 }}
|
|
35
44
|
min={1}
|
|
36
45
|
max={100}
|
|
37
|
-
value={
|
|
46
|
+
value={value}
|
|
38
47
|
onChange={(_, val) => {
|
|
48
|
+
setDragValue(val)
|
|
49
|
+
}}
|
|
50
|
+
onChangeCommitted={(_, val) => {
|
|
39
51
|
model.setAllowedGappyness(val)
|
|
52
|
+
setDragValue(undefined)
|
|
40
53
|
}}
|
|
41
54
|
/>
|
|
42
55
|
</div>
|
|
@@ -5,14 +5,15 @@ import Help from '@mui/icons-material/Help'
|
|
|
5
5
|
import { IconButton, Tooltip } from '@mui/material'
|
|
6
6
|
import { observer } from 'mobx-react'
|
|
7
7
|
|
|
8
|
+
import StatusMessage from '../StatusMessage.tsx'
|
|
8
9
|
import ColorSchemeMenu from './ColorSchemeMenu.tsx'
|
|
9
10
|
import FileMenu from './FileMenu.tsx'
|
|
10
11
|
import GappynessSlider from './GappynessSlider.tsx'
|
|
11
12
|
import HeaderInfoArea from './HeaderInfoArea.tsx'
|
|
12
|
-
import HeaderStatusArea from './HeaderStatusArea.tsx'
|
|
13
13
|
import LoadWarnings from './LoadWarnings.tsx'
|
|
14
14
|
import MSASettingsMenu from './MSASettingsMenu.tsx'
|
|
15
15
|
import MultiAlignmentSelector from './MultiAlignmentSelector.tsx'
|
|
16
|
+
import RowSearch from './RowSearch.tsx'
|
|
16
17
|
import TreeSettingsMenu from './TreeSettingsMenu.tsx'
|
|
17
18
|
import UnshareableDataWarning from './UnshareableDataWarning.tsx'
|
|
18
19
|
import ZoomControls from './ZoomControls.tsx'
|
|
@@ -42,14 +43,16 @@ const Header = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
42
43
|
<ZoomControls model={model} />
|
|
43
44
|
<ZoomMenu model={model} />
|
|
44
45
|
<GappynessSlider model={model} />
|
|
46
|
+
<RowSearch model={model} />
|
|
45
47
|
<div style={{ paddingLeft: 20, margin: 'auto' }}>
|
|
46
48
|
<MultiAlignmentSelector model={model} />
|
|
47
49
|
</div>
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
+
<div style={{ flex: '1 1 0', minWidth: 0, display: 'flex' }}>
|
|
51
|
+
<HeaderInfoArea model={model} />
|
|
52
|
+
</div>
|
|
50
53
|
<LoadWarnings model={model} />
|
|
51
54
|
<UnshareableDataWarning model={model} />
|
|
52
|
-
<
|
|
55
|
+
<StatusMessage model={model} variant="header" />
|
|
53
56
|
<Tooltip title="About">
|
|
54
57
|
<IconButton
|
|
55
58
|
aria-label="About"
|
|
@@ -64,8 +67,4 @@ const Header = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
64
67
|
)
|
|
65
68
|
})
|
|
66
69
|
|
|
67
|
-
function Spacer() {
|
|
68
|
-
return <div style={{ flex: 1 }} />
|
|
69
|
-
}
|
|
70
|
-
|
|
71
70
|
export default Header
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { Autocomplete, TextField, createFilterOptions } from '@mui/material'
|
|
4
|
+
import { observer } from 'mobx-react'
|
|
5
|
+
|
|
6
|
+
import { findableRowNames, revealRow } from './revealRow.ts'
|
|
7
|
+
|
|
8
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
9
|
+
|
|
10
|
+
const filterOptions = createFilterOptions<string>({ limit: 50 })
|
|
11
|
+
|
|
12
|
+
const RowSearch = observer(function ({ model }: { model: MsaViewModel }) {
|
|
13
|
+
const names = findableRowNames(model)
|
|
14
|
+
return names.length > 1 ? (
|
|
15
|
+
<Autocomplete
|
|
16
|
+
size="small"
|
|
17
|
+
options={names}
|
|
18
|
+
filterOptions={filterOptions}
|
|
19
|
+
value={null}
|
|
20
|
+
blurOnSelect
|
|
21
|
+
onChange={(_, name) => {
|
|
22
|
+
if (name) {
|
|
23
|
+
revealRow(model, name)
|
|
24
|
+
}
|
|
25
|
+
}}
|
|
26
|
+
style={{ width: 180, margin: 'auto 8px' }}
|
|
27
|
+
renderInput={params => (
|
|
28
|
+
<TextField {...params} placeholder="Find row" aria-label="Find row" />
|
|
29
|
+
)}
|
|
30
|
+
/>
|
|
31
|
+
) : null
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export default RowSearch
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
2
|
+
|
|
3
|
+
export function findableRowNames(model: MsaViewModel) {
|
|
4
|
+
const collapsed = new Set(model.collapsed)
|
|
5
|
+
return model.leaves
|
|
6
|
+
.filter(({ data: { id, name } }) => !(collapsed.has(id) && name === id))
|
|
7
|
+
.map(leaf => leaf.data.name)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function revealRow(model: MsaViewModel, name: string) {
|
|
11
|
+
const index = model.rowNamesSet.get(name)
|
|
12
|
+
if (index !== undefined) {
|
|
13
|
+
const { rowHeight, msaAreaHeight } = model
|
|
14
|
+
model.setScrollY(-(index + 0.5) * rowHeight + msaAreaHeight / 2)
|
|
15
|
+
model.setMousePos(undefined, index)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -1,6 +1,27 @@
|
|
|
1
|
+
import { treeOrders } from '../../constants.ts'
|
|
2
|
+
|
|
1
3
|
import type { MsaViewModel } from '../../model.ts'
|
|
4
|
+
import type { TreeOrder } from '../../types.ts'
|
|
2
5
|
import type { MenuItem } from '@jbrowse/core/ui/Menu'
|
|
3
6
|
|
|
7
|
+
function radio(label: string, checked: boolean, onClick: () => void) {
|
|
8
|
+
return { label, type: 'radio' as const, checked, onClick }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const treeOrderLabels: Record<TreeOrder, string> = {
|
|
12
|
+
branchLength: 'By branch length',
|
|
13
|
+
input: 'As written',
|
|
14
|
+
ladderize: 'Ladderize, small clades first',
|
|
15
|
+
ladderizeReverse: 'Ladderize, large clades first',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function outgroupLabel(outgroup: string[]) {
|
|
19
|
+
const [first, ...rest] = outgroup
|
|
20
|
+
return rest.length === 0
|
|
21
|
+
? `Outgroup ${first}`
|
|
22
|
+
: `Outgroup ${first} … ${rest.at(-1)}`
|
|
23
|
+
}
|
|
24
|
+
|
|
4
25
|
function toggle(label: string, checked: boolean, set: (arg: boolean) => void) {
|
|
5
26
|
return {
|
|
6
27
|
label,
|
|
@@ -56,8 +77,53 @@ export function treeSettingsMenuItems(model: MsaViewModel): MenuItem[] {
|
|
|
56
77
|
drawNodeLabels,
|
|
57
78
|
drawLabels,
|
|
58
79
|
showTreeOverview,
|
|
80
|
+
treeOrder,
|
|
81
|
+
treeRoot,
|
|
82
|
+
rotated,
|
|
59
83
|
} = model
|
|
60
84
|
return [
|
|
85
|
+
{
|
|
86
|
+
label: 'Order',
|
|
87
|
+
type: 'subMenu' as const,
|
|
88
|
+
subMenu: [
|
|
89
|
+
...treeOrders.map(order =>
|
|
90
|
+
radio(treeOrderLabels[order], treeOrder === order, () => {
|
|
91
|
+
model.setTreeOrder(order)
|
|
92
|
+
}),
|
|
93
|
+
),
|
|
94
|
+
{ type: 'divider' as const },
|
|
95
|
+
{
|
|
96
|
+
label: 'Undo rotations',
|
|
97
|
+
disabled: rotated.length === 0,
|
|
98
|
+
onClick: () => {
|
|
99
|
+
model.clearRotated()
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
label: 'Root',
|
|
106
|
+
type: 'subMenu' as const,
|
|
107
|
+
subMenu: [
|
|
108
|
+
radio('As written', treeRoot === undefined, () => {
|
|
109
|
+
model.setTreeRoot(undefined)
|
|
110
|
+
}),
|
|
111
|
+
radio('Midpoint', treeRoot === 'midpoint', () => {
|
|
112
|
+
model.setTreeRoot('midpoint')
|
|
113
|
+
}),
|
|
114
|
+
// "Reroot here" in a node's menu sets the outgroup, so this only
|
|
115
|
+
// reports it
|
|
116
|
+
...(typeof treeRoot === 'object'
|
|
117
|
+
? [
|
|
118
|
+
{
|
|
119
|
+
...radio(outgroupLabel(treeRoot.outgroup), true, () => {}),
|
|
120
|
+
disabled: true,
|
|
121
|
+
},
|
|
122
|
+
]
|
|
123
|
+
: []),
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
{ type: 'divider' as const },
|
|
61
127
|
toggle('Show branch length', showBranchLen, arg => {
|
|
62
128
|
model.setShowBranchLen(arg)
|
|
63
129
|
}),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
|
|
3
3
|
import { ErrorMessage, FileSelector } from '@jbrowse/core/ui'
|
|
4
|
-
import { Button, Container, Typography } from '@mui/material'
|
|
4
|
+
import { Box, Button, Container, Typography } from '@mui/material'
|
|
5
5
|
import { observer } from 'mobx-react'
|
|
6
6
|
|
|
7
7
|
import ImportFormExamples from './ImportFormExamples.tsx'
|
|
@@ -10,6 +10,8 @@ import { load } from './util.ts'
|
|
|
10
10
|
import type { MsaViewModel } from '../../model.ts'
|
|
11
11
|
import type { FileLocation } from '@jbrowse/core/util/types'
|
|
12
12
|
|
|
13
|
+
const phone = '@media (max-width: 640px)'
|
|
14
|
+
|
|
13
15
|
const ImportForm = observer(function ({ model }: { model: MsaViewModel }) {
|
|
14
16
|
const [msaFile, setMsaFile] = useState<FileLocation>()
|
|
15
17
|
const [treeFile, setTreeFile] = useState<FileLocation>()
|
|
@@ -18,7 +20,7 @@ const ImportForm = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
18
20
|
|
|
19
21
|
return (
|
|
20
22
|
<Container>
|
|
21
|
-
<
|
|
23
|
+
<Box sx={{ width: '50%', [phone]: { width: '100%' } }}>
|
|
22
24
|
{error ? <ErrorMessage error={error} /> : null}
|
|
23
25
|
<Typography>
|
|
24
26
|
Open an MSA file (FASTA, Stockholm, Clustal, A3M or EMF format) and/or
|
|
@@ -28,14 +30,19 @@ const ImportForm = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
28
30
|
An MSA alone or a tree alone is enough, and a Stockholm file with an
|
|
29
31
|
embedded tree needs no separate tree file.
|
|
30
32
|
</Typography>
|
|
31
|
-
</
|
|
33
|
+
</Box>
|
|
32
34
|
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
+
<Box
|
|
36
|
+
sx={{
|
|
35
37
|
display: 'flex',
|
|
36
38
|
justifyContent: 'center',
|
|
37
39
|
alignItems: 'center',
|
|
38
|
-
gap:
|
|
40
|
+
gap: '40px',
|
|
41
|
+
[phone]: {
|
|
42
|
+
flexDirection: 'column',
|
|
43
|
+
alignItems: 'stretch',
|
|
44
|
+
gap: '16px',
|
|
45
|
+
},
|
|
39
46
|
}}
|
|
40
47
|
>
|
|
41
48
|
<div>
|
|
@@ -74,7 +81,7 @@ const ImportForm = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
74
81
|
<Typography>Examples</Typography>
|
|
75
82
|
<ImportFormExamples model={model} />
|
|
76
83
|
</div>
|
|
77
|
-
</
|
|
84
|
+
</Box>
|
|
78
85
|
</Container>
|
|
79
86
|
)
|
|
80
87
|
})
|
|
@@ -3,16 +3,17 @@ import React from 'react'
|
|
|
3
3
|
import { Link, Typography } from '@mui/material'
|
|
4
4
|
import { observer } from 'mobx-react'
|
|
5
5
|
|
|
6
|
-
import { smallMSA,
|
|
6
|
+
import { smallMSA, smallTree } from './data/seq2.ts'
|
|
7
7
|
import { load } from './util.ts'
|
|
8
8
|
|
|
9
9
|
import type { MsaViewModel } from '../../model.ts'
|
|
10
10
|
|
|
11
11
|
const BASE = 'https://jbrowse.org/genomes/multiple_sequence_alignments'
|
|
12
12
|
const TREES = 'https://jbrowse.org/genomes/newick_trees'
|
|
13
|
+
const DEMO = 'https://gmod.org/JBrowseMSA/demo/data'
|
|
13
14
|
|
|
14
15
|
// each example points at remote files (msa/tree/gff urls) or carries its data
|
|
15
|
-
// inline
|
|
16
|
+
// inline
|
|
16
17
|
interface Example {
|
|
17
18
|
label: string
|
|
18
19
|
msa?: string
|
|
@@ -23,42 +24,33 @@ interface Example {
|
|
|
23
24
|
|
|
24
25
|
const examples: Example[] = [
|
|
25
26
|
{
|
|
26
|
-
label: '
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
label: 'Small protein MSA+tree',
|
|
31
|
-
inline: { msa: smallMSA, tree: smallTree },
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
label: 'Small MSA only',
|
|
35
|
-
inline: { msa: smallMSAOnly },
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
label: 'PFAM SARS-CoV2 multi-stockholm with domains',
|
|
39
|
-
msa: `${BASE}/pfam-cov2.stock`,
|
|
40
|
-
gff: `${BASE}/pfam-cov2-domains.gff`,
|
|
27
|
+
label: 'NLRP1 in 12 mammals, with Pfam domains',
|
|
28
|
+
msa: `${DEMO}/nlrp1.aln`,
|
|
29
|
+
tree: `${DEMO}/nlrp1.nh`,
|
|
30
|
+
gff: `${DEMO}/nlrp1-domains.gff`,
|
|
41
31
|
},
|
|
42
32
|
{
|
|
43
|
-
label: '
|
|
44
|
-
msa: `${
|
|
33
|
+
label: '474 human protein kinase domains, with a FastTree tree',
|
|
34
|
+
msa: `${DEMO}/kinase-pocket/kinase-pocket.afa`,
|
|
35
|
+
tree: `${DEMO}/kinase-pocket/kinase-pocket.nwk`,
|
|
45
36
|
},
|
|
46
37
|
{
|
|
47
|
-
label: '
|
|
48
|
-
msa: `${
|
|
38
|
+
label: 'RSV-A G gene in 1,840 genomes, with the Nextstrain tree',
|
|
39
|
+
msa: `${DEMO}/scale/rsv-full.aln`,
|
|
40
|
+
tree: `${DEMO}/scale/rsv-full.nh`,
|
|
49
41
|
},
|
|
50
42
|
{
|
|
51
|
-
label: '
|
|
52
|
-
|
|
43
|
+
label: '230k COVID-19 samples (tree only)',
|
|
44
|
+
tree: `${TREES}/sarscov2phylo.pub.ft.nh`,
|
|
53
45
|
},
|
|
54
46
|
{
|
|
55
|
-
label: '
|
|
56
|
-
msa: `${BASE}/
|
|
57
|
-
|
|
47
|
+
label: 'Pfam SARS-CoV-2 families, multi-Stockholm with domains',
|
|
48
|
+
msa: `${BASE}/pfam-cov2.stock`,
|
|
49
|
+
gff: `${BASE}/pfam-cov2-domains.gff`,
|
|
58
50
|
},
|
|
59
51
|
{
|
|
60
|
-
label: '
|
|
61
|
-
msa:
|
|
52
|
+
label: 'Small protein alignment and tree',
|
|
53
|
+
inline: { msa: smallMSA, tree: smallTree },
|
|
62
54
|
},
|
|
63
55
|
]
|
|
64
56
|
|
|
@@ -70,6 +70,13 @@ const Minimap = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
70
70
|
ref={ref}
|
|
71
71
|
width={barWidth}
|
|
72
72
|
height={barHeight}
|
|
73
|
+
onMouseDown={event => {
|
|
74
|
+
if (event.button === 0 && unit > 0) {
|
|
75
|
+
const x =
|
|
76
|
+
event.clientX - event.currentTarget.getBoundingClientRect().left
|
|
77
|
+
model.setScrollX(-x / unit + msaCanvasWidth / 2)
|
|
78
|
+
}
|
|
79
|
+
}}
|
|
73
80
|
style={{
|
|
74
81
|
display: 'block',
|
|
75
82
|
width: msaCanvasWidth,
|
|
@@ -32,16 +32,17 @@ const AnnotationLegend = observer(function ({
|
|
|
32
32
|
display: 'flex',
|
|
33
33
|
flexDirection: 'column',
|
|
34
34
|
opacity: 0.95,
|
|
35
|
+
pointerEvents: 'none',
|
|
35
36
|
}}
|
|
36
37
|
>
|
|
37
38
|
<IconButton
|
|
38
39
|
size="small"
|
|
39
40
|
title={expanded ? 'Collapse key' : 'Expand key'}
|
|
40
|
-
style={
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
41
|
+
style={{
|
|
42
|
+
padding: 1,
|
|
43
|
+
pointerEvents: 'auto',
|
|
44
|
+
...(expanded && { position: 'absolute', top: 0, right: 0 }),
|
|
45
|
+
}}
|
|
45
46
|
onClick={() => {
|
|
46
47
|
model.setShowDomainLegend(!expanded)
|
|
47
48
|
}}
|
|
@@ -59,7 +60,6 @@ const AnnotationLegend = observer(function ({
|
|
|
59
60
|
gap: 4,
|
|
60
61
|
marginRight: i === 0 ? 18 : 0,
|
|
61
62
|
}}
|
|
62
|
-
title={row.hint}
|
|
63
63
|
>
|
|
64
64
|
{row.color ? (
|
|
65
65
|
<div
|
|
@@ -74,10 +74,16 @@ const AnnotationLegend = observer(function ({
|
|
|
74
74
|
<Typography
|
|
75
75
|
variant="caption"
|
|
76
76
|
noWrap
|
|
77
|
+
title={
|
|
78
|
+
row.hint && row.hint !== row.label
|
|
79
|
+
? `${row.label} (${row.hint})`
|
|
80
|
+
: row.label
|
|
81
|
+
}
|
|
77
82
|
style={{
|
|
78
83
|
fontSize: 10,
|
|
79
84
|
lineHeight: 1.4,
|
|
80
85
|
fontWeight: row.color ? undefined : 'bold',
|
|
86
|
+
pointerEvents: 'auto',
|
|
81
87
|
}}
|
|
82
88
|
>
|
|
83
89
|
{row.label}
|
|
@@ -5,6 +5,7 @@ import { observer } from 'mobx-react'
|
|
|
5
5
|
import { useWheelScroll } from '../../useWheelScroll.ts'
|
|
6
6
|
import Loading from './Loading.tsx'
|
|
7
7
|
import MSACanvasBlock from './MSACanvasBlock.tsx'
|
|
8
|
+
import { onMsaKey } from './msaKeys.ts'
|
|
8
9
|
|
|
9
10
|
import type { MsaViewModel } from '../../model.ts'
|
|
10
11
|
|
|
@@ -52,6 +53,13 @@ const MSACanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
52
53
|
// screenshot callouts anchor to columns from this rect's origin:
|
|
53
54
|
// col*colWidth + scrollX, row*rowHeight + scrollY
|
|
54
55
|
data-testid="msa_canvas"
|
|
56
|
+
tabIndex={0}
|
|
57
|
+
aria-label="Alignment. Arrow keys scroll, shift pages, + and - zoom, Home and End jump to the first and last column."
|
|
58
|
+
onKeyDown={event => {
|
|
59
|
+
if (onMsaKey(model, event)) {
|
|
60
|
+
event.preventDefault()
|
|
61
|
+
}
|
|
62
|
+
}}
|
|
55
63
|
onMouseDown={onMouseDown}
|
|
56
64
|
onMouseUp={onMouseUp}
|
|
57
65
|
style={{
|