react-msaview 1.3.2 → 2.1.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.
Files changed (245) hide show
  1. package/bundle/index.js +285 -97099
  2. package/dist/StructureModel.d.ts +9 -0
  3. package/dist/StructureModel.js +11 -0
  4. package/dist/StructureModel.js.map +1 -0
  5. package/dist/UniprotTrack.d.ts +27 -0
  6. package/dist/UniprotTrack.js +53 -0
  7. package/dist/UniprotTrack.js.map +1 -0
  8. package/dist/colorSchemes.d.ts +5 -11
  9. package/dist/colorSchemes.js +27 -32
  10. package/dist/colorSchemes.js.map +1 -0
  11. package/dist/components/BoxTrack.d.ts +6 -6
  12. package/dist/components/BoxTrack.js +9 -137
  13. package/dist/components/BoxTrack.js.map +1 -0
  14. package/dist/components/BoxTrackBlock.d.ts +8 -0
  15. package/dist/components/BoxTrackBlock.js +136 -0
  16. package/dist/components/BoxTrackBlock.js.map +1 -0
  17. package/dist/components/Header.d.ts +2 -2
  18. package/dist/components/Header.js +55 -48
  19. package/dist/components/Header.js.map +1 -0
  20. package/dist/components/ImportForm.d.ts +2 -2
  21. package/dist/components/ImportForm.js +59 -71
  22. package/dist/components/ImportForm.js.map +1 -0
  23. package/dist/components/MSABlock.d.ts +8 -0
  24. package/dist/components/MSABlock.js +103 -0
  25. package/dist/components/MSABlock.js.map +1 -0
  26. package/dist/components/MSACanvas.d.ts +2 -2
  27. package/dist/components/MSACanvas.js +32 -133
  28. package/dist/components/MSACanvas.js.map +1 -0
  29. package/dist/components/MSAMouseoverCanvas.d.ts +6 -0
  30. package/dist/components/MSAMouseoverCanvas.js +52 -0
  31. package/dist/components/MSAMouseoverCanvas.js.map +1 -0
  32. package/dist/components/MSAView.d.ts +2 -2
  33. package/dist/components/MSAView.js +17 -62
  34. package/dist/components/MSAView.js.map +1 -0
  35. package/dist/components/MultiAlignmentSelector.d.ts +6 -0
  36. package/dist/components/MultiAlignmentSelector.js +13 -0
  37. package/dist/components/MultiAlignmentSelector.js.map +1 -0
  38. package/dist/components/ResizeHandles.d.ts +5 -5
  39. package/dist/components/ResizeHandles.js +31 -32
  40. package/dist/components/ResizeHandles.js.map +1 -0
  41. package/dist/components/Rubberband.d.ts +3 -2
  42. package/dist/components/Rubberband.js +41 -64
  43. package/dist/components/Rubberband.js.map +1 -0
  44. package/dist/components/Ruler.d.ts +2 -16
  45. package/dist/components/Ruler.js +19 -88
  46. package/dist/components/Ruler.js.map +1 -0
  47. package/dist/components/TextTrack.d.ts +5 -5
  48. package/dist/components/TextTrack.js +23 -24
  49. package/dist/components/TextTrack.js.map +1 -0
  50. package/dist/components/Track.d.ts +5 -5
  51. package/dist/components/Track.js +41 -40
  52. package/dist/components/Track.js.map +1 -0
  53. package/dist/components/TreeBranchMenu.d.ts +14 -0
  54. package/dist/components/TreeBranchMenu.js +26 -0
  55. package/dist/components/TreeBranchMenu.js.map +1 -0
  56. package/dist/components/TreeCanvas.d.ts +2 -2
  57. package/dist/components/TreeCanvas.js +24 -356
  58. package/dist/components/TreeCanvas.js.map +1 -0
  59. package/dist/components/TreeCanvasBlock.d.ts +7 -0
  60. package/dist/components/TreeCanvasBlock.js +252 -0
  61. package/dist/components/TreeCanvasBlock.js.map +1 -0
  62. package/dist/components/TreeMenu.d.ts +12 -0
  63. package/dist/components/TreeMenu.js +56 -0
  64. package/dist/components/TreeMenu.js.map +1 -0
  65. package/dist/components/TreeRuler.d.ts +2 -2
  66. package/dist/components/TreeRuler.js +3 -3
  67. package/dist/components/TreeRuler.js.map +1 -0
  68. package/dist/components/VerticalGuide.d.ts +7 -0
  69. package/dist/components/VerticalGuide.js +30 -0
  70. package/dist/components/VerticalGuide.js.map +1 -0
  71. package/dist/components/ZoomControls.d.ts +6 -0
  72. package/dist/components/ZoomControls.js +58 -0
  73. package/dist/components/ZoomControls.js.map +1 -0
  74. package/dist/components/data/seq2.d.ts +3 -3
  75. package/dist/components/data/seq2.js +33 -3
  76. package/dist/components/data/seq2.js.map +1 -0
  77. package/dist/components/dialogs/AboutDlg.d.ts +4 -0
  78. package/dist/components/dialogs/AboutDlg.js +40 -0
  79. package/dist/components/dialogs/AboutDlg.js.map +1 -0
  80. package/{bundle/components → dist/components/dialogs}/AddTrackDlg.d.ts +3 -3
  81. package/dist/components/dialogs/AddTrackDlg.js +26 -0
  82. package/dist/components/dialogs/AddTrackDlg.js.map +1 -0
  83. package/dist/components/{AnnotationDlg.d.ts → dialogs/AnnotationDlg.d.ts} +3 -3
  84. package/dist/components/dialogs/AnnotationDlg.js +65 -0
  85. package/dist/components/dialogs/AnnotationDlg.js.map +1 -0
  86. package/dist/components/dialogs/DetailsDlg.d.ts +7 -0
  87. package/dist/components/dialogs/DetailsDlg.js +13 -0
  88. package/dist/components/dialogs/DetailsDlg.js.map +1 -0
  89. package/dist/components/dialogs/MoreInfoDlg.d.ts +6 -0
  90. package/dist/components/dialogs/MoreInfoDlg.js +11 -0
  91. package/dist/components/dialogs/MoreInfoDlg.js.map +1 -0
  92. package/dist/components/dialogs/SettingsDlg.d.ts +7 -0
  93. package/dist/components/dialogs/SettingsDlg.js +48 -0
  94. package/dist/components/dialogs/SettingsDlg.js.map +1 -0
  95. package/dist/components/dialogs/TrackInfoDlg.d.ts +9 -0
  96. package/{bundle/components → dist/components/dialogs}/TrackInfoDlg.js +12 -13
  97. package/dist/components/dialogs/TrackInfoDlg.js.map +1 -0
  98. package/dist/components/dialogs/TracklistDlg.d.ts +7 -0
  99. package/dist/components/dialogs/TracklistDlg.js +18 -0
  100. package/dist/components/dialogs/TracklistDlg.js.map +1 -0
  101. package/{bundle/components/Ruler.d.ts → dist/components/util.d.ts} +1 -6
  102. package/dist/components/util.js +68 -0
  103. package/dist/components/util.js.map +1 -0
  104. package/dist/index.d.ts +2 -4
  105. package/dist/index.js +3 -3
  106. package/dist/index.js.map +1 -0
  107. package/dist/layout.js +14 -20
  108. package/dist/layout.js.map +1 -0
  109. package/dist/model.d.ts +114 -97
  110. package/dist/model.js +248 -486
  111. package/dist/model.js.map +1 -0
  112. package/dist/parseNewick.d.ts +1 -5
  113. package/dist/parseNewick.js +11 -8
  114. package/dist/parseNewick.js.map +1 -0
  115. package/dist/parsers/ClustalMSA.d.ts +6 -18
  116. package/dist/parsers/ClustalMSA.js +55 -64
  117. package/dist/parsers/ClustalMSA.js.map +1 -0
  118. package/dist/parsers/FastaMSA.d.ts +5 -12
  119. package/dist/parsers/FastaMSA.js +55 -64
  120. package/dist/parsers/FastaMSA.js.map +1 -0
  121. package/dist/parsers/StockholmMSA.d.ts +10 -17
  122. package/dist/parsers/StockholmMSA.js +81 -110
  123. package/dist/parsers/StockholmMSA.js.map +1 -0
  124. package/dist/util.d.ts +34 -7
  125. package/dist/util.js +76 -24
  126. package/dist/util.js.map +1 -0
  127. package/dist/version.d.ts +1 -0
  128. package/dist/version.js +2 -0
  129. package/dist/version.js.map +1 -0
  130. package/package.json +34 -34
  131. package/src/StructureModel.ts +11 -0
  132. package/src/UniprotTrack.ts +59 -0
  133. package/src/colorSchemes.ts +520 -0
  134. package/src/components/BoxTrack.tsx +33 -0
  135. package/src/components/BoxTrackBlock.tsx +198 -0
  136. package/src/components/Header.tsx +106 -0
  137. package/src/components/ImportForm.tsx +192 -0
  138. package/src/components/MSABlock.tsx +164 -0
  139. package/src/components/MSACanvas.tsx +142 -0
  140. package/src/components/MSAMouseoverCanvas.tsx +87 -0
  141. package/src/components/MSAView.tsx +88 -0
  142. package/src/components/MultiAlignmentSelector.tsx +33 -0
  143. package/src/components/ResizeHandles.tsx +137 -0
  144. package/src/components/Rubberband.tsx +270 -0
  145. package/src/components/Ruler.tsx +123 -0
  146. package/src/components/TextTrack.tsx +120 -0
  147. package/src/components/Track.tsx +153 -0
  148. package/src/components/TreeBranchMenu.tsx +67 -0
  149. package/src/components/TreeCanvas.tsx +128 -0
  150. package/src/components/TreeCanvasBlock.tsx +359 -0
  151. package/src/components/TreeMenu.tsx +105 -0
  152. package/src/components/TreeRuler.tsx +12 -0
  153. package/src/components/VerticalGuide.tsx +50 -0
  154. package/src/components/ZoomControls.tsx +78 -0
  155. package/src/components/data/seq2.ts +35 -0
  156. package/src/components/dialogs/AboutDlg.tsx +58 -0
  157. package/src/components/dialogs/AddTrackDlg.tsx +74 -0
  158. package/src/components/dialogs/AnnotationDlg.tsx +144 -0
  159. package/src/components/dialogs/DetailsDlg.tsx +28 -0
  160. package/src/components/dialogs/MoreInfoDlg.tsx +21 -0
  161. package/src/components/dialogs/SettingsDlg.tsx +154 -0
  162. package/src/components/dialogs/TrackInfoDlg.tsx +59 -0
  163. package/src/components/dialogs/TracklistDlg.tsx +59 -0
  164. package/src/components/util.ts +93 -0
  165. package/src/declare.d.ts +1 -0
  166. package/src/index.ts +2 -0
  167. package/src/layout.ts +83 -0
  168. package/src/model.ts +793 -0
  169. package/{bundle/parseNewick.d.ts → src/parseNewick.ts} +35 -5
  170. package/src/parsers/ClustalMSA.ts +79 -0
  171. package/src/parsers/FastaMSA.ts +82 -0
  172. package/src/parsers/StockholmMSA.ts +137 -0
  173. package/src/util.ts +142 -0
  174. package/src/version.ts +1 -0
  175. package/bundle/colorSchemes.d.ts +0 -16
  176. package/bundle/colorSchemes.js +0 -455
  177. package/bundle/components/AboutDlg.d.ts +0 -5
  178. package/bundle/components/AboutDlg.js +0 -47
  179. package/bundle/components/AddTrackDlg.js +0 -26
  180. package/bundle/components/AnnotationDlg.d.ts +0 -11
  181. package/bundle/components/AnnotationDlg.js +0 -77
  182. package/bundle/components/BoxTrack.d.ts +0 -7
  183. package/bundle/components/BoxTrack.js +0 -143
  184. package/bundle/components/DetailsDlg.d.ts +0 -8
  185. package/bundle/components/DetailsDlg.js +0 -12
  186. package/bundle/components/Header.d.ts +0 -6
  187. package/bundle/components/Header.js +0 -63
  188. package/bundle/components/ImportForm.d.ts +0 -6
  189. package/bundle/components/ImportForm.js +0 -89
  190. package/bundle/components/MSACanvas.d.ts +0 -6
  191. package/bundle/components/MSACanvas.js +0 -210
  192. package/bundle/components/MSAView.d.ts +0 -6
  193. package/bundle/components/MSAView.js +0 -88
  194. package/bundle/components/MoreInfoDlg.d.ts +0 -6
  195. package/bundle/components/MoreInfoDlg.js +0 -11
  196. package/bundle/components/ResizeHandles.d.ts +0 -8
  197. package/bundle/components/ResizeHandles.js +0 -110
  198. package/bundle/components/Rubberband.d.ts +0 -7
  199. package/bundle/components/Rubberband.js +0 -196
  200. package/bundle/components/Ruler.js +0 -121
  201. package/bundle/components/SettingsDlg.d.ts +0 -8
  202. package/bundle/components/SettingsDlg.js +0 -40
  203. package/bundle/components/TextTrack.d.ts +0 -7
  204. package/bundle/components/TextTrack.js +0 -72
  205. package/bundle/components/Track.d.ts +0 -11
  206. package/bundle/components/Track.js +0 -81
  207. package/bundle/components/TrackInfoDlg.d.ts +0 -6
  208. package/bundle/components/TracklistDlg.d.ts +0 -8
  209. package/bundle/components/TracklistDlg.js +0 -18
  210. package/bundle/components/TreeCanvas.d.ts +0 -6
  211. package/bundle/components/TreeCanvas.js +0 -431
  212. package/bundle/components/TreeRuler.d.ts +0 -6
  213. package/bundle/components/TreeRuler.js +0 -8
  214. package/bundle/components/data/seq2.d.ts +0 -3
  215. package/bundle/components/data/seq2.js +0 -3
  216. package/bundle/index.d.ts +0 -4
  217. package/bundle/layout.d.ts +0 -23
  218. package/bundle/layout.js +0 -53
  219. package/bundle/model.d.ts +0 -364
  220. package/bundle/model.js +0 -894
  221. package/bundle/parseNewick.js +0 -94
  222. package/bundle/parsers/ClustalMSA.d.ts +0 -39
  223. package/bundle/parsers/ClustalMSA.js +0 -77
  224. package/bundle/parsers/FastaMSA.d.ts +0 -26
  225. package/bundle/parsers/FastaMSA.js +0 -78
  226. package/bundle/parsers/StockholmMSA.d.ts +0 -75
  227. package/bundle/parsers/StockholmMSA.js +0 -142
  228. package/bundle/util.d.ts +0 -17
  229. package/bundle/util.js +0 -33
  230. package/dist/components/AboutDlg.d.ts +0 -5
  231. package/dist/components/AboutDlg.js +0 -50
  232. package/dist/components/AddTrackDlg.d.ts +0 -8
  233. package/dist/components/AddTrackDlg.js +0 -26
  234. package/dist/components/AnnotationDlg.js +0 -77
  235. package/dist/components/DetailsDlg.d.ts +0 -8
  236. package/dist/components/DetailsDlg.js +0 -12
  237. package/dist/components/MoreInfoDlg.d.ts +0 -6
  238. package/dist/components/MoreInfoDlg.js +0 -11
  239. package/dist/components/SettingsDlg.d.ts +0 -8
  240. package/dist/components/SettingsDlg.js +0 -40
  241. package/dist/components/TrackInfoDlg.d.ts +0 -6
  242. package/dist/components/TrackInfoDlg.js +0 -33
  243. package/dist/components/TracklistDlg.d.ts +0 -8
  244. package/dist/components/TracklistDlg.js +0 -18
  245. package/dist/components/package.json +0 -62
@@ -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
@@ -0,0 +1,128 @@
1
+ import React, { useEffect, useRef, useState } from 'react'
2
+ import normalizeWheel from 'normalize-wheel'
3
+ import { observer } from 'mobx-react'
4
+
5
+ // locals
6
+ import { MsaViewModel } from '../model'
7
+ import TreeCanvasBlock from './TreeCanvasBlock'
8
+
9
+ const padding = 600
10
+
11
+ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
12
+ const ref = useRef<HTMLDivElement>(null)
13
+ const scheduled = useRef(false)
14
+ const deltaY = useRef(0)
15
+ const prevY = useRef<number>(0)
16
+ const { treeWidth, height, blocksY } = model
17
+ const [mouseDragging, setMouseDragging] = useState(false)
18
+
19
+ useEffect(() => {
20
+ const curr = ref.current
21
+ if (!curr) {
22
+ return
23
+ }
24
+ function onWheel(origEvent: WheelEvent) {
25
+ const event = normalizeWheel(origEvent)
26
+ deltaY.current += event.pixelY
27
+
28
+ if (!scheduled.current) {
29
+ scheduled.current = true
30
+ requestAnimationFrame(() => {
31
+ model.doScrollY(-deltaY.current)
32
+ deltaY.current = 0
33
+ scheduled.current = false
34
+ })
35
+ }
36
+ origEvent.preventDefault()
37
+ }
38
+ curr.addEventListener('wheel', onWheel)
39
+ return () => {
40
+ curr.removeEventListener('wheel', onWheel)
41
+ }
42
+ }, [model])
43
+
44
+ useEffect(() => {
45
+ let cleanup = () => {}
46
+
47
+ function globalMouseMove(event: MouseEvent) {
48
+ event.preventDefault()
49
+ const currY = event.clientY
50
+ const distanceY = currY - prevY.current
51
+ if (distanceY) {
52
+ // use rAF to make it so multiple event handlers aren't fired per-frame
53
+ // see https://calendar.perfplanet.com/2013/the-runtime-performance-checklist/
54
+ if (!scheduled.current) {
55
+ scheduled.current = true
56
+ window.requestAnimationFrame(() => {
57
+ model.doScrollY(distanceY)
58
+ scheduled.current = false
59
+ prevY.current = event.clientY
60
+ })
61
+ }
62
+ }
63
+ }
64
+
65
+ function globalMouseUp() {
66
+ prevY.current = 0
67
+ if (mouseDragging) {
68
+ setMouseDragging(false)
69
+ }
70
+ }
71
+
72
+ if (mouseDragging) {
73
+ window.addEventListener('mousemove', globalMouseMove, true)
74
+ window.addEventListener('mouseup', globalMouseUp, true)
75
+ cleanup = () => {
76
+ window.removeEventListener('mousemove', globalMouseMove, true)
77
+ window.removeEventListener('mouseup', globalMouseUp, true)
78
+ }
79
+ }
80
+ return cleanup
81
+ }, [model, mouseDragging])
82
+
83
+ function mouseDown(event: React.MouseEvent) {
84
+ // check if clicking a draggable element or a resize handle
85
+ const target = event.target as HTMLElement
86
+ if (target.draggable || target.dataset.resizer) {
87
+ return
88
+ }
89
+
90
+ // otherwise do click and drag scroll
91
+ if (event.button === 0) {
92
+ prevY.current = event.clientY
93
+ setMouseDragging(true)
94
+ }
95
+ }
96
+
97
+ // this local mouseup is used in addition to the global because sometimes
98
+ // the global add/remove are not called in time, resulting in issue #533
99
+ function mouseUp(event: React.MouseEvent) {
100
+ event.preventDefault()
101
+ setMouseDragging(false)
102
+ }
103
+
104
+ function mouseLeave(event: React.MouseEvent) {
105
+ event.preventDefault()
106
+ }
107
+
108
+ return (
109
+ <div
110
+ ref={ref}
111
+ onMouseDown={mouseDown}
112
+ onMouseUp={mouseUp}
113
+ onMouseLeave={mouseLeave}
114
+ style={{
115
+ height,
116
+ position: 'relative',
117
+ overflow: 'hidden',
118
+ width: treeWidth + padding,
119
+ }}
120
+ >
121
+ {blocksY.map(block => (
122
+ <TreeCanvasBlock key={block} model={model} offsetY={block} />
123
+ ))}
124
+ </div>
125
+ )
126
+ })
127
+
128
+ export default TreeCanvas
@@ -0,0 +1,359 @@
1
+ import React, { useEffect, useRef, useState } from 'react'
2
+ import { observer } from 'mobx-react'
3
+ import RBush from 'rbush'
4
+
5
+ // locals
6
+ import { MsaViewModel } from '../model'
7
+ import TreeMenu from './TreeMenu'
8
+ import TreeBranchMenu from './TreeBranchMenu'
9
+
10
+ const extendBounds = 5
11
+ const radius = 3.5
12
+ const d = radius * 2
13
+
14
+ const padding = 600
15
+
16
+ interface TooltipData {
17
+ name: string
18
+ id: string
19
+ x: number
20
+ y: number
21
+ }
22
+
23
+ interface ClickEntry {
24
+ name: string
25
+ id: string
26
+ branch?: boolean
27
+ minX: number
28
+ maxX: number
29
+ minY: number
30
+ maxY: number
31
+ }
32
+
33
+ const TreeCanvasBlock = observer(function ({
34
+ model,
35
+ offsetY,
36
+ }: {
37
+ model: MsaViewModel
38
+ offsetY: number
39
+ }) {
40
+ const ref = useRef<HTMLCanvasElement>(null)
41
+ const clickMap = useRef(new RBush<ClickEntry>())
42
+ const mouseoverRef = useRef<HTMLCanvasElement>(null)
43
+ const [branchMenu, setBranchMenu] = useState<TooltipData>()
44
+ const [toggleNodeMenu, setToggleNodeMenu] = useState<TooltipData>()
45
+ const [hoverElt, setHoverElt] = useState<ClickEntry>()
46
+
47
+ const {
48
+ hierarchy,
49
+ rowHeight,
50
+ scrollY,
51
+ treeWidth,
52
+ showBranchLen,
53
+ collapsed,
54
+ margin,
55
+ labelsAlignRight,
56
+ noTree,
57
+ blockSize,
58
+ drawNodeBubbles,
59
+ drawTree,
60
+ treeAreaWidth,
61
+ structures,
62
+ highResScaleFactor,
63
+ } = model
64
+
65
+ useEffect(() => {
66
+ clickMap.current.clear()
67
+
68
+ if (!ref.current) {
69
+ return
70
+ }
71
+ const ctx = ref.current.getContext('2d')
72
+ if (!ctx) {
73
+ return
74
+ }
75
+
76
+ ctx.resetTransform()
77
+ ctx.scale(highResScaleFactor, highResScaleFactor)
78
+ ctx.clearRect(0, 0, treeWidth + padding, blockSize)
79
+ ctx.translate(margin.left, -offsetY)
80
+
81
+ const font = ctx.font
82
+ ctx.font = font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
83
+
84
+ if (!noTree && drawTree) {
85
+ for (const { source, target } of hierarchy.links()) {
86
+ const y = showBranchLen ? 'len' : 'y'
87
+ // @ts-expect-error
88
+ const { x: sy, [y]: sx } = source
89
+ // @ts-expect-error
90
+ const { x: ty, [y]: tx } = target
91
+
92
+ const y1 = Math.min(sy, ty)
93
+ const y2 = Math.max(sy, ty)
94
+ // 1d line intersection to check if line crosses block at all, this is
95
+ // an optimization that allows us to skip drawing most tree links
96
+ // outside the block
97
+ if (offsetY + blockSize >= y1 && y2 >= offsetY) {
98
+ ctx.beginPath()
99
+ ctx.moveTo(sx, sy)
100
+ ctx.lineTo(sx, ty)
101
+ ctx.lineTo(tx, ty)
102
+ ctx.stroke()
103
+ }
104
+ }
105
+
106
+ if (drawNodeBubbles) {
107
+ for (const node of hierarchy.descendants()) {
108
+ const val = showBranchLen ? 'len' : 'y'
109
+ const {
110
+ // @ts-expect-error
111
+ x: y,
112
+ // @ts-expect-error
113
+ [val]: x,
114
+ data,
115
+ } = node
116
+ const { id = '', name = '' } = data
117
+
118
+ if (
119
+ y > offsetY - extendBounds &&
120
+ y < offsetY + blockSize + extendBounds
121
+ ) {
122
+ ctx.strokeStyle = 'black'
123
+ ctx.fillStyle = collapsed.includes(id) ? 'black' : 'white'
124
+ ctx.beginPath()
125
+ ctx.arc(x, y, radius, 0, 2 * Math.PI)
126
+ ctx.fill()
127
+ ctx.stroke()
128
+
129
+ clickMap.current.insert({
130
+ minX: x - radius,
131
+ maxX: x - radius + d,
132
+ minY: y - radius,
133
+ maxY: y - radius + d,
134
+ branch: true,
135
+ id,
136
+ name,
137
+ })
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ if (rowHeight >= 5) {
144
+ if (labelsAlignRight) {
145
+ ctx.textAlign = 'right'
146
+ ctx.setLineDash([1, 3])
147
+ } else {
148
+ ctx.textAlign = 'start'
149
+ }
150
+ for (const node of hierarchy.leaves()) {
151
+ const {
152
+ // @ts-expect-error
153
+ x: y,
154
+ // @ts-expect-error
155
+ y: x,
156
+ data: { name, id },
157
+ // @ts-expect-error
158
+ len,
159
+ } = node
160
+
161
+ if (
162
+ y > offsetY - extendBounds &&
163
+ y < offsetY + blockSize + extendBounds
164
+ ) {
165
+ // note: +rowHeight/4 matches with -rowHeight/4 in msa
166
+ const yp = y + rowHeight / 4
167
+ const xp = showBranchLen ? len : x
168
+
169
+ const { width } = ctx.measureText(name)
170
+ const height = ctx.measureText('M').width // use an 'em' for height
171
+
172
+ const hasStructure = structures[name]
173
+ ctx.fillStyle = hasStructure ? 'blue' : 'black'
174
+
175
+ if (!drawTree && !labelsAlignRight) {
176
+ ctx.fillText(name, 0, yp)
177
+ clickMap.current.insert({
178
+ minX: 0,
179
+ maxX: width,
180
+ minY: yp - height,
181
+ maxY: yp,
182
+ name,
183
+ id,
184
+ })
185
+ } else if (labelsAlignRight) {
186
+ const smallPadding = 2
187
+ const offset = treeAreaWidth - smallPadding - margin.left
188
+ if (drawTree && !noTree) {
189
+ const { width } = ctx.measureText(name)
190
+ ctx.moveTo(xp + radius + 2, y)
191
+ ctx.lineTo(offset - smallPadding - width, y)
192
+ ctx.stroke()
193
+ }
194
+ ctx.fillText(name, offset, yp)
195
+ clickMap.current.insert({
196
+ minX: treeAreaWidth - margin.left - width,
197
+ maxX: treeAreaWidth - margin.left,
198
+ minY: yp - height,
199
+ maxY: yp,
200
+ name,
201
+ id,
202
+ })
203
+ } else {
204
+ ctx.fillText(name, xp + d, yp)
205
+ clickMap.current.insert({
206
+ minX: xp + d,
207
+ maxX: xp + d + width,
208
+ minY: yp - height,
209
+ maxY: yp,
210
+ name,
211
+ id,
212
+ })
213
+ }
214
+ }
215
+ }
216
+ ctx.setLineDash([])
217
+ }
218
+ }, [
219
+ collapsed,
220
+ rowHeight,
221
+ margin.left,
222
+ hierarchy,
223
+ offsetY,
224
+ treeWidth,
225
+ showBranchLen,
226
+ noTree,
227
+ blockSize,
228
+ drawNodeBubbles,
229
+ drawTree,
230
+ labelsAlignRight,
231
+ treeAreaWidth,
232
+ structures,
233
+ highResScaleFactor,
234
+ ])
235
+
236
+ useEffect(() => {
237
+ const canvas = mouseoverRef.current
238
+ if (!canvas) {
239
+ return
240
+ }
241
+ const ctx = canvas.getContext('2d')
242
+ if (!ctx) {
243
+ return
244
+ }
245
+
246
+ ctx.resetTransform()
247
+ ctx.clearRect(0, 0, treeWidth + padding, blockSize)
248
+ ctx.translate(margin.left, -offsetY)
249
+
250
+ if (hoverElt) {
251
+ const { minX, maxX, minY, maxY } = hoverElt
252
+
253
+ ctx.fillStyle = 'rgba(0,0,0,0.1)'
254
+ ctx.fillRect(minX, minY, maxX - minX, maxY - minY)
255
+ }
256
+ }, [hoverElt, margin.left, offsetY, blockSize, treeWidth])
257
+
258
+ function hoverBranchClickMap(event: React.MouseEvent) {
259
+ const x = event.nativeEvent.offsetX - margin.left
260
+ const y = event.nativeEvent.offsetY
261
+
262
+ const [entry] = clickMap.current.search({
263
+ minX: x,
264
+ maxX: x + 1,
265
+ minY: y + offsetY,
266
+ maxY: y + 1 + offsetY,
267
+ })
268
+
269
+ return entry && entry.branch
270
+ ? { ...entry, x: event.clientX, y: event.clientY }
271
+ : undefined
272
+ }
273
+
274
+ function hoverNameClickMap(event: React.MouseEvent) {
275
+ const x = event.nativeEvent.offsetX - margin.left
276
+ const y = event.nativeEvent.offsetY
277
+ const [entry] = clickMap.current.search({
278
+ minX: x,
279
+ maxX: x + 1,
280
+ minY: y + offsetY,
281
+ maxY: y + 1 + offsetY,
282
+ })
283
+
284
+ return entry && !entry.branch
285
+ ? { ...entry, x: event.clientX, y: event.clientY }
286
+ : undefined
287
+ }
288
+
289
+ return (
290
+ <>
291
+ {branchMenu?.id ? (
292
+ <TreeBranchMenu
293
+ node={branchMenu}
294
+ model={model}
295
+ onClose={() => setBranchMenu(undefined)}
296
+ />
297
+ ) : null}
298
+
299
+ {toggleNodeMenu?.id ? (
300
+ <TreeMenu
301
+ node={toggleNodeMenu}
302
+ model={model}
303
+ onClose={() => setToggleNodeMenu(undefined)}
304
+ />
305
+ ) : null}
306
+
307
+ <canvas
308
+ width={(treeWidth + padding) * highResScaleFactor}
309
+ height={blockSize * highResScaleFactor}
310
+ style={{
311
+ width: treeWidth + padding,
312
+ height: blockSize,
313
+ top: scrollY + offsetY,
314
+ left: 0,
315
+ position: 'absolute',
316
+ }}
317
+ onMouseMove={event => {
318
+ if (!ref.current) {
319
+ return
320
+ }
321
+
322
+ const ret = hoverNameClickMap(event) || hoverBranchClickMap(event)
323
+ ref.current.style.cursor = ret ? 'pointer' : 'default'
324
+ setHoverElt(hoverNameClickMap(event))
325
+ }}
326
+ onClick={event => {
327
+ const { clientX: x, clientY: y } = event
328
+
329
+ const data = hoverBranchClickMap(event)
330
+ if (data?.id) {
331
+ setBranchMenu({ ...data, x, y })
332
+ }
333
+
334
+ const data2 = hoverNameClickMap(event)
335
+ if (data2?.id) {
336
+ setToggleNodeMenu({ ...data2, x, y })
337
+ }
338
+ }}
339
+ ref={ref}
340
+ />
341
+ <canvas
342
+ style={{
343
+ width: treeWidth + padding,
344
+ height: blockSize,
345
+ top: scrollY + offsetY,
346
+ left: 0,
347
+ position: 'absolute',
348
+ pointerEvents: 'none',
349
+ zIndex: 100,
350
+ }}
351
+ width={treeWidth + padding}
352
+ height={blockSize}
353
+ ref={mouseoverRef}
354
+ />
355
+ </>
356
+ )
357
+ })
358
+
359
+ export default TreeCanvasBlock
@@ -0,0 +1,105 @@
1
+ import React, { lazy } 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
+ const MoreInfoDlg = lazy(() => import('./dialogs/MoreInfoDlg'))
9
+
10
+ const TreeMenu = observer(function ({
11
+ node,
12
+ onClose,
13
+ model,
14
+ }: {
15
+ node: { x: number; y: number; name: string }
16
+ model: MsaViewModel
17
+ onClose: () => void
18
+ }) {
19
+ const { structures } = model
20
+ const nodeDetails = node ? model.getRowData(node.name) : undefined
21
+
22
+ return (
23
+ <>
24
+ <Menu
25
+ anchorReference="anchorPosition"
26
+ anchorPosition={{
27
+ top: node.y,
28
+ left: node.x,
29
+ }}
30
+ transitionDuration={0}
31
+ keepMounted
32
+ open={Boolean(node)}
33
+ onClose={onClose}
34
+ >
35
+ <MenuItem dense disabled>
36
+ {node.name}
37
+ </MenuItem>
38
+
39
+ <MenuItem
40
+ dense
41
+ onClick={() => {
42
+ model.setDialogComponent(MoreInfoDlg, {
43
+ info: model.getRowData(node.name),
44
+ })
45
+ onClose()
46
+ }}
47
+ >
48
+ More info...
49
+ </MenuItem>
50
+
51
+ {structures[node.name]?.map(entry => {
52
+ return !model.selectedStructures.some(n => n.id === node.name) ? (
53
+ <MenuItem
54
+ key={JSON.stringify(entry)}
55
+ dense
56
+ onClick={() => {
57
+ model.addStructureToSelection({
58
+ structure: entry,
59
+ id: node.name,
60
+ })
61
+ onClose()
62
+ }}
63
+ >
64
+ Add PDB to selection ({entry.pdb})
65
+ </MenuItem>
66
+ ) : (
67
+ <MenuItem
68
+ key={JSON.stringify(entry)}
69
+ dense
70
+ onClick={() => {
71
+ model.removeStructureFromSelection({
72
+ structure: entry,
73
+ id: node.name,
74
+ })
75
+ onClose()
76
+ }}
77
+ >
78
+ Remove PDB from selection ({entry.pdb})
79
+ </MenuItem>
80
+ )
81
+ })}
82
+
83
+ {// @ts-expect-error
84
+ nodeDetails?.data.accession?.map(accession => (
85
+ <MenuItem
86
+ dense
87
+ key={accession}
88
+ onClick={() => {
89
+ model.addUniprotTrack({
90
+ // @ts-expect-error
91
+ name: nodeDetails?.data.name,
92
+ accession,
93
+ })
94
+ onClose()
95
+ }}
96
+ >
97
+ Open UniProt track ({accession})
98
+ </MenuItem>
99
+ ))}
100
+ </Menu>
101
+ </>
102
+ )
103
+ })
104
+
105
+ export default TreeMenu
@@ -0,0 +1,12 @@
1
+ import React from 'react'
2
+ import { observer } from 'mobx-react'
3
+
4
+ // locals
5
+ import { MsaViewModel } from '../model'
6
+
7
+ const TreeRuler = observer(({ model }: { model: MsaViewModel }) => {
8
+ const { treeWidth } = model
9
+ return <div style={{ width: treeWidth }} />
10
+ })
11
+
12
+ export default TreeRuler
@@ -0,0 +1,50 @@
1
+ import React from 'react'
2
+
3
+ import { observer } from 'mobx-react'
4
+ import { makeStyles } from 'tss-react/mui'
5
+ import { Tooltip } from '@mui/material'
6
+
7
+ // icons
8
+ import { MsaViewModel } from '../model'
9
+
10
+ const useStyles = makeStyles()({
11
+ guide: {
12
+ pointerEvents: 'none',
13
+ height: '100%',
14
+ width: 1,
15
+ position: 'absolute',
16
+ zIndex: 10,
17
+ },
18
+ })
19
+
20
+ const VerticalGuide = observer(function ({
21
+ model,
22
+ coordX,
23
+ }: {
24
+ model: MsaViewModel
25
+ coordX: number
26
+ }) {
27
+ const { treeAreaWidth } = model
28
+ const { classes } = useStyles()
29
+ return (
30
+ <>
31
+ <Tooltip open placement="top" title={`${model.pxToBp(coordX) + 1}`} arrow>
32
+ <div
33
+ style={{
34
+ left: coordX + treeAreaWidth,
35
+ position: 'absolute',
36
+ height: 1,
37
+ }}
38
+ />
39
+ </Tooltip>
40
+ <div
41
+ className={classes.guide}
42
+ style={{
43
+ left: coordX + treeAreaWidth,
44
+ background: 'red',
45
+ }}
46
+ />
47
+ </>
48
+ )
49
+ })
50
+ export default VerticalGuide