react-msaview 4.4.4 → 4.4.5
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 +111 -111
- package/bundle/index.js.LICENSE.txt +24 -6
- package/bundle/index.js.map +1 -1
- package/dist/components/dialogs/InterProScanDialog.js +1 -1
- package/dist/components/dialogs/InterProScanDialog.js.map +1 -1
- package/dist/components/dialogs/SettingsDialog.js +1 -1
- package/dist/components/dialogs/SettingsDialog.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +8 -1
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/renderMSABlock.js +22 -4
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +21 -1
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/tree/TreeCanvas.js +61 -2
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +56 -5
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/TreeNodeMenu.js +6 -2
- package/dist/components/tree/TreeNodeMenu.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.d.ts +14 -4
- package/dist/components/tree/renderTreeCanvas.js +3 -0
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/util.js +1 -1
- package/dist/components/util.js.map +1 -1
- package/dist/layout.d.ts +4 -1
- package/dist/layout.js +30 -8
- package/dist/layout.js.map +1 -1
- package/dist/model/treeModel.js +2 -2
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +14 -79
- package/dist/model.js +94 -4
- package/dist/model.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -2
- package/src/components/dialogs/InterProScanDialog.tsx +1 -1
- package/src/components/dialogs/SettingsDialog.tsx +1 -1
- package/src/components/msa/MSACanvasBlock.tsx +8 -1
- package/src/components/msa/renderMSABlock.ts +32 -2
- package/src/components/msa/renderMSAMouseover.ts +26 -0
- package/src/components/tree/TreeCanvas.tsx +76 -1
- package/src/components/tree/TreeCanvasBlock.tsx +69 -5
- package/src/components/tree/TreeNodeMenu.tsx +12 -1
- package/src/components/tree/renderTreeCanvas.ts +20 -4
- package/src/components/util.ts +1 -1
- package/src/layout.ts +48 -8
- package/src/model/treeModel.ts +2 -2
- package/src/model.ts +108 -5
- package/src/version.ts +1 -1
- package/dist/DataModel.d.ts +0 -34
- package/dist/DataModel.js +0 -46
- package/dist/DataModel.js.map +0 -1
- package/dist/DialogQueue.d.ts +0 -25
- package/dist/DialogQueue.js +0 -44
- package/dist/DialogQueue.js.map +0 -1
- package/dist/SelectedStructuresMixin.d.ts +0 -46
- package/dist/SelectedStructuresMixin.js +0 -52
- package/dist/SelectedStructuresMixin.js.map +0 -1
- package/dist/StructureModel.d.ts +0 -9
- package/dist/StructureModel.js +0 -11
- package/dist/StructureModel.js.map +0 -1
- package/dist/UniprotTrack.d.ts +0 -27
- package/dist/UniprotTrack.js +0 -53
- package/dist/UniprotTrack.js.map +0 -1
- package/dist/components/BoxTrack.d.ts +0 -7
- package/dist/components/BoxTrack.js +0 -15
- package/dist/components/BoxTrack.js.map +0 -1
- package/dist/components/BoxTrackBlock.d.ts +0 -8
- package/dist/components/BoxTrackBlock.js +0 -136
- package/dist/components/BoxTrackBlock.js.map +0 -1
- package/dist/components/ExportSVGDialog.d.ts +0 -6
- package/dist/components/ExportSVGDialog.js +0 -39
- package/dist/components/ExportSVGDialog.js.map +0 -1
- package/dist/components/Header.d.ts +0 -6
- package/dist/components/Header.js +0 -62
- package/dist/components/Header.js.map +0 -1
- package/dist/components/HeaderInfoArea.d.ts +0 -6
- package/dist/components/HeaderInfoArea.js +0 -12
- package/dist/components/HeaderInfoArea.js.map +0 -1
- package/dist/components/ImportForm/ImportFormExamples.d.ts +0 -6
- package/dist/components/ImportForm/ImportFormExamples.js +0 -50
- package/dist/components/ImportForm/ImportFormExamples.js.map +0 -1
- package/dist/components/ImportForm/data/seq2.d.ts +0 -3
- package/dist/components/ImportForm/data/seq2.js +0 -33
- package/dist/components/ImportForm/data/seq2.js.map +0 -1
- package/dist/components/ImportForm/index.d.ts +0 -6
- package/dist/components/ImportForm/index.js +0 -31
- package/dist/components/ImportForm/index.js.map +0 -1
- package/dist/components/ImportForm/util.d.ts +0 -3
- package/dist/components/ImportForm/util.js +0 -16
- package/dist/components/ImportForm/util.js.map +0 -1
- package/dist/components/MSACanvas.d.ts +0 -6
- package/dist/components/MSACanvas.js +0 -141
- package/dist/components/MSACanvas.js.map +0 -1
- package/dist/components/MSAPanel/Loading.d.ts +0 -2
- package/dist/components/MSAPanel/Loading.js +0 -12
- package/dist/components/MSAPanel/Loading.js.map +0 -1
- package/dist/components/MSAPanel/MSABlock.d.ts +0 -8
- package/dist/components/MSAPanel/MSABlock.js +0 -62
- package/dist/components/MSAPanel/MSABlock.js.map +0 -1
- package/dist/components/MSAPanel/MSACanvas.d.ts +0 -7
- package/dist/components/MSAPanel/MSACanvas.js +0 -69
- package/dist/components/MSAPanel/MSACanvas.js.map +0 -1
- package/dist/components/MSAPanel/MSAMouseoverCanvas.d.ts +0 -6
- package/dist/components/MSAPanel/MSAMouseoverCanvas.js +0 -27
- package/dist/components/MSAPanel/MSAMouseoverCanvas.js.map +0 -1
- package/dist/components/MSAPanel/index.d.ts +0 -5
- package/dist/components/MSAPanel/index.js +0 -9
- package/dist/components/MSAPanel/index.js.map +0 -1
- package/dist/components/MSAPanel/renderMSABlock.d.ts +0 -9
- package/dist/components/MSAPanel/renderMSABlock.js +0 -89
- package/dist/components/MSAPanel/renderMSABlock.js.map +0 -1
- package/dist/components/MSAPanel/renderMSABlock_BACKUP_139826.d.ts +0 -9
- package/dist/components/MSAPanel/renderMSABlock_BACKUP_139826.js +0 -89
- package/dist/components/MSAPanel/renderMSABlock_BACKUP_139826.js.map +0 -1
- package/dist/components/MSAPanel/renderMSABlock_BASE_139826.d.ts +0 -13
- package/dist/components/MSAPanel/renderMSABlock_BASE_139826.js +0 -82
- package/dist/components/MSAPanel/renderMSABlock_BASE_139826.js.map +0 -1
- package/dist/components/MSAPanel/renderMSABlock_LOCAL_139826.d.ts +0 -9
- package/dist/components/MSAPanel/renderMSABlock_LOCAL_139826.js +0 -89
- package/dist/components/MSAPanel/renderMSABlock_LOCAL_139826.js.map +0 -1
- package/dist/components/MSAPanel/renderMSABlock_REMOTE_139826.d.ts +0 -0
- package/dist/components/MSAPanel/renderMSABlock_REMOTE_139826.js +0 -2
- package/dist/components/MSAPanel/renderMSABlock_REMOTE_139826.js.map +0 -1
- package/dist/components/MSAPanel/renderMSAMouseover.d.ts +0 -5
- package/dist/components/MSAPanel/renderMSAMouseover.js +0 -30
- package/dist/components/MSAPanel/renderMSAMouseover.js.map +0 -1
- package/dist/components/Minimap.d.ts +0 -6
- package/dist/components/Minimap.js +0 -72
- package/dist/components/Minimap.js.map +0 -1
- package/dist/components/MinimapSVG.d.ts +0 -6
- package/dist/components/MinimapSVG.js +0 -25
- package/dist/components/MinimapSVG.js.map +0 -1
- package/dist/components/MultiAlignmentSelector.d.ts +0 -6
- package/dist/components/MultiAlignmentSelector.js +0 -13
- package/dist/components/MultiAlignmentSelector.js.map +0 -1
- package/dist/components/TreePanel/TreeBranchMenu.d.ts +0 -14
- package/dist/components/TreePanel/TreeBranchMenu.js +0 -26
- package/dist/components/TreePanel/TreeBranchMenu.js.map +0 -1
- package/dist/components/TreePanel/TreeCanvas.d.ts +0 -6
- package/dist/components/TreePanel/TreeCanvas.js +0 -100
- package/dist/components/TreePanel/TreeCanvas.js.map +0 -1
- package/dist/components/TreePanel/TreeCanvasBlock.d.ts +0 -7
- package/dist/components/TreePanel/TreeCanvasBlock.js +0 -118
- package/dist/components/TreePanel/TreeCanvasBlock.js.map +0 -1
- package/dist/components/TreePanel/TreeNodeMenu.d.ts +0 -13
- package/dist/components/TreePanel/TreeNodeMenu.js +0 -74
- package/dist/components/TreePanel/TreeNodeMenu.js.map +0 -1
- package/dist/components/TreePanel/TreeRuler.d.ts +0 -6
- package/dist/components/TreePanel/TreeRuler.js +0 -8
- package/dist/components/TreePanel/TreeRuler.js.map +0 -1
- package/dist/components/TreePanel/dialogs/TreeNodeInfoDialog.d.ts +0 -9
- package/dist/components/TreePanel/dialogs/TreeNodeInfoDialog.js +0 -16
- package/dist/components/TreePanel/dialogs/TreeNodeInfoDialog.js.map +0 -1
- package/dist/components/TreePanel/index.d.ts +0 -6
- package/dist/components/TreePanel/index.js +0 -10
- package/dist/components/TreePanel/index.js.map +0 -1
- package/dist/components/TreePanel/renderTreeCanvas.d.ts +0 -46
- package/dist/components/TreePanel/renderTreeCanvas.js +0 -180
- package/dist/components/TreePanel/renderTreeCanvas.js.map +0 -1
- package/dist/components/VerticalGuide.d.ts +0 -7
- package/dist/components/VerticalGuide.js +0 -30
- package/dist/components/VerticalGuide.js.map +0 -1
- package/dist/components/ZoomControls.d.ts +0 -6
- package/dist/components/ZoomControls.js +0 -59
- package/dist/components/ZoomControls.js.map +0 -1
- package/dist/components/msa/MSACanvas_BACKUP_139826.d.ts +0 -7
- package/dist/components/msa/MSACanvas_BACKUP_139826.js +0 -68
- package/dist/components/msa/MSACanvas_BACKUP_139826.js.map +0 -1
- package/dist/components/msa/MSACanvas_BASE_139826.d.ts +0 -6
- package/dist/components/msa/MSACanvas_BASE_139826.js +0 -107
- package/dist/components/msa/MSACanvas_BASE_139826.js.map +0 -1
- package/dist/components/msa/MSACanvas_LOCAL_139826.d.ts +0 -7
- package/dist/components/msa/MSACanvas_LOCAL_139826.js +0 -69
- package/dist/components/msa/MSACanvas_LOCAL_139826.js.map +0 -1
- package/dist/components/msa/MSACanvas_REMOTE_139826.d.ts +0 -6
- package/dist/components/msa/MSACanvas_REMOTE_139826.js +0 -106
- package/dist/components/msa/MSACanvas_REMOTE_139826.js.map +0 -1
- package/dist/sansserif.d.ts +0 -3
- package/dist/sansserif.js +0 -1583
- package/dist/sansserif.js.map +0 -1
|
@@ -101,8 +101,14 @@ function drawTiles({
|
|
|
101
101
|
columns,
|
|
102
102
|
colWidth,
|
|
103
103
|
rowHeight,
|
|
104
|
+
relativeTo,
|
|
104
105
|
} = model
|
|
105
106
|
|
|
107
|
+
// Get reference sequence if relativeTo is set
|
|
108
|
+
const referenceSeq = relativeTo
|
|
109
|
+
? columns[relativeTo]?.slice(xStart, xEnd)
|
|
110
|
+
: null
|
|
111
|
+
|
|
106
112
|
for (let i = 0, l1 = visibleLeaves.length; i < l1; i++) {
|
|
107
113
|
const node = visibleLeaves[i]!
|
|
108
114
|
const {
|
|
@@ -113,6 +119,11 @@ function drawTiles({
|
|
|
113
119
|
if (str) {
|
|
114
120
|
for (let i = 0, l2 = str.length; i < l2; i++) {
|
|
115
121
|
const letter = str[i]!
|
|
122
|
+
|
|
123
|
+
// Use a muted background for positions that match reference
|
|
124
|
+
const isMatchingReference =
|
|
125
|
+
referenceSeq && name !== relativeTo && letter === referenceSeq[i]
|
|
126
|
+
|
|
116
127
|
const r1 = colorSchemeName === 'clustalx_protein_dynamic'
|
|
117
128
|
const r2 = colorSchemeName === 'percent_identity_dynamic'
|
|
118
129
|
const color = r1
|
|
@@ -137,7 +148,11 @@ function drawTiles({
|
|
|
137
148
|
)
|
|
138
149
|
: colorScheme[letter.toUpperCase()]
|
|
139
150
|
if (bgColor || r1 || r2) {
|
|
140
|
-
|
|
151
|
+
// Use a very light background for matching positions in relative mode
|
|
152
|
+
const finalColor = isMatchingReference
|
|
153
|
+
? theme.palette.action.hover
|
|
154
|
+
: color || theme.palette.background.default
|
|
155
|
+
ctx.fillStyle = finalColor
|
|
141
156
|
ctx.fillRect(
|
|
142
157
|
i * colWidth + offsetX - (offsetX % colWidth),
|
|
143
158
|
y - rowHeight,
|
|
@@ -177,7 +192,14 @@ function drawText({
|
|
|
177
192
|
colWidth,
|
|
178
193
|
contrastLettering,
|
|
179
194
|
rowHeight,
|
|
195
|
+
relativeTo,
|
|
180
196
|
} = model
|
|
197
|
+
|
|
198
|
+
// Get reference sequence if relativeTo is set
|
|
199
|
+
const referenceSeq = relativeTo
|
|
200
|
+
? columns[relativeTo]?.slice(xStart, xEnd)
|
|
201
|
+
: null
|
|
202
|
+
|
|
181
203
|
if (showMsaLetters) {
|
|
182
204
|
for (let i = 0, l1 = visibleLeaves.length; i < l1; i++) {
|
|
183
205
|
const node = visibleLeaves[i]!
|
|
@@ -189,6 +211,14 @@ function drawText({
|
|
|
189
211
|
if (str) {
|
|
190
212
|
for (let i = 0, l2 = str.length; i < l2; i++) {
|
|
191
213
|
const letter = str[i]!
|
|
214
|
+
|
|
215
|
+
// Check if this position matches the reference
|
|
216
|
+
const isMatchingReference =
|
|
217
|
+
referenceSeq && name !== relativeTo && letter === referenceSeq[i]
|
|
218
|
+
|
|
219
|
+
// Show dot for matching positions, original letter for differences
|
|
220
|
+
const displayLetter = isMatchingReference ? '.' : letter
|
|
221
|
+
|
|
192
222
|
const color = colorScheme[letter.toUpperCase()]
|
|
193
223
|
const contrast = contrastLettering
|
|
194
224
|
? contrastScheme[letter.toUpperCase()] || 'black'
|
|
@@ -201,7 +231,7 @@ function drawText({
|
|
|
201
231
|
: bgColor
|
|
202
232
|
? contrast
|
|
203
233
|
: color || 'black'
|
|
204
|
-
ctx.fillText(
|
|
234
|
+
ctx.fillText(displayLetter, x + colWidth / 2, y - rowHeight / 4)
|
|
205
235
|
}
|
|
206
236
|
}
|
|
207
237
|
}
|
|
@@ -2,6 +2,8 @@ import type { MsaViewModel } from '../../model'
|
|
|
2
2
|
|
|
3
3
|
const hoverColor = 'rgba(0,0,0,0.15)'
|
|
4
4
|
const highlightColor = 'rgba(128,128,0,0.2)'
|
|
5
|
+
const referenceColor = 'rgba(0,128,255,0.3)' // Blue highlight for reference row
|
|
6
|
+
const multiRowHoverColor = 'rgba(255,165,0,0.15)' // Orange highlight for multi-row tree hover
|
|
5
7
|
|
|
6
8
|
export function renderMouseover({
|
|
7
9
|
ctx,
|
|
@@ -23,9 +25,33 @@ export function renderMouseover({
|
|
|
23
25
|
mouseCol2,
|
|
24
26
|
mouseClickRow,
|
|
25
27
|
mouseClickCol,
|
|
28
|
+
relativeTo,
|
|
29
|
+
rowNamesSet,
|
|
30
|
+
hoveredTreeNode,
|
|
26
31
|
} = model
|
|
27
32
|
ctx.resetTransform()
|
|
28
33
|
ctx.clearRect(0, 0, width, height)
|
|
34
|
+
|
|
35
|
+
// Highlight reference row (relativeTo) persistently
|
|
36
|
+
if (relativeTo) {
|
|
37
|
+
const referenceRowIndex = rowNamesSet.get(relativeTo)
|
|
38
|
+
if (referenceRowIndex !== undefined) {
|
|
39
|
+
ctx.fillStyle = referenceColor
|
|
40
|
+
ctx.fillRect(0, referenceRowIndex * rowHeight + scrollY, width, rowHeight)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Highlight multiple rows when hovering over tree nodes with children
|
|
45
|
+
if (hoveredTreeNode) {
|
|
46
|
+
ctx.fillStyle = multiRowHoverColor
|
|
47
|
+
for (const descendantName of hoveredTreeNode.descendantNames) {
|
|
48
|
+
const rowIndex = rowNamesSet.get(descendantName)
|
|
49
|
+
if (rowIndex !== undefined) {
|
|
50
|
+
ctx.fillRect(0, rowIndex * rowHeight + scrollY, width, rowHeight)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
29
55
|
if (mouseCol !== undefined) {
|
|
30
56
|
ctx.fillStyle = hoverColor
|
|
31
57
|
ctx.fillRect(mouseCol * colWidth + scrollX, 0, colWidth, height)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useState } from 'react'
|
|
2
2
|
|
|
3
|
+
import { autorun } from 'mobx'
|
|
3
4
|
import { observer } from 'mobx-react'
|
|
5
|
+
import { isAlive } from 'mobx-state-tree'
|
|
4
6
|
|
|
5
7
|
import TreeCanvasBlock from './TreeCanvasBlock'
|
|
6
8
|
import { padding } from './renderTreeCanvas'
|
|
@@ -9,10 +11,11 @@ import type { MsaViewModel } from '../../model'
|
|
|
9
11
|
|
|
10
12
|
const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
11
13
|
const ref = useRef<HTMLDivElement>(null)
|
|
14
|
+
const mouseoverRef = useRef<HTMLCanvasElement>(null)
|
|
12
15
|
const scheduled = useRef(false)
|
|
13
16
|
const deltaY = useRef(0)
|
|
14
17
|
const prevY = useRef<number>(0)
|
|
15
|
-
const { treeWidth, height, blocksY } = model
|
|
18
|
+
const { treeWidth, height, blocksY, treeAreaWidth, scrollY } = model
|
|
16
19
|
const [mouseDragging, setMouseDragging] = useState(false)
|
|
17
20
|
|
|
18
21
|
useEffect(() => {
|
|
@@ -80,6 +83,64 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
80
83
|
return cleanup
|
|
81
84
|
}, [model, mouseDragging])
|
|
82
85
|
|
|
86
|
+
// Global tree mouseover effect
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const ctx = mouseoverRef.current?.getContext('2d')
|
|
89
|
+
return ctx
|
|
90
|
+
? autorun(() => {
|
|
91
|
+
if (isAlive(model)) {
|
|
92
|
+
ctx.resetTransform()
|
|
93
|
+
ctx.clearRect(0, 0, treeAreaWidth, height)
|
|
94
|
+
|
|
95
|
+
// Highlight reference row (relativeTo) persistently
|
|
96
|
+
const { relativeTo, leaves, rowHeight, hoveredTreeNode } = model
|
|
97
|
+
if (relativeTo) {
|
|
98
|
+
const referenceLeaf = leaves.find(
|
|
99
|
+
leaf => leaf.data.name === relativeTo,
|
|
100
|
+
)
|
|
101
|
+
if (referenceLeaf) {
|
|
102
|
+
const y = referenceLeaf.x! + scrollY
|
|
103
|
+
ctx.fillStyle = 'rgba(0,128,255,0.3)' // Blue highlight for reference row
|
|
104
|
+
ctx.fillRect(0, y - rowHeight / 2, treeAreaWidth, rowHeight)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Highlight multiple rows when hovering over tree nodes
|
|
109
|
+
if (hoveredTreeNode) {
|
|
110
|
+
ctx.fillStyle = 'rgba(255,165,0,0.2)' // Orange highlight for tree hover
|
|
111
|
+
for (const descendantName of hoveredTreeNode.descendantNames) {
|
|
112
|
+
const matchingLeaf = leaves.find(
|
|
113
|
+
leaf => leaf.data.name === descendantName,
|
|
114
|
+
)
|
|
115
|
+
if (matchingLeaf) {
|
|
116
|
+
const y = matchingLeaf.x! + scrollY
|
|
117
|
+
ctx.fillRect(0, y - rowHeight / 2, treeAreaWidth, rowHeight)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Highlight single tree row corresponding to MSA mouseover (if not part of multi-row hover)
|
|
123
|
+
const { mouseOverRowName } = model
|
|
124
|
+
if (
|
|
125
|
+
mouseOverRowName &&
|
|
126
|
+
mouseOverRowName !== relativeTo &&
|
|
127
|
+
!hoveredTreeNode?.descendantNames.includes(mouseOverRowName)
|
|
128
|
+
) {
|
|
129
|
+
// Find the leaf node that matches the hovered row
|
|
130
|
+
const matchingLeaf = leaves.find(
|
|
131
|
+
leaf => leaf.data.name === mouseOverRowName,
|
|
132
|
+
)
|
|
133
|
+
if (matchingLeaf) {
|
|
134
|
+
const y = matchingLeaf.x! + scrollY
|
|
135
|
+
ctx.fillStyle = 'rgba(255,165,0,0.2)' // Orange highlight for MSA sync
|
|
136
|
+
ctx.fillRect(0, y - rowHeight / 2, treeAreaWidth, rowHeight)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
: undefined
|
|
142
|
+
}, [model, treeAreaWidth, height, scrollY])
|
|
143
|
+
|
|
83
144
|
function mouseDown(event: React.MouseEvent) {
|
|
84
145
|
// check if clicking a draggable element or a resize handle
|
|
85
146
|
const target = event.target as HTMLElement
|
|
@@ -117,6 +178,20 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
|
|
|
117
178
|
{blocksY.map(block => (
|
|
118
179
|
<TreeCanvasBlock key={block} model={model} offsetY={block} />
|
|
119
180
|
))}
|
|
181
|
+
<canvas
|
|
182
|
+
ref={mouseoverRef}
|
|
183
|
+
width={treeAreaWidth}
|
|
184
|
+
height={height}
|
|
185
|
+
style={{
|
|
186
|
+
position: 'absolute',
|
|
187
|
+
top: 0,
|
|
188
|
+
left: 0,
|
|
189
|
+
width: treeAreaWidth,
|
|
190
|
+
height,
|
|
191
|
+
zIndex: 1000,
|
|
192
|
+
pointerEvents: 'none',
|
|
193
|
+
}}
|
|
194
|
+
/>
|
|
120
195
|
</div>
|
|
121
196
|
)
|
|
122
197
|
})
|
|
@@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'
|
|
|
3
3
|
import { useTheme } from '@mui/material'
|
|
4
4
|
import { autorun } from 'mobx'
|
|
5
5
|
import { observer } from 'mobx-react'
|
|
6
|
-
import
|
|
6
|
+
import Flatbush from 'flatbush'
|
|
7
7
|
|
|
8
8
|
import TreeBranchMenu from './TreeBranchMenu'
|
|
9
9
|
import TreeNodeMenu from './TreeNodeMenu'
|
|
@@ -28,6 +28,46 @@ interface ClickEntry {
|
|
|
28
28
|
maxY: number
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
class ClickMapIndex {
|
|
32
|
+
private flatbush: Flatbush | null = null
|
|
33
|
+
private entries: ClickEntry[] = []
|
|
34
|
+
|
|
35
|
+
clear() {
|
|
36
|
+
this.flatbush = null
|
|
37
|
+
this.entries = []
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
insert(entry: ClickEntry) {
|
|
41
|
+
this.entries.push(entry)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
finish() {
|
|
45
|
+
if (this.entries.length === 0) {
|
|
46
|
+
this.flatbush = null
|
|
47
|
+
return
|
|
48
|
+
} else {
|
|
49
|
+
this.flatbush = new Flatbush(this.entries.length)
|
|
50
|
+
for (const entry of this.entries) {
|
|
51
|
+
this.flatbush.add(entry.minX, entry.minY, entry.maxX, entry.maxY)
|
|
52
|
+
}
|
|
53
|
+
this.flatbush.finish()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
search(box: {
|
|
58
|
+
minX: number
|
|
59
|
+
maxX: number
|
|
60
|
+
minY: number
|
|
61
|
+
maxY: number
|
|
62
|
+
}): ClickEntry[] {
|
|
63
|
+
return (
|
|
64
|
+
this.flatbush
|
|
65
|
+
?.search(box.minX, box.minY, box.maxX, box.maxY)
|
|
66
|
+
.map(i => this.entries[i]!) ?? []
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
31
71
|
const TreeCanvasBlock = observer(function ({
|
|
32
72
|
model,
|
|
33
73
|
offsetY,
|
|
@@ -37,7 +77,7 @@ const TreeCanvasBlock = observer(function ({
|
|
|
37
77
|
}) {
|
|
38
78
|
const theme = useTheme()
|
|
39
79
|
const ref = useRef<HTMLCanvasElement>(null)
|
|
40
|
-
const clickMap = useRef(new
|
|
80
|
+
const clickMap = useRef(new ClickMapIndex())
|
|
41
81
|
const mouseoverRef = useRef<HTMLCanvasElement>(null)
|
|
42
82
|
const [branchMenu, setBranchMenu] = useState<TooltipData>()
|
|
43
83
|
const [toggleNodeMenu, setToggleNodeMenu] = useState<TooltipData>()
|
|
@@ -93,6 +133,7 @@ const TreeCanvasBlock = observer(function ({
|
|
|
93
133
|
ctx.clearRect(0, 0, treeAreaWidth + padding, blockSize)
|
|
94
134
|
ctx.translate(0, -offsetY)
|
|
95
135
|
|
|
136
|
+
// Highlight tree element being directly hovered
|
|
96
137
|
if (hoverElt) {
|
|
97
138
|
const { minX, maxX, minY, maxY } = hoverElt
|
|
98
139
|
|
|
@@ -169,9 +210,29 @@ const TreeCanvasBlock = observer(function ({
|
|
|
169
210
|
return
|
|
170
211
|
}
|
|
171
212
|
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
213
|
+
const hoveredLeaf = hoverNameClickMap(event)
|
|
214
|
+
const hoveredBranch = hoverBranchClickMap(event)
|
|
215
|
+
const hoveredAny = hoveredLeaf || hoveredBranch
|
|
216
|
+
|
|
217
|
+
ref.current.style.cursor = hoveredAny ? 'pointer' : 'default'
|
|
218
|
+
setHoverElt(hoveredLeaf) // Only show direct hover highlight for leaf nodes
|
|
219
|
+
|
|
220
|
+
// Handle tree node hover for multi-row highlighting
|
|
221
|
+
if (hoveredAny) {
|
|
222
|
+
model.setHoveredTreeNode(hoveredAny.id)
|
|
223
|
+
|
|
224
|
+
// For leaf nodes, also set single row highlight for backward compatibility
|
|
225
|
+
if (hoveredLeaf?.name) {
|
|
226
|
+
const rowIndex = model.rowNamesSet.get(hoveredLeaf.name)
|
|
227
|
+
if (rowIndex !== undefined) {
|
|
228
|
+
model.setMousePos(undefined, rowIndex)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
// Clear all highlighting when not hovering over any tree node
|
|
233
|
+
model.setHoveredTreeNode(undefined)
|
|
234
|
+
model.setMousePos(undefined, undefined)
|
|
235
|
+
}
|
|
175
236
|
}}
|
|
176
237
|
onClick={event => {
|
|
177
238
|
const { clientX: x, clientY: y } = event
|
|
@@ -188,6 +249,9 @@ const TreeCanvasBlock = observer(function ({
|
|
|
188
249
|
}}
|
|
189
250
|
onMouseLeave={() => {
|
|
190
251
|
setHoverElt(undefined)
|
|
252
|
+
// Clear all highlighting when leaving tree area
|
|
253
|
+
model.setHoveredTreeNode(undefined)
|
|
254
|
+
model.setMousePos(undefined, undefined)
|
|
191
255
|
}}
|
|
192
256
|
ref={vref}
|
|
193
257
|
/>
|
|
@@ -79,12 +79,23 @@ const TreeMenu = observer(function ({
|
|
|
79
79
|
<MenuItem
|
|
80
80
|
dense
|
|
81
81
|
onClick={() => {
|
|
82
|
-
model.drawRelativeTo(node.
|
|
82
|
+
model.drawRelativeTo(node.name)
|
|
83
83
|
onClose()
|
|
84
84
|
}}
|
|
85
85
|
>
|
|
86
86
|
Indicate differences from this row
|
|
87
87
|
</MenuItem>
|
|
88
|
+
{model.relativeTo ? (
|
|
89
|
+
<MenuItem
|
|
90
|
+
dense
|
|
91
|
+
onClick={() => {
|
|
92
|
+
model.drawRelativeTo(undefined)
|
|
93
|
+
onClose()
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
Clear reference row
|
|
97
|
+
</MenuItem>
|
|
98
|
+
) : null}
|
|
88
99
|
</Menu>
|
|
89
100
|
)
|
|
90
101
|
})
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { MsaViewModel } from '../../model'
|
|
2
2
|
import type { Theme } from '@mui/material'
|
|
3
|
-
import type RBush from 'rbush'
|
|
4
3
|
|
|
5
4
|
export const padding = 600
|
|
6
5
|
|
|
@@ -18,6 +17,18 @@ interface ClickEntry {
|
|
|
18
17
|
maxY: number
|
|
19
18
|
}
|
|
20
19
|
|
|
20
|
+
interface ClickMapIndex {
|
|
21
|
+
clear(): void
|
|
22
|
+
insert(entry: ClickEntry): void
|
|
23
|
+
finish(): void
|
|
24
|
+
search(box: {
|
|
25
|
+
minX: number
|
|
26
|
+
maxX: number
|
|
27
|
+
minY: number
|
|
28
|
+
maxY: number
|
|
29
|
+
}): ClickEntry[]
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
export function renderTree({
|
|
22
33
|
offsetY,
|
|
23
34
|
ctx,
|
|
@@ -75,7 +86,7 @@ export function renderNodeBubbles({
|
|
|
75
86
|
blockSizeYOverride,
|
|
76
87
|
}: {
|
|
77
88
|
ctx: CanvasRenderingContext2D
|
|
78
|
-
clickMap?:
|
|
89
|
+
clickMap?: ClickMapIndex
|
|
79
90
|
offsetY: number
|
|
80
91
|
model: MsaViewModel
|
|
81
92
|
theme: Theme
|
|
@@ -133,7 +144,7 @@ export function renderTreeLabels({
|
|
|
133
144
|
model: MsaViewModel
|
|
134
145
|
offsetY: number
|
|
135
146
|
ctx: CanvasRenderingContext2D
|
|
136
|
-
clickMap?:
|
|
147
|
+
clickMap?: ClickMapIndex
|
|
137
148
|
theme: Theme
|
|
138
149
|
blockSizeYOverride?: number
|
|
139
150
|
}) {
|
|
@@ -239,12 +250,14 @@ export function renderTreeCanvas({
|
|
|
239
250
|
model: MsaViewModel
|
|
240
251
|
offsetY: number
|
|
241
252
|
ctx: CanvasRenderingContext2D
|
|
242
|
-
clickMap?:
|
|
253
|
+
clickMap?: ClickMapIndex
|
|
243
254
|
theme: Theme
|
|
244
255
|
highResScaleFactorOverride?: number
|
|
245
256
|
blockSizeYOverride?: number
|
|
246
257
|
}) {
|
|
247
258
|
clickMap?.clear()
|
|
259
|
+
|
|
260
|
+
// Defer the finish call until after all inserts are done
|
|
248
261
|
const {
|
|
249
262
|
noTree,
|
|
250
263
|
drawTree,
|
|
@@ -306,4 +319,7 @@ export function renderTreeCanvas({
|
|
|
306
319
|
blockSizeYOverride,
|
|
307
320
|
})
|
|
308
321
|
}
|
|
322
|
+
|
|
323
|
+
// Finish the index so it's ready for queries
|
|
324
|
+
clickMap?.finish()
|
|
309
325
|
}
|
package/src/components/util.ts
CHANGED
|
@@ -11,7 +11,7 @@ export function chooseGridPitch(
|
|
|
11
11
|
scale = Math.abs(scale)
|
|
12
12
|
const minMajorPitchBp = minMajorPitchPx * scale
|
|
13
13
|
const majorMagnitude = Number.parseInt(
|
|
14
|
-
|
|
14
|
+
minMajorPitchBp.toExponential().split(/e/i)[1]!,
|
|
15
15
|
10,
|
|
16
16
|
)
|
|
17
17
|
|
package/src/layout.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Flatbush from 'flatbush'
|
|
2
2
|
|
|
3
3
|
export default class Layout {
|
|
4
4
|
public rectangles: Map<
|
|
@@ -13,11 +13,13 @@ export default class Layout {
|
|
|
13
13
|
}
|
|
14
14
|
>
|
|
15
15
|
|
|
16
|
-
public maxHeightReached
|
|
16
|
+
public maxHeightReached = false
|
|
17
17
|
|
|
18
18
|
private maxHeight: number
|
|
19
19
|
|
|
20
|
-
private
|
|
20
|
+
private flatbush: Flatbush | null = null
|
|
21
|
+
|
|
22
|
+
private indexToId: string[] = []
|
|
21
23
|
|
|
22
24
|
private pTotalHeight: number
|
|
23
25
|
|
|
@@ -26,13 +28,51 @@ export default class Layout {
|
|
|
26
28
|
}: {
|
|
27
29
|
maxHeight?: number
|
|
28
30
|
} = {}) {
|
|
29
|
-
this.maxHeightReached = false
|
|
30
|
-
this.rbush = new RBush()
|
|
31
31
|
this.rectangles = new Map()
|
|
32
32
|
this.maxHeight = Math.ceil(maxHeight)
|
|
33
33
|
this.pTotalHeight = 0 // total height, in units of bitmap squares (px/pitchY)
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
private rebuildIndex() {
|
|
37
|
+
// Rebuild the spatial index with current rectangles
|
|
38
|
+
const rects = Array.from(this.rectangles.values())
|
|
39
|
+
if (rects.length === 0) {
|
|
40
|
+
this.flatbush = null
|
|
41
|
+
this.indexToId = []
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.flatbush = new Flatbush(rects.length)
|
|
46
|
+
this.indexToId = []
|
|
47
|
+
|
|
48
|
+
for (const rect of rects) {
|
|
49
|
+
this.flatbush.add(rect.minX, rect.minY, rect.maxX, rect.maxY)
|
|
50
|
+
this.indexToId.push(rect.id)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.flatbush.finish()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private collides(box: {
|
|
57
|
+
minX: number
|
|
58
|
+
minY: number
|
|
59
|
+
maxX: number
|
|
60
|
+
maxY: number
|
|
61
|
+
}): boolean {
|
|
62
|
+
if (!this.flatbush) {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const results = this.flatbush.search(
|
|
67
|
+
box.minX,
|
|
68
|
+
box.minY,
|
|
69
|
+
box.maxX,
|
|
70
|
+
box.maxY,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return results.length > 0
|
|
74
|
+
}
|
|
75
|
+
|
|
36
76
|
/**
|
|
37
77
|
* @returns top position for the rect, or Null if laying
|
|
38
78
|
* out the rect would exceed maxHeighe
|
|
@@ -44,7 +84,7 @@ export default class Layout {
|
|
|
44
84
|
height: number,
|
|
45
85
|
data: unknown,
|
|
46
86
|
): number | null {
|
|
47
|
-
// add to
|
|
87
|
+
// add to flatbush
|
|
48
88
|
const existingRecord = this.rectangles.get(id)
|
|
49
89
|
if (existingRecord) {
|
|
50
90
|
return existingRecord.minY
|
|
@@ -52,7 +92,7 @@ export default class Layout {
|
|
|
52
92
|
|
|
53
93
|
let currHeight = 0
|
|
54
94
|
while (
|
|
55
|
-
this.
|
|
95
|
+
this.collides({
|
|
56
96
|
minX: left,
|
|
57
97
|
minY: currHeight,
|
|
58
98
|
maxX: right,
|
|
@@ -71,8 +111,8 @@ export default class Layout {
|
|
|
71
111
|
id,
|
|
72
112
|
data,
|
|
73
113
|
}
|
|
74
|
-
this.rbush.insert(record)
|
|
75
114
|
this.rectangles.set(id, record)
|
|
115
|
+
this.rebuildIndex()
|
|
76
116
|
this.pTotalHeight = Math.max(this.pTotalHeight, currHeight)
|
|
77
117
|
return currHeight
|
|
78
118
|
}
|
package/src/model/treeModel.ts
CHANGED
|
@@ -70,14 +70,14 @@ export function TreeModelF() {
|
|
|
70
70
|
* set tree area width (px)
|
|
71
71
|
*/
|
|
72
72
|
setTreeAreaWidth(n: number) {
|
|
73
|
-
self.treeAreaWidth = n
|
|
73
|
+
self.treeAreaWidth = Math.round(n)
|
|
74
74
|
},
|
|
75
75
|
/**
|
|
76
76
|
* #action
|
|
77
77
|
* set tree width (px)
|
|
78
78
|
*/
|
|
79
79
|
setTreeWidth(n: number) {
|
|
80
|
-
self.treeWidth = n
|
|
80
|
+
self.treeWidth = Math.round(n)
|
|
81
81
|
},
|
|
82
82
|
|
|
83
83
|
/**
|