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,198 @@
1
+ import React, { useRef, useMemo, useEffect } from 'react'
2
+ import { observer } from 'mobx-react'
3
+ import { getSnapshot, isStateTreeNode } from 'mobx-state-tree'
4
+
5
+ // locals
6
+ import { IBoxTrack, MsaViewModel } from '../model'
7
+ import Layout from '../layout'
8
+
9
+ interface Feat {
10
+ start: number
11
+ end: number
12
+ }
13
+
14
+ const BoxTrackBlock = observer(function ({
15
+ track,
16
+ model,
17
+ offsetX,
18
+ }: {
19
+ track: IBoxTrack
20
+ model: MsaViewModel
21
+ offsetX: number
22
+ }) {
23
+ const {
24
+ blockSize,
25
+ colWidth,
26
+ blanks,
27
+ rowHeight,
28
+ highResScaleFactor,
29
+ scrollX,
30
+ } = model
31
+ const { height, features, associatedRowName } = track.model
32
+
33
+ const feats: Feat[] = isStateTreeNode(features)
34
+ ? // @ts-expect-error
35
+ getSnapshot(features)
36
+ : features
37
+
38
+ const layout = useMemo(() => {
39
+ const temp = new Layout()
40
+
41
+ feats?.forEach((feature, index) => {
42
+ const { start, end } = feature
43
+ if (associatedRowName) {
44
+ const s = model.rowSpecificBpToPx(associatedRowName, start - 1)
45
+ const e = model.rowSpecificBpToPx(associatedRowName, end)
46
+ temp.addRect(`${index}`, s, e, rowHeight, feature)
47
+ } else {
48
+ const s = model.globalBpToPx(start - 1)
49
+ const e = model.globalBpToPx(end)
50
+ temp.addRect(`${index}`, s, e, rowHeight, feature)
51
+ }
52
+ })
53
+ return temp
54
+
55
+ // might convert to autorun based drawing
56
+ // eslint-disable-next-line react-hooks/exhaustive-deps
57
+ }, [rowHeight, feats, associatedRowName, model, blanks])
58
+
59
+ const ref = useRef<HTMLCanvasElement>(null)
60
+ const labelRef = useRef<HTMLCanvasElement>(null)
61
+ const mouseoverRef = useRef<HTMLCanvasElement>(null)
62
+
63
+ useEffect(() => {
64
+ if (!ref.current) {
65
+ return
66
+ }
67
+
68
+ const ctx = ref.current.getContext('2d')
69
+ if (!ctx) {
70
+ return
71
+ }
72
+
73
+ ctx.resetTransform()
74
+ ctx.scale(highResScaleFactor, highResScaleFactor)
75
+ ctx.clearRect(0, 0, blockSize, height)
76
+ ctx.translate(-offsetX, 0)
77
+ ctx.textAlign = 'center'
78
+ ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
79
+
80
+ const xStart = Math.max(0, Math.floor(offsetX / colWidth))
81
+ ctx.fillStyle = 'goldenrod'
82
+ layout.rectangles.forEach(value => {
83
+ const { minX, maxX, minY, maxY } = value
84
+
85
+ const x1 = (minX - xStart) * colWidth + offsetX - (offsetX % colWidth)
86
+ const x2 = (maxX - xStart) * colWidth + offsetX - (offsetX % colWidth)
87
+
88
+ if (x2 - x1 > 0) {
89
+ ctx.fillRect(x1, minY, x2 - x1, (maxY - minY) / 2)
90
+ }
91
+ })
92
+ }, [
93
+ associatedRowName,
94
+ blockSize,
95
+ colWidth,
96
+ layout.rectangles,
97
+ model,
98
+ rowHeight,
99
+ height,
100
+ offsetX,
101
+ highResScaleFactor,
102
+ features,
103
+ blanks,
104
+ ])
105
+
106
+ useEffect(() => {
107
+ if (!labelRef.current) {
108
+ return
109
+ }
110
+
111
+ const ctx = labelRef.current.getContext('2d')
112
+ if (!ctx) {
113
+ return
114
+ }
115
+
116
+ // this logic is very similar to MSACanvas
117
+ ctx.resetTransform()
118
+ ctx.scale(highResScaleFactor, highResScaleFactor)
119
+ ctx.clearRect(0, 0, blockSize, height)
120
+ ctx.translate(-offsetX, 0)
121
+ ctx.textAlign = 'center'
122
+ ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
123
+
124
+ ctx.fillStyle = 'black'
125
+ ctx.textAlign = 'left'
126
+ for (const value of layout.rectangles.values()) {
127
+ const { minX, maxX, maxY, minY, data } = value
128
+
129
+ const x1 = minX * colWidth
130
+ const x2 = maxX * colWidth
131
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
132
+ const feature = data as any
133
+
134
+ if (x2 - x1 > 0) {
135
+ const note = feature.attributes?.Note?.[0]
136
+ const name = feature.attributes?.Name?.[0]
137
+ const type = feature.type
138
+ ctx.fillText(
139
+ [type, name, note].filter(f => !!f).join(' - '),
140
+ Math.max(Math.min(-scrollX, x2), x1),
141
+ minY + (maxY - minY),
142
+ )
143
+ }
144
+ }
145
+ }, [
146
+ blockSize,
147
+ colWidth,
148
+ scrollX,
149
+ highResScaleFactor,
150
+ height,
151
+ layout.rectangles,
152
+ offsetX,
153
+ features,
154
+ model,
155
+ rowHeight,
156
+ blanks,
157
+ ])
158
+
159
+ return !features ? null : (
160
+ <>
161
+ <canvas
162
+ ref={ref}
163
+ height={height * highResScaleFactor}
164
+ width={blockSize * highResScaleFactor}
165
+ style={{
166
+ position: 'absolute',
167
+ left: scrollX + offsetX,
168
+ width: blockSize,
169
+ height,
170
+ }}
171
+ />
172
+ <canvas
173
+ ref={labelRef}
174
+ height={height * highResScaleFactor}
175
+ width={blockSize * highResScaleFactor}
176
+ style={{
177
+ position: 'absolute',
178
+ left: scrollX + offsetX,
179
+ width: blockSize,
180
+ height,
181
+ }}
182
+ />
183
+ <canvas
184
+ ref={mouseoverRef}
185
+ height={height * highResScaleFactor}
186
+ width={blockSize * highResScaleFactor}
187
+ style={{
188
+ position: 'absolute',
189
+ left: scrollX + offsetX,
190
+ width: blockSize,
191
+ height,
192
+ }}
193
+ />
194
+ </>
195
+ )
196
+ })
197
+
198
+ export default BoxTrackBlock
@@ -0,0 +1,106 @@
1
+ import React, { Suspense, lazy, useState } from 'react'
2
+ import { IconButton, Typography } from '@mui/material'
3
+ import { observer } from 'mobx-react'
4
+
5
+ // locals
6
+ import { MsaViewModel } from '../model'
7
+
8
+ // icons
9
+ import FolderOpen from '@mui/icons-material/FolderOpen'
10
+ import Settings from '@mui/icons-material/Settings'
11
+ import Help from '@mui/icons-material/Help'
12
+ import Assignment from '@mui/icons-material/Assignment'
13
+ import List from '@mui/icons-material/List'
14
+ import ZoomControls from './ZoomControls'
15
+ import MultiAlignmentSelector from './MultiAlignmentSelector'
16
+
17
+ const SettingsDialog = lazy(() => import('./dialogs/SettingsDlg'))
18
+ const AboutDialog = lazy(() => import('./dialogs/AboutDlg'))
19
+ const DetailsDialog = lazy(() => import('./dialogs/DetailsDlg'))
20
+ const TracklistDialog = lazy(() => import('./dialogs/TracklistDlg'))
21
+
22
+ const InfoArea = observer(({ model }: { model: MsaViewModel }) => {
23
+ const { mouseOverRowName, mouseCol } = model
24
+ return (
25
+ <div>
26
+ <Typography display="inline">Row name: {mouseOverRowName}</Typography>
27
+ <span style={{ marginLeft: 10 }} />
28
+ <Typography display="inline">Position: {mouseCol}</Typography>
29
+ </div>
30
+ )
31
+ })
32
+
33
+ const Header = observer(({ model }: { model: MsaViewModel }) => {
34
+ const [settingsDialogViz, setSettingsDialogViz] = useState(false)
35
+ const [aboutDialogViz, setAboutDialogViz] = useState(false)
36
+ const [detailsDialogViz, setDetailsDialogViz] = useState(false)
37
+ const [tracklistDialogViz, setTracklistDialogViz] = useState(false)
38
+
39
+ return (
40
+ <div style={{ display: 'flex' }}>
41
+ <IconButton
42
+ onClick={async () => {
43
+ try {
44
+ model.setData({ tree: '', msa: '' })
45
+ await model.setTreeFilehandle(undefined)
46
+ await model.setMSAFilehandle(undefined)
47
+ model.setScrollY(0)
48
+ model.setScrollX(0)
49
+ model.setCurrentAlignment(0)
50
+ } catch (e) {
51
+ console.error(e)
52
+ model.setError(e)
53
+ }
54
+ }}
55
+ >
56
+ <FolderOpen />
57
+ </IconButton>
58
+ <IconButton onClick={() => setSettingsDialogViz(true)}>
59
+ <Settings />
60
+ </IconButton>
61
+ <IconButton onClick={() => setDetailsDialogViz(true)}>
62
+ <Assignment />
63
+ </IconButton>
64
+ <IconButton onClick={() => setTracklistDialogViz(true)}>
65
+ <List />
66
+ </IconButton>
67
+ <Suspense fallback={null}>
68
+ {settingsDialogViz ? (
69
+ <SettingsDialog
70
+ model={model}
71
+ onClose={() => setSettingsDialogViz(false)}
72
+ />
73
+ ) : null}
74
+ {aboutDialogViz ? (
75
+ <AboutDialog onClose={() => setAboutDialogViz(false)} />
76
+ ) : null}
77
+ {detailsDialogViz ? (
78
+ <DetailsDialog
79
+ model={model}
80
+ onClose={() => setDetailsDialogViz(false)}
81
+ />
82
+ ) : null}
83
+
84
+ {tracklistDialogViz ? (
85
+ <TracklistDialog
86
+ model={model}
87
+ onClose={() => setTracklistDialogViz(false)}
88
+ />
89
+ ) : null}
90
+ </Suspense>
91
+ <MultiAlignmentSelector model={model} />
92
+ <ZoomControls model={model} />
93
+ <InfoArea model={model} />
94
+ <Spacer />
95
+ <IconButton onClick={() => setAboutDialogViz(true)}>
96
+ <Help />
97
+ </IconButton>
98
+ </div>
99
+ )
100
+ })
101
+
102
+ function Spacer() {
103
+ return <div style={{ flex: 1 }} />
104
+ }
105
+
106
+ export default Header
@@ -0,0 +1,192 @@
1
+ import React, { useState } from 'react'
2
+ import { Button, Container, Grid, Typography, Link } from '@mui/material'
3
+ import { observer } from 'mobx-react'
4
+ import { FileSelector } from '@jbrowse/core/ui'
5
+ import { FileLocation } from '@jbrowse/core/util/types'
6
+
7
+ // locals
8
+ import { MsaViewModel } from '../model'
9
+ import { smallTree, smallMSA, smallMSAOnly } from './data/seq2'
10
+
11
+ async function load(
12
+ model: MsaViewModel,
13
+ msaFile?: FileLocation,
14
+ treeFile?: FileLocation,
15
+ ) {
16
+ model.setError(undefined)
17
+ try {
18
+ if (msaFile) {
19
+ await model.setMSAFilehandle(msaFile)
20
+ }
21
+ if (treeFile) {
22
+ await model.setTreeFilehandle(treeFile)
23
+ }
24
+ } catch (e) {
25
+ model.setError(e)
26
+ }
27
+ }
28
+
29
+ const ListItem = ({
30
+ onClick,
31
+ model,
32
+ children,
33
+ }: {
34
+ onClick: () => void
35
+ model: MsaViewModel
36
+ children: React.ReactNode
37
+ }) => (
38
+ <li>
39
+ <Link
40
+ onClick={event => {
41
+ model.setError(undefined)
42
+ event.preventDefault()
43
+ onClick()
44
+ }}
45
+ href="#"
46
+ >
47
+ <Typography display="inline">{children}</Typography>
48
+ </Link>
49
+ </li>
50
+ )
51
+ export default observer(({ model }: { model: MsaViewModel }) => {
52
+ const [msaFile, setMsaFile] = useState<FileLocation>()
53
+ const [treeFile, setTreeFile] = useState<FileLocation>()
54
+ const { error } = model
55
+
56
+ return (
57
+ <Container>
58
+ <div style={{ width: '50%' }}>
59
+ {error ? (
60
+ <div style={{ padding: 20 }}>
61
+ <Typography color="error">Error: {`${error}`}</Typography>
62
+ </div>
63
+ ) : null}
64
+ <Typography>
65
+ Open an MSA file (stockholm or clustal format) and/or a tree file
66
+ (newick format).
67
+ </Typography>
68
+ <Typography color="error">
69
+ Note: you can open up just an MSA or just a tree, both are not
70
+ required. Some MSA files e.g. stockholm format have an embedded tree
71
+ also and this is fine, and opening a separate tree file is not
72
+ required.
73
+ </Typography>
74
+ </div>
75
+
76
+ <Grid container spacing={10} justifyContent="center" alignItems="center">
77
+ <Grid item>
78
+ <Typography>MSA file or URL</Typography>
79
+ <FileSelector location={msaFile} setLocation={setMsaFile} />
80
+ <Typography>Tree file or URL</Typography>
81
+ <FileSelector location={treeFile} setLocation={setTreeFile} />
82
+ </Grid>
83
+
84
+ <Grid item>
85
+ <Button
86
+ onClick={() => load(model, msaFile, treeFile)}
87
+ variant="contained"
88
+ color="primary"
89
+ disabled={!msaFile && !treeFile}
90
+ >
91
+ Open
92
+ </Button>
93
+ </Grid>
94
+
95
+ <Grid item>
96
+ <Typography>Examples</Typography>
97
+ <ul>
98
+ <ListItem
99
+ model={model}
100
+ onClick={() =>
101
+ load(model, undefined, {
102
+ uri: 'https://jbrowse.org/genomes/newick_trees/sarscov2phylo.pub.ft.nh',
103
+ locationType: 'UriLocation',
104
+ })
105
+ }
106
+ >
107
+ 230k COVID-19 samples (tree only)
108
+ </ListItem>
109
+ <ListItem
110
+ model={model}
111
+ onClick={() => {
112
+ model.setData({ msa: smallMSA, tree: smallTree })
113
+ }}
114
+ >
115
+ Small protein MSA+tree
116
+ </ListItem>
117
+ <ListItem
118
+ model={model}
119
+ onClick={() => {
120
+ model.setData({ msa: smallMSAOnly })
121
+ }}
122
+ >
123
+ Small MSA only
124
+ </ListItem>
125
+ <ListItem
126
+ model={model}
127
+ onClick={() =>
128
+ load(model, {
129
+ uri: 'https://ihh.github.io/abrowse/build/pfam-cov2.stock',
130
+ locationType: 'UriLocation',
131
+ })
132
+ }
133
+ >
134
+ PFAM SARS-CoV2 multi-stockholm
135
+ </ListItem>
136
+ <ListItem
137
+ model={model}
138
+ onClick={() =>
139
+ load(model, {
140
+ uri: 'https://jbrowse.org/genomes/multiple_sequence_alignments/Lysine.stock',
141
+ locationType: 'UriLocation',
142
+ })
143
+ }
144
+ >
145
+ Lysine stockholm file
146
+ </ListItem>
147
+ <ListItem
148
+ model={model}
149
+ onClick={() =>
150
+ load(model, {
151
+ uri: 'https://jbrowse.org/genomes/multiple_sequence_alignments/PF01601_full.txt',
152
+ locationType: 'UriLocation',
153
+ })
154
+ }
155
+ >
156
+ PF01601 stockholm file (SARS-CoV2 spike protein)
157
+ </ListItem>
158
+ <ListItem
159
+ model={model}
160
+ onClick={() =>
161
+ load(model, {
162
+ uri: 'https://jbrowse.org/genomes/multiple_sequence_alignments/europe_covid.fa',
163
+ locationType: 'UriLocation',
164
+ })
165
+ }
166
+ >
167
+ Europe COVID full genomes (LR883044.1 and 199 other sequences)
168
+ </ListItem>
169
+ <ListItem
170
+ model={model}
171
+ onClick={() =>
172
+ load(
173
+ model,
174
+ {
175
+ uri: 'https://jbrowse.org/genomes/multiple_sequence_alignments/rhv_test-only.aligned_with_mafft_auto.fa',
176
+ locationType: 'UriLocation',
177
+ },
178
+ {
179
+ uri: 'https://jbrowse.org/genomes/multiple_sequence_alignments/rhv_test-only.aligned_with_mafft_auto.nh',
180
+ locationType: 'UriLocation',
181
+ },
182
+ )
183
+ }
184
+ >
185
+ MAFFT+VeryFastTree(17.9k samples)
186
+ </ListItem>
187
+ </ul>
188
+ </Grid>
189
+ </Grid>
190
+ </Container>
191
+ )
192
+ })
@@ -0,0 +1,164 @@
1
+ import React, { useEffect, useRef, useMemo } from 'react'
2
+ import { useTheme } from '@mui/material'
3
+ import { observer } from 'mobx-react'
4
+
5
+ // locals
6
+ import { MsaViewModel } from '../model'
7
+ import { colorContrast } from '../util'
8
+ import { getClustalXColor, getPercentIdentityColor } from '../colorSchemes'
9
+
10
+ const MSABlock = observer(function ({
11
+ model,
12
+ offsetX,
13
+ offsetY,
14
+ }: {
15
+ model: MsaViewModel
16
+ offsetX: number
17
+ offsetY: number
18
+ }) {
19
+ const {
20
+ MSA,
21
+ colWidth,
22
+ bgColor,
23
+ columns,
24
+ rowHeight,
25
+ scrollY,
26
+ scrollX,
27
+ hierarchy,
28
+ colorScheme,
29
+ colorSchemeName,
30
+ blockSize,
31
+ highResScaleFactor,
32
+ colStats,
33
+ } = model
34
+ const theme = useTheme()
35
+
36
+ const contrastScheme = useMemo(
37
+ () => colorContrast(colorScheme, theme),
38
+ [colorScheme, theme],
39
+ )
40
+
41
+ const ref = useRef<HTMLCanvasElement>(null)
42
+ useEffect(() => {
43
+ if (!ref.current) {
44
+ return
45
+ }
46
+
47
+ const ctx = ref.current.getContext('2d')
48
+ if (!ctx) {
49
+ return
50
+ }
51
+
52
+ ctx.resetTransform()
53
+ ctx.scale(highResScaleFactor, highResScaleFactor)
54
+ ctx.clearRect(0, 0, blockSize, blockSize)
55
+ ctx.translate(-offsetX, rowHeight / 2 - offsetY)
56
+ ctx.textAlign = 'center'
57
+ ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
58
+
59
+ const leaves = hierarchy.leaves()
60
+ const b = blockSize
61
+
62
+ // slice vertical rows, e.g. tree leaves, avoid negative slice
63
+ const yStart = Math.max(0, Math.floor((offsetY - rowHeight) / rowHeight))
64
+ const yEnd = Math.max(0, Math.ceil((offsetY + b + rowHeight) / rowHeight))
65
+
66
+ // slice horizontal visible letters, avoid negative slice
67
+ const xStart = Math.max(0, Math.floor(offsetX / colWidth))
68
+ const xEnd = Math.max(0, Math.ceil((offsetX + b) / colWidth))
69
+ const visibleLeaves = leaves.slice(yStart, yEnd)
70
+ for (const node of visibleLeaves) {
71
+ const {
72
+ // @ts-expect-error
73
+ x: y,
74
+ data: { name },
75
+ } = node
76
+
77
+ const str = columns[name]?.slice(xStart, xEnd)
78
+ for (let i = 0; i < str?.length; i++) {
79
+ const letter = str[i]
80
+ const color =
81
+ colorSchemeName === 'clustalx_protein_dynamic'
82
+ ? getClustalXColor(colStats[xStart + i], model, name, xStart + i)
83
+ : colorSchemeName === 'percent_identity_dynamic'
84
+ ? getPercentIdentityColor(
85
+ colStats[xStart + i],
86
+ model,
87
+ name,
88
+ xStart + i,
89
+ )
90
+ : colorScheme[letter.toUpperCase()]
91
+ if (bgColor) {
92
+ const x = i * colWidth + offsetX - (offsetX % colWidth)
93
+ ctx.fillStyle = color || 'white'
94
+ ctx.fillRect(x, y - rowHeight, colWidth, rowHeight)
95
+ }
96
+ }
97
+ }
98
+
99
+ if (rowHeight >= 5 && colWidth > rowHeight / 2) {
100
+ for (const node of visibleLeaves) {
101
+ const {
102
+ // @ts-expect-error
103
+ x: y,
104
+ data: { name },
105
+ } = node
106
+
107
+ const str = columns[name]?.slice(xStart, xEnd)
108
+ for (let i = 0; i < str?.length; i++) {
109
+ const letter = str[i]
110
+ const color = colorScheme[letter.toUpperCase()]
111
+ const contrast = contrastScheme[letter.toUpperCase()] || 'black'
112
+ const x = i * colWidth + offsetX - (offsetX % colWidth)
113
+
114
+ // note: -rowHeight/4 matches +rowHeight/4 in tree
115
+ ctx.fillStyle = bgColor ? contrast : color || 'black'
116
+ ctx.fillText(letter, x + colWidth / 2, y - rowHeight / 4)
117
+ }
118
+ }
119
+ }
120
+ // eslint-disable-next-line react-hooks/exhaustive-deps
121
+ }, [
122
+ MSA,
123
+ highResScaleFactor,
124
+ columns,
125
+ colorScheme,
126
+ contrastScheme,
127
+ bgColor,
128
+ rowHeight,
129
+ colWidth,
130
+ hierarchy,
131
+ offsetX,
132
+ offsetY,
133
+ blockSize,
134
+ ])
135
+ return (
136
+ <canvas
137
+ ref={ref}
138
+ onMouseMove={event => {
139
+ if (!ref.current) {
140
+ return
141
+ }
142
+ const { left, top } = ref.current.getBoundingClientRect()
143
+ const mouseX = event.clientX - left + offsetX
144
+ const mouseY = event.clientY - top + offsetY
145
+ model.setMousePos(
146
+ Math.floor(mouseX / colWidth) + 1,
147
+ Math.floor(mouseY / rowHeight),
148
+ )
149
+ }}
150
+ onMouseLeave={() => model.setMousePos()}
151
+ width={blockSize * highResScaleFactor}
152
+ height={blockSize * highResScaleFactor}
153
+ style={{
154
+ position: 'absolute',
155
+ top: scrollY + offsetY,
156
+ left: scrollX + offsetX,
157
+ width: blockSize,
158
+ height: blockSize,
159
+ }}
160
+ />
161
+ )
162
+ })
163
+
164
+ export default MSABlock