react-msaview 8.2.0 → 8.4.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 +77 -300
- package/bundle/index.js.map +4 -4
- package/dist/blockFasta.d.ts +6 -0
- package/dist/blockFasta.js +12 -0
- package/dist/blockFasta.js.map +1 -0
- package/dist/colorSchemes.d.ts +7 -0
- package/dist/colorSchemes.js +15 -0
- package/dist/colorSchemes.js.map +1 -1
- 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/MSAView.js +15 -9
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +39 -7
- package/dist/components/MSAViewer.js +72 -22
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/MetadataTable.d.ts +5 -0
- package/dist/components/MetadataTable.js +29 -0
- package/dist/components/MetadataTable.js.map +1 -0
- 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 +13 -4
- 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/dialogs/MetadataDialog.js +5 -6
- package/dist/components/dialogs/MetadataDialog.js.map +1 -1
- package/dist/components/getVisibleLeaves.d.ts +1 -1
- package/dist/components/header/ColorSchemeMenu.d.ts +6 -0
- package/dist/components/header/ColorSchemeMenu.js +23 -5
- package/dist/components/header/ColorSchemeMenu.js.map +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/{HeaderStatusArea.d.ts → GoToBox.d.ts} +2 -2
- package/dist/components/header/GoToBox.js +21 -0
- package/dist/components/header/GoToBox.js.map +1 -0
- package/dist/components/header/Header.js +8 -7
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/SelectionMenu.d.ts +12 -0
- package/dist/components/header/SelectionMenu.js +72 -0
- package/dist/components/header/SelectionMenu.js.map +1 -0
- package/dist/components/header/goTo.d.ts +20 -0
- package/dist/components/header/goTo.js +69 -0
- package/dist/components/header/goTo.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 +17 -1
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +6 -6
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/msaKeys.d.ts +5 -0
- package/dist/components/msa/msaKeys.js +41 -0
- package/dist/components/msa/msaKeys.js.map +1 -0
- package/dist/components/msa/msaRaster.js +1 -0
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/components/msa/renderHighlights.d.ts +14 -3
- package/dist/components/msa/renderHighlights.js +29 -4
- package/dist/components/msa/renderHighlights.js.map +1 -1
- package/dist/components/msa/tileColor.js +9 -8
- package/dist/components/msa/tileColor.js.map +1 -1
- package/dist/components/msa/useMsaBlockMouse.d.ts +2 -4
- package/dist/components/msa/useMsaBlockMouse.js +34 -14
- package/dist/components/msa/useMsaBlockMouse.js.map +1 -1
- package/dist/components/overlayColors.d.ts +2 -0
- package/dist/components/overlayColors.js +3 -0
- package/dist/components/overlayColors.js.map +1 -1
- package/dist/components/rowpanels/RowPanelBlock.js +10 -14
- package/dist/components/rowpanels/RowPanelBlock.js.map +1 -1
- package/dist/components/selectionDrag.d.ts +17 -0
- package/dist/components/selectionDrag.js +30 -0
- package/dist/components/selectionDrag.js.map +1 -0
- 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 +4 -6
- package/dist/components/tracks/useTrackHover.js +30 -10
- 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/dialogs/TreeNodeInfoDialog.js +9 -8
- package/dist/components/tree/dialogs/TreeNodeInfoDialog.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 +3 -0
- package/dist/constants.js +12 -3
- package/dist/constants.js.map +1 -1
- package/dist/element.d.ts +7 -3
- package/dist/element.js +32 -4
- package/dist/element.js.map +1 -1
- package/dist/expandSpec.js +3 -3
- package/dist/expandSpec.js.map +1 -1
- package/dist/exportFileName.d.ts +3 -3
- package/dist/exportFileName.js +4 -4
- package/dist/exportFileName.js.map +1 -1
- package/dist/featuresLayer.d.ts +8 -0
- package/dist/featuresLayer.js +42 -0
- package/dist/featuresLayer.js.map +1 -0
- 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 +2 -2
- package/dist/model/DataModel.d.ts +1 -1
- package/dist/model/DataModel.js +3 -3
- package/dist/model/DataModel.js.map +1 -1
- package/dist/model/msaModel.d.ts +14 -1
- package/dist/model/msaModel.js +17 -1
- package/dist/model/msaModel.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 +285 -53
- package/dist/model.js +693 -231
- 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 +172 -26
- package/dist/types.d.ts +75 -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/blockFasta.ts +18 -0
- package/src/colorSchemes.ts +20 -0
- 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/MSAView.tsx +16 -9
- package/src/components/MSAViewer.tsx +121 -26
- package/src/components/MetadataTable.tsx +55 -0
- package/src/components/SequenceTextArea.tsx +54 -8
- package/src/components/StatusMessage.tsx +70 -0
- package/src/components/Track.tsx +21 -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/dialogs/MetadataDialog.tsx +9 -8
- package/src/components/header/ColorSchemeMenu.tsx +31 -12
- package/src/components/header/FileMenu.tsx +9 -0
- package/src/components/header/GappynessSlider.tsx +17 -4
- package/src/components/header/GoToBox.tsx +50 -0
- package/src/components/header/Header.tsx +11 -8
- package/src/components/header/SelectionMenu.tsx +117 -0
- package/src/components/header/goTo.ts +75 -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 +19 -1
- package/src/components/msa/MSACanvasBlock.tsx +9 -11
- package/src/components/msa/msaKeys.ts +45 -0
- package/src/components/msa/msaRaster.ts +1 -0
- package/src/components/msa/renderHighlights.ts +46 -4
- package/src/components/msa/tileColor.ts +9 -8
- package/src/components/msa/useMsaBlockMouse.ts +44 -31
- package/src/components/overlayColors.ts +4 -0
- package/src/components/rowpanels/RowPanelBlock.tsx +16 -23
- package/src/components/selectionDrag.ts +42 -0
- package/src/components/tracks/drawTracks.ts +15 -2
- package/src/components/tracks/useTrackHover.ts +32 -10
- 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/dialogs/TreeNodeInfoDialog.tsx +14 -16
- 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/element.ts +45 -5
- package/src/expandSpec.ts +3 -3
- package/src/exportFileName.ts +4 -3
- package/src/featuresLayer.ts +48 -0
- package/src/hierarchy.ts +151 -4
- package/src/index.ts +8 -1
- package/src/model/DataModel.ts +3 -3
- package/src/model/msaModel.ts +19 -1
- package/src/model/treeModel.ts +22 -0
- package/src/model.ts +841 -272
- package/src/renderToSvg.tsx +6 -9
- package/src/rerootTree.ts +172 -0
- package/src/types.ts +92 -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
|
@@ -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,11 +1,10 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
|
-
import Attributes from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail/Attributes'
|
|
4
|
-
import BaseCard from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail/BaseCard'
|
|
5
3
|
import { Dialog } from '@jbrowse/core/ui'
|
|
6
|
-
import { DialogContent } from '@mui/material'
|
|
4
|
+
import { DialogContent, Typography } from '@mui/material'
|
|
7
5
|
import { observer } from 'mobx-react'
|
|
8
6
|
|
|
7
|
+
import MetadataTable, { isEmpty } from '../MetadataTable.tsx'
|
|
9
8
|
import SequenceTextArea from '../SequenceTextArea.tsx'
|
|
10
9
|
|
|
11
10
|
import type { MsaViewModel } from '../../model.ts'
|
|
@@ -18,7 +17,6 @@ const MetadataDialog = observer(function ({
|
|
|
18
17
|
onClose: () => void
|
|
19
18
|
}) {
|
|
20
19
|
const { header } = model
|
|
21
|
-
|
|
22
20
|
return (
|
|
23
21
|
<Dialog
|
|
24
22
|
onClose={() => {
|
|
@@ -29,10 +27,13 @@ const MetadataDialog = observer(function ({
|
|
|
29
27
|
maxWidth="xl"
|
|
30
28
|
>
|
|
31
29
|
<DialogContent>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
{Object.values(header).every(isEmpty) ? (
|
|
31
|
+
<Typography>This alignment has no metadata.</Typography>
|
|
32
|
+
) : (
|
|
33
|
+
<MetadataTable record={header} />
|
|
34
|
+
)}
|
|
35
|
+
<Typography variant="h6">Sequences</Typography>
|
|
36
|
+
<SequenceTextArea str={model.rows} />
|
|
36
37
|
</DialogContent>
|
|
37
38
|
</Dialog>
|
|
38
39
|
)
|
|
@@ -8,8 +8,37 @@ import colorSchemes from '../../colorSchemes.ts'
|
|
|
8
8
|
|
|
9
9
|
import type { MsaViewModel } from '../../model.ts'
|
|
10
10
|
|
|
11
|
+
// While a `customColorScheme` map is set, a checked Custom entry stands for it
|
|
12
|
+
// and no named scheme is checked. Picking a named scheme clears the map.
|
|
13
|
+
export function colorSchemeMenuItems(model: MsaViewModel) {
|
|
14
|
+
const { colorSchemeName, customColorScheme } = model
|
|
15
|
+
const custom = customColorScheme
|
|
16
|
+
? [
|
|
17
|
+
{
|
|
18
|
+
label: 'Custom',
|
|
19
|
+
type: 'radio',
|
|
20
|
+
checked: true,
|
|
21
|
+
onClick: () => {},
|
|
22
|
+
} as const,
|
|
23
|
+
]
|
|
24
|
+
: []
|
|
25
|
+
return [
|
|
26
|
+
...custom,
|
|
27
|
+
...Object.keys(colorSchemes).map(
|
|
28
|
+
option =>
|
|
29
|
+
({
|
|
30
|
+
label: option,
|
|
31
|
+
type: 'radio',
|
|
32
|
+
checked: !customColorScheme && colorSchemeName === option,
|
|
33
|
+
onClick: () => {
|
|
34
|
+
model.setColorSchemeName(option)
|
|
35
|
+
},
|
|
36
|
+
}) as const,
|
|
37
|
+
),
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
|
|
11
41
|
const ColorSchemeMenu = observer(function ({ model }: { model: MsaViewModel }) {
|
|
12
|
-
const { colorSchemeName } = model
|
|
13
42
|
return (
|
|
14
43
|
<CascadingMenuButton
|
|
15
44
|
data-testid="color_scheme_menu"
|
|
@@ -17,17 +46,7 @@ const ColorSchemeMenu = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
17
46
|
closeAfterItemClick
|
|
18
47
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
|
19
48
|
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
|
|
20
|
-
menuItems={
|
|
21
|
-
option =>
|
|
22
|
-
({
|
|
23
|
-
label: option,
|
|
24
|
-
type: 'radio',
|
|
25
|
-
checked: colorSchemeName === option,
|
|
26
|
-
onClick: () => {
|
|
27
|
-
model.setColorSchemeName(option)
|
|
28
|
-
},
|
|
29
|
-
}) as const,
|
|
30
|
-
)}
|
|
49
|
+
menuItems={colorSchemeMenuItems(model)}
|
|
31
50
|
>
|
|
32
51
|
<Palette />
|
|
33
52
|
</CascadingMenuButton>
|
|
@@ -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>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { Autocomplete, TextField } from '@mui/material'
|
|
4
|
+
import { observer } from 'mobx-react'
|
|
5
|
+
|
|
6
|
+
import { findableRowNames, goTo, rowNameSuggestions } from './goTo.ts'
|
|
7
|
+
|
|
8
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
9
|
+
|
|
10
|
+
const label = 'Go to row or column'
|
|
11
|
+
|
|
12
|
+
const GoToBox = observer(function ({ model }: { model: MsaViewModel }) {
|
|
13
|
+
const [missed, setMissed] = useState(false)
|
|
14
|
+
const names = findableRowNames(model)
|
|
15
|
+
return names.length > 1 || model.numColumns > 0 ? (
|
|
16
|
+
<Autocomplete
|
|
17
|
+
freeSolo
|
|
18
|
+
size="small"
|
|
19
|
+
options={names}
|
|
20
|
+
filterOptions={(options, { inputValue }) =>
|
|
21
|
+
rowNameSuggestions(options, inputValue)
|
|
22
|
+
}
|
|
23
|
+
value={null}
|
|
24
|
+
blurOnSelect
|
|
25
|
+
onInputChange={() => {
|
|
26
|
+
setMissed(false)
|
|
27
|
+
}}
|
|
28
|
+
onChange={(_, text) => {
|
|
29
|
+
if (text) {
|
|
30
|
+
setMissed(!goTo(model, text))
|
|
31
|
+
}
|
|
32
|
+
}}
|
|
33
|
+
style={{ width: 180, margin: 'auto 8px' }}
|
|
34
|
+
renderInput={params => (
|
|
35
|
+
<TextField
|
|
36
|
+
{...params}
|
|
37
|
+
error={missed}
|
|
38
|
+
placeholder={label}
|
|
39
|
+
title="A row name, a column number, or name:residue"
|
|
40
|
+
slotProps={{
|
|
41
|
+
...params.slotProps,
|
|
42
|
+
htmlInput: { ...params.slotProps.htmlInput, 'aria-label': label },
|
|
43
|
+
}}
|
|
44
|
+
/>
|
|
45
|
+
)}
|
|
46
|
+
/>
|
|
47
|
+
) : null
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
export default GoToBox
|
|
@@ -5,14 +5,16 @@ 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'
|
|
12
|
+
import GoToBox from './GoToBox.tsx'
|
|
11
13
|
import HeaderInfoArea from './HeaderInfoArea.tsx'
|
|
12
|
-
import HeaderStatusArea from './HeaderStatusArea.tsx'
|
|
13
14
|
import LoadWarnings from './LoadWarnings.tsx'
|
|
14
15
|
import MSASettingsMenu from './MSASettingsMenu.tsx'
|
|
15
16
|
import MultiAlignmentSelector from './MultiAlignmentSelector.tsx'
|
|
17
|
+
import SelectionMenu from './SelectionMenu.tsx'
|
|
16
18
|
import TreeSettingsMenu from './TreeSettingsMenu.tsx'
|
|
17
19
|
import UnshareableDataWarning from './UnshareableDataWarning.tsx'
|
|
18
20
|
import ZoomControls from './ZoomControls.tsx'
|
|
@@ -42,14 +44,19 @@ const Header = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
42
44
|
<ZoomControls model={model} />
|
|
43
45
|
<ZoomMenu model={model} />
|
|
44
46
|
<GappynessSlider model={model} />
|
|
47
|
+
<GoToBox model={model} />
|
|
45
48
|
<div style={{ paddingLeft: 20, margin: 'auto' }}>
|
|
46
49
|
<MultiAlignmentSelector model={model} />
|
|
47
50
|
</div>
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
{/* zero flex-basis, so what appears here on a selection or a hover
|
|
52
|
+
shrinks into the header's spare width and never wraps it */}
|
|
53
|
+
<div style={{ flex: '1 1 0', minWidth: 0, display: 'flex' }}>
|
|
54
|
+
<SelectionMenu model={model} />
|
|
55
|
+
<HeaderInfoArea model={model} />
|
|
56
|
+
</div>
|
|
50
57
|
<LoadWarnings model={model} />
|
|
51
58
|
<UnshareableDataWarning model={model} />
|
|
52
|
-
<
|
|
59
|
+
<StatusMessage model={model} variant="header" />
|
|
53
60
|
<Tooltip title="About">
|
|
54
61
|
<IconButton
|
|
55
62
|
aria-label="About"
|
|
@@ -64,8 +71,4 @@ const Header = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
64
71
|
)
|
|
65
72
|
})
|
|
66
73
|
|
|
67
|
-
function Spacer() {
|
|
68
|
-
return <div style={{ flex: 1 }} />
|
|
69
|
-
}
|
|
70
|
-
|
|
71
74
|
export default Header
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import ArrowDropDown from '@mui/icons-material/ArrowDropDown'
|
|
4
|
+
import Clear from '@mui/icons-material/Clear'
|
|
5
|
+
import ContentCopy from '@mui/icons-material/ContentCopy'
|
|
6
|
+
import ZoomIn from '@mui/icons-material/ZoomIn'
|
|
7
|
+
import {
|
|
8
|
+
Button,
|
|
9
|
+
ListItemIcon,
|
|
10
|
+
ListItemText,
|
|
11
|
+
Menu,
|
|
12
|
+
MenuItem,
|
|
13
|
+
Tooltip,
|
|
14
|
+
} from '@mui/material'
|
|
15
|
+
import { observer } from 'mobx-react'
|
|
16
|
+
|
|
17
|
+
import copy from '../../vendor/copyToClipboard.ts'
|
|
18
|
+
|
|
19
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
20
|
+
|
|
21
|
+
function count(n: number, noun: string) {
|
|
22
|
+
return `${n.toLocaleString('en-US')} ${noun}${n === 1 ? '' : 's'}`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The selected block's size, as a button opening what to do with it. It
|
|
27
|
+
* renders only while there is a selection. Header.tsx puts it in the slot
|
|
28
|
+
* with no width of its own, so the label ellipsizes before it could wrap the
|
|
29
|
+
* header onto a second line.
|
|
30
|
+
*/
|
|
31
|
+
const SelectionMenu = observer(function ({ model }: { model: MsaViewModel }) {
|
|
32
|
+
const [anchorEl, setAnchorEl] = useState<HTMLElement>()
|
|
33
|
+
const { selectionSize, selectionFileSpan } = model
|
|
34
|
+
if (!selectionSize || !selectionFileSpan) {
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
const label = `${count(selectionSize.columns, 'column')} × ${count(selectionSize.rows, 'row')}`
|
|
38
|
+
const items = [
|
|
39
|
+
{
|
|
40
|
+
text: 'Copy as FASTA',
|
|
41
|
+
Icon: ContentCopy,
|
|
42
|
+
act: () => {
|
|
43
|
+
copy(model.selectionFasta)
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
text: 'Zoom to selection',
|
|
48
|
+
Icon: ZoomIn,
|
|
49
|
+
act: () => {
|
|
50
|
+
model.zoomToSelection()
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
text: 'Clear selection',
|
|
55
|
+
Icon: Clear,
|
|
56
|
+
act: () => {
|
|
57
|
+
model.clearSelection()
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
]
|
|
61
|
+
return (
|
|
62
|
+
<>
|
|
63
|
+
<Tooltip
|
|
64
|
+
title={`Columns ${selectionFileSpan.start}-${selectionFileSpan.end} selected. Shift-drag the alignment to select, Escape to clear`}
|
|
65
|
+
>
|
|
66
|
+
<Button
|
|
67
|
+
size="small"
|
|
68
|
+
aria-label={`Selection: ${label}`}
|
|
69
|
+
endIcon={<ArrowDropDown />}
|
|
70
|
+
onClick={event => {
|
|
71
|
+
setAnchorEl(event.currentTarget)
|
|
72
|
+
}}
|
|
73
|
+
style={{
|
|
74
|
+
margin: 'auto 0 auto 8px',
|
|
75
|
+
flexShrink: 0,
|
|
76
|
+
maxWidth: '100%',
|
|
77
|
+
minWidth: 0,
|
|
78
|
+
textTransform: 'none',
|
|
79
|
+
whiteSpace: 'nowrap',
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
|
83
|
+
{label}
|
|
84
|
+
</span>
|
|
85
|
+
</Button>
|
|
86
|
+
</Tooltip>
|
|
87
|
+
{anchorEl ? (
|
|
88
|
+
<Menu
|
|
89
|
+
anchorEl={anchorEl}
|
|
90
|
+
open
|
|
91
|
+
transitionDuration={0}
|
|
92
|
+
onClose={() => {
|
|
93
|
+
setAnchorEl(undefined)
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
{items.map(({ text, Icon, act }) => (
|
|
97
|
+
<MenuItem
|
|
98
|
+
key={text}
|
|
99
|
+
dense
|
|
100
|
+
onClick={() => {
|
|
101
|
+
act()
|
|
102
|
+
setAnchorEl(undefined)
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
<ListItemIcon>
|
|
106
|
+
<Icon fontSize="small" />
|
|
107
|
+
</ListItemIcon>
|
|
108
|
+
<ListItemText>{text}</ListItemText>
|
|
109
|
+
</MenuItem>
|
|
110
|
+
))}
|
|
111
|
+
</Menu>
|
|
112
|
+
) : null}
|
|
113
|
+
</>
|
|
114
|
+
)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
export default SelectionMenu
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
// not MUI's createFilterOptions: JBrowse hosts do not re-export it, so
|
|
11
|
+
// jbrowse-plugin-msaview would fail to load
|
|
12
|
+
export function rowNameSuggestions(names: string[], input: string, limit = 50) {
|
|
13
|
+
const query = input.toLowerCase()
|
|
14
|
+
return names
|
|
15
|
+
.filter(name => name.toLowerCase().includes(query))
|
|
16
|
+
.slice(0, limit)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The visible row and column a "Go to" entry names: a row by its name, the
|
|
21
|
+
* column the ruler numbers N, or residue N of a row written `name:N`. A name
|
|
22
|
+
* is tried whole first, so a row called `chr1:100` stays reachable.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveGoTo(model: MsaViewModel, text: string) {
|
|
25
|
+
const query = text.trim()
|
|
26
|
+
const row = model.rowNamesSet.get(query)
|
|
27
|
+
if (row !== undefined) {
|
|
28
|
+
return { row }
|
|
29
|
+
}
|
|
30
|
+
const residue = /^(.+):(\d+)$/.exec(query)
|
|
31
|
+
if (residue) {
|
|
32
|
+
const [, name = '', pos] = residue
|
|
33
|
+
const rowIndex = model.rowNamesSet.get(name)
|
|
34
|
+
const col =
|
|
35
|
+
rowIndex === undefined ? undefined : columnOfResidue(model, name, +pos!)
|
|
36
|
+
return col === undefined ? undefined : { row: rowIndex, col }
|
|
37
|
+
}
|
|
38
|
+
if (/^\d+$/.test(query)) {
|
|
39
|
+
const n = +query
|
|
40
|
+
const { relativeTo, numColumns } = model
|
|
41
|
+
const col = relativeTo
|
|
42
|
+
? columnOfResidue(model, relativeTo, n)
|
|
43
|
+
: n >= 1 && n <= numColumns
|
|
44
|
+
? n - 1
|
|
45
|
+
: undefined
|
|
46
|
+
return col === undefined ? undefined : { col }
|
|
47
|
+
}
|
|
48
|
+
return undefined
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// seqPosToVisibleCol maps a residue past the row's end to the column after
|
|
52
|
+
// it; here that residue names nothing
|
|
53
|
+
function columnOfResidue(model: MsaViewModel, name: string, pos: number) {
|
|
54
|
+
const globalCol = model.seqPosIndex(name)?.[pos - 1]
|
|
55
|
+
return globalCol === undefined
|
|
56
|
+
? undefined
|
|
57
|
+
: model.globalColToVisibleCol(globalCol)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** centers the view on the entry's target and lights it; false if none */
|
|
61
|
+
export function goTo(model: MsaViewModel, text: string) {
|
|
62
|
+
const target = resolveGoTo(model, text)
|
|
63
|
+
if (target) {
|
|
64
|
+
const { row, col } = target
|
|
65
|
+
const { rowHeight, colWidth, msaAreaHeight, msaAreaWidth } = model
|
|
66
|
+
if (row !== undefined) {
|
|
67
|
+
model.setScrollY(-(row + 0.5) * rowHeight + msaAreaHeight / 2)
|
|
68
|
+
}
|
|
69
|
+
if (col !== undefined) {
|
|
70
|
+
model.setScrollX(-(col + 0.5) * colWidth + msaAreaWidth / 2)
|
|
71
|
+
}
|
|
72
|
+
model.setMousePos(col, row)
|
|
73
|
+
}
|
|
74
|
+
return target !== undefined
|
|
75
|
+
}
|
|
@@ -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
|
}),
|