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