react-msaview 2.1.5 → 3.0.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 (176) hide show
  1. package/bundle/index.js +37 -255
  2. package/dist/DialogQueue.d.ts +25 -0
  3. package/dist/DialogQueue.js +46 -0
  4. package/dist/DialogQueue.js.map +1 -0
  5. package/dist/UniprotTrack.js.map +1 -1
  6. package/dist/colorSchemes.js.map +1 -1
  7. package/dist/components/BoxTrackBlock.js +3 -2
  8. package/dist/components/BoxTrackBlock.js.map +1 -1
  9. package/dist/components/Header.js +10 -13
  10. package/dist/components/Header.js.map +1 -1
  11. package/dist/components/ImportForm/ImportFormExamples.d.ts +6 -0
  12. package/dist/components/ImportForm/ImportFormExamples.js +50 -0
  13. package/dist/components/ImportForm/ImportFormExamples.js.map +1 -0
  14. package/dist/components/ImportForm/data/seq2.js.map +1 -0
  15. package/dist/components/{ImportForm.d.ts → ImportForm/index.d.ts} +1 -1
  16. package/dist/components/ImportForm/index.js +31 -0
  17. package/dist/components/ImportForm/index.js.map +1 -0
  18. package/dist/components/ImportForm/util.d.ts +3 -0
  19. package/dist/components/ImportForm/util.js +15 -0
  20. package/dist/components/ImportForm/util.js.map +1 -0
  21. package/dist/components/{MSABlock.d.ts → MSAPanel/MSABlock.d.ts} +1 -1
  22. package/dist/components/MSAPanel/MSABlock.js +46 -0
  23. package/dist/components/MSAPanel/MSABlock.js.map +1 -0
  24. package/dist/components/{MSACanvas.d.ts → MSAPanel/MSACanvas.d.ts} +1 -1
  25. package/dist/components/{MSACanvas.js → MSAPanel/MSACanvas.js} +10 -3
  26. package/dist/components/MSAPanel/MSACanvas.js.map +1 -0
  27. package/dist/components/{MSAMouseoverCanvas.d.ts → MSAPanel/MSAMouseoverCanvas.d.ts} +1 -1
  28. package/dist/components/MSAPanel/MSAMouseoverCanvas.js +50 -0
  29. package/dist/components/MSAPanel/MSAMouseoverCanvas.js.map +1 -0
  30. package/dist/components/MSAPanel/index.d.ts +5 -0
  31. package/dist/components/MSAPanel/index.js +9 -0
  32. package/dist/components/MSAPanel/index.js.map +1 -0
  33. package/dist/components/MSAPanel/renderMSABlock.d.ts +8 -0
  34. package/dist/components/MSAPanel/renderMSABlock.js +81 -0
  35. package/dist/components/MSAPanel/renderMSABlock.js.map +1 -0
  36. package/dist/components/MSAView.d.ts +2 -2
  37. package/dist/components/MSAView.js +26 -31
  38. package/dist/components/MSAView.js.map +1 -1
  39. package/dist/components/Minimap.d.ts +6 -0
  40. package/dist/components/Minimap.js +72 -0
  41. package/dist/components/Minimap.js.map +1 -0
  42. package/dist/components/OverviewRubberband.d.ts +8 -0
  43. package/dist/components/OverviewRubberband.js +185 -0
  44. package/dist/components/OverviewRubberband.js.map +1 -0
  45. package/dist/components/ResizeHandles.js.map +1 -1
  46. package/dist/components/Rubberband.js +13 -1
  47. package/dist/components/Rubberband.js.map +1 -1
  48. package/dist/components/TextTrack.js +3 -2
  49. package/dist/components/TextTrack.js.map +1 -1
  50. package/dist/components/Track.js +5 -5
  51. package/dist/components/Track.js.map +1 -1
  52. package/dist/components/{TreeBranchMenu.d.ts → TreePanel/TreeBranchMenu.d.ts} +1 -1
  53. package/dist/components/TreePanel/TreeBranchMenu.js.map +1 -0
  54. package/dist/components/{TreeCanvas.d.ts → TreePanel/TreeCanvas.d.ts} +1 -1
  55. package/dist/components/{TreeCanvas.js → TreePanel/TreeCanvas.js} +1 -1
  56. package/dist/components/TreePanel/TreeCanvas.js.map +1 -0
  57. package/dist/components/{TreeCanvasBlock.d.ts → TreePanel/TreeCanvasBlock.d.ts} +1 -1
  58. package/dist/components/TreePanel/TreeCanvasBlock.js +117 -0
  59. package/dist/components/TreePanel/TreeCanvasBlock.js.map +1 -0
  60. package/dist/components/{TreeMenu.d.ts → TreePanel/TreeMenu.d.ts} +1 -1
  61. package/dist/components/{TreeMenu.js → TreePanel/TreeMenu.js} +9 -5
  62. package/dist/components/TreePanel/TreeMenu.js.map +1 -0
  63. package/dist/components/{TreeRuler.d.ts → TreePanel/TreeRuler.d.ts} +1 -1
  64. package/dist/components/TreePanel/TreeRuler.js +8 -0
  65. package/dist/components/TreePanel/TreeRuler.js.map +1 -0
  66. package/dist/components/{dialogs → TreePanel/dialogs}/TreeNodeInfoDlg.d.ts +1 -1
  67. package/dist/components/TreePanel/dialogs/TreeNodeInfoDlg.js.map +1 -0
  68. package/dist/components/TreePanel/index.d.ts +6 -0
  69. package/dist/components/TreePanel/index.js +10 -0
  70. package/dist/components/TreePanel/index.js.map +1 -0
  71. package/dist/components/TreePanel/renderTreeCanvas.d.ts +36 -0
  72. package/dist/components/TreePanel/renderTreeCanvas.js +153 -0
  73. package/dist/components/TreePanel/renderTreeCanvas.js.map +1 -0
  74. package/dist/components/dialogs/{AboutDlg.js → AboutDialog.js} +1 -1
  75. package/dist/components/dialogs/AboutDialog.js.map +1 -0
  76. package/dist/components/dialogs/{AddTrackDlg.js → AddTrackDialog.js} +1 -1
  77. package/dist/components/dialogs/AddTrackDialog.js.map +1 -0
  78. package/dist/components/dialogs/{AnnotationDlg.js → AnnotationDialog.js} +1 -1
  79. package/dist/components/dialogs/AnnotationDialog.js.map +1 -0
  80. package/dist/components/dialogs/{MetadataDlg.js → MetadataDialog.js} +1 -1
  81. package/dist/components/dialogs/MetadataDialog.js.map +1 -0
  82. package/dist/components/dialogs/SettingsDialog.js +54 -0
  83. package/dist/components/dialogs/SettingsDialog.js.map +1 -0
  84. package/dist/components/dialogs/{TrackInfoDlg.js → TrackInfoDialog.js} +1 -1
  85. package/dist/components/dialogs/TrackInfoDialog.js.map +1 -0
  86. package/dist/components/dialogs/{TracklistDlg.js → TracklistDialog.js} +1 -1
  87. package/dist/components/dialogs/TracklistDialog.js.map +1 -0
  88. package/dist/components/util.js.map +1 -1
  89. package/dist/layout.js.map +1 -1
  90. package/dist/model.d.ts +80 -47
  91. package/dist/model.js +130 -70
  92. package/dist/model.js.map +1 -1
  93. package/dist/parseNewick.js.map +1 -1
  94. package/dist/parsers/StockholmMSA.js.map +1 -1
  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/package.json +6 -4
  99. package/src/DialogQueue.ts +47 -0
  100. package/src/components/BoxTrackBlock.tsx +3 -1
  101. package/src/components/Header.tsx +9 -10
  102. package/src/components/ImportForm/ImportFormExamples.tsx +133 -0
  103. package/src/components/ImportForm/index.tsx +63 -0
  104. package/src/components/ImportForm/util.ts +20 -0
  105. package/src/components/MSAPanel/MSABlock.tsx +81 -0
  106. package/src/components/{MSACanvas.tsx → MSAPanel/MSACanvas.tsx} +17 -5
  107. package/src/components/MSAPanel/MSAMouseoverCanvas.tsx +92 -0
  108. package/src/components/MSAPanel/index.tsx +13 -0
  109. package/src/components/MSAPanel/renderMSABlock.ts +160 -0
  110. package/src/components/MSAView.tsx +36 -56
  111. package/src/components/Minimap.tsx +102 -0
  112. package/src/components/OverviewRubberband.tsx +283 -0
  113. package/src/components/Rubberband.tsx +14 -1
  114. package/src/components/TextTrack.tsx +3 -1
  115. package/src/components/Track.tsx +5 -5
  116. package/src/components/{TreeBranchMenu.tsx → TreePanel/TreeBranchMenu.tsx} +1 -1
  117. package/src/components/{TreeCanvas.tsx → TreePanel/TreeCanvas.tsx} +2 -3
  118. package/src/components/TreePanel/TreeCanvasBlock.tsx +190 -0
  119. package/src/components/{TreeMenu.tsx → TreePanel/TreeMenu.tsx} +10 -6
  120. package/src/components/{TreeRuler.tsx → TreePanel/TreeRuler.tsx} +3 -3
  121. package/src/components/{dialogs → TreePanel/dialogs}/TreeNodeInfoDlg.tsx +1 -1
  122. package/src/components/TreePanel/index.tsx +16 -0
  123. package/src/components/TreePanel/renderTreeCanvas.ts +245 -0
  124. package/src/components/dialogs/{SettingsDlg.tsx → SettingsDialog.tsx} +72 -47
  125. package/src/model.ts +157 -79
  126. package/src/version.ts +1 -1
  127. package/dist/components/ImportForm.js +0 -84
  128. package/dist/components/ImportForm.js.map +0 -1
  129. package/dist/components/MSABlock.js +0 -103
  130. package/dist/components/MSABlock.js.map +0 -1
  131. package/dist/components/MSACanvas.js.map +0 -1
  132. package/dist/components/MSAMouseoverCanvas.js +0 -61
  133. package/dist/components/MSAMouseoverCanvas.js.map +0 -1
  134. package/dist/components/Ruler.d.ts +0 -6
  135. package/dist/components/Ruler.js +0 -52
  136. package/dist/components/Ruler.js.map +0 -1
  137. package/dist/components/TreeBranchMenu.js.map +0 -1
  138. package/dist/components/TreeCanvas.js.map +0 -1
  139. package/dist/components/TreeCanvasBlock.js +0 -255
  140. package/dist/components/TreeCanvasBlock.js.map +0 -1
  141. package/dist/components/TreeMenu.js.map +0 -1
  142. package/dist/components/TreeRuler.js +0 -8
  143. package/dist/components/TreeRuler.js.map +0 -1
  144. package/dist/components/data/seq2.js.map +0 -1
  145. package/dist/components/dialogs/AboutDlg.js.map +0 -1
  146. package/dist/components/dialogs/AddTrackDlg.js.map +0 -1
  147. package/dist/components/dialogs/AnnotationDlg.js.map +0 -1
  148. package/dist/components/dialogs/MetadataDlg.js.map +0 -1
  149. package/dist/components/dialogs/SettingsDlg.js +0 -48
  150. package/dist/components/dialogs/SettingsDlg.js.map +0 -1
  151. package/dist/components/dialogs/TrackInfoDlg.js.map +0 -1
  152. package/dist/components/dialogs/TracklistDlg.js.map +0 -1
  153. package/dist/components/dialogs/TreeNodeInfoDlg.js.map +0 -1
  154. package/src/components/ImportForm.tsx +0 -192
  155. package/src/components/MSABlock.tsx +0 -164
  156. package/src/components/MSAMouseoverCanvas.tsx +0 -99
  157. package/src/components/Ruler.tsx +0 -123
  158. package/src/components/TreeCanvasBlock.tsx +0 -363
  159. /package/dist/components/{data → ImportForm/data}/seq2.d.ts +0 -0
  160. /package/dist/components/{data → ImportForm/data}/seq2.js +0 -0
  161. /package/dist/components/{TreeBranchMenu.js → TreePanel/TreeBranchMenu.js} +0 -0
  162. /package/dist/components/{dialogs → TreePanel/dialogs}/TreeNodeInfoDlg.js +0 -0
  163. /package/dist/components/dialogs/{AboutDlg.d.ts → AboutDialog.d.ts} +0 -0
  164. /package/dist/components/dialogs/{AddTrackDlg.d.ts → AddTrackDialog.d.ts} +0 -0
  165. /package/dist/components/dialogs/{AnnotationDlg.d.ts → AnnotationDialog.d.ts} +0 -0
  166. /package/dist/components/dialogs/{MetadataDlg.d.ts → MetadataDialog.d.ts} +0 -0
  167. /package/dist/components/dialogs/{SettingsDlg.d.ts → SettingsDialog.d.ts} +0 -0
  168. /package/dist/components/dialogs/{TrackInfoDlg.d.ts → TrackInfoDialog.d.ts} +0 -0
  169. /package/dist/components/dialogs/{TracklistDlg.d.ts → TracklistDialog.d.ts} +0 -0
  170. /package/src/components/{data → ImportForm/data}/seq2.ts +0 -0
  171. /package/src/components/dialogs/{AboutDlg.tsx → AboutDialog.tsx} +0 -0
  172. /package/src/components/dialogs/{AddTrackDlg.tsx → AddTrackDialog.tsx} +0 -0
  173. /package/src/components/dialogs/{AnnotationDlg.tsx → AnnotationDialog.tsx} +0 -0
  174. /package/src/components/dialogs/{MetadataDlg.tsx → MetadataDialog.tsx} +0 -0
  175. /package/src/components/dialogs/{TrackInfoDlg.tsx → TrackInfoDialog.tsx} +0 -0
  176. /package/src/components/dialogs/{TracklistDlg.tsx → TracklistDialog.tsx} +0 -0
@@ -0,0 +1,190 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react'
2
+ import { autorun } from 'mobx'
3
+ import { observer } from 'mobx-react'
4
+ import RBush from 'rbush'
5
+
6
+ // locals
7
+ import { MsaViewModel } from '../../model'
8
+ import TreeMenu from './TreeMenu'
9
+ import TreeBranchMenu from './TreeBranchMenu'
10
+ import { padding, renderTreeCanvas } from './renderTreeCanvas'
11
+
12
+ interface TooltipData {
13
+ name: string
14
+ id: string
15
+ x: number
16
+ y: number
17
+ }
18
+
19
+ interface ClickEntry {
20
+ name: string
21
+ id: string
22
+ branch?: boolean
23
+ minX: number
24
+ maxX: number
25
+ minY: number
26
+ maxY: number
27
+ }
28
+
29
+ const TreeCanvasBlock = observer(function ({
30
+ model,
31
+ offsetY,
32
+ }: {
33
+ model: MsaViewModel
34
+ offsetY: number
35
+ }) {
36
+ const ref = useRef<HTMLCanvasElement>()
37
+ const clickMap = useRef(new RBush<ClickEntry>())
38
+ const mouseoverRef = useRef<HTMLCanvasElement>(null)
39
+ const [branchMenu, setBranchMenu] = useState<TooltipData>()
40
+ const [toggleNodeMenu, setToggleNodeMenu] = useState<TooltipData>()
41
+ const [hoverElt, setHoverElt] = useState<ClickEntry>()
42
+
43
+ const { scrollY, treeAreaWidth, margin, blockSize, highResScaleFactor } =
44
+ model
45
+
46
+ const width = treeAreaWidth + padding
47
+ const height = blockSize
48
+ const w2 = width * highResScaleFactor
49
+ const h2 = height * highResScaleFactor
50
+
51
+ const vref = useCallback(
52
+ (arg: HTMLCanvasElement) => {
53
+ model.incrementRef()
54
+ ref.current = arg
55
+ },
56
+ // eslint-disable-next-line react-hooks/exhaustive-deps
57
+ [model, height, width],
58
+ )
59
+ useEffect(() => {
60
+ const ctx = ref.current?.getContext('2d')
61
+ if (!ctx) {
62
+ return
63
+ }
64
+
65
+ return autorun(() => {
66
+ renderTreeCanvas({
67
+ ctx,
68
+ model,
69
+ offsetY,
70
+ clickMap: clickMap.current,
71
+ })
72
+ })
73
+ }, [model, offsetY])
74
+
75
+ useEffect(() => {
76
+ const ctx = mouseoverRef.current?.getContext('2d')
77
+ if (!ctx) {
78
+ return
79
+ }
80
+
81
+ ctx.resetTransform()
82
+ ctx.clearRect(0, 0, treeAreaWidth + padding, blockSize)
83
+ ctx.translate(margin.left, -offsetY)
84
+
85
+ if (hoverElt) {
86
+ const { minX, maxX, minY, maxY } = hoverElt
87
+
88
+ ctx.fillStyle = 'rgba(0,0,0,0.1)'
89
+ ctx.fillRect(minX, minY, maxX - minX, maxY - minY)
90
+ }
91
+ }, [hoverElt, margin.left, offsetY, blockSize, treeAreaWidth])
92
+
93
+ function hoverBranchClickMap(event: React.MouseEvent) {
94
+ const x = event.nativeEvent.offsetX - margin.left
95
+ const y = event.nativeEvent.offsetY
96
+
97
+ const [entry] = clickMap.current.search({
98
+ minX: x,
99
+ maxX: x + 1,
100
+ minY: y + offsetY,
101
+ maxY: y + 1 + offsetY,
102
+ })
103
+
104
+ return entry && entry.branch
105
+ ? { ...entry, x: event.clientX, y: event.clientY }
106
+ : undefined
107
+ }
108
+
109
+ function hoverNameClickMap(event: React.MouseEvent) {
110
+ const x = event.nativeEvent.offsetX - margin.left
111
+ const y = event.nativeEvent.offsetY
112
+ const [entry] = clickMap.current.search({
113
+ minX: x,
114
+ maxX: x + 1,
115
+ minY: y + offsetY,
116
+ maxY: y + 1 + offsetY,
117
+ })
118
+
119
+ return entry && !entry.branch
120
+ ? { ...entry, x: event.clientX, y: event.clientY }
121
+ : undefined
122
+ }
123
+ const style = {
124
+ width,
125
+ height,
126
+ top: scrollY + offsetY,
127
+ left: 0,
128
+ position: 'absolute',
129
+ } as const
130
+ return (
131
+ <>
132
+ {branchMenu?.id ? (
133
+ <TreeBranchMenu
134
+ node={branchMenu}
135
+ model={model}
136
+ onClose={() => setBranchMenu(undefined)}
137
+ />
138
+ ) : null}
139
+
140
+ {toggleNodeMenu?.id ? (
141
+ <TreeMenu
142
+ node={toggleNodeMenu}
143
+ model={model}
144
+ onClose={() => setToggleNodeMenu(undefined)}
145
+ />
146
+ ) : null}
147
+
148
+ <canvas
149
+ width={w2}
150
+ height={h2}
151
+ style={style}
152
+ onMouseMove={event => {
153
+ if (!ref.current) {
154
+ return
155
+ }
156
+
157
+ const ret = hoverNameClickMap(event) || hoverBranchClickMap(event)
158
+ ref.current.style.cursor = ret ? 'pointer' : 'default'
159
+ setHoverElt(hoverNameClickMap(event))
160
+ }}
161
+ onClick={event => {
162
+ const { clientX: x, clientY: y } = event
163
+
164
+ const data = hoverBranchClickMap(event)
165
+ if (data?.id) {
166
+ setBranchMenu({ ...data, x, y })
167
+ }
168
+
169
+ const data2 = hoverNameClickMap(event)
170
+ if (data2?.id) {
171
+ setToggleNodeMenu({ ...data2, x, y })
172
+ }
173
+ }}
174
+ ref={vref}
175
+ />
176
+ <canvas
177
+ style={{
178
+ ...style,
179
+ pointerEvents: 'none',
180
+ zIndex: 100,
181
+ }}
182
+ width={width}
183
+ height={height}
184
+ ref={mouseoverRef}
185
+ />
186
+ </>
187
+ )
188
+ })
189
+
190
+ export default TreeCanvasBlock
@@ -3,7 +3,7 @@ import { Menu, MenuItem } from '@mui/material'
3
3
  import { observer } from 'mobx-react'
4
4
 
5
5
  // locals
6
- import { MsaViewModel } from '../model'
6
+ import { MsaViewModel } from '../../model'
7
7
 
8
8
  const TreeNodeInfoDlg = lazy(() => import('./dialogs/TreeNodeInfoDlg'))
9
9
 
@@ -39,11 +39,15 @@ const TreeMenu = observer(function ({
39
39
  <MenuItem
40
40
  dense
41
41
  onClick={() => {
42
- model.setDialogComponent(TreeNodeInfoDlg, {
43
- info: model.getRowData(node.name),
44
- model,
45
- nodeName: node.name,
46
- })
42
+ model.queueDialog(onClose => [
43
+ TreeNodeInfoDlg,
44
+ {
45
+ info: model.getRowData(node.name),
46
+ model,
47
+ nodeName: node.name,
48
+ onClose,
49
+ },
50
+ ])
47
51
  onClose()
48
52
  }}
49
53
  >
@@ -2,11 +2,11 @@ import React from 'react'
2
2
  import { observer } from 'mobx-react'
3
3
 
4
4
  // locals
5
- import { MsaViewModel } from '../model'
5
+ import { MsaViewModel } from '../../model'
6
6
 
7
7
  const TreeRuler = observer(({ model }: { model: MsaViewModel }) => {
8
- const { treeWidth } = model
9
- return <div style={{ width: treeWidth }} />
8
+ const { treeAreaWidth } = model
9
+ return <div style={{ flexShrink: 0, width: treeAreaWidth }} />
10
10
  })
11
11
 
12
12
  export default TreeRuler
@@ -6,7 +6,7 @@ import {
6
6
  Attributes,
7
7
  BaseCard,
8
8
  } from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail'
9
- import { MsaViewModel } from '../../model'
9
+ import { MsaViewModel } from '../../../model'
10
10
 
11
11
  export default observer(function ({
12
12
  info,
@@ -0,0 +1,16 @@
1
+ import React from 'react'
2
+ import { observer } from 'mobx-react'
3
+
4
+ import { MsaViewModel } from '../../model'
5
+ import TreeCanvas from './TreeCanvas'
6
+
7
+ const TreePanel = observer(function ({ model }: { model: MsaViewModel }) {
8
+ const { treeAreaWidth } = model
9
+ return (
10
+ <div style={{ flexShrink: 0, width: treeAreaWidth }}>
11
+ <TreeCanvas model={model} />
12
+ </div>
13
+ )
14
+ })
15
+
16
+ export default TreePanel
@@ -0,0 +1,245 @@
1
+ import RBush from 'rbush'
2
+
3
+ // locals
4
+ import { MsaViewModel } from '../../model'
5
+
6
+ export const padding = 600
7
+ const extendBounds = 5
8
+ const radius = 2.5
9
+ const d = radius * 2
10
+
11
+ interface ClickEntry {
12
+ name: string
13
+ id: string
14
+ branch?: boolean
15
+ minX: number
16
+ maxX: number
17
+ minY: number
18
+ maxY: number
19
+ }
20
+
21
+ export function renderTree({
22
+ offsetY,
23
+ ctx,
24
+ model,
25
+ }: {
26
+ offsetY: number
27
+ ctx: CanvasRenderingContext2D
28
+ model: MsaViewModel
29
+ }) {
30
+ const { hierarchy, showBranchLen, blockSize } = model
31
+ for (const { source, target } of hierarchy.links()) {
32
+ const y = showBranchLen ? 'len' : 'y'
33
+ // @ts-expect-error
34
+ const { x: sy, [y]: sx } = source
35
+ // @ts-expect-error
36
+ const { x: ty, [y]: tx } = target
37
+
38
+ const y1 = Math.min(sy, ty)
39
+ const y2 = Math.max(sy, ty)
40
+ // 1d line intersection to check if line crosses block at all, this is
41
+ // an optimization that allows us to skip drawing most tree links
42
+ // outside the block
43
+ if (offsetY + blockSize >= y1 && y2 >= offsetY) {
44
+ ctx.beginPath()
45
+ ctx.moveTo(sx, sy)
46
+ ctx.lineTo(sx, ty)
47
+ ctx.lineTo(tx, ty)
48
+ ctx.stroke()
49
+ }
50
+ }
51
+ }
52
+
53
+ export function renderNodeBubbles({
54
+ ctx,
55
+ clickMap,
56
+ offsetY,
57
+ model,
58
+ }: {
59
+ ctx: CanvasRenderingContext2D
60
+ clickMap: RBush<ClickEntry>
61
+ offsetY: number
62
+ model: MsaViewModel
63
+ }) {
64
+ const { hierarchy, showBranchLen, collapsed, blockSize } = model
65
+ for (const node of hierarchy.descendants()) {
66
+ const val = showBranchLen ? 'len' : 'y'
67
+ const {
68
+ // @ts-expect-error
69
+ x: y,
70
+ // @ts-expect-error
71
+ [val]: x,
72
+ data,
73
+ } = node
74
+ const { branchset, id = '', name = '' } = data
75
+ if (
76
+ branchset.length &&
77
+ y > offsetY - extendBounds &&
78
+ y < offsetY + blockSize + extendBounds
79
+ ) {
80
+ ctx.strokeStyle = 'black'
81
+ ctx.fillStyle = collapsed.includes(id) ? 'black' : 'white'
82
+ ctx.beginPath()
83
+ ctx.arc(x, y, radius, 0, 2 * Math.PI)
84
+ ctx.fill()
85
+ ctx.stroke()
86
+
87
+ clickMap.insert({
88
+ minX: x - radius,
89
+ maxX: x - radius + d,
90
+ minY: y - radius,
91
+ maxY: y - radius + d,
92
+ branch: true,
93
+ id,
94
+ name,
95
+ })
96
+ }
97
+ }
98
+ }
99
+
100
+ export function renderTreeLabels({
101
+ model,
102
+ offsetY,
103
+ ctx,
104
+ clickMap,
105
+ }: {
106
+ model: MsaViewModel
107
+ offsetY: number
108
+ ctx: CanvasRenderingContext2D
109
+ clickMap: RBush<ClickEntry>
110
+ }) {
111
+ const {
112
+ rowHeight,
113
+ showBranchLen,
114
+ treeMetadata,
115
+ hierarchy,
116
+ blockSize,
117
+ labelsAlignRight,
118
+ drawTree,
119
+ structures,
120
+ treeAreaWidth,
121
+ margin,
122
+ noTree,
123
+ } = model
124
+ if (labelsAlignRight) {
125
+ ctx.textAlign = 'right'
126
+ ctx.setLineDash([1, 3])
127
+ } else {
128
+ ctx.textAlign = 'start'
129
+ }
130
+ for (const node of hierarchy.leaves()) {
131
+ const {
132
+ // @ts-expect-error
133
+ x: y,
134
+ // @ts-expect-error
135
+ y: x,
136
+ data: { name, id },
137
+ // @ts-expect-error
138
+ len,
139
+ } = node
140
+
141
+ const displayName = treeMetadata[name]?.genome || name
142
+
143
+ if (y > offsetY - extendBounds && y < offsetY + blockSize + extendBounds) {
144
+ // note: +rowHeight/4 matches with -rowHeight/4 in msa
145
+ const yp = y + rowHeight / 4
146
+ const xp = showBranchLen ? len : x
147
+
148
+ const { width } = ctx.measureText(displayName)
149
+ const height = ctx.measureText('M').width // use an 'em' for height
150
+
151
+ const hasStructure = structures[name]
152
+ ctx.fillStyle = hasStructure ? 'blue' : 'black'
153
+
154
+ if (!drawTree && !labelsAlignRight) {
155
+ ctx.fillText(displayName, 0, yp)
156
+ clickMap.insert({
157
+ minX: 0,
158
+ maxX: width,
159
+ minY: yp - height,
160
+ maxY: yp,
161
+ name,
162
+ id,
163
+ })
164
+ } else if (labelsAlignRight) {
165
+ const smallPadding = 2
166
+ const offset = treeAreaWidth - smallPadding - margin.left
167
+ if (drawTree && !noTree) {
168
+ const { width } = ctx.measureText(displayName)
169
+ ctx.moveTo(xp + radius + 2, y)
170
+ ctx.lineTo(offset - smallPadding - width, y)
171
+ ctx.stroke()
172
+ }
173
+ ctx.fillText(displayName, offset, yp)
174
+ clickMap.insert({
175
+ minX: treeAreaWidth - margin.left - width,
176
+ maxX: treeAreaWidth - margin.left,
177
+ minY: yp - height,
178
+ maxY: yp,
179
+ name,
180
+ id,
181
+ })
182
+ } else {
183
+ ctx.fillText(displayName, xp + d, yp)
184
+ clickMap.insert({
185
+ minX: xp + d,
186
+ maxX: xp + d + width,
187
+ minY: yp - height,
188
+ maxY: yp,
189
+ name,
190
+ id,
191
+ })
192
+ }
193
+ }
194
+ }
195
+ ctx.setLineDash([])
196
+ }
197
+
198
+ export function renderTreeCanvas({
199
+ model,
200
+ clickMap,
201
+ ctx,
202
+ offsetY,
203
+ }: {
204
+ model: MsaViewModel
205
+ offsetY: number
206
+ ctx: CanvasRenderingContext2D
207
+ clickMap: RBush<ClickEntry>
208
+ }) {
209
+ clickMap.clear()
210
+ const {
211
+ noTree,
212
+ drawTree,
213
+ drawNodeBubbles,
214
+ treeWidth,
215
+ highResScaleFactor,
216
+ margin,
217
+ blockSize,
218
+ fontSize,
219
+ rowHeight,
220
+ // nref has to be kept as a unused var or at least reference to force
221
+ // redraw after canvas ref change
222
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
223
+ nref,
224
+ } = model
225
+
226
+ ctx.resetTransform()
227
+ ctx.scale(highResScaleFactor, highResScaleFactor)
228
+ ctx.clearRect(0, 0, treeWidth + padding, blockSize)
229
+ ctx.translate(margin.left, -offsetY)
230
+
231
+ const font = ctx.font
232
+ ctx.font = font.replace(/\d+px/, `${fontSize}px`)
233
+
234
+ if (!noTree && drawTree) {
235
+ renderTree({ ctx, offsetY, model })
236
+
237
+ if (drawNodeBubbles) {
238
+ renderNodeBubbles({ ctx, offsetY, clickMap, model })
239
+ }
240
+ }
241
+
242
+ if (rowHeight >= 5) {
243
+ renderTreeLabels({ ctx, offsetY, model, clickMap })
244
+ }
245
+ }
@@ -1,4 +1,4 @@
1
- import React, { useState } from 'react'
1
+ import React from 'react'
2
2
  import { observer } from 'mobx-react'
3
3
  import { makeStyles } from 'tss-react/mui'
4
4
  import { Dialog } from '@jbrowse/core/ui'
@@ -9,7 +9,9 @@ import {
9
9
  DialogContent,
10
10
  FormControlLabel,
11
11
  MenuItem,
12
+ Slider,
12
13
  TextField,
14
+ Typography,
13
15
  } from '@mui/material'
14
16
 
15
17
  import { MsaViewModel } from '../../model'
@@ -29,17 +31,19 @@ const SettingsDialog = observer(function ({
29
31
  onClose: () => void
30
32
  }) {
31
33
  const { classes } = useStyles()
32
- const { colorSchemeName, noTree } = model
33
- const [rowHeight, setRowHeight] = useState(`${model.rowHeight}`)
34
- const [colWidth, setColWidth] = useState(`${model.colWidth}`)
35
- const [treeWidth, setTreeWidth] = useState(`${model.treeWidth}`)
36
-
37
- function error(n: string) {
38
- return Number.isNaN(+n) || +n < 0
39
- }
40
- const rowHeightError = error(rowHeight)
41
- const colWidthError = error(colWidth)
42
- const treeWidthError = error(treeWidth)
34
+ const {
35
+ bgColor,
36
+ colWidth,
37
+ colorSchemeName,
38
+ drawTree,
39
+ drawNodeBubbles,
40
+ labelsAlignRight,
41
+ noTree,
42
+ rowHeight,
43
+ showBranchLen,
44
+ treeWidthMatchesArea,
45
+ treeWidth,
46
+ } = model
43
47
 
44
48
  return (
45
49
  <Dialog open onClose={() => onClose()} title="Settings">
@@ -47,8 +51,8 @@ const SettingsDialog = observer(function ({
47
51
  <FormControlLabel
48
52
  control={
49
53
  <Checkbox
50
- checked={model.showBranchLen}
51
- onChange={() => model.toggleBranchLen()}
54
+ checked={showBranchLen}
55
+ onChange={() => model.setShowBranchLen(!showBranchLen)}
52
56
  />
53
57
  }
54
58
  label="Show branch length"
@@ -56,8 +60,8 @@ const SettingsDialog = observer(function ({
56
60
  <FormControlLabel
57
61
  control={
58
62
  <Checkbox
59
- checked={model.bgColor}
60
- onChange={() => model.toggleBgColor()}
63
+ checked={bgColor}
64
+ onChange={() => model.setBgColor(!bgColor)}
61
65
  />
62
66
  }
63
67
  label="Color background"
@@ -65,8 +69,8 @@ const SettingsDialog = observer(function ({
65
69
  <FormControlLabel
66
70
  control={
67
71
  <Checkbox
68
- checked={model.drawNodeBubbles}
69
- onChange={() => model.toggleNodeBubbles()}
72
+ checked={drawNodeBubbles}
73
+ onChange={() => model.setDrawNodeBubbles(!drawNodeBubbles)}
70
74
  />
71
75
  }
72
76
  label="Draw node bubbles"
@@ -74,8 +78,8 @@ const SettingsDialog = observer(function ({
74
78
  <FormControlLabel
75
79
  control={
76
80
  <Checkbox
77
- checked={model.drawTree}
78
- onChange={() => model.toggleDrawTree()}
81
+ checked={drawTree}
82
+ onChange={() => model.setDrawTree(!drawTree)}
79
83
  />
80
84
  }
81
85
  label="Draw tree (if available)"
@@ -83,39 +87,61 @@ const SettingsDialog = observer(function ({
83
87
  <FormControlLabel
84
88
  control={
85
89
  <Checkbox
86
- checked={model.labelsAlignRight}
87
- onChange={() => model.toggleLabelsAlignRight()}
90
+ checked={labelsAlignRight}
91
+ onChange={() => model.setLabelsAlignRight(!labelsAlignRight)}
88
92
  />
89
93
  }
90
- label="Labels align right (note: labels may draw over tree, but can adjust tree width or tree area width in UI)"
94
+ label="Labels align right"
91
95
  />
92
96
 
93
- <TextField
94
- className={classes.field}
95
- label="Row height (px)"
96
- value={rowHeight}
97
- error={rowHeightError}
98
- onChange={event => setRowHeight(event.target.value)}
99
- />
100
- <TextField
101
- className={classes.field}
102
- label="Column width (px)"
103
- value={colWidth}
104
- error={colWidthError}
105
- onChange={event => setColWidth(event.target.value)}
106
- />
107
- <br />
108
97
  {!noTree ? (
109
- <TextField
110
- className={classes.field}
111
- label="Tree width (px)"
112
- value={treeWidth}
113
- error={treeWidthError}
114
- onChange={event => setTreeWidth(event.target.value)}
115
- />
98
+ <div>
99
+ <FormControlLabel
100
+ control={
101
+ <Checkbox
102
+ checked={treeWidthMatchesArea}
103
+ onChange={() =>
104
+ model.setTreeWidthMatchesArea(!treeWidthMatchesArea)
105
+ }
106
+ />
107
+ }
108
+ label="Make tree width fit to tree area?"
109
+ />
110
+ {!treeWidthMatchesArea ? (
111
+ <div style={{ display: 'flex' }}>
112
+ <Typography>Tree width ({treeWidth}px)</Typography>
113
+ <Slider
114
+ className={classes.field}
115
+ min={50}
116
+ max={600}
117
+ value={treeWidth}
118
+ onChange={(_, val) => model.setTreeWidth(val as number)}
119
+ />
120
+ </div>
121
+ ) : null}
122
+ </div>
116
123
  ) : null}
117
124
 
118
- <br />
125
+ <div style={{ display: 'flex' }}>
126
+ <Typography>Column width ({colWidth}px)</Typography>
127
+ <Slider
128
+ className={classes.field}
129
+ min={1}
130
+ max={50}
131
+ value={colWidth}
132
+ onChange={(_, val) => model.setColWidth(val as number)}
133
+ />
134
+ </div>
135
+ <div style={{ display: 'flex' }}>
136
+ <Typography>Row height ({rowHeight}px)</Typography>
137
+ <Slider
138
+ className={classes.field}
139
+ min={1}
140
+ max={50}
141
+ value={rowHeight}
142
+ onChange={(_, val) => model.setRowHeight(val as number)}
143
+ />
144
+ </div>
119
145
 
120
146
  <TextField
121
147
  select
@@ -131,7 +157,6 @@ const SettingsDialog = observer(function ({
131
157
  </TextField>
132
158
  <DialogActions>
133
159
  <Button
134
- disabled={rowHeightError || colWidthError || treeWidthError}
135
160
  onClick={() => {
136
161
  model.setRowHeight(+rowHeight)
137
162
  model.setColWidth(+colWidth)