react-msaview 1.3.1 → 2.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 (200) hide show
  1. package/bundle/index.js +283 -97265
  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 +52 -0
  7. package/dist/UniprotTrack.js.map +1 -0
  8. package/dist/colorSchemes.d.ts +2 -2
  9. package/dist/colorSchemes.js +24 -29
  10. package/dist/colorSchemes.js.map +1 -0
  11. package/dist/components/AboutDlg.d.ts +0 -1
  12. package/dist/components/AboutDlg.js +38 -48
  13. package/dist/components/AboutDlg.js.map +1 -0
  14. package/dist/components/AddTrackDlg.d.ts +0 -1
  15. package/dist/components/AddTrackDlg.js +13 -13
  16. package/dist/components/AddTrackDlg.js.map +1 -0
  17. package/dist/components/AnnotationDlg.d.ts +0 -1
  18. package/dist/components/AnnotationDlg.js +36 -48
  19. package/dist/components/AnnotationDlg.js.map +1 -0
  20. package/dist/components/BoxTrack.d.ts +3 -4
  21. package/dist/components/BoxTrack.js +51 -48
  22. package/dist/components/BoxTrack.js.map +1 -0
  23. package/dist/components/DetailsDlg.d.ts +0 -1
  24. package/dist/components/DetailsDlg.js +7 -7
  25. package/dist/components/DetailsDlg.js.map +1 -0
  26. package/dist/components/Header.d.ts +0 -1
  27. package/dist/components/Header.js +39 -34
  28. package/dist/components/Header.js.map +1 -0
  29. package/dist/components/ImportForm.d.ts +0 -1
  30. package/dist/components/ImportForm.js +59 -71
  31. package/dist/components/ImportForm.js.map +1 -0
  32. package/dist/components/MSACanvas.d.ts +0 -1
  33. package/dist/components/MSACanvas.js +71 -74
  34. package/dist/components/MSACanvas.js.map +1 -0
  35. package/dist/components/MSAView.d.ts +0 -1
  36. package/dist/components/MSAView.js +19 -38
  37. package/dist/components/MSAView.js.map +1 -0
  38. package/dist/components/MoreInfoDlg.d.ts +2 -3
  39. package/dist/components/MoreInfoDlg.js +5 -5
  40. package/dist/components/MoreInfoDlg.js.map +1 -0
  41. package/dist/components/ResizeHandles.d.ts +2 -3
  42. package/dist/components/ResizeHandles.js +31 -32
  43. package/dist/components/ResizeHandles.js.map +1 -0
  44. package/dist/components/Rubberband.d.ts +2 -1
  45. package/dist/components/Rubberband.js +42 -64
  46. package/dist/components/Rubberband.js.map +1 -0
  47. package/dist/components/Ruler.d.ts +0 -15
  48. package/dist/components/Ruler.js +18 -87
  49. package/dist/components/Ruler.js.map +1 -0
  50. package/dist/components/SettingsDlg.d.ts +0 -1
  51. package/dist/components/SettingsDlg.js +29 -22
  52. package/dist/components/SettingsDlg.js.map +1 -0
  53. package/dist/components/TextTrack.d.ts +3 -4
  54. package/dist/components/TextTrack.js +23 -24
  55. package/dist/components/TextTrack.js.map +1 -0
  56. package/dist/components/Track.d.ts +2 -3
  57. package/dist/components/Track.js +38 -38
  58. package/dist/components/Track.js.map +1 -0
  59. package/dist/components/TrackInfoDlg.d.ts +5 -3
  60. package/dist/components/TrackInfoDlg.js +12 -13
  61. package/dist/components/TrackInfoDlg.js.map +1 -0
  62. package/dist/components/TracklistDlg.d.ts +0 -1
  63. package/dist/components/TracklistDlg.js +9 -9
  64. package/dist/components/TracklistDlg.js.map +1 -0
  65. package/dist/components/TreeCanvas.d.ts +0 -1
  66. package/dist/components/TreeCanvas.js +135 -148
  67. package/dist/components/TreeCanvas.js.map +1 -0
  68. package/dist/components/TreeRuler.d.ts +0 -1
  69. package/dist/components/TreeRuler.js +3 -3
  70. package/dist/components/TreeRuler.js.map +1 -0
  71. package/dist/components/VerticalGuide.d.ts +6 -0
  72. package/dist/components/VerticalGuide.js +30 -0
  73. package/dist/components/VerticalGuide.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/{bundle/components/Ruler.d.ts → dist/components/util.d.ts} +1 -6
  78. package/dist/components/util.js +69 -0
  79. package/dist/components/util.js.map +1 -0
  80. package/dist/index.d.ts +2 -4
  81. package/dist/index.js +3 -3
  82. package/dist/index.js.map +1 -0
  83. package/dist/layout.js +14 -20
  84. package/dist/layout.js.map +1 -0
  85. package/dist/model.d.ts +94 -74
  86. package/dist/model.js +232 -473
  87. package/dist/model.js.map +1 -0
  88. package/dist/parseNewick.d.ts +1 -5
  89. package/dist/parseNewick.js +10 -7
  90. package/dist/parseNewick.js.map +1 -0
  91. package/dist/parsers/ClustalMSA.d.ts +6 -18
  92. package/dist/parsers/ClustalMSA.js +55 -64
  93. package/dist/parsers/ClustalMSA.js.map +1 -0
  94. package/dist/parsers/FastaMSA.d.ts +4 -9
  95. package/dist/parsers/FastaMSA.js +55 -64
  96. package/dist/parsers/FastaMSA.js.map +1 -0
  97. package/dist/parsers/StockholmMSA.d.ts +8 -13
  98. package/dist/parsers/StockholmMSA.js +78 -107
  99. package/dist/parsers/StockholmMSA.js.map +1 -0
  100. package/dist/util.d.ts +33 -4
  101. package/dist/util.js +76 -24
  102. package/dist/util.js.map +1 -0
  103. package/dist/version.d.ts +1 -0
  104. package/dist/version.js +2 -0
  105. package/dist/version.js.map +1 -0
  106. package/package.json +30 -30
  107. package/src/StructureModel.ts +11 -0
  108. package/src/UniprotTrack.ts +60 -0
  109. package/src/colorSchemes.ts +520 -0
  110. package/src/components/AboutDlg.tsx +64 -0
  111. package/src/components/AddTrackDlg.tsx +74 -0
  112. package/src/components/AnnotationDlg.tsx +144 -0
  113. package/src/components/BoxTrack.tsx +225 -0
  114. package/src/components/DetailsDlg.tsx +28 -0
  115. package/src/components/Header.tsx +117 -0
  116. package/src/components/ImportForm.tsx +192 -0
  117. package/src/components/MSACanvas.tsx +297 -0
  118. package/src/components/MSAView.tsx +132 -0
  119. package/src/components/MoreInfoDlg.tsx +21 -0
  120. package/src/components/ResizeHandles.tsx +137 -0
  121. package/src/components/Rubberband.tsx +271 -0
  122. package/src/components/Ruler.tsx +122 -0
  123. package/src/components/SettingsDlg.tsx +154 -0
  124. package/src/components/TextTrack.tsx +120 -0
  125. package/src/components/Track.tsx +150 -0
  126. package/src/components/TrackInfoDlg.tsx +59 -0
  127. package/src/components/TracklistDlg.tsx +61 -0
  128. package/src/components/TreeCanvas.tsx +633 -0
  129. package/src/components/TreeRuler.tsx +12 -0
  130. package/src/components/VerticalGuide.tsx +50 -0
  131. package/src/components/data/seq2.ts +35 -0
  132. package/src/components/util.ts +94 -0
  133. package/src/declare.d.ts +2 -0
  134. package/src/index.ts +2 -0
  135. package/src/layout.ts +83 -0
  136. package/src/model.ts +790 -0
  137. package/{bundle/parseNewick.d.ts → src/parseNewick.ts} +36 -5
  138. package/src/parsers/ClustalMSA.ts +79 -0
  139. package/src/parsers/FastaMSA.ts +82 -0
  140. package/src/parsers/StockholmMSA.ts +137 -0
  141. package/src/util.ts +142 -0
  142. package/src/version.ts +1 -0
  143. package/bundle/colorSchemes.d.ts +0 -16
  144. package/bundle/colorSchemes.js +0 -455
  145. package/bundle/components/AboutDlg.d.ts +0 -5
  146. package/bundle/components/AboutDlg.js +0 -47
  147. package/bundle/components/AddTrackDlg.d.ts +0 -8
  148. package/bundle/components/AddTrackDlg.js +0 -26
  149. package/bundle/components/AnnotationDlg.d.ts +0 -11
  150. package/bundle/components/AnnotationDlg.js +0 -77
  151. package/bundle/components/BoxTrack.d.ts +0 -7
  152. package/bundle/components/BoxTrack.js +0 -143
  153. package/bundle/components/DetailsDlg.d.ts +0 -8
  154. package/bundle/components/DetailsDlg.js +0 -12
  155. package/bundle/components/Header.d.ts +0 -6
  156. package/bundle/components/Header.js +0 -63
  157. package/bundle/components/ImportForm.d.ts +0 -6
  158. package/bundle/components/ImportForm.js +0 -89
  159. package/bundle/components/MSACanvas.d.ts +0 -6
  160. package/bundle/components/MSACanvas.js +0 -210
  161. package/bundle/components/MSAView.d.ts +0 -6
  162. package/bundle/components/MSAView.js +0 -88
  163. package/bundle/components/MoreInfoDlg.d.ts +0 -6
  164. package/bundle/components/MoreInfoDlg.js +0 -11
  165. package/bundle/components/ResizeHandles.d.ts +0 -8
  166. package/bundle/components/ResizeHandles.js +0 -110
  167. package/bundle/components/Rubberband.d.ts +0 -7
  168. package/bundle/components/Rubberband.js +0 -196
  169. package/bundle/components/Ruler.js +0 -121
  170. package/bundle/components/SettingsDlg.d.ts +0 -8
  171. package/bundle/components/SettingsDlg.js +0 -40
  172. package/bundle/components/TextTrack.d.ts +0 -7
  173. package/bundle/components/TextTrack.js +0 -72
  174. package/bundle/components/Track.d.ts +0 -11
  175. package/bundle/components/Track.js +0 -81
  176. package/bundle/components/TrackInfoDlg.d.ts +0 -6
  177. package/bundle/components/TrackInfoDlg.js +0 -33
  178. package/bundle/components/TracklistDlg.d.ts +0 -8
  179. package/bundle/components/TracklistDlg.js +0 -18
  180. package/bundle/components/TreeCanvas.d.ts +0 -6
  181. package/bundle/components/TreeCanvas.js +0 -431
  182. package/bundle/components/TreeRuler.d.ts +0 -6
  183. package/bundle/components/TreeRuler.js +0 -8
  184. package/bundle/components/data/seq2.d.ts +0 -3
  185. package/bundle/components/data/seq2.js +0 -3
  186. package/bundle/index.d.ts +0 -4
  187. package/bundle/layout.d.ts +0 -23
  188. package/bundle/layout.js +0 -53
  189. package/bundle/model.d.ts +0 -364
  190. package/bundle/model.js +0 -894
  191. package/bundle/parseNewick.js +0 -94
  192. package/bundle/parsers/ClustalMSA.d.ts +0 -39
  193. package/bundle/parsers/ClustalMSA.js +0 -77
  194. package/bundle/parsers/FastaMSA.d.ts +0 -26
  195. package/bundle/parsers/FastaMSA.js +0 -78
  196. package/bundle/parsers/StockholmMSA.d.ts +0 -75
  197. package/bundle/parsers/StockholmMSA.js +0 -142
  198. package/bundle/util.d.ts +0 -17
  199. package/bundle/util.js +0 -33
  200. package/dist/components/package.json +0 -62
@@ -0,0 +1,271 @@
1
+ import React, { useRef, useEffect, useState } from 'react'
2
+ import { observer } from 'mobx-react'
3
+ import { makeStyles } from 'tss-react/mui'
4
+ import { Popover, Typography, alpha } from '@mui/material'
5
+ import { Menu } from '@jbrowse/core/ui'
6
+
7
+ // icons
8
+ import AssignmentIcon from '@mui/icons-material/Assignment'
9
+
10
+ // locals
11
+ import { MsaViewModel } from '../model'
12
+ import VerticalGuide from './VerticalGuide'
13
+
14
+ const useStyles = makeStyles()(theme => {
15
+ const background =
16
+ 'tertiary' in theme.palette && theme.palette.tertiary
17
+ ? alpha(theme.palette.tertiary.main, 0.7)
18
+ : alpha(theme.palette.primary.main, 0.7)
19
+ return {
20
+ rubberband: {
21
+ height: '100%',
22
+ background,
23
+ position: 'absolute',
24
+ zIndex: 10,
25
+ textAlign: 'center',
26
+ overflow: 'hidden',
27
+ },
28
+ rubberbandControl: {
29
+ cursor: 'crosshair',
30
+ width: '100%',
31
+ minHeight: 8,
32
+ },
33
+ rubberbandText: {
34
+ color: theme.palette.tertiary
35
+ ? theme.palette.tertiary.contrastText
36
+ : theme.palette.primary.contrastText,
37
+ },
38
+ popover: {
39
+ mouseEvents: 'none',
40
+ cursor: 'crosshair',
41
+ },
42
+ paper: {
43
+ paddingLeft: theme.spacing(1),
44
+ paddingRight: theme.spacing(1),
45
+ },
46
+ }
47
+ })
48
+
49
+ function Rubberband({
50
+ model,
51
+ ControlComponent = <div />,
52
+ }: {
53
+ model: MsaViewModel
54
+ ControlComponent?: React.ReactElement
55
+ }) {
56
+ const { treeAreaWidth } = model
57
+ const [startX, setStartX] = useState<number>()
58
+ const [currentX, setCurrentX] = useState<number>()
59
+
60
+ // clientX and clientY used for anchorPosition for menu
61
+ // offsetX used for calculations about width of selection
62
+ const [anchorPosition, setAnchorPosition] = useState<{
63
+ offsetX: number
64
+ clientX: number
65
+ clientY: number
66
+ }>()
67
+ const [guideX, setGuideX] = useState<number | undefined>()
68
+ const controlsRef = useRef<HTMLDivElement>(null)
69
+ const rubberbandRef = useRef(null)
70
+ const { classes } = useStyles()
71
+ const mouseDragging = startX !== undefined && anchorPosition === undefined
72
+
73
+ useEffect(() => {
74
+ function globalMouseMove(event: MouseEvent) {
75
+ if (controlsRef.current && mouseDragging) {
76
+ const relativeX =
77
+ event.clientX - controlsRef.current.getBoundingClientRect().left
78
+ setCurrentX(relativeX)
79
+ }
80
+ }
81
+
82
+ function globalMouseUp(event: MouseEvent) {
83
+ if (startX !== undefined && controlsRef.current) {
84
+ const { clientX, clientY } = event
85
+ const ref = controlsRef.current
86
+ const offsetX = clientX - ref.getBoundingClientRect().left
87
+ // as stated above, store both clientX/Y and offsetX for different
88
+ // purposes
89
+ setAnchorPosition({
90
+ offsetX,
91
+ clientX,
92
+ clientY,
93
+ })
94
+ setGuideX(undefined)
95
+ }
96
+ }
97
+ if (mouseDragging) {
98
+ window.addEventListener('mousemove', globalMouseMove)
99
+ window.addEventListener('mouseup', globalMouseUp)
100
+ return () => {
101
+ window.removeEventListener('mousemove', globalMouseMove)
102
+ window.removeEventListener('mouseup', globalMouseUp)
103
+ }
104
+ }
105
+ return () => {}
106
+ }, [startX, mouseDragging, anchorPosition])
107
+
108
+ useEffect(() => {
109
+ if (
110
+ !mouseDragging &&
111
+ currentX !== undefined &&
112
+ startX !== undefined &&
113
+ Math.abs(currentX - startX) <= 3
114
+ ) {
115
+ handleClose()
116
+ }
117
+ }, [mouseDragging, currentX, startX, model.colWidth])
118
+
119
+ function mouseDown(event: React.MouseEvent<HTMLDivElement>) {
120
+ event.preventDefault()
121
+ event.stopPropagation()
122
+ const relativeX =
123
+ event.clientX -
124
+ (event.target as HTMLDivElement).getBoundingClientRect().left
125
+ setStartX(relativeX)
126
+ setCurrentX(relativeX)
127
+ }
128
+
129
+ function mouseMove(event: React.MouseEvent<HTMLDivElement>) {
130
+ const target = event.target as HTMLDivElement
131
+ setGuideX(event.clientX - target.getBoundingClientRect().left)
132
+ }
133
+
134
+ function mouseOut() {
135
+ setGuideX(undefined)
136
+ model.clearAnnotPos()
137
+ }
138
+
139
+ function handleClose() {
140
+ setAnchorPosition(undefined)
141
+ setStartX(undefined)
142
+ setCurrentX(undefined)
143
+ }
144
+
145
+ // eslint-disable-next-line @typescript-eslint/ban-types
146
+ function handleMenuItemClick(_: unknown, callback: Function) {
147
+ callback()
148
+ handleClose()
149
+ }
150
+
151
+ if (startX === undefined) {
152
+ return (
153
+ <>
154
+ {guideX !== undefined ? (
155
+ <VerticalGuide model={model} coordX={guideX} />
156
+ ) : null}
157
+ <div
158
+ data-testid="rubberband_controls"
159
+ className={classes.rubberbandControl}
160
+ role="presentation"
161
+ ref={controlsRef}
162
+ onMouseDown={mouseDown}
163
+ onMouseOut={mouseOut}
164
+ onMouseMove={mouseMove}
165
+ >
166
+ {ControlComponent}
167
+ </div>
168
+ </>
169
+ )
170
+ }
171
+
172
+ const right = anchorPosition ? anchorPosition.offsetX : currentX || 0
173
+ const left = right < startX ? right : startX
174
+ const width = Math.abs(right - startX)
175
+ const leftBpOffset = model.pxToBp(left)
176
+ const rightBpOffset = model.pxToBp(left + width)
177
+ const numOfBpSelected = Math.ceil(width / model.colWidth)
178
+
179
+ const menuItems = [
180
+ {
181
+ label: 'Create annotation',
182
+ icon: AssignmentIcon,
183
+ onClick: () => {
184
+ model.setOffsets(leftBpOffset, rightBpOffset)
185
+ handleClose()
186
+ },
187
+ },
188
+ ]
189
+ return (
190
+ <>
191
+ {rubberbandRef.current ? (
192
+ <>
193
+ <Popover
194
+ className={classes.popover}
195
+ classes={{
196
+ paper: classes.paper,
197
+ }}
198
+ open
199
+ anchorEl={rubberbandRef.current}
200
+ anchorOrigin={{
201
+ vertical: 'top',
202
+ horizontal: 'left',
203
+ }}
204
+ transformOrigin={{
205
+ vertical: 'bottom',
206
+ horizontal: 'right',
207
+ }}
208
+ keepMounted
209
+ disableRestoreFocus
210
+ >
211
+ <Typography>{leftBpOffset + 1}</Typography>
212
+ </Popover>
213
+ <Popover
214
+ className={classes.popover}
215
+ classes={{
216
+ paper: classes.paper,
217
+ }}
218
+ open
219
+ anchorEl={rubberbandRef.current}
220
+ anchorOrigin={{
221
+ vertical: 'top',
222
+ horizontal: 'right',
223
+ }}
224
+ transformOrigin={{
225
+ vertical: 'bottom',
226
+ horizontal: 'left',
227
+ }}
228
+ keepMounted
229
+ disableRestoreFocus
230
+ >
231
+ <Typography>{rightBpOffset + 1}</Typography>
232
+ </Popover>
233
+ </>
234
+ ) : null}
235
+ <div
236
+ ref={rubberbandRef}
237
+ className={classes.rubberband}
238
+ style={{ left: left + treeAreaWidth, width }}
239
+ >
240
+ <Typography variant="h6" className={classes.rubberbandText}>
241
+ {numOfBpSelected.toLocaleString('en-US')} bp
242
+ </Typography>
243
+ </div>
244
+ <div
245
+ data-testid="rubberband_controls"
246
+ className={classes.rubberbandControl}
247
+ role="presentation"
248
+ ref={controlsRef}
249
+ onMouseDown={mouseDown}
250
+ onMouseOut={mouseOut}
251
+ onMouseMove={mouseMove}
252
+ >
253
+ {ControlComponent}
254
+ </div>
255
+ {anchorPosition ? (
256
+ <Menu
257
+ anchorReference="anchorPosition"
258
+ anchorPosition={{
259
+ left: anchorPosition.clientX,
260
+ top: anchorPosition.clientY,
261
+ }}
262
+ onMenuItemClick={handleMenuItemClick}
263
+ open={Boolean(anchorPosition)}
264
+ onClose={handleClose}
265
+ menuItems={menuItems}
266
+ />
267
+ ) : null}
268
+ </>
269
+ )
270
+ }
271
+ export default observer(Rubberband)
@@ -0,0 +1,122 @@
1
+ import React, { useRef } from 'react'
2
+ import { makeStyles } from 'tss-react/mui'
3
+ import { observer } from 'mobx-react'
4
+ // locals
5
+ import { MsaViewModel } from '../model'
6
+ import { makeTicks, mathPower } from './util'
7
+
8
+ const useStyles = makeStyles()({
9
+ majorTickLabel: {
10
+ fontSize: '11px',
11
+ },
12
+ majorTick: {
13
+ stroke: '#555',
14
+ },
15
+ minorTick: {
16
+ stroke: '#999',
17
+ },
18
+ })
19
+
20
+ function RulerBlock({
21
+ start,
22
+ end,
23
+ bpPerPx,
24
+ reversed,
25
+ major,
26
+ minor,
27
+ }: {
28
+ start: number
29
+ end: number
30
+ bpPerPx: number
31
+ reversed?: boolean
32
+ major?: boolean
33
+ minor?: boolean
34
+ }) {
35
+ const { classes } = useStyles()
36
+ const ticks = makeTicks(start, end, bpPerPx, major, minor)
37
+ return (
38
+ <>
39
+ {ticks.map(tick => {
40
+ const x = (reversed ? end - tick.base : tick.base - start) / bpPerPx
41
+ return (
42
+ <line
43
+ key={tick.base}
44
+ x1={x}
45
+ x2={x}
46
+ y1={11}
47
+ y2={tick.type === 'major' ? 11 + 6 : 11 + 4}
48
+ strokeWidth={1}
49
+ stroke={tick.type === 'major' ? '#555' : '#999'}
50
+ className={
51
+ tick.type === 'major' ? classes.majorTick : classes.minorTick
52
+ }
53
+ data-bp={tick.base}
54
+ />
55
+ )
56
+ })}
57
+ {ticks
58
+ .filter(tick => tick.type === 'major')
59
+ .map(tick => {
60
+ const x = (reversed ? end - tick.base : tick.base - start) / bpPerPx
61
+ return (
62
+ <text
63
+ x={x}
64
+ y={10}
65
+ key={`label-${tick.base}`}
66
+ textAnchor="middle"
67
+ style={{ fontSize: '11px' }}
68
+ className={classes.majorTickLabel}
69
+ >
70
+ {mathPower(tick.base + 1)}
71
+ </text>
72
+ )
73
+ })}
74
+ </>
75
+ )
76
+ }
77
+
78
+ const Ruler = observer(function ({ model }: { model: MsaViewModel }) {
79
+ const {
80
+ MSA,
81
+ colWidth,
82
+ msaAreaWidth,
83
+ resizeHandleWidth,
84
+ scrollX,
85
+ blocksX,
86
+ blockSize,
87
+ } = model
88
+ const ref = useRef<HTMLDivElement>(null)
89
+ const offsetX = blocksX[0]
90
+
91
+ return !MSA ? null : (
92
+ <div
93
+ ref={ref}
94
+ style={{
95
+ position: 'relative',
96
+ width: msaAreaWidth,
97
+ cursor: 'crosshair',
98
+ overflow: 'hidden',
99
+ height: 20,
100
+ background: '#ccc',
101
+ }}
102
+ >
103
+ <svg
104
+ style={{
105
+ width: blocksX.length * blockSize,
106
+ position: 'absolute',
107
+ left: scrollX + offsetX + resizeHandleWidth,
108
+ pointerEvents: 'none',
109
+ }}
110
+ >
111
+ <RulerBlock
112
+ key={offsetX}
113
+ start={offsetX / colWidth}
114
+ end={offsetX / colWidth + (blockSize * blocksX.length) / colWidth}
115
+ bpPerPx={1 / colWidth}
116
+ />
117
+ </svg>
118
+ </div>
119
+ )
120
+ })
121
+
122
+ export default Ruler
@@ -0,0 +1,154 @@
1
+ import React, { useState } from 'react'
2
+ import { observer } from 'mobx-react'
3
+ import { makeStyles } from 'tss-react/mui'
4
+ import { Dialog } from '@jbrowse/core/ui'
5
+ import {
6
+ Button,
7
+ Checkbox,
8
+ DialogActions,
9
+ DialogContent,
10
+ FormControlLabel,
11
+ MenuItem,
12
+ TextField,
13
+ } from '@mui/material'
14
+
15
+ import { MsaViewModel } from '../model'
16
+ import colorSchemes from '../colorSchemes'
17
+
18
+ const useStyles = makeStyles()(theme => ({
19
+ field: {
20
+ margin: theme.spacing(4),
21
+ },
22
+ }))
23
+
24
+ export default observer(function ({
25
+ model,
26
+ onClose,
27
+ open,
28
+ }: {
29
+ model: MsaViewModel
30
+ onClose: () => void
31
+ open: boolean
32
+ }) {
33
+ const { classes } = useStyles()
34
+ const { colorSchemeName, noTree } = model
35
+ const [rowHeight, setRowHeight] = useState(`${model.rowHeight}`)
36
+ const [colWidth, setColWidth] = useState(`${model.colWidth}`)
37
+ const [treeWidth, setTreeWidth] = useState(`${model.treeWidth}`)
38
+
39
+ function error(n: string) {
40
+ return Number.isNaN(+n) || +n < 0
41
+ }
42
+ const rowHeightError = error(rowHeight)
43
+ const colWidthError = error(colWidth)
44
+ const treeWidthError = error(treeWidth)
45
+
46
+ return (
47
+ <Dialog onClose={() => onClose()} open={open} title="Settings">
48
+ <DialogContent>
49
+ <FormControlLabel
50
+ control={
51
+ <Checkbox
52
+ checked={model.showBranchLen}
53
+ onChange={() => model.toggleBranchLen()}
54
+ />
55
+ }
56
+ label="Show branch length"
57
+ />
58
+ <FormControlLabel
59
+ control={
60
+ <Checkbox
61
+ checked={model.bgColor}
62
+ onChange={() => model.toggleBgColor()}
63
+ />
64
+ }
65
+ label="Color background"
66
+ />
67
+ <FormControlLabel
68
+ control={
69
+ <Checkbox
70
+ checked={model.drawNodeBubbles}
71
+ onChange={() => model.toggleNodeBubbles()}
72
+ />
73
+ }
74
+ label="Draw node bubbles"
75
+ />
76
+ <FormControlLabel
77
+ control={
78
+ <Checkbox
79
+ checked={model.drawTree}
80
+ onChange={() => model.toggleDrawTree()}
81
+ />
82
+ }
83
+ label="Draw tree (if available)"
84
+ />
85
+ <FormControlLabel
86
+ control={
87
+ <Checkbox
88
+ checked={model.labelsAlignRight}
89
+ onChange={() => model.toggleLabelsAlignRight()}
90
+ />
91
+ }
92
+ label="Labels align right (note: labels may draw over tree, but can adjust tree width or tree area width in UI)"
93
+ />
94
+
95
+ <TextField
96
+ className={classes.field}
97
+ label="Row height (px)"
98
+ value={rowHeight}
99
+ error={rowHeightError}
100
+ onChange={event => setRowHeight(event.target.value)}
101
+ />
102
+ <TextField
103
+ className={classes.field}
104
+ label="Column width (px)"
105
+ value={colWidth}
106
+ error={colWidthError}
107
+ onChange={event => setColWidth(event.target.value)}
108
+ />
109
+ <br />
110
+ {!noTree ? (
111
+ <TextField
112
+ className={classes.field}
113
+ label="Tree width (px)"
114
+ value={treeWidth}
115
+ error={treeWidthError}
116
+ onChange={event => setTreeWidth(event.target.value)}
117
+ />
118
+ ) : null}
119
+
120
+ <br />
121
+
122
+ <TextField
123
+ select
124
+ label="Color scheme"
125
+ value={colorSchemeName}
126
+ onChange={event => model.setColorSchemeName(event.target.value)}
127
+ >
128
+ {Object.keys(colorSchemes).map(option => (
129
+ <MenuItem key={option} value={option}>
130
+ {option}
131
+ </MenuItem>
132
+ ))}
133
+ </TextField>
134
+ <DialogActions>
135
+ <Button
136
+ disabled={rowHeightError || colWidthError || treeWidthError}
137
+ onClick={() => {
138
+ model.setRowHeight(+rowHeight)
139
+ model.setColWidth(+colWidth)
140
+ if (!noTree) {
141
+ model.setTreeWidth(+treeWidth)
142
+ }
143
+ onClose()
144
+ }}
145
+ variant="contained"
146
+ color="primary"
147
+ >
148
+ Submit
149
+ </Button>
150
+ </DialogActions>
151
+ </DialogContent>
152
+ </Dialog>
153
+ )
154
+ })
@@ -0,0 +1,120 @@
1
+ import React, { useRef, useMemo, useEffect } from 'react'
2
+ import { useTheme } from '@mui/material'
3
+ import { observer } from 'mobx-react'
4
+ // locals
5
+ import { MsaViewModel, ITextTrack } from '../model'
6
+ import { colorContrast } from '../util'
7
+
8
+ const AnnotationBlock = observer(function ({
9
+ track,
10
+ model,
11
+ offsetX,
12
+ }: {
13
+ track: ITextTrack
14
+ model: MsaViewModel
15
+ offsetX: number
16
+ }) {
17
+ const {
18
+ blockSize,
19
+ scrollX,
20
+ bgColor,
21
+ colorScheme: modelColorScheme,
22
+ colWidth,
23
+ rowHeight,
24
+ highResScaleFactor,
25
+ } = model
26
+ const {
27
+ model: { customColorScheme, data },
28
+ } = track
29
+
30
+ const colorScheme = customColorScheme || modelColorScheme
31
+ const theme = useTheme()
32
+ const ref = useRef<HTMLCanvasElement>(null)
33
+ const contrastScheme = useMemo(
34
+ () => colorContrast(colorScheme, theme),
35
+ [colorScheme, theme],
36
+ )
37
+ useEffect(() => {
38
+ if (!ref.current) {
39
+ return
40
+ }
41
+
42
+ const ctx = ref.current.getContext('2d')
43
+ if (!ctx) {
44
+ return
45
+ }
46
+
47
+ // this logic is very similar to MSACanvas
48
+ ctx.resetTransform()
49
+ ctx.scale(highResScaleFactor, highResScaleFactor)
50
+ ctx.clearRect(0, 0, blockSize, rowHeight)
51
+ ctx.translate(-offsetX, 0)
52
+ ctx.textAlign = 'center'
53
+ ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
54
+
55
+ const xStart = Math.max(0, Math.floor(offsetX / colWidth))
56
+ const xEnd = Math.max(0, Math.ceil((offsetX + blockSize) / colWidth))
57
+ const str = data?.slice(xStart, xEnd)
58
+ for (let i = 0; str && i < str.length; i++) {
59
+ const letter = str[i]
60
+ const color = colorScheme[letter.toUpperCase()]
61
+ if (bgColor) {
62
+ const x = i * colWidth + offsetX - (offsetX % colWidth)
63
+ ctx.fillStyle = color || 'white'
64
+ ctx.fillRect(x, 0, colWidth, rowHeight)
65
+ if (rowHeight >= 10 && colWidth >= rowHeight / 2) {
66
+ ctx.fillStyle = contrastScheme[letter.toUpperCase()] || 'black'
67
+ ctx.fillText(letter, x + colWidth / 2, rowHeight / 2 + 1) // +1 to avoid cutoff at height:10
68
+ }
69
+ }
70
+ }
71
+ }, [
72
+ bgColor,
73
+ blockSize,
74
+ colWidth,
75
+ rowHeight,
76
+ offsetX,
77
+ contrastScheme,
78
+ colorScheme,
79
+ highResScaleFactor,
80
+ data,
81
+ ])
82
+ return (
83
+ <canvas
84
+ ref={ref}
85
+ height={rowHeight * highResScaleFactor}
86
+ width={blockSize * highResScaleFactor}
87
+ style={{
88
+ position: 'absolute',
89
+ left: scrollX + offsetX,
90
+ width: blockSize,
91
+ height: rowHeight,
92
+ }}
93
+ />
94
+ )
95
+ })
96
+ const AnnotationTrack = observer(function ({
97
+ track,
98
+ model,
99
+ }: {
100
+ track: ITextTrack
101
+ model: MsaViewModel
102
+ }) {
103
+ const { blocksX, msaAreaWidth, rowHeight } = model
104
+ return (
105
+ <div
106
+ style={{
107
+ position: 'relative',
108
+ height: rowHeight,
109
+ width: msaAreaWidth,
110
+ overflow: 'hidden',
111
+ }}
112
+ >
113
+ {blocksX.map(bx => (
114
+ <AnnotationBlock key={bx} track={track} model={model} offsetX={bx} />
115
+ ))}
116
+ </div>
117
+ )
118
+ })
119
+
120
+ export default AnnotationTrack