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/dist/model.js CHANGED
@@ -1,19 +1,18 @@
1
- import { clamp, fetchAndMaybeUnzipText, groupBy, localStorageGetBoolean, localStorageSetBoolean, notEmpty, sum, } from '@jbrowse/core/util';
1
+ import { clamp, fetchAndMaybeUnzipText, groupBy, notEmpty, sum, } from '@jbrowse/core/util';
2
2
  import { openLocation } from '@jbrowse/core/util/io';
3
3
  import { ElementId, FileLocation } from '@jbrowse/core/util/types/mst';
4
4
  import { addDisposer, cast, types } from '@jbrowse/mobx-state-tree';
5
5
  import { colord } from 'colord';
6
- import { ascending } from 'd3-array';
7
- import { cluster, hierarchy } from 'd3-hierarchy';
8
6
  import { autorun, transaction } from 'mobx';
9
- import { A3mMSA, ClustalMSA, EmfMSA, FastaMSA, StockholmMSA, generateNodeIds, gffToInterProResults, parseEmfTree, parseGFF, parseNewick, stockholmSniff, } from 'msa-parsers';
7
+ import { generateNodeIds, gffToInterProResults, parseEmfTree, parseGFF, parseMSA, parseNewick, } from 'msa-parsers';
10
8
  import { blocksX, blocksY } from "./calculateBlocks.js";
11
9
  import colorSchemes from "./colorSchemes.js";
12
10
  import ConservationTrack from "./components/ConservationTrack.js";
13
11
  import TextTrack from "./components/TextTrack.js";
14
- import { defaultAllowedGappyness, defaultBgColor, defaultColWidth, defaultColorSchemeName, defaultContrastLettering, defaultCurrentAlignment, defaultDrawLabels, defaultDrawMsaLetters, defaultDrawNodeBubbles, defaultDrawTree, defaultHeight, defaultHideGaps, defaultLabelsAlignRight, defaultRowHeight, defaultScrollX, defaultScrollY, defaultShowBranchLen, defaultShowDomains, defaultSubFeatureRows, defaultTreeAreaWidth, defaultTreeWidth, defaultTreeWidthMatchesArea, } from "./constants.js";
12
+ import { defaultAllowedGappyness, defaultBgColor, defaultColWidth, defaultColorSchemeName, defaultCurrentAlignment, defaultDrawLabels, defaultDrawMsaLetters, defaultDrawNodeBubbles, defaultDrawTree, defaultHeight, defaultHideGaps, defaultLabelsAlignRight, defaultRowHeight, defaultScrollX, defaultScrollY, defaultShowBranchLen, defaultShowDomains, defaultSubFeatureRows, defaultTreeAreaWidth, defaultTreeWidth, } from "./constants.js";
15
13
  import { createPaletteMap } from "./createPaletteMap.js";
16
14
  import { flatToTree } from "./flatToTree.js";
15
+ import { clusterLayout, collapse, find, hierarchy, leaves, links, maxLength, setBrLength, sort, sum as hierarchySum, } from "./hierarchy.js";
17
16
  import { measureTextCanvas } from "./measureTextCanvas.js";
18
17
  import { DataModelF } from "./model/DataModel.js";
19
18
  import { DialogQueueSessionMixin } from "./model/DialogQueue.js";
@@ -21,12 +20,10 @@ import { MSAModelF } from "./model/msaModel.js";
21
20
  import { TreeModelF } from "./model/treeModel.js";
22
21
  import { calculateNeighborJoiningTree } from "./neighborJoining.js";
23
22
  import { parseAsn1 } from "./parseAsn1.js";
24
- import { reparseTree } from "./reparseTree.js";
25
23
  import { globalColToVisibleCol, visibleColToGlobalCol, visibleColToSeqPosForRow, } from "./rowCoordinateCalculations.js";
26
24
  import { seqPosToGlobalCol } from "./seqPosToGlobalCol.js";
27
- import { collapse, len, maxLength, setBrLength, skipBlanks } from "./util.js";
25
+ import { len, skipBlanks } from "./util.js";
28
26
  import { saveAs } from "./vendor/fileSaver.js";
29
- const showZoomStarKey = 'msa-showZoomStar';
30
27
  /**
31
28
  * #stateModel MsaView
32
29
  * extends
@@ -54,10 +51,6 @@ function stateModelFactory() {
54
51
  * #property
55
52
  */
56
53
  allowedGappyness: defaultAllowedGappyness,
57
- /**
58
- * #property
59
- */
60
- contrastLettering: defaultContrastLettering,
61
54
  /**
62
55
  * #property
63
56
  */
@@ -119,7 +112,6 @@ function stateModelFactory() {
119
112
  gffFilehandle: types.maybe(FileLocation),
120
113
  /**
121
114
  * #property
122
- *
123
115
  */
124
116
  currentAlignment: defaultCurrentAlignment,
125
117
  /**
@@ -128,12 +120,6 @@ function stateModelFactory() {
128
120
  * hidden)
129
121
  */
130
122
  collapsed: types.array(types.string),
131
- /**
132
- * #property
133
- * array of tree leaf nodes that are 'collapsed' (just that leaf node
134
- * is hidden)
135
- */
136
- collapsedLeaves: types.array(types.string),
137
123
  /**
138
124
  * #property
139
125
  * focus on particular subtree
@@ -178,11 +164,6 @@ function stateModelFactory() {
178
164
  * screens
179
165
  */
180
166
  highResScaleFactor: 2,
181
- /**
182
- * #volatile
183
- * obtained from localStorage
184
- */
185
- showZoomStar: localStorageGetBoolean(showZoomStarKey, false),
186
167
  /**
187
168
  * #volatile
188
169
  */
@@ -256,11 +237,8 @@ function stateModelFactory() {
256
237
  /**
257
238
  * #volatile
258
239
  */
240
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
259
241
  error: undefined,
260
- /**
261
- * #volatile
262
- */
263
- annotPos: undefined,
264
242
  /**
265
243
  * #volatile
266
244
  */
@@ -285,24 +263,12 @@ function stateModelFactory() {
285
263
  setAllowedGappyness(arg) {
286
264
  self.allowedGappyness = arg;
287
265
  },
288
- /**
289
- * #action
290
- */
291
- setContrastLettering(arg) {
292
- self.contrastLettering = arg;
293
- },
294
266
  /**
295
267
  * #action
296
268
  */
297
269
  setLoadingMSA(arg) {
298
270
  self.loadingMSA = arg;
299
271
  },
300
- /**
301
- * #action
302
- */
303
- setShowZoomStar(arg) {
304
- self.showZoomStar = arg;
305
- },
306
272
  /**
307
273
  * #action
308
274
  */
@@ -346,14 +312,12 @@ function stateModelFactory() {
346
312
  self.hoveredTreeNode = undefined;
347
313
  return;
348
314
  }
349
- // Find the node in the hierarchy
350
- const node = self.hierarchy.find(n => n.data.id === nodeId);
315
+ const node = find(self.hierarchy, n => n.data.id === nodeId);
351
316
  if (!node) {
352
317
  self.hoveredTreeNode = undefined;
353
318
  return;
354
319
  }
355
- // Get all descendant leaf names
356
- const descendantNames = node.leaves().map(leaf => leaf.data.name);
320
+ const descendantNames = leaves(node).map(leaf => leaf.data.name);
357
321
  self.hoveredTreeNode = { nodeId, descendantNames };
358
322
  },
359
323
  /**
@@ -422,17 +386,6 @@ function stateModelFactory() {
422
386
  self.collapsed.push(node);
423
387
  }
424
388
  },
425
- /**
426
- * #action
427
- */
428
- toggleCollapsedLeaf(node) {
429
- if (self.collapsedLeaves.includes(node)) {
430
- self.collapsedLeaves.remove(node);
431
- }
432
- else {
433
- self.collapsedLeaves.push(node);
434
- }
435
- },
436
389
  /**
437
390
  * #action
438
391
  */
@@ -489,9 +442,7 @@ function stateModelFactory() {
489
442
  */
490
443
  get hideGapsEffective() {
491
444
  return (self.hideGaps &&
492
- (self.collapsed.length > 0 ||
493
- self.collapsedLeaves.length > 0 ||
494
- self.allowedGappyness < 100));
445
+ (self.collapsed.length > 0 || self.allowedGappyness < 100));
495
446
  },
496
447
  /**
497
448
  * #getter
@@ -505,9 +456,6 @@ function stateModelFactory() {
505
456
  get actuallyShowDomains() {
506
457
  return self.showDomains && !!self.interProAnnotations;
507
458
  },
508
- /**
509
- * #getter
510
- */
511
459
  get viewInitialized() {
512
460
  return self.volatileWidth !== undefined;
513
461
  },
@@ -539,7 +487,7 @@ function stateModelFactory() {
539
487
  * #getter
540
488
  */
541
489
  get header() {
542
- return this.MSA?.getHeader() || {};
490
+ return (this.MSA?.getHeader() || {});
543
491
  },
544
492
  /**
545
493
  * #getter
@@ -553,15 +501,9 @@ function stateModelFactory() {
553
501
  get noTree() {
554
502
  return !!this.tree.noTree;
555
503
  },
556
- /**
557
- * #getter
558
- */
559
504
  get noDomains() {
560
505
  return !self.interProAnnotations;
561
506
  },
562
- /**
563
- * #getter
564
- */
565
507
  menuItems() {
566
508
  return [];
567
509
  },
@@ -576,24 +518,9 @@ function stateModelFactory() {
576
518
  */
577
519
  get MSA() {
578
520
  const text = self.data.msa;
579
- if (text) {
580
- if (stockholmSniff(text)) {
581
- return new StockholmMSA(text, self.currentAlignment);
582
- }
583
- else if (A3mMSA.sniff(text)) {
584
- return new A3mMSA(text);
585
- }
586
- else if (text.startsWith('>')) {
587
- return new FastaMSA(text);
588
- }
589
- else if (text.startsWith('SEQ')) {
590
- return new EmfMSA(text);
591
- }
592
- else {
593
- return new ClustalMSA(text);
594
- }
595
- }
596
- return null;
521
+ // uses parseMSA so the named MSAParserType return type is portable
522
+ // to downstream consumers (avoids TS2883 with default exports)
523
+ return text ? parseMSA(text, self.currentAlignment) : null;
597
524
  },
598
525
  /**
599
526
  * #getter
@@ -606,7 +533,7 @@ function stateModelFactory() {
606
533
  */
607
534
  get tree() {
608
535
  const text = self.data.tree;
609
- return reparseTree(text
536
+ return text
610
537
  ? generateNodeIds(text.startsWith('BioTreeContainer')
611
538
  ? flatToTree(parseAsn1(text))
612
539
  : parseNewick(text.startsWith('SEQ') ? parseEmfTree(text).tree : text))
@@ -615,7 +542,7 @@ function stateModelFactory() {
615
542
  children: [],
616
543
  id: 'empty',
617
544
  name: 'empty',
618
- });
545
+ };
619
546
  },
620
547
  /**
621
548
  * #getter
@@ -661,25 +588,27 @@ function stateModelFactory() {
661
588
  * #getter
662
589
  */
663
590
  get root() {
664
- let hier = hierarchy(this.tree, d => d.children)
665
- // todo: investigate whether needed, typescript says children always true
666
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
667
- .sum(d => (d.children ? 0 : 1))
668
- // eslint-disable-next-line unicorn/no-array-sort
669
- .sort((a, b) => ascending(a.data.length || 1, b.data.length || 1));
591
+ let hier = hierarchy(this.tree, d => d.children);
592
+ hierarchySum(hier, d => (d.children.length > 0 ? 0 : 1));
593
+ sort(hier, (a, b) => (a.data.length ?? 1) - (b.data.length ?? 1));
670
594
  if (self.showOnly) {
671
- const res = hier.find(n => n.data.id === self.showOnly);
595
+ const res = find(hier, n => n.data.id === self.showOnly);
672
596
  if (res) {
673
597
  hier = res;
674
598
  }
675
599
  }
676
- ;
677
- [...self.collapsed, ...self.collapsedLeaves]
678
- .map(collapsedId => hier.find(node => node.data.id === collapsedId))
679
- .filter(notEmpty)
680
- .forEach(node => {
681
- collapse(node);
682
- });
600
+ for (const collapsedId of self.collapsed) {
601
+ const node = find(hier, n => n.data.id === collapsedId);
602
+ if (!node) {
603
+ continue;
604
+ }
605
+ if (node.children) {
606
+ collapse(node);
607
+ }
608
+ else if (node.parent?.children) {
609
+ node.parent.children = node.parent.children.filter(c => c.data.id !== collapsedId);
610
+ }
611
+ }
683
612
  return hier;
684
613
  },
685
614
  /**
@@ -1072,30 +1001,28 @@ function stateModelFactory() {
1072
1001
  */
1073
1002
  get hierarchy() {
1074
1003
  const r = this.root;
1075
- const clust = cluster()
1076
- .size([this.totalHeight, self.treeWidth])
1077
- .separation(() => 1);
1078
- clust(r);
1079
- setBrLength(r, (r.data.length = 0), self.treeWidth / maxLength(r));
1004
+ clusterLayout(r, this.totalHeight, self.treeWidth);
1005
+ r.data.length = 0;
1006
+ setBrLength(r, 0, self.treeWidth / maxLength(r));
1080
1007
  return r;
1081
1008
  },
1082
1009
  /**
1083
1010
  * #getter
1084
1011
  */
1085
1012
  get totalHeight() {
1086
- return this.root.leaves().length * self.rowHeight;
1013
+ return leaves(this.root).length * self.rowHeight;
1087
1014
  },
1088
1015
  /**
1089
1016
  * #getter
1090
1017
  */
1091
1018
  get leaves() {
1092
- return this.hierarchy.leaves();
1019
+ return leaves(this.hierarchy);
1093
1020
  },
1094
1021
  /**
1095
1022
  * #getter
1096
1023
  */
1097
1024
  get allBranchesLength0() {
1098
- return this.hierarchy.links().every(s => !s.source.data.length);
1025
+ return links(this.hierarchy).every(s => !s.source.data.length);
1099
1026
  },
1100
1027
  /**
1101
1028
  * #getter
@@ -1148,13 +1075,7 @@ function stateModelFactory() {
1148
1075
  * #getter
1149
1076
  */
1150
1077
  get blocks2d() {
1151
- const ret = [];
1152
- for (const by of self.blocksY) {
1153
- for (const bx of self.blocksX) {
1154
- ret.push([bx, by]);
1155
- }
1156
- }
1157
- return ret;
1078
+ return self.blocksY.flatMap(by => self.blocksX.map(bx => [bx, by]));
1158
1079
  },
1159
1080
  /**
1160
1081
  * #getter
@@ -1257,14 +1178,17 @@ function stateModelFactory() {
1257
1178
  /**
1258
1179
  * #action
1259
1180
  */
1181
+ doScrollY(deltaY) {
1182
+ self.scrollY = clamp(self.scrollY + deltaY, -self.totalHeight + 10, 0);
1183
+ },
1260
1184
  setInterProAnnotations(data) {
1261
1185
  self.interProAnnotations = data;
1262
1186
  },
1263
- /**
1264
- * #action
1265
- */
1266
- doScrollY(deltaY) {
1267
- self.scrollY = clamp(self.scrollY + deltaY, -self.totalHeight + 10, 0);
1187
+ applyGFFText(gffText) {
1188
+ const gffRecords = parseGFF(gffText);
1189
+ const interProResults = gffToInterProResults(gffRecords);
1190
+ self.interProAnnotations = interProResults;
1191
+ self.setShowDomains(true);
1268
1192
  },
1269
1193
  /**
1270
1194
  * #action
@@ -1301,14 +1225,11 @@ function stateModelFactory() {
1301
1225
  * #getter
1302
1226
  */
1303
1227
  get labelsWidth() {
1304
- let x = 0;
1305
1228
  const { rowHeight, leaves, treeMetadata, fontSize } = self;
1306
- if (rowHeight > 5) {
1307
- for (const node of leaves) {
1308
- x = Math.max(measureTextCanvas(treeMetadata[node.data.name]?.genome || node.data.name, fontSize), x);
1309
- }
1229
+ if (rowHeight <= 5) {
1230
+ return 0;
1310
1231
  }
1311
- return x;
1232
+ return leaves.reduce((max, node) => Math.max(max, measureTextCanvas(treeMetadata[node.data.name]?.genome || node.data.name, fontSize)), 0);
1312
1233
  },
1313
1234
  /**
1314
1235
  * #getter
@@ -1327,16 +1248,19 @@ function stateModelFactory() {
1327
1248
  */
1328
1249
  get adapterTrackModels() {
1329
1250
  const { rowHeight, MSA, hideGapsEffective, blanks } = self;
1330
- return (MSA?.tracks
1331
- .filter(t => t.data)
1251
+ const tracks = (MSA?.tracks ?? []);
1252
+ return tracks
1253
+ .filter(t => !!t.data)
1332
1254
  .map(t => ({
1333
1255
  model: {
1334
1256
  ...t,
1335
- data: hideGapsEffective ? skipBlanks(blanks, t.data) : t.data,
1257
+ data: hideGapsEffective && t.data
1258
+ ? skipBlanks(blanks, t.data)
1259
+ : t.data,
1336
1260
  height: rowHeight,
1337
1261
  },
1338
1262
  ReactComponent: TextTrack,
1339
- })) || []);
1263
+ }));
1340
1264
  },
1341
1265
  /**
1342
1266
  * #getter
@@ -1500,53 +1424,30 @@ function stateModelFactory() {
1500
1424
  get totalTrackAreaHeight() {
1501
1425
  return sum(self.turnedOnTracks.map(r => r.model.height));
1502
1426
  },
1503
- /**
1504
- * #getter
1505
- */
1506
1427
  get tidyInterProAnnotationTypes() {
1507
- const types = new Map();
1508
- for (const annot of this.tidyInterProAnnotations) {
1509
- types.set(annot.accession, annot);
1510
- }
1511
- return types;
1428
+ return new Map(this.tidyInterProAnnotations.map(annot => [annot.accession, annot]));
1512
1429
  },
1513
- /**
1514
- * #getter
1515
- */
1516
1430
  get tidyInterProAnnotations() {
1517
- const ret = [];
1518
1431
  const { interProAnnotations } = self;
1519
- if (interProAnnotations) {
1520
- for (const [id, val] of Object.entries(interProAnnotations)) {
1521
- for (const { signature, locations } of val.matches) {
1522
- const { entry } = signature;
1523
- if (entry) {
1524
- const { name, accession, description } = entry;
1525
- for (const { start, end } of locations) {
1526
- ret.push({
1527
- id,
1528
- name,
1529
- accession,
1530
- description,
1531
- start,
1532
- end,
1533
- });
1534
- }
1535
- }
1536
- }
1537
- }
1432
+ if (!interProAnnotations) {
1433
+ return [];
1538
1434
  }
1539
- return ret.toSorted((a, b) => len(b) - len(a));
1435
+ return Object.entries(interProAnnotations)
1436
+ .flatMap(([id, val]) => val.matches.flatMap(({ signature, locations }) => signature.entry
1437
+ ? locations.map(({ start, end }) => ({
1438
+ id,
1439
+ name: signature.entry.name,
1440
+ accession: signature.entry.accession,
1441
+ description: signature.entry.description,
1442
+ start,
1443
+ end,
1444
+ }))
1445
+ : []))
1446
+ .toSorted((a, b) => len(b) - len(a));
1540
1447
  },
1541
- /**
1542
- * #getter
1543
- */
1544
1448
  get tidyFilteredInterProAnnotations() {
1545
1449
  return this.tidyInterProAnnotations.filter(r => self.featureFilters.get(r.accession));
1546
1450
  },
1547
- /**
1548
- * #getter
1549
- */
1550
1451
  get tidyFilteredGatheredInterProAnnotations() {
1551
1452
  return groupBy(this.tidyFilteredInterProAnnotations, r => r.id);
1552
1453
  },
@@ -1566,15 +1467,9 @@ function stateModelFactory() {
1566
1467
  get verticalScrollbarWidth() {
1567
1468
  return self.showVerticalScrollbar ? 20 : 0;
1568
1469
  },
1569
- /**
1570
- * #getter
1571
- */
1572
1470
  get fillPalette() {
1573
1471
  return createPaletteMap([...self.tidyInterProAnnotationTypes.keys()]);
1574
1472
  },
1575
- /**
1576
- * #getter
1577
- */
1578
1473
  get strokePalette() {
1579
1474
  return Object.fromEntries(Object.entries(this.fillPalette).map(([key, val]) => [
1580
1475
  key,
@@ -1619,6 +1514,9 @@ function stateModelFactory() {
1619
1514
  self.setCurrentAlignment(0);
1620
1515
  self.setTreeFilehandle(undefined);
1621
1516
  self.setMSAFilehandle(undefined);
1517
+ self.setGFFFilehandle(undefined);
1518
+ self.setInterProAnnotations({});
1519
+ self.setShowDomains(false);
1622
1520
  },
1623
1521
  /**
1624
1522
  * #action
@@ -1636,18 +1534,12 @@ function stateModelFactory() {
1636
1534
  incrementRef() {
1637
1535
  self.nref++;
1638
1536
  },
1639
- /**
1640
- * #action
1641
- */
1642
1537
  initFilter(arg) {
1643
1538
  const ret = self.featureFilters.get(arg);
1644
1539
  if (ret === undefined) {
1645
1540
  self.featureFilters.set(arg, true);
1646
1541
  }
1647
1542
  },
1648
- /**
1649
- * #action
1650
- */
1651
1543
  setFilter(arg, flag) {
1652
1544
  self.featureFilters.set(arg, flag);
1653
1545
  },
@@ -1680,10 +1572,6 @@ function stateModelFactory() {
1680
1572
  this.initFilter(key);
1681
1573
  }
1682
1574
  }));
1683
- // autorun saves local settings
1684
- addDisposer(self, autorun(() => {
1685
- localStorageSetBoolean(showZoomStarKey, self.showZoomStar);
1686
- }));
1687
1575
  // autorun opens treeFilehandle
1688
1576
  addDisposer(self, autorun(async () => {
1689
1577
  const { treeFilehandle } = self;
@@ -1718,16 +1606,26 @@ function stateModelFactory() {
1718
1606
  }
1719
1607
  }
1720
1608
  }));
1609
+ // autorun parses inline gff text from data.gff
1610
+ addDisposer(self, autorun(() => {
1611
+ const gffText = self.data.gff;
1612
+ if (gffText) {
1613
+ try {
1614
+ self.applyGFFText(gffText);
1615
+ }
1616
+ catch (e) {
1617
+ console.error(e);
1618
+ self.setError(e);
1619
+ }
1620
+ }
1621
+ }));
1721
1622
  // autorun opens gffFilehandle for InterProScan domains
1722
1623
  addDisposer(self, autorun(async () => {
1723
1624
  const { gffFilehandle } = self;
1724
1625
  if (gffFilehandle) {
1725
1626
  try {
1726
1627
  const gffText = await fetchAndMaybeUnzipText(openLocation(gffFilehandle));
1727
- const gffRecords = parseGFF(gffText);
1728
- const interProResults = gffToInterProResults(gffRecords);
1729
- self.setInterProAnnotations(interProResults);
1730
- self.setShowDomains(true);
1628
+ self.applyGFFText(gffText);
1731
1629
  if (gffFilehandle.locationType === 'BlobLocation') {
1732
1630
  self.setGFFFilehandle(undefined);
1733
1631
  }
@@ -1777,10 +1675,8 @@ function stateModelFactory() {
1777
1675
  self.columns;
1778
1676
  }));
1779
1677
  // autorun synchronizes treeWidth with treeAreaWidth
1780
- addDisposer(self, autorun(async () => {
1781
- if (self.treeWidthMatchesArea) {
1782
- self.setTreeWidth(Math.max(50, self.treeAreaWidth - self.labelsWidth - 10 - self.marginLeft));
1783
- }
1678
+ addDisposer(self, autorun(() => {
1679
+ self.setTreeWidth(Math.max(50, self.treeAreaWidth - self.labelsWidth - 10 - self.marginLeft));
1784
1680
  }));
1785
1681
  },
1786
1682
  }))
@@ -1788,15 +1684,38 @@ function stateModelFactory() {
1788
1684
  const snap = result;
1789
1685
  const { data: { tree, msa, treeMetadata },
1790
1686
  // Main model properties
1791
- showDomains, hideGaps, allowedGappyness, contrastLettering, subFeatureRows, drawMsaLetters, height, rowHeight, scrollY, scrollX, colWidth, currentAlignment, collapsed, collapsedLeaves, showOnly, turnedOffTracks, featureFilters, relativeTo,
1687
+ showDomains, hideGaps, allowedGappyness, subFeatureRows, drawMsaLetters, height, rowHeight, scrollY, scrollX, colWidth, currentAlignment, collapsed, showOnly, turnedOffTracks, featureFilters, relativeTo,
1792
1688
  // MSA model properties
1793
1689
  bgColor, colorSchemeName,
1794
1690
  // Tree model properties
1795
- drawLabels, labelsAlignRight, treeAreaWidth, treeWidth, treeWidthMatchesArea, showBranchLen, drawTree, drawNodeBubbles,
1691
+ drawLabels, labelsAlignRight, treeAreaWidth, treeWidth, showBranchLen, drawTree, drawNodeBubbles,
1796
1692
  // Always include
1797
1693
  ...rest } = snap;
1798
- // remove the MSA/tree data from the tree if the filehandle available in
1799
- // which case it can be reloaded on refresh
1694
+ const defaults = {
1695
+ showDomains: defaultShowDomains,
1696
+ hideGaps: defaultHideGaps,
1697
+ allowedGappyness: defaultAllowedGappyness,
1698
+ subFeatureRows: defaultSubFeatureRows,
1699
+ drawMsaLetters: defaultDrawMsaLetters,
1700
+ height: defaultHeight,
1701
+ rowHeight: defaultRowHeight,
1702
+ scrollY: defaultScrollY,
1703
+ scrollX: defaultScrollX,
1704
+ colWidth: defaultColWidth,
1705
+ currentAlignment: defaultCurrentAlignment,
1706
+ bgColor: defaultBgColor,
1707
+ colorSchemeName: defaultColorSchemeName,
1708
+ drawLabels: defaultDrawLabels,
1709
+ labelsAlignRight: defaultLabelsAlignRight,
1710
+ treeAreaWidth: defaultTreeAreaWidth,
1711
+ treeWidth: defaultTreeWidth,
1712
+ showBranchLen: defaultShowBranchLen,
1713
+ drawTree: defaultDrawTree,
1714
+ drawNodeBubbles: defaultDrawNodeBubbles,
1715
+ };
1716
+ const nonDefaults = Object.fromEntries(Object.entries(defaults)
1717
+ .filter(([key, def]) => snap[key] !== def)
1718
+ .map(([key]) => [key, snap[key]]));
1800
1719
  return {
1801
1720
  ...rest,
1802
1721
  data: {
@@ -1804,29 +1723,9 @@ function stateModelFactory() {
1804
1723
  ...(result.msaFilehandle ? {} : { msa }),
1805
1724
  ...(result.treeMetadataFilehandle ? {} : { treeMetadata }),
1806
1725
  },
1807
- // Main model - only include non-default values
1808
- ...(showDomains !== defaultShowDomains ? { showDomains } : {}),
1809
- ...(hideGaps !== defaultHideGaps ? { hideGaps } : {}),
1810
- ...(allowedGappyness !== defaultAllowedGappyness
1811
- ? { allowedGappyness }
1812
- : {}),
1813
- ...(contrastLettering !== defaultContrastLettering
1814
- ? { contrastLettering }
1815
- : {}),
1816
- ...(subFeatureRows !== defaultSubFeatureRows ? { subFeatureRows } : {}),
1817
- ...(drawMsaLetters !== defaultDrawMsaLetters ? { drawMsaLetters } : {}),
1818
- ...(height !== defaultHeight ? { height } : {}),
1819
- ...(rowHeight !== defaultRowHeight ? { rowHeight } : {}),
1820
- ...(scrollY !== defaultScrollY ? { scrollY } : {}),
1821
- ...(scrollX !== defaultScrollX ? { scrollX } : {}),
1822
- ...(colWidth !== defaultColWidth ? { colWidth } : {}),
1823
- ...(currentAlignment !== defaultCurrentAlignment
1824
- ? { currentAlignment }
1825
- : {}),
1726
+ ...nonDefaults,
1826
1727
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1827
1728
  ...(collapsed?.length ? { collapsed } : {}),
1828
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1829
- ...(collapsedLeaves?.length ? { collapsedLeaves } : {}),
1830
1729
  ...(showOnly !== undefined ? { showOnly } : {}),
1831
1730
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1832
1731
  ...(turnedOffTracks && Object.keys(turnedOffTracks).length > 0
@@ -1837,26 +1736,6 @@ function stateModelFactory() {
1837
1736
  ? { featureFilters }
1838
1737
  : {}),
1839
1738
  ...(relativeTo !== undefined ? { relativeTo } : {}),
1840
- // MSA model - only include non-default values
1841
- ...(bgColor !== defaultBgColor ? { bgColor } : {}),
1842
- ...(colorSchemeName !== defaultColorSchemeName
1843
- ? { colorSchemeName }
1844
- : {}),
1845
- // Tree model - only include non-default values
1846
- ...(drawLabels !== defaultDrawLabels ? { drawLabels } : {}),
1847
- ...(labelsAlignRight !== defaultLabelsAlignRight
1848
- ? { labelsAlignRight }
1849
- : {}),
1850
- ...(treeAreaWidth !== defaultTreeAreaWidth ? { treeAreaWidth } : {}),
1851
- ...(treeWidth !== defaultTreeWidth ? { treeWidth } : {}),
1852
- ...(treeWidthMatchesArea !== defaultTreeWidthMatchesArea
1853
- ? { treeWidthMatchesArea }
1854
- : {}),
1855
- ...(showBranchLen !== defaultShowBranchLen ? { showBranchLen } : {}),
1856
- ...(drawTree !== defaultDrawTree ? { drawTree } : {}),
1857
- ...(drawNodeBubbles !== defaultDrawNodeBubbles
1858
- ? { drawNodeBubbles }
1859
- : {}),
1860
1739
  };
1861
1740
  });
1862
1741
  }