react-msaview 9.0.0 → 9.1.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/README.md +19 -10
- package/bundle/index.js +74 -74
- package/bundle/index.js.map +4 -4
- package/dist/colorSchemes.js +1 -3
- package/dist/colorSchemes.js.map +1 -1
- package/dist/columnCounts.d.ts +0 -1
- package/dist/columnCounts.js +1 -1
- package/dist/columnCounts.js.map +1 -1
- package/dist/components/Loading.js +5 -5
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +1891 -4
- package/dist/components/MSAViewer.js +144 -201
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/ResizeHandles.js +3 -2
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/Track.js +2 -1
- package/dist/components/Track.js.map +1 -1
- package/dist/components/controls.d.ts +11 -0
- package/dist/components/controls.js +48 -0
- package/dist/components/controls.js.map +1 -0
- package/dist/components/dialogs/AnnotationFileDialog.js +5 -16
- package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -1
- package/dist/components/dialogs/ExportSVGDialog.js +5 -7
- package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
- package/dist/components/dialogs/HelpDialog.d.ts +6 -0
- package/dist/components/dialogs/HelpDialog.js +30 -0
- package/dist/components/dialogs/HelpDialog.js.map +1 -0
- package/dist/components/header/FileMenu.js +33 -7
- package/dist/components/header/FileMenu.js.map +1 -1
- package/dist/components/header/Header.js +4 -6
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/TreeSettingsMenu.d.ts +1 -1
- package/dist/components/header/TreeSettingsMenu.js +1 -32
- package/dist/components/header/TreeSettingsMenu.js.map +1 -1
- package/dist/components/header/ZoomControls.js +28 -44
- package/dist/components/header/ZoomControls.js.map +1 -1
- package/dist/components/header/settingsMenuItems.d.ts +1 -0
- package/dist/components/header/settingsMenuItems.js +44 -0
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/import/data/seq2.d.ts +0 -1
- package/dist/components/import/data/seq2.js +0 -17
- package/dist/components/import/data/seq2.js.map +1 -1
- package/dist/components/msa/MSACanvas.js +2 -1
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +2 -5
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/drawFeatureSpans.d.ts +50 -11
- package/dist/components/msa/drawFeatureSpans.js +99 -56
- package/dist/components/msa/drawFeatureSpans.js.map +1 -1
- package/dist/components/msa/msaRaster.d.ts +14 -1
- package/dist/components/msa/msaRaster.js +19 -9
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +9 -0
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +19 -8
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderHighlights.d.ts +2 -2
- package/dist/components/msa/renderHighlights.js +2 -3
- package/dist/components/msa/renderHighlights.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +2 -1
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/renderCtx.d.ts +1 -1
- package/dist/components/rowpanels/renderFeaturePanel.d.ts +5 -1
- package/dist/components/rowpanels/renderFeaturePanel.js +12 -5
- package/dist/components/rowpanels/renderFeaturePanel.js.map +1 -1
- package/dist/components/tracks/drawTracks.d.ts +1 -53
- package/dist/components/tracks/drawTracks.js +5 -5
- package/dist/components/tracks/drawTracks.js.map +1 -1
- package/dist/components/tree/CladeLabels.d.ts +1 -1
- package/dist/components/tree/CladeLabels.js +22 -34
- package/dist/components/tree/CladeLabels.js.map +1 -1
- package/dist/components/tree/cladeBrackets.d.ts +17 -10
- package/dist/components/tree/cladeBrackets.js +33 -3
- package/dist/components/tree/cladeBrackets.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.js +2 -8
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/tree/renderTreeOverview.js +4 -11
- package/dist/components/tree/renderTreeOverview.js.map +1 -1
- package/dist/element.js +3 -6
- package/dist/element.js.map +1 -1
- package/dist/exportLayout.d.ts +39 -0
- package/dist/exportLayout.js +78 -0
- package/dist/exportLayout.js.map +1 -0
- package/dist/headlessRenderEnv.d.ts +12 -39
- package/dist/headlessRenderEnv.js +18 -12
- package/dist/headlessRenderEnv.js.map +1 -1
- package/dist/hierarchy.d.ts +5 -8
- package/dist/hierarchy.js +5 -79
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/measureTextCanvas.d.ts +7 -0
- package/dist/measureTextCanvas.js +8 -6
- package/dist/measureTextCanvas.js.map +1 -1
- package/dist/model/treeModel.d.ts +0 -5
- package/dist/model/treeModel.js +0 -7
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +38 -44
- package/dist/model.js +153 -112
- package/dist/model.js.map +1 -1
- package/dist/renderToSvg.d.ts +2 -7
- package/dist/renderToSvg.js +32 -120
- package/dist/renderToSvg.js.map +1 -1
- package/dist/svgTestUtil.d.ts +12 -20
- package/dist/types.d.ts +0 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/viewerSnapshot.d.ts +169 -0
- package/dist/viewerSnapshot.js +100 -0
- package/dist/viewerSnapshot.js.map +1 -0
- package/package.json +3 -3
- package/src/colorSchemes.ts +1 -4
- package/src/columnCounts.ts +1 -1
- package/src/components/Loading.tsx +19 -12
- package/src/components/MSAViewer.tsx +275 -251
- package/src/components/ResizeHandles.tsx +9 -2
- package/src/components/Track.tsx +2 -1
- package/src/components/controls.ts +61 -0
- package/src/components/dialogs/AnnotationFileDialog.tsx +5 -21
- package/src/components/dialogs/ExportSVGDialog.tsx +8 -19
- package/src/components/dialogs/HelpDialog.tsx +79 -0
- package/src/components/header/FileMenu.tsx +33 -7
- package/src/components/header/Header.tsx +4 -6
- package/src/components/header/TreeSettingsMenu.tsx +2 -32
- package/src/components/header/ZoomControls.tsx +30 -66
- package/src/components/header/settingsMenuItems.ts +59 -0
- package/src/components/import/data/seq2.ts +0 -18
- package/src/components/msa/MSACanvas.tsx +2 -1
- package/src/components/msa/MSACanvasBlock.tsx +2 -6
- package/src/components/msa/drawFeatureSpans.ts +160 -79
- package/src/components/msa/msaRaster.ts +27 -9
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +39 -24
- package/src/components/msa/renderHighlights.ts +2 -3
- package/src/components/msa/renderMSAMouseover.ts +5 -1
- package/src/components/renderCtx.ts +0 -1
- package/src/components/rowpanels/renderFeaturePanel.ts +26 -7
- package/src/components/tracks/drawTracks.ts +5 -5
- package/src/components/tree/CladeLabels.tsx +30 -54
- package/src/components/tree/cladeBrackets.ts +50 -3
- package/src/components/tree/renderTreeCanvas.ts +2 -13
- package/src/components/tree/renderTreeOverview.ts +7 -14
- package/src/element.ts +3 -7
- package/src/exportLayout.ts +134 -0
- package/src/headlessRenderEnv.ts +24 -11
- package/src/hierarchy.ts +5 -88
- package/src/index.ts +5 -5
- package/src/measureTextCanvas.ts +8 -6
- package/src/model/treeModel.ts +0 -7
- package/src/model.ts +170 -122
- package/src/renderToSvg.tsx +55 -194
- package/src/types.ts +0 -1
- package/src/version.ts +1 -1
- package/src/viewerSnapshot.ts +147 -0
- package/dist/components/header/ZoomMenu.d.ts +0 -6
- package/dist/components/header/ZoomMenu.js +0 -37
- package/dist/components/header/ZoomMenu.js.map +0 -1
- package/src/components/header/ZoomMenu.tsx +0 -48
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export interface Control {
|
|
2
|
+
label: string
|
|
3
|
+
action: string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface KeyControl extends Control {
|
|
7
|
+
keys: string[]
|
|
8
|
+
shiftKey?: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const arrows = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']
|
|
12
|
+
|
|
13
|
+
export const keyboardControls: KeyControl[] = [
|
|
14
|
+
{ label: 'Arrow keys', action: 'Scroll one cell', keys: arrows },
|
|
15
|
+
{
|
|
16
|
+
label: 'Shift + arrow keys',
|
|
17
|
+
action: 'Scroll one page',
|
|
18
|
+
keys: arrows,
|
|
19
|
+
shiftKey: true,
|
|
20
|
+
},
|
|
21
|
+
{ label: '+ and -', action: 'Zoom in and out', keys: ['+', '=', '-'] },
|
|
22
|
+
{
|
|
23
|
+
label: 'Home and End',
|
|
24
|
+
action: 'Jump to the first and last column',
|
|
25
|
+
keys: ['Home', 'End'],
|
|
26
|
+
},
|
|
27
|
+
{ label: 'Escape', action: 'Clear the selection', keys: ['Escape'] },
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
const selectCells: Control = {
|
|
31
|
+
label: 'Shift + drag',
|
|
32
|
+
action: 'Select a block of cells',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const mouseControls: Control[] = [
|
|
36
|
+
{ label: 'Drag', action: 'Pan the alignment' },
|
|
37
|
+
{
|
|
38
|
+
label: 'Wheel',
|
|
39
|
+
action: 'Scroll, or zoom as set in Alignment settings → Mouse wheel',
|
|
40
|
+
},
|
|
41
|
+
{ label: 'Ctrl + wheel', action: 'Zoom at the cursor' },
|
|
42
|
+
selectCells,
|
|
43
|
+
{
|
|
44
|
+
label: 'Drag along a track',
|
|
45
|
+
action: 'Select a range of columns across every row',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
label: 'Click a tree node',
|
|
49
|
+
action: 'Collapse, rotate, reroot or show only that clade',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
label: 'Click the tree overview',
|
|
53
|
+
action: 'Show only the clade under the pointer',
|
|
54
|
+
},
|
|
55
|
+
{ label: 'Drag a divider', action: 'Resize the tree area or a track' },
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
export const alignmentAriaLabel = [
|
|
59
|
+
'Alignment',
|
|
60
|
+
...[...keyboardControls, selectCells].map(c => `${c.label}: ${c.action}`),
|
|
61
|
+
].join('. ')
|
|
@@ -13,25 +13,9 @@ import {
|
|
|
13
13
|
Typography,
|
|
14
14
|
} from '@mui/material'
|
|
15
15
|
import { observer } from 'mobx-react'
|
|
16
|
-
import {
|
|
17
|
-
annotationsToGFF,
|
|
18
|
-
interProScanResponseToAnnotations,
|
|
19
|
-
} from 'msa-parsers'
|
|
16
|
+
import { annotationTextToGFF } from 'msa-parsers'
|
|
20
17
|
|
|
21
18
|
import type { MsaViewModel } from '../../model.ts'
|
|
22
|
-
import type { InterProScanResponse } from 'msa-parsers'
|
|
23
|
-
|
|
24
|
-
// GFF3 as the CLI writes it, or the JSON an InterProScan run returns. Both
|
|
25
|
-
// arrive as text and the model keeps GFF, so the JSON is converted on the way
|
|
26
|
-
// in rather than given a second path through the model
|
|
27
|
-
function toGFF(text: string) {
|
|
28
|
-
const trimmed = text.trimStart()
|
|
29
|
-
if (!trimmed.startsWith('{')) {
|
|
30
|
-
return text
|
|
31
|
-
}
|
|
32
|
-
const response: InterProScanResponse = JSON.parse(trimmed)
|
|
33
|
-
return annotationsToGFF(interProScanResponseToAnnotations(response))
|
|
34
|
-
}
|
|
35
19
|
|
|
36
20
|
async function fetchText(url: string) {
|
|
37
21
|
const response = await fetch(url)
|
|
@@ -137,10 +121,10 @@ const AnnotationFileDialog = observer(function ({
|
|
|
137
121
|
? file.text()
|
|
138
122
|
: fetchText(url))
|
|
139
123
|
// through the model's own GFF text, so the annotations travel
|
|
140
|
-
// in the snapshot and the shared URL like every other layer.
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
model.setGFF(
|
|
124
|
+
// in the snapshot and the shared URL like every other layer. A
|
|
125
|
+
// filehandle left set would make the snapshot drop that text
|
|
126
|
+
model.setGFFFilehandle(undefined)
|
|
127
|
+
model.setGFF(annotationTextToGFF(text))
|
|
144
128
|
model.setShowDomains(true)
|
|
145
129
|
handleClose()
|
|
146
130
|
} catch (e) {
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from '@mui/material'
|
|
18
18
|
import { observer } from 'mobx-react'
|
|
19
19
|
|
|
20
|
+
import { exportPageSize, getLayout } from '../../exportLayout.ts'
|
|
20
21
|
import Checkbox2 from '../Checkbox2.tsx'
|
|
21
22
|
|
|
22
23
|
import type { MsaViewModel } from '../../model.ts'
|
|
@@ -46,24 +47,12 @@ const ExportSVGDialog = observer(function ({
|
|
|
46
47
|
const [error, setError] = useState<unknown>()
|
|
47
48
|
const [exporting, setExporting] = useState(false)
|
|
48
49
|
const theme = useTheme()
|
|
49
|
-
const {
|
|
50
|
-
totalWidth,
|
|
51
|
-
totalHeight,
|
|
52
|
-
effectiveTreeAreaWidth,
|
|
53
|
-
rowPanelsWidth,
|
|
54
|
-
turnedOnTracks,
|
|
55
|
-
totalTrackAreaHeight,
|
|
56
|
-
numColumns,
|
|
57
|
-
leaves,
|
|
58
|
-
showMsaLetters,
|
|
59
|
-
treeOverviewHeight,
|
|
60
|
-
} = model
|
|
50
|
+
const { turnedOnTracks, numColumns, leaves, showMsaLetters } = model
|
|
61
51
|
const hasTracks = turnedOnTracks.length > 0
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
treeOverviewHeight
|
|
52
|
+
const entire = exportPageSize(
|
|
53
|
+
model,
|
|
54
|
+
getLayout(model, { theme, exportType: 'entire', includeTracks }),
|
|
55
|
+
)
|
|
67
56
|
// the background is one raster image whatever its size, so what the figure
|
|
68
57
|
// actually costs is its residue letters: each is an svg element of its own.
|
|
69
58
|
// Past roughly this many the export runs to tens of seconds and can exhaust
|
|
@@ -126,8 +115,8 @@ const ExportSVGDialog = observer(function ({
|
|
|
126
115
|
</div>
|
|
127
116
|
{isLargeExport ? (
|
|
128
117
|
<Alert severity="warning" style={{ marginTop: 8 }}>
|
|
129
|
-
The entire MSA is {Math.round(
|
|
130
|
-
{Math.round(
|
|
118
|
+
The entire MSA is {Math.round(entire.width)}x
|
|
119
|
+
{Math.round(entire.height)} pixels and draws{' '}
|
|
131
120
|
{Math.round(glyphs / 1000)}k residue letters, each its own SVG
|
|
132
121
|
element. Export may be slow or fail — zooming out far enough to hide
|
|
133
122
|
the letters exports the same alignment as an image instead.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React, { lazy } from 'react'
|
|
2
|
+
|
|
3
|
+
import { Dialog } from '@jbrowse/core/ui'
|
|
4
|
+
import {
|
|
5
|
+
Button,
|
|
6
|
+
DialogActions,
|
|
7
|
+
DialogContent,
|
|
8
|
+
Table,
|
|
9
|
+
TableBody,
|
|
10
|
+
TableCell,
|
|
11
|
+
TableRow,
|
|
12
|
+
Typography,
|
|
13
|
+
} from '@mui/material'
|
|
14
|
+
|
|
15
|
+
import { version } from '../../version.ts'
|
|
16
|
+
import { keyboardControls, mouseControls } from '../controls.ts'
|
|
17
|
+
|
|
18
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
19
|
+
import type { Control } from '../controls.ts'
|
|
20
|
+
|
|
21
|
+
const AboutDialog = lazy(() => import('./AboutDialog.tsx'))
|
|
22
|
+
|
|
23
|
+
function ControlTable({ title, rows }: { title: string; rows: Control[] }) {
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
<Typography variant="subtitle2" sx={{ mt: 1 }}>
|
|
27
|
+
{title}
|
|
28
|
+
</Typography>
|
|
29
|
+
<Table size="small" aria-label={title}>
|
|
30
|
+
<TableBody>
|
|
31
|
+
{rows.map(({ label, action }) => (
|
|
32
|
+
<TableRow key={label}>
|
|
33
|
+
<TableCell sx={{ whiteSpace: 'nowrap', fontWeight: 500 }}>
|
|
34
|
+
{label}
|
|
35
|
+
</TableCell>
|
|
36
|
+
<TableCell>{action}</TableCell>
|
|
37
|
+
</TableRow>
|
|
38
|
+
))}
|
|
39
|
+
</TableBody>
|
|
40
|
+
</Table>
|
|
41
|
+
</>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default function HelpDialog({
|
|
46
|
+
model,
|
|
47
|
+
onClose,
|
|
48
|
+
}: {
|
|
49
|
+
model: MsaViewModel
|
|
50
|
+
onClose: () => void
|
|
51
|
+
}) {
|
|
52
|
+
return (
|
|
53
|
+
<Dialog open title="Mouse & keyboard" onClose={onClose}>
|
|
54
|
+
<DialogContent>
|
|
55
|
+
<ControlTable title="Mouse" rows={mouseControls} />
|
|
56
|
+
<ControlTable
|
|
57
|
+
title="Keyboard (click the alignment first)"
|
|
58
|
+
rows={keyboardControls}
|
|
59
|
+
/>
|
|
60
|
+
</DialogContent>
|
|
61
|
+
<DialogActions>
|
|
62
|
+
<Typography variant="caption" sx={{ mr: 'auto', pl: 1 }}>
|
|
63
|
+
MSAView {version}
|
|
64
|
+
</Typography>
|
|
65
|
+
<Button
|
|
66
|
+
onClick={() => {
|
|
67
|
+
model.queueDialog(done => [AboutDialog, { onClose: done }])
|
|
68
|
+
onClose()
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
About & credits
|
|
72
|
+
</Button>
|
|
73
|
+
<Button variant="contained" onClick={onClose}>
|
|
74
|
+
Close
|
|
75
|
+
</Button>
|
|
76
|
+
</DialogActions>
|
|
77
|
+
</Dialog>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
@@ -8,6 +8,7 @@ import Menu from '@mui/icons-material/Menu'
|
|
|
8
8
|
import PhotoCamera from '@mui/icons-material/PhotoCamera'
|
|
9
9
|
import { observer } from 'mobx-react'
|
|
10
10
|
|
|
11
|
+
import { maxNeighborJoiningRows } from '../../constants.ts'
|
|
11
12
|
import { getAnnotationsMenu } from './getAnnotationsMenu.ts'
|
|
12
13
|
|
|
13
14
|
import type { MsaViewModel } from '../../model.ts'
|
|
@@ -15,19 +16,43 @@ import type { MsaViewModel } from '../../model.ts'
|
|
|
15
16
|
const MetadataDialog = lazy(() => import('../dialogs/MetadataDialog.tsx'))
|
|
16
17
|
const ExportSVGDialog = lazy(() => import('../dialogs/ExportSVGDialog.tsx'))
|
|
17
18
|
|
|
19
|
+
function neighborJoiningItem(model: MsaViewModel) {
|
|
20
|
+
const rows = model.rowMap.size
|
|
21
|
+
const tooManyRows = rows > maxNeighborJoiningRows
|
|
22
|
+
return {
|
|
23
|
+
label: tooManyRows
|
|
24
|
+
? `Calculate neighbor joining tree (over ${maxNeighborJoiningRows} rows, use FastTree)`
|
|
25
|
+
: 'Calculate neighbor joining tree (BLOSUM62)',
|
|
26
|
+
icon: AccountTree,
|
|
27
|
+
disabled: tooManyRows || rows < 2,
|
|
28
|
+
onClick: () => {
|
|
29
|
+
try {
|
|
30
|
+
model.calculateNeighborJoiningTreeFromMSA()
|
|
31
|
+
} catch (e) {
|
|
32
|
+
console.error('Failed to calculate NJ tree:', e)
|
|
33
|
+
model.addWarning(e instanceof Error ? e.message : String(e))
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
18
39
|
const FileMenu = observer(({ model }: { model: MsaViewModel }) => {
|
|
19
40
|
return (
|
|
20
41
|
<CascadingMenuButton
|
|
21
42
|
data-testid="file_menu"
|
|
22
43
|
tooltip="File"
|
|
23
44
|
menuItems={[
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
...(model.importForm
|
|
46
|
+
? [
|
|
47
|
+
{
|
|
48
|
+
label: 'Return to import form',
|
|
49
|
+
icon: FolderOpen,
|
|
50
|
+
onClick: () => {
|
|
51
|
+
model.reset()
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
]
|
|
55
|
+
: []),
|
|
31
56
|
{
|
|
32
57
|
label: 'Metadata',
|
|
33
58
|
icon: Assignment,
|
|
@@ -48,6 +73,7 @@ const FileMenu = observer(({ model }: { model: MsaViewModel }) => {
|
|
|
48
73
|
model,
|
|
49
74
|
}),
|
|
50
75
|
},
|
|
76
|
+
neighborJoiningItem(model),
|
|
51
77
|
{
|
|
52
78
|
label: 'Export tree (Newick)',
|
|
53
79
|
icon: AccountTree,
|
|
@@ -18,11 +18,10 @@ import SelectionMenu from './SelectionMenu.tsx'
|
|
|
18
18
|
import TreeSettingsMenu from './TreeSettingsMenu.tsx'
|
|
19
19
|
import UnshareableDataWarning from './UnshareableDataWarning.tsx'
|
|
20
20
|
import ZoomControls from './ZoomControls.tsx'
|
|
21
|
-
import ZoomMenu from './ZoomMenu.tsx'
|
|
22
21
|
|
|
23
22
|
import type { MsaViewModel } from '../../model.ts'
|
|
24
23
|
|
|
25
|
-
const
|
|
24
|
+
const HelpDialog = lazy(() => import('../dialogs/HelpDialog.tsx'))
|
|
26
25
|
|
|
27
26
|
const Header = observer(function ({ model }: { model: MsaViewModel }) {
|
|
28
27
|
const [ref, { height }] = useMeasure()
|
|
@@ -42,7 +41,6 @@ const Header = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
42
41
|
<TreeSettingsMenu model={model} />
|
|
43
42
|
<MSASettingsMenu model={model} />
|
|
44
43
|
<ZoomControls model={model} />
|
|
45
|
-
<ZoomMenu model={model} />
|
|
46
44
|
<GappynessSlider model={model} />
|
|
47
45
|
<GoToBox model={model} />
|
|
48
46
|
<div style={{ paddingLeft: 20, margin: 'auto' }}>
|
|
@@ -57,11 +55,11 @@ const Header = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
57
55
|
<LoadWarnings model={model} />
|
|
58
56
|
<UnshareableDataWarning model={model} />
|
|
59
57
|
<StatusMessage model={model} variant="header" />
|
|
60
|
-
<Tooltip title="
|
|
58
|
+
<Tooltip title="Mouse & keyboard help">
|
|
61
59
|
<IconButton
|
|
62
|
-
aria-label="
|
|
60
|
+
aria-label="Mouse & keyboard help"
|
|
63
61
|
onClick={() => {
|
|
64
|
-
model.queueDialog(onClose => [
|
|
62
|
+
model.queueDialog(onClose => [HelpDialog, { model, onClose }])
|
|
65
63
|
}}
|
|
66
64
|
>
|
|
67
65
|
<Help />
|
|
@@ -4,7 +4,6 @@ import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton'
|
|
|
4
4
|
import AccountTree from '@mui/icons-material/AccountTree'
|
|
5
5
|
import { observer } from 'mobx-react'
|
|
6
6
|
|
|
7
|
-
import { maxNeighborJoiningRows } from '../../constants.ts'
|
|
8
7
|
import { treeSettingsMenuItems } from './settingsMenuItems.ts'
|
|
9
8
|
|
|
10
9
|
import type { MsaViewModel } from '../../model.ts'
|
|
@@ -14,43 +13,14 @@ const TreeSettingsMenu = observer(function ({
|
|
|
14
13
|
}: {
|
|
15
14
|
model: MsaViewModel
|
|
16
15
|
}) {
|
|
17
|
-
|
|
18
|
-
const tooManyRows = rows.length > maxNeighborJoiningRows
|
|
19
|
-
return (
|
|
16
|
+
return model.noTree ? null : (
|
|
20
17
|
<CascadingMenuButton
|
|
21
18
|
data-testid="tree_settings_menu"
|
|
22
19
|
tooltip="Tree settings"
|
|
23
20
|
closeAfterItemClick={false}
|
|
24
21
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
|
25
22
|
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
|
|
26
|
-
menuItems={
|
|
27
|
-
...treeSettingsMenuItems(model),
|
|
28
|
-
...(rows.length >= 2
|
|
29
|
-
? [
|
|
30
|
-
{
|
|
31
|
-
label: 'Advanced',
|
|
32
|
-
type: 'subMenu' as const,
|
|
33
|
-
subMenu: [
|
|
34
|
-
{
|
|
35
|
-
// the label shows the cap before a click would freeze the tab
|
|
36
|
-
label: tooManyRows
|
|
37
|
-
? `Calculate neighbor joining tree (over ${maxNeighborJoiningRows} rows, use FastTree)`
|
|
38
|
-
: 'Calculate neighbor joining tree (BLOSUM62)',
|
|
39
|
-
disabled: tooManyRows,
|
|
40
|
-
onClick: () => {
|
|
41
|
-
try {
|
|
42
|
-
model.calculateNeighborJoiningTreeFromMSA()
|
|
43
|
-
} catch (e) {
|
|
44
|
-
console.error('Failed to calculate NJ tree:', e)
|
|
45
|
-
model.setError(e)
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
],
|
|
50
|
-
},
|
|
51
|
-
]
|
|
52
|
-
: []),
|
|
53
|
-
]}
|
|
23
|
+
menuItems={treeSettingsMenuItems(model)}
|
|
54
24
|
>
|
|
55
25
|
<AccountTree />
|
|
56
26
|
</CascadingMenuButton>
|
|
@@ -1,67 +1,20 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
3
|
import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton'
|
|
4
|
+
import FitScreen from '@mui/icons-material/FitScreen'
|
|
5
|
+
import RestartAlt from '@mui/icons-material/RestartAlt'
|
|
4
6
|
import ZoomIn from '@mui/icons-material/ZoomIn'
|
|
5
7
|
import ZoomOut from '@mui/icons-material/ZoomOut'
|
|
6
8
|
import { IconButton, Tooltip } from '@mui/material'
|
|
7
9
|
import { observer } from 'mobx-react'
|
|
8
10
|
|
|
9
|
-
import {
|
|
10
|
-
MouseScroll,
|
|
11
|
-
MouseZoomBoth,
|
|
12
|
-
MouseZoomHorizontal,
|
|
13
|
-
MouseZoomVertical,
|
|
14
|
-
} from './ScrollZoomIcons.tsx'
|
|
15
|
-
|
|
16
|
-
import type { ScrollZoomAxis } from '../../constants.ts'
|
|
17
11
|
import type { MsaViewModel } from '../../model.ts'
|
|
18
12
|
|
|
19
|
-
interface WheelMode {
|
|
20
|
-
label: string
|
|
21
|
-
// undefined is the wheel scrolling the alignment, which is the default
|
|
22
|
-
axis?: ScrollZoomAxis
|
|
23
|
-
Icon: typeof MouseScroll
|
|
24
|
-
helpText?: string
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const scrollMode: WheelMode = {
|
|
28
|
-
label: 'Scrolls the alignment',
|
|
29
|
-
Icon: MouseScroll,
|
|
30
|
-
helpText: 'Ctrl+wheel zooms both axes at the cursor.',
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const wheelModes: WheelMode[] = [
|
|
34
|
-
scrollMode,
|
|
35
|
-
{
|
|
36
|
-
label: 'Zooms both axes',
|
|
37
|
-
axis: 'both',
|
|
38
|
-
Icon: MouseZoomBoth,
|
|
39
|
-
helpText: 'Hold shift to pan while the wheel zooms.',
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
label: 'Zooms columns only',
|
|
43
|
-
axis: 'horizontal',
|
|
44
|
-
Icon: MouseZoomHorizontal,
|
|
45
|
-
helpText:
|
|
46
|
-
'Holds the row height, so the labels and letters stay at their size while the alignment compresses.',
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
label: 'Zooms rows only',
|
|
50
|
-
axis: 'vertical',
|
|
51
|
-
Icon: MouseZoomVertical,
|
|
52
|
-
helpText:
|
|
53
|
-
'Holds the column width, so the alignment stays as wide while the rows grow or shrink.',
|
|
54
|
-
},
|
|
55
|
-
]
|
|
56
|
-
|
|
57
13
|
const ZoomControls = observer(function ZoomControls({
|
|
58
14
|
model,
|
|
59
15
|
}: {
|
|
60
16
|
model: MsaViewModel
|
|
61
17
|
}) {
|
|
62
|
-
const { scrollZoom, scrollZoomAxis } = model
|
|
63
|
-
const activeAxis = scrollZoom ? scrollZoomAxis : undefined
|
|
64
|
-
const current = wheelModes.find(m => m.axis === activeAxis) ?? scrollMode
|
|
65
18
|
return (
|
|
66
19
|
<>
|
|
67
20
|
<Tooltip title="Zoom in">
|
|
@@ -85,30 +38,41 @@ const ZoomControls = observer(function ZoomControls({
|
|
|
85
38
|
</IconButton>
|
|
86
39
|
</Tooltip>
|
|
87
40
|
<CascadingMenuButton
|
|
88
|
-
data-testid="
|
|
89
|
-
tooltip=
|
|
90
|
-
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
|
91
|
-
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
|
|
41
|
+
data-testid="fit_menu"
|
|
42
|
+
tooltip="Fit or reset zoom"
|
|
92
43
|
menuItems={[
|
|
93
|
-
{
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
44
|
+
{
|
|
45
|
+
label: 'Fit to window',
|
|
46
|
+
onClick: () => {
|
|
47
|
+
model.fit()
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
label: 'Fit horizontally',
|
|
100
52
|
onClick: () => {
|
|
101
|
-
model.
|
|
102
|
-
if (axis !== undefined) {
|
|
103
|
-
model.setScrollZoomAxis(axis)
|
|
104
|
-
}
|
|
53
|
+
model.fitHorizontally()
|
|
105
54
|
},
|
|
106
|
-
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
label: 'Fit vertically',
|
|
58
|
+
onClick: () => {
|
|
59
|
+
model.fitVertically()
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{ type: 'divider' },
|
|
63
|
+
{
|
|
64
|
+
label: 'Reset zoom',
|
|
65
|
+
icon: RestartAlt,
|
|
66
|
+
onClick: () => {
|
|
67
|
+
model.resetZoom()
|
|
68
|
+
},
|
|
69
|
+
},
|
|
107
70
|
]}
|
|
108
71
|
>
|
|
109
|
-
<
|
|
72
|
+
<FitScreen />
|
|
110
73
|
</CascadingMenuButton>
|
|
111
74
|
</>
|
|
112
75
|
)
|
|
113
76
|
})
|
|
77
|
+
|
|
114
78
|
export default ZoomControls
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { treeOrders } from '../../constants.ts'
|
|
2
|
+
import {
|
|
3
|
+
MouseScroll,
|
|
4
|
+
MouseZoomBoth,
|
|
5
|
+
MouseZoomHorizontal,
|
|
6
|
+
MouseZoomVertical,
|
|
7
|
+
} from './ScrollZoomIcons.tsx'
|
|
2
8
|
|
|
9
|
+
import type { ScrollZoomAxis } from '../../constants.ts'
|
|
3
10
|
import type { MsaViewModel } from '../../model.ts'
|
|
4
11
|
import type { TreeOrder } from '../../types.ts'
|
|
5
12
|
import type { MenuItem } from '@jbrowse/core/ui/Menu'
|
|
@@ -43,6 +50,53 @@ function tracksSubMenu(model: MsaViewModel): MenuItem[] {
|
|
|
43
50
|
)
|
|
44
51
|
}
|
|
45
52
|
|
|
53
|
+
const wheelModes: {
|
|
54
|
+
label: string
|
|
55
|
+
axis?: ScrollZoomAxis
|
|
56
|
+
icon: typeof MouseScroll
|
|
57
|
+
helpText: string
|
|
58
|
+
}[] = [
|
|
59
|
+
{
|
|
60
|
+
label: 'Scrolls the alignment',
|
|
61
|
+
icon: MouseScroll,
|
|
62
|
+
helpText: 'Ctrl+wheel zooms both axes at the cursor.',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
label: 'Zooms both axes',
|
|
66
|
+
axis: 'both',
|
|
67
|
+
icon: MouseZoomBoth,
|
|
68
|
+
helpText: 'Hold shift to pan while the wheel zooms.',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
label: 'Zooms columns only',
|
|
72
|
+
axis: 'horizontal',
|
|
73
|
+
icon: MouseZoomHorizontal,
|
|
74
|
+
helpText:
|
|
75
|
+
'Holds the row height, so the labels and letters stay at their size while the alignment compresses.',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
label: 'Zooms rows only',
|
|
79
|
+
axis: 'vertical',
|
|
80
|
+
icon: MouseZoomVertical,
|
|
81
|
+
helpText:
|
|
82
|
+
'Holds the column width, so the alignment stays as wide while the rows grow or shrink.',
|
|
83
|
+
},
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
export function wheelSubMenu(model: MsaViewModel): MenuItem[] {
|
|
87
|
+
const active = model.scrollZoom ? model.scrollZoomAxis : undefined
|
|
88
|
+
return wheelModes.map(({ label, axis, icon, helpText }) => ({
|
|
89
|
+
...radio(label, axis === active, () => {
|
|
90
|
+
model.setScrollZoom(axis !== undefined)
|
|
91
|
+
if (axis !== undefined) {
|
|
92
|
+
model.setScrollZoomAxis(axis)
|
|
93
|
+
}
|
|
94
|
+
}),
|
|
95
|
+
icon,
|
|
96
|
+
helpText,
|
|
97
|
+
}))
|
|
98
|
+
}
|
|
99
|
+
|
|
46
100
|
export function msaSettingsMenuItems(model: MsaViewModel): MenuItem[] {
|
|
47
101
|
const { drawMsaLetters, hideGaps, bgColor, showColumnStats } = model
|
|
48
102
|
return [
|
|
@@ -51,6 +105,11 @@ export function msaSettingsMenuItems(model: MsaViewModel): MenuItem[] {
|
|
|
51
105
|
type: 'subMenu' as const,
|
|
52
106
|
subMenu: tracksSubMenu(model),
|
|
53
107
|
},
|
|
108
|
+
{
|
|
109
|
+
label: 'Mouse wheel',
|
|
110
|
+
type: 'subMenu' as const,
|
|
111
|
+
subMenu: wheelSubMenu(model),
|
|
112
|
+
},
|
|
54
113
|
{ type: 'divider' as const },
|
|
55
114
|
toggle('Show track values on hover', showColumnStats, arg => {
|
|
56
115
|
model.setShowColumnStats(arg)
|
|
@@ -13,23 +13,5 @@ UniProt/Swiss-Prot|Q7JFM4|IL2_AOTVO MLKPPLPLRSLLFLQLPLLGVGLNPKFLTPSGNEDIG
|
|
|
13
13
|
UniProt/Swiss-Prot|Q29416|IL2_CANFA --------------LFLQLSLLG-------------------------------------
|
|
14
14
|
`
|
|
15
15
|
|
|
16
|
-
export const smallMSAOnly = `CLUSTAL O(1.2.4) multiple sequence alignment
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
sp|P69905|HBA_HUMAN MVLSPADKTNVKAAWGKVGAHAGEYGAEALERMFLSFPTTKTYFPHFDLSHGSAQVKGHG 60
|
|
20
|
-
sp|P01942|HBA_MOUSE MVLSGEDKSNIKAAWGKIGGHGAEYGAEALERMFASFPTTKTYFPHFDVSHGSAQVKGHG 60
|
|
21
|
-
sp|P13786|HBAZ_CAPHI MSLTRTERTIILSLWSKISTQADVIGTETLERLFSCYPQAKTYFPHFDLHSGSAQLRAHG 60
|
|
22
|
-
* *: ::: : : *.*:. :. *:*:***:* .:* :********: ****::.**
|
|
23
|
-
|
|
24
|
-
sp|P69905|HBA_HUMAN KKVADALTNAVAHVDDMPNALSALSDLHAHKLRVDPVNFKLLSHCLLVTLAAHLPAEFTP 120
|
|
25
|
-
sp|P01942|HBA_MOUSE KKVADALASAAGHLDDLPGALSALSDLHAHKLRVDPVNFKLLSHCLLVTLASHHPADFTP 120
|
|
26
|
-
sp|P13786|HBAZ_CAPHI SKVVAAVGDAVKSIDNVTSALSKLSELHAYVLRVDPVNFKFLSHCLLVTLASHFPADFTA 120
|
|
27
|
-
.**. *: .*. :*:: .*** **:***: *********:**********:* **:**
|
|
28
|
-
|
|
29
|
-
sp|P69905|HBA_HUMAN AVHASLDKFLASVSTVLTSKYR 142
|
|
30
|
-
sp|P01942|HBA_MOUSE AVHASLDKFLASVSTVLTSKYR 142
|
|
31
|
-
sp|P13786|HBAZ_CAPHI DAHAAWDKFLSIVSGVLTEKYR 142
|
|
32
|
-
.**: ****: ** ***.***`
|
|
33
|
-
|
|
34
16
|
export const smallTree =
|
|
35
17
|
'(((UniProt/Swiss-Prot|P26898|IL2RA_SHEEP:0.24036,(UniProt/Swiss-Prot|P41690|IL2RA_FELCA:0.17737,(UniProt/Swiss-Prot|P01589|IL2RA_HUMAN:0.03906,UniProt/Swiss-Prot|Q5MNY4|IL2RA_MACMU:0.03787):0.13033):0.04964):0.02189,UniProt/Swiss-Prot|P01590|IL2RA_MOUSE:0.23072):0.06814,(((UniProt/Swiss-Prot|Q95118|IL2RG_BOVIN:0.09600,UniProt/Swiss-Prot|P40321|IL2RG_CANFA:0.09845):0.25333,UniProt/Swiss-Prot|Q29416|IL2_CANFA:-0.35055):0.10231,(UniProt/Swiss-Prot|P26896|IL2RB_RAT:0.33631,UniProt/Swiss-Prot|Q7JFM4|IL2_AOTVO:-0.33631):0.10166):0.01607,(UniProt/Swiss-Prot|Q8BZM1|GLMN_MOUSE:0.32378,UniProt/Swiss-Prot|P36835|IL2_CAPHI:-0.32378):0.09999)'
|
|
@@ -3,6 +3,7 @@ import React, { useCallback, useRef } from 'react'
|
|
|
3
3
|
import { observer } from 'mobx-react'
|
|
4
4
|
|
|
5
5
|
import { useWheelScroll } from '../../useWheelScroll.ts'
|
|
6
|
+
import { alignmentAriaLabel } from '../controls.ts'
|
|
6
7
|
import Loading from './Loading.tsx'
|
|
7
8
|
import MSACanvasBlock from './MSACanvasBlock.tsx'
|
|
8
9
|
import { onMsaKey } from './msaKeys.ts'
|
|
@@ -54,7 +55,7 @@ const MSACanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
54
55
|
// col*colWidth + scrollX, row*rowHeight + scrollY
|
|
55
56
|
data-testid="msa_canvas"
|
|
56
57
|
tabIndex={0}
|
|
57
|
-
aria-label=
|
|
58
|
+
aria-label={alignmentAriaLabel}
|
|
58
59
|
onKeyDown={event => {
|
|
59
60
|
if (onMsaKey(model, event)) {
|
|
60
61
|
event.preventDefault()
|
|
@@ -5,7 +5,7 @@ import { useTheme } from '@mui/material'
|
|
|
5
5
|
import { observer } from 'mobx-react'
|
|
6
6
|
|
|
7
7
|
import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
|
|
8
|
-
import { drawMsaRaster,
|
|
8
|
+
import { drawMsaRaster, rasterApplies } from './msaRaster.ts'
|
|
9
9
|
import { renderBoxFeatureCanvasBlock } from './renderBoxFeatureCanvasBlock.ts'
|
|
10
10
|
import { renderMSABlock } from './renderMSABlock.ts'
|
|
11
11
|
import { useMsaBlockMouse } from './useMsaBlockMouse.ts'
|
|
@@ -38,11 +38,7 @@ const MSACanvasBlock = observer(function ({
|
|
|
38
38
|
offsetY,
|
|
39
39
|
model,
|
|
40
40
|
})
|
|
41
|
-
|
|
42
|
-
// domain overlay paints its own boxes instead, and letter-color mode has
|
|
43
|
-
// no background to paint
|
|
44
|
-
const rasterTiles =
|
|
45
|
-
!model.actuallyShowDomains && model.bgColor && rasterSupported()
|
|
41
|
+
const rasterTiles = rasterApplies(model)
|
|
46
42
|
if (rasterTiles) {
|
|
47
43
|
drawMsaRaster({ ctx, model, theme, offsetX, offsetY })
|
|
48
44
|
}
|