react-msaview 5.5.0 → 5.6.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 +173 -162
- package/bundle/index.js.map +4 -4
- package/dist/components/ConservationTrack.js +20 -26
- package/dist/components/ConservationTrack.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +7 -1
- package/dist/components/MSAViewer.js +4 -1
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/SequenceTextArea.js +1 -1
- package/dist/components/SequenceTextArea.js.map +1 -1
- package/dist/components/TextTrack.js +20 -27
- package/dist/components/TextTrack.js.map +1 -1
- package/dist/components/Track.js +1 -1
- package/dist/components/Track.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/ColorSchemeMenu.js +1 -1
- package/dist/components/header/ColorSchemeMenu.js.map +1 -1
- package/dist/components/header/FileMenu.js +1 -16
- package/dist/components/header/FileMenu.js.map +1 -1
- package/dist/components/header/Header.js +6 -5
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/HeaderInfoArea.js +1 -1
- package/dist/components/header/HeaderInfoArea.js.map +1 -1
- package/dist/components/header/HeaderStatusArea.js +1 -1
- package/dist/components/header/HeaderStatusArea.js.map +1 -1
- package/dist/components/header/MSASettingsMenu.js +1 -1
- package/dist/components/header/MSASettingsMenu.js.map +1 -1
- package/dist/components/header/TreeSettingsMenu.js +1 -1
- package/dist/components/header/TreeSettingsMenu.js.map +1 -1
- package/dist/components/header/ZoomControls.js +14 -12
- package/dist/components/header/ZoomControls.js.map +1 -1
- package/dist/components/header/ZoomMenu.js +1 -10
- package/dist/components/header/ZoomMenu.js.map +1 -1
- package/dist/components/header/settingsMenuItems.js +9 -1
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/{header/HeaderMenu.d.ts → msa/ColumnStats.d.ts} +3 -3
- package/dist/components/msa/ColumnStats.js +42 -0
- package/dist/components/msa/ColumnStats.js.map +1 -0
- package/dist/components/msa/DomainLegend.js +1 -1
- package/dist/components/msa/DomainLegend.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +39 -39
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/MSAMouseoverCanvas.js +6 -12
- package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +3 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +1 -2
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/overlayColors.d.ts +3 -0
- package/dist/components/overlayColors.js +11 -0
- package/dist/components/overlayColors.js.map +1 -0
- package/dist/components/tracks/renderTracksSvg.d.ts +6 -3
- package/dist/components/tracks/renderTracksSvg.js +17 -8
- package/dist/components/tracks/renderTracksSvg.js.map +1 -1
- package/dist/components/tree/TreeCanvas.js +31 -38
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +12 -47
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/clickMap.d.ts +24 -0
- package/dist/components/tree/clickMap.js +33 -0
- package/dist/components/tree/clickMap.js.map +1 -0
- package/dist/components/tree/renderTreeCanvas.d.ts +1 -21
- package/dist/components/tree/renderTreeCanvas.js +8 -7
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/fetchUtils.d.ts +2 -1
- package/dist/fetchUtils.js +3 -2
- package/dist/fetchUtils.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/model/msaModel.d.ts +25 -2
- package/dist/model/msaModel.js +15 -3
- package/dist/model/msaModel.js.map +1 -1
- package/dist/model/treeModel.d.ts +46 -5
- package/dist/model/treeModel.js +20 -7
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +545 -132
- package/dist/model.js +101 -75
- package/dist/model.js.map +1 -1
- package/dist/propertyConservation.d.ts +7 -0
- package/dist/propertyConservation.js +66 -0
- package/dist/propertyConservation.js.map +1 -0
- package/dist/types.d.ts +4 -1
- package/dist/useCanvasAutorun.d.ts +2 -0
- package/dist/useCanvasAutorun.js +23 -0
- package/dist/useCanvasAutorun.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -6
- package/src/colorSchemes.docs.test.ts +55 -0
- package/src/columnStats.test.ts +94 -0
- package/src/components/ConservationTrack.tsx +17 -15
- package/src/components/MSAViewer.tsx +12 -0
- package/src/components/SequenceTextArea.tsx +1 -1
- package/src/components/TextTrack.tsx +7 -11
- package/src/components/Track.tsx +1 -1
- package/src/components/dialogs/TrackInfoDialog.tsx +1 -1
- package/src/components/header/ColorSchemeMenu.tsx +1 -0
- package/src/components/header/FileMenu.tsx +1 -15
- package/src/components/header/Header.tsx +11 -8
- package/src/components/header/HeaderInfoArea.tsx +1 -1
- package/src/components/header/HeaderStatusArea.tsx +1 -1
- package/src/components/header/MSASettingsMenu.tsx +1 -0
- package/src/components/header/TreeSettingsMenu.tsx +1 -0
- package/src/components/header/ZoomControls.tsx +24 -17
- package/src/components/header/ZoomMenu.tsx +1 -10
- package/src/components/header/settingsMenuItems.ts +9 -1
- package/src/components/msa/ColumnStats.tsx +54 -0
- package/src/components/msa/DomainLegend.tsx +5 -1
- package/src/components/msa/MSACanvasBlock.tsx +29 -26
- package/src/components/msa/MSAMouseoverCanvas.tsx +10 -13
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +3 -1
- package/src/components/msa/renderMSAMouseover.ts +2 -2
- package/src/components/overlayColors.ts +12 -0
- package/src/components/tracks/renderTracksSvg.ts +22 -8
- package/src/components/tree/TreeCanvas.tsx +47 -54
- package/src/components/tree/TreeCanvasBlock.tsx +10 -68
- package/src/components/tree/clickMap.ts +55 -0
- package/src/components/tree/renderTreeCanvas.ts +10 -34
- package/src/constants.ts +1 -0
- package/src/fetchUtils.ts +6 -3
- package/src/index.ts +1 -1
- package/src/model/msaModel.ts +21 -3
- package/src/model/treeModel.ts +21 -7
- package/src/model.ts +116 -117
- package/src/propertyConservation.test.ts +47 -0
- package/src/propertyConservation.ts +73 -0
- package/src/stripDefaultSnapshot.test.ts +54 -0
- package/src/types.ts +7 -1
- package/src/useCanvasAutorun.ts +28 -0
- package/src/version.ts +1 -1
- package/dist/components/dialogs/SettingsDialog.d.ts +0 -7
- package/dist/components/dialogs/SettingsDialog.js +0 -100
- package/dist/components/dialogs/SettingsDialog.js.map +0 -1
- package/dist/components/header/HeaderMenu.js +0 -27
- package/dist/components/header/HeaderMenu.js.map +0 -1
- package/src/components/dialogs/SettingsDialog.tsx +0 -234
- package/src/components/header/HeaderMenu.tsx +0 -38
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
2
|
|
|
3
3
|
import { BaseTooltip } from '@jbrowse/core/ui'
|
|
4
|
-
import { autorun } from 'mobx'
|
|
5
4
|
import { observer } from 'mobx-react'
|
|
6
5
|
|
|
6
|
+
import ColumnStats from './ColumnStats.tsx'
|
|
7
7
|
import { renderBoxFeatureCanvasBlock } from './renderBoxFeatureCanvasBlock.ts'
|
|
8
8
|
import { renderMSABlock } from './renderMSABlock.ts'
|
|
9
|
+
import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
|
|
9
10
|
import { useColorContrast } from '../../useColorContrast.ts'
|
|
10
11
|
|
|
11
12
|
import type { MsaViewModel } from '../../model.ts'
|
|
@@ -47,13 +48,8 @@ const MSACanvasBlock = observer(function ({
|
|
|
47
48
|
} = model
|
|
48
49
|
const { theme, contrastScheme } = useColorContrast(colorScheme)
|
|
49
50
|
|
|
50
|
-
const ref =
|
|
51
|
-
|
|
52
|
-
const ctx = ref.current?.getContext('2d')
|
|
53
|
-
if (!ctx) {
|
|
54
|
-
return
|
|
55
|
-
}
|
|
56
|
-
return autorun(() => {
|
|
51
|
+
const ref = useCanvasAutorun(
|
|
52
|
+
ctx => {
|
|
57
53
|
ctx.resetTransform()
|
|
58
54
|
ctx.clearRect(
|
|
59
55
|
0,
|
|
@@ -77,19 +73,20 @@ const MSACanvasBlock = observer(function ({
|
|
|
77
73
|
contrastScheme,
|
|
78
74
|
model,
|
|
79
75
|
})
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
76
|
+
},
|
|
77
|
+
[
|
|
78
|
+
model,
|
|
79
|
+
offsetX,
|
|
80
|
+
offsetY,
|
|
81
|
+
theme,
|
|
82
|
+
blockSize,
|
|
83
|
+
highResScaleFactor,
|
|
84
|
+
contrastScheme,
|
|
85
|
+
],
|
|
86
|
+
)
|
|
90
87
|
|
|
91
88
|
const [mousePosition, setMousePosition] = useState<{ x: number; y: number }>()
|
|
92
|
-
const { hoveredInsertion, mouseOverDomains } = model
|
|
89
|
+
const { hoveredInsertion, mouseOverDomains, showColumnStats } = model
|
|
93
90
|
|
|
94
91
|
return (
|
|
95
92
|
<>
|
|
@@ -117,12 +114,15 @@ const MSACanvasBlock = observer(function ({
|
|
|
117
114
|
model.setMousePos(undefined, undefined)
|
|
118
115
|
}
|
|
119
116
|
// only track client coords when there's something to show in the
|
|
120
|
-
// tooltip
|
|
121
|
-
// re-render on every move
|
|
117
|
+
// tooltip: an insertion, a domain under the cursor, or column stats
|
|
118
|
+
// (when that option is enabled). avoids a re-render on every move
|
|
119
|
+
// when nothing would be shown
|
|
120
|
+
const hasTooltip =
|
|
121
|
+
model.hoveredInsertion ||
|
|
122
|
+
model.mouseOverDomains.length > 0 ||
|
|
123
|
+
(model.showColumnStats && !!model.mouseOverColumnStats)
|
|
122
124
|
setMousePosition(
|
|
123
|
-
|
|
124
|
-
? { x: event.clientX, y: event.clientY }
|
|
125
|
-
: undefined,
|
|
125
|
+
hasTooltip ? { x: event.clientX, y: event.clientY } : undefined,
|
|
126
126
|
)
|
|
127
127
|
}
|
|
128
128
|
}}
|
|
@@ -168,7 +168,9 @@ const MSACanvasBlock = observer(function ({
|
|
|
168
168
|
: hoveredInsertion.letters}
|
|
169
169
|
</BaseTooltip>
|
|
170
170
|
) : null}
|
|
171
|
-
{!hoveredInsertion &&
|
|
171
|
+
{!hoveredInsertion &&
|
|
172
|
+
mousePosition &&
|
|
173
|
+
(mouseOverDomains.length > 0 || showColumnStats) ? (
|
|
172
174
|
<BaseTooltip
|
|
173
175
|
clientPoint={{ x: mousePosition.x, y: mousePosition.y + 15 }}
|
|
174
176
|
>
|
|
@@ -183,6 +185,7 @@ const MSACanvasBlock = observer(function ({
|
|
|
183
185
|
) : null}
|
|
184
186
|
</div>
|
|
185
187
|
))}
|
|
188
|
+
{showColumnStats ? <ColumnStats model={model} /> : null}
|
|
186
189
|
</BaseTooltip>
|
|
187
190
|
) : null}
|
|
188
191
|
</>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react'
|
|
2
2
|
|
|
3
3
|
import { isAlive } from '@jbrowse/mobx-state-tree'
|
|
4
|
-
import { autorun } from 'mobx'
|
|
5
4
|
import { observer } from 'mobx-react'
|
|
6
5
|
|
|
7
6
|
import { renderMouseover } from './renderMSAMouseover.ts'
|
|
7
|
+
import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
|
|
8
8
|
|
|
9
9
|
import type { MsaViewModel } from '../../model.ts'
|
|
10
10
|
|
|
@@ -13,20 +13,17 @@ const MSAMouseoverCanvas = observer(function ({
|
|
|
13
13
|
}: {
|
|
14
14
|
model: MsaViewModel
|
|
15
15
|
}) {
|
|
16
|
-
const ref = useRef<HTMLCanvasElement>(null)
|
|
17
16
|
const { height, msaAreaWidth, verticalScrollbarWidth, highResScaleFactor } =
|
|
18
17
|
model
|
|
19
18
|
const width = msaAreaWidth - verticalScrollbarWidth
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
: undefined
|
|
29
|
-
}, [model])
|
|
19
|
+
const ref = useCanvasAutorun(
|
|
20
|
+
ctx => {
|
|
21
|
+
if (isAlive(model)) {
|
|
22
|
+
renderMouseover({ ctx, model })
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
[model],
|
|
26
|
+
)
|
|
30
27
|
|
|
31
28
|
return (
|
|
32
29
|
<canvas
|
|
@@ -88,7 +88,9 @@ function drawTiles({
|
|
|
88
88
|
ctx.strokeRect(x, t, lw, h)
|
|
89
89
|
// segment (exon) number drawn once per band, on the top visible row,
|
|
90
90
|
// so it reads as a column header for the whole band
|
|
91
|
-
const label = drawSegmentLabels
|
|
91
|
+
const label = drawSegmentLabels
|
|
92
|
+
? segmentLabels.get(accession)
|
|
93
|
+
: undefined
|
|
92
94
|
if (label !== undefined && i === 0) {
|
|
93
95
|
const fontSize = Math.min(h - 2, 11)
|
|
94
96
|
ctx.font = `${fontSize}px sans-serif`
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { multiRowHoverColor, referenceColor } from '../overlayColors.ts'
|
|
2
|
+
|
|
1
3
|
import type { MsaViewModel } from '../../model.ts'
|
|
2
4
|
|
|
3
5
|
const hoverColor = 'rgba(0,0,0,0.15)'
|
|
@@ -7,8 +9,6 @@ const highlightColor = 'rgba(128,128,0,0.2)'
|
|
|
7
9
|
// faint hover-style wash alone is invisible against clustalx coloring)
|
|
8
10
|
const highlightColumnsFill = 'rgba(255,140,0,0.28)'
|
|
9
11
|
const highlightColumnsBorder = 'rgba(210,90,0,0.95)'
|
|
10
|
-
const referenceColor = 'rgba(0,128,255,0.3)' // Blue highlight for reference row
|
|
11
|
-
const multiRowHoverColor = 'rgba(255,165,0,0.15)' // Orange highlight for multi-row tree hover
|
|
12
12
|
|
|
13
13
|
// Collapse sorted column indices into contiguous [start,end] runs so a run of
|
|
14
14
|
// highlighted columns draws as one bordered band rather than per-column slices
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Shared highlight colors for the MSA/tree hover and selection overlay canvases.
|
|
2
|
+
// Kept in one place so the reference-row color (drawn identically over both the
|
|
3
|
+
// tree and the alignment) can't drift between the two surfaces.
|
|
4
|
+
|
|
5
|
+
// reference (relativeTo) row, drawn over both the tree and the alignment
|
|
6
|
+
export const referenceColor = 'rgba(0,128,255,0.3)'
|
|
7
|
+
|
|
8
|
+
// tree-node descendant rows highlighted on hover. The alpha differs by surface
|
|
9
|
+
// intentionally: the tree background is plain so it can take a stronger wash,
|
|
10
|
+
// while the alignment cells are already colored so a lighter wash reads better.
|
|
11
|
+
export const treeHoverColor = 'rgba(255,165,0,0.2)'
|
|
12
|
+
export const multiRowHoverColor = 'rgba(255,165,0,0.15)'
|
|
@@ -7,14 +7,16 @@ import type { RenderCtx } from '../renderCtx.ts'
|
|
|
7
7
|
|
|
8
8
|
export function drawConservationBars({
|
|
9
9
|
ctx,
|
|
10
|
-
|
|
10
|
+
values,
|
|
11
|
+
color,
|
|
11
12
|
colWidth,
|
|
12
13
|
trackHeight,
|
|
13
14
|
offsetX,
|
|
14
15
|
blockSize,
|
|
15
16
|
}: {
|
|
16
17
|
ctx: RenderCtx
|
|
17
|
-
|
|
18
|
+
values: number[]
|
|
19
|
+
color: string
|
|
18
20
|
colWidth: number
|
|
19
21
|
trackHeight: number
|
|
20
22
|
offsetX: number
|
|
@@ -26,15 +28,23 @@ export function drawConservationBars({
|
|
|
26
28
|
colWidth,
|
|
27
29
|
})
|
|
28
30
|
|
|
29
|
-
ctx.fillStyle =
|
|
30
|
-
for (let i = xStart; i < xEnd && i <
|
|
31
|
-
const value =
|
|
31
|
+
ctx.fillStyle = color
|
|
32
|
+
for (let i = xStart; i < xEnd && i < values.length; i++) {
|
|
33
|
+
const value = values[i]!
|
|
32
34
|
const barHeight = value * trackHeight
|
|
33
35
|
const x = i * colWidth
|
|
34
36
|
ctx.fillRect(x, trackHeight - barHeight, colWidth, barHeight)
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
// the bar-track values live on model getters (kept off the plain track object so
|
|
41
|
+
// the live canvas autorun stays reactive); select the right one by track id
|
|
42
|
+
export function barTrackValues(model: MsaViewModel, trackId: string) {
|
|
43
|
+
return trackId === 'property-conservation'
|
|
44
|
+
? model.propertyConservation
|
|
45
|
+
: model.conservation
|
|
46
|
+
}
|
|
47
|
+
|
|
38
48
|
export function drawTextTrackContent({
|
|
39
49
|
ctx,
|
|
40
50
|
data,
|
|
@@ -81,6 +91,7 @@ export function drawTextTrackContent({
|
|
|
81
91
|
export function renderConservationTrack({
|
|
82
92
|
model,
|
|
83
93
|
ctx,
|
|
94
|
+
track,
|
|
84
95
|
offsetX,
|
|
85
96
|
offsetY,
|
|
86
97
|
trackHeight,
|
|
@@ -89,13 +100,14 @@ export function renderConservationTrack({
|
|
|
89
100
|
}: {
|
|
90
101
|
model: MsaViewModel
|
|
91
102
|
ctx: RenderCtx
|
|
103
|
+
track: BasicTrack
|
|
92
104
|
offsetX: number
|
|
93
105
|
offsetY: number
|
|
94
106
|
trackHeight: number
|
|
95
107
|
blockSizeXOverride?: number
|
|
96
108
|
highResScaleFactorOverride?: number
|
|
97
109
|
}) {
|
|
98
|
-
const { blockSize, colWidth, highResScaleFactor
|
|
110
|
+
const { blockSize, colWidth, highResScaleFactor } = model
|
|
99
111
|
const bx = blockSizeXOverride ?? blockSize
|
|
100
112
|
const k = highResScaleFactorOverride ?? highResScaleFactor
|
|
101
113
|
|
|
@@ -105,7 +117,8 @@ export function renderConservationTrack({
|
|
|
105
117
|
|
|
106
118
|
drawConservationBars({
|
|
107
119
|
ctx,
|
|
108
|
-
|
|
120
|
+
values: barTrackValues(model, track.model.id),
|
|
121
|
+
color: track.model.barColor ?? 'gray',
|
|
109
122
|
colWidth,
|
|
110
123
|
trackHeight,
|
|
111
124
|
offsetX,
|
|
@@ -191,10 +204,11 @@ export function renderAllTracks({
|
|
|
191
204
|
for (const track of turnedOnTracks) {
|
|
192
205
|
const trackHeight = track.model.height
|
|
193
206
|
|
|
194
|
-
if (track.model.
|
|
207
|
+
if (track.model.barColor !== undefined) {
|
|
195
208
|
renderConservationTrack({
|
|
196
209
|
model,
|
|
197
210
|
ctx,
|
|
211
|
+
track,
|
|
198
212
|
offsetX,
|
|
199
213
|
offsetY: currentY,
|
|
200
214
|
trackHeight,
|
|
@@ -1,21 +1,18 @@
|
|
|
1
|
-
import React, { useCallback,
|
|
1
|
+
import React, { useCallback, useRef } from 'react'
|
|
2
2
|
|
|
3
3
|
import { isAlive } from '@jbrowse/mobx-state-tree'
|
|
4
|
-
import { autorun } from 'mobx'
|
|
5
4
|
import { observer } from 'mobx-react'
|
|
6
5
|
|
|
7
6
|
import TreeCanvasBlock from './TreeCanvasBlock.tsx'
|
|
8
7
|
import { padding } from './renderTreeCanvas.ts'
|
|
8
|
+
import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
|
|
9
9
|
import { useWheelScroll } from '../../useWheelScroll.ts'
|
|
10
|
+
import { referenceColor, treeHoverColor } from '../overlayColors.ts'
|
|
10
11
|
|
|
11
12
|
import type { MsaViewModel } from '../../model.ts'
|
|
12
13
|
|
|
13
|
-
const referenceColor = 'rgba(0,128,255,0.3)'
|
|
14
|
-
const treeHoverColor = 'rgba(255,165,0,0.2)'
|
|
15
|
-
|
|
16
14
|
const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
17
15
|
const ref = useRef<HTMLDivElement>(null)
|
|
18
|
-
const mouseoverRef = useRef<HTMLCanvasElement>(null)
|
|
19
16
|
const { treeWidth, height, blocksY, treeAreaWidth } = model
|
|
20
17
|
const onScrollY = useCallback(
|
|
21
18
|
(d: number) => {
|
|
@@ -25,58 +22,54 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
25
22
|
)
|
|
26
23
|
const { onMouseDown, onMouseUp } = useWheelScroll({ ref, onScrollY })
|
|
27
24
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
ctx.resetTransform()
|
|
44
|
-
ctx.clearRect(0, 0, w, h)
|
|
45
|
-
|
|
46
|
-
const leafByName = new Map(
|
|
47
|
-
leaves.map(leaf => [leaf.data.name, leaf]),
|
|
48
|
-
)
|
|
49
|
-
const fillRow = (name: string) => {
|
|
50
|
-
const leaf = leafByName.get(name)
|
|
51
|
-
if (leaf) {
|
|
52
|
-
ctx.fillRect(0, leaf.x! + sy - rowHeight / 2, w, rowHeight)
|
|
53
|
-
}
|
|
54
|
-
}
|
|
25
|
+
const mouseoverRef = useCanvasAutorun(
|
|
26
|
+
ctx => {
|
|
27
|
+
if (isAlive(model)) {
|
|
28
|
+
const {
|
|
29
|
+
relativeTo,
|
|
30
|
+
leaves,
|
|
31
|
+
rowHeight,
|
|
32
|
+
hoveredTreeNode,
|
|
33
|
+
treeAreaWidth: w,
|
|
34
|
+
height: h,
|
|
35
|
+
scrollY: sy,
|
|
36
|
+
mouseOverRowName,
|
|
37
|
+
} = model
|
|
38
|
+
ctx.resetTransform()
|
|
39
|
+
ctx.clearRect(0, 0, w, h)
|
|
55
40
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
41
|
+
const leafByName = new Map(leaves.map(leaf => [leaf.data.name, leaf]))
|
|
42
|
+
const fillRow = (name: string) => {
|
|
43
|
+
const leaf = leafByName.get(name)
|
|
44
|
+
if (leaf) {
|
|
45
|
+
ctx.fillRect(0, leaf.x! + sy - rowHeight / 2, w, rowHeight)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
60
48
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
}
|
|
49
|
+
if (relativeTo) {
|
|
50
|
+
ctx.fillStyle = referenceColor
|
|
51
|
+
fillRow(relativeTo)
|
|
52
|
+
}
|
|
67
53
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
) {
|
|
73
|
-
ctx.fillStyle = treeHoverColor
|
|
74
|
-
fillRow(mouseOverRowName)
|
|
75
|
-
}
|
|
54
|
+
if (hoveredTreeNode) {
|
|
55
|
+
ctx.fillStyle = treeHoverColor
|
|
56
|
+
for (const descendantName of hoveredTreeNode.descendantNames) {
|
|
57
|
+
fillRow(descendantName)
|
|
76
58
|
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
mouseOverRowName &&
|
|
63
|
+
mouseOverRowName !== relativeTo &&
|
|
64
|
+
!hoveredTreeNode?.descendantNames.includes(mouseOverRowName)
|
|
65
|
+
) {
|
|
66
|
+
ctx.fillStyle = treeHoverColor
|
|
67
|
+
fillRow(mouseOverRowName)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
[model],
|
|
72
|
+
)
|
|
80
73
|
|
|
81
74
|
return (
|
|
82
75
|
<div
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useState } from 'react'
|
|
2
2
|
|
|
3
|
+
import { makeStyles } from '@jbrowse/core/util/tss-react'
|
|
3
4
|
import { useTheme } from '@mui/material'
|
|
4
|
-
import Flatbush from 'flatbush'
|
|
5
|
-
import { autorun } from 'mobx'
|
|
6
5
|
import { observer } from 'mobx-react'
|
|
7
|
-
import { makeStyles } from 'tss-react/mui'
|
|
8
6
|
|
|
9
7
|
import TreeBranchMenu from './TreeBranchMenu.tsx'
|
|
10
8
|
import TreeNodeMenu from './TreeNodeMenu.tsx'
|
|
9
|
+
import { ClickMapIndex } from './clickMap.ts'
|
|
11
10
|
import { padding, renderTreeCanvas } from './renderTreeCanvas.ts'
|
|
11
|
+
import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
|
|
12
12
|
|
|
13
|
+
import type { ClickEntry } from './clickMap.ts'
|
|
13
14
|
import type { MsaViewModel } from '../../model.ts'
|
|
14
15
|
|
|
15
16
|
const useStyles = makeStyles()(theme => ({
|
|
@@ -36,55 +37,6 @@ interface TooltipData {
|
|
|
36
37
|
y: number
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
interface ClickEntry {
|
|
40
|
-
name: string
|
|
41
|
-
id: string
|
|
42
|
-
branch?: boolean
|
|
43
|
-
minX: number
|
|
44
|
-
maxX: number
|
|
45
|
-
minY: number
|
|
46
|
-
maxY: number
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
class ClickMapIndex {
|
|
50
|
-
private flatbush: Flatbush | null = null
|
|
51
|
-
private entries: ClickEntry[] = []
|
|
52
|
-
|
|
53
|
-
clear() {
|
|
54
|
-
this.flatbush = null
|
|
55
|
-
this.entries = []
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
insert(entry: ClickEntry) {
|
|
59
|
-
this.entries.push(entry)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
finish() {
|
|
63
|
-
if (this.entries.length === 0) {
|
|
64
|
-
this.flatbush = null
|
|
65
|
-
} else {
|
|
66
|
-
this.flatbush = new Flatbush(this.entries.length)
|
|
67
|
-
for (const entry of this.entries) {
|
|
68
|
-
this.flatbush.add(entry.minX, entry.minY, entry.maxX, entry.maxY)
|
|
69
|
-
}
|
|
70
|
-
this.flatbush.finish()
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
search(box: {
|
|
75
|
-
minX: number
|
|
76
|
-
maxX: number
|
|
77
|
-
minY: number
|
|
78
|
-
maxY: number
|
|
79
|
-
}): ClickEntry[] {
|
|
80
|
-
return (
|
|
81
|
-
this.flatbush
|
|
82
|
-
?.search(box.minX, box.minY, box.maxX, box.maxY)
|
|
83
|
-
.map(i => this.entries[i]!) ?? []
|
|
84
|
-
)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
40
|
const TreeCanvasBlock = observer(function ({
|
|
89
41
|
model,
|
|
90
42
|
offsetY,
|
|
@@ -94,7 +46,6 @@ const TreeCanvasBlock = observer(function ({
|
|
|
94
46
|
}) {
|
|
95
47
|
const { classes } = useStyles()
|
|
96
48
|
const theme = useTheme()
|
|
97
|
-
const ref = useRef<HTMLCanvasElement>(null)
|
|
98
49
|
const clickMap = useRef(new ClickMapIndex())
|
|
99
50
|
const mouseoverRef = useRef<HTMLCanvasElement>(null)
|
|
100
51
|
const [branchMenu, setBranchMenu] = useState<TooltipData>()
|
|
@@ -113,20 +64,10 @@ const TreeCanvasBlock = observer(function ({
|
|
|
113
64
|
const w2 = width * highResScaleFactor
|
|
114
65
|
const h2 = height * highResScaleFactor
|
|
115
66
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (!ctx) {
|
|
119
|
-
return
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return autorun(() => {
|
|
67
|
+
const ref = useCanvasAutorun(
|
|
68
|
+
ctx => {
|
|
123
69
|
ctx.resetTransform()
|
|
124
|
-
ctx.clearRect(
|
|
125
|
-
0,
|
|
126
|
-
0,
|
|
127
|
-
(treeAreaWidth + padding) * highResScaleFactor,
|
|
128
|
-
blockSize * highResScaleFactor,
|
|
129
|
-
)
|
|
70
|
+
ctx.clearRect(0, 0, w2, h2)
|
|
130
71
|
renderTreeCanvas({
|
|
131
72
|
ctx,
|
|
132
73
|
model,
|
|
@@ -134,8 +75,9 @@ const TreeCanvasBlock = observer(function ({
|
|
|
134
75
|
clickMap: clickMap.current,
|
|
135
76
|
theme,
|
|
136
77
|
})
|
|
137
|
-
}
|
|
138
|
-
|
|
78
|
+
},
|
|
79
|
+
[model, blockSize, highResScaleFactor, treeAreaWidth, offsetY, theme],
|
|
80
|
+
)
|
|
139
81
|
|
|
140
82
|
useEffect(() => {
|
|
141
83
|
const ctx = mouseoverRef.current?.getContext('2d')
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import Flatbush from 'flatbush'
|
|
2
|
+
|
|
3
|
+
export interface ClickEntry {
|
|
4
|
+
name: string
|
|
5
|
+
id: string
|
|
6
|
+
branch?: boolean
|
|
7
|
+
minX: number
|
|
8
|
+
maxX: number
|
|
9
|
+
minY: number
|
|
10
|
+
maxY: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface Box {
|
|
14
|
+
minX: number
|
|
15
|
+
maxX: number
|
|
16
|
+
minY: number
|
|
17
|
+
maxY: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Spatial index of clickable tree elements (leaf labels, node bubbles,
|
|
21
|
+
// collapsed-clade triangles). Entries are collected during a render pass, then
|
|
22
|
+
// finish() builds the Flatbush index queried by hit-testing.
|
|
23
|
+
export class ClickMapIndex {
|
|
24
|
+
private flatbush: Flatbush | null = null
|
|
25
|
+
private entries: ClickEntry[] = []
|
|
26
|
+
|
|
27
|
+
clear() {
|
|
28
|
+
this.flatbush = null
|
|
29
|
+
this.entries = []
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
insert(entry: ClickEntry) {
|
|
33
|
+
this.entries.push(entry)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
finish() {
|
|
37
|
+
if (this.entries.length === 0) {
|
|
38
|
+
this.flatbush = null
|
|
39
|
+
} else {
|
|
40
|
+
this.flatbush = new Flatbush(this.entries.length)
|
|
41
|
+
for (const entry of this.entries) {
|
|
42
|
+
this.flatbush.add(entry.minX, entry.minY, entry.maxX, entry.maxY)
|
|
43
|
+
}
|
|
44
|
+
this.flatbush.finish()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
search(box: Box): ClickEntry[] {
|
|
49
|
+
return (
|
|
50
|
+
this.flatbush
|
|
51
|
+
?.search(box.minX, box.minY, box.maxX, box.maxY)
|
|
52
|
+
.map(i => this.entries[i]!) ?? []
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from '../../hierarchy.ts'
|
|
6
6
|
import { setFontSize } from '../../setFontSize.ts'
|
|
7
7
|
|
|
8
|
+
import type { ClickMapIndex } from './clickMap.ts'
|
|
8
9
|
import type { HierarchyNode } from '../../hierarchy.ts'
|
|
9
10
|
import type { MsaViewModel } from '../../model.ts'
|
|
10
11
|
import type { RenderCtx } from '../renderCtx.ts'
|
|
@@ -16,6 +17,12 @@ const extendBounds = 5
|
|
|
16
17
|
const radius = 2.5
|
|
17
18
|
const d = radius * 2
|
|
18
19
|
|
|
20
|
+
// whether a node's row-center y falls within the block being drawn, padded by
|
|
21
|
+
// extendBounds so bubbles/labels/triangles straddling the edge still render
|
|
22
|
+
function inYBlock(y: number, offsetY: number, by: number) {
|
|
23
|
+
return y > offsetY - extendBounds && y < offsetY + by + extendBounds
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
// Calculate node x-coordinate for both phylogram (with branch lengths) and cladogram (topology only) modes
|
|
20
27
|
// For cladograms: x = (maxDepthToLeaf - nodeDepthToLeaf) / maxDepthToLeaf * maxWidth
|
|
21
28
|
// This positions: leaves at maxWidth (rightmost), root at 0 (leftmost), internal nodes proportionally in between
|
|
@@ -36,28 +43,6 @@ function getNodeX(
|
|
|
36
43
|
return ((maxDepthToLeaf - depthToLeaf) / maxDepthToLeaf) * maxBranchLen
|
|
37
44
|
}
|
|
38
45
|
|
|
39
|
-
interface ClickEntry {
|
|
40
|
-
name: string
|
|
41
|
-
id: string
|
|
42
|
-
branch?: boolean
|
|
43
|
-
minX: number
|
|
44
|
-
maxX: number
|
|
45
|
-
minY: number
|
|
46
|
-
maxY: number
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface ClickMapIndex {
|
|
50
|
-
clear(): void
|
|
51
|
-
insert(entry: ClickEntry): void
|
|
52
|
-
finish(): void
|
|
53
|
-
search(box: {
|
|
54
|
-
minX: number
|
|
55
|
-
maxX: number
|
|
56
|
-
minY: number
|
|
57
|
-
maxY: number
|
|
58
|
-
}): ClickEntry[]
|
|
59
|
-
}
|
|
60
|
-
|
|
61
46
|
export function renderTree({
|
|
62
47
|
offsetY,
|
|
63
48
|
ctx,
|
|
@@ -137,8 +122,7 @@ export function renderCollapsedTriangles({
|
|
|
137
122
|
if (collapsed.includes(id)) {
|
|
138
123
|
const apexX = getNodeX(node, showBranchLen, maxBranchLen, maxDepthToLeaf)
|
|
139
124
|
const y = node.x!
|
|
140
|
-
const inBlock =
|
|
141
|
-
y > offsetY - extendBounds && y < offsetY + by + extendBounds
|
|
125
|
+
const inBlock = inYBlock(y, offsetY, by)
|
|
142
126
|
// in cladogram mode the hidden tips align at the right edge, otherwise use
|
|
143
127
|
// the branch-length extent recorded in the model's hierarchy getter
|
|
144
128
|
const baseX = showBranchLen
|
|
@@ -214,11 +198,7 @@ export function renderNodeBubbles({
|
|
|
214
198
|
const { data } = node
|
|
215
199
|
const y = node.x!
|
|
216
200
|
const { id, name } = data
|
|
217
|
-
if (
|
|
218
|
-
node.height >= 1 &&
|
|
219
|
-
y > offsetY - extendBounds &&
|
|
220
|
-
y < offsetY + by + extendBounds
|
|
221
|
-
) {
|
|
201
|
+
if (node.height >= 1 && inYBlock(y, offsetY, by)) {
|
|
222
202
|
const isCollapsed = collapsed.includes(id)
|
|
223
203
|
ctx.strokeStyle = 'black'
|
|
224
204
|
ctx.fillStyle = isCollapsed ? 'black' : 'white'
|
|
@@ -296,11 +276,7 @@ export function renderTreeLabels({
|
|
|
296
276
|
// a collapsed clade is drawn as a triangle + tip count; suppress its leaf
|
|
297
277
|
// label when the "name" is just the auto-generated internal-node id
|
|
298
278
|
const isAnonymousCollapsed = collapsed.includes(id) && name === id
|
|
299
|
-
if (
|
|
300
|
-
!isAnonymousCollapsed &&
|
|
301
|
-
y > offsetY - extendBounds &&
|
|
302
|
-
y < offsetY + by + extendBounds
|
|
303
|
-
) {
|
|
279
|
+
if (!isAnonymousCollapsed && inYBlock(y, offsetY, by)) {
|
|
304
280
|
// note: +rowHeight/4 matches with -rowHeight/4 in msa
|
|
305
281
|
const yp = y + fontSize / 4
|
|
306
282
|
let xp = 0
|
package/src/constants.ts
CHANGED