react-msaview 3.1.7 → 3.1.8

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 (270) hide show
  1. package/bundle/index.js +32 -32
  2. package/dist/components/Checkbox2.d.ts +7 -0
  3. package/dist/components/Checkbox2.js +10 -0
  4. package/dist/components/Checkbox2.js.map +1 -0
  5. package/dist/components/Loading.js +12 -4
  6. package/dist/components/Loading.js.map +1 -1
  7. package/dist/components/MSAView.js +5 -8
  8. package/dist/components/MSAView.js.map +1 -1
  9. package/dist/components/SequenceTextArea.d.ts +4 -0
  10. package/dist/components/SequenceTextArea.js +38 -0
  11. package/dist/components/SequenceTextArea.js.map +1 -0
  12. package/dist/components/Track.js +9 -8
  13. package/dist/components/Track.js.map +1 -1
  14. package/dist/components/dialogs/AddTrackDialog.js +0 -1
  15. package/dist/components/dialogs/AddTrackDialog.js.map +1 -1
  16. package/dist/components/{ExportSVGDialog.d.ts → dialogs/ExportSVGDialog.d.ts} +1 -1
  17. package/dist/components/{ExportSVGDialog.js → dialogs/ExportSVGDialog.js} +3 -4
  18. package/dist/components/dialogs/ExportSVGDialog.js.map +1 -0
  19. package/dist/components/dialogs/FeatureDialog.d.ts +7 -0
  20. package/dist/components/dialogs/FeatureDialog.js +52 -0
  21. package/dist/components/dialogs/FeatureDialog.js.map +1 -0
  22. package/dist/components/dialogs/InterProScanDialog.d.ts +7 -0
  23. package/dist/components/dialogs/InterProScanDialog.js +163 -0
  24. package/dist/components/dialogs/InterProScanDialog.js.map +1 -0
  25. package/dist/components/dialogs/MetadataDialog.js +6 -3
  26. package/dist/components/dialogs/MetadataDialog.js.map +1 -1
  27. package/dist/components/dialogs/SettingsDialog.js +6 -11
  28. package/dist/components/dialogs/SettingsDialog.js.map +1 -1
  29. package/dist/components/{Header.d.ts → header/Header.d.ts} +1 -1
  30. package/dist/components/header/Header.js +30 -0
  31. package/dist/components/header/Header.js.map +1 -0
  32. package/dist/components/{HeaderInfoArea.d.ts → header/HeaderInfoArea.d.ts} +2 -2
  33. package/dist/components/header/HeaderInfoArea.js +20 -0
  34. package/dist/components/header/HeaderInfoArea.js.map +1 -0
  35. package/dist/components/header/HeaderMenu.d.ts +6 -0
  36. package/dist/components/header/HeaderMenu.js +40 -0
  37. package/dist/components/header/HeaderMenu.js.map +1 -0
  38. package/dist/components/header/HeaderMenuExtra.d.ts +6 -0
  39. package/dist/components/header/HeaderMenuExtra.js +92 -0
  40. package/dist/components/header/HeaderMenuExtra.js.map +1 -0
  41. package/dist/components/header/HeaderStatusArea.d.ts +6 -0
  42. package/dist/components/header/HeaderStatusArea.js +20 -0
  43. package/dist/components/header/HeaderStatusArea.js.map +1 -0
  44. package/dist/components/{MultiAlignmentSelector.d.ts → header/MultiAlignmentSelector.d.ts} +1 -1
  45. package/dist/components/header/MultiAlignmentSelector.js.map +1 -0
  46. package/dist/components/{ZoomControls.d.ts → header/ZoomControls.d.ts} +1 -1
  47. package/dist/components/header/ZoomControls.js +15 -0
  48. package/dist/components/header/ZoomControls.js.map +1 -0
  49. package/dist/components/{ImportForm/index.js → import/ImportForm.js} +1 -1
  50. package/dist/components/import/ImportForm.js.map +1 -0
  51. package/dist/components/{ImportForm → import}/ImportFormExamples.js +6 -2
  52. package/dist/components/import/ImportFormExamples.js.map +1 -0
  53. package/dist/components/import/data/seq2.js.map +1 -0
  54. package/dist/components/import/util.js +10 -0
  55. package/dist/components/import/util.js.map +1 -0
  56. package/dist/components/{Minimap.d.ts → minimap/Minimap.d.ts} +1 -1
  57. package/dist/components/minimap/Minimap.js.map +1 -0
  58. package/dist/components/{MinimapSVG.d.ts → minimap/MinimapSVG.d.ts} +1 -1
  59. package/dist/components/minimap/MinimapSVG.js.map +1 -0
  60. package/dist/components/msa/Loading.js.map +1 -0
  61. package/dist/components/{MSAPanel → msa}/MSACanvas.js +3 -2
  62. package/dist/components/msa/MSACanvas.js.map +1 -0
  63. package/dist/components/{MSAPanel/MSABlock.js → msa/MSACanvasBlock.js} +21 -9
  64. package/dist/components/msa/MSACanvasBlock.js.map +1 -0
  65. package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -0
  66. package/dist/components/msa/MSAPanel.d.ts +6 -0
  67. package/dist/components/{MSAPanel/index.js → msa/MSAPanel.js} +6 -3
  68. package/dist/components/msa/MSAPanel.js.map +1 -0
  69. package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +9 -0
  70. package/dist/components/msa/renderBoxFeatureCanvasBlock.js +44 -0
  71. package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -0
  72. package/dist/components/{MSAPanel → msa}/renderMSABlock.js +24 -20
  73. package/dist/components/msa/renderMSABlock.js.map +1 -0
  74. package/dist/components/msa/renderMSAMouseover.js.map +1 -0
  75. package/dist/components/tree/TreeBranchMenu.js.map +1 -0
  76. package/dist/components/{TreePanel → tree}/TreeCanvas.js +1 -1
  77. package/dist/components/tree/TreeCanvas.js.map +1 -0
  78. package/dist/components/{TreePanel → tree}/TreeCanvasBlock.js +1 -1
  79. package/dist/components/tree/TreeCanvasBlock.js.map +1 -0
  80. package/dist/components/{TreePanel → tree}/TreeNodeMenu.js +2 -33
  81. package/dist/components/tree/TreeNodeMenu.js.map +1 -0
  82. package/dist/components/{TreePanel/index.js → tree/TreePanel.js} +2 -2
  83. package/dist/components/tree/TreePanel.js.map +1 -0
  84. package/dist/components/tree/TreeRuler.js.map +1 -0
  85. package/dist/components/{TreePanel → tree}/dialogs/TreeNodeInfoDialog.js +6 -2
  86. package/dist/components/tree/dialogs/TreeNodeInfoDialog.js.map +1 -0
  87. package/dist/components/{TreePanel → tree}/renderTreeCanvas.js +23 -37
  88. package/dist/components/tree/renderTreeCanvas.js.map +1 -0
  89. package/dist/fetchUtils.d.ts +5 -0
  90. package/dist/fetchUtils.js +23 -0
  91. package/dist/fetchUtils.js.map +1 -0
  92. package/dist/ggplotPalettes.d.ts +3 -0
  93. package/dist/ggplotPalettes.js +24 -0
  94. package/dist/ggplotPalettes.js.map +1 -0
  95. package/dist/index.d.ts +1 -1
  96. package/dist/index.js +1 -1
  97. package/dist/index.js.map +1 -1
  98. package/dist/launchInterProScan.d.ts +32 -0
  99. package/dist/launchInterProScan.js +47 -0
  100. package/dist/launchInterProScan.js.map +1 -0
  101. package/dist/model/DataModel.js.map +1 -0
  102. package/dist/model/DialogQueue.js.map +1 -0
  103. package/dist/model/msaModel.d.ts +14 -0
  104. package/dist/model/msaModel.js +36 -0
  105. package/dist/model/msaModel.js.map +1 -0
  106. package/dist/model/treeModel.d.ts +46 -0
  107. package/dist/model/treeModel.js +105 -0
  108. package/dist/model/treeModel.js.map +1 -0
  109. package/dist/model.d.ts +261 -273
  110. package/dist/model.js +1043 -1029
  111. package/dist/model.js.map +1 -1
  112. package/dist/parseGFF.d.ts +10 -0
  113. package/dist/parseGFF.js +29 -0
  114. package/dist/parseGFF.js.map +1 -0
  115. package/dist/renderToSvg.js +23 -9
  116. package/dist/renderToSvg.js.map +1 -1
  117. package/dist/reparseTree.d.ts +2 -0
  118. package/dist/reparseTree.js +13 -0
  119. package/dist/reparseTree.js.map +1 -0
  120. package/dist/util.d.ts +4 -10
  121. package/dist/util.js +3 -28
  122. package/dist/util.js.map +1 -1
  123. package/dist/version.d.ts +1 -1
  124. package/dist/version.js +1 -1
  125. package/package.json +12 -2
  126. package/src/components/Checkbox2.tsx +34 -0
  127. package/src/components/Loading.tsx +27 -11
  128. package/src/components/MSAView.tsx +6 -10
  129. package/src/components/SequenceTextArea.tsx +63 -0
  130. package/src/components/Track.tsx +8 -13
  131. package/src/components/dialogs/AddTrackDialog.tsx +0 -1
  132. package/src/components/{ExportSVGDialog.tsx → dialogs/ExportSVGDialog.tsx} +9 -16
  133. package/src/components/dialogs/FeatureDialog.tsx +109 -0
  134. package/src/components/dialogs/InterProScanDialog.tsx +230 -0
  135. package/src/components/dialogs/MetadataDialog.tsx +9 -2
  136. package/src/components/dialogs/SettingsDialog.tsx +10 -30
  137. package/src/components/header/Header.tsx +44 -0
  138. package/src/components/header/HeaderInfoArea.tsx +27 -0
  139. package/src/components/header/HeaderMenu.tsx +54 -0
  140. package/src/components/header/HeaderMenuExtra.tsx +108 -0
  141. package/src/components/header/HeaderStatusArea.tsx +31 -0
  142. package/src/components/{MultiAlignmentSelector.tsx → header/MultiAlignmentSelector.tsx} +1 -1
  143. package/src/components/header/ZoomControls.tsx +28 -0
  144. package/src/components/{ImportForm → import}/ImportFormExamples.tsx +12 -1
  145. package/src/components/{ImportForm → import}/util.ts +5 -10
  146. package/src/components/{Minimap.tsx → minimap/Minimap.tsx} +1 -1
  147. package/src/components/{MinimapSVG.tsx → minimap/MinimapSVG.tsx} +1 -1
  148. package/src/components/{MSAPanel → msa}/MSACanvas.tsx +3 -2
  149. package/src/components/{MSAPanel/MSABlock.tsx → msa/MSACanvasBlock.tsx} +25 -12
  150. package/src/components/{MSAPanel/index.tsx → msa/MSAPanel.tsx} +8 -2
  151. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +88 -0
  152. package/src/components/{MSAPanel → msa}/renderMSABlock.ts +26 -20
  153. package/src/components/{TreePanel → tree}/TreeCanvas.tsx +1 -1
  154. package/src/components/{TreePanel → tree}/TreeCanvasBlock.tsx +1 -1
  155. package/src/components/{TreePanel → tree}/TreeNodeMenu.tsx +1 -53
  156. package/src/components/{TreePanel/index.tsx → tree/TreePanel.tsx} +1 -1
  157. package/src/components/{TreePanel → tree}/dialogs/TreeNodeInfoDialog.tsx +9 -2
  158. package/src/components/{TreePanel → tree}/renderTreeCanvas.ts +25 -41
  159. package/src/fetchUtils.ts +30 -0
  160. package/src/ggplotPalettes.ts +25 -0
  161. package/src/index.ts +1 -1
  162. package/src/launchInterProScan.ts +98 -0
  163. package/src/model/msaModel.ts +39 -0
  164. package/src/model/treeModel.ts +116 -0
  165. package/src/model.ts +1124 -1126
  166. package/src/parseGFF.ts +32 -0
  167. package/src/renderToSvg.tsx +27 -8
  168. package/src/reparseTree.ts +16 -0
  169. package/src/util.ts +4 -33
  170. package/src/version.ts +1 -1
  171. package/dist/DataModel.js.map +0 -1
  172. package/dist/DialogQueue.js.map +0 -1
  173. package/dist/SelectedStructuresMixin.d.ts +0 -46
  174. package/dist/SelectedStructuresMixin.js +0 -52
  175. package/dist/SelectedStructuresMixin.js.map +0 -1
  176. package/dist/StructureModel.d.ts +0 -9
  177. package/dist/StructureModel.js +0 -11
  178. package/dist/StructureModel.js.map +0 -1
  179. package/dist/UniprotTrack.d.ts +0 -27
  180. package/dist/UniprotTrack.js +0 -53
  181. package/dist/UniprotTrack.js.map +0 -1
  182. package/dist/components/BoxTrack.d.ts +0 -7
  183. package/dist/components/BoxTrack.js +0 -15
  184. package/dist/components/BoxTrack.js.map +0 -1
  185. package/dist/components/BoxTrackBlock.d.ts +0 -8
  186. package/dist/components/BoxTrackBlock.js +0 -136
  187. package/dist/components/BoxTrackBlock.js.map +0 -1
  188. package/dist/components/ExportSVGDialog.js.map +0 -1
  189. package/dist/components/Header.js +0 -62
  190. package/dist/components/Header.js.map +0 -1
  191. package/dist/components/HeaderInfoArea.js +0 -12
  192. package/dist/components/HeaderInfoArea.js.map +0 -1
  193. package/dist/components/ImportForm/ImportFormExamples.js.map +0 -1
  194. package/dist/components/ImportForm/data/seq2.js.map +0 -1
  195. package/dist/components/ImportForm/index.js.map +0 -1
  196. package/dist/components/ImportForm/util.js +0 -16
  197. package/dist/components/ImportForm/util.js.map +0 -1
  198. package/dist/components/MSAPanel/Loading.js.map +0 -1
  199. package/dist/components/MSAPanel/MSABlock.js.map +0 -1
  200. package/dist/components/MSAPanel/MSACanvas.js.map +0 -1
  201. package/dist/components/MSAPanel/MSAMouseoverCanvas.js.map +0 -1
  202. package/dist/components/MSAPanel/index.d.ts +0 -5
  203. package/dist/components/MSAPanel/index.js.map +0 -1
  204. package/dist/components/MSAPanel/renderMSABlock.js.map +0 -1
  205. package/dist/components/MSAPanel/renderMSAMouseover.js.map +0 -1
  206. package/dist/components/Minimap.js.map +0 -1
  207. package/dist/components/MinimapSVG.js.map +0 -1
  208. package/dist/components/MultiAlignmentSelector.js.map +0 -1
  209. package/dist/components/TreePanel/TreeBranchMenu.js.map +0 -1
  210. package/dist/components/TreePanel/TreeCanvas.js.map +0 -1
  211. package/dist/components/TreePanel/TreeCanvasBlock.js.map +0 -1
  212. package/dist/components/TreePanel/TreeNodeMenu.js.map +0 -1
  213. package/dist/components/TreePanel/TreeRuler.js.map +0 -1
  214. package/dist/components/TreePanel/dialogs/TreeNodeInfoDialog.js.map +0 -1
  215. package/dist/components/TreePanel/index.js.map +0 -1
  216. package/dist/components/TreePanel/renderTreeCanvas.js.map +0 -1
  217. package/dist/components/VerticalGuide.d.ts +0 -7
  218. package/dist/components/VerticalGuide.js +0 -30
  219. package/dist/components/VerticalGuide.js.map +0 -1
  220. package/dist/components/ZoomControls.js +0 -59
  221. package/dist/components/ZoomControls.js.map +0 -1
  222. package/src/SelectedStructuresMixin.ts +0 -59
  223. package/src/StructureModel.ts +0 -11
  224. package/src/UniprotTrack.ts +0 -59
  225. package/src/components/BoxTrack.tsx +0 -33
  226. package/src/components/BoxTrackBlock.tsx +0 -200
  227. package/src/components/Header.tsx +0 -99
  228. package/src/components/HeaderInfoArea.tsx +0 -21
  229. package/src/components/VerticalGuide.tsx +0 -50
  230. package/src/components/ZoomControls.tsx +0 -86
  231. package/dist/components/{MultiAlignmentSelector.js → header/MultiAlignmentSelector.js} +0 -0
  232. package/dist/components/{ImportForm/index.d.ts → import/ImportForm.d.ts} +0 -0
  233. package/dist/components/{ImportForm → import}/ImportFormExamples.d.ts +0 -0
  234. package/dist/components/{ImportForm → import}/data/seq2.d.ts +0 -0
  235. package/dist/components/{ImportForm → import}/data/seq2.js +0 -0
  236. package/dist/components/{ImportForm → import}/util.d.ts +0 -0
  237. package/dist/components/{Minimap.js → minimap/Minimap.js} +0 -0
  238. package/dist/components/{MinimapSVG.js → minimap/MinimapSVG.js} +0 -0
  239. package/dist/components/{MSAPanel → msa}/Loading.d.ts +0 -0
  240. package/dist/components/{MSAPanel → msa}/Loading.js +0 -0
  241. package/dist/components/{MSAPanel → msa}/MSACanvas.d.ts +0 -0
  242. package/dist/components/{MSAPanel/MSABlock.d.ts → msa/MSACanvasBlock.d.ts} +0 -0
  243. package/dist/components/{MSAPanel → msa}/MSAMouseoverCanvas.d.ts +0 -0
  244. package/dist/components/{MSAPanel → msa}/MSAMouseoverCanvas.js +0 -0
  245. package/dist/components/{MSAPanel → msa}/renderMSABlock.d.ts +1 -1
  246. /package/dist/components/{MSAPanel → msa}/renderMSAMouseover.d.ts +0 -0
  247. /package/dist/components/{MSAPanel → msa}/renderMSAMouseover.js +0 -0
  248. /package/dist/components/{TreePanel → tree}/TreeBranchMenu.d.ts +0 -0
  249. /package/dist/components/{TreePanel → tree}/TreeBranchMenu.js +0 -0
  250. /package/dist/components/{TreePanel → tree}/TreeCanvas.d.ts +0 -0
  251. /package/dist/components/{TreePanel → tree}/TreeCanvasBlock.d.ts +0 -0
  252. /package/dist/components/{TreePanel → tree}/TreeNodeMenu.d.ts +0 -0
  253. /package/dist/components/{TreePanel/index.d.ts → tree/TreePanel.d.ts} +0 -0
  254. /package/dist/components/{TreePanel → tree}/TreeRuler.d.ts +0 -0
  255. /package/dist/components/{TreePanel → tree}/TreeRuler.js +0 -0
  256. /package/dist/components/{TreePanel → tree}/dialogs/TreeNodeInfoDialog.d.ts +0 -0
  257. /package/dist/components/{TreePanel → tree}/renderTreeCanvas.d.ts +0 -0
  258. /package/dist/{DataModel.d.ts → model/DataModel.d.ts} +0 -0
  259. /package/dist/{DataModel.js → model/DataModel.js} +0 -0
  260. /package/dist/{DialogQueue.d.ts → model/DialogQueue.d.ts} +0 -0
  261. /package/dist/{DialogQueue.js → model/DialogQueue.js} +0 -0
  262. /package/src/components/{ImportForm/index.tsx → import/ImportForm.tsx} +0 -0
  263. /package/src/components/{ImportForm → import}/data/seq2.ts +0 -0
  264. /package/src/components/{MSAPanel → msa}/Loading.tsx +0 -0
  265. /package/src/components/{MSAPanel → msa}/MSAMouseoverCanvas.tsx +0 -0
  266. /package/src/components/{MSAPanel → msa}/renderMSAMouseover.ts +0 -0
  267. /package/src/components/{TreePanel → tree}/TreeBranchMenu.tsx +0 -0
  268. /package/src/components/{TreePanel → tree}/TreeRuler.tsx +0 -0
  269. /package/src/{DataModel.ts → model/DataModel.ts} +0 -0
  270. /package/src/{DialogQueue.ts → model/DialogQueue.ts} +0 -0
package/src/model.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import React from 'react'
2
- import { autorun } from 'mobx'
2
+ import { autorun, transaction } from 'mobx'
3
3
  import { Instance, cast, types, addDisposer } from 'mobx-state-tree'
4
4
  import { hierarchy, cluster, HierarchyNode } from 'd3-hierarchy'
5
5
  import { ascending } from 'd3-array'
@@ -11,8 +11,13 @@ import { Theme } from '@mui/material'
11
11
  import { FileLocation, ElementId } from '@jbrowse/core/util/types/mst'
12
12
  import { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
13
13
  import { openLocation } from '@jbrowse/core/util/io'
14
- import { notEmpty, sum } from '@jbrowse/core/util'
15
- import BaseViewModel from '@jbrowse/core/pluggableElementTypes/models/BaseViewModel'
14
+ import {
15
+ groupBy,
16
+ localStorageGetItem,
17
+ localStorageSetItem,
18
+ notEmpty,
19
+ sum,
20
+ } from '@jbrowse/core/util'
16
21
 
17
22
  // locals
18
23
  import {
@@ -24,14 +29,16 @@ import {
24
29
  skipBlanks,
25
30
  NodeWithIds,
26
31
  NodeWithIdsAndLength,
32
+ len,
27
33
  } from './util'
28
-
34
+ import { colord } from 'colord'
35
+ import { reparseTree } from './reparseTree'
29
36
  import { blocksX, blocksY } from './calculateBlocks'
30
37
  import { measureTextCanvas } from './measureTextCanvas'
38
+ import palettes from './ggplotPalettes'
31
39
 
32
40
  // components
33
41
  import TextTrack from './components/TextTrack'
34
- import BoxTrack from './components/BoxTrack'
35
42
 
36
43
  // parsers
37
44
  import ClustalMSA from './parsers/ClustalMSA'
@@ -41,1257 +48,1248 @@ import parseNewick from './parseNewick'
41
48
  import colorSchemes from './colorSchemes'
42
49
 
43
50
  // models
44
- import { UniprotTrack } from './UniprotTrack'
45
- import { DataModelF } from './DataModel'
46
- import { DialogQueueSessionMixin } from './DialogQueue'
47
- import { SelectedStructuresMixin } from './SelectedStructuresMixin'
51
+ import { DataModelF } from './model/DataModel'
52
+ import { DialogQueueSessionMixin } from './model/DialogQueue'
53
+ import { TreeF } from './model/treeModel'
54
+ import { MSAModelF } from './model/msaModel'
55
+ import {
56
+ InterProScanResults,
57
+ launchInterProScan,
58
+ loadInterProScanResults,
59
+ } from './launchInterProScan'
48
60
 
49
- export interface RowDetails {
50
- [key: string]: unknown
61
+ interface Accession {
62
+ accession: string
51
63
  name: string
52
- range?: { start: number; end: number }
64
+ description: string
53
65
  }
54
-
55
66
  export interface BasicTrackModel {
56
67
  id: string
57
68
  name: string
58
69
  associatedRowName?: string
59
70
  height: number
60
71
  }
61
- export interface Structure {
62
- pdb: string
63
- startPos: number
64
- endPos: number
65
- }
66
72
 
67
73
  export interface TextTrackModel extends BasicTrackModel {
68
74
  customColorScheme?: Record<string, string>
69
75
  data: string
70
76
  }
71
77
 
72
- export interface BoxTrackModel extends BasicTrackModel {
73
- features: {
74
- start: number
75
- end: number
76
- }[]
77
- }
78
78
  export interface ITextTrack {
79
79
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
80
80
  ReactComponent: React.FC<any>
81
81
  model: TextTrackModel
82
82
  }
83
83
 
84
- export interface IBoxTrack {
85
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
- ReactComponent: React.FC<any>
87
- model: BoxTrackModel
88
- }
89
-
90
- export type BasicTrack = IBoxTrack | ITextTrack
84
+ export type BasicTrack = ITextTrack
91
85
 
92
86
  /**
93
87
  * #stateModel MsaView
94
88
  * extends
95
- * - BaseViewModel
96
89
  * - DialogQueueSessionMixin
97
- * - SelectedStructuresMixin
90
+ * - MSAModel
91
+ * - Tree
98
92
  */
99
- function x() {} // eslint-disable-line @typescript-eslint/no-unused-vars
100
-
101
- function reparseTree(tree: NodeWithIds): NodeWithIds {
102
- return {
103
- ...tree,
104
- branchset: tree.branchset.map(r =>
105
- r.branchset.length
106
- ? reparseTree(r)
107
- : {
108
- branchset: [r],
109
- id: `${r.id}-leafnode`,
110
- name: `${r.name}-hidden`,
111
- },
112
- ),
113
- }
114
- }
93
+ function stateModelFactory() {
94
+ return types
95
+ .compose(
96
+ DialogQueueSessionMixin(),
97
+ TreeF(),
98
+ MSAModelF(),
99
+ types.model('MsaView', {
100
+ /**
101
+ * #property
102
+ * id of view, randomly generated if not provided
103
+ */
104
+ id: ElementId,
105
+ /**
106
+ * #property
107
+ */
108
+ featureMode: false,
109
+ /**
110
+ * #property
111
+ */
112
+ subFeatureRows: false,
113
+
114
+ /**
115
+ * #property
116
+ * hardcoded view type
117
+ */
118
+ type: types.literal('MsaView'),
119
+
120
+ /**
121
+ * #property
122
+ * height of the div containing the view, px
123
+ */
124
+ height: types.optional(types.number, 550),
125
+
126
+ /**
127
+ * #property
128
+ * height of each row, px
129
+ */
130
+ rowHeight: 20,
131
+
132
+ /**
133
+ * #property
134
+ * scroll position, Y-offset, px
135
+ */
136
+ scrollY: 0,
137
+
138
+ /**
139
+ * #property
140
+ * scroll position, X-offset, px
141
+ */
142
+ scrollX: 0,
143
+
144
+ /**
145
+ * #property
146
+ * width of columns, px
147
+ */
148
+ colWidth: 16,
149
+
150
+ /**
151
+ * #property
152
+ * filehandle object for the tree
153
+ */
154
+ treeFilehandle: types.maybe(FileLocation),
155
+
156
+ /**
157
+ * #property
158
+ * filehandle object for the MSA (which could contain a tree e.g. with
159
+ * stockholm files)
160
+ */
161
+ msaFilehandle: types.maybe(FileLocation),
162
+
163
+ /**
164
+ * #property
165
+ * filehandle object for tree metadata
166
+ */
167
+ treeMetadataFilehandle: types.maybe(FileLocation),
168
+
169
+ /**
170
+ * #property
171
+ *
172
+ */
173
+ currentAlignment: 0,
174
+
175
+ /**
176
+ * #property
177
+ * array of tree parent nodes that are 'collapsed'
178
+ */
179
+ collapsed: types.array(types.string),
180
+
181
+ /**
182
+ * #property
183
+ * array of tree leaf nodes that are 'collapsed'
184
+ */
185
+ collapsed2: types.array(types.string),
186
+ /**
187
+ * #property
188
+ * focus on particular subtree
189
+ */
190
+ showOnly: types.maybe(types.string),
191
+ /**
192
+ * #property
193
+ * turned off tracks
194
+ */
195
+ turnedOffTracks: types.map(types.boolean),
196
+
197
+ /**
198
+ * #property
199
+ * data from the loaded tree/msa/treeMetadata, generally loaded by
200
+ * autorun
201
+ */
202
+ data: types.optional(DataModelF(), { tree: '', msa: '' }),
203
+ /**
204
+ * #property
205
+ */
206
+ featureFilters: types.map(types.boolean),
207
+ }),
208
+ )
209
+ .volatile(() => ({
210
+ status: undefined as { msg: string; url?: string } | undefined,
211
+ /**
212
+ * #volatile
213
+ * high resolution scale factor, helps make canvas look better on hi-dpi
214
+ * screens
215
+ */
216
+ highResScaleFactor: 1,
217
+ /**
218
+ * #volatile
219
+ */
220
+ loadingMSA: false,
221
+ /**
222
+ * #volatile
223
+ */
224
+ loadingTree: false,
225
+ /**
226
+ * #volatile
227
+ */
228
+ width: 800,
229
+ /**
230
+ * #volatile
231
+ * resize handle width between tree and msa area, px
232
+ */
233
+ resizeHandleWidth: 5,
115
234
 
116
- export type DialogComponentType =
117
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
118
- | React.LazyExoticComponent<React.FC<any>>
119
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
120
- | React.FC<any>
235
+ /**
236
+ * #volatile
237
+ * size of blocks of content to be drawn, px
238
+ */
239
+ blockSize: 1000,
121
240
 
122
- const model = types
123
- .compose(
124
- BaseViewModel,
125
- DialogQueueSessionMixin(),
126
- SelectedStructuresMixin(),
127
- types.model('MsaView', {
128
241
  /**
129
- * #property
130
- * id of view, randomly generated if not provided
242
+ * #volatile
243
+ * the currently mouse-hovered row
131
244
  */
132
- id: ElementId,
245
+ mouseRow: undefined as number | undefined,
133
246
 
134
247
  /**
135
- * #property
136
- * hardcoded view type
248
+ * #volatile
249
+ * the currently mouse-hovered column
137
250
  */
138
- type: types.literal('MsaView'),
251
+ mouseCol: undefined as number | undefined,
139
252
 
140
253
  /**
141
- * #property
142
- * height of the div containing the view, px
254
+ * #volatile
255
+ * the currently mouse-click row
143
256
  */
144
- height: types.optional(types.number, 550),
257
+ mouseClickRow: undefined as number | undefined,
145
258
 
146
259
  /**
147
- * #property
148
- * width of the area the tree is drawn in, px
260
+ * #volatile
261
+ * the currently mouse-click column
149
262
  */
150
- treeAreaWidth: types.optional(types.number, 400),
263
+ mouseClickCol: undefined as number | undefined,
151
264
 
152
265
  /**
153
- * #property
154
- * width of the tree within the treeArea, px
266
+ * #volatile
267
+ * a dummy variable that is incremented when ref changes so autorun for
268
+ * drawing canvas commands will run
155
269
  */
156
- treeWidth: types.optional(types.number, 300),
270
+ nref: 0,
157
271
 
158
272
  /**
159
- * #getter
160
- * synchronization that matches treeWidth to treeAreaWidth
273
+ * #volatile
161
274
  */
162
- treeWidthMatchesArea: true,
275
+ minimapHeight: 56,
163
276
 
164
277
  /**
165
- * #property
166
- * height of each row, px
278
+ * #volatile
167
279
  */
168
- rowHeight: 20,
280
+ marginLeft: 20,
169
281
 
170
282
  /**
171
- * #property
172
- * scroll position, Y-offset, px
283
+ * #volatile
173
284
  */
174
- scrollY: 0,
285
+ error: undefined as unknown,
175
286
 
176
287
  /**
177
- * #property
178
- * scroll position, X-offset, px
288
+ * #volatile
179
289
  */
180
- scrollX: 0,
290
+ annotPos: undefined as { left: number; right: number } | undefined,
181
291
 
182
292
  /**
183
- * #property
184
- * right-align the labels
293
+ * #volatile
294
+ *
295
+ */
296
+ loadedInterProAnnotations: undefined as
297
+ | undefined
298
+ | Record<string, InterProScanResults>,
299
+ /**
300
+ * #volatile
301
+ */
302
+ interProScanJobIds: JSON.parse(
303
+ localStorageGetItem('msaview-interproscanqueries') || '[]',
304
+ ) as { jobId: string; date: number }[],
305
+ }))
306
+ .actions(self => ({
307
+ /**
308
+ * #action
309
+ */
310
+ setLoadingMSA(arg: boolean) {
311
+ self.loadingMSA = arg
312
+ },
313
+ /**
314
+ * #action
315
+ */
316
+ setLoadingTree(arg: boolean) {
317
+ self.loadingTree = arg
318
+ },
319
+ /**
320
+ * #action
185
321
  */
186
- labelsAlignRight: false,
322
+ setWidth(arg: number) {
323
+ self.width = arg
324
+ },
325
+ /**
326
+ * #action
327
+ * set the height of the view in px
328
+ */
329
+ setHeight(height: number) {
330
+ self.height = height
331
+ },
187
332
 
188
333
  /**
189
- * #property
190
- * width of columns, px
334
+ * #action
335
+ * set error state
191
336
  */
192
- colWidth: 16,
337
+ setError(error?: unknown) {
338
+ self.error = error
339
+ },
193
340
 
194
341
  /**
195
- * #property
196
- * use "branch length" e.g. evolutionary distance to draw tree branch
197
- * lengths. if false, the layout is a "cladogram" that does not take into
198
- * account evolutionary distances
342
+ * #action
343
+ * set mouse position (row, column) in the MSA
199
344
  */
200
- showBranchLen: true,
345
+ setMousePos(col?: number, row?: number) {
346
+ self.mouseCol = col
347
+ self.mouseRow = row
348
+ },
201
349
  /**
202
- * #property
203
- * draw MSA tiles with a background color
350
+ * #action
204
351
  */
205
- bgColor: true,
352
+ setFeatureMode(arg: boolean) {
353
+ self.featureMode = arg
354
+ },
206
355
 
207
356
  /**
208
- * #property
209
- * draw tree, boolean
357
+ * #action
358
+ */
359
+ setSubFeatureRows(arg: boolean) {
360
+ self.subFeatureRows = arg
361
+ },
362
+ /**
363
+ * #action
364
+ * set mouse click position (row, column) in the MSA
210
365
  */
211
- drawTree: true,
366
+ setMouseClickPos(col?: number, row?: number) {
367
+ self.mouseClickCol = col
368
+ self.mouseClickRow = row
369
+ },
212
370
 
213
371
  /**
214
- * #property
215
- * draw clickable node bubbles on the tree
372
+ * #action
373
+ * set row height (px)
216
374
  */
217
- drawNodeBubbles: true,
375
+ setRowHeight(n: number) {
376
+ self.rowHeight = n
377
+ },
218
378
 
219
379
  /**
220
- * #property
221
- * high resolution scale factor, helps make canvas look better on hi-dpi
222
- * screens
380
+ * #action
381
+ * set col width (px)
223
382
  */
224
- highResScaleFactor: 2,
383
+ setColWidth(n: number) {
384
+ self.colWidth = n
385
+ },
225
386
 
226
387
  /**
227
- * #property
228
- * default color scheme name
388
+ * #action
389
+ * set scroll Y-offset (px)
229
390
  */
230
- colorSchemeName: 'maeditor',
391
+ setScrollY(n: number) {
392
+ self.scrollY = n
393
+ },
231
394
 
232
395
  /**
233
- * #property
234
- * filehandle object for the tree
396
+ * #action
397
+ *
235
398
  */
236
- treeFilehandle: types.maybe(FileLocation),
399
+ setCurrentAlignment(n: number) {
400
+ self.currentAlignment = n
401
+ },
237
402
 
238
403
  /**
239
- * #property
240
- * filehandle object for the MSA (which could contain a tree e.g. with
241
- * stockholm files)
404
+ * #action
242
405
  */
243
- msaFilehandle: types.maybe(FileLocation),
406
+ toggleCollapsed(node: string) {
407
+ if (self.collapsed.includes(node)) {
408
+ self.collapsed.remove(node)
409
+ } else {
410
+ self.collapsed.push(node)
411
+ }
412
+ },
244
413
 
245
414
  /**
246
- * #property
247
- * filehandle object for tree metadata
415
+ * #action
248
416
  */
249
- treeMetadataFilehandle: types.maybe(FileLocation),
417
+ toggleCollapsed2(node: string) {
418
+ if (self.collapsed2.includes(node)) {
419
+ self.collapsed2.remove(node)
420
+ } else {
421
+ self.collapsed2.push(node)
422
+ }
423
+ },
424
+ /**
425
+ * #action
426
+ */
427
+ setShowOnly(node?: string) {
428
+ self.showOnly = node
429
+ },
250
430
 
251
431
  /**
252
- * #property
253
- *
432
+ * #action
254
433
  */
255
- currentAlignment: 0,
434
+ setData(data: { msa?: string; tree?: string }) {
435
+ self.data = cast(data)
436
+ },
256
437
 
257
438
  /**
258
- * #property
259
- * array of tree nodes that are 'collapsed'
439
+ * #action
260
440
  */
261
- collapsed: types.array(types.string),
262
-
263
- collapsed2: types.array(types.string),
441
+ setMSAFilehandle(msaFilehandle?: FileLocationType) {
442
+ self.msaFilehandle = msaFilehandle
443
+ },
444
+
264
445
  /**
265
- * #property
266
- * focus on particular subtree
267
- */
268
- showOnly: types.maybe(types.string),
269
-
270
- /**
271
- * #property
272
- * a list of "tracks" to display, as box-like glyphs (e.g. protein
273
- * domains)
274
- */
275
- boxTracks: types.array(UniprotTrack),
276
-
277
- /**
278
- * #property
279
- * turned off tracks
280
- */
281
- turnedOffTracks: types.map(types.boolean),
282
-
283
- /**
284
- * #property
285
- * data from the loaded tree/msa/treeMetadata, generally loaded by
286
- * autorun
287
- */
288
- data: types.optional(DataModelF(), { tree: '', msa: '' }),
289
- }),
290
- )
291
- .volatile(() => ({
292
- /**
293
- * #volatile
294
- * resize handle width between tree and msa area, px
295
- */
296
- resizeHandleWidth: 5,
297
-
298
- /**
299
- * #volatile
300
- * size of blocks of content to be drawn, px
301
- */
302
- blockSize: 1000,
303
-
304
- /**
305
- * #volatile
306
- * the currently mouse-hovered row
307
- */
308
- mouseRow: undefined as number | undefined,
309
-
310
- /**
311
- * #volatile
312
- * the currently mouse-hovered column
313
- */
314
- mouseCol: undefined as number | undefined,
315
-
316
- /**
317
- * #volatile
318
- * the currently mouse-click row
319
- */
320
- mouseClickRow: undefined as number | undefined,
321
-
322
- /**
323
- * #volatile
324
- * the currently mouse-click column
325
- */
326
- mouseClickCol: undefined as number | undefined,
327
-
328
- /**
329
- * #volatile
330
- * a dummy variable that is incremented when ref changes so autorun for
331
- * drawing canvas commands will run
332
- */
333
- nref: 0,
334
-
335
- /**
336
- * #volatile
337
- */
338
- minimapHeight: 56,
339
-
340
- /**
341
- * #volatile
342
- */
343
- marginLeft: 20,
344
-
345
- /**
346
- * #volatile
347
- */
348
- error: undefined as unknown,
349
-
350
- /**
351
- * #volatile
352
- */
353
- annotPos: undefined as { left: number; right: number } | undefined,
354
- }))
355
- .actions(self => ({
356
- /**
357
- * #action
358
- * set the height of the view in px
359
- */
360
- setHeight(height: number) {
361
- self.height = height
362
- },
363
-
364
- /**
365
- * #action
366
- * set error state
367
- */
368
- setError(error?: unknown) {
369
- self.error = error
370
- },
371
-
372
- /**
373
- * #action
374
- * set mouse position (row, column) in the MSA
375
- */
376
- setMousePos(col?: number, row?: number) {
377
- self.mouseCol = col
378
- self.mouseRow = row
379
- },
380
-
381
- /**
382
- * #action
383
- * set mouse click position (row, column) in the MSA
384
- */
385
- setMouseClickPos(col?: number, row?: number) {
386
- self.mouseClickCol = col
387
- self.mouseClickRow = row
388
- },
389
-
390
- /**
391
- * #action
392
- * set row height (px)
393
- */
394
- setRowHeight(n: number) {
395
- self.rowHeight = n
396
- },
397
-
398
- /**
399
- * #action
400
- * set col width (px)
401
- */
402
- setColWidth(n: number) {
403
- self.colWidth = n
404
- },
405
-
406
- /**
407
- * #action
408
- * set color scheme name
409
- */
410
- setColorSchemeName(name: string) {
411
- self.colorSchemeName = name
412
- },
413
-
414
- /**
415
- * #action
416
- * synchronize the treewidth and treeareawidth
417
- */
418
- setTreeWidthMatchesArea(arg: boolean) {
419
- self.treeWidthMatchesArea = arg
420
- },
421
-
422
- /**
423
- * #action
424
- * set scroll Y-offset (px)
425
- */
426
- setScrollY(n: number) {
427
- self.scrollY = n
428
- },
429
-
430
- /**
431
- * #action
432
- * set tree area width (px)
433
- */
434
- setTreeAreaWidth(n: number) {
435
- self.treeAreaWidth = n
436
- },
437
- /**
438
- * #action
439
- * set tree width (px)
440
- */
441
- setTreeWidth(n: number) {
442
- self.treeWidth = n
443
- },
444
-
445
- /**
446
- * #action
447
- *
448
- */
449
- setCurrentAlignment(n: number) {
450
- self.currentAlignment = n
451
- },
452
-
453
- /**
454
- * #action
455
- */
456
- setLabelsAlignRight(arg: boolean) {
457
- self.labelsAlignRight = arg
458
- },
459
- /**
460
- * #action
461
- */
462
- setDrawTree(arg: boolean) {
463
- self.drawTree = arg
464
- },
465
-
466
- /**
467
- * #action
468
- */
469
- toggleCollapsed(node: string) {
470
- if (self.collapsed.includes(node)) {
471
- self.collapsed.remove(node)
472
- } else {
473
- self.collapsed.push(node)
474
- }
475
- },
476
-
477
- /**
478
- * #action
479
- */
480
- toggleCollapsed2(node: string) {
481
- if (self.collapsed2.includes(node)) {
482
- self.collapsed2.remove(node)
483
- } else {
484
- self.collapsed2.push(node)
485
- }
486
- },
487
- /**
488
- * #action
489
- */
490
- setShowOnly(node?: string) {
491
- self.showOnly = node
492
- },
493
-
494
- /**
495
- * #action
496
- */
497
- setShowBranchLen(arg: boolean) {
498
- self.showBranchLen = arg
499
- },
500
-
501
- /**
502
- * #action
503
- */
504
- setBgColor(arg: boolean) {
505
- self.bgColor = arg
506
- },
507
-
508
- /**
509
- * #action
510
- */
511
- setDrawNodeBubbles(arg: boolean) {
512
- self.drawNodeBubbles = arg
513
- },
514
-
515
- /**
516
- * #action
517
- */
518
- setData(data: { msa?: string; tree?: string }) {
519
- self.data = cast(data)
520
- },
521
-
522
- /**
523
- * #action
524
- */
525
- async setMSAFilehandle(msaFilehandle?: FileLocationType) {
526
- self.msaFilehandle = msaFilehandle
527
- },
528
-
529
- /**
530
- * #action
531
- */
532
- async setTreeFilehandle(treeFilehandle?: FileLocationType) {
533
- if (treeFilehandle && 'blobId' in treeFilehandle) {
534
- const r = await openLocation(treeFilehandle).readFile('utf8')
535
- this.setTree(r)
536
- } else {
446
+ * #action
447
+ */
448
+ setTreeFilehandle(treeFilehandle?: FileLocationType) {
537
449
  self.treeFilehandle = treeFilehandle
538
- }
539
- },
540
-
541
- /**
542
- * #action
543
- */
544
- setMSA(result: string) {
545
- self.data.setMSA(result)
546
- },
547
-
548
- /**
549
- * #action
550
- */
551
- setTree(result: string) {
552
- self.data.setTree(result)
553
- },
554
-
555
- /**
556
- * #action
557
- */
558
- setTreeMetadata(result: string) {
559
- self.data.setTreeMetadata(result)
560
- },
561
- }))
562
-
563
- .views(self => ({
564
- /**
565
- * #getter
566
- */
567
- get colorScheme() {
568
- return colorSchemes[self.colorSchemeName]
569
- },
570
-
571
- /**
572
- * #getter
573
- */
574
- get header() {
575
- return this.MSA?.getHeader() || {}
576
- },
577
-
578
- /**
579
- * #method
580
- */
581
- getRowData(name: string) {
582
- const matches = name.match(/\S+\/(\d+)-(\d+)/)
583
- return {
584
- data: this.MSA?.getRowData(name) || ({} as Record<string, unknown>),
585
- ...(matches && {
586
- range: {
587
- start: +matches[1],
588
- end: +matches[2],
589
- },
590
- }),
591
- }
592
- },
593
- /**
594
- * #getter
595
- */
596
- get currentAlignmentName() {
597
- return this.alignmentNames[self.currentAlignment]
598
- },
599
- /**
600
- * #getter
601
- */
602
- get alignmentNames() {
603
- return this.MSA?.alignmentNames || []
604
- },
605
- /**
606
- * #getter
607
- */
608
- get noTree() {
609
- return !!this._tree.noTree
610
- },
611
- /**
612
- * #getter
613
- */
614
- get menuItems() {
615
- return []
616
- },
617
- /**
618
- * #getter
619
- */
620
- get treeMetadata() {
621
- return self.data.treeMetadata ? JSON.parse(self.data.treeMetadata) : {}
622
- },
623
- /**
624
- * #getter
625
- */
626
- get MSA() {
627
- const text = self.data.msa
628
- if (text) {
629
- if (Stockholm.sniff(text)) {
630
- return new StockholmMSA(text, self.currentAlignment)
631
- } else if (text.startsWith('>')) {
632
- return new FastaMSA(text)
633
- } else {
634
- return new ClustalMSA(text)
450
+ },
451
+
452
+ /**
453
+ * #action
454
+ */
455
+ setMSA(result: string) {
456
+ self.data.setMSA(result)
457
+ },
458
+
459
+ /**
460
+ * #action
461
+ */
462
+ setTree(result: string) {
463
+ self.data.setTree(result)
464
+ },
465
+
466
+ /**
467
+ * #action
468
+ */
469
+ setTreeMetadata(result: string) {
470
+ self.data.setTreeMetadata(result)
471
+ },
472
+ }))
473
+
474
+ .views(self => ({
475
+ /**
476
+ * #method
477
+ * unused here, but can be used by derived classes to add extra items
478
+ */
479
+ extraViewMenuItems() {
480
+ return []
481
+ },
482
+ /**
483
+ * #getter
484
+ */
485
+ get colorScheme() {
486
+ return colorSchemes[self.colorSchemeName]
487
+ },
488
+
489
+ /**
490
+ * #getter
491
+ */
492
+ get header() {
493
+ return this.MSA?.getHeader() || {}
494
+ },
495
+
496
+ /**
497
+ * #method
498
+ */
499
+ getRowData(name: string) {
500
+ return {
501
+ data: this.MSA?.getRowData(name),
635
502
  }
636
- }
637
- return null
638
- },
639
- /**
640
- * #getter
641
- */
642
- get numColumns() {
643
- return (this.MSA?.getWidth() || 0) - this.blanks.length
644
- },
645
- /**
646
- * #getter
647
- */
648
- get _tree(): NodeWithIds {
649
- const ret = self.data.tree
650
- ? generateNodeIds(parseNewick(self.data.tree))
651
- : this.MSA?.getTree() || {
652
- noTree: true,
653
- branchset: [],
654
- id: 'empty',
655
- name: 'empty',
503
+ },
504
+
505
+ /**
506
+ * #getter
507
+ */
508
+ get alignmentNames() {
509
+ return this.MSA?.alignmentNames || []
510
+ },
511
+ /**
512
+ * #getter
513
+ */
514
+ get noTree() {
515
+ return !!this._tree.noTree
516
+ },
517
+ /**
518
+ * #getter
519
+ */
520
+ get noAnnotations() {
521
+ return !self.loadedInterProAnnotations
522
+ },
523
+ /**
524
+ * #getter
525
+ */
526
+ get menuItems() {
527
+ return []
528
+ },
529
+ /**
530
+ * #getter
531
+ */
532
+ get treeMetadata() {
533
+ return self.data.treeMetadata ? JSON.parse(self.data.treeMetadata) : {}
534
+ },
535
+ /**
536
+ * #getter
537
+ */
538
+ get MSA() {
539
+ const text = self.data.msa
540
+ if (text) {
541
+ if (Stockholm.sniff(text)) {
542
+ return new StockholmMSA(text, self.currentAlignment)
543
+ } else if (text.startsWith('>')) {
544
+ return new FastaMSA(text)
545
+ } else {
546
+ return new ClustalMSA(text)
547
+ }
548
+ }
549
+ return null
550
+ },
551
+ /**
552
+ * #getter
553
+ */
554
+ get numColumns() {
555
+ return (this.MSA?.getWidth() || 0) - this.blanks.length
556
+ },
557
+
558
+ /**
559
+ * #getter
560
+ */
561
+ get _tree(): NodeWithIds {
562
+ const ret = self.data.tree
563
+ ? generateNodeIds(parseNewick(self.data.tree))
564
+ : this.MSA?.getTree() || {
565
+ noTree: true,
566
+ branchset: [],
567
+ id: 'empty',
568
+ name: 'empty',
569
+ }
570
+ return reparseTree(ret)
571
+ },
572
+ /**
573
+ * #getter
574
+ */
575
+ get rowNames(): string[] {
576
+ return this.hierarchy.leaves().map(n => n.data.name)
577
+ },
578
+ /**
579
+ * #getter
580
+ */
581
+ get mouseOverRowName() {
582
+ const { mouseRow } = self
583
+ return mouseRow !== undefined ? this.rowNames[mouseRow] : undefined
584
+ },
585
+
586
+ /**
587
+ * #getter
588
+ */
589
+ get root() {
590
+ let hier = hierarchy(this._tree, d => d.branchset)
591
+ .sum(d => (d.branchset ? 0 : 1))
592
+ .sort((a, b) => ascending(a.data.length || 1, b.data.length || 1))
593
+
594
+ if (self.showOnly) {
595
+ const res = hier.find(n => n.data.id === self.showOnly)
596
+ if (res) {
597
+ hier = res
656
598
  }
657
- return reparseTree(ret)
658
- },
659
- /**
660
- * #getter
661
- */
662
- get rowNames(): string[] {
663
- return this.hierarchy.leaves().map(node => node.data.name)
664
- },
665
- /**
666
- * #getter
667
- */
668
- get mouseOverRowName() {
669
- return self.mouseRow !== undefined
670
- ? this.rowNames[self.mouseRow]
671
- : undefined
672
- },
673
-
674
- /**
675
- * #method
676
- */
677
- getMouseOverResidue(rowName: string) {
678
- return this.columns[rowName]
679
- },
680
-
681
- /**
682
- * #getter
683
- */
684
- get root() {
685
- let hier = hierarchy(this._tree, d => d.branchset)
686
- .sum(d => (d.branchset ? 0 : 1))
687
- .sort((a, b) => ascending(a.data.length || 1, b.data.length || 1))
688
-
689
- if (self.showOnly) {
690
- const res = hier.find(node => node.data.id === self.showOnly)
691
- if (res) {
692
- hier = res
693
599
  }
694
- }
695
600
 
696
- if (self.collapsed.length || self.collapsed2.length) {
697
601
  ;[...self.collapsed, ...self.collapsed2]
698
602
  .map(collapsedId => hier.find(node => node.data.id === collapsedId))
699
603
  .filter(notEmpty)
700
604
  .map(node => collapse(node))
701
- }
702
605
 
703
- return hier
704
- },
705
- /**
706
- * #getter
707
- */
708
- get structures(): Record<string, Structure[]> {
709
- return this.MSA?.getStructures() || {}
710
- },
711
- /**
712
- * #getter
713
- */
714
- get inverseStructures() {
715
- return Object.fromEntries(
716
- Object.entries(this.structures).flatMap(([key, val]) =>
717
- val.map(pdbEntry => [pdbEntry.pdb, { id: key }]),
718
- ),
719
- )
720
- },
721
- /**
722
- * #getter
723
- * widget width minus the tree area gives the space for the MSA
724
- */
725
- get msaAreaWidth() {
726
- return self.width - self.treeAreaWidth
727
- },
728
-
729
- /**
730
- * #getter
731
- */
732
- get treeAreaWidthMinusMargin() {
733
- return self.treeAreaWidth - self.marginLeft
734
- },
735
- /**
736
- * #getter
737
- */
738
- get blanks() {
739
- const blanks = []
740
- const strs = this.hierarchy
741
- .leaves()
742
- .map(leaf => this.MSA?.getRow(leaf.data.name))
743
- .filter((item): item is string => !!item)
744
-
745
- for (let i = 0; i < strs[0]?.length; i++) {
746
- let counter = 0
747
- for (const str of strs) {
748
- if (str[i] === '-') {
749
- counter++
606
+ return hier
607
+ },
608
+
609
+ /**
610
+ * #getter
611
+ * widget width minus the tree area gives the space for the MSA
612
+ */
613
+ get msaAreaWidth() {
614
+ return self.width - self.treeAreaWidth
615
+ },
616
+
617
+ /**
618
+ * #getter
619
+ */
620
+ get treeAreaWidthMinusMargin() {
621
+ return self.treeAreaWidth - self.marginLeft
622
+ },
623
+ /**
624
+ * #getter
625
+ */
626
+ get blanks() {
627
+ const blanks = []
628
+ const strs = this.hierarchy
629
+ .leaves()
630
+ .map(leaf => this.MSA?.getRow(leaf.data.name))
631
+ .filter((item): item is string => !!item)
632
+
633
+ for (let i = 0; i < strs[0]?.length; i++) {
634
+ let counter = 0
635
+ for (const str of strs) {
636
+ if (str[i] === '-') {
637
+ counter++
638
+ }
750
639
  }
751
- }
752
- if (counter === strs.length) {
753
- blanks.push(i)
754
- }
755
- }
756
- return blanks
757
- },
758
- /**
759
- * #getter
760
- */
761
- get rows() {
762
- const MSA = this.MSA
763
- return this.hierarchy
764
- .leaves()
765
- .map(leaf => [leaf.data.name, MSA?.getRow(leaf.data.name)] as const)
766
- .filter((f): f is [string, string] => !!f[1])
767
- },
768
- /**
769
- * #getter
770
- */
771
- get columns() {
772
- return Object.fromEntries(
773
- this.rows.map((row, index) => [row[0], this.columns2d[index]] as const),
774
- )
775
- },
776
- /**
777
- * #getter
778
- */
779
- get columns2d() {
780
- return this.rows.map(r => r[1]).map(str => skipBlanks(this.blanks, str))
781
- },
782
- /**
783
- * #getter
784
- */
785
- get fontSize() {
786
- return Math.max(8, self.rowHeight - 8)
787
- },
788
- /**
789
- * #getter
790
- */
791
- get colStats() {
792
- const r = [] as Record<string, number>[]
793
- const columns = this.columns2d
794
- for (const column of columns) {
795
- for (let j = 0; j < column.length; j++) {
796
- const l = r[j] || {}
797
- if (!l[column[j]]) {
798
- l[column[j]] = 0
640
+ if (counter === strs.length) {
641
+ blanks.push(i)
799
642
  }
800
- l[column[j]]++
801
- r[j] = l
802
- }
803
- }
804
- return r
805
- },
806
- /**
807
- * #getter
808
- * generates a new tree that is clustered with x,y positions
809
- */
810
- get hierarchy(): HierarchyNode<NodeWithIdsAndLength> {
811
- const r = this.root
812
- const clust = cluster<NodeWithIds>()
813
- .size([this.totalHeight, self.treeWidth])
814
- .separation(() => 1)
815
- clust(r)
816
- setBrLength(r, (r.data.length = 0), self.treeWidth / maxLength(r))
817
- return r as HierarchyNode<NodeWithIdsAndLength>
818
- },
819
-
820
- /**
821
- * #getter
822
- */
823
- get totalHeight() {
824
- return this.root.leaves().length * self.rowHeight
825
- },
826
- }))
827
- .views(self => ({
828
- /**
829
- * #getter
830
- */
831
- get totalWidth() {
832
- return self.numColumns * self.colWidth
833
- },
834
- }))
835
-
836
- .views(self => ({
837
- /**
838
- * #getter
839
- */
840
- get initialized() {
841
- return (
842
- (self.data.msa ||
843
- self.data.tree ||
844
- self.msaFilehandle ||
845
- self.treeFilehandle) &&
846
- !self.error
847
- )
848
- },
849
- /**
850
- * #getter
851
- */
852
- get blocksX() {
853
- return blocksX({
854
- viewportWidth: self.msaAreaWidth,
855
- viewportX: -self.scrollX,
856
- blockSize: self.blockSize,
857
- mapWidth: self.totalWidth,
858
- })
859
- },
860
- /**
861
- * #getter
862
- */
863
- get blocksY() {
864
- return blocksY({
865
- viewportHeight: self.height,
866
- viewportY: -self.scrollY,
867
- blockSize: self.blockSize,
868
- mapHeight: self.totalHeight,
869
- })
870
- },
871
- }))
872
- .views(self => ({
873
- /**
874
- * #getter
875
- */
876
- get blocks2d() {
877
- const ret = []
878
- for (const by of self.blocksY) {
879
- for (const bx of self.blocksX) {
880
- ret.push([bx, by])
881
643
  }
882
- }
883
- return ret
884
- },
885
-
886
- /**
887
- * #getter
888
- */
889
- get done() {
890
- return self.initialized && (self.data.msa || self.data.tree)
891
- },
892
- /**
893
- * #getter
894
- */
895
- get maxScrollX() {
896
- return -self.totalWidth + (self.msaAreaWidth - 100)
897
- },
898
- }))
899
- .actions(self => ({
900
- /**
901
- * #action
902
- */
903
- addUniprotTrack(node: { name: string; accession: string }) {
904
- if (self.boxTracks.some(t => t.name === node.name)) {
905
- if (self.turnedOffTracks.has(node.name)) {
906
- this.toggleTrack(node.name)
644
+ return blanks
645
+ },
646
+ /**
647
+ * #getter
648
+ */
649
+ get rows() {
650
+ const MSA = this.MSA
651
+ return this.hierarchy
652
+ .leaves()
653
+ .map(leaf => [leaf.data.name, MSA?.getRow(leaf.data.name)] as const)
654
+ .filter((f): f is [string, string] => !!f[1])
655
+ },
656
+ /**
657
+ * #getter
658
+ */
659
+ get columns() {
660
+ return Object.fromEntries(
661
+ this.rows.map(
662
+ (row, index) => [row[0], this.columns2d[index]] as const,
663
+ ),
664
+ )
665
+ },
666
+ /**
667
+ * #getter
668
+ */
669
+ get columns2d() {
670
+ return this.rows.map(r => r[1]).map(str => skipBlanks(this.blanks, str))
671
+ },
672
+ /**
673
+ * #getter
674
+ */
675
+ get fontSize() {
676
+ return Math.min(Math.max(6, self.rowHeight - 8), 18)
677
+ },
678
+ /**
679
+ * #getter
680
+ */
681
+ get colStats() {
682
+ const r = [] as Record<string, number>[]
683
+ const columns = this.columns2d
684
+ for (const column of columns) {
685
+ for (let j = 0; j < column.length; j++) {
686
+ const l = r[j] || {}
687
+ if (!l[column[j]]) {
688
+ l[column[j]] = 0
689
+ }
690
+ l[column[j]]++
691
+ r[j] = l
692
+ }
907
693
  }
908
- } else {
909
- self.boxTracks.push({
910
- ...node,
911
- id: node.name,
912
- associatedRowName: node.name,
694
+ return r
695
+ },
696
+ /**
697
+ * #getter
698
+ * generates a new tree that is clustered with x,y positions
699
+ */
700
+ get hierarchy(): HierarchyNode<NodeWithIdsAndLength> {
701
+ const r = this.root
702
+ const clust = cluster<NodeWithIds>()
703
+ .size([this.totalHeight, self.treeWidth])
704
+ .separation(() => 1)
705
+ clust(r)
706
+ setBrLength(r, (r.data.length = 0), self.treeWidth / maxLength(r))
707
+ return r as HierarchyNode<NodeWithIdsAndLength>
708
+ },
709
+
710
+ /**
711
+ * #getter
712
+ */
713
+ get totalHeight() {
714
+ return this.root.leaves().length * self.rowHeight
715
+ },
716
+ }))
717
+ .views(self => ({
718
+ /**
719
+ * #getter
720
+ */
721
+ get totalWidth() {
722
+ return self.numColumns * self.colWidth
723
+ },
724
+ }))
725
+
726
+ .views(self => ({
727
+ /**
728
+ * #getter
729
+ */
730
+ get initialized() {
731
+ return (self.data.msa || self.data.tree) && !self.error
732
+ },
733
+ /**
734
+ * #getter
735
+ */
736
+ get blocksX() {
737
+ return blocksX({
738
+ viewportWidth: self.msaAreaWidth,
739
+ viewportX: -self.scrollX,
740
+ blockSize: self.blockSize,
741
+ mapWidth: self.totalWidth,
913
742
  })
914
- }
915
- },
916
-
917
- /**
918
- * #action
919
- */
920
- doScrollY(deltaY: number) {
921
- self.scrollY = clamp(-self.totalHeight + 10, self.scrollY + deltaY, 0)
922
- },
923
-
924
- /**
925
- * #action
926
- */
927
- doScrollX(deltaX: number) {
928
- self.scrollX = clamp(self.maxScrollX, self.scrollX + deltaX, 0)
929
- },
930
-
931
- /**
932
- * #action
933
- */
934
- setScrollX(n: number) {
935
- self.scrollX = clamp(self.maxScrollX, n, 0)
936
- },
937
-
938
- /**
939
- * #action
940
- */
941
- setMouseoveredColumn(n: number, chain: string, file: string) {
942
- let j = 0
943
- let i = 0
944
- const { id } = self.inverseStructures[file.slice(0, -4)] || {}
945
- const row = self.MSA?.getRow(id)
946
-
947
- if (row) {
948
- for (i = 0; i < row.length && j < n; i++) {
949
- if (row[i] !== '-') {
950
- j++
743
+ },
744
+ /**
745
+ * #getter
746
+ */
747
+ get blocksY() {
748
+ return blocksY({
749
+ viewportHeight: self.height,
750
+ viewportY: -self.scrollY,
751
+ blockSize: self.blockSize,
752
+ mapHeight: self.totalHeight,
753
+ })
754
+ },
755
+ }))
756
+ .views(self => ({
757
+ /**
758
+ * #getter
759
+ */
760
+ get blocks2d() {
761
+ const ret = []
762
+ for (const by of self.blocksY) {
763
+ for (const bx of self.blocksX) {
764
+ ret.push([bx, by])
951
765
  }
952
766
  }
953
- self.mouseCol = j + 1
954
- } else {
955
- self.mouseCol = undefined
956
- }
957
- },
958
- /**
959
- * #action
960
- */
961
- toggleTrack(id: string) {
962
- if (self.turnedOffTracks.has(id)) {
963
- self.turnedOffTracks.delete(id)
964
- } else {
965
- self.turnedOffTracks.set(id, true)
966
- }
967
- },
968
- }))
969
- .views(self => ({
970
- /**
971
- * #getter
972
- */
973
- get labelsWidth() {
974
- let x = 0
975
- const { rowHeight, hierarchy, treeMetadata, fontSize } = self
976
- if (rowHeight > 5) {
977
- for (const node of hierarchy.leaves()) {
978
- x = Math.max(
979
- measureTextCanvas(
980
- treeMetadata[node.data.name]?.genome || node.data.name,
981
- fontSize,
982
- ),
983
- x,
984
- )
767
+ return ret
768
+ },
769
+
770
+ /**
771
+ * #getter
772
+ */
773
+ get isLoading() {
774
+ return self.loadingMSA || self.loadingTree
775
+ },
776
+ /**
777
+ * #getter
778
+ */
779
+ get maxScrollX() {
780
+ return -self.totalWidth + (self.msaAreaWidth - 100)
781
+ },
782
+ }))
783
+ .actions(self => ({
784
+ /**
785
+ * #action
786
+ */
787
+ zoomIn() {
788
+ self.colWidth = Math.ceil(self.colWidth * 1.5)
789
+ self.rowHeight = Math.ceil(self.rowHeight * 1.5)
790
+ self.scrollX = clamp(self.maxScrollX, self.scrollX, 0)
791
+ },
792
+ /**
793
+ * #action
794
+ */
795
+ zoomOut() {
796
+ self.colWidth = Math.max(1, Math.floor(self.colWidth * 0.75))
797
+ self.rowHeight = Math.max(1.5, Math.floor(self.rowHeight * 0.75))
798
+ self.scrollX = clamp(self.maxScrollX, self.scrollX, 0)
799
+ },
800
+ /**
801
+ * #action
802
+ */
803
+ setLoadedInterProAnnotations(data: Record<string, InterProScanResults>) {
804
+ self.loadedInterProAnnotations = data
805
+ },
806
+
807
+ /**
808
+ * #action
809
+ */
810
+ doScrollY(deltaY: number) {
811
+ self.scrollY = clamp(-self.totalHeight + 10, self.scrollY + deltaY, 0)
812
+ },
813
+
814
+ /**
815
+ * #action
816
+ */
817
+ doScrollX(deltaX: number) {
818
+ self.scrollX = clamp(self.maxScrollX, self.scrollX + deltaX, 0)
819
+ },
820
+
821
+ /**
822
+ * #action
823
+ */
824
+ setScrollX(n: number) {
825
+ self.scrollX = clamp(self.maxScrollX, n, 0)
826
+ },
827
+
828
+ /**
829
+ * #action
830
+ */
831
+ toggleTrack(id: string) {
832
+ if (self.turnedOffTracks.has(id)) {
833
+ self.turnedOffTracks.delete(id)
834
+ } else {
835
+ self.turnedOffTracks.set(id, true)
985
836
  }
986
- }
987
- return x
988
- },
989
-
990
- /**
991
- * #getter
992
- */
993
- get secondaryStructureConsensus() {
994
- return self.MSA?.secondaryStructureConsensus
995
- },
996
-
997
- /**
998
- * #getter
999
- */
1000
- get seqConsensus() {
1001
- return self.MSA?.seqConsensus
1002
- },
1003
-
1004
- /**
1005
- * #getter
1006
- */
1007
- get conservation() {
1008
- if (self.columns2d.length) {
1009
- for (let i = 0; i < self.columns2d[0].length; i++) {
1010
- const col = []
1011
- for (const column of self.columns2d) {
1012
- col.push(column[i])
837
+ },
838
+ /**
839
+ * #action
840
+ */
841
+ addInterProScanJobId(arg: string) {
842
+ self.interProScanJobIds = [
843
+ ...self.interProScanJobIds,
844
+ { jobId: arg, date: +Date.now() },
845
+ ]
846
+ },
847
+ /**
848
+ * #action
849
+ */
850
+ setStatus(status?: { msg: string; url?: string }) {
851
+ self.status = status
852
+ },
853
+ }))
854
+ .views(self => ({
855
+ /**
856
+ * #getter
857
+ */
858
+ get labelsWidth() {
859
+ let x = 0
860
+ const { rowHeight, hierarchy, treeMetadata, fontSize } = self
861
+ if (rowHeight > 5) {
862
+ for (const node of hierarchy.leaves()) {
863
+ x = Math.max(
864
+ measureTextCanvas(
865
+ treeMetadata[node.data.name]?.genome || node.data.name,
866
+ fontSize,
867
+ ),
868
+ x,
869
+ )
1013
870
  }
1014
871
  }
1015
- }
1016
- return ['a']
1017
- },
1018
-
1019
- /**
1020
- * #getter
1021
- */
1022
- get adapterTrackModels(): BasicTrack[] {
1023
- return (
1024
- self.MSA?.tracks.map(t => ({
1025
- model: {
1026
- ...t,
1027
- data: t.data ? skipBlanks(self.blanks, t.data) : undefined,
1028
- height: self.rowHeight,
1029
- } as TextTrackModel,
1030
- ReactComponent: TextTrack,
1031
- })) || []
1032
- )
1033
- },
1034
-
1035
- /**
1036
- * #getter
1037
- */
1038
- get boxTrackModels(): BasicTrack[] {
1039
- return self.boxTracks
1040
- .filter(track => self.rows.some(row => row[0] === track.name))
1041
- .map(track => ({
1042
- model: track as BoxTrackModel,
1043
- ReactComponent: BoxTrack,
1044
- }))
1045
- },
1046
-
1047
- /**
1048
- * #getter
1049
- */
1050
- get tracks(): BasicTrack[] {
1051
- return [...this.adapterTrackModels, ...this.boxTrackModels]
1052
- },
1053
- /**
1054
- * #getter
1055
- */
1056
- get turnedOnTracks() {
1057
- return this.tracks.filter(f => !self.turnedOffTracks.has(f.model.id))
1058
- },
1059
-
1060
- /**
1061
- * #method
1062
- * returns coordinate in the current relative coordinate scheme
1063
- */
1064
- pxToBp(coord: number) {
1065
- return Math.floor((coord - self.scrollX) / self.colWidth)
1066
- },
1067
-
1068
- /**
1069
- * #method
1070
- */
1071
- rowSpecificBpToPx(rowName: string, position: number) {
1072
- const { rowNames, rows } = self
1073
- const index = rowNames.indexOf(rowName)
1074
- const row = rows[index][1]
1075
- const details = self.getRowData(rowName)
1076
- const offset = details.range?.start || 0
1077
- const current = position - offset
1078
- const s = new Set(self.blanks)
1079
-
1080
- if (current < 0) {
1081
- return 0
1082
- }
872
+ return x
873
+ },
1083
874
 
1084
- let j = 0
1085
- let i = 0
875
+ /**
876
+ * #getter
877
+ */
878
+ get secondaryStructureConsensus() {
879
+ return self.MSA?.secondaryStructureConsensus
880
+ },
1086
881
 
1087
- for (; i < row.length; i++) {
1088
- if (row[i] !== '-' && j++ === current) {
1089
- break
1090
- }
1091
- }
882
+ /**
883
+ * #getter
884
+ */
885
+ get seqConsensus() {
886
+ return self.MSA?.seqConsensus
887
+ },
1092
888
 
1093
- let count = 0
1094
- for (let k = 0; k < row.length; k++) {
1095
- if (s.has(k) && k < i + 1) {
1096
- count++
889
+ /**
890
+ * #getter
891
+ */
892
+ get conservation() {
893
+ if (self.columns2d.length) {
894
+ for (let i = 0; i < self.columns2d[0].length; i++) {
895
+ const col = []
896
+ for (const column of self.columns2d) {
897
+ col.push(column[i])
898
+ }
899
+ }
1097
900
  }
1098
- }
901
+ return ['a']
902
+ },
1099
903
 
1100
- return i - count
1101
- },
904
+ /**
905
+ * #getter
906
+ */
907
+ get adapterTrackModels(): BasicTrack[] {
908
+ return (
909
+ self.MSA?.tracks.map(t => ({
910
+ model: {
911
+ ...t,
912
+ data: t.data ? skipBlanks(self.blanks, t.data) : undefined,
913
+ height: self.rowHeight,
914
+ } as TextTrackModel,
915
+ ReactComponent: TextTrack,
916
+ })) || []
917
+ )
918
+ },
1102
919
 
1103
- /**
1104
- * #method
1105
- */
1106
- globalBpToPx(position: number) {
1107
- let count = 0
1108
- const s = new Set(self.blanks)
920
+ /**
921
+ * #getter
922
+ */
923
+ get tracks(): BasicTrack[] {
924
+ return this.adapterTrackModels
925
+ },
1109
926
 
1110
- for (let k = 0; k < self.rows[0]?.[1].length; k++) {
1111
- if (s.has(k) && k < position + 1) {
1112
- count++
1113
- }
1114
- }
927
+ /**
928
+ * #getter
929
+ */
930
+ get turnedOnTracks() {
931
+ return this.tracks.filter(f => !self.turnedOffTracks.has(f.model.id))
932
+ },
1115
933
 
1116
- return position - count
1117
- },
1118
-
1119
- /**
1120
- * #method
1121
- */
1122
- relativePxToBp(rowName: string, position: number) {
1123
- const { rowNames, rows } = self
1124
- const index = rowNames.indexOf(rowName)
1125
- if (index !== -1 && rows[index]) {
1126
- const row = rows[index][1]
1127
-
1128
- let k = 0
1129
- for (let i = 0; i < position; i++) {
1130
- if (row[i] !== '-') {
1131
- k++
1132
- } else if (k >= position) {
1133
- break
934
+ /**
935
+ * #method
936
+ * return a row-specific sequence coordinate, skipping gaps, given a global
937
+ * coordinate
938
+ */
939
+ globalCoordToRowSpecificSeqCoord(rowName: string, position: number) {
940
+ const { rowNames, rows } = self
941
+ const index = rowNames.indexOf(rowName)
942
+ if (index !== -1 && rows[index]) {
943
+ const row = rows[index][1]
944
+
945
+ let k = 0
946
+ for (let i = 0; i < position; i++) {
947
+ if (row[i] !== '-') {
948
+ k++
949
+ } else if (k >= position) {
950
+ break
951
+ }
1134
952
  }
953
+ return k
1135
954
  }
1136
- return k
1137
- }
1138
- return 0
1139
- },
1140
-
1141
- /**
1142
- * #method
1143
- */
1144
- relativePxToBp2(rowName: string, position: number) {
1145
- const { rowNames, rows } = self
1146
- const index = rowNames.indexOf(rowName)
1147
- if (index !== -1 && rows[index]) {
1148
- const row = rows[index][1]
1149
-
1150
- let k = 0
1151
- let i = 0
1152
- for (; k < position; i++) {
1153
- if (row[i] !== '-') {
1154
- k++
1155
- } else if (k >= position) {
1156
- break
955
+ return 0
956
+ },
957
+
958
+ /**
959
+ * #method
960
+ * return a global coordinate given a row-specific sequence coordinate
961
+ * which does not not include gaps
962
+ */
963
+ seqCoordToRowSpecificGlobalCoord(rowName: string, position: number) {
964
+ const { rowNames, rows } = self
965
+ const index = rowNames.indexOf(rowName)
966
+ if (index !== -1 && rows[index]) {
967
+ const row = rows[index][1]
968
+
969
+ let k = 0
970
+ let i = 0
971
+ for (; k < position; i++) {
972
+ if (row[i] !== '-') {
973
+ k++
974
+ } else if (k >= position) {
975
+ break
976
+ }
1157
977
  }
978
+ return i
1158
979
  }
1159
- return i
1160
- }
1161
- return 0
1162
- },
1163
- /**
1164
- * #method
1165
- */
1166
- getPos(pos: number) {
1167
- let j = 0
1168
- for (let i = 0, k = 0; i < pos; i++, j++) {
1169
- while (j === self.blanks[k]) {
1170
- k++
1171
- j++
980
+ return 0
981
+ },
982
+ }))
983
+
984
+ .views(self => ({
985
+ /**
986
+ * #getter
987
+ * total height of track area (px)
988
+ */
989
+ get totalTrackAreaHeight() {
990
+ return sum(self.turnedOnTracks.map(r => r.model.height))
991
+ },
992
+ /**
993
+ * #getter
994
+ */
995
+ get tidyTypes() {
996
+ const types = new Map<string, Accession>()
997
+ if (this.tidyAnnotations) {
998
+ for (const { name, accession, description } of this.tidyAnnotations) {
999
+ types.set(accession, { name, accession, description })
1000
+ }
1172
1001
  }
1173
- }
1174
- return j
1175
- },
1176
- }))
1177
-
1178
- .views(self => ({
1179
- /**
1180
- * #getter
1181
- * total height of track area (px)
1182
- */
1183
- get totalTrackAreaHeight() {
1184
- return sum(self.turnedOnTracks.map(r => r.model.height))
1185
- },
1186
- }))
1187
- .actions(self => ({
1188
- /**
1189
- * #action
1190
- */
1191
- async exportSVG(opts: {
1192
- theme: Theme
1193
- includeMinimap?: boolean
1194
- exportType: string
1195
- }) {
1196
- const { renderToSvg } = await import('./renderToSvg')
1197
- const html = await renderToSvg(self as MsaViewModel, opts)
1198
- const blob = new Blob([html], { type: 'image/svg+xml' })
1199
- saveAs(blob, 'image.svg')
1200
- },
1201
- /**
1202
- * #action
1203
- * internal, used for drawing to canvas
1204
- */
1205
- incrementRef() {
1206
- self.nref++
1207
- },
1208
- afterCreate() {
1209
- // autorun opens treeFilehandle
1210
- addDisposer(
1211
- self,
1212
- autorun(async () => {
1213
- const { treeFilehandle } = self
1214
- if (treeFilehandle) {
1215
- try {
1216
- self.setTree(await openLocation(treeFilehandle).readFile('utf8'))
1217
- } catch (e) {
1218
- console.error(e)
1219
- self.setError(e)
1002
+ return types
1003
+ },
1004
+ get tidyAnnotations() {
1005
+ const ret = []
1006
+ const { loadedInterProAnnotations } = self
1007
+ if (loadedInterProAnnotations) {
1008
+ for (const [id, val] of Object.entries(loadedInterProAnnotations)) {
1009
+ for (const { signature, locations } of val.matches) {
1010
+ const { entry } = signature
1011
+ if (entry) {
1012
+ const { name, accession, description } = entry
1013
+ for (const { start, end } of locations) {
1014
+ ret.push({
1015
+ id,
1016
+ name,
1017
+ accession,
1018
+ description,
1019
+ start,
1020
+ end,
1021
+ })
1022
+ }
1023
+ }
1220
1024
  }
1221
1025
  }
1222
- }),
1223
- )
1224
- // autorun opens treeMetadataFilehandle
1225
- addDisposer(
1226
- self,
1227
- autorun(async () => {
1228
- const { treeMetadataFilehandle } = self
1229
- if (treeMetadataFilehandle) {
1230
- try {
1231
- self.setTreeMetadata(
1232
- await openLocation(treeMetadataFilehandle).readFile('utf8'),
1233
- )
1234
- } catch (e) {
1235
- console.error(e)
1236
- self.setError(e)
1026
+ }
1027
+ return ret.sort((a, b) => len(b) - len(a))
1028
+ },
1029
+ /**
1030
+ * #getter
1031
+ */
1032
+ get tidyFilteredAnnotations() {
1033
+ return this.tidyAnnotations.filter(r =>
1034
+ self.featureFilters.get(r.accession),
1035
+ )
1036
+ },
1037
+ /**
1038
+ * #getter
1039
+ */
1040
+ get tidyFilteredGatheredAnnotations() {
1041
+ return groupBy(this.tidyFilteredAnnotations, r => r.id)
1042
+ },
1043
+ }))
1044
+ .views(self => ({
1045
+ get fillPalette() {
1046
+ const arr = [...self.tidyTypes.keys()]
1047
+ let i = 0
1048
+ const map = {} as Record<string, string>
1049
+ for (const key of arr) {
1050
+ const k = Math.min(arr.length - 1, palettes.length - 1)
1051
+ map[key] = palettes[k][i]
1052
+ i++
1053
+ }
1054
+ return map
1055
+ },
1056
+ get strokePalette() {
1057
+ return Object.fromEntries(
1058
+ Object.entries(this.fillPalette).map(([key, val]) => [
1059
+ key,
1060
+ colord(val).darken(0.1).toHex(),
1061
+ ]),
1062
+ )
1063
+ },
1064
+ }))
1065
+ .actions(self => ({
1066
+ /**
1067
+ * #action
1068
+ */
1069
+ async loadInterProScanResults(jobId: string) {
1070
+ self.setStatus({ msg: 'Loading ' + jobId })
1071
+ const ret = await loadInterProScanResults(jobId)
1072
+ self.setStatus()
1073
+ self.setLoadedInterProAnnotations(
1074
+ Object.fromEntries(ret.results.map(r => [r.xref[0].id, r])),
1075
+ )
1076
+ },
1077
+ /**
1078
+ * #action
1079
+ */
1080
+ async queryInterProScan(programs: string[]) {
1081
+ const { rows } = self
1082
+ if (rows.length > 140) {
1083
+ throw new Error('Too many sequences, please run InterProScan offline')
1084
+ }
1085
+
1086
+ const ret = await launchInterProScan({
1087
+ algorithm: 'interproscan',
1088
+ programs: programs,
1089
+ seq: rows
1090
+ .map(row => `>${row[0]}\n${row[1].replaceAll('-', '')}`)
1091
+ .join('\n'),
1092
+ onProgress: arg => self.setStatus(arg),
1093
+ onJobId: jobId => self.addInterProScanJobId(jobId),
1094
+ })
1095
+
1096
+ self.setLoadedInterProAnnotations(
1097
+ Object.fromEntries(ret.results.map(r => [r.xref[0].id, r])),
1098
+ )
1099
+ },
1100
+ /**
1101
+ * #action
1102
+ */
1103
+ reset() {
1104
+ transaction(() => {
1105
+ self.setData({ tree: '', msa: '' })
1106
+ self.setScrollY(0)
1107
+ self.setScrollX(0)
1108
+ self.setCurrentAlignment(0)
1109
+ self.setTreeFilehandle(undefined)
1110
+ self.setMSAFilehandle(undefined)
1111
+ })
1112
+ },
1113
+ /**
1114
+ * #action
1115
+ */
1116
+ async exportSVG(opts: {
1117
+ theme: Theme
1118
+ includeMinimap?: boolean
1119
+ exportType: string
1120
+ }) {
1121
+ const { renderToSvg } = await import('./renderToSvg')
1122
+ const html = await renderToSvg(self as MsaViewModel, opts)
1123
+ const blob = new Blob([html], { type: 'image/svg+xml' })
1124
+ saveAs(blob, 'image.svg')
1125
+ },
1126
+ /**
1127
+ * #action
1128
+ * internal, used for drawing to canvas
1129
+ */
1130
+ incrementRef() {
1131
+ self.nref++
1132
+ },
1133
+
1134
+ /**
1135
+ * #action
1136
+ */
1137
+ initFilter(arg: string) {
1138
+ const ret = self.featureFilters.get(arg)
1139
+ if (ret === undefined) {
1140
+ self.featureFilters.set(arg, true)
1141
+ }
1142
+ },
1143
+ /**
1144
+ * #action
1145
+ */
1146
+ setFilter(arg: string, flag: boolean) {
1147
+ self.featureFilters.set(arg, flag)
1148
+ },
1149
+
1150
+ afterCreate() {
1151
+ addDisposer(
1152
+ self,
1153
+ autorun(() => {
1154
+ for (const key of self.tidyTypes.keys()) {
1155
+ this.initFilter(key)
1237
1156
  }
1238
- }
1239
- }),
1240
- )
1241
-
1242
- // autorun opens msaFilehandle
1243
- addDisposer(
1244
- self,
1245
- autorun(async () => {
1246
- const { msaFilehandle } = self
1247
- if (msaFilehandle) {
1248
- try {
1249
- self.setMSA(await openLocation(msaFilehandle).readFile('utf8'))
1250
- } catch (e) {
1251
- console.error(e)
1252
- self.setError(e)
1157
+ }),
1158
+ )
1159
+ addDisposer(
1160
+ self,
1161
+ autorun(async () => {
1162
+ // const res = Object.fromEntries(
1163
+ // await Promise.all(
1164
+ // self.annotationTracks.map(async f => {
1165
+ // const d = await jsonfetch(
1166
+ // `https://jbrowse.org/demos/interproscan/json/${encodeURIComponent(f)}`,
1167
+ // )
1168
+ // return [
1169
+ // decodeURIComponent(f).replace('.json', ''),
1170
+ // d.result.results[0],
1171
+ // ] as const
1172
+ // }),
1173
+ // ),
1174
+ // )
1175
+ // self.setLoadedInterProAnnotations(res)
1176
+ }),
1177
+ )
1178
+ // autorun opens treeFilehandle
1179
+ addDisposer(
1180
+ self,
1181
+ autorun(async () => {
1182
+ const { treeFilehandle } = self
1183
+ if (treeFilehandle) {
1184
+ try {
1185
+ self.setLoadingTree(true)
1186
+ self.setTree(
1187
+ await openLocation(treeFilehandle).readFile('utf8'),
1188
+ )
1189
+ if (treeFilehandle.locationType === 'BlobLocation') {
1190
+ // clear filehandle after loading if from a local file
1191
+ self.setTreeFilehandle(undefined)
1192
+ }
1193
+ } catch (e) {
1194
+ console.error(e)
1195
+ self.setError(e)
1196
+ } finally {
1197
+ self.setLoadingTree(false)
1198
+ }
1253
1199
  }
1254
- }
1255
- }),
1256
- )
1257
-
1258
- // autorun synchronizes treeWidth with treeAreaWidth
1259
- addDisposer(
1260
- self,
1261
- autorun(async () => {
1262
- if (self.treeWidthMatchesArea) {
1263
- self.setTreeWidth(
1264
- Math.max(
1265
- 50,
1266
- self.treeAreaWidth - self.labelsWidth - 10 - self.marginLeft,
1267
- ),
1200
+ }),
1201
+ )
1202
+ // autorun opens treeMetadataFilehandle
1203
+ addDisposer(
1204
+ self,
1205
+ autorun(async () => {
1206
+ const { treeMetadataFilehandle } = self
1207
+ if (treeMetadataFilehandle) {
1208
+ try {
1209
+ self.setTreeMetadata(
1210
+ await openLocation(treeMetadataFilehandle).readFile('utf8'),
1211
+ )
1212
+ } catch (e) {
1213
+ console.error(e)
1214
+ self.setError(e)
1215
+ }
1216
+ }
1217
+ }),
1218
+ )
1219
+
1220
+ // autorun opens msaFilehandle
1221
+ addDisposer(
1222
+ self,
1223
+ autorun(async () => {
1224
+ const { msaFilehandle } = self
1225
+ if (msaFilehandle) {
1226
+ try {
1227
+ self.setLoadingMSA(true)
1228
+ const res = await openLocation(msaFilehandle).readFile('utf8')
1229
+ transaction(() => {
1230
+ self.setMSA(res)
1231
+ if (msaFilehandle.locationType === 'BlobLocation') {
1232
+ // clear filehandle after loading if from a local file
1233
+ self.setMSAFilehandle(undefined)
1234
+ }
1235
+ })
1236
+ } catch (e) {
1237
+ console.error(e)
1238
+ self.setError(e)
1239
+ } finally {
1240
+ self.setLoadingMSA(false)
1241
+ }
1242
+ }
1243
+ }),
1244
+ )
1245
+
1246
+ // autorun synchronizes treeWidth with treeAreaWidth
1247
+ addDisposer(
1248
+ self,
1249
+ autorun(async () => {
1250
+ if (self.treeWidthMatchesArea) {
1251
+ self.setTreeWidth(
1252
+ Math.max(
1253
+ 50,
1254
+ self.treeAreaWidth - self.labelsWidth - 10 - self.marginLeft,
1255
+ ),
1256
+ )
1257
+ }
1258
+ }),
1259
+ )
1260
+
1261
+ addDisposer(
1262
+ self,
1263
+ autorun(() => {
1264
+ localStorageSetItem(
1265
+ 'msaview-interproscanqueries',
1266
+ JSON.stringify(self.interProScanJobIds),
1268
1267
  )
1269
- }
1270
- }),
1271
- )
1272
- },
1273
- }))
1274
- .postProcessSnapshot(result => {
1275
- const snap = result as Omit<typeof result, symbol>
1276
- const {
1277
- data: { tree, msa, treeMetadata },
1278
- ...rest
1279
- } = snap
1280
-
1281
- // remove the MSA/tree data from the tree if the filehandle available in
1282
- // which case it can be reloaded on refresh
1283
- return {
1284
- data: {
1285
- // https://andreasimonecosta.dev/posts/the-shortest-way-to-conditionally-insert-properties-into-an-object-literal/
1286
- ...(!result.treeFilehandle && { tree }),
1287
- ...(!result.msaFilehandle && { msa }),
1288
- ...(!result.treeMetadataFilehandle && { treeMetadata }),
1289
- },
1290
- ...rest,
1291
- }
1292
- })
1293
-
1294
- export default model
1295
-
1296
- export type MsaViewStateModel = typeof model
1268
+ }),
1269
+ )
1270
+ },
1271
+ }))
1272
+ .postProcessSnapshot(result => {
1273
+ const snap = result as Omit<typeof result, symbol>
1274
+ const {
1275
+ data: { tree, msa, treeMetadata },
1276
+ ...rest
1277
+ } = snap
1278
+
1279
+ // remove the MSA/tree data from the tree if the filehandle available in
1280
+ // which case it can be reloaded on refresh
1281
+ return {
1282
+ data: {
1283
+ ...(result.treeFilehandle ? {} : { tree }),
1284
+ ...(result.msaFilehandle ? {} : { msa }),
1285
+ ...(result.treeMetadataFilehandle ? {} : { treeMetadata }),
1286
+ },
1287
+ ...rest,
1288
+ }
1289
+ })
1290
+ }
1291
+
1292
+ export default stateModelFactory
1293
+
1294
+ export type MsaViewStateModel = ReturnType<typeof stateModelFactory>
1297
1295
  export type MsaViewModel = Instance<MsaViewStateModel>