react-msaview 5.0.7 → 5.0.16

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 (174) hide show
  1. package/bundle/index.js +135 -35
  2. package/bundle/index.js.LICENSE.txt +1 -1
  3. package/bundle/index.js.map +1 -1
  4. package/dist/components/Checkbox2.js +3 -6
  5. package/dist/components/Checkbox2.js.map +1 -1
  6. package/dist/components/MSAViewer.d.ts +14 -0
  7. package/dist/components/MSAViewer.js +34 -0
  8. package/dist/components/MSAViewer.js.map +1 -0
  9. package/dist/components/SequenceTextArea.js +4 -4
  10. package/dist/components/SequenceTextArea.js.map +1 -1
  11. package/dist/components/Track.js +5 -24
  12. package/dist/components/Track.js.map +1 -1
  13. package/dist/components/dialogs/DomainDialog.js +2 -5
  14. package/dist/components/dialogs/DomainDialog.js.map +1 -1
  15. package/dist/components/dialogs/InterProScanDialog.js +7 -7
  16. package/dist/components/dialogs/InterProScanDialog.js.map +1 -1
  17. package/dist/components/dialogs/SettingsDialog.js +3 -19
  18. package/dist/components/dialogs/SettingsDialog.js.map +1 -1
  19. package/dist/components/header/ColorSchemeMenu.d.ts +6 -0
  20. package/dist/components/header/ColorSchemeMenu.js +19 -0
  21. package/dist/components/header/ColorSchemeMenu.js.map +1 -0
  22. package/dist/components/header/{ZoomStar.d.ts → FileMenu.d.ts} +2 -2
  23. package/dist/components/header/FileMenu.js +71 -0
  24. package/dist/components/header/FileMenu.js.map +1 -0
  25. package/dist/components/header/Header.js +8 -6
  26. package/dist/components/header/Header.js.map +1 -1
  27. package/dist/components/header/HeaderMenu.js +3 -145
  28. package/dist/components/header/HeaderMenu.js.map +1 -1
  29. package/dist/components/header/MSASettingsMenu.d.ts +6 -0
  30. package/dist/components/header/MSASettingsMenu.js +36 -0
  31. package/dist/components/header/MSASettingsMenu.js.map +1 -0
  32. package/dist/components/header/SettingsMenu.js +1 -21
  33. package/dist/components/header/SettingsMenu.js.map +1 -1
  34. package/dist/components/header/TreeSettingsMenu.d.ts +6 -0
  35. package/dist/components/header/TreeSettingsMenu.js +74 -0
  36. package/dist/components/header/TreeSettingsMenu.js.map +1 -0
  37. package/dist/components/header/ZoomMenu.js +0 -8
  38. package/dist/components/header/ZoomMenu.js.map +1 -1
  39. package/dist/components/header/getDomainsMenu.d.ts +31 -0
  40. package/dist/components/header/getDomainsMenu.js +75 -0
  41. package/dist/components/header/getDomainsMenu.js.map +1 -0
  42. package/dist/components/import/ImportFormExamples.js +22 -20
  43. package/dist/components/import/ImportFormExamples.js.map +1 -1
  44. package/dist/components/msa/MSACanvas.js +13 -84
  45. package/dist/components/msa/MSACanvas.js.map +1 -1
  46. package/dist/components/msa/MSACanvasBlock.js +1 -3
  47. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  48. package/dist/components/msa/renderMSABlock.js +2 -4
  49. package/dist/components/msa/renderMSABlock.js.map +1 -1
  50. package/dist/components/msa/renderMSAMouseover.js +1 -7
  51. package/dist/components/msa/renderMSAMouseover.js.map +1 -1
  52. package/dist/components/tree/TreeCanvas.js +14 -91
  53. package/dist/components/tree/TreeCanvas.js.map +1 -1
  54. package/dist/components/tree/TreeNodeMenu.js +5 -16
  55. package/dist/components/tree/TreeNodeMenu.js.map +1 -1
  56. package/dist/components/tree/renderTreeCanvas.js +55 -22
  57. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  58. package/dist/constants.d.ts +0 -2
  59. package/dist/constants.js +0 -2
  60. package/dist/constants.js.map +1 -1
  61. package/dist/fetchUtils.d.ts +0 -1
  62. package/dist/fetchUtils.js +0 -4
  63. package/dist/fetchUtils.js.map +1 -1
  64. package/dist/flatToTree.d.ts +0 -5
  65. package/dist/flatToTree.js +13 -30
  66. package/dist/flatToTree.js.map +1 -1
  67. package/dist/hierarchy.d.ts +29 -0
  68. package/dist/hierarchy.js +164 -0
  69. package/dist/hierarchy.js.map +1 -0
  70. package/dist/index.d.ts +2 -0
  71. package/dist/index.js +1 -0
  72. package/dist/index.js.map +1 -1
  73. package/dist/launchInterProScan.d.ts +0 -5
  74. package/dist/launchInterProScan.js +5 -3
  75. package/dist/launchInterProScan.js.map +1 -1
  76. package/dist/model/DataModel.d.ts +9 -0
  77. package/dist/model/DataModel.js +12 -1
  78. package/dist/model/DataModel.js.map +1 -1
  79. package/dist/model/msaModel.d.ts +3 -0
  80. package/dist/model/msaModel.js +0 -1
  81. package/dist/model/msaModel.js.map +1 -1
  82. package/dist/model/treeModel.d.ts +3 -6
  83. package/dist/model/treeModel.js +3 -15
  84. package/dist/model/treeModel.js.map +1 -1
  85. package/dist/model.d.ts +24 -77
  86. package/dist/model.js +118 -239
  87. package/dist/model.js.map +1 -1
  88. package/dist/neighborJoining.js +38 -629
  89. package/dist/neighborJoining.js.map +1 -1
  90. package/dist/parseAsn1.d.ts +0 -12
  91. package/dist/parseAsn1.js +125 -332
  92. package/dist/parseAsn1.js.map +1 -1
  93. package/dist/useWheelScroll.d.ts +8 -0
  94. package/dist/useWheelScroll.js +93 -0
  95. package/dist/useWheelScroll.js.map +1 -0
  96. package/dist/util.d.ts +1 -6
  97. package/dist/util.js +5 -34
  98. package/dist/util.js.map +1 -1
  99. package/dist/vendor/copyToClipboard.d.ts +1 -10
  100. package/dist/vendor/copyToClipboard.js +14 -109
  101. package/dist/vendor/copyToClipboard.js.map +1 -1
  102. package/dist/vendor/fileSaver.d.ts +1 -11
  103. package/dist/vendor/fileSaver.js +7 -76
  104. package/dist/vendor/fileSaver.js.map +1 -1
  105. package/dist/version.d.ts +1 -1
  106. package/dist/version.js +1 -1
  107. package/dist/version.js.map +1 -1
  108. package/package.json +10 -13
  109. package/src/collapseLogic.test.ts +115 -0
  110. package/src/components/Checkbox2.tsx +9 -18
  111. package/src/components/MSAViewer.tsx +67 -0
  112. package/src/components/SequenceTextArea.tsx +4 -4
  113. package/src/components/Track.tsx +10 -26
  114. package/src/components/dialogs/DomainDialog.tsx +4 -5
  115. package/src/components/dialogs/InterProScanDialog.tsx +7 -7
  116. package/src/components/dialogs/SettingsDialog.tsx +0 -37
  117. package/src/components/header/ColorSchemeMenu.tsx +35 -0
  118. package/src/components/header/FileMenu.tsx +84 -0
  119. package/src/components/header/Header.tsx +8 -6
  120. package/src/components/header/HeaderMenu.tsx +4 -155
  121. package/src/components/header/MSASettingsMenu.tsx +48 -0
  122. package/src/components/header/SettingsMenu.tsx +0 -23
  123. package/src/components/header/TreeSettingsMenu.tsx +96 -0
  124. package/src/components/header/ZoomMenu.tsx +0 -8
  125. package/src/components/header/getDomainsMenu.ts +83 -0
  126. package/src/components/import/ImportFormExamples.tsx +38 -35
  127. package/src/components/msa/MSACanvas.tsx +21 -91
  128. package/src/components/msa/MSACanvasBlock.tsx +1 -3
  129. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +1 -1
  130. package/src/components/msa/renderMSABlock.ts +2 -5
  131. package/src/components/msa/renderMSAMouseover.ts +0 -6
  132. package/src/components/tree/TreeCanvas.tsx +35 -100
  133. package/src/components/tree/TreeNodeMenu.tsx +5 -14
  134. package/src/components/tree/renderTreeCanvas.test.ts +205 -0
  135. package/src/components/tree/renderTreeCanvas.ts +64 -27
  136. package/src/constants.ts +0 -2
  137. package/src/fetchUtils.ts +0 -5
  138. package/src/flatToTree.ts +20 -38
  139. package/src/hierarchy.test.ts +120 -0
  140. package/src/hierarchy.ts +221 -0
  141. package/src/index.ts +2 -0
  142. package/src/launchInterProScan.ts +4 -3
  143. package/src/model/DataModel.ts +12 -1
  144. package/src/model/msaModel.ts +0 -2
  145. package/src/model/treeModel.ts +2 -18
  146. package/src/model.ts +180 -278
  147. package/src/neighborJoining.ts +38 -628
  148. package/src/parseAsn1.test.ts +4 -1
  149. package/src/parseAsn1.ts +135 -405
  150. package/src/useWheelScroll.ts +109 -0
  151. package/src/util.ts +5 -50
  152. package/src/vendor/copyToClipboard.ts +14 -122
  153. package/src/vendor/fileSaver.ts +8 -105
  154. package/src/version.ts +1 -1
  155. package/dist/components/dialogs/AddTrackDialog.d.ts +0 -8
  156. package/dist/components/dialogs/AddTrackDialog.js +0 -30
  157. package/dist/components/dialogs/AddTrackDialog.js.map +0 -1
  158. package/dist/components/dialogs/TabPanel.d.ts +0 -6
  159. package/dist/components/dialogs/TabPanel.js +0 -6
  160. package/dist/components/dialogs/TabPanel.js.map +0 -1
  161. package/dist/components/header/ZoomStar.js +0 -40
  162. package/dist/components/header/ZoomStar.js.map +0 -1
  163. package/dist/layout.d.ts +0 -26
  164. package/dist/layout.js +0 -74
  165. package/dist/layout.js.map +0 -1
  166. package/dist/reparseTree.d.ts +0 -2
  167. package/dist/reparseTree.js +0 -15
  168. package/dist/reparseTree.js.map +0 -1
  169. package/src/components/dialogs/AddTrackDialog.tsx +0 -85
  170. package/src/components/dialogs/TabPanel.tsx +0 -19
  171. package/src/components/header/ZoomStar.tsx +0 -74
  172. package/src/createPaletteMap.test.ts +0 -57
  173. package/src/layout.ts +0 -118
  174. package/src/reparseTree.ts +0 -18
package/src/model.ts CHANGED
@@ -2,8 +2,6 @@ import {
2
2
  clamp,
3
3
  fetchAndMaybeUnzipText,
4
4
  groupBy,
5
- localStorageGetBoolean,
6
- localStorageSetBoolean,
7
5
  notEmpty,
8
6
  sum,
9
7
  } from '@jbrowse/core/util'
@@ -11,21 +9,14 @@ import { openLocation } from '@jbrowse/core/util/io'
11
9
  import { ElementId, FileLocation } from '@jbrowse/core/util/types/mst'
12
10
  import { addDisposer, cast, types } from '@jbrowse/mobx-state-tree'
13
11
  import { colord } from 'colord'
14
- import { ascending } from 'd3-array'
15
- import { cluster, hierarchy } from 'd3-hierarchy'
16
12
  import { autorun, transaction } from 'mobx'
17
13
  import {
18
- A3mMSA,
19
- ClustalMSA,
20
- EmfMSA,
21
- FastaMSA,
22
- StockholmMSA,
23
14
  generateNodeIds,
24
15
  gffToInterProResults,
25
16
  parseEmfTree,
26
17
  parseGFF,
18
+ parseMSA,
27
19
  parseNewick,
28
- stockholmSniff,
29
20
  } from 'msa-parsers'
30
21
 
31
22
  import { blocksX, blocksY } from './calculateBlocks.ts'
@@ -37,7 +28,6 @@ import {
37
28
  defaultBgColor,
38
29
  defaultColWidth,
39
30
  defaultColorSchemeName,
40
- defaultContrastLettering,
41
31
  defaultCurrentAlignment,
42
32
  defaultDrawLabels,
43
33
  defaultDrawMsaLetters,
@@ -54,10 +44,21 @@ import {
54
44
  defaultSubFeatureRows,
55
45
  defaultTreeAreaWidth,
56
46
  defaultTreeWidth,
57
- defaultTreeWidthMatchesArea,
58
47
  } from './constants.ts'
59
48
  import { createPaletteMap } from './createPaletteMap.ts'
60
49
  import { flatToTree } from './flatToTree.ts'
50
+ import {
51
+ clusterLayout,
52
+ collapse,
53
+ find,
54
+ hierarchy,
55
+ leaves,
56
+ links,
57
+ maxLength,
58
+ setBrLength,
59
+ sort,
60
+ sum as hierarchySum,
61
+ } from './hierarchy.ts'
61
62
  import { measureTextCanvas } from './measureTextCanvas.ts'
62
63
  import { DataModelF } from './model/DataModel.ts'
63
64
  import { DialogQueueSessionMixin } from './model/DialogQueue.ts'
@@ -65,19 +66,18 @@ import { MSAModelF } from './model/msaModel.ts'
65
66
  import { TreeModelF } from './model/treeModel.ts'
66
67
  import { calculateNeighborJoiningTree } from './neighborJoining.ts'
67
68
  import { parseAsn1 } from './parseAsn1.ts'
68
- import { reparseTree } from './reparseTree.ts'
69
69
  import {
70
70
  globalColToVisibleCol,
71
71
  visibleColToGlobalCol,
72
72
  visibleColToSeqPosForRow,
73
73
  } from './rowCoordinateCalculations.ts'
74
74
  import { seqPosToGlobalCol } from './seqPosToGlobalCol.ts'
75
- import { collapse, len, maxLength, setBrLength, skipBlanks } from './util.ts'
75
+ import { len, skipBlanks } from './util.ts'
76
76
  import { saveAs } from './vendor/fileSaver.ts'
77
77
 
78
+ import type { HierarchyNode } from './hierarchy.ts'
78
79
  import type { InterProScanResults } from './launchInterProScan.ts'
79
80
  import type {
80
- Accession,
81
81
  BasicTrack,
82
82
  NodeWithIds,
83
83
  NodeWithIdsAndLength,
@@ -86,9 +86,6 @@ import type {
86
86
  import type { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
87
87
  import type { Instance } from '@jbrowse/mobx-state-tree'
88
88
  import type { Theme } from '@mui/material'
89
- import type { HierarchyNode } from 'd3-hierarchy'
90
-
91
- const showZoomStarKey = 'msa-showZoomStar'
92
89
 
93
90
  /**
94
91
  * #stateModel MsaView
@@ -122,11 +119,6 @@ function stateModelFactory() {
122
119
  * #property
123
120
  */
124
121
  allowedGappyness: defaultAllowedGappyness,
125
- /**
126
- * #property
127
- */
128
- contrastLettering: defaultContrastLettering,
129
-
130
122
  /**
131
123
  * #property
132
124
  */
@@ -200,7 +192,6 @@ function stateModelFactory() {
200
192
 
201
193
  /**
202
194
  * #property
203
- *
204
195
  */
205
196
  currentAlignment: defaultCurrentAlignment,
206
197
 
@@ -211,12 +202,6 @@ function stateModelFactory() {
211
202
  */
212
203
  collapsed: types.array(types.string),
213
204
 
214
- /**
215
- * #property
216
- * array of tree leaf nodes that are 'collapsed' (just that leaf node
217
- * is hidden)
218
- */
219
- collapsedLeaves: types.array(types.string),
220
205
  /**
221
206
  * #property
222
207
  * focus on particular subtree
@@ -265,11 +250,6 @@ function stateModelFactory() {
265
250
  */
266
251
  highResScaleFactor: 2,
267
252
 
268
- /**
269
- * #volatile
270
- * obtained from localStorage
271
- */
272
- showZoomStar: localStorageGetBoolean(showZoomStarKey, false),
273
253
  /**
274
254
  * #volatile
275
255
  */
@@ -357,13 +337,9 @@ function stateModelFactory() {
357
337
  /**
358
338
  * #volatile
359
339
  */
340
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
360
341
  error: undefined as unknown,
361
342
 
362
- /**
363
- * #volatile
364
- */
365
- annotPos: undefined as { left: number; right: number } | undefined,
366
-
367
343
  /**
368
344
  * #volatile
369
345
  */
@@ -390,24 +366,12 @@ function stateModelFactory() {
390
366
  setAllowedGappyness(arg: number) {
391
367
  self.allowedGappyness = arg
392
368
  },
393
- /**
394
- * #action
395
- */
396
- setContrastLettering(arg: boolean) {
397
- self.contrastLettering = arg
398
- },
399
369
  /**
400
370
  * #action
401
371
  */
402
372
  setLoadingMSA(arg: boolean) {
403
373
  self.loadingMSA = arg
404
374
  },
405
- /**
406
- * #action
407
- */
408
- setShowZoomStar(arg: boolean) {
409
- self.showZoomStar = arg
410
- },
411
375
  /**
412
376
  * #action
413
377
  */
@@ -455,8 +419,8 @@ function stateModelFactory() {
455
419
  return
456
420
  }
457
421
 
458
- // Find the node in the hierarchy
459
- const node = (self as MsaViewModel).hierarchy.find(
422
+ const node = find(
423
+ (self as MsaViewModel).hierarchy,
460
424
  n => n.data.id === nodeId,
461
425
  )
462
426
  if (!node) {
@@ -464,8 +428,7 @@ function stateModelFactory() {
464
428
  return
465
429
  }
466
430
 
467
- // Get all descendant leaf names
468
- const descendantNames = node.leaves().map(leaf => leaf.data.name)
431
+ const descendantNames = leaves(node).map(leaf => leaf.data.name)
469
432
 
470
433
  self.hoveredTreeNode = { nodeId, descendantNames }
471
434
  },
@@ -482,7 +445,6 @@ function stateModelFactory() {
482
445
  setShowDomains(arg: boolean) {
483
446
  self.showDomains = arg
484
447
  },
485
-
486
448
  /**
487
449
  * #action
488
450
  */
@@ -541,16 +503,6 @@ function stateModelFactory() {
541
503
  }
542
504
  },
543
505
 
544
- /**
545
- * #action
546
- */
547
- toggleCollapsedLeaf(node: string) {
548
- if (self.collapsedLeaves.includes(node)) {
549
- self.collapsedLeaves.remove(node)
550
- } else {
551
- self.collapsedLeaves.push(node)
552
- }
553
- },
554
506
  /**
555
507
  * #action
556
508
  */
@@ -561,7 +513,12 @@ function stateModelFactory() {
561
513
  /**
562
514
  * #action
563
515
  */
564
- setData(data: { msa?: string; tree?: string; treeMetadata?: string }) {
516
+ setData(data: {
517
+ msa?: string
518
+ tree?: string
519
+ treeMetadata?: string
520
+ gff?: string
521
+ }) {
565
522
  self.data = cast(data)
566
523
  },
567
524
 
@@ -616,9 +573,7 @@ function stateModelFactory() {
616
573
  get hideGapsEffective() {
617
574
  return (
618
575
  self.hideGaps &&
619
- (self.collapsed.length > 0 ||
620
- self.collapsedLeaves.length > 0 ||
621
- self.allowedGappyness < 100)
576
+ (self.collapsed.length > 0 || self.allowedGappyness < 100)
622
577
  )
623
578
  },
624
579
  /**
@@ -633,9 +588,6 @@ function stateModelFactory() {
633
588
  get actuallyShowDomains() {
634
589
  return self.showDomains && !!self.interProAnnotations
635
590
  },
636
- /**
637
- * #getter
638
- */
639
591
  get viewInitialized() {
640
592
  return self.volatileWidth !== undefined
641
593
  },
@@ -668,7 +620,7 @@ function stateModelFactory() {
668
620
  * #getter
669
621
  */
670
622
  get header() {
671
- return this.MSA?.getHeader() || {}
623
+ return (this.MSA?.getHeader() || {}) as Record<string, unknown>
672
624
  },
673
625
 
674
626
  /**
@@ -683,15 +635,9 @@ function stateModelFactory() {
683
635
  get noTree() {
684
636
  return !!this.tree.noTree
685
637
  },
686
- /**
687
- * #getter
688
- */
689
638
  get noDomains() {
690
639
  return !self.interProAnnotations
691
640
  },
692
- /**
693
- * #getter
694
- */
695
641
  menuItems() {
696
642
  return []
697
643
  },
@@ -706,20 +652,9 @@ function stateModelFactory() {
706
652
  */
707
653
  get MSA() {
708
654
  const text = self.data.msa
709
- if (text) {
710
- if (stockholmSniff(text)) {
711
- return new StockholmMSA(text, self.currentAlignment)
712
- } else if (A3mMSA.sniff(text)) {
713
- return new A3mMSA(text)
714
- } else if (text.startsWith('>')) {
715
- return new FastaMSA(text)
716
- } else if (text.startsWith('SEQ')) {
717
- return new EmfMSA(text)
718
- } else {
719
- return new ClustalMSA(text)
720
- }
721
- }
722
- return null
655
+ // uses parseMSA so the named MSAParserType return type is portable
656
+ // to downstream consumers (avoids TS2883 with default exports)
657
+ return text ? parseMSA(text, self.currentAlignment) : null
723
658
  },
724
659
  /**
725
660
  * #getter
@@ -734,22 +669,20 @@ function stateModelFactory() {
734
669
  get tree(): NodeWithIds {
735
670
  const text = self.data.tree
736
671
 
737
- return reparseTree(
738
- text
739
- ? generateNodeIds(
740
- text.startsWith('BioTreeContainer')
741
- ? flatToTree(parseAsn1(text))
742
- : parseNewick(
743
- text.startsWith('SEQ') ? parseEmfTree(text).tree : text,
744
- ),
745
- )
746
- : this.MSA?.getTree() || {
747
- noTree: true,
748
- children: [],
749
- id: 'empty',
750
- name: 'empty',
751
- },
752
- )
672
+ return text
673
+ ? generateNodeIds(
674
+ text.startsWith('BioTreeContainer')
675
+ ? flatToTree(parseAsn1(text))
676
+ : parseNewick(
677
+ text.startsWith('SEQ') ? parseEmfTree(text).tree : text,
678
+ ),
679
+ )
680
+ : this.MSA?.getTree() || {
681
+ noTree: true,
682
+ children: [],
683
+ id: 'empty',
684
+ name: 'empty',
685
+ }
753
686
  },
754
687
 
755
688
  /**
@@ -798,25 +731,29 @@ function stateModelFactory() {
798
731
  */
799
732
  get root() {
800
733
  let hier = hierarchy(this.tree, d => d.children)
801
- // todo: investigate whether needed, typescript says children always true
802
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
803
- .sum(d => (d.children ? 0 : 1))
804
- // eslint-disable-next-line unicorn/no-array-sort
805
- .sort((a, b) => ascending(a.data.length || 1, b.data.length || 1))
734
+ hierarchySum(hier, d => (d.children.length > 0 ? 0 : 1))
735
+ sort(hier, (a, b) => (a.data.length ?? 1) - (b.data.length ?? 1))
806
736
 
807
737
  if (self.showOnly) {
808
- const res = hier.find(n => n.data.id === self.showOnly)
738
+ const res = find(hier, n => n.data.id === self.showOnly)
809
739
  if (res) {
810
740
  hier = res
811
741
  }
812
742
  }
813
743
 
814
- ;[...self.collapsed, ...self.collapsedLeaves]
815
- .map(collapsedId => hier.find(node => node.data.id === collapsedId))
816
- .filter(notEmpty)
817
- .forEach(node => {
744
+ for (const collapsedId of self.collapsed) {
745
+ const node = find(hier, n => n.data.id === collapsedId)
746
+ if (!node) {
747
+ continue
748
+ }
749
+ if (node.children) {
818
750
  collapse(node)
819
- })
751
+ } else if (node.parent?.children) {
752
+ node.parent.children = node.parent.children.filter(
753
+ c => c.data.id !== collapsedId,
754
+ )
755
+ }
756
+ }
820
757
 
821
758
  return hier
822
759
  },
@@ -1252,11 +1189,9 @@ function stateModelFactory() {
1252
1189
  */
1253
1190
  get hierarchy(): HierarchyNode<NodeWithIdsAndLength> {
1254
1191
  const r = this.root
1255
- const clust = cluster<NodeWithIds>()
1256
- .size([this.totalHeight, self.treeWidth])
1257
- .separation(() => 1)
1258
- clust(r)
1259
- setBrLength(r, (r.data.length = 0), self.treeWidth / maxLength(r))
1192
+ clusterLayout(r, this.totalHeight, self.treeWidth)
1193
+ r.data.length = 0
1194
+ setBrLength(r, 0, self.treeWidth / maxLength(r))
1260
1195
  return r as HierarchyNode<NodeWithIdsAndLength>
1261
1196
  },
1262
1197
 
@@ -1264,21 +1199,21 @@ function stateModelFactory() {
1264
1199
  * #getter
1265
1200
  */
1266
1201
  get totalHeight() {
1267
- return this.root.leaves().length * self.rowHeight
1202
+ return leaves(this.root).length * self.rowHeight
1268
1203
  },
1269
1204
 
1270
1205
  /**
1271
1206
  * #getter
1272
1207
  */
1273
1208
  get leaves() {
1274
- return this.hierarchy.leaves()
1209
+ return leaves(this.hierarchy)
1275
1210
  },
1276
1211
 
1277
1212
  /**
1278
1213
  * #getter
1279
1214
  */
1280
1215
  get allBranchesLength0() {
1281
- return this.hierarchy.links().every(s => !s.source.data.length)
1216
+ return links(this.hierarchy).every(s => !s.source.data.length)
1282
1217
  },
1283
1218
 
1284
1219
  /**
@@ -1333,13 +1268,9 @@ function stateModelFactory() {
1333
1268
  * #getter
1334
1269
  */
1335
1270
  get blocks2d() {
1336
- const ret = []
1337
- for (const by of self.blocksY) {
1338
- for (const bx of self.blocksX) {
1339
- ret.push([bx, by] as const)
1340
- }
1341
- }
1342
- return ret
1271
+ return self.blocksY.flatMap(by =>
1272
+ self.blocksX.map(bx => [bx, by] as const),
1273
+ )
1343
1274
  },
1344
1275
 
1345
1276
  /**
@@ -1447,15 +1378,19 @@ function stateModelFactory() {
1447
1378
  /**
1448
1379
  * #action
1449
1380
  */
1381
+ doScrollY(deltaY: number) {
1382
+ self.scrollY = clamp(self.scrollY + deltaY, -self.totalHeight + 10, 0)
1383
+ },
1384
+
1450
1385
  setInterProAnnotations(data: Record<string, InterProScanResults>) {
1451
1386
  self.interProAnnotations = data
1452
1387
  },
1453
1388
 
1454
- /**
1455
- * #action
1456
- */
1457
- doScrollY(deltaY: number) {
1458
- self.scrollY = clamp(self.scrollY + deltaY, -self.totalHeight + 10, 0)
1389
+ applyGFFText(gffText: string) {
1390
+ const gffRecords = parseGFF(gffText)
1391
+ const interProResults = gffToInterProResults(gffRecords)
1392
+ self.interProAnnotations = interProResults
1393
+ self.setShowDomains(true)
1459
1394
  },
1460
1395
 
1461
1396
  /**
@@ -1494,20 +1429,21 @@ function stateModelFactory() {
1494
1429
  * #getter
1495
1430
  */
1496
1431
  get labelsWidth() {
1497
- let x = 0
1498
1432
  const { rowHeight, leaves, treeMetadata, fontSize } = self
1499
- if (rowHeight > 5) {
1500
- for (const node of leaves) {
1501
- x = Math.max(
1433
+ if (rowHeight <= 5) {
1434
+ return 0
1435
+ }
1436
+ return leaves.reduce(
1437
+ (max, node) =>
1438
+ Math.max(
1439
+ max,
1502
1440
  measureTextCanvas(
1503
1441
  treeMetadata[node.data.name]?.genome || node.data.name,
1504
1442
  fontSize,
1505
1443
  ),
1506
- x,
1507
- )
1508
- }
1509
- }
1510
- return x
1444
+ ),
1445
+ 0,
1446
+ )
1511
1447
  },
1512
1448
 
1513
1449
  /**
@@ -1529,18 +1465,22 @@ function stateModelFactory() {
1529
1465
  */
1530
1466
  get adapterTrackModels(): BasicTrack[] {
1531
1467
  const { rowHeight, MSA, hideGapsEffective, blanks } = self
1532
- return (
1533
- MSA?.tracks
1534
- .filter(t => t.data)
1535
- .map(t => ({
1536
- model: {
1537
- ...t,
1538
- data: hideGapsEffective ? skipBlanks(blanks, t.data!) : t.data,
1539
- height: rowHeight,
1540
- } as TextTrackModel,
1541
- ReactComponent: TextTrack,
1542
- })) || []
1543
- )
1468
+ const tracks = (MSA?.tracks ?? []) as (TextTrackModel & {
1469
+ data?: string
1470
+ })[]
1471
+ return tracks
1472
+ .filter(t => !!t.data)
1473
+ .map(t => ({
1474
+ model: {
1475
+ ...t,
1476
+ data:
1477
+ hideGapsEffective && t.data
1478
+ ? skipBlanks(blanks, t.data)
1479
+ : t.data,
1480
+ height: rowHeight,
1481
+ },
1482
+ ReactComponent: TextTrack,
1483
+ }))
1544
1484
  },
1545
1485
 
1546
1486
  /**
@@ -1718,55 +1658,38 @@ function stateModelFactory() {
1718
1658
  get totalTrackAreaHeight() {
1719
1659
  return sum(self.turnedOnTracks.map(r => r.model.height))
1720
1660
  },
1721
- /**
1722
- * #getter
1723
- */
1724
1661
  get tidyInterProAnnotationTypes() {
1725
- const types = new Map<string, Accession>()
1726
- for (const annot of this.tidyInterProAnnotations) {
1727
- types.set(annot.accession, annot)
1728
- }
1729
- return types
1662
+ return new Map(
1663
+ this.tidyInterProAnnotations.map(annot => [annot.accession, annot]),
1664
+ )
1730
1665
  },
1731
- /**
1732
- * #getter
1733
- */
1734
1666
  get tidyInterProAnnotations() {
1735
- const ret = []
1736
1667
  const { interProAnnotations } = self
1737
- if (interProAnnotations) {
1738
- for (const [id, val] of Object.entries(interProAnnotations)) {
1739
- for (const { signature, locations } of val.matches) {
1740
- const { entry } = signature
1741
- if (entry) {
1742
- const { name, accession, description } = entry
1743
- for (const { start, end } of locations) {
1744
- ret.push({
1668
+ if (!interProAnnotations) {
1669
+ return []
1670
+ }
1671
+ return Object.entries(interProAnnotations)
1672
+ .flatMap(([id, val]) =>
1673
+ val.matches.flatMap(({ signature, locations }) =>
1674
+ signature.entry
1675
+ ? locations.map(({ start, end }) => ({
1745
1676
  id,
1746
- name,
1747
- accession,
1748
- description,
1677
+ name: signature.entry!.name,
1678
+ accession: signature.entry!.accession,
1679
+ description: signature.entry!.description,
1749
1680
  start,
1750
1681
  end,
1751
- })
1752
- }
1753
- }
1754
- }
1755
- }
1756
- }
1757
- return ret.toSorted((a, b) => len(b) - len(a))
1682
+ }))
1683
+ : [],
1684
+ ),
1685
+ )
1686
+ .toSorted((a, b) => len(b) - len(a))
1758
1687
  },
1759
- /**
1760
- * #getter
1761
- */
1762
1688
  get tidyFilteredInterProAnnotations() {
1763
1689
  return this.tidyInterProAnnotations.filter(r =>
1764
1690
  self.featureFilters.get(r.accession),
1765
1691
  )
1766
1692
  },
1767
- /**
1768
- * #getter
1769
- */
1770
1693
  get tidyFilteredGatheredInterProAnnotations() {
1771
1694
  return groupBy(this.tidyFilteredInterProAnnotations, r => r.id)
1772
1695
  },
@@ -1786,15 +1709,9 @@ function stateModelFactory() {
1786
1709
  get verticalScrollbarWidth() {
1787
1710
  return self.showVerticalScrollbar ? 20 : 0
1788
1711
  },
1789
- /**
1790
- * #getter
1791
- */
1792
1712
  get fillPalette() {
1793
1713
  return createPaletteMap([...self.tidyInterProAnnotationTypes.keys()])
1794
1714
  },
1795
- /**
1796
- * #getter
1797
- */
1798
1715
  get strokePalette() {
1799
1716
  return Object.fromEntries(
1800
1717
  Object.entries(this.fillPalette).map(([key, val]) => [
@@ -1842,6 +1759,9 @@ function stateModelFactory() {
1842
1759
  self.setCurrentAlignment(0)
1843
1760
  self.setTreeFilehandle(undefined)
1844
1761
  self.setMSAFilehandle(undefined)
1762
+ self.setGFFFilehandle(undefined)
1763
+ self.setInterProAnnotations({})
1764
+ self.setShowDomains(false)
1845
1765
  },
1846
1766
  /**
1847
1767
  * #action
@@ -1865,21 +1785,16 @@ function stateModelFactory() {
1865
1785
  self.nref++
1866
1786
  },
1867
1787
 
1868
- /**
1869
- * #action
1870
- */
1871
1788
  initFilter(arg: string) {
1872
1789
  const ret = self.featureFilters.get(arg)
1873
1790
  if (ret === undefined) {
1874
1791
  self.featureFilters.set(arg, true)
1875
1792
  }
1876
1793
  },
1877
- /**
1878
- * #action
1879
- */
1880
1794
  setFilter(arg: string, flag: boolean) {
1881
1795
  self.featureFilters.set(arg, flag)
1882
1796
  },
1797
+
1883
1798
  /**
1884
1799
  * #action
1885
1800
  */
@@ -1914,14 +1829,6 @@ function stateModelFactory() {
1914
1829
  }),
1915
1830
  )
1916
1831
 
1917
- // autorun saves local settings
1918
- addDisposer(
1919
- self,
1920
- autorun(() => {
1921
- localStorageSetBoolean(showZoomStarKey, self.showZoomStar)
1922
- }),
1923
- )
1924
-
1925
1832
  // autorun opens treeFilehandle
1926
1833
  addDisposer(
1927
1834
  self,
@@ -1966,6 +1873,22 @@ function stateModelFactory() {
1966
1873
  }),
1967
1874
  )
1968
1875
 
1876
+ // autorun parses inline gff text from data.gff
1877
+ addDisposer(
1878
+ self,
1879
+ autorun(() => {
1880
+ const gffText = self.data.gff
1881
+ if (gffText) {
1882
+ try {
1883
+ self.applyGFFText(gffText)
1884
+ } catch (e) {
1885
+ console.error(e)
1886
+ self.setError(e)
1887
+ }
1888
+ }
1889
+ }),
1890
+ )
1891
+
1969
1892
  // autorun opens gffFilehandle for InterProScan domains
1970
1893
  addDisposer(
1971
1894
  self,
@@ -1976,10 +1899,7 @@ function stateModelFactory() {
1976
1899
  const gffText = await fetchAndMaybeUnzipText(
1977
1900
  openLocation(gffFilehandle),
1978
1901
  )
1979
- const gffRecords = parseGFF(gffText)
1980
- const interProResults = gffToInterProResults(gffRecords)
1981
- self.setInterProAnnotations(interProResults)
1982
- self.setShowDomains(true)
1902
+ self.applyGFFText(gffText)
1983
1903
  if (gffFilehandle.locationType === 'BlobLocation') {
1984
1904
  self.setGFFFilehandle(undefined)
1985
1905
  }
@@ -2040,15 +1960,13 @@ function stateModelFactory() {
2040
1960
  // autorun synchronizes treeWidth with treeAreaWidth
2041
1961
  addDisposer(
2042
1962
  self,
2043
- autorun(async () => {
2044
- if (self.treeWidthMatchesArea) {
2045
- self.setTreeWidth(
2046
- Math.max(
2047
- 50,
2048
- self.treeAreaWidth - self.labelsWidth - 10 - self.marginLeft,
2049
- ),
2050
- )
2051
- }
1963
+ autorun(() => {
1964
+ self.setTreeWidth(
1965
+ Math.max(
1966
+ 50,
1967
+ self.treeAreaWidth - self.labelsWidth - 10 - self.marginLeft,
1968
+ ),
1969
+ )
2052
1970
  }),
2053
1971
  )
2054
1972
  },
@@ -2061,7 +1979,6 @@ function stateModelFactory() {
2061
1979
  showDomains,
2062
1980
  hideGaps,
2063
1981
  allowedGappyness,
2064
- contrastLettering,
2065
1982
  subFeatureRows,
2066
1983
  drawMsaLetters,
2067
1984
  height,
@@ -2071,7 +1988,6 @@ function stateModelFactory() {
2071
1988
  colWidth,
2072
1989
  currentAlignment,
2073
1990
  collapsed,
2074
- collapsedLeaves,
2075
1991
  showOnly,
2076
1992
  turnedOffTracks,
2077
1993
  featureFilters,
@@ -2084,7 +2000,6 @@ function stateModelFactory() {
2084
2000
  labelsAlignRight,
2085
2001
  treeAreaWidth,
2086
2002
  treeWidth,
2087
- treeWidthMatchesArea,
2088
2003
  showBranchLen,
2089
2004
  drawTree,
2090
2005
  drawNodeBubbles,
@@ -2092,8 +2007,35 @@ function stateModelFactory() {
2092
2007
  ...rest
2093
2008
  } = snap
2094
2009
 
2095
- // remove the MSA/tree data from the tree if the filehandle available in
2096
- // which case it can be reloaded on refresh
2010
+ const defaults: Record<string, unknown> = {
2011
+ showDomains: defaultShowDomains,
2012
+ hideGaps: defaultHideGaps,
2013
+ allowedGappyness: defaultAllowedGappyness,
2014
+ subFeatureRows: defaultSubFeatureRows,
2015
+ drawMsaLetters: defaultDrawMsaLetters,
2016
+ height: defaultHeight,
2017
+ rowHeight: defaultRowHeight,
2018
+ scrollY: defaultScrollY,
2019
+ scrollX: defaultScrollX,
2020
+ colWidth: defaultColWidth,
2021
+ currentAlignment: defaultCurrentAlignment,
2022
+ bgColor: defaultBgColor,
2023
+ colorSchemeName: defaultColorSchemeName,
2024
+ drawLabels: defaultDrawLabels,
2025
+ labelsAlignRight: defaultLabelsAlignRight,
2026
+ treeAreaWidth: defaultTreeAreaWidth,
2027
+ treeWidth: defaultTreeWidth,
2028
+ showBranchLen: defaultShowBranchLen,
2029
+ drawTree: defaultDrawTree,
2030
+ drawNodeBubbles: defaultDrawNodeBubbles,
2031
+ }
2032
+
2033
+ const nonDefaults = Object.fromEntries(
2034
+ Object.entries(defaults)
2035
+ .filter(([key, def]) => snap[key as keyof typeof snap] !== def)
2036
+ .map(([key]) => [key, snap[key as keyof typeof snap]]),
2037
+ )
2038
+
2097
2039
  return {
2098
2040
  ...rest,
2099
2041
  data: {
@@ -2101,29 +2043,9 @@ function stateModelFactory() {
2101
2043
  ...(result.msaFilehandle ? {} : { msa }),
2102
2044
  ...(result.treeMetadataFilehandle ? {} : { treeMetadata }),
2103
2045
  },
2104
- // Main model - only include non-default values
2105
- ...(showDomains !== defaultShowDomains ? { showDomains } : {}),
2106
- ...(hideGaps !== defaultHideGaps ? { hideGaps } : {}),
2107
- ...(allowedGappyness !== defaultAllowedGappyness
2108
- ? { allowedGappyness }
2109
- : {}),
2110
- ...(contrastLettering !== defaultContrastLettering
2111
- ? { contrastLettering }
2112
- : {}),
2113
- ...(subFeatureRows !== defaultSubFeatureRows ? { subFeatureRows } : {}),
2114
- ...(drawMsaLetters !== defaultDrawMsaLetters ? { drawMsaLetters } : {}),
2115
- ...(height !== defaultHeight ? { height } : {}),
2116
- ...(rowHeight !== defaultRowHeight ? { rowHeight } : {}),
2117
- ...(scrollY !== defaultScrollY ? { scrollY } : {}),
2118
- ...(scrollX !== defaultScrollX ? { scrollX } : {}),
2119
- ...(colWidth !== defaultColWidth ? { colWidth } : {}),
2120
- ...(currentAlignment !== defaultCurrentAlignment
2121
- ? { currentAlignment }
2122
- : {}),
2046
+ ...nonDefaults,
2123
2047
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
2124
2048
  ...(collapsed?.length ? { collapsed } : {}),
2125
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
2126
- ...(collapsedLeaves?.length ? { collapsedLeaves } : {}),
2127
2049
  ...(showOnly !== undefined ? { showOnly } : {}),
2128
2050
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
2129
2051
  ...(turnedOffTracks && Object.keys(turnedOffTracks).length > 0
@@ -2134,26 +2056,6 @@ function stateModelFactory() {
2134
2056
  ? { featureFilters }
2135
2057
  : {}),
2136
2058
  ...(relativeTo !== undefined ? { relativeTo } : {}),
2137
- // MSA model - only include non-default values
2138
- ...(bgColor !== defaultBgColor ? { bgColor } : {}),
2139
- ...(colorSchemeName !== defaultColorSchemeName
2140
- ? { colorSchemeName }
2141
- : {}),
2142
- // Tree model - only include non-default values
2143
- ...(drawLabels !== defaultDrawLabels ? { drawLabels } : {}),
2144
- ...(labelsAlignRight !== defaultLabelsAlignRight
2145
- ? { labelsAlignRight }
2146
- : {}),
2147
- ...(treeAreaWidth !== defaultTreeAreaWidth ? { treeAreaWidth } : {}),
2148
- ...(treeWidth !== defaultTreeWidth ? { treeWidth } : {}),
2149
- ...(treeWidthMatchesArea !== defaultTreeWidthMatchesArea
2150
- ? { treeWidthMatchesArea }
2151
- : {}),
2152
- ...(showBranchLen !== defaultShowBranchLen ? { showBranchLen } : {}),
2153
- ...(drawTree !== defaultDrawTree ? { drawTree } : {}),
2154
- ...(drawNodeBubbles !== defaultDrawNodeBubbles
2155
- ? { drawNodeBubbles }
2156
- : {}),
2157
2059
  } as typeof snap
2158
2060
  })
2159
2061
  }