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
package/src/model.ts ADDED
@@ -0,0 +1,790 @@
1
+ import { Instance, cast, types, addDisposer, SnapshotIn } from 'mobx-state-tree'
2
+ import { hierarchy, cluster, HierarchyNode } from 'd3-hierarchy'
3
+ import { ascending } from 'd3-array'
4
+ import { FileLocation, ElementId } from '@jbrowse/core/util/types/mst'
5
+ import { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
6
+ import { openLocation } from '@jbrowse/core/util/io'
7
+ import { autorun } from 'mobx'
8
+ import BaseViewModel from '@jbrowse/core/pluggableElementTypes/models/BaseViewModel'
9
+ import Stockholm from 'stockholm-js'
10
+
11
+ export interface RowDetails {
12
+ [key: string]: unknown
13
+ name: string
14
+ range?: { start: number; end: number }
15
+ }
16
+
17
+ // locals
18
+ import {
19
+ collapse,
20
+ generateNodeIds,
21
+ maxLength,
22
+ setBrLength,
23
+ skipBlanks,
24
+ clamp,
25
+ NodeWithIds,
26
+ NodeWithIdsAndLength,
27
+ } from './util'
28
+ import TextTrack from './components/TextTrack'
29
+ import BoxTrack from './components/BoxTrack'
30
+ import ClustalMSA from './parsers/ClustalMSA'
31
+ import StockholmMSA from './parsers/StockholmMSA'
32
+ import FastaMSA from './parsers/FastaMSA'
33
+ import parseNewick from './parseNewick'
34
+ import colorSchemes from './colorSchemes'
35
+ import { UniprotTrack } from './UniprotTrack'
36
+ import { StructureModel } from './StructureModel'
37
+
38
+ interface BasicTrackModel {
39
+ id: string
40
+ name: string
41
+ associatedRowName?: string
42
+ height: number
43
+ }
44
+
45
+ export interface TextTrackModel extends BasicTrackModel {
46
+ customColorScheme?: { [key: string]: string }
47
+ data: string
48
+ }
49
+
50
+ export interface BoxTrackModel extends BasicTrackModel {
51
+ features: { start: number; end: number }[]
52
+ }
53
+ export interface ITextTrack {
54
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
55
+ ReactComponent: React.FC<any>
56
+ model: TextTrackModel
57
+ }
58
+
59
+ export interface IBoxTrack {
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ ReactComponent: React.FC<any>
62
+ model: BoxTrackModel
63
+ }
64
+
65
+ type BasicTrack = IBoxTrack | ITextTrack
66
+
67
+ const MSAModel = types
68
+ .model('MsaView', {
69
+ id: ElementId,
70
+ type: types.literal('MsaView'),
71
+ height: types.optional(types.number, 550),
72
+ treeAreaWidth: types.optional(types.number, 400),
73
+ treeWidth: types.optional(types.number, 300),
74
+ rowHeight: 20,
75
+ scrollY: 0,
76
+ scrollX: 0,
77
+ resizeHandleWidth: 5,
78
+ blockSize: 1000,
79
+ mouseRow: types.maybe(types.number),
80
+ mouseCol: types.maybe(types.number),
81
+ selectedStructures: types.array(StructureModel),
82
+ labelsAlignRight: false,
83
+ colWidth: 16,
84
+ showBranchLen: true,
85
+ bgColor: true,
86
+ drawTree: true,
87
+ drawNodeBubbles: true,
88
+ highResScaleFactor: 2,
89
+ colorSchemeName: 'maeditor',
90
+ treeFilehandle: types.maybe(FileLocation),
91
+ msaFilehandle: types.maybe(FileLocation),
92
+ currentAlignment: 0,
93
+ collapsed: types.array(types.string),
94
+ showOnly: types.maybe(types.string),
95
+ boxTracks: types.array(UniprotTrack),
96
+ turnedOffTracks: types.map(types.boolean),
97
+ annotatedRegions: types.array(
98
+ types.model({
99
+ start: types.number,
100
+ end: types.number,
101
+ attributes: types.frozen<Record<string, string[]>>(),
102
+ }),
103
+ ),
104
+ data: types.optional(
105
+ types
106
+ .model({
107
+ tree: types.maybe(types.string),
108
+ msa: types.maybe(types.string),
109
+ })
110
+ .actions(self => ({
111
+ setTree(tree?: string) {
112
+ self.tree = tree
113
+ },
114
+ setMSA(msa?: string) {
115
+ self.msa = msa
116
+ },
117
+ })),
118
+ { tree: '', msa: '' },
119
+ ),
120
+ })
121
+ .volatile(() => ({
122
+ error: undefined as unknown,
123
+ margin: { left: 20, top: 20 },
124
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
125
+ DialogComponent: undefined as undefined | React.FC<any>,
126
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
127
+ DialogProps: undefined as any,
128
+
129
+ // annotations
130
+ annotPos: undefined as { left: number; right: number } | undefined,
131
+ }))
132
+ .actions(self => ({
133
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
134
+ setDialogComponent(dlg: React.FC<any> | undefined, props: any) {
135
+ self.DialogComponent = dlg
136
+ self.DialogProps = props
137
+ },
138
+ setHeight(height: number) {
139
+ self.height = height
140
+ },
141
+ addStructureToSelection(elt: SnapshotIn<typeof StructureModel>) {
142
+ self.selectedStructures.push(elt)
143
+ },
144
+ removeStructureFromSelection(elt: SnapshotIn<typeof StructureModel>) {
145
+ const r = self.selectedStructures.find(node => node.id === elt.id)
146
+ if (r) {
147
+ self.selectedStructures.remove(r)
148
+ }
149
+ },
150
+ toggleStructureSelection(elt: {
151
+ id: string
152
+ structure: { startPos: number; endPos: number; pdb: string }
153
+ }) {
154
+ const r = self.selectedStructures.find(node => node.id === elt.id)
155
+ if (r) {
156
+ self.selectedStructures.remove(r)
157
+ } else {
158
+ self.selectedStructures.push(elt)
159
+ }
160
+ },
161
+ clearSelectedStructures() {
162
+ // @ts-expect-error
163
+ self.selectedStructures = []
164
+ },
165
+ setError(error?: unknown) {
166
+ self.error = error
167
+ },
168
+ setMousePos(col?: number, row?: number) {
169
+ self.mouseCol = col
170
+ self.mouseRow = row
171
+ },
172
+ setRowHeight(n: number) {
173
+ self.rowHeight = n
174
+ },
175
+ setColWidth(n: number) {
176
+ self.colWidth = n
177
+ },
178
+ setColorSchemeName(name: string) {
179
+ self.colorSchemeName = name
180
+ },
181
+ setScrollY(n: number) {
182
+ self.scrollY = n
183
+ },
184
+ setScrollX(n: number) {
185
+ self.scrollX = n
186
+ },
187
+ setTreeAreaWidth(n: number) {
188
+ self.treeAreaWidth = n
189
+ },
190
+ setTreeWidth(n: number) {
191
+ self.treeWidth = n
192
+ },
193
+ setCurrentAlignment(n: number) {
194
+ self.currentAlignment = n
195
+ },
196
+ toggleLabelsAlignRight() {
197
+ self.labelsAlignRight = !self.labelsAlignRight
198
+ },
199
+ toggleDrawTree() {
200
+ self.drawTree = !self.drawTree
201
+ },
202
+ toggleCollapsed(node: string) {
203
+ if (self.collapsed.includes(node)) {
204
+ self.collapsed.remove(node)
205
+ } else {
206
+ self.collapsed.push(node)
207
+ }
208
+ },
209
+ setShowOnly(node?: string) {
210
+ self.showOnly = node
211
+ },
212
+ toggleBranchLen() {
213
+ self.showBranchLen = !self.showBranchLen
214
+ },
215
+ toggleBgColor() {
216
+ self.bgColor = !self.bgColor
217
+ },
218
+ toggleNodeBubbles() {
219
+ self.drawNodeBubbles = !self.drawNodeBubbles
220
+ },
221
+ setData(data: { msa?: string; tree?: string }) {
222
+ self.data = cast(data)
223
+ },
224
+ async setMSAFilehandle(msaFilehandle?: FileLocationType) {
225
+ self.msaFilehandle = msaFilehandle
226
+ },
227
+ async setTreeFilehandle(treeFilehandle?: FileLocationType) {
228
+ if (treeFilehandle && 'blobId' in treeFilehandle) {
229
+ const r = await openLocation(treeFilehandle).readFile('utf8')
230
+ this.setTree(r)
231
+ } else {
232
+ self.treeFilehandle = treeFilehandle
233
+ }
234
+ },
235
+ setMSA(result: string) {
236
+ self.data.setMSA(result)
237
+ },
238
+ setTree(result: string) {
239
+ self.data.setTree(result)
240
+ },
241
+
242
+ afterCreate() {
243
+ addDisposer(
244
+ self,
245
+ autorun(async () => {
246
+ const { treeFilehandle } = self
247
+ if (treeFilehandle) {
248
+ try {
249
+ this.setTree(await openLocation(treeFilehandle).readFile('utf8'))
250
+ } catch (e) {
251
+ console.error(e)
252
+ this.setError(e)
253
+ }
254
+ }
255
+ }),
256
+ )
257
+ addDisposer(
258
+ self,
259
+ autorun(async () => {
260
+ const { msaFilehandle } = self
261
+
262
+ if (msaFilehandle) {
263
+ try {
264
+ this.setMSA(await openLocation(msaFilehandle).readFile('utf8'))
265
+ } catch (e) {
266
+ console.error(e)
267
+ this.setError(e)
268
+ }
269
+ }
270
+ }),
271
+ )
272
+ },
273
+ }))
274
+ .views(self => {
275
+ let oldBlocksX: number[] = []
276
+ let oldBlocksY: number[] = []
277
+ let oldValX = 0
278
+ let oldValY = 0
279
+ return {
280
+ get initialized() {
281
+ return (
282
+ (self.data.msa ||
283
+ self.data.tree ||
284
+ self.msaFilehandle ||
285
+ self.treeFilehandle) &&
286
+ !self.error
287
+ )
288
+ },
289
+
290
+ get blocksX() {
291
+ const { scrollX, blockSize: size, colWidth } = self
292
+ const ret = -(size * Math.floor(scrollX / size)) - size
293
+
294
+ const b = []
295
+ for (let i = ret; i < ret + size * 3; i += size) {
296
+ if (i + size > 0) {
297
+ b.push(i)
298
+ }
299
+ }
300
+ if (
301
+ JSON.stringify(b) !== JSON.stringify(oldBlocksX) ||
302
+ colWidth !== oldValX
303
+ ) {
304
+ oldBlocksX = b
305
+ oldValX = colWidth
306
+ }
307
+ return oldBlocksX
308
+ },
309
+
310
+ get blocksY() {
311
+ const { scrollY, blockSize: size, rowHeight } = self
312
+ const ret = -(size * Math.floor(scrollY / size)) - 2 * size
313
+
314
+ const b = []
315
+ for (let i = ret; i < ret + size * 3; i += size) {
316
+ if (i + size > 0) {
317
+ b.push(i)
318
+ }
319
+ }
320
+ if (
321
+ JSON.stringify(b) !== JSON.stringify(oldBlocksY) ||
322
+ rowHeight !== oldValY
323
+ ) {
324
+ oldBlocksY = b
325
+ oldValY = rowHeight
326
+ }
327
+ return oldBlocksY
328
+ },
329
+ }
330
+ })
331
+ .views(self => ({
332
+ get blocks2d() {
333
+ return self.blocksY.flatMap(by => self.blocksX.map(bx => [bx, by]))
334
+ },
335
+ get done() {
336
+ return self.initialized && (self.data.msa || self.data.tree)
337
+ },
338
+
339
+ get colorScheme() {
340
+ return colorSchemes[self.colorSchemeName]
341
+ },
342
+
343
+ get header() {
344
+ return this.MSA?.getHeader() || {}
345
+ },
346
+
347
+ getRowData(name: string) {
348
+ const matches = name.match(/\S+\/(\d+)-(\d+)/)
349
+ return {
350
+ data: this.MSA?.getRowData(name) || ({} as Record<string, unknown>),
351
+ ...(matches && { range: { start: +matches[1], end: +matches[2] } }),
352
+ }
353
+ },
354
+
355
+ get currentAlignmentName() {
356
+ return this.alignmentNames[self.currentAlignment]
357
+ },
358
+
359
+ get alignmentNames() {
360
+ return this.MSA?.alignmentNames || []
361
+ },
362
+
363
+ get noTree() {
364
+ return !!this.tree.noTree
365
+ },
366
+
367
+ get menuItems() {
368
+ return []
369
+ },
370
+
371
+ get MSA() {
372
+ const text = self.data.msa
373
+ if (text) {
374
+ if (Stockholm.sniff(text)) {
375
+ return new StockholmMSA(text, self.currentAlignment)
376
+ } else if (text.startsWith('>')) {
377
+ return new FastaMSA(text)
378
+ } else {
379
+ return new ClustalMSA(text)
380
+ }
381
+ }
382
+ return null
383
+ },
384
+
385
+ get numColumns() {
386
+ return ((this.MSA?.getWidth() || 0) - this.blanks.length) * self.colWidth
387
+ },
388
+
389
+ get tree(): NodeWithIds {
390
+ return self.data.tree
391
+ ? generateNodeIds(parseNewick(self.data.tree))
392
+ : this.MSA?.getTree() || {
393
+ noTree: true,
394
+ branchset: [],
395
+ id: 'empty',
396
+ name: 'empty',
397
+ }
398
+ },
399
+
400
+ get rowNames(): string[] {
401
+ return this.hierarchy.leaves().map(node => node.data.name)
402
+ },
403
+
404
+ get mouseOverRowName() {
405
+ return self.mouseRow !== undefined
406
+ ? this.rowNames[self.mouseRow]
407
+ : undefined
408
+ },
409
+
410
+ getMouseOverResidue(rowName: string) {
411
+ return this.columns[rowName]
412
+ },
413
+
414
+ get root() {
415
+ let hier = hierarchy(this.tree, d => d.branchset)
416
+ .sum(d => (d.branchset ? 0 : 1))
417
+ .sort((a, b) => ascending(a.data.length || 1, b.data.length || 1))
418
+ if (self.showOnly) {
419
+ const res = hier.find(node => node.data.id === self.showOnly)
420
+ if (res) {
421
+ hier = res
422
+ }
423
+ }
424
+
425
+ if (self.collapsed.length) {
426
+ self.collapsed
427
+ .map(collapsedId => hier.find(node => node.data.id === collapsedId))
428
+ .filter((f): f is HierarchyNode<NodeWithIds> => !!f)
429
+ .map(node => collapse(node))
430
+ }
431
+ return hier
432
+ },
433
+
434
+ get structures(): {
435
+ [key: string]: {
436
+ pdb: string
437
+ startPos: number
438
+ endPos: number
439
+ }[]
440
+ } {
441
+ return this.MSA?.getStructures() || {}
442
+ },
443
+
444
+ get inverseStructures() {
445
+ return Object.fromEntries(
446
+ Object.entries(this.structures).flatMap(([key, val]) =>
447
+ val.map(pdbEntry => [pdbEntry.pdb, { id: key }]),
448
+ ),
449
+ )
450
+ },
451
+
452
+ get msaAreaWidth() {
453
+ // @ts-expect-error
454
+ return self.width - self.treeAreaWidth
455
+ },
456
+
457
+ get blanks() {
458
+ const blanks = []
459
+ const strs = this.hierarchy
460
+ .leaves()
461
+ .map(({ data }) => this.MSA?.getRow(data.name))
462
+ .filter((item): item is string[] => !!item)
463
+
464
+ for (let i = 0; i < strs[0]?.length; i++) {
465
+ let counter = 0
466
+ for (let j = 0; j < strs.length; j++) {
467
+ if (strs[j][i] === '-') {
468
+ counter++
469
+ }
470
+ }
471
+ if (counter === strs.length) {
472
+ blanks.push(i)
473
+ }
474
+ }
475
+ return blanks
476
+ },
477
+
478
+ get rows() {
479
+ return this.hierarchy
480
+ .leaves()
481
+ .map(({ data }) => [data.name, this.MSA?.getRow(data.name)] as const)
482
+ .filter((f): f is [string, string[]] => !!f[1])
483
+ },
484
+
485
+ get columns() {
486
+ return Object.fromEntries(
487
+ this.rows.map((row, index) => [row[0], this.columns2d[index]] as const),
488
+ )
489
+ },
490
+
491
+ get columns2d() {
492
+ return this.rows.map(r => r[1]).map(str => skipBlanks(this.blanks, str))
493
+ },
494
+
495
+ get colStats() {
496
+ const r = [] as { [key: string]: number }[]
497
+ const m = this.columns2d
498
+ for (let i = 0; i < m.length; i++) {
499
+ for (let j = 0; j < m[i].length; j++) {
500
+ const l = r[j] || {}
501
+ if (!l[m[i][j]]) {
502
+ l[m[i][j]] = 0
503
+ }
504
+ l[m[i][j]]++
505
+ r[j] = l
506
+ }
507
+ }
508
+ return r
509
+ },
510
+
511
+ // generates a new tree that is clustered with x,y positions
512
+ get hierarchy(): HierarchyNode<NodeWithIdsAndLength> {
513
+ const root = this.root
514
+ const clust = cluster<NodeWithIds>()
515
+ .size([this.totalHeight, self.treeWidth])
516
+ .separation(() => 1)
517
+ clust(root)
518
+ setBrLength(
519
+ root,
520
+ (root.data.length = 0),
521
+ self.treeWidth / maxLength(root),
522
+ )
523
+ return root as HierarchyNode<NodeWithIdsAndLength>
524
+ },
525
+
526
+ get totalHeight() {
527
+ return this.root.leaves().length * self.rowHeight
528
+ },
529
+ }))
530
+ .actions(self => ({
531
+ addUniprotTrack(node: { name: string; accession: string }) {
532
+ if (self.boxTracks.some(t => t.name === node.name)) {
533
+ if (self.turnedOffTracks.has(node.name)) {
534
+ this.toggleTrack(node.name)
535
+ }
536
+ } else {
537
+ self.boxTracks.push({
538
+ ...node,
539
+ id: node.name,
540
+ associatedRowName: node.name,
541
+ })
542
+ }
543
+ },
544
+
545
+ doScrollY(deltaY: number) {
546
+ self.scrollY = clamp(-self.totalHeight + 10, self.scrollY + deltaY, 0)
547
+ },
548
+
549
+ doScrollX(deltaX: number) {
550
+ self.scrollX = clamp(
551
+ -self.numColumns + (self.msaAreaWidth - 100),
552
+ self.scrollX + deltaX,
553
+ 0,
554
+ )
555
+ },
556
+ setMouseoveredColumn(n: number, chain: string, file: string) {
557
+ let j = 0
558
+ let i = 0
559
+ const { id } = self.inverseStructures[file.slice(0, -4)] || {}
560
+ const row = self.MSA?.getRow(id)
561
+
562
+ if (row) {
563
+ for (i = 0; i < row.length && j < n; i++) {
564
+ if (row[i] !== '-') {
565
+ j++
566
+ }
567
+ }
568
+ self.mouseCol = j + 1
569
+ } else {
570
+ self.mouseCol = undefined
571
+ }
572
+ },
573
+
574
+ toggleTrack(id: string) {
575
+ if (self.turnedOffTracks.has(id)) {
576
+ self.turnedOffTracks.delete(id)
577
+ } else {
578
+ self.turnedOffTracks.set(id, true)
579
+ }
580
+ },
581
+ }))
582
+ .views(self => ({
583
+ get secondaryStructureConsensus() {
584
+ return self.MSA?.secondaryStructureConsensus
585
+ },
586
+
587
+ get seqConsensus() {
588
+ return self.MSA?.seqConsensus
589
+ },
590
+
591
+ get conservation() {
592
+ const m = self.columns2d
593
+
594
+ if (m.length) {
595
+ for (let i = 0; i < m[0].length; i++) {
596
+ const col = []
597
+ for (let j = 0; j < m.length; j++) {
598
+ col.push(m[j][i])
599
+ }
600
+ }
601
+ }
602
+ return ['a']
603
+ },
604
+
605
+ get tracks(): BasicTrack[] {
606
+ const blanks = self.blanks
607
+ const adapterTracks = self.MSA
608
+ ? self.MSA.tracks.map(track => {
609
+ const { data } = track
610
+ return {
611
+ model: {
612
+ ...track,
613
+ data: data ? skipBlanks(blanks, data) : undefined,
614
+ height: self.rowHeight,
615
+ } as TextTrackModel,
616
+ ReactComponent: TextTrack,
617
+ }
618
+ })
619
+ : ([] as BasicTrack[])
620
+
621
+ const boxTracks = self.boxTracks
622
+ // filter out tracks that are associated with hidden rows
623
+ .filter(track => !!self.rows.some(row => row[0] === track.name))
624
+ .map(track => ({
625
+ model: track as BoxTrackModel,
626
+ ReactComponent: BoxTrack,
627
+ }))
628
+
629
+ const annotationTracks =
630
+ self.annotatedRegions.length > 0
631
+ ? [
632
+ {
633
+ model: {
634
+ features: self.annotatedRegions,
635
+ height: 100,
636
+ id: 'annotations',
637
+ name: 'User-created annotations',
638
+ data: self.annotatedRegions
639
+ .map(region => {
640
+ const attrs = region.attributes
641
+ ? Object.entries(region.attributes)
642
+ .map(([k, v]) => `${k}=${v.join(',')}`)
643
+ .join(';')
644
+ : '.'
645
+ return [
646
+ 'MSA_refcoord',
647
+ '.',
648
+ '.',
649
+ region.start,
650
+ region.end,
651
+ '.',
652
+ '.',
653
+ '.',
654
+ attrs,
655
+ ].join('\t')
656
+ })
657
+ .join('\n'),
658
+ } as BoxTrackModel,
659
+ ReactComponent: BoxTrack,
660
+ },
661
+ ]
662
+ : ([] as BasicTrack[])
663
+
664
+ return [...adapterTracks, ...boxTracks, ...annotationTracks]
665
+ },
666
+
667
+ get turnedOnTracks() {
668
+ return this.tracks.filter(f => !self.turnedOffTracks.has(f.model.id))
669
+ },
670
+
671
+ // returns coordinate in the current relative coordinate scheme
672
+ pxToBp(coord: number) {
673
+ return Math.floor((coord - self.scrollX) / self.colWidth)
674
+ },
675
+
676
+ rowSpecificBpToPx(rowName: string, position: number) {
677
+ const { rowNames, rows, blanks } = self
678
+ const index = rowNames.indexOf(rowName)
679
+ const row = rows[index][1]
680
+ const details = self.getRowData(rowName)
681
+ const offset = details.range?.start || 0
682
+ const current = position - offset
683
+
684
+ if (current < 0) {
685
+ return 0
686
+ }
687
+
688
+ let j = 0
689
+ let i = 0
690
+
691
+ for (; i < row.length; i++) {
692
+ if (row[i] !== '-' && j++ === current) {
693
+ break
694
+ }
695
+ }
696
+
697
+ let count = 0
698
+ for (let k = 0; k < row.length; k++) {
699
+ if (blanks.includes(k) && k < i + 1) {
700
+ count++
701
+ }
702
+ }
703
+
704
+ return i - count
705
+ },
706
+ globalBpToPx(position: number) {
707
+ let count = 0
708
+ for (let k = 0; k < self.rows[0]?.[1].length; k++) {
709
+ if (self.blanks.includes(k) && k < position + 1) {
710
+ count++
711
+ }
712
+ }
713
+
714
+ return position - count
715
+ },
716
+
717
+ relativePxToBp(rowName: string, position: number) {
718
+ const { rowNames, rows } = self
719
+ const index = rowNames.indexOf(rowName)
720
+ const row = rows[index][1]
721
+
722
+ let k = 0
723
+ for (let i = 0; i < position; i++) {
724
+ if (row[i] !== '-') {
725
+ k++
726
+ } else if (k >= position) {
727
+ break
728
+ }
729
+ }
730
+ return k
731
+ },
732
+
733
+ getPos(pos: number) {
734
+ let j = 0
735
+ for (let i = 0, k = 0; i < pos; i++, j++) {
736
+ while (j === self.blanks[k]) {
737
+ k++
738
+ j++
739
+ }
740
+ }
741
+ return j
742
+ },
743
+ }))
744
+ .actions(self => ({
745
+ addAnnotation(
746
+ start: number,
747
+ end: number,
748
+ attributes: { [key: string]: string[] },
749
+ ) {
750
+ self.annotatedRegions.push({
751
+ start: self.getPos(start),
752
+ end: self.getPos(end),
753
+ attributes,
754
+ })
755
+ },
756
+
757
+ setOffsets(left: number, right: number) {
758
+ self.annotPos = { left, right }
759
+ },
760
+
761
+ clearAnnotPos() {
762
+ self.annotPos = undefined
763
+ },
764
+ }))
765
+
766
+ const model = types.snapshotProcessor(types.compose(BaseViewModel, MSAModel), {
767
+ postProcessor(result) {
768
+ const snap = result as Omit<typeof result, symbol>
769
+ const {
770
+ data: { tree, msa },
771
+ ...rest
772
+ } = snap
773
+
774
+ // remove the MSA/tree data from the tree if the filehandle available in
775
+ // which case it can be reloaded on refresh
776
+ return {
777
+ data: {
778
+ // https://andreasimonecosta.dev/posts/the-shortest-way-to-conditionally-insert-properties-into-an-object-literal/
779
+ ...(!result.treeFilehandle && { tree }),
780
+ ...(!result.msaFilehandle && { msa }),
781
+ },
782
+ ...rest,
783
+ }
784
+ },
785
+ })
786
+
787
+ export default model
788
+
789
+ export type MsaViewStateModel = typeof model
790
+ export type MsaViewModel = Instance<MsaViewStateModel>