react-msaview 5.0.6 → 5.0.13

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 (190) hide show
  1. package/bundle/index.js +106 -106
  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/Track.d.ts +0 -4
  10. package/dist/components/Track.js +6 -26
  11. package/dist/components/Track.js.map +1 -1
  12. package/dist/components/dialogs/DomainDialog.js +2 -5
  13. package/dist/components/dialogs/DomainDialog.js.map +1 -1
  14. package/dist/components/dialogs/InterProScanDialog.js +7 -7
  15. package/dist/components/dialogs/InterProScanDialog.js.map +1 -1
  16. package/dist/components/dialogs/SettingsDialog.js +3 -19
  17. package/dist/components/dialogs/SettingsDialog.js.map +1 -1
  18. package/dist/components/header/ColorSchemeMenu.d.ts +6 -0
  19. package/dist/components/header/ColorSchemeMenu.js +19 -0
  20. package/dist/components/header/ColorSchemeMenu.js.map +1 -0
  21. package/dist/components/header/{ZoomStar.d.ts → FileMenu.d.ts} +2 -2
  22. package/dist/components/header/FileMenu.js +71 -0
  23. package/dist/components/header/FileMenu.js.map +1 -0
  24. package/dist/components/header/Header.js +8 -6
  25. package/dist/components/header/Header.js.map +1 -1
  26. package/dist/components/header/HeaderMenu.js +3 -145
  27. package/dist/components/header/HeaderMenu.js.map +1 -1
  28. package/dist/components/header/MSASettingsMenu.d.ts +6 -0
  29. package/dist/components/header/MSASettingsMenu.js +36 -0
  30. package/dist/components/header/MSASettingsMenu.js.map +1 -0
  31. package/dist/components/header/SettingsMenu.js +1 -21
  32. package/dist/components/header/SettingsMenu.js.map +1 -1
  33. package/dist/components/header/TreeSettingsMenu.d.ts +6 -0
  34. package/dist/components/header/TreeSettingsMenu.js +74 -0
  35. package/dist/components/header/TreeSettingsMenu.js.map +1 -0
  36. package/dist/components/header/ZoomMenu.js +0 -8
  37. package/dist/components/header/ZoomMenu.js.map +1 -1
  38. package/dist/components/header/getDomainsMenu.d.ts +31 -0
  39. package/dist/components/header/getDomainsMenu.js +75 -0
  40. package/dist/components/header/getDomainsMenu.js.map +1 -0
  41. package/dist/components/import/ImportFormExamples.js +21 -19
  42. package/dist/components/import/ImportFormExamples.js.map +1 -1
  43. package/dist/components/msa/MSACanvas.js +13 -89
  44. package/dist/components/msa/MSACanvas.js.map +1 -1
  45. package/dist/components/msa/MSACanvasBlock.js +1 -3
  46. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  47. package/dist/components/msa/renderMSABlock.js +2 -4
  48. package/dist/components/msa/renderMSABlock.js.map +1 -1
  49. package/dist/components/msa/renderMSAMouseover.js +1 -7
  50. package/dist/components/msa/renderMSAMouseover.js.map +1 -1
  51. package/dist/components/tree/TreeCanvas.js +18 -101
  52. package/dist/components/tree/TreeCanvas.js.map +1 -1
  53. package/dist/components/tree/TreeCanvasBlock.js +33 -1
  54. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  55. package/dist/components/tree/TreeNodeMenu.js +5 -16
  56. package/dist/components/tree/TreeNodeMenu.js.map +1 -1
  57. package/dist/components/tree/renderTreeCanvas.js +4 -12
  58. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  59. package/dist/constants.d.ts +0 -2
  60. package/dist/constants.js +0 -2
  61. package/dist/constants.js.map +1 -1
  62. package/dist/fetchUtils.d.ts +0 -1
  63. package/dist/fetchUtils.js +0 -4
  64. package/dist/fetchUtils.js.map +1 -1
  65. package/dist/flatToTree.d.ts +0 -5
  66. package/dist/flatToTree.js +13 -30
  67. package/dist/flatToTree.js.map +1 -1
  68. package/dist/hierarchy.d.ts +28 -0
  69. package/dist/hierarchy.js +164 -0
  70. package/dist/hierarchy.js.map +1 -0
  71. package/dist/index.d.ts +2 -0
  72. package/dist/index.js +1 -0
  73. package/dist/index.js.map +1 -1
  74. package/dist/launchInterProScan.d.ts +0 -5
  75. package/dist/launchInterProScan.js +5 -3
  76. package/dist/launchInterProScan.js.map +1 -1
  77. package/dist/model/DataModel.d.ts +9 -0
  78. package/dist/model/DataModel.js +12 -1
  79. package/dist/model/DataModel.js.map +1 -1
  80. package/dist/model/msaModel.d.ts +3 -0
  81. package/dist/model/msaModel.js +0 -1
  82. package/dist/model/msaModel.js.map +1 -1
  83. package/dist/model/treeModel.d.ts +3 -6
  84. package/dist/model/treeModel.js +3 -15
  85. package/dist/model/treeModel.js.map +1 -1
  86. package/dist/model.d.ts +34 -77
  87. package/dist/model.js +140 -239
  88. package/dist/model.js.map +1 -1
  89. package/dist/neighborJoining.js +40 -633
  90. package/dist/neighborJoining.js.map +1 -1
  91. package/dist/parseAsn1.d.ts +0 -12
  92. package/dist/parseAsn1.js +125 -332
  93. package/dist/parseAsn1.js.map +1 -1
  94. package/dist/useWheelScroll.d.ts +8 -0
  95. package/dist/useWheelScroll.js +93 -0
  96. package/dist/useWheelScroll.js.map +1 -0
  97. package/dist/util.d.ts +1 -6
  98. package/dist/util.js +5 -34
  99. package/dist/util.js.map +1 -1
  100. package/dist/vendor/copyToClipboard.d.ts +1 -10
  101. package/dist/vendor/copyToClipboard.js +14 -109
  102. package/dist/vendor/copyToClipboard.js.map +1 -1
  103. package/dist/vendor/fileSaver.d.ts +1 -11
  104. package/dist/vendor/fileSaver.js +7 -76
  105. package/dist/vendor/fileSaver.js.map +1 -1
  106. package/dist/version.d.ts +1 -1
  107. package/dist/version.js +1 -1
  108. package/dist/version.js.map +1 -1
  109. package/package.json +14 -14
  110. package/src/collapseLogic.test.ts +115 -0
  111. package/src/components/Checkbox2.tsx +9 -18
  112. package/src/components/MSAViewer.tsx +67 -0
  113. package/src/components/Track.tsx +11 -30
  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 +37 -34
  127. package/src/components/msa/MSACanvas.tsx +21 -97
  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 +48 -111
  133. package/src/components/tree/TreeCanvasBlock.tsx +44 -0
  134. package/src/components/tree/TreeNodeMenu.tsx +5 -14
  135. package/src/components/tree/renderTreeCanvas.ts +8 -21
  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 +220 -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 +203 -278
  147. package/src/neighborJoining.test.ts +15 -7
  148. package/src/neighborJoining.ts +40 -632
  149. package/src/parseAsn1.test.ts +5 -2
  150. package/src/parseAsn1.ts +135 -405
  151. package/src/useWheelScroll.ts +109 -0
  152. package/src/util.ts +5 -50
  153. package/src/vendor/copyToClipboard.ts +14 -122
  154. package/src/vendor/fileSaver.ts +8 -105
  155. package/src/version.ts +1 -1
  156. package/dist/components/dialogs/AddTrackDialog.d.ts +0 -8
  157. package/dist/components/dialogs/AddTrackDialog.js +0 -30
  158. package/dist/components/dialogs/AddTrackDialog.js.map +0 -1
  159. package/dist/components/dialogs/TabPanel.d.ts +0 -6
  160. package/dist/components/dialogs/TabPanel.js +0 -6
  161. package/dist/components/dialogs/TabPanel.js.map +0 -1
  162. package/dist/components/header/ZoomStar.js +0 -40
  163. package/dist/components/header/ZoomStar.js.map +0 -1
  164. package/dist/createPaletteMap.test.d.ts +0 -1
  165. package/dist/createPaletteMap.test.js +0 -49
  166. package/dist/createPaletteMap.test.js.map +0 -1
  167. package/dist/layout.d.ts +0 -26
  168. package/dist/layout.js +0 -74
  169. package/dist/layout.js.map +0 -1
  170. package/dist/neighborJoining.test.d.ts +0 -1
  171. package/dist/neighborJoining.test.js +0 -110
  172. package/dist/neighborJoining.test.js.map +0 -1
  173. package/dist/parseAsn1.test.d.ts +0 -1
  174. package/dist/parseAsn1.test.js +0 -8
  175. package/dist/parseAsn1.test.js.map +0 -1
  176. package/dist/reparseTree.d.ts +0 -2
  177. package/dist/reparseTree.js +0 -15
  178. package/dist/reparseTree.js.map +0 -1
  179. package/dist/rowCoordinateCalculations.test.d.ts +0 -1
  180. package/dist/rowCoordinateCalculations.test.js +0 -224
  181. package/dist/rowCoordinateCalculations.test.js.map +0 -1
  182. package/dist/seqPosToGlobalCol.test.d.ts +0 -1
  183. package/dist/seqPosToGlobalCol.test.js +0 -60
  184. package/dist/seqPosToGlobalCol.test.js.map +0 -1
  185. package/src/components/dialogs/AddTrackDialog.tsx +0 -85
  186. package/src/components/dialogs/TabPanel.tsx +0 -19
  187. package/src/components/header/ZoomStar.tsx +0 -74
  188. package/src/createPaletteMap.test.ts +0 -57
  189. package/src/layout.ts +0 -118
  190. 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
  */
@@ -359,11 +339,6 @@ function stateModelFactory() {
359
339
  */
360
340
  error: undefined as unknown,
361
341
 
362
- /**
363
- * #volatile
364
- */
365
- annotPos: undefined as { left: number; right: number } | undefined,
366
-
367
342
  /**
368
343
  * #volatile
369
344
  */
@@ -390,24 +365,12 @@ function stateModelFactory() {
390
365
  setAllowedGappyness(arg: number) {
391
366
  self.allowedGappyness = arg
392
367
  },
393
- /**
394
- * #action
395
- */
396
- setContrastLettering(arg: boolean) {
397
- self.contrastLettering = arg
398
- },
399
368
  /**
400
369
  * #action
401
370
  */
402
371
  setLoadingMSA(arg: boolean) {
403
372
  self.loadingMSA = arg
404
373
  },
405
- /**
406
- * #action
407
- */
408
- setShowZoomStar(arg: boolean) {
409
- self.showZoomStar = arg
410
- },
411
374
  /**
412
375
  * #action
413
376
  */
@@ -455,8 +418,8 @@ function stateModelFactory() {
455
418
  return
456
419
  }
457
420
 
458
- // Find the node in the hierarchy
459
- const node = (self as MsaViewModel).hierarchy.find(
421
+ const node = find(
422
+ (self as MsaViewModel).hierarchy,
460
423
  n => n.data.id === nodeId,
461
424
  )
462
425
  if (!node) {
@@ -464,8 +427,7 @@ function stateModelFactory() {
464
427
  return
465
428
  }
466
429
 
467
- // Get all descendant leaf names
468
- const descendantNames = node.leaves().map(leaf => leaf.data.name)
430
+ const descendantNames = leaves(node).map(leaf => leaf.data.name)
469
431
 
470
432
  self.hoveredTreeNode = { nodeId, descendantNames }
471
433
  },
@@ -482,7 +444,6 @@ function stateModelFactory() {
482
444
  setShowDomains(arg: boolean) {
483
445
  self.showDomains = arg
484
446
  },
485
-
486
447
  /**
487
448
  * #action
488
449
  */
@@ -541,16 +502,6 @@ function stateModelFactory() {
541
502
  }
542
503
  },
543
504
 
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
505
  /**
555
506
  * #action
556
507
  */
@@ -561,7 +512,12 @@ function stateModelFactory() {
561
512
  /**
562
513
  * #action
563
514
  */
564
- setData(data: { msa?: string; tree?: string; treeMetadata?: string }) {
515
+ setData(data: {
516
+ msa?: string
517
+ tree?: string
518
+ treeMetadata?: string
519
+ gff?: string
520
+ }) {
565
521
  self.data = cast(data)
566
522
  },
567
523
 
@@ -616,9 +572,7 @@ function stateModelFactory() {
616
572
  get hideGapsEffective() {
617
573
  return (
618
574
  self.hideGaps &&
619
- (self.collapsed.length > 0 ||
620
- self.collapsedLeaves.length > 0 ||
621
- self.allowedGappyness < 100)
575
+ (self.collapsed.length > 0 || self.allowedGappyness < 100)
622
576
  )
623
577
  },
624
578
  /**
@@ -633,9 +587,6 @@ function stateModelFactory() {
633
587
  get actuallyShowDomains() {
634
588
  return self.showDomains && !!self.interProAnnotations
635
589
  },
636
- /**
637
- * #getter
638
- */
639
590
  get viewInitialized() {
640
591
  return self.volatileWidth !== undefined
641
592
  },
@@ -668,7 +619,7 @@ function stateModelFactory() {
668
619
  * #getter
669
620
  */
670
621
  get header() {
671
- return this.MSA?.getHeader() || {}
622
+ return (this.MSA?.getHeader() || {}) as Record<string, unknown>
672
623
  },
673
624
 
674
625
  /**
@@ -683,15 +634,9 @@ function stateModelFactory() {
683
634
  get noTree() {
684
635
  return !!this.tree.noTree
685
636
  },
686
- /**
687
- * #getter
688
- */
689
637
  get noDomains() {
690
638
  return !self.interProAnnotations
691
639
  },
692
- /**
693
- * #getter
694
- */
695
640
  menuItems() {
696
641
  return []
697
642
  },
@@ -706,20 +651,9 @@ function stateModelFactory() {
706
651
  */
707
652
  get MSA() {
708
653
  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
654
+ // uses parseMSA so the named MSAParserType return type is portable
655
+ // to downstream consumers (avoids TS2883 with default exports)
656
+ return text ? parseMSA(text, self.currentAlignment) : null
723
657
  },
724
658
  /**
725
659
  * #getter
@@ -734,22 +668,20 @@ function stateModelFactory() {
734
668
  get tree(): NodeWithIds {
735
669
  const text = self.data.tree
736
670
 
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
- )
671
+ return text
672
+ ? generateNodeIds(
673
+ text.startsWith('BioTreeContainer')
674
+ ? flatToTree(parseAsn1(text))
675
+ : parseNewick(
676
+ text.startsWith('SEQ') ? parseEmfTree(text).tree : text,
677
+ ),
678
+ )
679
+ : this.MSA?.getTree() || {
680
+ noTree: true,
681
+ children: [],
682
+ id: 'empty',
683
+ name: 'empty',
684
+ }
753
685
  },
754
686
 
755
687
  /**
@@ -798,25 +730,29 @@ function stateModelFactory() {
798
730
  */
799
731
  get root() {
800
732
  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))
733
+ hierarchySum(hier, d => (d.children.length > 0 ? 0 : 1))
734
+ sort(hier, (a, b) => (a.data.length ?? 1) - (b.data.length ?? 1))
806
735
 
807
736
  if (self.showOnly) {
808
- const res = hier.find(n => n.data.id === self.showOnly)
737
+ const res = find(hier, n => n.data.id === self.showOnly)
809
738
  if (res) {
810
739
  hier = res
811
740
  }
812
741
  }
813
742
 
814
- ;[...self.collapsed, ...self.collapsedLeaves]
815
- .map(collapsedId => hier.find(node => node.data.id === collapsedId))
816
- .filter(notEmpty)
817
- .forEach(node => {
743
+ for (const collapsedId of self.collapsed) {
744
+ const node = find(hier, n => n.data.id === collapsedId)
745
+ if (!node) {
746
+ continue
747
+ }
748
+ if (node.children) {
818
749
  collapse(node)
819
- })
750
+ } else if (node.parent?.children) {
751
+ node.parent.children = node.parent.children.filter(
752
+ c => c.data.id !== collapsedId,
753
+ )
754
+ }
755
+ }
820
756
 
821
757
  return hier
822
758
  },
@@ -1252,11 +1188,9 @@ function stateModelFactory() {
1252
1188
  */
1253
1189
  get hierarchy(): HierarchyNode<NodeWithIdsAndLength> {
1254
1190
  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))
1191
+ clusterLayout(r, this.totalHeight, self.treeWidth)
1192
+ r.data.length = 0
1193
+ setBrLength(r, 0, self.treeWidth / maxLength(r))
1260
1194
  return r as HierarchyNode<NodeWithIdsAndLength>
1261
1195
  },
1262
1196
 
@@ -1264,21 +1198,21 @@ function stateModelFactory() {
1264
1198
  * #getter
1265
1199
  */
1266
1200
  get totalHeight() {
1267
- return this.root.leaves().length * self.rowHeight
1201
+ return leaves(this.root).length * self.rowHeight
1268
1202
  },
1269
1203
 
1270
1204
  /**
1271
1205
  * #getter
1272
1206
  */
1273
1207
  get leaves() {
1274
- return this.hierarchy.leaves()
1208
+ return leaves(this.hierarchy)
1275
1209
  },
1276
1210
 
1277
1211
  /**
1278
1212
  * #getter
1279
1213
  */
1280
1214
  get allBranchesLength0() {
1281
- return this.hierarchy.links().every(s => !s.source.data.length)
1215
+ return links(this.hierarchy).every(s => !s.source.data.length)
1282
1216
  },
1283
1217
 
1284
1218
  /**
@@ -1333,13 +1267,9 @@ function stateModelFactory() {
1333
1267
  * #getter
1334
1268
  */
1335
1269
  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
1270
+ return self.blocksY.flatMap(by =>
1271
+ self.blocksX.map(bx => [bx, by] as const),
1272
+ )
1343
1273
  },
1344
1274
 
1345
1275
  /**
@@ -1447,15 +1377,19 @@ function stateModelFactory() {
1447
1377
  /**
1448
1378
  * #action
1449
1379
  */
1380
+ doScrollY(deltaY: number) {
1381
+ self.scrollY = clamp(self.scrollY + deltaY, -self.totalHeight + 10, 0)
1382
+ },
1383
+
1450
1384
  setInterProAnnotations(data: Record<string, InterProScanResults>) {
1451
1385
  self.interProAnnotations = data
1452
1386
  },
1453
1387
 
1454
- /**
1455
- * #action
1456
- */
1457
- doScrollY(deltaY: number) {
1458
- self.scrollY = clamp(self.scrollY + deltaY, -self.totalHeight + 10, 0)
1388
+ applyGFFText(gffText: string) {
1389
+ const gffRecords = parseGFF(gffText)
1390
+ const interProResults = gffToInterProResults(gffRecords)
1391
+ self.interProAnnotations = interProResults
1392
+ self.setShowDomains(true)
1459
1393
  },
1460
1394
 
1461
1395
  /**
@@ -1494,20 +1428,21 @@ function stateModelFactory() {
1494
1428
  * #getter
1495
1429
  */
1496
1430
  get labelsWidth() {
1497
- let x = 0
1498
1431
  const { rowHeight, leaves, treeMetadata, fontSize } = self
1499
- if (rowHeight > 5) {
1500
- for (const node of leaves) {
1501
- x = Math.max(
1432
+ if (rowHeight <= 5) {
1433
+ return 0
1434
+ }
1435
+ return leaves.reduce(
1436
+ (max, node) =>
1437
+ Math.max(
1438
+ max,
1502
1439
  measureTextCanvas(
1503
1440
  treeMetadata[node.data.name]?.genome || node.data.name,
1504
1441
  fontSize,
1505
1442
  ),
1506
- x,
1507
- )
1508
- }
1509
- }
1510
- return x
1443
+ ),
1444
+ 0,
1445
+ )
1511
1446
  },
1512
1447
 
1513
1448
  /**
@@ -1529,18 +1464,22 @@ function stateModelFactory() {
1529
1464
  */
1530
1465
  get adapterTrackModels(): BasicTrack[] {
1531
1466
  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
- )
1467
+ const tracks = (MSA?.tracks ?? []) as (TextTrackModel & {
1468
+ data?: string
1469
+ })[]
1470
+ return tracks
1471
+ .filter(t => !!t.data)
1472
+ .map(t => ({
1473
+ model: {
1474
+ ...t,
1475
+ data:
1476
+ hideGapsEffective && t.data
1477
+ ? skipBlanks(blanks, t.data)
1478
+ : t.data,
1479
+ height: rowHeight,
1480
+ },
1481
+ ReactComponent: TextTrack,
1482
+ }))
1544
1483
  },
1545
1484
 
1546
1485
  /**
@@ -1676,6 +1615,30 @@ function stateModelFactory() {
1676
1615
  }))
1677
1616
 
1678
1617
  .views(self => ({
1618
+ /**
1619
+ * #getter
1620
+ * Returns information about the currently hovered cell
1621
+ */
1622
+ get hoveredCell() {
1623
+ const { mouseCol, mouseRow } = self
1624
+ if (mouseCol === undefined || mouseRow === undefined) {
1625
+ return undefined
1626
+ }
1627
+ const rowName = self.rowNames[mouseRow]
1628
+ if (!rowName) {
1629
+ return undefined
1630
+ }
1631
+ const seq = self.columns[rowName]
1632
+ const base = seq?.[mouseCol]
1633
+ const seqPos = self.visibleColToSeqPosOneBased(rowName, mouseCol)
1634
+ return {
1635
+ rowName,
1636
+ col: mouseCol,
1637
+ base,
1638
+ seqPos,
1639
+ }
1640
+ },
1641
+
1679
1642
  /**
1680
1643
  * #getter
1681
1644
  * widget width minus the tree area gives the space for the MSA
@@ -1694,55 +1657,38 @@ function stateModelFactory() {
1694
1657
  get totalTrackAreaHeight() {
1695
1658
  return sum(self.turnedOnTracks.map(r => r.model.height))
1696
1659
  },
1697
- /**
1698
- * #getter
1699
- */
1700
1660
  get tidyInterProAnnotationTypes() {
1701
- const types = new Map<string, Accession>()
1702
- for (const annot of this.tidyInterProAnnotations) {
1703
- types.set(annot.accession, annot)
1704
- }
1705
- return types
1661
+ return new Map(
1662
+ this.tidyInterProAnnotations.map(annot => [annot.accession, annot]),
1663
+ )
1706
1664
  },
1707
- /**
1708
- * #getter
1709
- */
1710
1665
  get tidyInterProAnnotations() {
1711
- const ret = []
1712
1666
  const { interProAnnotations } = self
1713
- if (interProAnnotations) {
1714
- for (const [id, val] of Object.entries(interProAnnotations)) {
1715
- for (const { signature, locations } of val.matches) {
1716
- const { entry } = signature
1717
- if (entry) {
1718
- const { name, accession, description } = entry
1719
- for (const { start, end } of locations) {
1720
- ret.push({
1667
+ if (!interProAnnotations) {
1668
+ return []
1669
+ }
1670
+ return Object.entries(interProAnnotations)
1671
+ .flatMap(([id, val]) =>
1672
+ val.matches.flatMap(({ signature, locations }) =>
1673
+ signature.entry
1674
+ ? locations.map(({ start, end }) => ({
1721
1675
  id,
1722
- name,
1723
- accession,
1724
- description,
1676
+ name: signature.entry!.name,
1677
+ accession: signature.entry!.accession,
1678
+ description: signature.entry!.description,
1725
1679
  start,
1726
1680
  end,
1727
- })
1728
- }
1729
- }
1730
- }
1731
- }
1732
- }
1733
- return ret.toSorted((a, b) => len(b) - len(a))
1681
+ }))
1682
+ : [],
1683
+ ),
1684
+ )
1685
+ .toSorted((a, b) => len(b) - len(a))
1734
1686
  },
1735
- /**
1736
- * #getter
1737
- */
1738
1687
  get tidyFilteredInterProAnnotations() {
1739
1688
  return this.tidyInterProAnnotations.filter(r =>
1740
1689
  self.featureFilters.get(r.accession),
1741
1690
  )
1742
1691
  },
1743
- /**
1744
- * #getter
1745
- */
1746
1692
  get tidyFilteredGatheredInterProAnnotations() {
1747
1693
  return groupBy(this.tidyFilteredInterProAnnotations, r => r.id)
1748
1694
  },
@@ -1762,15 +1708,9 @@ function stateModelFactory() {
1762
1708
  get verticalScrollbarWidth() {
1763
1709
  return self.showVerticalScrollbar ? 20 : 0
1764
1710
  },
1765
- /**
1766
- * #getter
1767
- */
1768
1711
  get fillPalette() {
1769
1712
  return createPaletteMap([...self.tidyInterProAnnotationTypes.keys()])
1770
1713
  },
1771
- /**
1772
- * #getter
1773
- */
1774
1714
  get strokePalette() {
1775
1715
  return Object.fromEntries(
1776
1716
  Object.entries(this.fillPalette).map(([key, val]) => [
@@ -1818,6 +1758,9 @@ function stateModelFactory() {
1818
1758
  self.setCurrentAlignment(0)
1819
1759
  self.setTreeFilehandle(undefined)
1820
1760
  self.setMSAFilehandle(undefined)
1761
+ self.setGFFFilehandle(undefined)
1762
+ self.setInterProAnnotations({})
1763
+ self.setShowDomains(false)
1821
1764
  },
1822
1765
  /**
1823
1766
  * #action
@@ -1841,21 +1784,16 @@ function stateModelFactory() {
1841
1784
  self.nref++
1842
1785
  },
1843
1786
 
1844
- /**
1845
- * #action
1846
- */
1847
1787
  initFilter(arg: string) {
1848
1788
  const ret = self.featureFilters.get(arg)
1849
1789
  if (ret === undefined) {
1850
1790
  self.featureFilters.set(arg, true)
1851
1791
  }
1852
1792
  },
1853
- /**
1854
- * #action
1855
- */
1856
1793
  setFilter(arg: string, flag: boolean) {
1857
1794
  self.featureFilters.set(arg, flag)
1858
1795
  },
1796
+
1859
1797
  /**
1860
1798
  * #action
1861
1799
  */
@@ -1890,14 +1828,6 @@ function stateModelFactory() {
1890
1828
  }),
1891
1829
  )
1892
1830
 
1893
- // autorun saves local settings
1894
- addDisposer(
1895
- self,
1896
- autorun(() => {
1897
- localStorageSetBoolean(showZoomStarKey, self.showZoomStar)
1898
- }),
1899
- )
1900
-
1901
1831
  // autorun opens treeFilehandle
1902
1832
  addDisposer(
1903
1833
  self,
@@ -1942,6 +1872,22 @@ function stateModelFactory() {
1942
1872
  }),
1943
1873
  )
1944
1874
 
1875
+ // autorun parses inline gff text from data.gff
1876
+ addDisposer(
1877
+ self,
1878
+ autorun(() => {
1879
+ const gffText = self.data.gff
1880
+ if (gffText) {
1881
+ try {
1882
+ self.applyGFFText(gffText)
1883
+ } catch (e) {
1884
+ console.error(e)
1885
+ self.setError(e)
1886
+ }
1887
+ }
1888
+ }),
1889
+ )
1890
+
1945
1891
  // autorun opens gffFilehandle for InterProScan domains
1946
1892
  addDisposer(
1947
1893
  self,
@@ -1952,10 +1898,7 @@ function stateModelFactory() {
1952
1898
  const gffText = await fetchAndMaybeUnzipText(
1953
1899
  openLocation(gffFilehandle),
1954
1900
  )
1955
- const gffRecords = parseGFF(gffText)
1956
- const interProResults = gffToInterProResults(gffRecords)
1957
- self.setInterProAnnotations(interProResults)
1958
- self.setShowDomains(true)
1901
+ self.applyGFFText(gffText)
1959
1902
  if (gffFilehandle.locationType === 'BlobLocation') {
1960
1903
  self.setGFFFilehandle(undefined)
1961
1904
  }
@@ -2016,15 +1959,13 @@ function stateModelFactory() {
2016
1959
  // autorun synchronizes treeWidth with treeAreaWidth
2017
1960
  addDisposer(
2018
1961
  self,
2019
- autorun(async () => {
2020
- if (self.treeWidthMatchesArea) {
2021
- self.setTreeWidth(
2022
- Math.max(
2023
- 50,
2024
- self.treeAreaWidth - self.labelsWidth - 10 - self.marginLeft,
2025
- ),
2026
- )
2027
- }
1962
+ autorun(() => {
1963
+ self.setTreeWidth(
1964
+ Math.max(
1965
+ 50,
1966
+ self.treeAreaWidth - self.labelsWidth - 10 - self.marginLeft,
1967
+ ),
1968
+ )
2028
1969
  }),
2029
1970
  )
2030
1971
  },
@@ -2037,7 +1978,6 @@ function stateModelFactory() {
2037
1978
  showDomains,
2038
1979
  hideGaps,
2039
1980
  allowedGappyness,
2040
- contrastLettering,
2041
1981
  subFeatureRows,
2042
1982
  drawMsaLetters,
2043
1983
  height,
@@ -2047,7 +1987,6 @@ function stateModelFactory() {
2047
1987
  colWidth,
2048
1988
  currentAlignment,
2049
1989
  collapsed,
2050
- collapsedLeaves,
2051
1990
  showOnly,
2052
1991
  turnedOffTracks,
2053
1992
  featureFilters,
@@ -2060,7 +1999,6 @@ function stateModelFactory() {
2060
1999
  labelsAlignRight,
2061
2000
  treeAreaWidth,
2062
2001
  treeWidth,
2063
- treeWidthMatchesArea,
2064
2002
  showBranchLen,
2065
2003
  drawTree,
2066
2004
  drawNodeBubbles,
@@ -2068,8 +2006,35 @@ function stateModelFactory() {
2068
2006
  ...rest
2069
2007
  } = snap
2070
2008
 
2071
- // remove the MSA/tree data from the tree if the filehandle available in
2072
- // which case it can be reloaded on refresh
2009
+ const defaults: Record<string, unknown> = {
2010
+ showDomains: defaultShowDomains,
2011
+ hideGaps: defaultHideGaps,
2012
+ allowedGappyness: defaultAllowedGappyness,
2013
+ subFeatureRows: defaultSubFeatureRows,
2014
+ drawMsaLetters: defaultDrawMsaLetters,
2015
+ height: defaultHeight,
2016
+ rowHeight: defaultRowHeight,
2017
+ scrollY: defaultScrollY,
2018
+ scrollX: defaultScrollX,
2019
+ colWidth: defaultColWidth,
2020
+ currentAlignment: defaultCurrentAlignment,
2021
+ bgColor: defaultBgColor,
2022
+ colorSchemeName: defaultColorSchemeName,
2023
+ drawLabels: defaultDrawLabels,
2024
+ labelsAlignRight: defaultLabelsAlignRight,
2025
+ treeAreaWidth: defaultTreeAreaWidth,
2026
+ treeWidth: defaultTreeWidth,
2027
+ showBranchLen: defaultShowBranchLen,
2028
+ drawTree: defaultDrawTree,
2029
+ drawNodeBubbles: defaultDrawNodeBubbles,
2030
+ }
2031
+
2032
+ const nonDefaults = Object.fromEntries(
2033
+ Object.entries(defaults)
2034
+ .filter(([key, def]) => snap[key as keyof typeof snap] !== def)
2035
+ .map(([key]) => [key, snap[key as keyof typeof snap]]),
2036
+ )
2037
+
2073
2038
  return {
2074
2039
  ...rest,
2075
2040
  data: {
@@ -2077,29 +2042,9 @@ function stateModelFactory() {
2077
2042
  ...(result.msaFilehandle ? {} : { msa }),
2078
2043
  ...(result.treeMetadataFilehandle ? {} : { treeMetadata }),
2079
2044
  },
2080
- // Main model - only include non-default values
2081
- ...(showDomains !== defaultShowDomains ? { showDomains } : {}),
2082
- ...(hideGaps !== defaultHideGaps ? { hideGaps } : {}),
2083
- ...(allowedGappyness !== defaultAllowedGappyness
2084
- ? { allowedGappyness }
2085
- : {}),
2086
- ...(contrastLettering !== defaultContrastLettering
2087
- ? { contrastLettering }
2088
- : {}),
2089
- ...(subFeatureRows !== defaultSubFeatureRows ? { subFeatureRows } : {}),
2090
- ...(drawMsaLetters !== defaultDrawMsaLetters ? { drawMsaLetters } : {}),
2091
- ...(height !== defaultHeight ? { height } : {}),
2092
- ...(rowHeight !== defaultRowHeight ? { rowHeight } : {}),
2093
- ...(scrollY !== defaultScrollY ? { scrollY } : {}),
2094
- ...(scrollX !== defaultScrollX ? { scrollX } : {}),
2095
- ...(colWidth !== defaultColWidth ? { colWidth } : {}),
2096
- ...(currentAlignment !== defaultCurrentAlignment
2097
- ? { currentAlignment }
2098
- : {}),
2045
+ ...nonDefaults,
2099
2046
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
2100
2047
  ...(collapsed?.length ? { collapsed } : {}),
2101
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
2102
- ...(collapsedLeaves?.length ? { collapsedLeaves } : {}),
2103
2048
  ...(showOnly !== undefined ? { showOnly } : {}),
2104
2049
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
2105
2050
  ...(turnedOffTracks && Object.keys(turnedOffTracks).length > 0
@@ -2110,26 +2055,6 @@ function stateModelFactory() {
2110
2055
  ? { featureFilters }
2111
2056
  : {}),
2112
2057
  ...(relativeTo !== undefined ? { relativeTo } : {}),
2113
- // MSA model - only include non-default values
2114
- ...(bgColor !== defaultBgColor ? { bgColor } : {}),
2115
- ...(colorSchemeName !== defaultColorSchemeName
2116
- ? { colorSchemeName }
2117
- : {}),
2118
- // Tree model - only include non-default values
2119
- ...(drawLabels !== defaultDrawLabels ? { drawLabels } : {}),
2120
- ...(labelsAlignRight !== defaultLabelsAlignRight
2121
- ? { labelsAlignRight }
2122
- : {}),
2123
- ...(treeAreaWidth !== defaultTreeAreaWidth ? { treeAreaWidth } : {}),
2124
- ...(treeWidth !== defaultTreeWidth ? { treeWidth } : {}),
2125
- ...(treeWidthMatchesArea !== defaultTreeWidthMatchesArea
2126
- ? { treeWidthMatchesArea }
2127
- : {}),
2128
- ...(showBranchLen !== defaultShowBranchLen ? { showBranchLen } : {}),
2129
- ...(drawTree !== defaultDrawTree ? { drawTree } : {}),
2130
- ...(drawNodeBubbles !== defaultDrawNodeBubbles
2131
- ? { drawNodeBubbles }
2132
- : {}),
2133
2058
  } as typeof snap
2134
2059
  })
2135
2060
  }