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
|
@@ -159,6 +159,20 @@ export function rulerStep(colWidth: number, minPixels = 55) {
|
|
|
159
159
|
return step
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
// The 0-based columns in [xStart, end) to label: column 1, then each column
|
|
163
|
+
// whose 1-based number is a multiple of the step. A step of 2 or more puts the
|
|
164
|
+
// first multiple at least half a step's width past column 1, which clears the
|
|
165
|
+
// two labels.
|
|
166
|
+
export function rulerCols(xStart: number, end: number, colWidth: number) {
|
|
167
|
+
const step = rulerStep(colWidth)
|
|
168
|
+
const cols = step > 1 && xStart <= 0 && end > 0 ? [0] : []
|
|
169
|
+
const first = Math.max(1, Math.ceil((xStart + 1) / step)) * step
|
|
170
|
+
for (let n = first; n <= end; n += step) {
|
|
171
|
+
cols.push(n - 1)
|
|
172
|
+
}
|
|
173
|
+
return cols
|
|
174
|
+
}
|
|
175
|
+
|
|
162
176
|
const RULER_FONT_SIZE = 10
|
|
163
177
|
const TICK_HEIGHT = 4
|
|
164
178
|
|
|
@@ -186,7 +200,6 @@ export function drawColumnRuler({
|
|
|
186
200
|
blockWidth: blockSize,
|
|
187
201
|
colWidth,
|
|
188
202
|
})
|
|
189
|
-
const step = rulerStep(colWidth)
|
|
190
203
|
const end = Math.min(xEnd, numColumns)
|
|
191
204
|
setFontSize(ctx, RULER_FONT_SIZE)
|
|
192
205
|
ctx.textAlign = 'center'
|
|
@@ -195,7 +208,7 @@ export function drawColumnRuler({
|
|
|
195
208
|
ctx.strokeStyle = textColor
|
|
196
209
|
ctx.lineWidth = 1
|
|
197
210
|
|
|
198
|
-
for (
|
|
211
|
+
for (const col of rulerCols(xStart, end, colWidth)) {
|
|
199
212
|
const text = label(col)
|
|
200
213
|
if (text === undefined) {
|
|
201
214
|
continue
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dragSelection } from '../selectionDrag.ts'
|
|
2
|
+
import { useHoverAnchor } from '../useHoverAnchor.ts'
|
|
2
3
|
|
|
3
4
|
import type { MsaViewModel } from '../../model.ts'
|
|
4
5
|
import type React from 'react'
|
|
@@ -6,7 +7,8 @@ import type React from 'react'
|
|
|
6
7
|
/**
|
|
7
8
|
* Translates pointer events over a track's row into the hovered column and a
|
|
8
9
|
* tooltip anchor. The track shares the alignment's columns, so hovering it sets
|
|
9
|
-
* the model's mouse column too and the alignment's hover band follows along.
|
|
10
|
+
* the model's mouse column too and the alignment's hover band follows along. A
|
|
11
|
+
* drag along the row selects the columns it covers across every row.
|
|
10
12
|
*
|
|
11
13
|
* `tooltip` off skips the anchor, so a track whose tooltip is turned off still
|
|
12
14
|
* drives the hover band without re-rendering on every mouse move.
|
|
@@ -18,25 +20,45 @@ export function useTrackHover({
|
|
|
18
20
|
model: MsaViewModel
|
|
19
21
|
tooltip: boolean
|
|
20
22
|
}) {
|
|
21
|
-
const
|
|
23
|
+
const { anchor, hoverAt, clearAnchor } = useHoverAnchor<number>()
|
|
22
24
|
|
|
23
25
|
// the blocks are laid out in column space and scrolled by a transform on
|
|
24
26
|
// their container, so the pointer's column comes off the untransformed row
|
|
27
|
+
function colAt(clientX: number, left: number) {
|
|
28
|
+
return Math.floor((clientX - left - model.scrollX) / model.colWidth)
|
|
29
|
+
}
|
|
30
|
+
|
|
25
31
|
function onMouseMove(event: React.MouseEvent, el: HTMLElement) {
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const col = Math.floor((event.clientX - left - scrollX) / colWidth)
|
|
29
|
-
const hit = col >= 0 && col < numColumns
|
|
32
|
+
const col = colAt(event.clientX, el.getBoundingClientRect().left)
|
|
33
|
+
const hit = col >= 0 && col < model.numColumns
|
|
30
34
|
model.setMousePos(hit ? col : undefined, undefined)
|
|
31
35
|
if (tooltip) {
|
|
32
|
-
|
|
36
|
+
hoverAt(event, hit ? col : undefined)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// a press on a track's canvas, which leaves out the resize divider under it.
|
|
41
|
+
// The row takes the focus, so Escape reaches onMsaKey from there too
|
|
42
|
+
function onMouseDown(event: React.MouseEvent, el: HTMLElement) {
|
|
43
|
+
if (event.button === 0 && event.target instanceof HTMLCanvasElement) {
|
|
44
|
+
event.preventDefault()
|
|
45
|
+
el.focus({ preventScroll: true })
|
|
46
|
+
const { left } = el.getBoundingClientRect()
|
|
47
|
+
dragSelection(model, event, ({ clientX }) => ({
|
|
48
|
+
col: colAt(clientX, left),
|
|
49
|
+
}))
|
|
33
50
|
}
|
|
34
51
|
}
|
|
35
52
|
|
|
36
53
|
function onMouseLeave() {
|
|
37
|
-
|
|
54
|
+
clearAnchor()
|
|
38
55
|
model.setMousePos(undefined, undefined)
|
|
39
56
|
}
|
|
40
57
|
|
|
41
|
-
return {
|
|
58
|
+
return {
|
|
59
|
+
anchor: tooltip ? anchor : undefined,
|
|
60
|
+
onMouseMove,
|
|
61
|
+
onMouseDown,
|
|
62
|
+
onMouseLeave,
|
|
63
|
+
}
|
|
42
64
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The branch-length scale bar: its label over a bracket `bar.px` wide, both
|
|
5
|
+
* resting on `y`.
|
|
6
|
+
*/
|
|
7
|
+
export default function ScaleBarSVG({
|
|
8
|
+
bar,
|
|
9
|
+
marginLeft,
|
|
10
|
+
y,
|
|
11
|
+
color,
|
|
12
|
+
}: {
|
|
13
|
+
bar: { label: string; px: number }
|
|
14
|
+
marginLeft: number
|
|
15
|
+
y: number
|
|
16
|
+
color: string
|
|
17
|
+
}) {
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
<text x={marginLeft} y={y - 9} fontSize={10} fill={color}>
|
|
21
|
+
{bar.label}
|
|
22
|
+
</text>
|
|
23
|
+
<path
|
|
24
|
+
d={`M${marginLeft} ${y - 7} v6 h${bar.px} v-6`}
|
|
25
|
+
fill="none"
|
|
26
|
+
stroke={color}
|
|
27
|
+
/>
|
|
28
|
+
</>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -5,15 +5,15 @@ import { observer } from 'mobx-react'
|
|
|
5
5
|
|
|
6
6
|
import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
|
|
7
7
|
import { useWheelScroll } from '../../useWheelScroll.ts'
|
|
8
|
-
import { referenceColor, treeHoverColor } from '../overlayColors.ts'
|
|
9
8
|
import CladeLabels from './CladeLabels.tsx'
|
|
10
9
|
import TreeCanvasBlock from './TreeCanvasBlock.tsx'
|
|
10
|
+
import { renderTreeMouseover } from './renderTreeMouseover.ts'
|
|
11
11
|
|
|
12
12
|
import type { MsaViewModel } from '../../model.ts'
|
|
13
13
|
|
|
14
14
|
const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
15
15
|
const ref = useRef<HTMLDivElement>(null)
|
|
16
|
-
const { height, blocksY, treeAreaWidth, scrollY } = model
|
|
16
|
+
const { height, blocksY, treeAreaWidth, scrollY, highResScaleFactor } = model
|
|
17
17
|
const onScrollY = useCallback(
|
|
18
18
|
(d: number) => {
|
|
19
19
|
model.doScrollY(d)
|
|
@@ -22,47 +22,16 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
22
22
|
)
|
|
23
23
|
const { onMouseDown, onMouseUp } = useWheelScroll({ ref, onScrollY })
|
|
24
24
|
|
|
25
|
+
const canvasWidth = treeAreaWidth * highResScaleFactor
|
|
26
|
+
const canvasHeight = height * highResScaleFactor
|
|
25
27
|
const mouseoverRef = useCanvasAutorun({
|
|
26
28
|
draw: ctx => {
|
|
27
29
|
if (isAlive(model)) {
|
|
28
|
-
|
|
29
|
-
rowHeight,
|
|
30
|
-
treeAreaWidth: w,
|
|
31
|
-
height: h,
|
|
32
|
-
scrollY: sy,
|
|
33
|
-
mouseRow,
|
|
34
|
-
referenceRowIndex,
|
|
35
|
-
hoveredRowIndices,
|
|
36
|
-
} = model
|
|
37
|
-
ctx.resetTransform()
|
|
38
|
-
ctx.clearRect(0, 0, w, h)
|
|
39
|
-
|
|
40
|
-
// rows are laid out at a fixed pitch, so a row index is all that is
|
|
41
|
-
// needed to place its band (leaf.x is rowHeight*(index+0.5))
|
|
42
|
-
const fillRow = (index: number) => {
|
|
43
|
-
ctx.fillRect(0, index * rowHeight + sy, w, rowHeight)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (referenceRowIndex !== undefined) {
|
|
47
|
-
ctx.fillStyle = referenceColor
|
|
48
|
-
fillRow(referenceRowIndex)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
ctx.fillStyle = treeHoverColor
|
|
52
|
-
for (const index of hoveredRowIndices) {
|
|
53
|
-
fillRow(index)
|
|
54
|
-
}
|
|
55
|
-
if (
|
|
56
|
-
mouseRow !== undefined &&
|
|
57
|
-
mouseRow !== referenceRowIndex &&
|
|
58
|
-
!hoveredRowIndices.includes(mouseRow)
|
|
59
|
-
) {
|
|
60
|
-
fillRow(mouseRow)
|
|
61
|
-
}
|
|
30
|
+
renderTreeMouseover({ ctx, model })
|
|
62
31
|
}
|
|
63
32
|
},
|
|
64
|
-
width:
|
|
65
|
-
height,
|
|
33
|
+
width: canvasWidth,
|
|
34
|
+
height: canvasHeight,
|
|
66
35
|
deps: [model],
|
|
67
36
|
})
|
|
68
37
|
|
|
@@ -95,8 +64,8 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
95
64
|
</div>
|
|
96
65
|
<canvas
|
|
97
66
|
ref={mouseoverRef}
|
|
98
|
-
width={
|
|
99
|
-
height={
|
|
67
|
+
width={canvasWidth}
|
|
68
|
+
height={canvasHeight}
|
|
100
69
|
style={{
|
|
101
70
|
position: 'absolute',
|
|
102
71
|
top: 0,
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
|
|
3
|
+
import BaseTooltip from '@jbrowse/core/ui/BaseTooltip'
|
|
3
4
|
import { makeStyles } from '@jbrowse/core/util/tss-react'
|
|
4
5
|
import { useTheme } from '@mui/material'
|
|
5
6
|
import { observer } from 'mobx-react'
|
|
6
7
|
|
|
7
8
|
import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
|
|
8
|
-
import
|
|
9
|
-
import TreeBranchMenu from './TreeBranchMenu.tsx'
|
|
10
|
-
import TreeNodeMenu from './TreeNodeMenu.tsx'
|
|
9
|
+
import TreeMenu from './TreeMenu.tsx'
|
|
11
10
|
import { renderTreeCanvas } from './renderTreeCanvas.ts'
|
|
12
11
|
import { useTreeHover } from './useTreeHover.ts'
|
|
13
12
|
|
|
14
13
|
import type { MsaViewModel } from '../../model.ts'
|
|
14
|
+
import type { TreeMenuTarget } from './TreeMenu.tsx'
|
|
15
15
|
|
|
16
16
|
const useStyles = makeStyles()(theme => ({
|
|
17
17
|
hover: {
|
|
@@ -22,13 +22,6 @@ const useStyles = makeStyles()(theme => ({
|
|
|
22
22
|
},
|
|
23
23
|
}))
|
|
24
24
|
|
|
25
|
-
interface MenuData {
|
|
26
|
-
name: string
|
|
27
|
-
id: string
|
|
28
|
-
x: number
|
|
29
|
-
y: number
|
|
30
|
-
}
|
|
31
|
-
|
|
32
25
|
const TreeCanvasBlock = observer(function ({
|
|
33
26
|
model,
|
|
34
27
|
offsetY,
|
|
@@ -38,10 +31,11 @@ const TreeCanvasBlock = observer(function ({
|
|
|
38
31
|
}) {
|
|
39
32
|
const { classes } = useStyles()
|
|
40
33
|
const theme = useTheme()
|
|
41
|
-
const [
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
const [menu, setMenu] = useState<TreeMenuTarget>()
|
|
35
|
+
const { clickMap, anchor, hitTest, onMouseMove, onMouseLeave } = useTreeHover(
|
|
36
|
+
{ model, offsetY },
|
|
37
|
+
)
|
|
38
|
+
const hovered = anchor?.value
|
|
45
39
|
|
|
46
40
|
const { treeAreaWidth, blockSize, highResScaleFactor } = model
|
|
47
41
|
// TreePanel clips to treeAreaWidth, so a wider canvas is wasted backing store
|
|
@@ -77,22 +71,12 @@ const TreeCanvasBlock = observer(function ({
|
|
|
77
71
|
|
|
78
72
|
return (
|
|
79
73
|
<>
|
|
80
|
-
{
|
|
81
|
-
<
|
|
82
|
-
node={
|
|
83
|
-
model={model}
|
|
84
|
-
onClose={() => {
|
|
85
|
-
setBranchMenu(undefined)
|
|
86
|
-
}}
|
|
87
|
-
/>
|
|
88
|
-
) : null}
|
|
89
|
-
|
|
90
|
-
{nodeMenu ? (
|
|
91
|
-
<TreeNodeMenu
|
|
92
|
-
node={nodeMenu}
|
|
74
|
+
{menu ? (
|
|
75
|
+
<TreeMenu
|
|
76
|
+
node={menu}
|
|
93
77
|
model={model}
|
|
94
78
|
onClose={() => {
|
|
95
|
-
|
|
79
|
+
setMenu(undefined)
|
|
96
80
|
}}
|
|
97
81
|
/>
|
|
98
82
|
) : null}
|
|
@@ -111,17 +95,13 @@ const TreeCanvasBlock = observer(function ({
|
|
|
111
95
|
onClick={event => {
|
|
112
96
|
const entry = hitTest(event)
|
|
113
97
|
if (entry) {
|
|
114
|
-
|
|
98
|
+
setMenu({
|
|
115
99
|
x: event.clientX,
|
|
116
100
|
y: event.clientY,
|
|
117
101
|
id: entry.id,
|
|
118
102
|
name: entry.name,
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
setBranchMenu(menu)
|
|
122
|
-
} else {
|
|
123
|
-
setNodeMenu(menu)
|
|
124
|
-
}
|
|
103
|
+
leaf: !entry.branch,
|
|
104
|
+
})
|
|
125
105
|
}
|
|
126
106
|
}}
|
|
127
107
|
/>
|
|
@@ -139,10 +119,10 @@ const TreeCanvasBlock = observer(function ({
|
|
|
139
119
|
/>
|
|
140
120
|
) : null}
|
|
141
121
|
|
|
142
|
-
{
|
|
143
|
-
<
|
|
144
|
-
{
|
|
145
|
-
</
|
|
122
|
+
{anchor ? (
|
|
123
|
+
<BaseTooltip clientPoint={anchor.clientPoint}>
|
|
124
|
+
{anchor.value.name}
|
|
125
|
+
</BaseTooltip>
|
|
146
126
|
) : null}
|
|
147
127
|
</>
|
|
148
128
|
)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import React, { lazy } from 'react'
|
|
2
|
+
|
|
3
|
+
import { Menu, MenuItem } from '@mui/material'
|
|
4
|
+
import { observer } from 'mobx-react'
|
|
5
|
+
|
|
6
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
7
|
+
|
|
8
|
+
const TreeNodeInfoDialog = lazy(
|
|
9
|
+
() => import('./dialogs/TreeNodeInfoDialog.tsx'),
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
export interface TreeMenuTarget {
|
|
13
|
+
x: number
|
|
14
|
+
y: number
|
|
15
|
+
name: string
|
|
16
|
+
id: string
|
|
17
|
+
leaf: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const TreeMenu = observer(function ({
|
|
21
|
+
node,
|
|
22
|
+
model,
|
|
23
|
+
onClose,
|
|
24
|
+
}: {
|
|
25
|
+
node: TreeMenuTarget
|
|
26
|
+
model: MsaViewModel
|
|
27
|
+
onClose: () => void
|
|
28
|
+
}) {
|
|
29
|
+
const { collapsed, showOnly, relativeTo } = model
|
|
30
|
+
const { name, id, leaf } = node
|
|
31
|
+
const isCollapsed = collapsed.includes(id)
|
|
32
|
+
const isRoot = id === model.tree.id
|
|
33
|
+
const noun = leaf ? 'subtree' : 'this node'
|
|
34
|
+
return (
|
|
35
|
+
<Menu
|
|
36
|
+
anchorReference="anchorPosition"
|
|
37
|
+
anchorPosition={{ left: node.x, top: node.y }}
|
|
38
|
+
transitionDuration={0}
|
|
39
|
+
open
|
|
40
|
+
onClose={onClose}
|
|
41
|
+
>
|
|
42
|
+
<MenuItem dense disabled>
|
|
43
|
+
{name}
|
|
44
|
+
</MenuItem>
|
|
45
|
+
{leaf ? (
|
|
46
|
+
<MenuItem
|
|
47
|
+
dense
|
|
48
|
+
onClick={() => {
|
|
49
|
+
model.queueDialog(onClose => [
|
|
50
|
+
TreeNodeInfoDialog,
|
|
51
|
+
{ info: model.getRowData(name), model, nodeName: name, onClose },
|
|
52
|
+
])
|
|
53
|
+
onClose()
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
More info...
|
|
57
|
+
</MenuItem>
|
|
58
|
+
) : null}
|
|
59
|
+
<MenuItem
|
|
60
|
+
dense
|
|
61
|
+
onClick={() => {
|
|
62
|
+
model.toggleCollapsed(id)
|
|
63
|
+
onClose()
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
{isCollapsed ? `Expand ${noun}` : `Collapse ${noun}`}
|
|
67
|
+
</MenuItem>
|
|
68
|
+
{leaf ? null : (
|
|
69
|
+
<MenuItem
|
|
70
|
+
dense
|
|
71
|
+
onClick={() => {
|
|
72
|
+
model.setShowOnly(showOnly === id ? undefined : id)
|
|
73
|
+
onClose()
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
{showOnly === id
|
|
77
|
+
? 'Disable show only this node'
|
|
78
|
+
: 'Show only this node'}
|
|
79
|
+
</MenuItem>
|
|
80
|
+
)}
|
|
81
|
+
{leaf || isCollapsed ? null : (
|
|
82
|
+
<MenuItem
|
|
83
|
+
dense
|
|
84
|
+
onClick={() => {
|
|
85
|
+
model.toggleRotated(id)
|
|
86
|
+
onClose()
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
Rotate this node
|
|
90
|
+
</MenuItem>
|
|
91
|
+
)}
|
|
92
|
+
{isRoot ? null : (
|
|
93
|
+
<MenuItem
|
|
94
|
+
dense
|
|
95
|
+
onClick={() => {
|
|
96
|
+
model.rerootAt(id)
|
|
97
|
+
onClose()
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
Reroot here
|
|
101
|
+
</MenuItem>
|
|
102
|
+
)}
|
|
103
|
+
{leaf ? (
|
|
104
|
+
<MenuItem
|
|
105
|
+
dense
|
|
106
|
+
onClick={() => {
|
|
107
|
+
model.drawRelativeTo(name)
|
|
108
|
+
onClose()
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
Indicate differences from this row
|
|
112
|
+
</MenuItem>
|
|
113
|
+
) : null}
|
|
114
|
+
{leaf && relativeTo ? (
|
|
115
|
+
<MenuItem
|
|
116
|
+
dense
|
|
117
|
+
onClick={() => {
|
|
118
|
+
model.drawRelativeTo(undefined)
|
|
119
|
+
onClose()
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
Clear reference row
|
|
123
|
+
</MenuItem>
|
|
124
|
+
) : null}
|
|
125
|
+
</Menu>
|
|
126
|
+
)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
export default TreeMenu
|
|
@@ -4,6 +4,7 @@ import { useTheme } from '@mui/material'
|
|
|
4
4
|
import { observer } from 'mobx-react'
|
|
5
5
|
|
|
6
6
|
import { treeScaleBarHeight as h } from '../../constants.ts'
|
|
7
|
+
import ScaleBarSVG from './ScaleBarSVG.tsx'
|
|
7
8
|
|
|
8
9
|
import type { MsaViewModel } from '../../model.ts'
|
|
9
10
|
|
|
@@ -18,19 +19,12 @@ const TreeRuler = observer(({ model }: { model: MsaViewModel }) => {
|
|
|
18
19
|
return (
|
|
19
20
|
<div style={{ flexShrink: 0, width: treeAreaWidth }}>
|
|
20
21
|
{bar ? (
|
|
21
|
-
<svg
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
<text x={marginLeft} y={h - 9} fontSize={10} fill="currentColor">
|
|
28
|
-
{bar.label}
|
|
29
|
-
</text>
|
|
30
|
-
<path
|
|
31
|
-
d={`M${marginLeft} ${h - 7} v6 h${bar.px} v-6`}
|
|
32
|
-
fill="none"
|
|
33
|
-
stroke="currentColor"
|
|
22
|
+
<svg width={treeAreaWidth} height={h} style={{ display: 'block' }}>
|
|
23
|
+
<ScaleBarSVG
|
|
24
|
+
bar={bar}
|
|
25
|
+
marginLeft={marginLeft}
|
|
26
|
+
y={h}
|
|
27
|
+
color={theme.palette.text.primary}
|
|
34
28
|
/>
|
|
35
29
|
</svg>
|
|
36
30
|
) : null}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
|
-
import Attributes from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail/Attributes'
|
|
4
|
-
import BaseCard from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail/BaseCard'
|
|
5
3
|
import { Dialog } from '@jbrowse/core/ui'
|
|
6
|
-
import { DialogContent } from '@mui/material'
|
|
4
|
+
import { DialogContent, Typography } from '@mui/material'
|
|
7
5
|
import { observer } from 'mobx-react'
|
|
8
6
|
|
|
7
|
+
import MetadataTable from '../../MetadataTable.tsx'
|
|
9
8
|
import SequenceTextArea from '../../SequenceTextArea.tsx'
|
|
10
9
|
|
|
11
10
|
import type { MsaViewModel } from '../../../model.ts'
|
|
@@ -34,20 +33,19 @@ const TreeNodeInfoDialog = observer(function ({
|
|
|
34
33
|
maxWidth="xl"
|
|
35
34
|
>
|
|
36
35
|
<DialogContent>
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
</
|
|
40
|
-
|
|
41
|
-
{res
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
)}
|
|
46
|
-
</BaseCard>
|
|
36
|
+
<Typography variant="h6">Attributes</Typography>
|
|
37
|
+
<MetadataTable record={{ nodeName, ...info }} />
|
|
38
|
+
<Typography variant="h6">Sequence</Typography>
|
|
39
|
+
{res ? (
|
|
40
|
+
<SequenceTextArea str={[res]} />
|
|
41
|
+
) : (
|
|
42
|
+
<Typography>Sequence not found</Typography>
|
|
43
|
+
)}
|
|
47
44
|
{metadata ? (
|
|
48
|
-
|
|
49
|
-
<
|
|
50
|
-
|
|
45
|
+
<>
|
|
46
|
+
<Typography variant="h6">Extra metadata</Typography>
|
|
47
|
+
<MetadataTable record={metadata} />
|
|
48
|
+
</>
|
|
51
49
|
) : null}
|
|
52
50
|
</DialogContent>
|
|
53
51
|
</Dialog>
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
highlightLabelHeight,
|
|
7
7
|
highlightRowFill,
|
|
8
8
|
} from '../msa/renderHighlights.ts'
|
|
9
|
+
import { referenceColor } from '../overlayColors.ts'
|
|
9
10
|
import { bracketBarWidth, bracketGap } from './cladeBrackets.ts'
|
|
10
11
|
|
|
11
12
|
import type { HierarchyNode } from '../../hierarchy.ts'
|
|
@@ -64,6 +65,13 @@ function forEachNodeInBlock(
|
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
// withId gives an unnamed node its path, node-0-1-1, as its name
|
|
69
|
+
function cladeName(node: HierarchyNode, unnamed: string) {
|
|
70
|
+
const { id, name } = node.data
|
|
71
|
+
const label = name === id ? unnamed : name
|
|
72
|
+
return node.value === undefined ? label : `${label} (${node.value} tips)`
|
|
73
|
+
}
|
|
74
|
+
|
|
67
75
|
// Calculate node x-coordinate for both phylogram (with branch lengths) and
|
|
68
76
|
// cladogram (topology only) modes. tipX is the pixel x the tips sit at.
|
|
69
77
|
// For cladograms: x = (maxDepthToLeaf - nodeDepthToLeaf) / maxDepthToLeaf * tipX
|
|
@@ -182,7 +190,7 @@ function renderCollapsedTriangles({
|
|
|
182
190
|
const by = blockSizeYOverride ?? blockSize
|
|
183
191
|
const halfHeight = Math.max(2, rowHeight * 0.42)
|
|
184
192
|
forEachNodeInBlock(hierarchy, offsetY, by, blockPad(model), node => {
|
|
185
|
-
const { id
|
|
193
|
+
const { id } = node.data
|
|
186
194
|
if (collapsedSet.has(id)) {
|
|
187
195
|
const apexX = getNodeX(node, showBranchLen, tipX, maxDepthToLeaf)
|
|
188
196
|
const y = node.x!
|
|
@@ -212,7 +220,6 @@ function renderCollapsedTriangles({
|
|
|
212
220
|
// the whole triangle is a click/hover target that opens the
|
|
213
221
|
// branch menu (Expand this node); the apex bubble's own click entry is
|
|
214
222
|
// skipped for collapsed nodes so this descriptive label wins
|
|
215
|
-
const label = name === id ? 'Collapsed clade' : name
|
|
216
223
|
clickMap?.insert({
|
|
217
224
|
minX: apexX + ml,
|
|
218
225
|
maxX: baseX + ml,
|
|
@@ -220,7 +227,7 @@ function renderCollapsedTriangles({
|
|
|
220
227
|
maxY: y + halfHeight,
|
|
221
228
|
branch: true,
|
|
222
229
|
id,
|
|
223
|
-
name:
|
|
230
|
+
name: cladeName(node, 'Collapsed clade'),
|
|
224
231
|
})
|
|
225
232
|
}
|
|
226
233
|
}
|
|
@@ -266,7 +273,7 @@ function renderNodeBubbles({
|
|
|
266
273
|
}
|
|
267
274
|
const { data } = node
|
|
268
275
|
const y = node.x!
|
|
269
|
-
const { id
|
|
276
|
+
const { id } = data
|
|
270
277
|
if (node.height >= 1 && inYBlock(y, offsetY, by, blockPad(model))) {
|
|
271
278
|
const isCollapsed = collapsedSet.has(id)
|
|
272
279
|
if (draw) {
|
|
@@ -290,7 +297,7 @@ function renderNodeBubbles({
|
|
|
290
297
|
maxY: y - radius + d,
|
|
291
298
|
branch: true,
|
|
292
299
|
id,
|
|
293
|
-
name,
|
|
300
|
+
name: cladeName(node, 'Clade'),
|
|
294
301
|
})
|
|
295
302
|
}
|
|
296
303
|
}
|
|
@@ -572,9 +579,9 @@ function renderCladeBrackets({
|
|
|
572
579
|
ctx.strokeStyle = theme.palette.text.primary
|
|
573
580
|
}
|
|
574
581
|
|
|
575
|
-
// the `rowTint` encoding and the row sets of `highlights`,
|
|
576
|
-
// tree area under the labels, with each set's label in the
|
|
577
|
-
// row
|
|
582
|
+
// the `rowTint` encoding, the reference row and the row sets of `highlights`,
|
|
583
|
+
// washed across the tree area under the labels, with each set's label in the
|
|
584
|
+
// gutter at its first row
|
|
578
585
|
function renderRowHighlights({
|
|
579
586
|
ctx,
|
|
580
587
|
model,
|
|
@@ -591,6 +598,7 @@ function renderRowHighlights({
|
|
|
591
598
|
const {
|
|
592
599
|
resolvedHighlights,
|
|
593
600
|
rowTints,
|
|
601
|
+
referenceRowIndex,
|
|
594
602
|
rowHeight,
|
|
595
603
|
treeAreaWidth,
|
|
596
604
|
marginLeft,
|
|
@@ -616,6 +624,10 @@ function renderRowHighlights({
|
|
|
616
624
|
}
|
|
617
625
|
}
|
|
618
626
|
}
|
|
627
|
+
if (referenceRowIndex !== undefined) {
|
|
628
|
+
ctx.fillStyle = referenceColor
|
|
629
|
+
fillRow(referenceRowIndex)
|
|
630
|
+
}
|
|
619
631
|
for (const { rowIndices, label, color } of resolvedHighlights) {
|
|
620
632
|
ctx.fillStyle = color ?? highlightRowFill
|
|
621
633
|
for (const index of rowIndices) {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { treeHoverColor } from '../overlayColors.ts'
|
|
2
|
+
|
|
3
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
4
|
+
|
|
5
|
+
export function renderTreeMouseover({
|
|
6
|
+
ctx,
|
|
7
|
+
model,
|
|
8
|
+
}: {
|
|
9
|
+
ctx: CanvasRenderingContext2D
|
|
10
|
+
model: MsaViewModel
|
|
11
|
+
}) {
|
|
12
|
+
const {
|
|
13
|
+
rowHeight,
|
|
14
|
+
treeAreaWidth: width,
|
|
15
|
+
height,
|
|
16
|
+
scrollY,
|
|
17
|
+
mouseRow,
|
|
18
|
+
referenceRowIndex,
|
|
19
|
+
hoveredRowIndices,
|
|
20
|
+
highResScaleFactor,
|
|
21
|
+
} = model
|
|
22
|
+
ctx.resetTransform()
|
|
23
|
+
ctx.clearRect(0, 0, width * highResScaleFactor, height * highResScaleFactor)
|
|
24
|
+
ctx.scale(highResScaleFactor, highResScaleFactor)
|
|
25
|
+
|
|
26
|
+
const rowBand = (index: number) => {
|
|
27
|
+
ctx.fillRect(0, index * rowHeight + scrollY, width, rowHeight)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
ctx.fillStyle = treeHoverColor
|
|
31
|
+
for (const index of hoveredRowIndices) {
|
|
32
|
+
rowBand(index)
|
|
33
|
+
}
|
|
34
|
+
if (
|
|
35
|
+
mouseRow !== undefined &&
|
|
36
|
+
mouseRow !== referenceRowIndex &&
|
|
37
|
+
!hoveredRowIndices.includes(mouseRow)
|
|
38
|
+
) {
|
|
39
|
+
rowBand(mouseRow)
|
|
40
|
+
}
|
|
41
|
+
}
|