react-msaview 8.1.0 → 8.3.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 +104 -117
- package/bundle/index.js.map +4 -4
- 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/MSAViewer.d.ts +12 -1
- package/dist/components/MSAViewer.js +12 -1
- package/dist/components/MSAViewer.js.map +1 -1
- 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 +3 -3
- 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/getVisibleLeaves.d.ts +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/Header.js +6 -7
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/{HeaderStatusArea.d.ts → RowSearch.d.ts} +2 -2
- package/dist/components/header/RowSearch.js +15 -0
- package/dist/components/header/RowSearch.js.map +1 -0
- package/dist/components/header/revealRow.d.ts +3 -0
- package/dist/components/header/revealRow.js +15 -0
- package/dist/components/header/revealRow.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 +6 -1
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +3 -7
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/msaKeys.d.ts +5 -0
- package/dist/components/msa/msaKeys.js +35 -0
- package/dist/components/msa/msaKeys.js.map +1 -0
- package/dist/components/msa/useMsaBlockMouse.d.ts +2 -4
- package/dist/components/msa/useMsaBlockMouse.js +18 -5
- package/dist/components/msa/useMsaBlockMouse.js.map +1 -1
- package/dist/components/rowpanels/RowPanelBlock.js +10 -14
- package/dist/components/rowpanels/RowPanelBlock.js.map +1 -1
- 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 +1 -5
- package/dist/components/tracks/useTrackHover.js +5 -5
- 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/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 +4 -1
- package/dist/constants.js +12 -3
- package/dist/constants.js.map +1 -1
- package/dist/expandSpec.d.ts +46 -0
- package/dist/expandSpec.js +127 -0
- package/dist/expandSpec.js.map +1 -0
- package/dist/exportFileName.d.ts +3 -3
- package/dist/exportFileName.js +4 -4
- package/dist/exportFileName.js.map +1 -1
- 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 +4 -2
- package/dist/index.js +2 -0
- package/dist/index.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 +188 -47
- package/dist/model.js +482 -192
- 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 +114 -20
- package/dist/types.d.ts +25 -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/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/MSAViewer.tsx +26 -0
- package/src/components/SequenceTextArea.tsx +54 -8
- package/src/components/StatusMessage.tsx +70 -0
- package/src/components/Track.tsx +8 -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/header/FileMenu.tsx +9 -0
- package/src/components/header/GappynessSlider.tsx +17 -4
- package/src/components/header/Header.tsx +7 -8
- package/src/components/header/RowSearch.tsx +34 -0
- package/src/components/header/revealRow.ts +17 -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 +8 -0
- package/src/components/msa/MSACanvasBlock.tsx +5 -11
- package/src/components/msa/msaKeys.ts +39 -0
- package/src/components/msa/useMsaBlockMouse.ts +23 -7
- package/src/components/rowpanels/RowPanelBlock.tsx +16 -23
- package/src/components/tracks/drawTracks.ts +15 -2
- package/src/components/tracks/useTrackHover.ts +5 -5
- 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/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/expandSpec.ts +196 -0
- package/src/exportFileName.ts +4 -3
- package/src/hierarchy.ts +151 -4
- package/src/index.ts +11 -1
- package/src/model/treeModel.ts +22 -0
- package/src/model.ts +588 -221
- package/src/renderToSvg.tsx +6 -9
- package/src/rerootTree.ts +172 -0
- package/src/types.ts +34 -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
|
@@ -23,13 +23,8 @@ const MSACanvasBlock = observer(function ({
|
|
|
23
23
|
}) {
|
|
24
24
|
const { blockSize, highResScaleFactor } = model
|
|
25
25
|
const theme = useTheme()
|
|
26
|
-
const {
|
|
27
|
-
{
|
|
28
|
-
model,
|
|
29
|
-
offsetX,
|
|
30
|
-
offsetY,
|
|
31
|
-
},
|
|
32
|
-
)
|
|
26
|
+
const { anchor, onMouseMove, onMouseDown, onClick, onMouseLeave } =
|
|
27
|
+
useMsaBlockMouse({ model, offsetX, offsetY })
|
|
33
28
|
|
|
34
29
|
const canvasSize = blockSize * highResScaleFactor
|
|
35
30
|
const ref = useCanvasAutorun({
|
|
@@ -76,6 +71,7 @@ const MSACanvasBlock = observer(function ({
|
|
|
76
71
|
onMouseMove(event, ref.current)
|
|
77
72
|
}
|
|
78
73
|
}}
|
|
74
|
+
onMouseDown={onMouseDown}
|
|
79
75
|
onClick={event => {
|
|
80
76
|
if (ref.current) {
|
|
81
77
|
onClick(event, ref.current)
|
|
@@ -94,10 +90,8 @@ const MSACanvasBlock = observer(function ({
|
|
|
94
90
|
height: blockSize,
|
|
95
91
|
}}
|
|
96
92
|
/>
|
|
97
|
-
{
|
|
98
|
-
<BaseTooltip
|
|
99
|
-
clientPoint={{ x: tooltipPoint.x, y: tooltipPoint.y + 15 }}
|
|
100
|
-
>
|
|
93
|
+
{anchor ? (
|
|
94
|
+
<BaseTooltip clientPoint={anchor.clientPoint}>
|
|
101
95
|
{hoveredInsertion ? (
|
|
102
96
|
<>
|
|
103
97
|
Insertion ({hoveredInsertion.letters.length}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { MsaViewModel } from '../../model.ts'
|
|
2
|
+
|
|
3
|
+
export function onMsaKey(
|
|
4
|
+
model: MsaViewModel,
|
|
5
|
+
{ key, shiftKey }: { key: string; shiftKey: boolean },
|
|
6
|
+
) {
|
|
7
|
+
const { colWidth, rowHeight, msaCanvasWidth, msaAreaHeight } = model
|
|
8
|
+
const dx = shiftKey ? msaCanvasWidth : colWidth
|
|
9
|
+
const dy = shiftKey ? msaAreaHeight : rowHeight
|
|
10
|
+
switch (key) {
|
|
11
|
+
case 'ArrowLeft':
|
|
12
|
+
model.doScrollX(dx)
|
|
13
|
+
return true
|
|
14
|
+
case 'ArrowRight':
|
|
15
|
+
model.doScrollX(-dx)
|
|
16
|
+
return true
|
|
17
|
+
case 'ArrowUp':
|
|
18
|
+
model.doScrollY(dy)
|
|
19
|
+
return true
|
|
20
|
+
case 'ArrowDown':
|
|
21
|
+
model.doScrollY(-dy)
|
|
22
|
+
return true
|
|
23
|
+
case '+':
|
|
24
|
+
case '=':
|
|
25
|
+
model.zoomIn()
|
|
26
|
+
return true
|
|
27
|
+
case '-':
|
|
28
|
+
model.zoomOut()
|
|
29
|
+
return true
|
|
30
|
+
case 'Home':
|
|
31
|
+
model.setScrollX(0)
|
|
32
|
+
return true
|
|
33
|
+
case 'End':
|
|
34
|
+
model.setScrollX(model.maxScrollX)
|
|
35
|
+
return true
|
|
36
|
+
default:
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
import { useHoverAnchor } from '../useHoverAnchor.ts'
|
|
2
4
|
|
|
3
5
|
import type { MsaViewModel } from '../../model.ts'
|
|
4
6
|
import type React from 'react'
|
|
5
7
|
|
|
8
|
+
const maxClickTravel = 3
|
|
9
|
+
|
|
6
10
|
function eventToColRow({
|
|
7
11
|
event,
|
|
8
12
|
el,
|
|
@@ -44,7 +48,8 @@ export function useMsaBlockMouse({
|
|
|
44
48
|
offsetX: number
|
|
45
49
|
offsetY: number
|
|
46
50
|
}) {
|
|
47
|
-
const
|
|
51
|
+
const { anchor, hoverAt, clearAnchor } = useHoverAnchor<true>()
|
|
52
|
+
const downAt = useRef<{ x: number; y: number }>(undefined)
|
|
48
53
|
|
|
49
54
|
function colRow(event: React.MouseEvent, el: HTMLElement) {
|
|
50
55
|
const { colWidth, rowHeight } = model
|
|
@@ -65,12 +70,23 @@ export function useMsaBlockMouse({
|
|
|
65
70
|
|
|
66
71
|
const hasTooltip =
|
|
67
72
|
!!model.hoveredInsertion || model.mouseOverDomains.length > 0
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
hoverAt(event, hasTooltip || undefined)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function onMouseDown(event: React.MouseEvent) {
|
|
77
|
+
downAt.current = { x: event.clientX, y: event.clientY }
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
function onClick(event: React.MouseEvent, el: HTMLElement) {
|
|
81
|
+
const down = downAt.current
|
|
82
|
+
downAt.current = undefined
|
|
83
|
+
if (
|
|
84
|
+
down &&
|
|
85
|
+
Math.hypot(event.clientX - down.x, event.clientY - down.y) >
|
|
86
|
+
maxClickTravel
|
|
87
|
+
) {
|
|
88
|
+
return
|
|
89
|
+
}
|
|
74
90
|
const pos = colRow(event, el)
|
|
75
91
|
const { col, row } = pos
|
|
76
92
|
const { mouseClickCol, mouseClickRow } = model
|
|
@@ -83,8 +99,8 @@ export function useMsaBlockMouse({
|
|
|
83
99
|
|
|
84
100
|
function onMouseLeave() {
|
|
85
101
|
model.setMousePos()
|
|
86
|
-
|
|
102
|
+
clearAnchor()
|
|
87
103
|
}
|
|
88
104
|
|
|
89
|
-
return {
|
|
105
|
+
return { anchor, onMouseMove, onMouseDown, onClick, onMouseLeave }
|
|
90
106
|
}
|
|
@@ -1,23 +1,17 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react'
|
|
2
2
|
|
|
3
|
+
import BaseTooltip from '@jbrowse/core/ui/BaseTooltip'
|
|
3
4
|
import { useTheme } from '@mui/material'
|
|
4
5
|
import { observer } from 'mobx-react'
|
|
5
6
|
|
|
6
7
|
import { featureField, featureName } from '../../featureFields.ts'
|
|
7
8
|
import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
|
|
8
|
-
import
|
|
9
|
+
import { useHoverAnchor } from '../useHoverAnchor.ts'
|
|
9
10
|
import { renderRowPanel } from './renderRowPanel.ts'
|
|
10
11
|
|
|
11
12
|
import type { MsaViewModel } from '../../model.ts'
|
|
12
13
|
import type { ResolvedRowPanel } from '../../types.ts'
|
|
13
14
|
|
|
14
|
-
interface Hover {
|
|
15
|
-
name: string
|
|
16
|
-
lines: string[]
|
|
17
|
-
clientX: number
|
|
18
|
-
clientY: number
|
|
19
|
-
}
|
|
20
|
-
|
|
21
15
|
// what the cell or span under the pointer reads, which is the field a strip
|
|
22
16
|
// colors by, and a span's name, its coordinates and its encoded field
|
|
23
17
|
function hoverLines({
|
|
@@ -66,7 +60,10 @@ const RowPanelBlock = observer(function ({
|
|
|
66
60
|
panel: ResolvedRowPanel
|
|
67
61
|
offsetY: number
|
|
68
62
|
}) {
|
|
69
|
-
const
|
|
63
|
+
const { anchor, hoverAt, clearAnchor } = useHoverAnchor<{
|
|
64
|
+
name: string
|
|
65
|
+
lines: string[]
|
|
66
|
+
}>()
|
|
70
67
|
const theme = useTheme()
|
|
71
68
|
const { blockSize, highResScaleFactor, rowHeight } = model
|
|
72
69
|
const width = panel.width
|
|
@@ -107,30 +104,26 @@ const RowPanelBlock = observer(function ({
|
|
|
107
104
|
name,
|
|
108
105
|
x: event.clientX - left,
|
|
109
106
|
})
|
|
110
|
-
|
|
107
|
+
hoverAt(
|
|
108
|
+
event,
|
|
111
109
|
name === undefined || lines.length === 0
|
|
112
110
|
? undefined
|
|
113
|
-
: {
|
|
114
|
-
name,
|
|
115
|
-
lines,
|
|
116
|
-
clientX: event.clientX,
|
|
117
|
-
clientY: event.clientY,
|
|
118
|
-
},
|
|
111
|
+
: { name, lines },
|
|
119
112
|
)
|
|
120
113
|
model.setMousePos(undefined, name === undefined ? undefined : row)
|
|
121
114
|
}}
|
|
122
115
|
onMouseLeave={() => {
|
|
123
|
-
|
|
116
|
+
clearAnchor()
|
|
124
117
|
model.setMousePos(undefined, undefined)
|
|
125
118
|
}}
|
|
126
119
|
/>
|
|
127
|
-
{
|
|
128
|
-
<
|
|
129
|
-
<div>{
|
|
130
|
-
{
|
|
120
|
+
{anchor ? (
|
|
121
|
+
<BaseTooltip clientPoint={anchor.clientPoint}>
|
|
122
|
+
<div>{anchor.value.name}</div>
|
|
123
|
+
{anchor.value.lines.map(line => (
|
|
131
124
|
<div key={line}>{line}</div>
|
|
132
125
|
))}
|
|
133
|
-
</
|
|
126
|
+
</BaseTooltip>
|
|
134
127
|
) : null}
|
|
135
128
|
</>
|
|
136
129
|
)
|
|
@@ -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,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useHoverAnchor } from '../useHoverAnchor.ts'
|
|
2
2
|
|
|
3
3
|
import type { MsaViewModel } from '../../model.ts'
|
|
4
4
|
import type React from 'react'
|
|
@@ -18,7 +18,7 @@ export function useTrackHover({
|
|
|
18
18
|
model: MsaViewModel
|
|
19
19
|
tooltip: boolean
|
|
20
20
|
}) {
|
|
21
|
-
const
|
|
21
|
+
const { anchor, hoverAt, clearAnchor } = useHoverAnchor<number>()
|
|
22
22
|
|
|
23
23
|
// the blocks are laid out in column space and scrolled by a transform on
|
|
24
24
|
// their container, so the pointer's column comes off the untransformed row
|
|
@@ -29,14 +29,14 @@ export function useTrackHover({
|
|
|
29
29
|
const hit = col >= 0 && col < numColumns
|
|
30
30
|
model.setMousePos(hit ? col : undefined, undefined)
|
|
31
31
|
if (tooltip) {
|
|
32
|
-
|
|
32
|
+
hoverAt(event, hit ? col : undefined)
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function onMouseLeave() {
|
|
37
|
-
|
|
37
|
+
clearAnchor()
|
|
38
38
|
model.setMousePos(undefined, undefined)
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
return {
|
|
41
|
+
return { anchor: tooltip ? anchor : undefined, onMouseMove, onMouseLeave }
|
|
42
42
|
}
|
|
@@ -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}
|