react-msaview 2.0.0 → 2.1.1

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.
Files changed (149) hide show
  1. package/bundle/index.js +35 -33
  2. package/dist/UniprotTrack.js +6 -5
  3. package/dist/UniprotTrack.js.map +1 -1
  4. package/dist/colorSchemes.d.ts +3 -9
  5. package/dist/colorSchemes.js +8 -6
  6. package/dist/colorSchemes.js.map +1 -1
  7. package/dist/components/BoxTrack.d.ts +4 -3
  8. package/dist/components/BoxTrack.js +6 -137
  9. package/dist/components/BoxTrack.js.map +1 -1
  10. package/dist/components/BoxTrackBlock.d.ts +8 -0
  11. package/dist/components/BoxTrackBlock.js +136 -0
  12. package/dist/components/BoxTrackBlock.js.map +1 -0
  13. package/dist/components/Header.d.ts +2 -1
  14. package/dist/components/Header.js +29 -27
  15. package/dist/components/Header.js.map +1 -1
  16. package/dist/components/ImportForm.d.ts +2 -1
  17. package/dist/components/MSABlock.d.ts +8 -0
  18. package/dist/components/MSABlock.js +103 -0
  19. package/dist/components/MSABlock.js.map +1 -0
  20. package/dist/components/MSACanvas.d.ts +2 -1
  21. package/dist/components/MSACanvas.js +3 -101
  22. package/dist/components/MSACanvas.js.map +1 -1
  23. package/dist/components/MSAMouseoverCanvas.d.ts +6 -0
  24. package/dist/components/MSAMouseoverCanvas.js +52 -0
  25. package/dist/components/MSAMouseoverCanvas.js.map +1 -0
  26. package/dist/components/MSAView.d.ts +2 -1
  27. package/dist/components/MSAView.js +10 -36
  28. package/dist/components/MSAView.js.map +1 -1
  29. package/dist/components/MultiAlignmentSelector.d.ts +6 -0
  30. package/dist/components/MultiAlignmentSelector.js +13 -0
  31. package/dist/components/MultiAlignmentSelector.js.map +1 -0
  32. package/dist/components/ResizeHandles.d.ts +3 -2
  33. package/dist/components/Rubberband.d.ts +1 -1
  34. package/dist/components/Rubberband.js +0 -1
  35. package/dist/components/Rubberband.js.map +1 -1
  36. package/dist/components/Ruler.d.ts +2 -1
  37. package/dist/components/Ruler.js +2 -2
  38. package/dist/components/Ruler.js.map +1 -1
  39. package/dist/components/TextTrack.d.ts +2 -1
  40. package/dist/components/Track.d.ts +3 -2
  41. package/dist/components/Track.js +4 -3
  42. package/dist/components/Track.js.map +1 -1
  43. package/dist/components/TreeBranchMenu.d.ts +14 -0
  44. package/dist/components/TreeBranchMenu.js +26 -0
  45. package/dist/components/TreeBranchMenu.js.map +1 -0
  46. package/dist/components/TreeCanvas.d.ts +2 -1
  47. package/dist/components/TreeCanvas.js +2 -320
  48. package/dist/components/TreeCanvas.js.map +1 -1
  49. package/dist/components/TreeCanvasBlock.d.ts +7 -0
  50. package/dist/components/TreeCanvasBlock.js +252 -0
  51. package/dist/components/TreeCanvasBlock.js.map +1 -0
  52. package/dist/components/TreeMenu.d.ts +12 -0
  53. package/dist/components/TreeMenu.js +56 -0
  54. package/dist/components/TreeMenu.js.map +1 -0
  55. package/dist/components/TreeRuler.d.ts +2 -1
  56. package/dist/components/VerticalGuide.d.ts +2 -1
  57. package/dist/components/ZoomControls.d.ts +6 -0
  58. package/dist/components/ZoomControls.js +58 -0
  59. package/dist/components/ZoomControls.js.map +1 -0
  60. package/dist/components/dialogs/AboutDlg.d.ts +4 -0
  61. package/dist/components/{AboutDlg.js → dialogs/AboutDlg.js} +3 -3
  62. package/dist/components/dialogs/AboutDlg.js.map +1 -0
  63. package/dist/components/{AddTrackDlg.d.ts → dialogs/AddTrackDlg.d.ts} +3 -2
  64. package/dist/components/dialogs/AddTrackDlg.js.map +1 -0
  65. package/dist/components/{AnnotationDlg.d.ts → dialogs/AnnotationDlg.d.ts} +3 -2
  66. package/dist/components/dialogs/AnnotationDlg.js.map +1 -0
  67. package/dist/components/dialogs/DetailsDlg.d.ts +7 -0
  68. package/dist/components/{DetailsDlg.js → dialogs/DetailsDlg.js} +3 -2
  69. package/dist/components/dialogs/DetailsDlg.js.map +1 -0
  70. package/dist/components/{MoreInfoDlg.d.ts → dialogs/MoreInfoDlg.d.ts} +2 -1
  71. package/dist/components/dialogs/MoreInfoDlg.js.map +1 -0
  72. package/dist/components/dialogs/SettingsDlg.d.ts +7 -0
  73. package/dist/components/{SettingsDlg.js → dialogs/SettingsDlg.js} +4 -3
  74. package/dist/components/dialogs/SettingsDlg.js.map +1 -0
  75. package/dist/components/{TrackInfoDlg.d.ts → dialogs/TrackInfoDlg.d.ts} +2 -1
  76. package/dist/components/dialogs/TrackInfoDlg.js.map +1 -0
  77. package/dist/components/dialogs/TracklistDlg.d.ts +7 -0
  78. package/dist/components/{TracklistDlg.js → dialogs/TracklistDlg.js} +2 -2
  79. package/dist/components/dialogs/TracklistDlg.js.map +1 -0
  80. package/dist/components/util.js +0 -1
  81. package/dist/components/util.js.map +1 -1
  82. package/dist/model.d.ts +36 -39
  83. package/dist/model.js +25 -22
  84. package/dist/model.js.map +1 -1
  85. package/dist/parseNewick.js +2 -2
  86. package/dist/parseNewick.js.map +1 -1
  87. package/dist/parsers/FastaMSA.d.ts +1 -3
  88. package/dist/parsers/FastaMSA.js +2 -2
  89. package/dist/parsers/FastaMSA.js.map +1 -1
  90. package/dist/parsers/StockholmMSA.d.ts +3 -5
  91. package/dist/parsers/StockholmMSA.js +3 -3
  92. package/dist/parsers/StockholmMSA.js.map +1 -1
  93. package/dist/util.d.ts +5 -7
  94. package/dist/util.js +4 -2
  95. package/dist/util.js.map +1 -1
  96. package/dist/version.d.ts +1 -1
  97. package/dist/version.js +1 -1
  98. package/dist/version.js.map +1 -1
  99. package/package.json +9 -9
  100. package/src/UniprotTrack.ts +6 -7
  101. package/src/colorSchemes.ts +12 -9
  102. package/src/components/BoxTrack.tsx +6 -198
  103. package/src/components/BoxTrackBlock.tsx +198 -0
  104. package/src/components/Header.tsx +49 -60
  105. package/src/components/MSABlock.tsx +164 -0
  106. package/src/components/MSACanvas.tsx +3 -158
  107. package/src/components/MSAMouseoverCanvas.tsx +87 -0
  108. package/src/components/MSAView.tsx +19 -63
  109. package/src/components/MultiAlignmentSelector.tsx +33 -0
  110. package/src/components/Rubberband.tsx +0 -1
  111. package/src/components/Ruler.tsx +2 -1
  112. package/src/components/Track.tsx +9 -6
  113. package/src/components/TreeBranchMenu.tsx +67 -0
  114. package/src/components/TreeCanvas.tsx +2 -507
  115. package/src/components/TreeCanvasBlock.tsx +359 -0
  116. package/src/components/TreeMenu.tsx +105 -0
  117. package/src/components/ZoomControls.tsx +78 -0
  118. package/src/components/{AboutDlg.tsx → dialogs/AboutDlg.tsx} +3 -9
  119. package/src/components/{AddTrackDlg.tsx → dialogs/AddTrackDlg.tsx} +1 -1
  120. package/src/components/{AnnotationDlg.tsx → dialogs/AnnotationDlg.tsx} +2 -2
  121. package/src/components/{DetailsDlg.tsx → dialogs/DetailsDlg.tsx} +5 -5
  122. package/src/components/{SettingsDlg.tsx → dialogs/SettingsDlg.tsx} +6 -6
  123. package/src/components/{TracklistDlg.tsx → dialogs/TracklistDlg.tsx} +2 -4
  124. package/src/components/util.ts +0 -1
  125. package/src/declare.d.ts +0 -1
  126. package/src/model.ts +32 -29
  127. package/src/parseNewick.ts +2 -3
  128. package/src/parsers/FastaMSA.ts +3 -3
  129. package/src/parsers/StockholmMSA.ts +5 -5
  130. package/src/util.ts +8 -5
  131. package/src/version.ts +1 -1
  132. package/dist/components/AboutDlg.d.ts +0 -4
  133. package/dist/components/AboutDlg.js.map +0 -1
  134. package/dist/components/AddTrackDlg.js.map +0 -1
  135. package/dist/components/AnnotationDlg.js.map +0 -1
  136. package/dist/components/DetailsDlg.d.ts +0 -7
  137. package/dist/components/DetailsDlg.js.map +0 -1
  138. package/dist/components/MoreInfoDlg.js.map +0 -1
  139. package/dist/components/SettingsDlg.d.ts +0 -7
  140. package/dist/components/SettingsDlg.js.map +0 -1
  141. package/dist/components/TrackInfoDlg.js.map +0 -1
  142. package/dist/components/TracklistDlg.d.ts +0 -7
  143. package/dist/components/TracklistDlg.js.map +0 -1
  144. /package/dist/components/{AddTrackDlg.js → dialogs/AddTrackDlg.js} +0 -0
  145. /package/dist/components/{AnnotationDlg.js → dialogs/AnnotationDlg.js} +0 -0
  146. /package/dist/components/{MoreInfoDlg.js → dialogs/MoreInfoDlg.js} +0 -0
  147. /package/dist/components/{TrackInfoDlg.js → dialogs/TrackInfoDlg.js} +0 -0
  148. /package/src/components/{MoreInfoDlg.tsx → dialogs/MoreInfoDlg.tsx} +0 -0
  149. /package/src/components/{TrackInfoDlg.tsx → dialogs/TrackInfoDlg.tsx} +0 -0
@@ -0,0 +1,33 @@
1
+ import React from 'react'
2
+ import { observer } from 'mobx-react'
3
+ import { Select } from '@mui/material'
4
+
5
+ // locals
6
+ import { MsaViewModel } from '../model'
7
+
8
+ const MultiAlignmentSelector = observer(function ({
9
+ model,
10
+ }: {
11
+ model: MsaViewModel
12
+ }) {
13
+ const { currentAlignment, alignmentNames } = model
14
+ return alignmentNames.length > 0 ? (
15
+ <Select
16
+ native
17
+ value={currentAlignment}
18
+ size="small"
19
+ onChange={event => {
20
+ model.setCurrentAlignment(+(event.target.value as string))
21
+ model.setScrollX(0)
22
+ model.setScrollY(0)
23
+ }}
24
+ >
25
+ {alignmentNames.map((option, index) => (
26
+ <option key={`${option}-${index}`} value={index}>
27
+ {option}
28
+ </option>
29
+ ))}
30
+ </Select>
31
+ ) : null
32
+ })
33
+ export default MultiAlignmentSelector
@@ -142,7 +142,6 @@ function Rubberband({
142
142
  setCurrentX(undefined)
143
143
  }
144
144
 
145
- // eslint-disable-next-line @typescript-eslint/ban-types
146
145
  function handleMenuItemClick(_: unknown, callback: Function) {
147
146
  callback()
148
147
  handleClose()
@@ -80,6 +80,7 @@ const Ruler = observer(function ({ model }: { model: MsaViewModel }) {
80
80
  MSA,
81
81
  colWidth,
82
82
  msaAreaWidth,
83
+ rulerHeight,
83
84
  resizeHandleWidth,
84
85
  scrollX,
85
86
  blocksX,
@@ -96,7 +97,7 @@ const Ruler = observer(function ({ model }: { model: MsaViewModel }) {
96
97
  width: msaAreaWidth,
97
98
  cursor: 'crosshair',
98
99
  overflow: 'hidden',
99
- height: 20,
100
+ height: rulerHeight,
100
101
  background: '#ccc',
101
102
  }}
102
103
  >
@@ -1,4 +1,4 @@
1
- import React, { useState, useRef, useEffect } from 'react'
1
+ import React, { useState, useRef, useEffect, lazy, Suspense } from 'react'
2
2
  import normalizeWheel from 'normalize-wheel'
3
3
  import { observer } from 'mobx-react'
4
4
  import { IconButton, Menu, MenuItem } from '@mui/material'
@@ -9,7 +9,8 @@ import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'
9
9
 
10
10
  // locals
11
11
  import { MsaViewModel } from '../model'
12
- import TrackInfoDialog from './TrackInfoDlg'
12
+
13
+ const TrackInfoDialog = lazy(() => import('./dialogs/TrackInfoDlg'))
13
14
 
14
15
  const useStyles = makeStyles()({
15
16
  button: {
@@ -84,10 +85,12 @@ export const TrackLabel = observer(function ({
84
85
  </Menu>
85
86
  ) : null}
86
87
  {trackInfoDlgOpen ? (
87
- <TrackInfoDialog
88
- model={track.model}
89
- onClose={() => setTrackInfoDlgOpen(false)}
90
- />
88
+ <Suspense fallback={null}>
89
+ <TrackInfoDialog
90
+ model={track.model}
91
+ onClose={() => setTrackInfoDlgOpen(false)}
92
+ />
93
+ </Suspense>
91
94
  ) : null}
92
95
  </div>
93
96
  )
@@ -0,0 +1,67 @@
1
+ import React from 'react'
2
+ import { Menu, MenuItem } from '@mui/material'
3
+ import { observer } from 'mobx-react'
4
+
5
+ // locals
6
+ import { MsaViewModel } from '../model'
7
+
8
+ interface Node {
9
+ x: number
10
+ y: number
11
+ name: string
12
+ id: string
13
+ }
14
+
15
+ const TreeBranchMenu = observer(function ({
16
+ node,
17
+ model,
18
+ onClose,
19
+ }: {
20
+ node: Node
21
+ model: MsaViewModel
22
+ onClose: () => void
23
+ }) {
24
+ return (
25
+ <Menu
26
+ anchorReference="anchorPosition"
27
+ anchorPosition={{
28
+ left: node.x,
29
+ top: node.y,
30
+ }}
31
+ transitionDuration={0}
32
+ keepMounted
33
+ open={Boolean(node)}
34
+ onClose={onClose}
35
+ >
36
+ <MenuItem dense disabled>
37
+ {node.name}
38
+ </MenuItem>
39
+ <MenuItem
40
+ dense
41
+ onClick={() => {
42
+ model.toggleCollapsed(node.id)
43
+ onClose()
44
+ }}
45
+ >
46
+ {model.collapsed.includes(node.id)
47
+ ? 'Expand this node'
48
+ : 'Collapse this node'}
49
+ </MenuItem>
50
+ <MenuItem
51
+ dense
52
+ onClick={() => {
53
+ model.showOnly === node.id
54
+ ? model.setShowOnly(undefined)
55
+ : model.setShowOnly(node.id)
56
+ onClose()
57
+ }}
58
+ >
59
+ {model.showOnly === node.id
60
+ ? 'Disable show only this node'
61
+ : 'Show only this node'}
62
+ </MenuItem>
63
+ </Menu>
64
+ )
65
+ })
66
+
67
+ export default TreeBranchMenu
@@ -1,518 +1,13 @@
1
1
  import React, { useEffect, useRef, useState } from 'react'
2
- import { Menu, MenuItem } from '@mui/material'
3
2
  import normalizeWheel from 'normalize-wheel'
4
3
  import { observer } from 'mobx-react'
5
- import RBush from 'rbush'
6
4
 
7
5
  // locals
8
6
  import { MsaViewModel } from '../model'
9
- import MoreInfoDlg from './MoreInfoDlg'
10
-
11
- const extendBounds = 5
12
- const radius = 3.5
13
- const d = radius * 2
7
+ import TreeCanvasBlock from './TreeCanvasBlock'
14
8
 
15
9
  const padding = 600
16
10
 
17
- interface TooltipData {
18
- name: string
19
- id: string
20
- x: number
21
- y: number
22
- }
23
-
24
- interface ClickEntry {
25
- name: string
26
- id: string
27
- branch?: boolean
28
- minX: number
29
- maxX: number
30
- minY: number
31
- maxY: number
32
- }
33
-
34
- const TreeMenu = observer(function ({
35
- node,
36
- onClose,
37
- model,
38
- }: {
39
- node: { x: number; y: number; name: string }
40
- model: MsaViewModel
41
- onClose: () => void
42
- }) {
43
- const { structures } = model
44
- const nodeDetails = node ? model.getRowData(node.name) : undefined
45
-
46
- return (
47
- <>
48
- <Menu
49
- anchorReference="anchorPosition"
50
- anchorPosition={{
51
- top: node.y,
52
- left: node.x,
53
- }}
54
- transitionDuration={0}
55
- keepMounted
56
- open={Boolean(node)}
57
- onClose={onClose}
58
- >
59
- <MenuItem dense disabled>
60
- {node.name}
61
- </MenuItem>
62
-
63
- <MenuItem
64
- dense
65
- onClick={() => {
66
- model.setDialogComponent(MoreInfoDlg, {
67
- info: model.getRowData(node.name),
68
- })
69
- onClose()
70
- }}
71
- >
72
- More info...
73
- </MenuItem>
74
-
75
- {structures[node.name]?.map(entry => {
76
- return !model.selectedStructures.some(n => n.id === node.name) ? (
77
- <MenuItem
78
- key={JSON.stringify(entry)}
79
- dense
80
- onClick={() => {
81
- model.addStructureToSelection({
82
- structure: entry,
83
- id: node.name,
84
- })
85
- onClose()
86
- }}
87
- >
88
- Add PDB to selection ({entry.pdb})
89
- </MenuItem>
90
- ) : (
91
- <MenuItem
92
- key={JSON.stringify(entry)}
93
- dense
94
- onClick={() => {
95
- model.removeStructureFromSelection({
96
- structure: entry,
97
- id: node.name,
98
- })
99
- onClose()
100
- }}
101
- >
102
- Remove PDB from selection ({entry.pdb})
103
- </MenuItem>
104
- )
105
- })}
106
-
107
- {
108
- // @ts-expect-error
109
- nodeDetails?.data.accession?.map((accession: string) => (
110
- <MenuItem
111
- dense
112
- key={accession}
113
- onClick={() => {
114
- model.addUniprotTrack({
115
- // @ts-expect-error
116
- name: nodeDetails?.data.name,
117
- accession,
118
- })
119
- onClose()
120
- }}
121
- >
122
- Open UniProt track ({accession})
123
- </MenuItem>
124
- ))
125
- }
126
- </Menu>
127
- </>
128
- )
129
- })
130
- interface Node {
131
- x: number
132
- y: number
133
- name: string
134
- id: string
135
- }
136
-
137
- const TreeBranchMenu = observer(function ({
138
- node,
139
- model,
140
- onClose,
141
- }: {
142
- node: Node
143
- model: MsaViewModel
144
- onClose: () => void
145
- }) {
146
- return (
147
- <Menu
148
- anchorReference="anchorPosition"
149
- anchorPosition={{
150
- left: node.x,
151
- top: node.y,
152
- }}
153
- transitionDuration={0}
154
- keepMounted
155
- open={Boolean(node)}
156
- onClose={onClose}
157
- >
158
- <MenuItem dense disabled>
159
- {node.name}
160
- </MenuItem>
161
- <MenuItem
162
- dense
163
- onClick={() => {
164
- model.toggleCollapsed(node.id)
165
- onClose()
166
- }}
167
- >
168
- {model.collapsed.includes(node.id)
169
- ? 'Expand this node'
170
- : 'Collapse this node'}
171
- </MenuItem>
172
- <MenuItem
173
- dense
174
- onClick={() => {
175
- model.showOnly === node.id
176
- ? model.setShowOnly(undefined)
177
- : model.setShowOnly(node.id)
178
- onClose()
179
- }}
180
- >
181
- {model.showOnly === node.id
182
- ? 'Disable show only this node'
183
- : 'Show only this node'}
184
- </MenuItem>
185
- </Menu>
186
- )
187
- })
188
-
189
- const TreeBlock = observer(function ({
190
- model,
191
- offsetY,
192
- }: {
193
- model: MsaViewModel
194
- offsetY: number
195
- }) {
196
- const ref = useRef<HTMLCanvasElement>(null)
197
- const clickMap = useRef(new RBush<ClickEntry>())
198
- const mouseoverRef = useRef<HTMLCanvasElement>(null)
199
- const [branchMenu, setBranchMenu] = useState<TooltipData>()
200
- const [toggleNodeMenu, setToggleNodeMenu] = useState<TooltipData>()
201
- const [hoverElt, setHoverElt] = useState<ClickEntry>()
202
-
203
- const {
204
- hierarchy,
205
- rowHeight,
206
- scrollY,
207
- treeWidth,
208
- showBranchLen,
209
- collapsed,
210
- margin,
211
- labelsAlignRight,
212
- noTree,
213
- blockSize,
214
- drawNodeBubbles,
215
- drawTree,
216
- treeAreaWidth,
217
- structures,
218
- highResScaleFactor,
219
- } = model
220
-
221
- useEffect(() => {
222
- clickMap.current.clear()
223
-
224
- if (!ref.current) {
225
- return
226
- }
227
- const ctx = ref.current.getContext('2d')
228
- if (!ctx) {
229
- return
230
- }
231
-
232
- ctx.resetTransform()
233
- ctx.scale(highResScaleFactor, highResScaleFactor)
234
- ctx.clearRect(0, 0, treeWidth + padding, blockSize)
235
- ctx.translate(margin.left, -offsetY)
236
-
237
- const font = ctx.font
238
- ctx.font = font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
239
-
240
- if (!noTree && drawTree) {
241
- hierarchy.links().forEach(({ source, target }) => {
242
- const y = showBranchLen ? 'len' : 'y'
243
- // @ts-expect-error
244
- const { x: sy, [y]: sx } = source
245
- // @ts-expect-error
246
- const { x: ty, [y]: tx } = target
247
-
248
- const y1 = Math.min(sy, ty)
249
- const y2 = Math.max(sy, ty)
250
- // 1d line intersection to check if line crosses block at all, this is
251
- // an optimization that allows us to skip drawing most tree links
252
- // outside the block
253
- if (offsetY + blockSize >= y1 && y2 >= offsetY) {
254
- ctx.beginPath()
255
- ctx.moveTo(sx, sy)
256
- ctx.lineTo(sx, ty)
257
- ctx.lineTo(tx, ty)
258
- ctx.stroke()
259
- }
260
- })
261
-
262
- if (drawNodeBubbles) {
263
- hierarchy.descendants().forEach(node => {
264
- const val = showBranchLen ? 'len' : 'y'
265
- const {
266
- // @ts-expect-error
267
- x: y,
268
- // @ts-expect-error
269
- [val]: x,
270
- data,
271
- } = node
272
- const { id = '', name = '' } = data
273
-
274
- if (
275
- y > offsetY - extendBounds &&
276
- y < offsetY + blockSize + extendBounds
277
- ) {
278
- ctx.strokeStyle = 'black'
279
- ctx.fillStyle = collapsed.includes(id) ? 'black' : 'white'
280
- ctx.beginPath()
281
- ctx.arc(x, y, radius, 0, 2 * Math.PI)
282
- ctx.fill()
283
- ctx.stroke()
284
-
285
- clickMap.current.insert({
286
- minX: x - radius,
287
- maxX: x - radius + d,
288
- minY: y - radius,
289
- maxY: y - radius + d,
290
- branch: true,
291
- id,
292
- name,
293
- })
294
- }
295
- })
296
- }
297
- }
298
-
299
- if (rowHeight >= 10) {
300
- if (labelsAlignRight) {
301
- ctx.textAlign = 'right'
302
- ctx.setLineDash([1, 3])
303
- } else {
304
- ctx.textAlign = 'start'
305
- }
306
- hierarchy.leaves().forEach(node => {
307
- const {
308
- // @ts-expect-error
309
- x: y,
310
- // @ts-expect-error
311
- y: x,
312
- data: { name, id },
313
- // @ts-expect-error
314
- len,
315
- } = node
316
-
317
- if (
318
- y > offsetY - extendBounds &&
319
- y < offsetY + blockSize + extendBounds
320
- ) {
321
- // note: +rowHeight/4 matches with -rowHeight/4 in msa
322
- const yp = y + rowHeight / 4
323
- const xp = showBranchLen ? len : x
324
-
325
- const { width } = ctx.measureText(name)
326
- const height = ctx.measureText('M').width // use an 'em' for height
327
-
328
- const hasStructure = structures[name]
329
- ctx.fillStyle = hasStructure ? 'blue' : 'black'
330
-
331
- if (!drawTree && !labelsAlignRight) {
332
- ctx.fillText(name, 0, yp)
333
- clickMap.current.insert({
334
- minX: 0,
335
- maxX: width,
336
- minY: yp - height,
337
- maxY: yp,
338
- name,
339
- id,
340
- })
341
- } else if (labelsAlignRight) {
342
- const smallPadding = 2
343
- const offset = treeAreaWidth - smallPadding - margin.left
344
- if (drawTree && !noTree) {
345
- const { width } = ctx.measureText(name)
346
- ctx.moveTo(xp + radius + 2, y)
347
- ctx.lineTo(offset - smallPadding - width, y)
348
- ctx.stroke()
349
- }
350
- ctx.fillText(name, offset, yp)
351
- clickMap.current.insert({
352
- minX: treeAreaWidth - margin.left - width,
353
- maxX: treeAreaWidth - margin.left,
354
- minY: yp - height,
355
- maxY: yp,
356
- name,
357
- id,
358
- })
359
- } else {
360
- ctx.fillText(name, xp + d, yp)
361
- clickMap.current.insert({
362
- minX: xp + d,
363
- maxX: xp + d + width,
364
- minY: yp - height,
365
- maxY: yp,
366
- name,
367
- id,
368
- })
369
- }
370
- }
371
- })
372
- ctx.setLineDash([])
373
- }
374
- }, [
375
- collapsed,
376
- rowHeight,
377
- margin.left,
378
- hierarchy,
379
- offsetY,
380
- treeWidth,
381
- showBranchLen,
382
- noTree,
383
- blockSize,
384
- drawNodeBubbles,
385
- drawTree,
386
- labelsAlignRight,
387
- treeAreaWidth,
388
- structures,
389
- highResScaleFactor,
390
- ])
391
-
392
- useEffect(() => {
393
- const canvas = mouseoverRef.current
394
- if (!canvas) {
395
- return
396
- }
397
- const ctx = canvas.getContext('2d')
398
- if (!ctx) {
399
- return
400
- }
401
-
402
- ctx.resetTransform()
403
- ctx.clearRect(0, 0, treeWidth + padding, blockSize)
404
- ctx.translate(margin.left, -offsetY)
405
-
406
- if (hoverElt) {
407
- const { minX, maxX, minY, maxY } = hoverElt
408
-
409
- ctx.fillStyle = 'rgba(0,0,0,0.1)'
410
- ctx.fillRect(minX, minY, maxX - minX, maxY - minY)
411
- }
412
- }, [hoverElt, margin.left, offsetY, blockSize, treeWidth])
413
-
414
- function hoverBranchClickMap(event: React.MouseEvent) {
415
- const x = event.nativeEvent.offsetX - margin.left
416
- const y = event.nativeEvent.offsetY
417
-
418
- const [entry] = clickMap.current.search({
419
- minX: x,
420
- maxX: x + 1,
421
- minY: y + offsetY,
422
- maxY: y + 1 + offsetY,
423
- })
424
-
425
- return entry && entry.branch
426
- ? { ...entry, x: event.clientX, y: event.clientY }
427
- : undefined
428
- }
429
-
430
- function hoverNameClickMap(event: React.MouseEvent) {
431
- const x = event.nativeEvent.offsetX - margin.left
432
- const y = event.nativeEvent.offsetY
433
- const [entry] = clickMap.current.search({
434
- minX: x,
435
- maxX: x + 1,
436
- minY: y + offsetY,
437
- maxY: y + 1 + offsetY,
438
- })
439
-
440
- return entry && !entry.branch
441
- ? { ...entry, x: event.clientX, y: event.clientY }
442
- : undefined
443
- }
444
-
445
- return (
446
- <>
447
- {branchMenu?.id ? (
448
- <TreeBranchMenu
449
- node={branchMenu}
450
- model={model}
451
- onClose={() => setBranchMenu(undefined)}
452
- />
453
- ) : null}
454
-
455
- {toggleNodeMenu?.id ? (
456
- <TreeMenu
457
- node={toggleNodeMenu}
458
- model={model}
459
- onClose={() => setToggleNodeMenu(undefined)}
460
- />
461
- ) : null}
462
-
463
- <canvas
464
- width={(treeWidth + padding) * highResScaleFactor}
465
- height={blockSize * highResScaleFactor}
466
- style={{
467
- width: treeWidth + padding,
468
- height: blockSize,
469
- top: scrollY + offsetY,
470
- left: 0,
471
- position: 'absolute',
472
- }}
473
- onMouseMove={event => {
474
- if (!ref.current) {
475
- return
476
- }
477
-
478
- const ret = hoverNameClickMap(event) || hoverBranchClickMap(event)
479
-
480
- ref.current.style.cursor = ret ? 'pointer' : 'default'
481
-
482
- setHoverElt(hoverNameClickMap(event))
483
- }}
484
- onClick={event => {
485
- const { clientX: x, clientY: y } = event
486
-
487
- const data = hoverBranchClickMap(event)
488
- if (data?.id) {
489
- setBranchMenu({ ...data, x, y })
490
- }
491
-
492
- const data2 = hoverNameClickMap(event)
493
- if (data2?.id) {
494
- setToggleNodeMenu({ ...data2, x, y })
495
- }
496
- }}
497
- ref={ref}
498
- />
499
- <canvas
500
- style={{
501
- width: treeWidth + padding,
502
- height: blockSize,
503
- top: scrollY + offsetY,
504
- left: 0,
505
- position: 'absolute',
506
- pointerEvents: 'none',
507
- zIndex: 100,
508
- }}
509
- width={treeWidth + padding}
510
- height={blockSize}
511
- ref={mouseoverRef}
512
- />
513
- </>
514
- )
515
- })
516
11
  const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
517
12
  const ref = useRef<HTMLDivElement>(null)
518
13
  const scheduled = useRef(false)
@@ -624,7 +119,7 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
624
119
  }}
625
120
  >
626
121
  {blocksY.map(block => (
627
- <TreeBlock key={block} model={model} offsetY={block} />
122
+ <TreeCanvasBlock key={block} model={model} offsetY={block} />
628
123
  ))}
629
124
  </div>
630
125
  )