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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { calcDepthToLeaf } from '../../hierarchy.ts'
|
|
1
|
+
import { calcDepthToLeaf, nodeCoveringRows } from '../../hierarchy.ts'
|
|
2
2
|
import { setFontSize } from '../../setFontSize.ts'
|
|
3
3
|
import { getVisibleLeaves } from '../getVisibleLeaves.ts'
|
|
4
4
|
import {
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
highlightLabelHeight,
|
|
7
7
|
highlightRowFill,
|
|
8
8
|
} from '../msa/renderHighlights.ts'
|
|
9
|
+
import { bracketBarWidth, bracketGap } from './cladeBrackets.ts'
|
|
9
10
|
|
|
10
11
|
import type { HierarchyNode } from '../../hierarchy.ts'
|
|
11
12
|
import type { MsaViewModel } from '../../model.ts'
|
|
@@ -32,12 +33,12 @@ function inYBlock(y: number, offsetY: number, by: number, pad: number) {
|
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* Every node whose subtree reaches into the block, skipping the subtrees that
|
|
35
|
-
* do not. A subtree's leaves are one contiguous run of rows
|
|
36
|
-
*
|
|
37
|
-
*
|
|
36
|
+
* do not. A subtree's leaves are one contiguous run of rows, recorded by
|
|
37
|
+
* clusterLayout as xMin/xMax, so a block of a 200k-tip tree visits a few dozen
|
|
38
|
+
* nodes.
|
|
38
39
|
*
|
|
39
|
-
* A link out of a visited node is
|
|
40
|
-
*
|
|
40
|
+
* A link out of a visited node is drawn even when its child is pruned, so a
|
|
41
|
+
* branch crossing the block stays visible.
|
|
41
42
|
*/
|
|
42
43
|
function forEachNodeInBlock(
|
|
43
44
|
root: HierarchyNode,
|
|
@@ -101,9 +102,15 @@ function renderTree({
|
|
|
101
102
|
maxDepthToLeaf: number
|
|
102
103
|
blockSizeYOverride?: number
|
|
103
104
|
}) {
|
|
104
|
-
const {
|
|
105
|
+
const {
|
|
106
|
+
hierarchy,
|
|
107
|
+
showBranchLenEffective: showBranchLen,
|
|
108
|
+
blockSize,
|
|
109
|
+
branchColors,
|
|
110
|
+
} = model
|
|
105
111
|
const by = blockSizeYOverride ?? blockSize
|
|
106
|
-
|
|
112
|
+
const defaultStroke = theme.palette.text.primary
|
|
113
|
+
ctx.strokeStyle = defaultStroke
|
|
107
114
|
forEachNodeInBlock(hierarchy, offsetY, by, blockPad(model), source => {
|
|
108
115
|
const sy = source.x!
|
|
109
116
|
const sx = getNodeX(source, showBranchLen, tipX, maxDepthToLeaf)
|
|
@@ -122,6 +129,10 @@ function renderTree({
|
|
|
122
129
|
// optimization that allows us to skip drawing most tree links outside the
|
|
123
130
|
// block
|
|
124
131
|
if (offsetY + by >= y1 && y2 >= offsetY) {
|
|
132
|
+
// the edge belongs to the child, whose id the branch channel colors
|
|
133
|
+
if (branchColors) {
|
|
134
|
+
ctx.strokeStyle = branchColors.get(target.data.id) ?? defaultStroke
|
|
135
|
+
}
|
|
125
136
|
ctx.beginPath()
|
|
126
137
|
ctx.moveTo(sx, sy)
|
|
127
138
|
ctx.lineTo(sx, ty)
|
|
@@ -130,6 +141,7 @@ function renderTree({
|
|
|
130
141
|
}
|
|
131
142
|
}
|
|
132
143
|
})
|
|
144
|
+
ctx.strokeStyle = defaultStroke
|
|
133
145
|
}
|
|
134
146
|
|
|
135
147
|
function renderCollapsedTriangles({
|
|
@@ -160,6 +172,7 @@ function renderCollapsedTriangles({
|
|
|
160
172
|
rowHeight,
|
|
161
173
|
fontSize,
|
|
162
174
|
marginLeft: ml,
|
|
175
|
+
branchColors,
|
|
163
176
|
} = model
|
|
164
177
|
// nothing collapsed is the common case, and the traversal below visits every
|
|
165
178
|
// node in the tree on every block of every redraw
|
|
@@ -183,7 +196,7 @@ function renderCollapsedTriangles({
|
|
|
183
196
|
ctx.lineTo(baseX, y - halfHeight)
|
|
184
197
|
ctx.lineTo(baseX, y + halfHeight)
|
|
185
198
|
ctx.closePath()
|
|
186
|
-
ctx.fillStyle = theme.palette.action.disabled
|
|
199
|
+
ctx.fillStyle = branchColors?.get(id) ?? theme.palette.action.disabled
|
|
187
200
|
ctx.fill()
|
|
188
201
|
ctx.strokeStyle = theme.palette.text.primary
|
|
189
202
|
ctx.stroke()
|
|
@@ -218,6 +231,7 @@ function renderCollapsedTriangles({
|
|
|
218
231
|
// always runs and `draw` only decides whether it also strokes the circles
|
|
219
232
|
function renderNodeBubbles({
|
|
220
233
|
ctx,
|
|
234
|
+
theme,
|
|
221
235
|
clickMap,
|
|
222
236
|
offsetY,
|
|
223
237
|
model,
|
|
@@ -236,6 +250,7 @@ function renderNodeBubbles({
|
|
|
236
250
|
blockSizeYOverride?: number
|
|
237
251
|
collapsedSet: Set<string>
|
|
238
252
|
draw: boolean
|
|
253
|
+
theme: Theme
|
|
239
254
|
}) {
|
|
240
255
|
const {
|
|
241
256
|
hierarchy,
|
|
@@ -255,8 +270,10 @@ function renderNodeBubbles({
|
|
|
255
270
|
if (node.height >= 1 && inYBlock(y, offsetY, by, blockPad(model))) {
|
|
256
271
|
const isCollapsed = collapsedSet.has(id)
|
|
257
272
|
if (draw) {
|
|
258
|
-
ctx.strokeStyle =
|
|
259
|
-
ctx.fillStyle = isCollapsed
|
|
273
|
+
ctx.strokeStyle = theme.palette.text.primary
|
|
274
|
+
ctx.fillStyle = isCollapsed
|
|
275
|
+
? theme.palette.text.primary
|
|
276
|
+
: theme.palette.background.default
|
|
260
277
|
ctx.beginPath()
|
|
261
278
|
ctx.arc(x, y, radius, 0, 2 * Math.PI)
|
|
262
279
|
ctx.fill()
|
|
@@ -280,6 +297,53 @@ function renderNodeBubbles({
|
|
|
280
297
|
})
|
|
281
298
|
}
|
|
282
299
|
|
|
300
|
+
// The label a newick file puts on an internal node, which is where a bootstrap
|
|
301
|
+
// or posterior support value lands: ((A,B)95,(C,D)80);. withId falls back to the
|
|
302
|
+
// path-derived id for an unnamed node, so a node whose name is its id has no
|
|
303
|
+
// label of its own and an ungated pass prints node-0-1-1 across the tree.
|
|
304
|
+
function renderNodeLabels({
|
|
305
|
+
ctx,
|
|
306
|
+
theme,
|
|
307
|
+
offsetY,
|
|
308
|
+
model,
|
|
309
|
+
tipX,
|
|
310
|
+
maxDepthToLeaf,
|
|
311
|
+
blockSizeYOverride,
|
|
312
|
+
}: {
|
|
313
|
+
ctx: RenderCtx
|
|
314
|
+
theme: Theme
|
|
315
|
+
offsetY: number
|
|
316
|
+
model: MsaViewModel
|
|
317
|
+
tipX: number
|
|
318
|
+
maxDepthToLeaf: number
|
|
319
|
+
blockSizeYOverride?: number
|
|
320
|
+
}) {
|
|
321
|
+
const {
|
|
322
|
+
hierarchy,
|
|
323
|
+
showBranchLenEffective: showBranchLen,
|
|
324
|
+
blockSize,
|
|
325
|
+
fontSize,
|
|
326
|
+
} = model
|
|
327
|
+
const by = blockSizeYOverride ?? blockSize
|
|
328
|
+
const pad = blockPad(model)
|
|
329
|
+
setFontSize(ctx, Math.max(6, fontSize - 3))
|
|
330
|
+
ctx.fillStyle = theme.palette.text.primary
|
|
331
|
+
ctx.textAlign = 'right'
|
|
332
|
+
forEachNodeInBlock(hierarchy, offsetY, by, pad, node => {
|
|
333
|
+
const { id, name } = node.data
|
|
334
|
+
if (node.height < 1 || name === id) {
|
|
335
|
+
return
|
|
336
|
+
}
|
|
337
|
+
const x = getNodeX(node, showBranchLen, tipX, maxDepthToLeaf)
|
|
338
|
+
const y = node.x!
|
|
339
|
+
if (x !== undefined && inYBlock(y, offsetY, by, pad)) {
|
|
340
|
+
ctx.fillText(name, x - radius - 1, y - radius - 1)
|
|
341
|
+
}
|
|
342
|
+
})
|
|
343
|
+
ctx.textAlign = 'start'
|
|
344
|
+
setFontSize(ctx, fontSize)
|
|
345
|
+
}
|
|
346
|
+
|
|
283
347
|
function renderTreeLabels({
|
|
284
348
|
theme,
|
|
285
349
|
model,
|
|
@@ -304,7 +368,7 @@ function renderTreeLabels({
|
|
|
304
368
|
const {
|
|
305
369
|
fontSize,
|
|
306
370
|
showBranchLenEffective: showBranchLen,
|
|
307
|
-
|
|
371
|
+
tipLabelColors,
|
|
308
372
|
blockSize,
|
|
309
373
|
labelsAlignRight,
|
|
310
374
|
drawTree,
|
|
@@ -333,7 +397,7 @@ function renderTreeLabels({
|
|
|
333
397
|
} = node
|
|
334
398
|
const y = node.x!
|
|
335
399
|
|
|
336
|
-
const displayName =
|
|
400
|
+
const displayName = model.rowDataOf(name)?.genome || name
|
|
337
401
|
// a collapsed clade is drawn as a triangle + tip count; suppress its leaf
|
|
338
402
|
// label when the "name" is just the auto-generated internal-node id
|
|
339
403
|
const isAnonymousCollapsed = collapsedSet.has(id) && name === id
|
|
@@ -351,7 +415,7 @@ function renderTreeLabels({
|
|
|
351
415
|
? ctx.measureText(displayName).width
|
|
352
416
|
: measured * labelWidthScale
|
|
353
417
|
|
|
354
|
-
ctx.fillStyle = theme.palette.text.primary
|
|
418
|
+
ctx.fillStyle = tipLabelColors?.get(name) ?? theme.palette.text.primary
|
|
355
419
|
if (labelsAlignRight) {
|
|
356
420
|
const smallPadding = 2
|
|
357
421
|
const offset = treeAreaWidthMinusMargin - smallPadding
|
|
@@ -393,8 +457,123 @@ function renderTreeLabels({
|
|
|
393
457
|
ctx.setLineDash([])
|
|
394
458
|
}
|
|
395
459
|
|
|
396
|
-
// the row
|
|
397
|
-
//
|
|
460
|
+
// the first row of a set, as a loop: spreading into Math.min passes one
|
|
461
|
+
// argument per row, and the ~125k argument limit throws RangeError on a set
|
|
462
|
+
// covering the 230k-tip COVID tree
|
|
463
|
+
function firstRow(rowIndices: number[]) {
|
|
464
|
+
let min = Infinity
|
|
465
|
+
for (const index of rowIndices) {
|
|
466
|
+
if (index < min) {
|
|
467
|
+
min = index
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return min
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// the highlight mark of the `clades` layer: a translucent rectangle from the
|
|
474
|
+
// clade's common ancestor to the right edge of the tree area, under the
|
|
475
|
+
// branches. A clade spanning hundreds of rows crosses several blocks, so each
|
|
476
|
+
// block draws the whole rectangle and the canvas takes the part that is its.
|
|
477
|
+
function renderCladeHighlights({
|
|
478
|
+
ctx,
|
|
479
|
+
model,
|
|
480
|
+
offsetY,
|
|
481
|
+
tipX,
|
|
482
|
+
maxDepthToLeaf,
|
|
483
|
+
blockSizeYOverride,
|
|
484
|
+
}: {
|
|
485
|
+
ctx: RenderCtx
|
|
486
|
+
model: MsaViewModel
|
|
487
|
+
offsetY: number
|
|
488
|
+
tipX: number
|
|
489
|
+
maxDepthToLeaf: number
|
|
490
|
+
blockSizeYOverride?: number
|
|
491
|
+
}) {
|
|
492
|
+
const {
|
|
493
|
+
resolvedClades,
|
|
494
|
+
hierarchy,
|
|
495
|
+
showBranchLenEffective: showBranchLen,
|
|
496
|
+
rowHeight,
|
|
497
|
+
treeAreaWidth,
|
|
498
|
+
marginLeft,
|
|
499
|
+
blockSize,
|
|
500
|
+
} = model
|
|
501
|
+
const by = blockSizeYOverride ?? blockSize
|
|
502
|
+
const right = treeAreaWidth - marginLeft
|
|
503
|
+
for (const { rows, color, mark } of resolvedClades) {
|
|
504
|
+
if (mark !== 'highlight') {
|
|
505
|
+
continue
|
|
506
|
+
}
|
|
507
|
+
const top = rows[0] * rowHeight
|
|
508
|
+
const bottom = (rows[1] + 1) * rowHeight
|
|
509
|
+
if (bottom < offsetY || top > offsetY + by) {
|
|
510
|
+
continue
|
|
511
|
+
}
|
|
512
|
+
const node = nodeCoveringRows(
|
|
513
|
+
hierarchy,
|
|
514
|
+
top + rowHeight / 2,
|
|
515
|
+
bottom - rowHeight / 2,
|
|
516
|
+
)
|
|
517
|
+
const x = getNodeX(node, showBranchLen, tipX, maxDepthToLeaf) ?? 0
|
|
518
|
+
ctx.fillStyle = color
|
|
519
|
+
ctx.fillRect(x, top, right - x, bottom - top)
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// the bar of the bracket mark, in the gutter `cladeGutterWidth` reserves at the
|
|
524
|
+
// right of the tree area. The label beside it is DOM text on screen and a
|
|
525
|
+
// <text> in the export, since the canvas layer has no rotation.
|
|
526
|
+
function renderCladeBrackets({
|
|
527
|
+
ctx,
|
|
528
|
+
model,
|
|
529
|
+
theme,
|
|
530
|
+
offsetY,
|
|
531
|
+
blockSizeYOverride,
|
|
532
|
+
}: {
|
|
533
|
+
ctx: RenderCtx
|
|
534
|
+
model: MsaViewModel
|
|
535
|
+
theme: Theme
|
|
536
|
+
offsetY: number
|
|
537
|
+
blockSizeYOverride?: number
|
|
538
|
+
}) {
|
|
539
|
+
const {
|
|
540
|
+
resolvedClades,
|
|
541
|
+
cladeGutterWidth,
|
|
542
|
+
rowHeight,
|
|
543
|
+
treeAreaWidth,
|
|
544
|
+
marginLeft,
|
|
545
|
+
blockSize,
|
|
546
|
+
} = model
|
|
547
|
+
if (cladeGutterWidth === 0) {
|
|
548
|
+
return
|
|
549
|
+
}
|
|
550
|
+
const by = blockSizeYOverride ?? blockSize
|
|
551
|
+
const x =
|
|
552
|
+
treeAreaWidth -
|
|
553
|
+
marginLeft -
|
|
554
|
+
cladeGutterWidth +
|
|
555
|
+
bracketGap +
|
|
556
|
+
bracketBarWidth / 2
|
|
557
|
+
ctx.lineWidth = bracketBarWidth
|
|
558
|
+
for (const { rows, mark, markColor } of resolvedClades) {
|
|
559
|
+
const top = rows[0] * rowHeight + 1
|
|
560
|
+
const bottom = (rows[1] + 1) * rowHeight - 1
|
|
561
|
+
if (mark !== 'bracket' || bottom < offsetY || top > offsetY + by) {
|
|
562
|
+
continue
|
|
563
|
+
}
|
|
564
|
+
ctx.strokeStyle = markColor ?? theme.palette.text.primary
|
|
565
|
+
ctx.beginPath()
|
|
566
|
+
ctx.moveTo(x, top)
|
|
567
|
+
ctx.lineTo(x, bottom)
|
|
568
|
+
ctx.stroke()
|
|
569
|
+
}
|
|
570
|
+
ctx.lineWidth = 1
|
|
571
|
+
ctx.strokeStyle = theme.palette.text.primary
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// the `rowTint` encoding and the row sets of `highlights`, washed across the
|
|
575
|
+
// tree area under the labels, with each set's label in the gutter at its first
|
|
576
|
+
// row
|
|
398
577
|
function renderRowHighlights({
|
|
399
578
|
ctx,
|
|
400
579
|
model,
|
|
@@ -410,6 +589,7 @@ function renderRowHighlights({
|
|
|
410
589
|
}) {
|
|
411
590
|
const {
|
|
412
591
|
resolvedHighlights,
|
|
592
|
+
rowTints,
|
|
413
593
|
rowHeight,
|
|
414
594
|
treeAreaWidth,
|
|
415
595
|
marginLeft,
|
|
@@ -418,15 +598,31 @@ function renderRowHighlights({
|
|
|
418
598
|
const by = blockSizeYOverride ?? blockSize
|
|
419
599
|
const pad = blockPad(model)
|
|
420
600
|
const placed: LabelBox[] = []
|
|
601
|
+
const fillRow = (index: number) => {
|
|
602
|
+
ctx.fillRect(-marginLeft, index * rowHeight, treeAreaWidth, rowHeight)
|
|
603
|
+
}
|
|
604
|
+
if (rowTints) {
|
|
605
|
+
const top = Math.max(0, Math.floor((offsetY - pad) / rowHeight))
|
|
606
|
+
const bottom = Math.min(
|
|
607
|
+
rowTints.length - 1,
|
|
608
|
+
Math.ceil((offsetY + by + pad) / rowHeight),
|
|
609
|
+
)
|
|
610
|
+
for (let index = top; index <= bottom; index++) {
|
|
611
|
+
const color = rowTints[index]
|
|
612
|
+
if (color) {
|
|
613
|
+
ctx.fillStyle = color
|
|
614
|
+
fillRow(index)
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
421
618
|
for (const { rowIndices, label, color } of resolvedHighlights) {
|
|
422
619
|
ctx.fillStyle = color ?? highlightRowFill
|
|
423
620
|
for (const index of rowIndices) {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
ctx.fillRect(-marginLeft, y, treeAreaWidth, rowHeight)
|
|
621
|
+
if (inYBlock(index * rowHeight + rowHeight / 2, offsetY, by, pad)) {
|
|
622
|
+
fillRow(index)
|
|
427
623
|
}
|
|
428
624
|
}
|
|
429
|
-
const first =
|
|
625
|
+
const first = firstRow(rowIndices)
|
|
430
626
|
if (
|
|
431
627
|
label &&
|
|
432
628
|
rowIndices.length &&
|
|
@@ -470,6 +666,7 @@ export function renderTreeCanvas({
|
|
|
470
666
|
noTree,
|
|
471
667
|
drawTree,
|
|
472
668
|
drawNodeBubbles,
|
|
669
|
+
drawNodeLabels,
|
|
473
670
|
highResScaleFactor,
|
|
474
671
|
fontSize,
|
|
475
672
|
showTreeText,
|
|
@@ -500,6 +697,14 @@ export function renderTreeCanvas({
|
|
|
500
697
|
// every block of every redraw, and `collapsed` is a list
|
|
501
698
|
const collapsedSet = new Set(model.collapsed)
|
|
502
699
|
|
|
700
|
+
renderCladeHighlights({
|
|
701
|
+
ctx,
|
|
702
|
+
model,
|
|
703
|
+
offsetY,
|
|
704
|
+
tipX,
|
|
705
|
+
maxDepthToLeaf,
|
|
706
|
+
blockSizeYOverride,
|
|
707
|
+
})
|
|
503
708
|
renderRowHighlights({ ctx, model, theme, offsetY, blockSizeYOverride })
|
|
504
709
|
setFontSize(ctx, fontSize)
|
|
505
710
|
|
|
@@ -528,6 +733,7 @@ export function renderTreeCanvas({
|
|
|
528
733
|
|
|
529
734
|
renderNodeBubbles({
|
|
530
735
|
ctx,
|
|
736
|
+
theme,
|
|
531
737
|
offsetY,
|
|
532
738
|
clickMap,
|
|
533
739
|
model,
|
|
@@ -537,6 +743,18 @@ export function renderTreeCanvas({
|
|
|
537
743
|
collapsedSet,
|
|
538
744
|
draw: drawNodeBubbles,
|
|
539
745
|
})
|
|
746
|
+
|
|
747
|
+
if (drawNodeLabels) {
|
|
748
|
+
renderNodeLabels({
|
|
749
|
+
ctx,
|
|
750
|
+
theme,
|
|
751
|
+
offsetY,
|
|
752
|
+
model,
|
|
753
|
+
tipX,
|
|
754
|
+
maxDepthToLeaf,
|
|
755
|
+
blockSizeYOverride,
|
|
756
|
+
})
|
|
757
|
+
}
|
|
540
758
|
}
|
|
541
759
|
|
|
542
760
|
if (showTreeText) {
|
|
@@ -553,6 +771,8 @@ export function renderTreeCanvas({
|
|
|
553
771
|
})
|
|
554
772
|
}
|
|
555
773
|
|
|
774
|
+
renderCladeBrackets({ ctx, model, theme, offsetY, blockSizeYOverride })
|
|
775
|
+
|
|
556
776
|
// Finish the index so it's ready for queries
|
|
557
777
|
clickMap?.finish()
|
|
558
778
|
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { descendants } from '../../hierarchy.ts'
|
|
2
|
+
import { getNodeX } from './renderTreeCanvas.ts'
|
|
3
|
+
|
|
4
|
+
import type { HierarchyNode } from '../../hierarchy.ts'
|
|
5
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
6
|
+
import type { RasterCanvas } from '../msa/msaRaster.ts'
|
|
7
|
+
import type { RenderCtx } from '../renderCtx.ts'
|
|
8
|
+
import type { Theme } from '@mui/material'
|
|
9
|
+
|
|
10
|
+
// the tree draws this far inside the band, so a box around the first or the
|
|
11
|
+
// last row keeps both of its edges
|
|
12
|
+
const pad = 1
|
|
13
|
+
|
|
14
|
+
interface OverviewArgs {
|
|
15
|
+
model: MsaViewModel
|
|
16
|
+
ctx: RenderCtx
|
|
17
|
+
theme: Theme
|
|
18
|
+
width: number
|
|
19
|
+
height: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function rowScale(numTips: number, height: number) {
|
|
23
|
+
const inner = height - pad * 2
|
|
24
|
+
return (row: number) => pad + (row / numTips) * inner
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The whole tree at the size of the overview band, with the clade highlights
|
|
29
|
+
* under it.
|
|
30
|
+
*
|
|
31
|
+
* A branch that moves less than a pixel on both axes is dropped. The 230k-tip
|
|
32
|
+
* COVID tree has 460k of them over a 120px band, and the ones landing on the
|
|
33
|
+
* pixel their parent already covers cost a path segment each and change no
|
|
34
|
+
* pixel; the test in renderTreeOverview.test.ts measures a 200k-tip tree.
|
|
35
|
+
*/
|
|
36
|
+
export function drawTreeOverview({
|
|
37
|
+
model,
|
|
38
|
+
ctx,
|
|
39
|
+
theme,
|
|
40
|
+
width,
|
|
41
|
+
height,
|
|
42
|
+
}: OverviewArgs) {
|
|
43
|
+
const layout = model.treeOverviewLayout
|
|
44
|
+
if (!layout) {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
const { root, numTips, maxDepthToLeaf, showBranchLen } = layout
|
|
48
|
+
const innerWidth = width - pad * 2
|
|
49
|
+
const rowY = rowScale(numTips, height)
|
|
50
|
+
const nodeX = (node: HierarchyNode) =>
|
|
51
|
+
pad + (getNodeX(node, showBranchLen, 1, maxDepthToLeaf) ?? 0) * innerWidth
|
|
52
|
+
|
|
53
|
+
for (const { rows, color } of model.treeOverviewClades) {
|
|
54
|
+
ctx.fillStyle = color
|
|
55
|
+
ctx.fillRect(0, rowY(rows[0]), width, rowY(rows[1] + 1) - rowY(rows[0]))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
ctx.strokeStyle = theme.palette.text.primary
|
|
59
|
+
ctx.beginPath()
|
|
60
|
+
for (const source of descendants(root)) {
|
|
61
|
+
if (!source.children) {
|
|
62
|
+
continue
|
|
63
|
+
}
|
|
64
|
+
const sx = nodeX(source)
|
|
65
|
+
const sy = rowY(source.x!)
|
|
66
|
+
for (const target of source.children) {
|
|
67
|
+
const tx = nodeX(target)
|
|
68
|
+
const ty = rowY(target.x!)
|
|
69
|
+
if (Math.abs(tx - sx) < 1 && Math.abs(ty - sy) < 1) {
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
ctx.moveTo(sx, sy)
|
|
73
|
+
ctx.lineTo(sx, ty)
|
|
74
|
+
ctx.lineTo(tx, ty)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
ctx.stroke()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A box across the overview around a run of tip rows: the focused subtree's
|
|
82
|
+
* extent, or the one a hovered pixel would focus.
|
|
83
|
+
*/
|
|
84
|
+
export function drawTreeOverviewBox({
|
|
85
|
+
model,
|
|
86
|
+
ctx,
|
|
87
|
+
color,
|
|
88
|
+
rows,
|
|
89
|
+
width,
|
|
90
|
+
height,
|
|
91
|
+
}: Omit<OverviewArgs, 'theme'> & {
|
|
92
|
+
color: string
|
|
93
|
+
rows: [number, number]
|
|
94
|
+
}) {
|
|
95
|
+
const layout = model.treeOverviewLayout
|
|
96
|
+
if (!layout) {
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
const rowY = rowScale(layout.numTips, height)
|
|
100
|
+
const top = rowY(rows[0])
|
|
101
|
+
ctx.strokeStyle = color
|
|
102
|
+
ctx.strokeRect(0.5, top, width - 1, Math.max(2, rowY(rows[1] + 1) - top))
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The overview as the figure carries it: the tree, the clade highlights and
|
|
107
|
+
* the focus box. The SVG export draws it straight onto a svgcanvas Context,
|
|
108
|
+
* and the live canvas blits `treeOverviewImage` in place of the first two.
|
|
109
|
+
*/
|
|
110
|
+
export function renderTreeOverview(args: OverviewArgs) {
|
|
111
|
+
drawTreeOverview(args)
|
|
112
|
+
const rows = args.model.treeOverviewFocusRows
|
|
113
|
+
if (rows) {
|
|
114
|
+
drawTreeOverviewBox({
|
|
115
|
+
...args,
|
|
116
|
+
rows,
|
|
117
|
+
color: args.theme.palette.text.primary,
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface OverviewCache {
|
|
123
|
+
keys: unknown[]
|
|
124
|
+
canvas: RasterCanvas | undefined
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const caches = new WeakMap<object, OverviewCache>()
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Everything the drawn tree depends on. `treeOverviewLayout` and
|
|
131
|
+
* `treeOverviewClades` are computed views, which recompute on every read
|
|
132
|
+
* outside a reaction, so the key holds the values behind them instead.
|
|
133
|
+
*/
|
|
134
|
+
function overviewKeys({
|
|
135
|
+
model,
|
|
136
|
+
theme,
|
|
137
|
+
width,
|
|
138
|
+
height,
|
|
139
|
+
scale,
|
|
140
|
+
}: {
|
|
141
|
+
model: MsaViewModel
|
|
142
|
+
theme: Theme
|
|
143
|
+
width: number
|
|
144
|
+
height: number
|
|
145
|
+
scale: number
|
|
146
|
+
}) {
|
|
147
|
+
return [
|
|
148
|
+
model.data.tree,
|
|
149
|
+
model.data.msa,
|
|
150
|
+
model.currentAlignment,
|
|
151
|
+
model.collapsed.join(','),
|
|
152
|
+
JSON.stringify(model.clades),
|
|
153
|
+
model.showBranchLen,
|
|
154
|
+
width,
|
|
155
|
+
height,
|
|
156
|
+
scale,
|
|
157
|
+
theme.palette.text.primary,
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function makeCanvas(width: number, height: number) {
|
|
162
|
+
const canvas =
|
|
163
|
+
typeof OffscreenCanvas !== 'undefined'
|
|
164
|
+
? new OffscreenCanvas(width, height)
|
|
165
|
+
: typeof document !== 'undefined'
|
|
166
|
+
? Object.assign(document.createElement('canvas'), { width, height })
|
|
167
|
+
: undefined
|
|
168
|
+
const ctx = canvas?.getContext('2d')
|
|
169
|
+
return ctx && typeof (ctx as CanvasRenderingContext2D).stroke === 'function'
|
|
170
|
+
? { canvas: canvas as RasterCanvas, ctx: ctx as CanvasRenderingContext2D }
|
|
171
|
+
: undefined
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The tree and its clade highlights on an offscreen canvas, drawn once per
|
|
176
|
+
* change of anything they depend on and blitted every frame after that. A
|
|
177
|
+
* 230k-branch tree takes hundreds of milliseconds to walk and cannot be redrawn
|
|
178
|
+
* as the focus box follows the pointer.
|
|
179
|
+
*
|
|
180
|
+
* Returns undefined where a canvas has no 2d context (jsdom), and the caller
|
|
181
|
+
* then draws the tree itself.
|
|
182
|
+
*/
|
|
183
|
+
export function treeOverviewImage({
|
|
184
|
+
model,
|
|
185
|
+
theme,
|
|
186
|
+
width,
|
|
187
|
+
height,
|
|
188
|
+
scale,
|
|
189
|
+
}: {
|
|
190
|
+
model: MsaViewModel
|
|
191
|
+
theme: Theme
|
|
192
|
+
width: number
|
|
193
|
+
height: number
|
|
194
|
+
scale: number
|
|
195
|
+
}) {
|
|
196
|
+
const keys = overviewKeys({ model, theme, width, height, scale })
|
|
197
|
+
const prev = caches.get(model)
|
|
198
|
+
if (prev && keys.every((key, i) => key === prev.keys[i])) {
|
|
199
|
+
return prev.canvas
|
|
200
|
+
}
|
|
201
|
+
const made = makeCanvas(
|
|
202
|
+
Math.max(1, Math.round(width * scale)),
|
|
203
|
+
Math.max(1, Math.round(height * scale)),
|
|
204
|
+
)
|
|
205
|
+
if (made) {
|
|
206
|
+
made.ctx.scale(scale, scale)
|
|
207
|
+
drawTreeOverview({ model, ctx: made.ctx, theme, width, height })
|
|
208
|
+
}
|
|
209
|
+
const next = { keys, canvas: made?.canvas }
|
|
210
|
+
caches.set(model, next)
|
|
211
|
+
return next.canvas
|
|
212
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useState } from 'react'
|
|
2
2
|
|
|
3
3
|
import { ClickMapIndex } from './clickMap.ts'
|
|
4
4
|
|
|
@@ -24,9 +24,7 @@ export function useTreeHover({
|
|
|
24
24
|
model: MsaViewModel
|
|
25
25
|
offsetY: number
|
|
26
26
|
}) {
|
|
27
|
-
const
|
|
28
|
-
clickMapRef.current ??= new ClickMapIndex()
|
|
29
|
-
const clickMap = clickMapRef.current
|
|
27
|
+
const [clickMap] = useState(() => new ClickMapIndex())
|
|
30
28
|
const [hovered, setHovered] = useState<TreeHoverTarget>()
|
|
31
29
|
|
|
32
30
|
// leaf labels win over the branch/bubble targets they overlap, so a click on a
|