react-msaview 7.0.0 → 8.0.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 +11 -10
- package/bundle/index.js +122 -111
- package/bundle/index.js.map +4 -4
- package/dist/colorSchemes.js +1 -2
- package/dist/colorSchemes.js.map +1 -1
- package/dist/columnStats.d.ts +28 -0
- package/dist/columnStats.js +34 -0
- package/dist/columnStats.js.map +1 -0
- package/dist/components/DragHandle.d.ts +3 -2
- package/dist/components/DragHandle.js +11 -5
- package/dist/components/DragHandle.js.map +1 -1
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAView.js +17 -6
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +64 -7
- package/dist/components/MSAViewer.js +128 -23
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/PortalTooltip.d.ts +11 -0
- package/dist/components/PortalTooltip.js +29 -0
- package/dist/components/PortalTooltip.js.map +1 -0
- package/dist/components/ResizeHandles.js +11 -29
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/SequenceTextArea.js +3 -3
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/Track.js +41 -22
- package/dist/components/Track.js.map +1 -1
- package/dist/components/dialogs/ExportSVGDialog.js +5 -3
- package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
- package/dist/components/dialogs/TrackInfoDialog.js +1 -1
- package/dist/components/dialogs/TrackInfoDialog.js.map +1 -1
- package/dist/components/header/GappynessSlider.js +6 -11
- package/dist/components/header/GappynessSlider.js.map +1 -1
- package/dist/components/header/Header.js +3 -0
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/LoadWarnings.js +3 -4
- package/dist/components/header/LoadWarnings.js.map +1 -1
- package/dist/components/header/ScrollZoomIcons.d.ts +6 -0
- package/dist/components/header/ScrollZoomIcons.js +35 -0
- package/dist/components/header/ScrollZoomIcons.js.map +1 -0
- package/dist/components/header/TreeSettingsMenu.js +1 -3
- package/dist/components/header/TreeSettingsMenu.js.map +1 -1
- package/dist/components/header/UnshareableDataWarning.js +2 -4
- package/dist/components/header/UnshareableDataWarning.js.map +1 -1
- package/dist/components/header/ZoomControls.js +49 -6
- package/dist/components/header/ZoomControls.js.map +1 -1
- package/dist/components/header/getAnnotationsMenu.js +3 -6
- package/dist/components/header/getAnnotationsMenu.js.map +1 -1
- package/dist/components/header/settingsMenuItems.js +10 -7
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/import/util.js +1 -2
- package/dist/components/import/util.js.map +1 -1
- package/dist/components/minimap/Minimap.js +1 -2
- package/dist/components/minimap/Minimap.js.map +1 -1
- package/dist/components/msa/AnnotationLegend.js +27 -19
- package/dist/components/msa/AnnotationLegend.js.map +1 -1
- package/dist/components/msa/MSACanvas.js +1 -1
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +13 -15
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/MSAMouseoverCanvas.js +2 -1
- package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
- package/dist/components/msa/domainBandCursor.d.ts +7 -11
- package/dist/components/msa/domainBandCursor.js +7 -11
- package/dist/components/msa/domainBandCursor.js.map +1 -1
- package/dist/components/msa/drawFeatureSpans.d.ts +53 -0
- package/dist/components/msa/drawFeatureSpans.js +78 -0
- package/dist/components/msa/drawFeatureSpans.js.map +1 -0
- package/dist/components/msa/legendRows.d.ts +8 -0
- package/dist/components/msa/legendRows.js +16 -0
- package/dist/components/msa/legendRows.js.map +1 -0
- package/dist/components/msa/msaRaster.d.ts +24 -31
- package/dist/components/msa/msaRaster.js +30 -39
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/components/msa/packDomainLanes.d.ts +20 -0
- package/dist/components/msa/packDomainLanes.js +33 -0
- package/dist/components/msa/packDomainLanes.js.map +1 -0
- package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +3 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +51 -87
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderHighlights.d.ts +15 -13
- package/dist/components/msa/renderHighlights.js +43 -20
- package/dist/components/msa/renderHighlights.js.map +1 -1
- package/dist/components/msa/renderMSABlock.js +5 -4
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/tileColor.js +4 -7
- package/dist/components/msa/tileColor.js.map +1 -1
- package/dist/components/msa/useMsaBlockMouse.d.ts +5 -3
- package/dist/components/msa/useMsaBlockMouse.js +6 -6
- package/dist/components/msa/useMsaBlockMouse.js.map +1 -1
- package/dist/components/rowpanels/RowPanelBlock.d.ts +9 -0
- package/dist/components/rowpanels/RowPanelBlock.js +86 -0
- package/dist/components/rowpanels/RowPanelBlock.js.map +1 -0
- package/dist/components/rowpanels/RowPanelHeaders.d.ts +12 -0
- package/dist/components/rowpanels/RowPanelHeaders.js +38 -0
- package/dist/components/rowpanels/RowPanelHeaders.js.map +1 -0
- package/dist/components/{msa/ColumnStats.d.ts → rowpanels/RowPanels.d.ts} +2 -2
- package/dist/components/rowpanels/RowPanels.js +32 -0
- package/dist/components/rowpanels/RowPanels.js.map +1 -0
- package/dist/components/rowpanels/headerLayout.d.ts +1 -0
- package/dist/components/rowpanels/headerLayout.js +5 -0
- package/dist/components/rowpanels/headerLayout.js.map +1 -0
- package/dist/components/rowpanels/renderFeaturePanel.d.ts +21 -0
- package/dist/components/rowpanels/renderFeaturePanel.js +37 -0
- package/dist/components/rowpanels/renderFeaturePanel.js.map +1 -0
- package/dist/components/rowpanels/renderRowPanel.d.ts +23 -0
- package/dist/components/rowpanels/renderRowPanel.js +22 -0
- package/dist/components/rowpanels/renderRowPanel.js.map +1 -0
- package/dist/components/rowpanels/renderStrip.d.ts +20 -0
- package/dist/components/rowpanels/renderStrip.js +26 -0
- package/dist/components/rowpanels/renderStrip.js.map +1 -0
- package/dist/components/tracks/TrackBlocks.js +3 -5
- package/dist/components/tracks/TrackBlocks.js.map +1 -1
- package/dist/components/tracks/TrackTooltipContent.d.ts +15 -0
- package/dist/components/tracks/TrackTooltipContent.js +109 -0
- package/dist/components/tracks/TrackTooltipContent.js.map +1 -0
- package/dist/components/tracks/drawTracks.d.ts +4 -4
- package/dist/components/tracks/drawTracks.js +17 -34
- package/dist/components/tracks/drawTracks.js.map +1 -1
- package/dist/components/tracks/useTrackHover.d.ts +22 -0
- package/dist/components/tracks/useTrackHover.js +30 -0
- package/dist/components/tracks/useTrackHover.js.map +1 -0
- package/dist/components/tree/CladeLabels.d.ts +12 -0
- package/dist/components/tree/CladeLabels.js +48 -0
- package/dist/components/tree/CladeLabels.js.map +1 -0
- package/dist/components/tree/TreeCanvas.js +4 -1
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +4 -21
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/TreeOverview.d.ts +14 -0
- package/dist/components/tree/TreeOverview.js +75 -0
- package/dist/components/tree/TreeOverview.js.map +1 -0
- package/dist/components/tree/TreeRuler.js +9 -13
- package/dist/components/tree/TreeRuler.js.map +1 -1
- package/dist/components/tree/cladeBrackets.d.ts +25 -0
- package/dist/components/tree/cladeBrackets.js +51 -0
- package/dist/components/tree/cladeBrackets.js.map +1 -0
- package/dist/components/tree/dialogs/TreeNodeInfoDialog.js +2 -2
- package/dist/components/tree/dialogs/TreeNodeInfoDialog.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.js +158 -24
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/tree/renderTreeOverview.d.ts +52 -0
- package/dist/components/tree/renderTreeOverview.js +138 -0
- package/dist/components/tree/renderTreeOverview.js.map +1 -0
- package/dist/components/tree/useTreeHover.js +2 -4
- package/dist/components/tree/useTreeHover.js.map +1 -1
- package/dist/constants.d.ts +15 -0
- package/dist/constants.js +48 -22
- package/dist/constants.js.map +1 -1
- package/dist/createPaletteMap.d.ts +7 -6
- package/dist/createPaletteMap.js +10 -9
- package/dist/createPaletteMap.js.map +1 -1
- package/dist/element.d.ts +13 -0
- package/dist/element.js +269 -0
- package/dist/element.js.map +1 -0
- package/dist/featureFields.d.ts +11 -0
- package/dist/featureFields.js +17 -0
- package/dist/featureFields.js.map +1 -0
- package/dist/flatToTree.js +1 -2
- package/dist/flatToTree.js.map +1 -1
- package/dist/ggplotPalettes.d.ts +10 -0
- package/dist/ggplotPalettes.js +58 -0
- package/dist/ggplotPalettes.js.map +1 -1
- package/dist/hierarchy.d.ts +27 -0
- package/dist/hierarchy.js +66 -9
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/model/DataModel.js +2 -5
- package/dist/model/DataModel.js.map +1 -1
- package/dist/model/msaModel.d.ts +3 -2
- package/dist/model/msaModel.js +3 -2
- package/dist/model/msaModel.js.map +1 -1
- package/dist/model/treeModel.d.ts +30 -0
- package/dist/model/treeModel.js +37 -1
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +531 -275
- package/dist/model.js +1295 -547
- package/dist/model.js.map +1 -1
- package/dist/mount.d.ts +11 -0
- package/dist/mount.js +22 -0
- package/dist/mount.js.map +1 -0
- package/dist/neighborJoining.js +8 -32
- package/dist/neighborJoining.js.map +1 -1
- package/dist/renderToStaticMarkup.d.ts +10 -19
- package/dist/renderToStaticMarkup.js +13 -24
- package/dist/renderToStaticMarkup.js.map +1 -1
- package/dist/renderToSvg.js +180 -56
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rowCoordinateCalculations.d.ts +4 -6
- package/dist/rowCoordinateCalculations.js +4 -6
- package/dist/rowCoordinateCalculations.js.map +1 -1
- package/dist/scales.d.ts +22 -0
- package/dist/scales.js +30 -0
- package/dist/scales.js.map +1 -0
- package/dist/seqPosToGlobalCol.d.ts +2 -3
- package/dist/seqPosToGlobalCol.js +2 -3
- package/dist/seqPosToGlobalCol.js.map +1 -1
- package/dist/sequenceLogo.d.ts +6 -11
- package/dist/sequenceLogo.js +6 -11
- package/dist/sequenceLogo.js.map +1 -1
- package/dist/stripDefault.d.ts +7 -14
- package/dist/stripDefault.js +7 -14
- package/dist/stripDefault.js.map +1 -1
- package/dist/svgTestUtil.d.ts +172 -52
- package/dist/types.d.ts +219 -21
- package/dist/umd.d.ts +2 -0
- package/dist/umd.js +4 -2
- package/dist/umd.js.map +1 -1
- package/dist/useCanvasAutorun.d.ts +5 -10
- package/dist/useCanvasAutorun.js +5 -10
- package/dist/useCanvasAutorun.js.map +1 -1
- package/dist/useMsaSvgFigure.d.ts +20 -0
- package/dist/useMsaSvgFigure.js +46 -0
- package/dist/useMsaSvgFigure.js.map +1 -0
- package/dist/util.d.ts +5 -6
- package/dist/util.js +13 -6
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +19 -12
- package/src/colorSchemes.ts +1 -2
- package/src/columnStats.ts +58 -0
- package/src/components/DragHandle.tsx +18 -7
- package/src/components/Loading.tsx +2 -6
- package/src/components/MSAView.tsx +19 -5
- package/src/components/MSAViewer.tsx +240 -28
- package/src/components/PortalTooltip.tsx +47 -0
- package/src/components/ResizeHandles.tsx +17 -32
- package/src/components/SequenceTextArea.tsx +3 -3
- package/src/components/Track.tsx +55 -18
- package/src/components/dialogs/ExportSVGDialog.tsx +7 -2
- package/src/components/dialogs/TrackInfoDialog.tsx +1 -1
- package/src/components/header/GappynessSlider.tsx +6 -11
- package/src/components/header/Header.tsx +6 -0
- package/src/components/header/LoadWarnings.tsx +3 -4
- package/src/components/header/ScrollZoomIcons.tsx +52 -0
- package/src/components/header/TreeSettingsMenu.tsx +1 -3
- package/src/components/header/UnshareableDataWarning.tsx +2 -4
- package/src/components/header/ZoomControls.tsx +74 -14
- package/src/components/header/getAnnotationsMenu.ts +3 -6
- package/src/components/header/settingsMenuItems.ts +11 -6
- package/src/components/import/ImportFormExamples.tsx +2 -2
- package/src/components/import/util.ts +1 -2
- package/src/components/minimap/Minimap.tsx +1 -2
- package/src/components/msa/AnnotationLegend.tsx +48 -38
- package/src/components/msa/MSACanvas.tsx +3 -5
- package/src/components/msa/MSACanvasBlock.tsx +13 -16
- package/src/components/msa/MSAMouseoverCanvas.tsx +2 -1
- package/src/components/msa/domainBandCursor.ts +7 -11
- package/src/components/msa/drawFeatureSpans.ts +150 -0
- package/src/components/msa/legendRows.ts +24 -0
- package/src/components/msa/msaRaster.ts +30 -39
- package/src/components/msa/packDomainLanes.ts +36 -0
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +69 -123
- package/src/components/msa/renderHighlights.ts +53 -20
- package/src/components/msa/renderMSABlock.ts +5 -3
- package/src/components/msa/tileColor.ts +4 -7
- package/src/components/msa/useMsaBlockMouse.ts +6 -6
- package/src/components/rowpanels/RowPanelBlock.tsx +139 -0
- package/src/components/rowpanels/RowPanelHeaders.tsx +57 -0
- package/src/components/rowpanels/RowPanels.tsx +77 -0
- package/src/components/rowpanels/headerLayout.ts +5 -0
- package/src/components/rowpanels/renderFeaturePanel.ts +66 -0
- package/src/components/rowpanels/renderRowPanel.ts +41 -0
- package/src/components/rowpanels/renderStrip.ts +46 -0
- package/src/components/tracks/TrackBlocks.tsx +3 -5
- package/src/components/tracks/TrackTooltipContent.tsx +156 -0
- package/src/components/tracks/drawTracks.ts +17 -34
- package/src/components/tracks/useTrackHover.ts +42 -0
- package/src/components/tree/CladeLabels.tsx +77 -0
- package/src/components/tree/TreeCanvas.tsx +2 -0
- package/src/components/tree/TreeCanvasBlock.tsx +8 -32
- package/src/components/tree/TreeOverview.tsx +106 -0
- package/src/components/tree/TreeRuler.tsx +11 -22
- package/src/components/tree/cladeBrackets.ts +73 -0
- package/src/components/tree/dialogs/TreeNodeInfoDialog.tsx +2 -2
- package/src/components/tree/renderTreeCanvas.ts +240 -20
- package/src/components/tree/renderTreeOverview.ts +212 -0
- package/src/components/tree/useTreeHover.ts +2 -4
- package/src/constants.ts +56 -22
- package/src/createPaletteMap.ts +10 -9
- package/src/element.ts +321 -0
- package/src/featureFields.ts +19 -0
- package/src/flatToTree.ts +1 -2
- package/src/ggplotPalettes.ts +60 -0
- package/src/hierarchy.ts +77 -9
- package/src/index.ts +31 -7
- package/src/model/DataModel.ts +2 -5
- package/src/model/msaModel.ts +3 -2
- package/src/model/treeModel.ts +42 -0
- package/src/model.ts +1523 -560
- package/src/mount.tsx +32 -0
- package/src/neighborJoining.ts +8 -32
- package/src/renderToStaticMarkup.ts +13 -24
- package/src/renderToSvg.tsx +335 -68
- package/src/rowCoordinateCalculations.ts +4 -6
- package/src/scales.ts +47 -0
- package/src/seqPosToGlobalCol.ts +2 -3
- package/src/sequenceLogo.ts +6 -11
- package/src/stripDefault.ts +7 -14
- package/src/types.ts +258 -45
- package/src/umd.ts +4 -2
- package/src/useCanvasAutorun.ts +5 -10
- package/src/useMsaSvgFigure.ts +72 -0
- package/src/util.ts +15 -6
- package/src/version.ts +1 -1
- package/dist/components/msa/ColumnStats.js +0 -42
- package/dist/components/msa/ColumnStats.js.map +0 -1
- package/src/components/msa/ColumnStats.tsx +0 -54
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ColumnCounts } from './columnCounts.ts'
|
|
2
|
+
|
|
3
|
+
export interface ColumnStats {
|
|
4
|
+
col: number
|
|
5
|
+
total: number
|
|
6
|
+
gaps: number
|
|
7
|
+
gapFraction: number
|
|
8
|
+
conservation: number
|
|
9
|
+
propertyConservation: number | undefined
|
|
10
|
+
consensusLetter: string
|
|
11
|
+
consensusCount: number
|
|
12
|
+
consensusFraction: number
|
|
13
|
+
distribution: [string, number][]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Summarize one column: consensus residue and its identity fraction, the two
|
|
18
|
+
* conservation scores, gap fraction, and the non-gap residue distribution
|
|
19
|
+
* sorted by count. undefined past the end of the alignment or for a column with
|
|
20
|
+
* no sequences.
|
|
21
|
+
*
|
|
22
|
+
* A module-level function: a getter that reached the method through `this` made
|
|
23
|
+
* TypeScript infer the whole views object to type it, and fell back to `any`.
|
|
24
|
+
*/
|
|
25
|
+
export function columnStats({
|
|
26
|
+
col,
|
|
27
|
+
colStats,
|
|
28
|
+
conservation,
|
|
29
|
+
propertyConservation,
|
|
30
|
+
}: {
|
|
31
|
+
col: number
|
|
32
|
+
colStats: ColumnCounts
|
|
33
|
+
conservation: number[]
|
|
34
|
+
propertyConservation: number[]
|
|
35
|
+
}): ColumnStats | undefined {
|
|
36
|
+
if (col >= colStats.numColumns) {
|
|
37
|
+
return undefined
|
|
38
|
+
}
|
|
39
|
+
const total = colStats.total(col)
|
|
40
|
+
if (!total) {
|
|
41
|
+
return undefined
|
|
42
|
+
}
|
|
43
|
+
const gaps = colStats.gapCount(col)
|
|
44
|
+
const distribution = colStats.residueEntries(col).sort((a, b) => b[1] - a[1])
|
|
45
|
+
const consensus = distribution[0]
|
|
46
|
+
return {
|
|
47
|
+
col,
|
|
48
|
+
total,
|
|
49
|
+
gaps,
|
|
50
|
+
gapFraction: gaps / total,
|
|
51
|
+
conservation: conservation[col] ?? 0,
|
|
52
|
+
propertyConservation: propertyConservation[col],
|
|
53
|
+
consensusLetter: consensus?.[0] ?? '',
|
|
54
|
+
consensusCount: consensus?.[1] ?? 0,
|
|
55
|
+
consensusFraction: consensus ? consensus[1] / total : 0,
|
|
56
|
+
distribution,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
|
|
3
|
+
import { useTheme } from '@mui/material'
|
|
4
|
+
|
|
3
5
|
import { useDragScroll } from '../useDragScroll.ts'
|
|
4
6
|
|
|
7
|
+
import type { Theme } from '@mui/material'
|
|
8
|
+
|
|
5
9
|
export const scrollbarThumbFill = 'rgba(66,119,127,0.3)'
|
|
6
10
|
|
|
7
11
|
// The scroll thumbs (minimap, vertical scrollbar) and the resize dividers are
|
|
@@ -11,23 +15,29 @@ export const scrollbarThumbFill = 'rgba(66,119,127,0.3)'
|
|
|
11
15
|
const variants: Record<
|
|
12
16
|
'thumb' | 'resizer',
|
|
13
17
|
{
|
|
14
|
-
fill: string
|
|
15
|
-
fillHovered: string
|
|
18
|
+
fill: (theme: Theme) => string
|
|
19
|
+
fillHovered: (theme: Theme) => string
|
|
16
20
|
cursor: (axis: 'x' | 'y') => string
|
|
17
21
|
position: React.CSSProperties['position']
|
|
18
22
|
zIndex?: number
|
|
19
23
|
}
|
|
20
24
|
> = {
|
|
21
25
|
thumb: {
|
|
22
|
-
fill: scrollbarThumbFill,
|
|
23
|
-
fillHovered: 'rgba(66,119,127,0.6)',
|
|
26
|
+
fill: () => scrollbarThumbFill,
|
|
27
|
+
fillHovered: () => 'rgba(66,119,127,0.6)',
|
|
24
28
|
cursor: () => 'pointer',
|
|
25
29
|
position: 'absolute',
|
|
26
30
|
zIndex: 100,
|
|
27
31
|
},
|
|
28
32
|
resizer: {
|
|
29
|
-
fill:
|
|
30
|
-
|
|
33
|
+
fill: theme =>
|
|
34
|
+
theme.palette.mode === 'dark'
|
|
35
|
+
? theme.palette.grey[800]
|
|
36
|
+
: 'rgba(200,200,200)',
|
|
37
|
+
fillHovered: theme =>
|
|
38
|
+
theme.palette.mode === 'dark'
|
|
39
|
+
? theme.palette.grey[600]
|
|
40
|
+
: 'rgba(150,150,150)',
|
|
31
41
|
cursor: axis => (axis === 'x' ? 'ew-resize' : 'ns-resize'),
|
|
32
42
|
position: 'relative',
|
|
33
43
|
},
|
|
@@ -54,6 +64,7 @@ export default function DragHandle({
|
|
|
54
64
|
onDrag: (delta: number, startValue: number) => void
|
|
55
65
|
style: React.CSSProperties
|
|
56
66
|
}) {
|
|
67
|
+
const theme = useTheme()
|
|
57
68
|
const [hovered, setHovered] = useState(false)
|
|
58
69
|
const { startDrag } = useDragScroll(axis, onDrag)
|
|
59
70
|
const { fill, fillHovered, cursor, position, zIndex } = variants[variant]
|
|
@@ -73,7 +84,7 @@ export default function DragHandle({
|
|
|
73
84
|
position,
|
|
74
85
|
zIndex,
|
|
75
86
|
cursor: cursor(axis),
|
|
76
|
-
background: hovered ? fillHovered : fill,
|
|
87
|
+
background: hovered ? fillHovered(theme) : fill(theme),
|
|
77
88
|
...style,
|
|
78
89
|
}}
|
|
79
90
|
/>
|
|
@@ -109,12 +109,8 @@ const Loading = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
109
109
|
<MSAView model={model} />
|
|
110
110
|
)
|
|
111
111
|
) : error ? (
|
|
112
|
-
//
|
|
113
|
-
// only an abort clears it
|
|
114
|
-
// fell to the spinner below, and the ImportForm that renders
|
|
115
|
-
// `model.error` was never reached: the reader got "Downloading file"
|
|
116
|
-
// and a Cancel button for as long as they were willing to watch it.
|
|
117
|
-
// Found on AlphaFold's files/msa/ prefix answering 403 to every key.
|
|
112
|
+
// checked before the spinner: a failed load keeps its filehandle
|
|
113
|
+
// (only an abort clears it), so hasPendingFilehandle stays true
|
|
118
114
|
<Reset model={model} error={error} />
|
|
119
115
|
) : hasPendingFilehandle || isLoading ? (
|
|
120
116
|
<LoadingSpinner model={model} />
|
|
@@ -12,16 +12,27 @@ import Header from './header/Header.tsx'
|
|
|
12
12
|
import Minimap from './minimap/Minimap.tsx'
|
|
13
13
|
import MSAPanel from './msa/MSAPanel.tsx'
|
|
14
14
|
import { clickColor, hoverColor } from './overlayColors.ts'
|
|
15
|
+
import RowPanelHeaders from './rowpanels/RowPanelHeaders.tsx'
|
|
16
|
+
import RowPanels from './rowpanels/RowPanels.tsx'
|
|
17
|
+
import TreeOverview from './tree/TreeOverview.tsx'
|
|
15
18
|
import TreePanel from './tree/TreePanel.tsx'
|
|
16
19
|
import TreeRuler from './tree/TreeRuler.tsx'
|
|
17
20
|
|
|
18
21
|
import type { MsaViewModel } from '../model.ts'
|
|
19
22
|
|
|
23
|
+
// The band above the panels: the tree overview and scale bar, the strip
|
|
24
|
+
// headers, and the minimap over the alignment. The spacer stands in for the
|
|
25
|
+
// resize handle, so the minimap starts exactly where the alignment canvas does.
|
|
20
26
|
const TopArea = observer(function ({ model }: { model: MsaViewModel }) {
|
|
21
|
-
const { showHorizontalScrollbar } = model
|
|
27
|
+
const { showHorizontalScrollbar, showTreeOverview, resizeHandleWidth } = model
|
|
22
28
|
return (
|
|
23
29
|
<div style={{ display: 'flex' }}>
|
|
24
|
-
<
|
|
30
|
+
<div style={{ flexShrink: 0 }}>
|
|
31
|
+
{showTreeOverview ? <TreeOverview model={model} /> : null}
|
|
32
|
+
<TreeRuler model={model} />
|
|
33
|
+
</div>
|
|
34
|
+
<RowPanelHeaders model={model} />
|
|
35
|
+
<div style={{ width: resizeHandleWidth, flexShrink: 0 }} />
|
|
25
36
|
{showHorizontalScrollbar ? <Minimap model={model} /> : null}
|
|
26
37
|
</div>
|
|
27
38
|
)
|
|
@@ -38,6 +49,7 @@ const TrackColumnIndicator = observer(function ({
|
|
|
38
49
|
colWidth,
|
|
39
50
|
scrollX,
|
|
40
51
|
treeAreaWidth,
|
|
52
|
+
rowPanelsWidth,
|
|
41
53
|
resizeHandleWidth,
|
|
42
54
|
totalTrackAreaHeight,
|
|
43
55
|
msaCanvasWidth,
|
|
@@ -53,7 +65,7 @@ const TrackColumnIndicator = observer(function ({
|
|
|
53
65
|
<div
|
|
54
66
|
style={{
|
|
55
67
|
position: 'absolute',
|
|
56
|
-
left: treeAreaWidth + resizeHandleWidth,
|
|
68
|
+
left: treeAreaWidth + rowPanelsWidth + resizeHandleWidth,
|
|
57
69
|
top: 0,
|
|
58
70
|
width: msaCanvasWidth,
|
|
59
71
|
height: totalTrackAreaHeight,
|
|
@@ -102,6 +114,7 @@ const MainArea = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
102
114
|
return (
|
|
103
115
|
<div style={{ display: 'flex' }}>
|
|
104
116
|
<TreePanel model={model} />
|
|
117
|
+
<RowPanels model={model} />
|
|
105
118
|
<VerticalResizeHandle model={model} />
|
|
106
119
|
<MSAPanel model={model} />
|
|
107
120
|
{showVerticalScrollbar ? <VerticalScrollbar model={model} /> : null}
|
|
@@ -120,14 +133,15 @@ const View = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
120
133
|
})
|
|
121
134
|
|
|
122
135
|
const MSAView = observer(function ({ model }: { model: MsaViewModel }) {
|
|
123
|
-
const { height, viewInitialized, DialogComponent, DialogProps } =
|
|
136
|
+
const { height, hideHeader, viewInitialized, DialogComponent, DialogProps } =
|
|
137
|
+
model
|
|
124
138
|
|
|
125
139
|
return (
|
|
126
140
|
<div>
|
|
127
141
|
{viewInitialized ? (
|
|
128
142
|
<>
|
|
129
143
|
<div style={{ height, overflow: 'hidden' }}>
|
|
130
|
-
<Header model={model} />
|
|
144
|
+
{hideHeader ? null : <Header model={model} />}
|
|
131
145
|
<View model={model} />
|
|
132
146
|
</div>
|
|
133
147
|
<HorizontalResizeHandle model={model} />
|
|
@@ -4,16 +4,38 @@ import { createJBrowseTheme } from '@jbrowse/core/ui/theme'
|
|
|
4
4
|
import useMeasure from '@jbrowse/core/util/useMeasure'
|
|
5
5
|
import { destroy, isAlive } from '@jbrowse/mobx-state-tree'
|
|
6
6
|
import { ThemeProvider } from '@mui/material/styles'
|
|
7
|
+
import { compareStructural, reaction, when } from 'mobx'
|
|
7
8
|
|
|
8
9
|
import MSAModelF from '../model.ts'
|
|
9
10
|
import Loading from './Loading.tsx'
|
|
10
11
|
|
|
11
|
-
import type {
|
|
12
|
+
import type { MsaViewModel } from '../model.ts'
|
|
13
|
+
import type {
|
|
14
|
+
Cell,
|
|
15
|
+
Clade,
|
|
16
|
+
ColumnTrackSpec,
|
|
17
|
+
Encoding,
|
|
18
|
+
Highlight,
|
|
19
|
+
Region,
|
|
20
|
+
ResidueEncoding,
|
|
21
|
+
ResidueMapping,
|
|
22
|
+
RowPanelSpec,
|
|
23
|
+
Viewport,
|
|
24
|
+
} from '../types.ts'
|
|
12
25
|
import type { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
|
|
26
|
+
import type { ThemeOptions } from '@mui/material/styles'
|
|
13
27
|
|
|
14
|
-
|
|
28
|
+
function resolveTheme(theme: MSAViewerProps['theme']) {
|
|
29
|
+
return createJBrowseTheme(
|
|
30
|
+
theme === 'dark'
|
|
31
|
+
? { palette: { mode: 'dark' } }
|
|
32
|
+
: typeof theme === 'object'
|
|
33
|
+
? theme
|
|
34
|
+
: undefined,
|
|
35
|
+
)
|
|
36
|
+
}
|
|
15
37
|
|
|
16
|
-
interface MSAViewerProps {
|
|
38
|
+
export interface MSAViewerProps {
|
|
17
39
|
msa?: string
|
|
18
40
|
tree?: string
|
|
19
41
|
gff?: string
|
|
@@ -27,9 +49,7 @@ interface MSAViewerProps {
|
|
|
27
49
|
/** initial per-row pixel height */
|
|
28
50
|
rowHeight?: number
|
|
29
51
|
/**
|
|
30
|
-
* hide columns at least this percent gaps (default 100, i.e. hide nothing)
|
|
31
|
-
* A deep family's alignment is mostly insertions carried by a few members;
|
|
32
|
-
* dropping those columns is what makes the rest readable
|
|
52
|
+
* hide columns at least this percent gaps (default 100, i.e. hide nothing)
|
|
33
53
|
*/
|
|
34
54
|
allowedGappyness?: number
|
|
35
55
|
/** alignment columns (0-based) to highlight with a persistent overlay */
|
|
@@ -40,21 +60,136 @@ interface MSAViewerProps {
|
|
|
40
60
|
* for whole rows, each with an optional `label` and `color`
|
|
41
61
|
*/
|
|
42
62
|
highlights?: Highlight[]
|
|
63
|
+
/**
|
|
64
|
+
* clades of the tree with a mark drawn over them: `{mrca, tips, mark}` or
|
|
65
|
+
* `{range, tips, mark}`, where `mark` is `highlight`, `bracket`, `collapse`
|
|
66
|
+
* or `focus` (see docs/layers.md)
|
|
67
|
+
*/
|
|
68
|
+
clades?: Clade[]
|
|
43
69
|
/** row name to diff every other row against (matches render as ".") */
|
|
44
70
|
relativeTo?: string
|
|
45
71
|
/** tracks supplied as data: per-column bar values or a text row (see docs/layers.md) */
|
|
46
72
|
columnTracks?: ColumnTrackSpec[]
|
|
47
73
|
/** which residue of which structure each row's residues are (see docs/layers.md) */
|
|
48
74
|
residueMappings?: ResidueMapping[]
|
|
75
|
+
/** extra fields per row name, such as a lineage or a host (see docs/layers.md) */
|
|
76
|
+
rowData?: Record<string, Record<string, string>>
|
|
77
|
+
/**
|
|
78
|
+
* what the viewer's marks read from a table: `{channel, field, scale?}`,
|
|
79
|
+
* where `channel` is `tipLabel`, `rowTint` or `branch` over `rowData`, or
|
|
80
|
+
* `featureFill` or `featureLabel` over the annotations (see docs/layers.md)
|
|
81
|
+
*/
|
|
82
|
+
encodings?: Encoding[]
|
|
83
|
+
/**
|
|
84
|
+
* panels between the tree and the alignment, one cell per row:
|
|
85
|
+
* `{kind: 'strip', field, scale?, width?, header?}` colors each row by a
|
|
86
|
+
* `rowData` field (see docs/layers.md)
|
|
87
|
+
*/
|
|
88
|
+
rowPanels?: RowPanelSpec[]
|
|
49
89
|
/** draw the phylogenetic tree (default true); false leaves a labels-only gutter */
|
|
50
90
|
drawTree?: boolean
|
|
51
91
|
/** fixed width (px) of the tree/label area */
|
|
52
92
|
treeAreaWidth?: number
|
|
53
93
|
/** auto-size the tree/label area to the labels (used when drawTree is false) */
|
|
54
94
|
autoTreeAreaWidth?: boolean
|
|
95
|
+
/** draw branch lengths (default true); false draws a cladogram */
|
|
96
|
+
showBranchLen?: boolean
|
|
97
|
+
/**
|
|
98
|
+
* which channel `colorScheme` paints: `fill` (default) colors the background
|
|
99
|
+
* of the cell a residue sits in, `color` colors the letter itself and leaves
|
|
100
|
+
* the background plain, which lets a domain overlay mark each span with a bar
|
|
101
|
+
* under the row and the residue colors show through
|
|
102
|
+
*/
|
|
103
|
+
residueEncoding?: ResidueEncoding
|
|
104
|
+
/**
|
|
105
|
+
* a span to zoom and scroll to once the alignment loads, and again whenever
|
|
106
|
+
* it changes: `{row, start, end}` in residues of that row, or `{start, end}`
|
|
107
|
+
* in columns, 1-based and inclusive
|
|
108
|
+
*/
|
|
109
|
+
region?: Region
|
|
110
|
+
/** leave out the toolbar, for a page drawing its own controls */
|
|
111
|
+
hideHeader?: boolean
|
|
112
|
+
/**
|
|
113
|
+
* 'light' (default), 'dark', or MUI theme options (palette, typography)
|
|
114
|
+
* merged over the JBrowse theme
|
|
115
|
+
*/
|
|
116
|
+
theme?: 'light' | 'dark' | ThemeOptions
|
|
117
|
+
/** the cell under the pointer, or undefined when it leaves the alignment */
|
|
118
|
+
onCellHover?: (cell: Cell | undefined) => void
|
|
119
|
+
/** the cell a click pinned, or undefined when a click clears it */
|
|
120
|
+
onCellClick?: (cell: Cell | undefined) => void
|
|
121
|
+
/** the alignment columns on screen, after every scroll, zoom and resize */
|
|
122
|
+
onViewportChange?: (viewport: Viewport | undefined) => void
|
|
123
|
+
/**
|
|
124
|
+
* the model the viewer built, for the model API; called again with the new
|
|
125
|
+
* model when msa, tree, gff or a filehandle changes
|
|
126
|
+
*/
|
|
127
|
+
onModel?: (model: MsaViewModel) => void
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
type DataSource = Pick<
|
|
131
|
+
MSAViewerProps,
|
|
132
|
+
'msa' | 'tree' | 'gff' | 'msaFilehandle' | 'treeFilehandle' | 'gffFilehandle'
|
|
133
|
+
>
|
|
134
|
+
|
|
135
|
+
function sameSource(a: DataSource, b: DataSource) {
|
|
136
|
+
return (
|
|
137
|
+
a.msa === b.msa &&
|
|
138
|
+
a.tree === b.tree &&
|
|
139
|
+
a.gff === b.gff &&
|
|
140
|
+
JSON.stringify([a.msaFilehandle, a.treeFilehandle, a.gffFilehandle]) ===
|
|
141
|
+
JSON.stringify([b.msaFilehandle, b.treeFilehandle, b.gffFilehandle])
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* A new alignment, tree or annotation file needs a new model, so the viewer
|
|
147
|
+
* remounts when one of those props changes and keeps its model across every
|
|
148
|
+
* other change.
|
|
149
|
+
*/
|
|
150
|
+
export default function MSAViewer(props: MSAViewerProps) {
|
|
151
|
+
const { msa, tree, gff, msaFilehandle, treeFilehandle, gffFilehandle } = props
|
|
152
|
+
const source = {
|
|
153
|
+
msa,
|
|
154
|
+
tree,
|
|
155
|
+
gff,
|
|
156
|
+
msaFilehandle,
|
|
157
|
+
treeFilehandle,
|
|
158
|
+
gffFilehandle,
|
|
159
|
+
}
|
|
160
|
+
const [shown, setShown] = useState({ source, generation: 0 })
|
|
161
|
+
if (!sameSource(shown.source, source)) {
|
|
162
|
+
setShown({ source, generation: shown.generation + 1 })
|
|
163
|
+
}
|
|
164
|
+
return <Viewer key={shown.generation} {...props} />
|
|
55
165
|
}
|
|
56
166
|
|
|
57
|
-
|
|
167
|
+
function useModelReaction<K extends 'hoveredCell' | 'clickedCell' | 'viewport'>(
|
|
168
|
+
model: MsaViewModel,
|
|
169
|
+
key: K,
|
|
170
|
+
handler: ((value: MsaViewModel[K]) => void) | undefined,
|
|
171
|
+
) {
|
|
172
|
+
const latest = useRef(handler)
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
latest.current = handler
|
|
175
|
+
})
|
|
176
|
+
const listening = !!handler
|
|
177
|
+
useEffect(
|
|
178
|
+
() =>
|
|
179
|
+
listening
|
|
180
|
+
? reaction(
|
|
181
|
+
() => model[key],
|
|
182
|
+
value => {
|
|
183
|
+
latest.current?.(value)
|
|
184
|
+
},
|
|
185
|
+
{ equals: compareStructural },
|
|
186
|
+
)
|
|
187
|
+
: undefined,
|
|
188
|
+
[model, key, listening],
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function Viewer({
|
|
58
193
|
msa,
|
|
59
194
|
tree,
|
|
60
195
|
gff,
|
|
@@ -68,20 +203,40 @@ export default function MSAViewer({
|
|
|
68
203
|
allowedGappyness,
|
|
69
204
|
highlightColumns,
|
|
70
205
|
highlights,
|
|
206
|
+
clades,
|
|
71
207
|
relativeTo,
|
|
72
208
|
columnTracks,
|
|
73
209
|
residueMappings,
|
|
210
|
+
rowData,
|
|
211
|
+
encodings,
|
|
212
|
+
rowPanels,
|
|
74
213
|
drawTree,
|
|
214
|
+
residueEncoding,
|
|
75
215
|
treeAreaWidth,
|
|
76
216
|
autoTreeAreaWidth,
|
|
217
|
+
showBranchLen,
|
|
218
|
+
region,
|
|
219
|
+
hideHeader,
|
|
220
|
+
theme,
|
|
221
|
+
onCellHover,
|
|
222
|
+
onCellClick,
|
|
223
|
+
onViewportChange,
|
|
224
|
+
onModel,
|
|
77
225
|
}: MSAViewerProps) {
|
|
78
|
-
// lazy initializer: the model is created exactly once from the initial props
|
|
79
|
-
// (a stable MST instance — not a value safe to recompute, so not useMemo)
|
|
80
226
|
const [model] = useState(() =>
|
|
81
227
|
MSAModelF().create({
|
|
82
228
|
type: 'MsaView',
|
|
83
|
-
...(msa || tree || gff
|
|
84
|
-
? {
|
|
229
|
+
...(msa || tree || gff || rowData
|
|
230
|
+
? {
|
|
231
|
+
data: {
|
|
232
|
+
msa: msa ?? '',
|
|
233
|
+
tree: tree ?? '',
|
|
234
|
+
gff,
|
|
235
|
+
// the row table is stored as this JSON string, which is what a
|
|
236
|
+
// shared URL and the size limit already cover
|
|
237
|
+
...(rowData ? { treeMetadata: JSON.stringify(rowData) } : {}),
|
|
238
|
+
},
|
|
239
|
+
}
|
|
85
240
|
: {}),
|
|
86
241
|
...(msaFilehandle ? { msaFilehandle } : {}),
|
|
87
242
|
...(treeFilehandle ? { treeFilehandle } : {}),
|
|
@@ -93,12 +248,19 @@ export default function MSAViewer({
|
|
|
93
248
|
...(allowedGappyness !== undefined ? { allowedGappyness } : {}),
|
|
94
249
|
...(highlightColumns ? { highlightColumns } : {}),
|
|
95
250
|
...(highlights ? { highlights } : {}),
|
|
251
|
+
...(clades ? { clades } : {}),
|
|
96
252
|
...(relativeTo ? { relativeTo } : {}),
|
|
97
253
|
...(columnTracks ? { columnTracks } : {}),
|
|
98
254
|
...(residueMappings ? { residueMappings } : {}),
|
|
255
|
+
...(encodings ? { encodings } : {}),
|
|
256
|
+
...(rowPanels ? { rowPanels } : {}),
|
|
99
257
|
...(drawTree !== undefined ? { drawTree } : {}),
|
|
100
258
|
...(treeAreaWidth ? { treeAreaWidth } : {}),
|
|
101
259
|
...(autoTreeAreaWidth ? { autoTreeAreaWidth } : {}),
|
|
260
|
+
...(showBranchLen !== undefined ? { showBranchLen } : {}),
|
|
261
|
+
...(residueEncoding !== undefined
|
|
262
|
+
? { bgColor: residueEncoding === 'fill' }
|
|
263
|
+
: {}),
|
|
102
264
|
}),
|
|
103
265
|
)
|
|
104
266
|
|
|
@@ -113,14 +275,8 @@ export default function MSAViewer({
|
|
|
113
275
|
}
|
|
114
276
|
}, [model, width])
|
|
115
277
|
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
// toggle, a color-scheme picker -- without remounting it and re-fetching its
|
|
119
|
-
// alignment. Each effect depends only on its own prop, so a change the user
|
|
120
|
-
// makes inside the viewer (picking a scheme from the menu, dragging a row
|
|
121
|
-
// taller) survives the host's next render rather than being snapped back.
|
|
122
|
-
// Data props are not among them: new msa/tree/gff is a different alignment,
|
|
123
|
-
// which is a new model, which React spells `key`.
|
|
278
|
+
// Each effect depends only on its own prop, so a change made inside the
|
|
279
|
+
// viewer survives the host's next render.
|
|
124
280
|
useEffect(() => {
|
|
125
281
|
if (height !== undefined) {
|
|
126
282
|
model.setHeight(height)
|
|
@@ -156,18 +312,34 @@ export default function MSAViewer({
|
|
|
156
312
|
model.setTreeAreaWidth(treeAreaWidth)
|
|
157
313
|
}
|
|
158
314
|
}, [model, treeAreaWidth])
|
|
159
|
-
|
|
315
|
+
useEffect(() => {
|
|
316
|
+
if (showBranchLen !== undefined) {
|
|
317
|
+
model.setShowBranchLen(showBranchLen)
|
|
318
|
+
}
|
|
319
|
+
}, [model, showBranchLen])
|
|
320
|
+
useEffect(() => {
|
|
321
|
+
if (residueEncoding !== undefined) {
|
|
322
|
+
model.setBgColor(residueEncoding === 'fill')
|
|
323
|
+
}
|
|
324
|
+
}, [model, residueEncoding])
|
|
325
|
+
useEffect(() => {
|
|
326
|
+
model.setHideHeader(!!hideHeader)
|
|
327
|
+
}, [model, hideHeader])
|
|
328
|
+
// unguarded, so removing the prop turns the reference diff off
|
|
160
329
|
useEffect(() => {
|
|
161
330
|
model.drawRelativeTo(relativeTo)
|
|
162
331
|
}, [model, relativeTo])
|
|
163
332
|
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
// redraw the overlay for nothing
|
|
333
|
+
// keyed by content, since a host computing a layer inline passes a new array
|
|
334
|
+
// on every render
|
|
167
335
|
const highlightsKey = JSON.stringify(highlights ?? [])
|
|
168
336
|
useEffect(() => {
|
|
169
337
|
model.setHighlights(JSON.parse(highlightsKey))
|
|
170
338
|
}, [model, highlightsKey])
|
|
339
|
+
const cladesKey = JSON.stringify(clades ?? [])
|
|
340
|
+
useEffect(() => {
|
|
341
|
+
model.setClades(JSON.parse(cladesKey))
|
|
342
|
+
}, [model, cladesKey])
|
|
171
343
|
const columnTracksKey = JSON.stringify(columnTracks ?? [])
|
|
172
344
|
useEffect(() => {
|
|
173
345
|
model.setColumnTracks(JSON.parse(columnTracksKey))
|
|
@@ -176,15 +348,55 @@ export default function MSAViewer({
|
|
|
176
348
|
useEffect(() => {
|
|
177
349
|
model.setResidueMappings(JSON.parse(residueMappingsKey))
|
|
178
350
|
}, [model, residueMappingsKey])
|
|
351
|
+
const rowDataKey = JSON.stringify(rowData ?? {})
|
|
352
|
+
useEffect(() => {
|
|
353
|
+
const table = JSON.parse(rowDataKey)
|
|
354
|
+
// an empty table leaves whatever the snapshot or a filehandle loaded
|
|
355
|
+
if (Object.keys(table).length) {
|
|
356
|
+
model.setRowData(table)
|
|
357
|
+
}
|
|
358
|
+
}, [model, rowDataKey])
|
|
359
|
+
const encodingsKey = JSON.stringify(encodings ?? [])
|
|
360
|
+
useEffect(() => {
|
|
361
|
+
model.setEncodings(JSON.parse(encodingsKey))
|
|
362
|
+
}, [model, encodingsKey])
|
|
363
|
+
const rowPanelsKey = JSON.stringify(rowPanels ?? [])
|
|
364
|
+
useEffect(() => {
|
|
365
|
+
model.setRowPanels(JSON.parse(rowPanelsKey))
|
|
366
|
+
}, [model, rowPanelsKey])
|
|
179
367
|
const highlightColumnsKey = JSON.stringify(highlightColumns ?? null)
|
|
180
368
|
useEffect(() => {
|
|
181
369
|
model.setHighlightedColumns(JSON.parse(highlightColumnsKey) ?? undefined)
|
|
182
370
|
}, [model, highlightColumnsKey])
|
|
183
371
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
372
|
+
const regionKey = JSON.stringify(region ?? null)
|
|
373
|
+
useEffect(() => {
|
|
374
|
+
const target = JSON.parse(regionKey) as Region | null
|
|
375
|
+
return target
|
|
376
|
+
? when(
|
|
377
|
+
() => model.viewInitialized && model.numColumns > 0,
|
|
378
|
+
() => {
|
|
379
|
+
model.zoomToRegion(target)
|
|
380
|
+
},
|
|
381
|
+
)
|
|
382
|
+
: undefined
|
|
383
|
+
}, [model, regionKey])
|
|
384
|
+
|
|
385
|
+
useModelReaction(model, 'hoveredCell', onCellHover)
|
|
386
|
+
useModelReaction(model, 'clickedCell', onCellClick)
|
|
387
|
+
useModelReaction(model, 'viewport', onViewportChange)
|
|
388
|
+
|
|
389
|
+
const latestOnModel = useRef(onModel)
|
|
390
|
+
useEffect(() => {
|
|
391
|
+
latestOnModel.current = onModel
|
|
392
|
+
})
|
|
393
|
+
useEffect(() => {
|
|
394
|
+
latestOnModel.current?.(model)
|
|
395
|
+
}, [model])
|
|
396
|
+
|
|
397
|
+
// destroy releases the model's matchMedia listener and fetches. It waits a
|
|
398
|
+
// tick because StrictMode remounts with the same state, and the remount's
|
|
399
|
+
// effect cancels it.
|
|
188
400
|
useEffect(() => {
|
|
189
401
|
if (destroyTimer.current !== undefined) {
|
|
190
402
|
clearTimeout(destroyTimer.current)
|
|
@@ -201,7 +413,7 @@ export default function MSAViewer({
|
|
|
201
413
|
}, [model])
|
|
202
414
|
|
|
203
415
|
return (
|
|
204
|
-
<ThemeProvider theme={theme}>
|
|
416
|
+
<ThemeProvider theme={resolveTheme(theme)}>
|
|
205
417
|
<div ref={ref}>
|
|
206
418
|
<Loading model={model} />
|
|
207
419
|
</div>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { makeStyles } from '@jbrowse/core/util/tss-react'
|
|
4
|
+
import { createPortal } from 'react-dom'
|
|
5
|
+
|
|
6
|
+
const useStyles = makeStyles()(theme => ({
|
|
7
|
+
tooltip: {
|
|
8
|
+
position: 'fixed',
|
|
9
|
+
pointerEvents: 'none',
|
|
10
|
+
zIndex: 10000,
|
|
11
|
+
backgroundColor: theme.palette.grey[700],
|
|
12
|
+
color: theme.palette.common.white,
|
|
13
|
+
padding: '4px 8px',
|
|
14
|
+
borderRadius: 4,
|
|
15
|
+
fontSize: 12,
|
|
16
|
+
whiteSpace: 'nowrap',
|
|
17
|
+
maxWidth: 300,
|
|
18
|
+
overflow: 'hidden',
|
|
19
|
+
textOverflow: 'ellipsis',
|
|
20
|
+
},
|
|
21
|
+
}))
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A tooltip beside the pointer, portaled to the body. A panel's scroll
|
|
25
|
+
* transform is a containing block, so a fixed tooltip left inside it would
|
|
26
|
+
* position against the scrolled content instead of the window.
|
|
27
|
+
*/
|
|
28
|
+
export default function PortalTooltip({
|
|
29
|
+
clientX,
|
|
30
|
+
clientY,
|
|
31
|
+
children,
|
|
32
|
+
}: {
|
|
33
|
+
clientX: number
|
|
34
|
+
clientY: number
|
|
35
|
+
children: React.ReactNode
|
|
36
|
+
}) {
|
|
37
|
+
const { classes } = useStyles()
|
|
38
|
+
return createPortal(
|
|
39
|
+
<div
|
|
40
|
+
className={classes.tooltip}
|
|
41
|
+
style={{ left: clientX + 12, top: clientY + 12 }}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
</div>,
|
|
45
|
+
document.body,
|
|
46
|
+
)
|
|
47
|
+
}
|