react-msaview 3.1.12 → 3.2.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 (186) hide show
  1. package/bundle/index.js +32 -31
  2. package/dist/colorSchemes.d.ts +2 -2
  3. package/dist/colorSchemes.js +3 -4
  4. package/dist/colorSchemes.js.map +1 -1
  5. package/dist/components/Loading.d.ts +1 -1
  6. package/dist/components/Loading.js +4 -4
  7. package/dist/components/Loading.js.map +1 -1
  8. package/dist/components/MSAView.d.ts +1 -1
  9. package/dist/components/MSAView.js +13 -9
  10. package/dist/components/MSAView.js.map +1 -1
  11. package/dist/components/ResizeHandles.d.ts +1 -1
  12. package/dist/components/ResizeHandles.js +2 -2
  13. package/dist/components/TextTrack.d.ts +1 -1
  14. package/dist/components/Track.d.ts +1 -1
  15. package/dist/components/VerticalScrollbar.d.ts +6 -0
  16. package/dist/components/VerticalScrollbar.js +65 -0
  17. package/dist/components/VerticalScrollbar.js.map +1 -0
  18. package/dist/components/dialogs/AddTrackDialog.d.ts +1 -1
  19. package/dist/components/dialogs/DomainDialog.d.ts +1 -1
  20. package/dist/components/dialogs/DomainDialog.js +2 -2
  21. package/dist/components/dialogs/DomainDialog.js.map +1 -1
  22. package/dist/components/dialogs/ExportSVGDialog.d.ts +1 -1
  23. package/dist/components/dialogs/FeatureDialog.d.ts +1 -1
  24. package/dist/components/dialogs/FeatureDialog.js.map +1 -1
  25. package/dist/components/dialogs/{InterProScanPanel.d.ts → InterProScanDialog.d.ts} +1 -1
  26. package/dist/components/dialogs/{InterProScanPanel.js → InterProScanDialog.js} +4 -3
  27. package/dist/components/dialogs/InterProScanDialog.js.map +1 -0
  28. package/dist/components/dialogs/MetadataDialog.d.ts +1 -1
  29. package/dist/components/dialogs/SettingsDialog.d.ts +1 -1
  30. package/dist/components/dialogs/SettingsDialog.js +10 -1
  31. package/dist/components/dialogs/SettingsDialog.js.map +1 -1
  32. package/dist/components/dialogs/TracklistDialog.d.ts +1 -1
  33. package/dist/components/dialogs/UserProvidedDomainsDialog.d.ts +7 -0
  34. package/dist/components/dialogs/UserProvidedDomainsDialog.js +58 -0
  35. package/dist/components/dialogs/UserProvidedDomainsDialog.js.map +1 -0
  36. package/dist/components/header/Header.d.ts +1 -1
  37. package/dist/components/header/Header.js +8 -3
  38. package/dist/components/header/Header.js.map +1 -1
  39. package/dist/components/header/HeaderInfoArea.d.ts +1 -1
  40. package/dist/components/header/HeaderMenu.d.ts +1 -1
  41. package/dist/components/header/HeaderMenuExtra.d.ts +1 -1
  42. package/dist/components/header/HeaderMenuExtra.js +30 -28
  43. package/dist/components/header/HeaderMenuExtra.js.map +1 -1
  44. package/dist/components/header/HeaderStatusArea.d.ts +2 -2
  45. package/dist/components/header/HeaderStatusArea.js +1 -1
  46. package/dist/components/header/HeaderStatusArea.js.map +1 -1
  47. package/dist/components/header/MultiAlignmentSelector.d.ts +1 -1
  48. package/dist/components/header/ZoomControls.js +31 -1
  49. package/dist/components/header/ZoomControls.js.map +1 -1
  50. package/dist/components/import/ImportForm.d.ts +1 -1
  51. package/dist/components/import/ImportForm.js +1 -1
  52. package/dist/components/import/ImportForm.js.map +1 -1
  53. package/dist/components/import/ImportFormExamples.d.ts +1 -1
  54. package/dist/components/import/ImportFormExamples.js +10 -8
  55. package/dist/components/import/ImportFormExamples.js.map +1 -1
  56. package/dist/components/import/util.d.ts +2 -2
  57. package/dist/components/minimap/Minimap.d.ts +1 -1
  58. package/dist/components/minimap/Minimap.js +14 -15
  59. package/dist/components/minimap/Minimap.js.map +1 -1
  60. package/dist/components/minimap/MinimapSVG.d.ts +1 -1
  61. package/dist/components/minimap/MinimapSVG.js +1 -1
  62. package/dist/components/minimap/MinimapSVG.js.map +1 -1
  63. package/dist/components/msa/MSACanvas.d.ts +1 -1
  64. package/dist/components/msa/MSACanvas.js +3 -3
  65. package/dist/components/msa/MSACanvas.js.map +1 -1
  66. package/dist/components/msa/MSACanvasBlock.d.ts +3 -3
  67. package/dist/components/msa/MSACanvasBlock.js +4 -3
  68. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  69. package/dist/components/msa/MSAMouseoverCanvas.d.ts +2 -2
  70. package/dist/components/msa/MSAMouseoverCanvas.js +1 -1
  71. package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
  72. package/dist/components/msa/MSAPanel.d.ts +1 -1
  73. package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +1 -1
  74. package/dist/components/msa/renderBoxFeatureCanvasBlock.js +1 -2
  75. package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
  76. package/dist/components/msa/renderMSABlock.d.ts +2 -2
  77. package/dist/components/msa/renderMSABlock.js +12 -12
  78. package/dist/components/msa/renderMSABlock.js.map +1 -1
  79. package/dist/components/msa/renderMSAMouseover.d.ts +1 -1
  80. package/dist/components/tree/TreeBranchMenu.d.ts +1 -1
  81. package/dist/components/tree/TreeCanvas.d.ts +1 -1
  82. package/dist/components/tree/TreeCanvas.js +13 -12
  83. package/dist/components/tree/TreeCanvas.js.map +1 -1
  84. package/dist/components/tree/TreeCanvasBlock.d.ts +1 -1
  85. package/dist/components/tree/TreeCanvasBlock.js +2 -1
  86. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  87. package/dist/components/tree/TreeNodeMenu.d.ts +1 -1
  88. package/dist/components/tree/TreeNodeMenu.js +2 -2
  89. package/dist/components/tree/TreeNodeMenu.js.map +1 -1
  90. package/dist/components/tree/TreePanel.d.ts +1 -1
  91. package/dist/components/tree/TreeRuler.d.ts +1 -1
  92. package/dist/components/tree/dialogs/TreeNodeInfoDialog.d.ts +1 -1
  93. package/dist/components/tree/renderTreeCanvas.d.ts +3 -3
  94. package/dist/components/tree/renderTreeCanvas.js +25 -9
  95. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  96. package/dist/components/util.js +1 -1
  97. package/dist/components/util.js.map +1 -1
  98. package/dist/launchInterProScan.d.ts +1 -1
  99. package/dist/launchInterProScan.js +7 -9
  100. package/dist/launchInterProScan.js.map +1 -1
  101. package/dist/model/DataModel.d.ts +5 -1
  102. package/dist/model/DataModel.js +10 -1
  103. package/dist/model/DataModel.js.map +1 -1
  104. package/dist/model/DialogQueue.d.ts +1 -1
  105. package/dist/model.d.ts +141 -24
  106. package/dist/model.js +236 -50
  107. package/dist/model.js.map +1 -1
  108. package/dist/parseNewick.js +1 -1
  109. package/dist/parseNewick.js.map +1 -1
  110. package/dist/parsers/ClustalMSA.d.ts +1 -1
  111. package/dist/parsers/FastaMSA.d.ts +1 -1
  112. package/dist/parsers/StockholmMSA.d.ts +1 -1
  113. package/dist/parsers/StockholmMSA.js.map +1 -1
  114. package/dist/renderToSvg.d.ts +2 -2
  115. package/dist/renderToSvg.js +3 -5
  116. package/dist/renderToSvg.js.map +1 -1
  117. package/dist/reparseTree.d.ts +1 -1
  118. package/dist/util.d.ts +2 -2
  119. package/dist/util.js +0 -2
  120. package/dist/util.js.map +1 -1
  121. package/dist/version.d.ts +1 -1
  122. package/dist/version.js +1 -1
  123. package/dist/version.js.map +1 -1
  124. package/package.json +5 -2
  125. package/src/colorSchemes.ts +3 -2
  126. package/src/components/Checkbox2.tsx +1 -1
  127. package/src/components/Loading.tsx +11 -5
  128. package/src/components/MSAView.tsx +27 -18
  129. package/src/components/ResizeHandles.tsx +3 -3
  130. package/src/components/TextTrack.tsx +1 -1
  131. package/src/components/Track.tsx +1 -1
  132. package/src/components/VerticalScrollbar.tsx +85 -0
  133. package/src/components/dialogs/AddTrackDialog.tsx +2 -2
  134. package/src/components/dialogs/DomainDialog.tsx +3 -3
  135. package/src/components/dialogs/ExportSVGDialog.tsx +1 -1
  136. package/src/components/dialogs/FeatureDialog.tsx +3 -3
  137. package/src/components/dialogs/{InterProScanPanel.tsx → InterProScanDialog.tsx} +11 -4
  138. package/src/components/dialogs/MetadataDialog.tsx +1 -1
  139. package/src/components/dialogs/SettingsDialog.tsx +38 -3
  140. package/src/components/dialogs/TracklistDialog.tsx +1 -1
  141. package/src/components/dialogs/UserProvidedDomainsDialog.tsx +133 -0
  142. package/src/components/header/Header.tsx +9 -4
  143. package/src/components/header/HeaderInfoArea.tsx +1 -1
  144. package/src/components/header/HeaderMenu.tsx +1 -1
  145. package/src/components/header/HeaderMenuExtra.tsx +36 -32
  146. package/src/components/header/HeaderStatusArea.tsx +2 -6
  147. package/src/components/header/MultiAlignmentSelector.tsx +1 -1
  148. package/src/components/header/ZoomControls.tsx +34 -0
  149. package/src/components/import/ImportForm.tsx +3 -3
  150. package/src/components/import/ImportFormExamples.tsx +19 -17
  151. package/src/components/import/util.ts +2 -2
  152. package/src/components/minimap/Minimap.tsx +15 -22
  153. package/src/components/minimap/MinimapSVG.tsx +2 -2
  154. package/src/components/msa/MSACanvas.tsx +11 -4
  155. package/src/components/msa/MSACanvasBlock.tsx +5 -4
  156. package/src/components/msa/MSAMouseoverCanvas.tsx +2 -6
  157. package/src/components/msa/MSAPanel.tsx +1 -1
  158. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +4 -5
  159. package/src/components/msa/renderMSABlock.ts +37 -17
  160. package/src/components/msa/renderMSAMouseover.ts +1 -1
  161. package/src/components/tree/TreeBranchMenu.tsx +1 -1
  162. package/src/components/tree/TreeCanvas.tsx +15 -16
  163. package/src/components/tree/TreeCanvasBlock.tsx +3 -2
  164. package/src/components/tree/TreeNodeMenu.tsx +3 -3
  165. package/src/components/tree/TreePanel.tsx +1 -1
  166. package/src/components/tree/TreeRuler.tsx +1 -1
  167. package/src/components/tree/dialogs/TreeNodeInfoDialog.tsx +1 -1
  168. package/src/components/tree/renderTreeCanvas.ts +32 -12
  169. package/src/components/util.ts +1 -1
  170. package/src/launchInterProScan.ts +8 -10
  171. package/src/model/DataModel.ts +10 -0
  172. package/src/model/DialogQueue.ts +1 -1
  173. package/src/model.ts +262 -62
  174. package/src/parseNewick.ts +1 -1
  175. package/src/parsers/ClustalMSA.ts +1 -1
  176. package/src/parsers/FastaMSA.ts +1 -1
  177. package/src/parsers/StockholmMSA.ts +1 -1
  178. package/src/renderToSvg.tsx +6 -6
  179. package/src/reparseTree.ts +1 -1
  180. package/src/util.ts +2 -4
  181. package/src/version.ts +1 -1
  182. package/dist/components/dialogs/InterProScanPanel.js.map +0 -1
  183. package/dist/components/dialogs/UserProvidedResultPanel.d.ts +0 -7
  184. package/dist/components/dialogs/UserProvidedResultPanel.js +0 -56
  185. package/dist/components/dialogs/UserProvidedResultPanel.js.map +0 -1
  186. package/src/components/dialogs/UserProvidedResultPanel.tsx +0 -119
package/src/model.ts CHANGED
@@ -1,23 +1,23 @@
1
1
  import React from 'react'
2
+ import type { Buffer } from 'buffer'
2
3
  import { autorun, transaction } from 'mobx'
3
- import { Instance, cast, types, addDisposer } from 'mobx-state-tree'
4
- import { hierarchy, cluster, HierarchyNode } from 'd3-hierarchy'
4
+ import { type Instance, cast, types, addDisposer } from 'mobx-state-tree'
5
+ import { hierarchy, cluster, type HierarchyNode } from 'd3-hierarchy'
5
6
  import { ascending } from 'd3-array'
6
7
  import Stockholm from 'stockholm-js'
7
8
  import { saveAs } from 'file-saver'
8
- import { Theme } from '@mui/material'
9
+ import type { Theme } from '@mui/material'
10
+ import { ungzip } from 'pako'
9
11
 
10
12
  // jbrowse
11
13
  import { FileLocation, ElementId } from '@jbrowse/core/util/types/mst'
12
- import { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
14
+ import type { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
13
15
  import { openLocation } from '@jbrowse/core/util/io'
14
- import {
15
- groupBy,
16
- localStorageGetItem,
17
- localStorageSetItem,
18
- notEmpty,
19
- sum,
20
- } from '@jbrowse/core/util'
16
+ import { groupBy, notEmpty, sum } from '@jbrowse/core/util'
17
+
18
+ export function isGzip(buf: Buffer) {
19
+ return buf[0] === 31 && buf[1] === 139 && buf[2] === 8
20
+ }
21
21
 
22
22
  // locals
23
23
  import {
@@ -27,8 +27,8 @@ import {
27
27
  maxLength,
28
28
  setBrLength,
29
29
  skipBlanks,
30
- NodeWithIds,
31
- NodeWithIdsAndLength,
30
+ type NodeWithIds,
31
+ type NodeWithIdsAndLength,
32
32
  len,
33
33
  } from './util'
34
34
  import { colord } from 'colord'
@@ -52,7 +52,7 @@ import { DataModelF } from './model/DataModel'
52
52
  import { DialogQueueSessionMixin } from './model/DialogQueue'
53
53
  import { TreeF } from './model/treeModel'
54
54
  import { MSAModelF } from './model/msaModel'
55
- import { InterProScanResults } from './launchInterProScan'
55
+ import type { InterProScanResults } from './launchInterProScan'
56
56
 
57
57
  export interface Accession {
58
58
  accession: string
@@ -98,10 +98,20 @@ function stateModelFactory() {
98
98
  * id of view, randomly generated if not provided
99
99
  */
100
100
  id: ElementId,
101
+
101
102
  /**
102
103
  * #property
103
104
  */
104
105
  showDomains: false,
106
+ /**
107
+ * #property
108
+ */
109
+ allowedGappyness: 100,
110
+ /**
111
+ * #property
112
+ */
113
+ contrastLettering: true,
114
+
105
115
  /**
106
116
  * #property
107
117
  */
@@ -113,6 +123,20 @@ function stateModelFactory() {
113
123
  */
114
124
  type: types.literal('MsaView'),
115
125
 
126
+ /**
127
+ * #property
128
+ */
129
+ drawMsaLetters: true,
130
+ /**
131
+ * #property
132
+ */
133
+ hideGaps: true,
134
+
135
+ /**
136
+ * #property
137
+ */
138
+ drawTreeText: true,
139
+
116
140
  /**
117
141
  * #property
118
142
  * height of the div containing the view, px
@@ -170,15 +194,17 @@ function stateModelFactory() {
170
194
 
171
195
  /**
172
196
  * #property
173
- * array of tree parent nodes that are 'collapsed'
197
+ * array of tree parent nodes that are 'collapsed' (all children are
198
+ * hidden)
174
199
  */
175
200
  collapsed: types.array(types.string),
176
201
 
177
202
  /**
178
203
  * #property
179
- * array of tree leaf nodes that are 'collapsed'
204
+ * array of tree leaf nodes that are 'collapsed' (just that leaf node
205
+ * is hidden)
180
206
  */
181
- collapsed2: types.array(types.string),
207
+ collapsedLeaves: types.array(types.string),
182
208
  /**
183
209
  * #property
184
210
  * focus on particular subtree
@@ -196,6 +222,7 @@ function stateModelFactory() {
196
222
  * autorun
197
223
  */
198
224
  data: types.optional(DataModelF(), { tree: '', msa: '' }),
225
+
199
226
  /**
200
227
  * #property
201
228
  */
@@ -203,6 +230,13 @@ function stateModelFactory() {
203
230
  }),
204
231
  )
205
232
  .volatile(() => ({
233
+ /**
234
+ * #volatile
235
+ */
236
+ headerHeight: 0,
237
+ /**
238
+ * #volatile
239
+ */
206
240
  status: undefined as { msg: string; url?: string } | undefined,
207
241
  /**
208
242
  * #volatile
@@ -221,7 +255,7 @@ function stateModelFactory() {
221
255
  /**
222
256
  * #volatile
223
257
  */
224
- width: 800,
258
+ volatileWidth: undefined as number | undefined,
225
259
  /**
226
260
  * #volatile
227
261
  * resize handle width between tree and msa area, px
@@ -232,7 +266,7 @@ function stateModelFactory() {
232
266
  * #volatile
233
267
  * size of blocks of content to be drawn, px
234
268
  */
235
- blockSize: 1000,
269
+ blockSize: 500,
236
270
 
237
271
  /**
238
272
  * #volatile
@@ -289,11 +323,29 @@ function stateModelFactory() {
289
323
  * #volatile
290
324
  *
291
325
  */
292
- loadedInterProAnnotations: undefined as
326
+ interProAnnotations: undefined as
293
327
  | undefined
294
328
  | Record<string, InterProScanResults>,
295
329
  }))
296
330
  .actions(self => ({
331
+ /**
332
+ * #action
333
+ */
334
+ setHideGaps(arg: boolean) {
335
+ self.hideGaps = arg
336
+ },
337
+ /**
338
+ * #action
339
+ */
340
+ setAllowedGappyness(arg: number) {
341
+ self.allowedGappyness = arg
342
+ },
343
+ /**
344
+ * #action
345
+ */
346
+ setContrastLettering(arg: boolean) {
347
+ self.contrastLettering = arg
348
+ },
297
349
  /**
298
350
  * #action
299
351
  */
@@ -310,7 +362,7 @@ function stateModelFactory() {
310
362
  * #action
311
363
  */
312
364
  setWidth(arg: number) {
313
- self.width = arg
365
+ self.volatileWidth = arg
314
366
  },
315
367
  /**
316
368
  * #action
@@ -405,10 +457,10 @@ function stateModelFactory() {
405
457
  * #action
406
458
  */
407
459
  toggleCollapsed2(node: string) {
408
- if (self.collapsed2.includes(node)) {
409
- self.collapsed2.remove(node)
460
+ if (self.collapsedLeaves.includes(node)) {
461
+ self.collapsedLeaves.remove(node)
410
462
  } else {
411
- self.collapsed2.push(node)
463
+ self.collapsedLeaves.push(node)
412
464
  }
413
465
  },
414
466
  /**
@@ -461,6 +513,29 @@ function stateModelFactory() {
461
513
  },
462
514
  }))
463
515
 
516
+ .views(self => ({
517
+ /**
518
+ * #getter
519
+ */
520
+ get actuallyShowDomains() {
521
+ return self.showDomains && !!self.interProAnnotations
522
+ },
523
+ /**
524
+ * #getter
525
+ */
526
+ get viewInitialized() {
527
+ return self.volatileWidth !== undefined
528
+ },
529
+ /**
530
+ * #getter
531
+ */
532
+ get width() {
533
+ if (self.volatileWidth === undefined) {
534
+ throw new Error('not initialized')
535
+ }
536
+ return self.volatileWidth
537
+ },
538
+ }))
464
539
  .views(self => ({
465
540
  /**
466
541
  * #method
@@ -502,13 +577,13 @@ function stateModelFactory() {
502
577
  * #getter
503
578
  */
504
579
  get noTree() {
505
- return !!this._tree.noTree
580
+ return !!this.tree.noTree
506
581
  },
507
582
  /**
508
583
  * #getter
509
584
  */
510
- get noAnnotations() {
511
- return !self.loadedInterProAnnotations
585
+ get noDomains() {
586
+ return !self.interProAnnotations
512
587
  },
513
588
  /**
514
589
  * #getter
@@ -530,11 +605,11 @@ function stateModelFactory() {
530
605
  if (text) {
531
606
  if (Stockholm.sniff(text)) {
532
607
  return new StockholmMSA(text, self.currentAlignment)
533
- } else if (text.startsWith('>')) {
608
+ }
609
+ if (text.startsWith('>')) {
534
610
  return new FastaMSA(text)
535
- } else {
536
- return new ClustalMSA(text)
537
611
  }
612
+ return new ClustalMSA(text)
538
613
  }
539
614
  return null
540
615
  },
@@ -548,7 +623,7 @@ function stateModelFactory() {
548
623
  /**
549
624
  * #getter
550
625
  */
551
- get _tree(): NodeWithIds {
626
+ get tree(): NodeWithIds {
552
627
  const ret = self.data.tree
553
628
  ? generateNodeIds(parseNewick(self.data.tree))
554
629
  : this.MSA?.getTree() || {
@@ -563,7 +638,7 @@ function stateModelFactory() {
563
638
  * #getter
564
639
  */
565
640
  get rowNames(): string[] {
566
- return this.hierarchy.leaves().map(n => n.data.name)
641
+ return this.leaves.map(n => n.data.name)
567
642
  },
568
643
  /**
569
644
  * #getter
@@ -577,7 +652,7 @@ function stateModelFactory() {
577
652
  * #getter
578
653
  */
579
654
  get root() {
580
- let hier = hierarchy(this._tree, d => d.branchset)
655
+ let hier = hierarchy(this.tree, d => d.branchset)
581
656
  .sum(d => (d.branchset ? 0 : 1))
582
657
  .sort((a, b) => ascending(a.data.length || 1, b.data.length || 1))
583
658
 
@@ -588,7 +663,7 @@ function stateModelFactory() {
588
663
  }
589
664
  }
590
665
 
591
- ;[...self.collapsed, ...self.collapsed2]
666
+ ;[...self.collapsed, ...self.collapsedLeaves]
592
667
  .map(collapsedId => hier.find(node => node.data.id === collapsedId))
593
668
  .filter(notEmpty)
594
669
  .map(node => collapse(node))
@@ -614,9 +689,9 @@ function stateModelFactory() {
614
689
  * #getter
615
690
  */
616
691
  get blanks() {
692
+ const { allowedGappyness } = self
617
693
  const blanks = []
618
- const strs = this.hierarchy
619
- .leaves()
694
+ const strs = this.leaves
620
695
  .map(leaf => this.MSA?.getRow(leaf.data.name))
621
696
  .filter((item): item is string => !!item)
622
697
 
@@ -627,7 +702,7 @@ function stateModelFactory() {
627
702
  counter++
628
703
  }
629
704
  }
630
- if (counter === strs.length) {
705
+ if (counter / strs.length >= allowedGappyness / 100) {
631
706
  blanks.push(i)
632
707
  }
633
708
  }
@@ -638,8 +713,7 @@ function stateModelFactory() {
638
713
  */
639
714
  get rows() {
640
715
  const MSA = this.MSA
641
- return this.hierarchy
642
- .leaves()
716
+ return this.leaves
643
717
  .map(leaf => [leaf.data.name, MSA?.getRow(leaf.data.name)] as const)
644
718
  .filter((f): f is [string, string] => !!f[1])
645
719
  },
@@ -683,6 +757,17 @@ function stateModelFactory() {
683
757
  }
684
758
  return r
685
759
  },
760
+
761
+ /**
762
+ * #getter
763
+ */
764
+ get colStatsSums() {
765
+ return Object.fromEntries(
766
+ Object.entries(this.colStats).map(([key, val]) => {
767
+ return [key, sum(Object.values(val))]
768
+ }),
769
+ )
770
+ },
686
771
  /**
687
772
  * #getter
688
773
  * generates a new tree that is clustered with x,y positions
@@ -703,6 +788,13 @@ function stateModelFactory() {
703
788
  get totalHeight() {
704
789
  return this.root.leaves().length * self.rowHeight
705
790
  },
791
+
792
+ /**
793
+ * #getter
794
+ */
795
+ get leaves() {
796
+ return this.hierarchy.leaves()
797
+ },
706
798
  }))
707
799
  .views(self => ({
708
800
  /**
@@ -717,7 +809,7 @@ function stateModelFactory() {
717
809
  /**
718
810
  * #getter
719
811
  */
720
- get initialized() {
812
+ get dataInitialized() {
721
813
  return (self.data.msa || self.data.tree) && !self.error
722
814
  },
723
815
  /**
@@ -769,29 +861,78 @@ function stateModelFactory() {
769
861
  get maxScrollX() {
770
862
  return -self.totalWidth + (self.msaAreaWidth - 100)
771
863
  },
864
+ /**
865
+ * #getter
866
+ */
867
+ get showMsaLetters() {
868
+ return self.drawMsaLetters && self.rowHeight >= 5
869
+ },
870
+ /**
871
+ * #getter
872
+ */
873
+ get showTreeText() {
874
+ return self.drawLabels && self.rowHeight >= 5
875
+ },
772
876
  }))
773
877
  .actions(self => ({
774
878
  /**
775
879
  * #action
776
880
  */
777
- zoomIn() {
881
+ setDrawMsaLetters(arg: boolean) {
882
+ self.drawMsaLetters = arg
883
+ },
884
+
885
+ /**
886
+ * #action
887
+ */
888
+ zoomOutHorizontal() {
889
+ self.colWidth = Math.max(1, Math.floor(self.colWidth * 0.75))
890
+ self.scrollX = clamp(self.maxScrollX, self.scrollX, 0)
891
+ },
892
+ /**
893
+ * #action
894
+ */
895
+ zoomInHorizontal() {
778
896
  self.colWidth = Math.ceil(self.colWidth * 1.5)
779
- self.rowHeight = Math.ceil(self.rowHeight * 1.5)
780
897
  self.scrollX = clamp(self.maxScrollX, self.scrollX, 0)
781
898
  },
782
899
  /**
783
900
  * #action
784
901
  */
785
- zoomOut() {
786
- self.colWidth = Math.max(1, Math.floor(self.colWidth * 0.75))
902
+ zoomInVertical() {
903
+ self.rowHeight = Math.ceil(self.rowHeight * 1.5)
904
+ },
905
+ /**
906
+ * #action
907
+ */
908
+ zoomOutVertical() {
787
909
  self.rowHeight = Math.max(1.5, Math.floor(self.rowHeight * 0.75))
788
- self.scrollX = clamp(self.maxScrollX, self.scrollX, 0)
789
910
  },
790
911
  /**
791
912
  * #action
792
913
  */
793
- setLoadedInterProAnnotations(data: Record<string, InterProScanResults>) {
794
- self.loadedInterProAnnotations = data
914
+ zoomIn() {
915
+ transaction(() => {
916
+ self.colWidth = Math.ceil(self.colWidth * 1.5)
917
+ self.rowHeight = Math.ceil(self.rowHeight * 1.5)
918
+ self.scrollX = clamp(self.maxScrollX, self.scrollX, 0)
919
+ })
920
+ },
921
+ /**
922
+ * #action
923
+ */
924
+ zoomOut() {
925
+ transaction(() => {
926
+ self.colWidth = Math.max(1, Math.floor(self.colWidth * 0.75))
927
+ self.rowHeight = Math.max(1.5, Math.floor(self.rowHeight * 0.75))
928
+ self.scrollX = clamp(self.maxScrollX, self.scrollX, 0)
929
+ })
930
+ },
931
+ /**
932
+ * #action
933
+ */
934
+ setInterProAnnotations(data: Record<string, InterProScanResults>) {
935
+ self.interProAnnotations = data
795
936
  },
796
937
 
797
938
  /**
@@ -838,9 +979,9 @@ function stateModelFactory() {
838
979
  */
839
980
  get labelsWidth() {
840
981
  let x = 0
841
- const { rowHeight, hierarchy, treeMetadata, fontSize } = self
982
+ const { rowHeight, leaves, treeMetadata, fontSize } = self
842
983
  if (rowHeight > 5) {
843
- for (const node of hierarchy.leaves()) {
984
+ for (const node of leaves) {
844
985
  x = Math.max(
845
986
  measureTextCanvas(
846
987
  treeMetadata[node.data.name]?.genome || node.data.name,
@@ -912,6 +1053,13 @@ function stateModelFactory() {
912
1053
  return this.tracks.filter(f => !self.turnedOffTracks.has(f.model.id))
913
1054
  },
914
1055
 
1056
+ /**
1057
+ * #getter
1058
+ */
1059
+ get showHorizontalScrollbar() {
1060
+ return self.msaAreaWidth < self.totalWidth
1061
+ },
1062
+
915
1063
  /**
916
1064
  * #method
917
1065
  * return a row-specific sequence coordinate, skipping gaps, given a global
@@ -963,6 +1111,17 @@ function stateModelFactory() {
963
1111
  }))
964
1112
 
965
1113
  .views(self => ({
1114
+ /**
1115
+ * #getter
1116
+ * widget width minus the tree area gives the space for the MSA
1117
+ */
1118
+ get msaAreaHeight() {
1119
+ return (
1120
+ self.height -
1121
+ (self.showHorizontalScrollbar ? self.minimapHeight : 0) -
1122
+ self.headerHeight
1123
+ )
1124
+ },
966
1125
  /**
967
1126
  * #getter
968
1127
  * total height of track area (px)
@@ -982,11 +1141,14 @@ function stateModelFactory() {
982
1141
  }
983
1142
  return types
984
1143
  },
1144
+ /**
1145
+ * #getter
1146
+ */
985
1147
  get tidyAnnotations() {
986
1148
  const ret = []
987
- const { loadedInterProAnnotations } = self
988
- if (loadedInterProAnnotations) {
989
- for (const [id, val] of Object.entries(loadedInterProAnnotations)) {
1149
+ const { interProAnnotations } = self
1150
+ if (interProAnnotations) {
1151
+ for (const [id, val] of Object.entries(interProAnnotations)) {
990
1152
  for (const { signature, locations } of val.matches) {
991
1153
  const { entry } = signature
992
1154
  if (entry) {
@@ -1023,6 +1185,23 @@ function stateModelFactory() {
1023
1185
  },
1024
1186
  }))
1025
1187
  .views(self => ({
1188
+ /**
1189
+ * #getter
1190
+ */
1191
+ get showVerticalScrollbar() {
1192
+ return self.msaAreaHeight < self.totalHeight
1193
+ },
1194
+ }))
1195
+ .views(self => ({
1196
+ /**
1197
+ * #getter
1198
+ */
1199
+ get verticalScrollbarWidth() {
1200
+ return self.showVerticalScrollbar ? 20 : 0
1201
+ },
1202
+ /**
1203
+ * #getter
1204
+ */
1026
1205
  get fillPalette() {
1027
1206
  const arr = [...self.tidyTypes.keys()]
1028
1207
  let i = 0
@@ -1034,6 +1213,9 @@ function stateModelFactory() {
1034
1213
  }
1035
1214
  return map
1036
1215
  },
1216
+ /**
1217
+ * #getter
1218
+ */
1037
1219
  get strokePalette() {
1038
1220
  return Object.fromEntries(
1039
1221
  Object.entries(this.fillPalette).map(([key, val]) => [
@@ -1044,18 +1226,23 @@ function stateModelFactory() {
1044
1226
  },
1045
1227
  }))
1046
1228
  .actions(self => ({
1229
+ /**
1230
+ * #action
1231
+ */
1232
+ setHeaderHeight(arg: number) {
1233
+ self.headerHeight = arg
1234
+ },
1047
1235
  /**
1048
1236
  * #action
1049
1237
  */
1050
1238
  reset() {
1051
- transaction(() => {
1052
- self.setData({ tree: '', msa: '' })
1053
- self.setScrollY(0)
1054
- self.setScrollX(0)
1055
- self.setCurrentAlignment(0)
1056
- self.setTreeFilehandle(undefined)
1057
- self.setMSAFilehandle(undefined)
1058
- })
1239
+ self.setData({ tree: '', msa: '' })
1240
+ self.setError(undefined)
1241
+ self.setScrollY(0)
1242
+ self.setScrollX(0)
1243
+ self.setCurrentAlignment(0)
1244
+ self.setTreeFilehandle(undefined)
1245
+ self.setMSAFilehandle(undefined)
1059
1246
  },
1060
1247
  /**
1061
1248
  * #action
@@ -1154,9 +1341,11 @@ function stateModelFactory() {
1154
1341
  if (msaFilehandle) {
1155
1342
  try {
1156
1343
  self.setLoadingMSA(true)
1157
- const res = await openLocation(msaFilehandle).readFile('utf8')
1344
+ const res = await openLocation(msaFilehandle).readFile()
1345
+ const buf = isGzip(res) ? ungzip(res) : res
1346
+ const txt = new TextDecoder('utf8').decode(buf)
1158
1347
  transaction(() => {
1159
- self.setMSA(res)
1348
+ self.setMSA(txt)
1160
1349
  if (msaFilehandle.locationType === 'BlobLocation') {
1161
1350
  // clear filehandle after loading if from a local file
1162
1351
  self.setMSAFilehandle(undefined)
@@ -1172,6 +1361,17 @@ function stateModelFactory() {
1172
1361
  }),
1173
1362
  )
1174
1363
 
1364
+ addDisposer(
1365
+ self,
1366
+ autorun(() => {
1367
+ // force colStats not to go stale,
1368
+ // xref solution https://github.com/mobxjs/mobx/issues/266#issuecomment-222007278
1369
+ // xref problem https://github.com/GMOD/react-msaview/issues/75
1370
+ self.colStats
1371
+ self.colStatsSums
1372
+ self.columns
1373
+ }),
1374
+ )
1175
1375
  // autorun synchronizes treeWidth with treeAreaWidth
1176
1376
  addDisposer(
1177
1377
  self,
@@ -85,7 +85,7 @@ export default function parse(s: string) {
85
85
  if (x === ')' || x === '(' || x === ',') {
86
86
  tree.name = token
87
87
  } else if (x === ':') {
88
- tree.length = parseFloat(token)
88
+ tree.length = Number.parseFloat(token)
89
89
  }
90
90
  }
91
91
  }
@@ -1,5 +1,5 @@
1
1
  import { parse } from 'clustal-js'
2
- import { NodeWithIds } from '../util'
2
+ import type { NodeWithIds } from '../util'
3
3
  export default class ClustalMSA {
4
4
  private MSA: ReturnType<typeof parse>
5
5
 
@@ -1,4 +1,4 @@
1
- import { NodeWithIds } from '../util'
1
+ import type { NodeWithIds } from '../util'
2
2
 
3
3
  function parseSmallFasta(text: string) {
4
4
  return text
@@ -1,7 +1,7 @@
1
1
  import Stockholm from 'stockholm-js'
2
2
  import parseNewick from '../parseNewick'
3
3
 
4
- import { NodeWithIds, generateNodeIds } from '../util'
4
+ import { type NodeWithIds, generateNodeIds } from '../util'
5
5
  interface StockholmEntry {
6
6
  gf: {
7
7
  DE?: string[]
@@ -2,10 +2,10 @@ import React from 'react'
2
2
  import { createRoot } from 'react-dom/client'
3
3
  import { when } from 'mobx'
4
4
  import { renderToStaticMarkup } from '@jbrowse/core/util'
5
- import { Theme } from '@mui/material'
5
+ import type { Theme } from '@mui/material'
6
6
 
7
7
  // locals
8
- import { MsaViewModel } from './model'
8
+ import type { MsaViewModel } from './model'
9
9
  import { renderTreeCanvas } from './components/tree/renderTreeCanvas'
10
10
  import { renderMSABlock } from './components/msa/renderMSABlock'
11
11
  import { colorContrast } from './util'
@@ -17,7 +17,7 @@ export async function renderToSvg(
17
17
  model: MsaViewModel,
18
18
  opts: { theme: Theme; includeMinimap?: boolean; exportType: string },
19
19
  ) {
20
- await when(() => !!model.initialized)
20
+ await when(() => !!model.dataInitialized)
21
21
  const { width, height, scrollX, scrollY } = model
22
22
  const { exportType, theme, includeMinimap } = opts
23
23
 
@@ -31,7 +31,8 @@ export async function renderToSvg(
31
31
  offsetX: 0,
32
32
  includeMinimap,
33
33
  })
34
- } else if (exportType === 'viewport') {
34
+ }
35
+ if (exportType === 'viewport') {
35
36
  return render({
36
37
  width,
37
38
  height,
@@ -41,9 +42,8 @@ export async function renderToSvg(
41
42
  offsetX: -scrollX,
42
43
  includeMinimap,
43
44
  })
44
- } else {
45
- throw new Error('unknown export type')
46
45
  }
46
+ throw new Error('unknown export type')
47
47
  }
48
48
 
49
49
  async function render({
@@ -1,4 +1,4 @@
1
- import { NodeWithIds } from './util'
1
+ import type { NodeWithIds } from './util'
2
2
 
3
3
  export function reparseTree(tree: NodeWithIds): NodeWithIds {
4
4
  return {