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
|
@@ -13,13 +13,19 @@ import type { MsaViewModel } from '../model.ts'
|
|
|
13
13
|
import type {
|
|
14
14
|
Cell,
|
|
15
15
|
Clade,
|
|
16
|
+
ColorScheme,
|
|
16
17
|
ColumnTrackSpec,
|
|
17
18
|
Encoding,
|
|
19
|
+
Feature,
|
|
18
20
|
Highlight,
|
|
21
|
+
MsaSelection,
|
|
19
22
|
Region,
|
|
20
23
|
ResidueEncoding,
|
|
21
24
|
ResidueMapping,
|
|
25
|
+
RowDataInput,
|
|
22
26
|
RowPanelSpec,
|
|
27
|
+
TreeOrder,
|
|
28
|
+
TreeRoot,
|
|
23
29
|
Viewport,
|
|
24
30
|
} from '../types.ts'
|
|
25
31
|
import type { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
|
|
@@ -42,7 +48,11 @@ export interface MSAViewerProps {
|
|
|
42
48
|
msaFilehandle?: FileLocationType
|
|
43
49
|
treeFilehandle?: FileLocationType
|
|
44
50
|
gffFilehandle?: FileLocationType
|
|
45
|
-
|
|
51
|
+
/**
|
|
52
|
+
* a scheme name from the built-in table, or `{map}`, a color per residue
|
|
53
|
+
* letter that leaves every other letter uncolored
|
|
54
|
+
*/
|
|
55
|
+
colorScheme?: ColorScheme
|
|
46
56
|
height?: number
|
|
47
57
|
/** initial per-column pixel width (zoom level) */
|
|
48
58
|
colWidth?: number
|
|
@@ -60,6 +70,13 @@ export interface MSAViewerProps {
|
|
|
60
70
|
* for whole rows, each with an optional `label` and `color`
|
|
61
71
|
*/
|
|
62
72
|
highlights?: Highlight[]
|
|
73
|
+
/**
|
|
74
|
+
* features on the rows as data, drawn with the GFF's: `{row, start, end}` in
|
|
75
|
+
* residues of that row, 1-based and inclusive, with an optional `name`,
|
|
76
|
+
* `description`, `type`, `strand` and `color`, and any other field an
|
|
77
|
+
* encoding reads (see docs/layers.md)
|
|
78
|
+
*/
|
|
79
|
+
features?: Feature[]
|
|
63
80
|
/**
|
|
64
81
|
* clades of the tree with a mark drawn over them: `{mrca, tips, mark}` or
|
|
65
82
|
* `{range, tips, mark}`, where `mark` is `highlight`, `bracket`, `collapse`
|
|
@@ -73,7 +90,7 @@ export interface MSAViewerProps {
|
|
|
73
90
|
/** which residue of which structure each row's residues are (see docs/layers.md) */
|
|
74
91
|
residueMappings?: ResidueMapping[]
|
|
75
92
|
/** extra fields per row name, such as a lineage or a host (see docs/layers.md) */
|
|
76
|
-
rowData?:
|
|
93
|
+
rowData?: RowDataInput
|
|
77
94
|
/**
|
|
78
95
|
* what the viewer's marks read from a table: `{channel, field, scale?}`,
|
|
79
96
|
* where `channel` is `tipLabel`, `rowTint` or `branch` over `rowData`, or
|
|
@@ -94,6 +111,17 @@ export interface MSAViewerProps {
|
|
|
94
111
|
autoTreeAreaWidth?: boolean
|
|
95
112
|
/** draw branch lengths (default true); false draws a cladogram */
|
|
96
113
|
showBranchLen?: boolean
|
|
114
|
+
/**
|
|
115
|
+
* the order each node's children draw in: `branchLength` (default,
|
|
116
|
+
* shortest first), `input` (as the file gives them), `ladderize` (fewest
|
|
117
|
+
* tips first) or `ladderizeReverse` (most tips first)
|
|
118
|
+
*/
|
|
119
|
+
treeOrder?: TreeOrder
|
|
120
|
+
/**
|
|
121
|
+
* reroot the tree: `midpoint`, or `{outgroup: [tip names]}` on the branch
|
|
122
|
+
* above those tips' common ancestor
|
|
123
|
+
*/
|
|
124
|
+
treeRoot?: TreeRoot
|
|
97
125
|
/**
|
|
98
126
|
* which channel `colorScheme` paints: `fill` (default) colors the background
|
|
99
127
|
* of the cell a residue sits in, `color` colors the letter itself and leaves
|
|
@@ -107,6 +135,11 @@ export interface MSAViewerProps {
|
|
|
107
135
|
* in columns, 1-based and inclusive
|
|
108
136
|
*/
|
|
109
137
|
region?: Region
|
|
138
|
+
/**
|
|
139
|
+
* the selected block: `{start, end}` columns of the file, 1-based and
|
|
140
|
+
* inclusive, and `rows` by name, every row where absent
|
|
141
|
+
*/
|
|
142
|
+
selection?: MsaSelection
|
|
110
143
|
/** leave out the toolbar, for a page drawing its own controls */
|
|
111
144
|
hideHeader?: boolean
|
|
112
145
|
/**
|
|
@@ -120,43 +153,48 @@ export interface MSAViewerProps {
|
|
|
120
153
|
onCellClick?: (cell: Cell | undefined) => void
|
|
121
154
|
/** the alignment columns on screen, after every scroll, zoom and resize */
|
|
122
155
|
onViewportChange?: (viewport: Viewport | undefined) => void
|
|
156
|
+
/**
|
|
157
|
+
* the selected block, after each change a drag, the header menu, Escape or
|
|
158
|
+
* the `selection` prop makes, or undefined when it clears
|
|
159
|
+
*/
|
|
160
|
+
onSelectionChange?: (selection: MsaSelection | undefined) => void
|
|
123
161
|
/**
|
|
124
162
|
* the model the viewer built, for the model API; called again with the new
|
|
125
|
-
* model when msa, tree,
|
|
163
|
+
* model when msa, tree, msaFilehandle or treeFilehandle changes
|
|
126
164
|
*/
|
|
127
165
|
onModel?: (model: MsaViewModel) => void
|
|
128
166
|
}
|
|
129
167
|
|
|
168
|
+
function colorSchemeSnapshot(scheme: ColorScheme | undefined) {
|
|
169
|
+
return typeof scheme === 'string'
|
|
170
|
+
? { colorSchemeName: scheme }
|
|
171
|
+
: scheme
|
|
172
|
+
? { customColorScheme: scheme.map }
|
|
173
|
+
: {}
|
|
174
|
+
}
|
|
175
|
+
|
|
130
176
|
type DataSource = Pick<
|
|
131
177
|
MSAViewerProps,
|
|
132
|
-
'msa' | 'tree' | '
|
|
178
|
+
'msa' | 'tree' | 'msaFilehandle' | 'treeFilehandle'
|
|
133
179
|
>
|
|
134
180
|
|
|
135
181
|
function sameSource(a: DataSource, b: DataSource) {
|
|
136
182
|
return (
|
|
137
183
|
a.msa === b.msa &&
|
|
138
184
|
a.tree === b.tree &&
|
|
139
|
-
a.
|
|
140
|
-
|
|
141
|
-
JSON.stringify([b.msaFilehandle, b.treeFilehandle, b.gffFilehandle])
|
|
185
|
+
JSON.stringify([a.msaFilehandle, a.treeFilehandle]) ===
|
|
186
|
+
JSON.stringify([b.msaFilehandle, b.treeFilehandle])
|
|
142
187
|
)
|
|
143
188
|
}
|
|
144
189
|
|
|
145
190
|
/**
|
|
146
|
-
* A new alignment
|
|
147
|
-
*
|
|
148
|
-
*
|
|
191
|
+
* A new alignment or tree needs a new model, so the viewer remounts when one
|
|
192
|
+
* of those props changes and keeps its model across every other change,
|
|
193
|
+
* including a new annotation file.
|
|
149
194
|
*/
|
|
150
195
|
export default function MSAViewer(props: MSAViewerProps) {
|
|
151
|
-
const { msa, tree,
|
|
152
|
-
const source = {
|
|
153
|
-
msa,
|
|
154
|
-
tree,
|
|
155
|
-
gff,
|
|
156
|
-
msaFilehandle,
|
|
157
|
-
treeFilehandle,
|
|
158
|
-
gffFilehandle,
|
|
159
|
-
}
|
|
196
|
+
const { msa, tree, msaFilehandle, treeFilehandle } = props
|
|
197
|
+
const source = { msa, tree, msaFilehandle, treeFilehandle }
|
|
160
198
|
const [shown, setShown] = useState({ source, generation: 0 })
|
|
161
199
|
if (!sameSource(shown.source, source)) {
|
|
162
200
|
setShown({ source, generation: shown.generation + 1 })
|
|
@@ -164,7 +202,9 @@ export default function MSAViewer(props: MSAViewerProps) {
|
|
|
164
202
|
return <Viewer key={shown.generation} {...props} />
|
|
165
203
|
}
|
|
166
204
|
|
|
167
|
-
function useModelReaction<
|
|
205
|
+
function useModelReaction<
|
|
206
|
+
K extends 'hoveredCell' | 'clickedCell' | 'viewport' | 'selection',
|
|
207
|
+
>(
|
|
168
208
|
model: MsaViewModel,
|
|
169
209
|
key: K,
|
|
170
210
|
handler: ((value: MsaViewModel[K]) => void) | undefined,
|
|
@@ -203,6 +243,7 @@ function Viewer({
|
|
|
203
243
|
allowedGappyness,
|
|
204
244
|
highlightColumns,
|
|
205
245
|
highlights,
|
|
246
|
+
features,
|
|
206
247
|
clades,
|
|
207
248
|
relativeTo,
|
|
208
249
|
columnTracks,
|
|
@@ -215,12 +256,16 @@ function Viewer({
|
|
|
215
256
|
treeAreaWidth,
|
|
216
257
|
autoTreeAreaWidth,
|
|
217
258
|
showBranchLen,
|
|
259
|
+
treeOrder,
|
|
260
|
+
treeRoot,
|
|
218
261
|
region,
|
|
262
|
+
selection,
|
|
219
263
|
hideHeader,
|
|
220
264
|
theme,
|
|
221
265
|
onCellHover,
|
|
222
266
|
onCellClick,
|
|
223
267
|
onViewportChange,
|
|
268
|
+
onSelectionChange,
|
|
224
269
|
onModel,
|
|
225
270
|
}: MSAViewerProps) {
|
|
226
271
|
const [model] = useState(() =>
|
|
@@ -241,13 +286,15 @@ function Viewer({
|
|
|
241
286
|
...(msaFilehandle ? { msaFilehandle } : {}),
|
|
242
287
|
...(treeFilehandle ? { treeFilehandle } : {}),
|
|
243
288
|
...(gffFilehandle ? { gffFilehandle } : {}),
|
|
244
|
-
...(colorScheme
|
|
289
|
+
...colorSchemeSnapshot(colorScheme),
|
|
245
290
|
...(height ? { height } : {}),
|
|
246
291
|
...(colWidth ? { colWidth } : {}),
|
|
247
292
|
...(rowHeight ? { rowHeight } : {}),
|
|
248
293
|
...(allowedGappyness !== undefined ? { allowedGappyness } : {}),
|
|
249
294
|
...(highlightColumns ? { highlightColumns } : {}),
|
|
250
295
|
...(highlights ? { highlights } : {}),
|
|
296
|
+
...(features ? { features } : {}),
|
|
297
|
+
...(selection ? { selection } : {}),
|
|
251
298
|
...(clades ? { clades } : {}),
|
|
252
299
|
...(relativeTo ? { relativeTo } : {}),
|
|
253
300
|
...(columnTracks ? { columnTracks } : {}),
|
|
@@ -258,6 +305,8 @@ function Viewer({
|
|
|
258
305
|
...(treeAreaWidth ? { treeAreaWidth } : {}),
|
|
259
306
|
...(autoTreeAreaWidth ? { autoTreeAreaWidth } : {}),
|
|
260
307
|
...(showBranchLen !== undefined ? { showBranchLen } : {}),
|
|
308
|
+
...(treeOrder ? { treeOrder } : {}),
|
|
309
|
+
...(treeRoot ? { treeRoot } : {}),
|
|
261
310
|
...(residueEncoding !== undefined
|
|
262
311
|
? { bgColor: residueEncoding === 'fill' }
|
|
263
312
|
: {}),
|
|
@@ -282,11 +331,6 @@ function Viewer({
|
|
|
282
331
|
model.setHeight(height)
|
|
283
332
|
}
|
|
284
333
|
}, [model, height])
|
|
285
|
-
useEffect(() => {
|
|
286
|
-
if (colorScheme !== undefined) {
|
|
287
|
-
model.setColorSchemeName(colorScheme)
|
|
288
|
-
}
|
|
289
|
-
}, [model, colorScheme])
|
|
290
334
|
useEffect(() => {
|
|
291
335
|
if (colWidth !== undefined) {
|
|
292
336
|
model.setColWidth(colWidth)
|
|
@@ -317,6 +361,11 @@ function Viewer({
|
|
|
317
361
|
model.setShowBranchLen(showBranchLen)
|
|
318
362
|
}
|
|
319
363
|
}, [model, showBranchLen])
|
|
364
|
+
useEffect(() => {
|
|
365
|
+
if (treeOrder !== undefined) {
|
|
366
|
+
model.setTreeOrder(treeOrder)
|
|
367
|
+
}
|
|
368
|
+
}, [model, treeOrder])
|
|
320
369
|
useEffect(() => {
|
|
321
370
|
if (residueEncoding !== undefined) {
|
|
322
371
|
model.setBgColor(residueEncoding === 'fill')
|
|
@@ -330,12 +379,57 @@ function Viewer({
|
|
|
330
379
|
model.drawRelativeTo(relativeTo)
|
|
331
380
|
}, [model, relativeTo])
|
|
332
381
|
|
|
382
|
+
// The snapshot already holds the first gff and gffFilehandle, so these
|
|
383
|
+
// effects apply only a later change. Removing either one sets the text back
|
|
384
|
+
// to the gff prop, which clears the annotations when that is absent too.
|
|
385
|
+
const appliedGff = useRef(gff)
|
|
386
|
+
const gffFilehandleKey = JSON.stringify(gffFilehandle ?? null)
|
|
387
|
+
const appliedGffFilehandle = useRef(gffFilehandleKey)
|
|
388
|
+
useEffect(() => {
|
|
389
|
+
if (appliedGffFilehandle.current !== gffFilehandleKey) {
|
|
390
|
+
appliedGffFilehandle.current = gffFilehandleKey
|
|
391
|
+
const location = JSON.parse(gffFilehandleKey) as FileLocationType | null
|
|
392
|
+
if (location) {
|
|
393
|
+
model.setGFFFilehandle(location)
|
|
394
|
+
} else {
|
|
395
|
+
model.setGFF(appliedGff.current)
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}, [model, gffFilehandleKey])
|
|
399
|
+
useEffect(() => {
|
|
400
|
+
if (appliedGff.current !== gff) {
|
|
401
|
+
appliedGff.current = gff
|
|
402
|
+
model.setGFF(gff)
|
|
403
|
+
}
|
|
404
|
+
}, [model, gff])
|
|
405
|
+
|
|
333
406
|
// keyed by content, since a host computing a layer inline passes a new array
|
|
334
407
|
// on every render
|
|
408
|
+
const colorSchemeKey = JSON.stringify(colorScheme ?? null)
|
|
409
|
+
useEffect(() => {
|
|
410
|
+
const scheme = JSON.parse(colorSchemeKey) as ColorScheme | null
|
|
411
|
+
if (typeof scheme === 'string') {
|
|
412
|
+
model.setColorSchemeName(scheme)
|
|
413
|
+
} else if (scheme) {
|
|
414
|
+
model.setCustomColorScheme(scheme.map)
|
|
415
|
+
}
|
|
416
|
+
}, [model, colorSchemeKey])
|
|
335
417
|
const highlightsKey = JSON.stringify(highlights ?? [])
|
|
336
418
|
useEffect(() => {
|
|
337
419
|
model.setHighlights(JSON.parse(highlightsKey))
|
|
338
420
|
}, [model, highlightsKey])
|
|
421
|
+
const featuresKey = JSON.stringify(features ?? [])
|
|
422
|
+
useEffect(() => {
|
|
423
|
+
model.setFeatures(JSON.parse(featuresKey))
|
|
424
|
+
}, [model, featuresKey])
|
|
425
|
+
const selectionKey = JSON.stringify(selection ?? null)
|
|
426
|
+
useEffect(() => {
|
|
427
|
+
model.setSelection(JSON.parse(selectionKey) ?? undefined)
|
|
428
|
+
}, [model, selectionKey])
|
|
429
|
+
const treeRootKey = JSON.stringify(treeRoot ?? null)
|
|
430
|
+
useEffect(() => {
|
|
431
|
+
model.setTreeRoot(JSON.parse(treeRootKey) ?? undefined)
|
|
432
|
+
}, [model, treeRootKey])
|
|
339
433
|
const cladesKey = JSON.stringify(clades ?? [])
|
|
340
434
|
useEffect(() => {
|
|
341
435
|
model.setClades(JSON.parse(cladesKey))
|
|
@@ -385,6 +479,7 @@ function Viewer({
|
|
|
385
479
|
useModelReaction(model, 'hoveredCell', onCellHover)
|
|
386
480
|
useModelReaction(model, 'clickedCell', onCellClick)
|
|
387
481
|
useModelReaction(model, 'viewport', onViewportChange)
|
|
482
|
+
useModelReaction(model, 'selection', onSelectionChange)
|
|
388
483
|
|
|
389
484
|
const latestOnModel = useRef(onModel)
|
|
390
485
|
useEffect(() => {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { makeStyles } from '@jbrowse/core/util/tss-react'
|
|
4
|
+
import { Table, TableBody, TableCell, TableRow } from '@mui/material'
|
|
5
|
+
|
|
6
|
+
const useStyles = makeStyles()({
|
|
7
|
+
key: {
|
|
8
|
+
fontWeight: 'bold',
|
|
9
|
+
verticalAlign: 'top',
|
|
10
|
+
},
|
|
11
|
+
value: {
|
|
12
|
+
whiteSpace: 'pre-wrap',
|
|
13
|
+
wordBreak: 'break-word',
|
|
14
|
+
},
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
18
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function isEmpty(value: unknown) {
|
|
22
|
+
return isRecord(value) && Object.keys(value).length === 0
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default function MetadataTable({
|
|
26
|
+
record,
|
|
27
|
+
}: {
|
|
28
|
+
record: Record<string, unknown>
|
|
29
|
+
}) {
|
|
30
|
+
const { classes } = useStyles()
|
|
31
|
+
return (
|
|
32
|
+
<Table size="small">
|
|
33
|
+
<TableBody>
|
|
34
|
+
{Object.entries(record)
|
|
35
|
+
.filter(([, value]) => value != null && !isEmpty(value))
|
|
36
|
+
.map(([key, value]) => (
|
|
37
|
+
<TableRow key={key}>
|
|
38
|
+
<TableCell component="th" className={classes.key}>
|
|
39
|
+
{key}
|
|
40
|
+
</TableCell>
|
|
41
|
+
<TableCell className={classes.value}>
|
|
42
|
+
{isRecord(value) ? (
|
|
43
|
+
<MetadataTable record={value} />
|
|
44
|
+
) : Array.isArray(value) ? (
|
|
45
|
+
value.join('\n')
|
|
46
|
+
) : (
|
|
47
|
+
String(value)
|
|
48
|
+
)}
|
|
49
|
+
</TableCell>
|
|
50
|
+
</TableRow>
|
|
51
|
+
))}
|
|
52
|
+
</TableBody>
|
|
53
|
+
</Table>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
|
|
3
3
|
import { makeStyles } from '@jbrowse/core/util/tss-react'
|
|
4
|
-
import { TextField } from '@mui/material'
|
|
4
|
+
import { Button, TextField } from '@mui/material'
|
|
5
5
|
import { getUngappedSequence } from 'msa-parsers'
|
|
6
6
|
|
|
7
|
+
import { saveAs } from '../vendor/fileSaver.ts'
|
|
7
8
|
import Checkbox2 from './Checkbox2.tsx'
|
|
8
9
|
import CopyButton from './CopyButton.tsx'
|
|
9
10
|
|
|
@@ -18,19 +19,59 @@ const useStyles = makeStyles()(theme => ({
|
|
|
18
19
|
},
|
|
19
20
|
}))
|
|
20
21
|
|
|
22
|
+
export const maxShownRecords = 100
|
|
23
|
+
export const maxShownChars = 200_000
|
|
24
|
+
|
|
25
|
+
function fastaRecord([name, seq]: [string, string], showGaps: boolean) {
|
|
26
|
+
return `>${name}\n${showGaps ? seq : getUngappedSequence(seq)}`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isAllGaps(seq: string) {
|
|
30
|
+
return !/[^.-]/.test(seq)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The first records of `rows` as FASTA, up to `maxShownRecords` records or
|
|
35
|
+
* `maxShownChars` characters, whichever comes first. A textarea holding every
|
|
36
|
+
* row of a large alignment freezes the page.
|
|
37
|
+
*/
|
|
38
|
+
export function fastaPreview(rows: [string, string][], showGaps: boolean) {
|
|
39
|
+
const shown: string[] = []
|
|
40
|
+
let chars = 0
|
|
41
|
+
for (const row of rows) {
|
|
42
|
+
if (shown.length >= maxShownRecords || chars >= maxShownChars) {
|
|
43
|
+
break
|
|
44
|
+
}
|
|
45
|
+
const record = fastaRecord(row, showGaps)
|
|
46
|
+
shown.push(record)
|
|
47
|
+
chars += record.length + 1
|
|
48
|
+
}
|
|
49
|
+
return { text: shown.join('\n'), hidden: rows.length - shown.length }
|
|
50
|
+
}
|
|
51
|
+
|
|
21
52
|
export default function SequenceTextArea({ str }: { str: [string, string][] }) {
|
|
22
53
|
const { classes } = useStyles()
|
|
23
54
|
const [showGaps, setShowGaps] = useState(false)
|
|
24
55
|
const [showEmpty, setShowEmpty] = useState(false)
|
|
25
56
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
.filter(([,
|
|
29
|
-
|
|
30
|
-
|
|
57
|
+
const rows = showEmpty
|
|
58
|
+
? str
|
|
59
|
+
: str.filter(([, seq]) => (showGaps ? !!seq : !isAllGaps(seq)))
|
|
60
|
+
const { text, hidden } = fastaPreview(rows, showGaps)
|
|
61
|
+
const fullText = () => rows.map(row => fastaRecord(row, showGaps)).join('\n')
|
|
31
62
|
return (
|
|
32
63
|
<>
|
|
33
|
-
<CopyButton text={
|
|
64
|
+
<CopyButton text={fullText} />
|
|
65
|
+
<Button
|
|
66
|
+
onClick={() => {
|
|
67
|
+
saveAs(
|
|
68
|
+
new Blob([fullText()], { type: 'text/plain' }),
|
|
69
|
+
'sequences.fasta',
|
|
70
|
+
)
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
Download
|
|
74
|
+
</Button>
|
|
34
75
|
<Checkbox2
|
|
35
76
|
label="Show gaps"
|
|
36
77
|
checked={showGaps}
|
|
@@ -52,7 +93,12 @@ export default function SequenceTextArea({ str }: { str: [string, string][] }) {
|
|
|
52
93
|
minRows={5}
|
|
53
94
|
maxRows={10}
|
|
54
95
|
fullWidth
|
|
55
|
-
value={
|
|
96
|
+
value={text}
|
|
97
|
+
helperText={
|
|
98
|
+
hidden > 0
|
|
99
|
+
? `Showing ${rows.length - hidden} of ${rows.length.toLocaleString()} sequences. ${hidden.toLocaleString()} more are in the copy and the download.`
|
|
100
|
+
: undefined
|
|
101
|
+
}
|
|
56
102
|
slotProps={{
|
|
57
103
|
input: {
|
|
58
104
|
readOnly: true,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { LoadingEllipses } from '@jbrowse/core/ui'
|
|
4
|
+
import { makeStyles } from '@jbrowse/core/util/tss-react'
|
|
5
|
+
import { Button, CircularProgress, Typography } from '@mui/material'
|
|
6
|
+
import { observer } from 'mobx-react'
|
|
7
|
+
|
|
8
|
+
import type { MsaViewModel } from '../model.ts'
|
|
9
|
+
|
|
10
|
+
const useStyles = makeStyles()({
|
|
11
|
+
page: {
|
|
12
|
+
display: 'flex',
|
|
13
|
+
alignItems: 'center',
|
|
14
|
+
gap: 12,
|
|
15
|
+
padding: 20,
|
|
16
|
+
},
|
|
17
|
+
header: {
|
|
18
|
+
display: 'flex',
|
|
19
|
+
alignItems: 'center',
|
|
20
|
+
gap: 4,
|
|
21
|
+
margin: 'auto',
|
|
22
|
+
marginLeft: 10,
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The model's status line with its Cancel button: a spinner and a heading in
|
|
28
|
+
* place of the view while it loads, or a line in the header afterwards, which
|
|
29
|
+
* disappears when there is no status.
|
|
30
|
+
*/
|
|
31
|
+
const StatusMessage = observer(function ({
|
|
32
|
+
model,
|
|
33
|
+
variant,
|
|
34
|
+
}: {
|
|
35
|
+
model: MsaViewModel
|
|
36
|
+
variant: 'page' | 'header'
|
|
37
|
+
}) {
|
|
38
|
+
const { status } = model
|
|
39
|
+
const { classes } = useStyles()
|
|
40
|
+
if (variant === 'header' && !status) {
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
const message = status?.msg ?? 'Loading...'
|
|
44
|
+
const onCancel = status?.onCancel
|
|
45
|
+
return (
|
|
46
|
+
<div className={classes[variant]}>
|
|
47
|
+
{variant === 'page' ? (
|
|
48
|
+
<>
|
|
49
|
+
<CircularProgress size={24} />
|
|
50
|
+
<Typography variant="h6">{message}</Typography>
|
|
51
|
+
</>
|
|
52
|
+
) : (
|
|
53
|
+
<LoadingEllipses message={message} component="span" />
|
|
54
|
+
)}
|
|
55
|
+
{onCancel ? (
|
|
56
|
+
<Button
|
|
57
|
+
variant={variant === 'page' ? 'outlined' : 'text'}
|
|
58
|
+
size="small"
|
|
59
|
+
onClick={() => {
|
|
60
|
+
onCancel()
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
Cancel
|
|
64
|
+
</Button>
|
|
65
|
+
) : null}
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
export default StatusMessage
|
package/src/components/Track.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import { IconButton, Menu, MenuItem } from '@mui/material'
|
|
|
7
7
|
import { observer } from 'mobx-react'
|
|
8
8
|
|
|
9
9
|
import { useWheelScroll } from '../useWheelScroll.ts'
|
|
10
|
+
import { onMsaKey } from './msa/msaKeys.ts'
|
|
10
11
|
import TrackTooltipContent from './tracks/TrackTooltipContent.tsx'
|
|
11
12
|
import { useTrackHover } from './tracks/useTrackHover.ts'
|
|
12
13
|
|
|
@@ -146,7 +147,7 @@ const Track = observer(function ({
|
|
|
146
147
|
)
|
|
147
148
|
useWheelScroll({ ref, onScrollX })
|
|
148
149
|
const { classes } = useStyles()
|
|
149
|
-
const {
|
|
150
|
+
const { anchor, onMouseMove, onMouseDown, onMouseLeave } = useTrackHover({
|
|
150
151
|
model,
|
|
151
152
|
tooltip: showColumnStats,
|
|
152
153
|
})
|
|
@@ -162,19 +163,35 @@ const Track = observer(function ({
|
|
|
162
163
|
<div
|
|
163
164
|
ref={ref}
|
|
164
165
|
data-testid={`track_${track.model.id}`}
|
|
166
|
+
tabIndex={-1}
|
|
167
|
+
style={{ outline: 'none' }}
|
|
168
|
+
onKeyDown={event => {
|
|
169
|
+
if (onMsaKey(model, event)) {
|
|
170
|
+
event.preventDefault()
|
|
171
|
+
}
|
|
172
|
+
}}
|
|
165
173
|
onMouseMove={event => {
|
|
166
174
|
if (ref.current) {
|
|
167
175
|
onMouseMove(event, ref.current)
|
|
168
176
|
}
|
|
169
177
|
}}
|
|
178
|
+
onMouseDown={event => {
|
|
179
|
+
if (ref.current) {
|
|
180
|
+
onMouseDown(event, ref.current)
|
|
181
|
+
}
|
|
182
|
+
}}
|
|
170
183
|
onMouseLeave={() => {
|
|
171
184
|
onMouseLeave()
|
|
172
185
|
}}
|
|
173
186
|
>
|
|
174
187
|
<track.ReactComponent model={model} track={track} />
|
|
175
|
-
{
|
|
176
|
-
<BaseTooltip clientPoint={
|
|
177
|
-
<TrackTooltipContent
|
|
188
|
+
{anchor ? (
|
|
189
|
+
<BaseTooltip clientPoint={anchor.clientPoint}>
|
|
190
|
+
<TrackTooltipContent
|
|
191
|
+
model={model}
|
|
192
|
+
track={track}
|
|
193
|
+
col={anchor.value}
|
|
194
|
+
/>
|
|
178
195
|
</BaseTooltip>
|
|
179
196
|
) : null}
|
|
180
197
|
</div>
|
|
@@ -23,6 +23,13 @@ const VerticalScrollbar = observer(({ model }: { model: MsaViewModel }) => {
|
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
25
|
<div
|
|
26
|
+
onMouseDown={event => {
|
|
27
|
+
if (event.button === 0 && event.target === event.currentTarget) {
|
|
28
|
+
const y =
|
|
29
|
+
event.clientY - event.currentTarget.getBoundingClientRect().top
|
|
30
|
+
model.setScrollY(-y / unit + msaAreaHeight / 2)
|
|
31
|
+
}
|
|
32
|
+
}}
|
|
26
33
|
style={{
|
|
27
34
|
position: 'relative',
|
|
28
35
|
width,
|
|
@@ -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
|