react-msaview 9.0.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/README.md +19 -10
  2. package/bundle/index.js +74 -74
  3. package/bundle/index.js.map +4 -4
  4. package/dist/colorSchemes.js +1 -3
  5. package/dist/colorSchemes.js.map +1 -1
  6. package/dist/columnCounts.d.ts +0 -1
  7. package/dist/columnCounts.js +1 -1
  8. package/dist/columnCounts.js.map +1 -1
  9. package/dist/components/Loading.js +5 -5
  10. package/dist/components/Loading.js.map +1 -1
  11. package/dist/components/MSAViewer.d.ts +1891 -4
  12. package/dist/components/MSAViewer.js +144 -201
  13. package/dist/components/MSAViewer.js.map +1 -1
  14. package/dist/components/ResizeHandles.js +3 -2
  15. package/dist/components/ResizeHandles.js.map +1 -1
  16. package/dist/components/Track.js +2 -1
  17. package/dist/components/Track.js.map +1 -1
  18. package/dist/components/controls.d.ts +11 -0
  19. package/dist/components/controls.js +48 -0
  20. package/dist/components/controls.js.map +1 -0
  21. package/dist/components/dialogs/AnnotationFileDialog.js +5 -16
  22. package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -1
  23. package/dist/components/dialogs/ExportSVGDialog.js +5 -7
  24. package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
  25. package/dist/components/dialogs/HelpDialog.d.ts +6 -0
  26. package/dist/components/dialogs/HelpDialog.js +30 -0
  27. package/dist/components/dialogs/HelpDialog.js.map +1 -0
  28. package/dist/components/header/FileMenu.js +33 -7
  29. package/dist/components/header/FileMenu.js.map +1 -1
  30. package/dist/components/header/Header.js +4 -6
  31. package/dist/components/header/Header.js.map +1 -1
  32. package/dist/components/header/TreeSettingsMenu.d.ts +1 -1
  33. package/dist/components/header/TreeSettingsMenu.js +1 -32
  34. package/dist/components/header/TreeSettingsMenu.js.map +1 -1
  35. package/dist/components/header/ZoomControls.js +28 -44
  36. package/dist/components/header/ZoomControls.js.map +1 -1
  37. package/dist/components/header/settingsMenuItems.d.ts +1 -0
  38. package/dist/components/header/settingsMenuItems.js +44 -0
  39. package/dist/components/header/settingsMenuItems.js.map +1 -1
  40. package/dist/components/import/data/seq2.d.ts +0 -1
  41. package/dist/components/import/data/seq2.js +0 -17
  42. package/dist/components/import/data/seq2.js.map +1 -1
  43. package/dist/components/msa/MSACanvas.js +2 -1
  44. package/dist/components/msa/MSACanvas.js.map +1 -1
  45. package/dist/components/msa/MSACanvasBlock.js +2 -5
  46. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  47. package/dist/components/msa/drawFeatureSpans.d.ts +50 -11
  48. package/dist/components/msa/drawFeatureSpans.js +99 -56
  49. package/dist/components/msa/drawFeatureSpans.js.map +1 -1
  50. package/dist/components/msa/msaRaster.d.ts +14 -1
  51. package/dist/components/msa/msaRaster.js +19 -9
  52. package/dist/components/msa/msaRaster.js.map +1 -1
  53. package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +9 -0
  54. package/dist/components/msa/renderBoxFeatureCanvasBlock.js +19 -8
  55. package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
  56. package/dist/components/msa/renderHighlights.d.ts +2 -2
  57. package/dist/components/msa/renderHighlights.js +2 -3
  58. package/dist/components/msa/renderHighlights.js.map +1 -1
  59. package/dist/components/msa/renderMSAMouseover.js +2 -1
  60. package/dist/components/msa/renderMSAMouseover.js.map +1 -1
  61. package/dist/components/renderCtx.d.ts +1 -1
  62. package/dist/components/rowpanels/renderFeaturePanel.d.ts +5 -1
  63. package/dist/components/rowpanels/renderFeaturePanel.js +12 -5
  64. package/dist/components/rowpanels/renderFeaturePanel.js.map +1 -1
  65. package/dist/components/tracks/drawTracks.d.ts +1 -53
  66. package/dist/components/tracks/drawTracks.js +5 -5
  67. package/dist/components/tracks/drawTracks.js.map +1 -1
  68. package/dist/components/tree/CladeLabels.d.ts +1 -1
  69. package/dist/components/tree/CladeLabels.js +22 -34
  70. package/dist/components/tree/CladeLabels.js.map +1 -1
  71. package/dist/components/tree/cladeBrackets.d.ts +17 -10
  72. package/dist/components/tree/cladeBrackets.js +33 -3
  73. package/dist/components/tree/cladeBrackets.js.map +1 -1
  74. package/dist/components/tree/renderTreeCanvas.js +2 -8
  75. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  76. package/dist/components/tree/renderTreeOverview.js +4 -11
  77. package/dist/components/tree/renderTreeOverview.js.map +1 -1
  78. package/dist/element.js +3 -6
  79. package/dist/element.js.map +1 -1
  80. package/dist/exportLayout.d.ts +39 -0
  81. package/dist/exportLayout.js +78 -0
  82. package/dist/exportLayout.js.map +1 -0
  83. package/dist/headlessRenderEnv.d.ts +12 -39
  84. package/dist/headlessRenderEnv.js +18 -12
  85. package/dist/headlessRenderEnv.js.map +1 -1
  86. package/dist/hierarchy.d.ts +5 -8
  87. package/dist/hierarchy.js +5 -79
  88. package/dist/hierarchy.js.map +1 -1
  89. package/dist/index.d.ts +3 -1
  90. package/dist/index.js +4 -1
  91. package/dist/index.js.map +1 -1
  92. package/dist/measureTextCanvas.d.ts +7 -0
  93. package/dist/measureTextCanvas.js +8 -6
  94. package/dist/measureTextCanvas.js.map +1 -1
  95. package/dist/model/treeModel.d.ts +0 -5
  96. package/dist/model/treeModel.js +0 -7
  97. package/dist/model/treeModel.js.map +1 -1
  98. package/dist/model.d.ts +38 -44
  99. package/dist/model.js +153 -112
  100. package/dist/model.js.map +1 -1
  101. package/dist/renderToSvg.d.ts +2 -7
  102. package/dist/renderToSvg.js +32 -120
  103. package/dist/renderToSvg.js.map +1 -1
  104. package/dist/svgTestUtil.d.ts +12 -20
  105. package/dist/types.d.ts +0 -1
  106. package/dist/version.d.ts +1 -1
  107. package/dist/version.js +1 -1
  108. package/dist/viewerSnapshot.d.ts +169 -0
  109. package/dist/viewerSnapshot.js +100 -0
  110. package/dist/viewerSnapshot.js.map +1 -0
  111. package/package.json +3 -3
  112. package/src/colorSchemes.ts +1 -4
  113. package/src/columnCounts.ts +1 -1
  114. package/src/components/Loading.tsx +19 -12
  115. package/src/components/MSAViewer.tsx +275 -251
  116. package/src/components/ResizeHandles.tsx +9 -2
  117. package/src/components/Track.tsx +2 -1
  118. package/src/components/controls.ts +61 -0
  119. package/src/components/dialogs/AnnotationFileDialog.tsx +5 -21
  120. package/src/components/dialogs/ExportSVGDialog.tsx +8 -19
  121. package/src/components/dialogs/HelpDialog.tsx +79 -0
  122. package/src/components/header/FileMenu.tsx +33 -7
  123. package/src/components/header/Header.tsx +4 -6
  124. package/src/components/header/TreeSettingsMenu.tsx +2 -32
  125. package/src/components/header/ZoomControls.tsx +30 -66
  126. package/src/components/header/settingsMenuItems.ts +59 -0
  127. package/src/components/import/data/seq2.ts +0 -18
  128. package/src/components/msa/MSACanvas.tsx +2 -1
  129. package/src/components/msa/MSACanvasBlock.tsx +2 -6
  130. package/src/components/msa/drawFeatureSpans.ts +160 -79
  131. package/src/components/msa/msaRaster.ts +27 -9
  132. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +39 -24
  133. package/src/components/msa/renderHighlights.ts +2 -3
  134. package/src/components/msa/renderMSAMouseover.ts +5 -1
  135. package/src/components/renderCtx.ts +0 -1
  136. package/src/components/rowpanels/renderFeaturePanel.ts +26 -7
  137. package/src/components/tracks/drawTracks.ts +5 -5
  138. package/src/components/tree/CladeLabels.tsx +30 -54
  139. package/src/components/tree/cladeBrackets.ts +50 -3
  140. package/src/components/tree/renderTreeCanvas.ts +2 -13
  141. package/src/components/tree/renderTreeOverview.ts +7 -14
  142. package/src/element.ts +3 -7
  143. package/src/exportLayout.ts +134 -0
  144. package/src/headlessRenderEnv.ts +24 -11
  145. package/src/hierarchy.ts +5 -88
  146. package/src/index.ts +5 -5
  147. package/src/measureTextCanvas.ts +8 -6
  148. package/src/model/treeModel.ts +0 -7
  149. package/src/model.ts +170 -122
  150. package/src/renderToSvg.tsx +55 -194
  151. package/src/types.ts +0 -1
  152. package/src/version.ts +1 -1
  153. package/src/viewerSnapshot.ts +147 -0
  154. package/dist/components/header/ZoomMenu.d.ts +0 -6
  155. package/dist/components/header/ZoomMenu.js +0 -37
  156. package/dist/components/header/ZoomMenu.js.map +0 -1
  157. package/src/components/header/ZoomMenu.tsx +0 -48
@@ -163,13 +163,6 @@ export function TreeModelF() {
163
163
  setShowTreeOverview(arg: boolean) {
164
164
  self.showTreeOverview = arg
165
165
  },
166
- /**
167
- * #action
168
- * set the height of the tree overview band (px)
169
- */
170
- setOverviewHeight(n: number) {
171
- self.overviewHeight = Math.round(n)
172
- },
173
166
  /**
174
167
  * #action
175
168
  */
package/src/model.ts CHANGED
@@ -19,7 +19,6 @@ import {
19
19
  import {
20
20
  generateNodeIds,
21
21
  gffToAnnotations,
22
- interProScanToAnnotations,
23
22
  parseEmfTree,
24
23
  parseGFF,
25
24
  parseMSA,
@@ -34,7 +33,6 @@ import { columnCountsFromRows, letterOfResidueSlot } from './columnCounts.ts'
34
33
  import { columnStats } from './columnStats.ts'
35
34
  import { packDomainLanes } from './components/msa/packDomainLanes.ts'
36
35
  import { visibleColRange } from './components/msa/visibleColRange.ts'
37
- import TrackBlocks from './components/tracks/TrackBlocks.tsx'
38
36
  import { cladeGutterWidth } from './components/tree/cladeBrackets.ts'
39
37
  import { scaleBarLength } from './components/tree/scaleBar.ts'
40
38
  import {
@@ -83,8 +81,6 @@ import { featuresToAnnotations } from './featuresLayer.ts'
83
81
  import { fetchTextWithProgress, isAbortError } from './fetchUtils.ts'
84
82
  import { flatToTree } from './flatToTree.ts'
85
83
  import {
86
- calcDepthToLeaf,
87
- clusterLayout,
88
84
  collapse,
89
85
  find,
90
86
  forEachDescendant,
@@ -92,10 +88,8 @@ import {
92
88
  layoutTree,
93
89
  leafIndex,
94
90
  leaves,
95
- maxLength,
96
91
  mrca,
97
92
  nodeCoveringRows,
98
- setBrLength,
99
93
  sort,
100
94
  sum as hierarchySum,
101
95
  } from './hierarchy.ts'
@@ -179,7 +173,6 @@ import type {
179
173
  import type { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
180
174
  import type { Instance } from '@jbrowse/mobx-state-tree'
181
175
  import type { IComputedValue } from 'mobx'
182
- import type { InterProScanResults } from 'msa-parsers'
183
176
 
184
177
  function parseTreeText(text: string) {
185
178
  if (text.startsWith('BioTreeContainer')) {
@@ -559,40 +552,80 @@ const seedingMarks = new Set<CladeMark>(['collapse', 'focus', 'rotate'])
559
552
  const noDomains: Annotation[] = []
560
553
  const noClades: ResolvedClade[] = []
561
554
 
555
+ /**
556
+ * Each tip of the full tree in the order the tree draws it, with the on-screen
557
+ * row that shows it: its own, or the row of the collapsed node standing for it
558
+ */
559
+ function tipRows(root: HierarchyNode<NodeWithIds>) {
560
+ const rows = new Map<string, { order: number; row: number }>()
561
+ let row = 0
562
+ const pushReversed = (
563
+ stack: HierarchyNode<NodeWithIds>[],
564
+ nodes: HierarchyNode<NodeWithIds>[],
565
+ ) => {
566
+ for (let i = nodes.length - 1; i >= 0; i--) {
567
+ stack.push(nodes[i]!)
568
+ }
569
+ }
570
+ const stack = [root]
571
+ while (stack.length > 0) {
572
+ const node = stack.pop()!
573
+ if (node.children?.length) {
574
+ pushReversed(stack, node.children)
575
+ } else {
576
+ const hidden = [node]
577
+ while (hidden.length > 0) {
578
+ const tip = hidden.pop()!
579
+ const kids = tip.children ?? tip._children
580
+ if (kids?.length) {
581
+ pushReversed(hidden, kids)
582
+ } else {
583
+ rows.set(tip.data.name, { order: rows.size, row })
584
+ }
585
+ }
586
+ row++
587
+ }
588
+ }
589
+ return rows
590
+ }
591
+
562
592
  /**
563
593
  * The rows a clade covers and the node an `mrca` names, or undefined when the
564
594
  * clade does not resolve: a tip name the tree does not have or has twice, or a
565
- * leaf count `tips` disagrees with. `index` and `rowNamesSet` are the memoized
566
- * passes over the tree.
595
+ * leaf count `tips` disagrees with. A `range` counts its tips in the order
596
+ * `tips` gives them, so a collapsed clade inside it counts every leaf it
597
+ * stands for. `index` is the memoized leaf pass over the tree.
567
598
  */
568
599
  function cladeRows(
569
600
  clade: Clade,
570
601
  root: HierarchyNode<NodeWithIds>,
571
602
  index: Map<string, HierarchyNode<NodeWithIds> | undefined>,
572
- rowNamesSet: Map<string, number>,
603
+ tips: Map<string, { order: number; row: number }>,
573
604
  ): { rows: [number, number]; nodeId?: string } | undefined {
574
605
  if (clade.range) {
575
606
  const [a, b] = clade.range.map(name =>
576
- index.get(name) ? rowNamesSet.get(name) : undefined,
607
+ index.get(name) ? tips.get(name) : undefined,
577
608
  )
578
609
  if (a === undefined || b === undefined) {
579
610
  return undefined
580
611
  }
581
- const rows: [number, number] = a <= b ? [a, b] : [b, a]
582
- return rows[1] - rows[0] + 1 === clade.tips ? { rows } : undefined
612
+ const [first, last] = a.order <= b.order ? [a, b] : [b, a]
613
+ return last.order - first.order + 1 === clade.tips
614
+ ? { rows: [first.row, last.row] }
615
+ : undefined
583
616
  }
584
617
  const node = clade.mrca ? mrca(root, clade.mrca, index) : undefined
585
618
  if (!node) {
586
619
  return undefined
587
620
  }
588
- const tips = leaves(node)
589
- if (tips.length !== clade.tips) {
621
+ const nodeTips = leaves(node)
622
+ if (nodeTips.length !== clade.tips) {
590
623
  return undefined
591
624
  }
592
625
  let first = Infinity
593
626
  let last = -Infinity
594
- for (const tip of tips) {
595
- const row = rowNamesSet.get(tip.data.name)
627
+ for (const tip of nodeTips) {
628
+ const row = tips.get(tip.data.name)?.row
596
629
  if (row !== undefined) {
597
630
  first = Math.min(first, row)
598
631
  last = Math.max(last, row)
@@ -604,19 +637,21 @@ function cladeRows(
604
637
  }
605
638
 
606
639
  /**
607
- * Every clade that resolves, with its fill color settled, against the leaf
608
- * order `rowNamesSet` gives. The tree panel resolves against the displayed
609
- * rows and the overview against its own, so both take the tree they draw. A
610
- * `range` record names no node, so the seeding marks, which need one, drop it.
640
+ * Every clade that resolves, with its fill color settled, against the rows of
641
+ * `displayed`, the tree as drawn. The tree panel resolves against the
642
+ * displayed rows and the overview against its own, so both take the tree they
643
+ * draw. A `range` record names no node, so the seeding marks, which need one,
644
+ * drop it.
611
645
  */
612
646
  function resolveClades(
613
647
  clades: Clade[],
614
648
  root: HierarchyNode<NodeWithIds>,
615
- rowNamesSet: Map<string, number>,
649
+ displayed: HierarchyNode<NodeWithIds>,
616
650
  ): ResolvedClade[] {
617
651
  const index = leafIndex(root)
652
+ const tips = tipRows(displayed)
618
653
  return clades.flatMap(clade => {
619
- const resolved = cladeRows(clade, root, index, rowNamesSet)
654
+ const resolved = cladeRows(clade, root, index, tips)
620
655
  const seeding = seedingMarks.has(clade.mark)
621
656
  return resolved && !(seeding && resolved.nodeId === undefined)
622
657
  ? [
@@ -750,8 +785,8 @@ function rootTree(tree: NodeWithIds, treeRoot: TreeRoot) {
750
785
  : tree
751
786
  }
752
787
 
753
- // the inclusive tip indices a node covers, from the row-space extent
754
- // clusterLayout writes to xMin/xMax as tip centers
788
+ // the inclusive tip indices a node covers, from its xMin/xMax in a layout one
789
+ // row per tip
755
790
  function tipRange(node: HierarchyNode): [number, number] {
756
791
  return [Math.round(node.xMin! - 0.5), Math.round(node.xMax! - 0.5)]
757
792
  }
@@ -765,6 +800,16 @@ const featureChannels = new Set<EncodingChannel>([
765
800
  // seqPos -> column indexes per row, keyed on the parse so they are garbage
766
801
  // collected with it. A computed would rebuild every row's index when read
767
802
  // outside a reactive context.
803
+ // Newick tools write a colon in a name as an underscore, so a tree's row
804
+ // `chr1_100-200` is the GFF's `chr1:100-200`, as FastaMSA.getRow resolves it
805
+ function annotationRowName(id: string, rowNames: Map<string, number>) {
806
+ if (rowNames.has(id)) {
807
+ return id
808
+ }
809
+ const underscored = id.replaceAll(':', '_')
810
+ return rowNames.has(underscored) ? underscored : id
811
+ }
812
+
768
813
  const seqPosIndexCache = new WeakMap<object, Map<string, Int32Array>>()
769
814
 
770
815
  // A segment is a 1:1 run. A segment whose sides differ in length is malformed,
@@ -1096,9 +1141,10 @@ function stateModelFactory({
1096
1141
  /**
1097
1142
  * #property
1098
1143
  * where the view opens, in `highlights` coordinates: `{row, start,
1099
- * end}` zooms onto residues of that row, `{start, end}` onto alignment
1100
- * columns. Applied once the alignment and any tree file have loaded,
1101
- * then cleared, so a reloaded session keeps the reader's own scroll.
1144
+ * end}` zooms onto residues of that row and scrolls the row to the
1145
+ * middle, `{start, end}` zooms onto alignment columns. Applied once the
1146
+ * alignment and any tree file have loaded, then cleared, so a reloaded
1147
+ * session keeps the reader's own scroll.
1102
1148
  */
1103
1149
  region: types.frozen<Region | undefined>(),
1104
1150
  /**
@@ -1140,6 +1186,12 @@ function stateModelFactory({
1140
1186
  * of the snapshot so a link opened in the full app shows the toolbar.
1141
1187
  */
1142
1188
  hideHeader: false,
1189
+ /**
1190
+ * #volatile
1191
+ * shows the import form while the view has no data. MSAViewer turns it
1192
+ * off, since its host supplies the data and the form is the demo app's
1193
+ */
1194
+ importForm: true,
1143
1195
  /**
1144
1196
  * #volatile
1145
1197
  */
@@ -1651,20 +1703,18 @@ function stateModelFactory({
1651
1703
  .views(self => ({
1652
1704
  /**
1653
1705
  * #getter
1654
- * hideGaps takes effect when there are collapsed rows or allowedGappyness < 100
1706
+ * hideGaps takes effect when a collapse or a focus hides rows, whose
1707
+ * residues leave columns all gaps among the rest, or when
1708
+ * allowedGappyness is under 100
1655
1709
  */
1656
1710
  get hideGapsEffective() {
1657
1711
  return (
1658
1712
  self.hideGaps &&
1659
- (self.collapsed.length > 0 || self.allowedGappyness < 100)
1713
+ (self.collapsed.length > 0 ||
1714
+ self.showOnly !== undefined ||
1715
+ self.allowedGappyness < 100)
1660
1716
  )
1661
1717
  },
1662
- /**
1663
- * #getter
1664
- */
1665
- get realAllowedGappyness() {
1666
- return this.hideGapsEffective ? self.allowedGappyness : 100
1667
- },
1668
1718
  /**
1669
1719
  * #getter
1670
1720
  * the annotations the overlay, the legend and the encodings draw: the
@@ -1931,7 +1981,7 @@ function stateModelFactory({
1931
1981
  return resolveClades(
1932
1982
  self.clades,
1933
1983
  hierarchy(this.tree, d => d.children),
1934
- this.rowNamesSet,
1984
+ this.root,
1935
1985
  )
1936
1986
  },
1937
1987
  /**
@@ -2120,21 +2170,19 @@ function stateModelFactory({
2120
2170
  if (!self.showTreeOverview) {
2121
2171
  return undefined
2122
2172
  }
2123
- const root = buildTreeRoot(this.tree, {
2124
- collapsed: self.collapsed,
2125
- rotated: self.rotated,
2126
- order: self.treeOrder,
2127
- })
2128
- const numTips = leaves(root).length
2129
- clusterLayout(root, numTips, 1)
2130
- const rootLen = Math.max(root.data.length || 0, 0)
2131
- const extent = maxLength(root) - rootLen
2132
- setBrLength(root, -rootLen, extent ? 1 / extent : 0)
2173
+ const layout = layoutTree(
2174
+ buildTreeRoot(this.tree, {
2175
+ collapsed: self.collapsed,
2176
+ rotated: self.rotated,
2177
+ order: self.treeOrder,
2178
+ }),
2179
+ { rowHeight: 1, treeWidth: 1 },
2180
+ )
2133
2181
  return {
2134
- root,
2135
- numTips,
2136
- maxDepthToLeaf: calcDepthToLeaf(root),
2137
- showBranchLen: self.showBranchLen && extent > 0,
2182
+ root: layout.root,
2183
+ numTips: layout.leaves.length,
2184
+ maxDepthToLeaf: layout.root.depthToLeaf,
2185
+ showBranchLen: self.showBranchLen && layout.rootToTipLength > 0,
2138
2186
  }
2139
2187
  },
2140
2188
 
@@ -2148,10 +2196,11 @@ function stateModelFactory({
2148
2196
  if (!layout || self.clades.length === 0) {
2149
2197
  return noClades
2150
2198
  }
2151
- const rowNames = new Map(
2152
- leaves(layout.root).map((leaf, index) => [leaf.data.name, index]),
2199
+ return resolveClades(
2200
+ self.clades,
2201
+ hierarchy(this.tree, d => d.children),
2202
+ layout.root,
2153
2203
  )
2154
- return resolveClades(self.clades, layout.root, rowNames)
2155
2204
  },
2156
2205
 
2157
2206
  /**
@@ -2244,7 +2293,7 @@ function stateModelFactory({
2244
2293
  * #getter
2245
2294
  */
2246
2295
  get blanks() {
2247
- const { hideGapsEffective, realAllowedGappyness } = self
2296
+ const { hideGapsEffective, allowedGappyness } = self
2248
2297
  if (!hideGapsEffective) {
2249
2298
  return []
2250
2299
  }
@@ -2260,7 +2309,7 @@ function stateModelFactory({
2260
2309
  // row that stops early counts as gapped for the remainder
2261
2310
  const numCols = this.MSA?.getWidth() ?? 0
2262
2311
  const numRows = strs.length
2263
- const threshold = Math.ceil((realAllowedGappyness / 100) * numRows)
2312
+ const threshold = Math.ceil((allowedGappyness / 100) * numRows)
2264
2313
  const blankCounts = new Uint32Array(numCols)
2265
2314
  for (let j = 0; j < numRows; j++) {
2266
2315
  const str = strs[j]!
@@ -2344,11 +2393,19 @@ function stateModelFactory({
2344
2393
  return index
2345
2394
  },
2346
2395
 
2396
+ /**
2397
+ * #method
2398
+ * the row's aligned sequence by the name the tree gives it, including
2399
+ * rows a collapsed clade hides. `MSA.getRow` resolves a FASTA name whose
2400
+ * colon a Newick tool wrote as an underscore, which `rowMap` does not
2401
+ */
2402
+ rowSequence(name: string) {
2403
+ return this.MSA?.getRow(name) || undefined
2404
+ },
2347
2405
  /**
2348
2406
  * #getter
2349
- * every sequence in the alignment, keyed by row name, including rows a
2350
- * collapsed clade hides. `rows` holds only the rows on screen; lookups
2351
- * by row name use this
2407
+ * every sequence in the alignment, keyed by the alignment's own row
2408
+ * names, including rows a collapsed clade hides
2352
2409
  */
2353
2410
  get rowMap() {
2354
2411
  const MSA = this.MSA
@@ -2399,10 +2456,11 @@ function stateModelFactory({
2399
2456
  */
2400
2457
  get sequenceType(): 'dna' | 'rna' | 'amino' {
2401
2458
  const letters = this.colStats.lettersPresent
2402
- // isDna already excludes U (not in the DNA set) and isRna excludes T,
2403
- // so the set membership alone disambiguates the two
2404
- const dna = new Set(['A', 'C', 'G', 'T', 'N'])
2405
- const rna = new Set(['A', 'C', 'G', 'U', 'N'])
2459
+ // the two-base IUPAC codes keep an ambiguous read nucleotide; the
2460
+ // three-base ones (B, D, H, V) are common amino acids, so they don't
2461
+ const ambiguous = ['N', 'R', 'Y', 'K', 'M', 'S', 'W']
2462
+ const dna = new Set(['A', 'C', 'G', 'T', ...ambiguous])
2463
+ const rna = new Set(['A', 'C', 'G', 'U', ...ambiguous])
2406
2464
  const isDna = letters.size > 0 && [...letters].every(l => dna.has(l))
2407
2465
  const isRna = letters.size > 0 && [...letters].every(l => rna.has(l))
2408
2466
  return isDna ? 'dna' : isRna ? 'rna' : 'amino'
@@ -2536,15 +2594,6 @@ function stateModelFactory({
2536
2594
  return this.treeLayout.rootToTipLength
2537
2595
  },
2538
2596
 
2539
- /**
2540
- * #getter
2541
- * x-position of the farthest tip in a phylogram, px: effectiveTreeWidth,
2542
- * or 0 for a tree with no branch lengths (drawn as a cladogram)
2543
- */
2544
- get maxBranchLength() {
2545
- return this.rootToTipLength ? this.effectiveTreeWidth : 0
2546
- },
2547
-
2548
2597
  /**
2549
2598
  * #getter
2550
2599
  * pixels per unit of branch length in the phylogram layout, 0 in
@@ -2721,7 +2770,6 @@ function stateModelFactory({
2721
2770
  data: content?.data,
2722
2771
  arcs: content?.arcs,
2723
2772
  },
2724
- ReactComponent: TrackBlocks,
2725
2773
  }
2726
2774
  })
2727
2775
  columnTrackModels.set(track, computedModel)
@@ -2783,7 +2831,6 @@ function stateModelFactory({
2783
2831
  data: hideGapsEffective ? skipBlanks(blanks, t.data!) : t.data,
2784
2832
  height: rowHeight,
2785
2833
  },
2786
- ReactComponent: TrackBlocks,
2787
2834
  }))
2788
2835
  },
2789
2836
 
@@ -2806,7 +2853,6 @@ function stateModelFactory({
2806
2853
  height: self.trackHeight('arc'),
2807
2854
  arcs,
2808
2855
  },
2809
- ReactComponent: TrackBlocks,
2810
2856
  },
2811
2857
  ]
2812
2858
  : []
@@ -2854,7 +2900,6 @@ function stateModelFactory({
2854
2900
  heightKey: resizableKinds.has(model.kind) ? model.kind : undefined,
2855
2901
  height: self.trackHeight(model.kind),
2856
2902
  },
2857
- ReactComponent: TrackBlocks,
2858
2903
  }))
2859
2904
  },
2860
2905
 
@@ -2897,7 +2942,7 @@ function stateModelFactory({
2897
2942
  * @returns The character at that position
2898
2943
  */
2899
2944
  visibleColToRowLetter(rowName: string, visibleCol: number) {
2900
- return self.rowMap.get(rowName)?.[
2945
+ return self.rowSequence(rowName)?.[
2901
2946
  this.visibleColToGlobalCol(visibleCol)
2902
2947
  ]
2903
2948
  },
@@ -2982,7 +3027,7 @@ function stateModelFactory({
2982
3027
  * @returns The global column index in the full MSA, or undefined
2983
3028
  */
2984
3029
  seqPosToGlobalCol(rowName: string, seqPos: number) {
2985
- const seq = self.rowMap.get(rowName)
3030
+ const seq = self.rowSequence(rowName)
2986
3031
  if (seq === undefined) {
2987
3032
  return undefined
2988
3033
  }
@@ -3290,7 +3335,10 @@ function stateModelFactory({
3290
3335
  )
3291
3336
  },
3292
3337
  get annotationsByRow() {
3293
- return groupBy(this.filteredAnnotations, r => r.id)
3338
+ const { rowNamesSet } = self
3339
+ return groupBy(this.filteredAnnotations, r =>
3340
+ annotationRowName(r.id, rowNamesSet),
3341
+ )
3294
3342
  },
3295
3343
  }))
3296
3344
  .views(self => ({
@@ -3381,7 +3429,9 @@ function stateModelFactory({
3381
3429
  const { annotations, rowNamesSet } = self
3382
3430
  return self.dataInitialized &&
3383
3431
  annotations.length > 0 &&
3384
- !annotations.some(a => rowNamesSet.has(a.id))
3432
+ !annotations.some(a =>
3433
+ rowNamesSet.has(annotationRowName(a.id, rowNamesSet)),
3434
+ )
3385
3435
  ? [
3386
3436
  `0 of ${annotations.length} annotations name a row in this alignment`,
3387
3437
  ]
@@ -3521,10 +3571,13 @@ function stateModelFactory({
3521
3571
  * #action
3522
3572
  * swap in a different tree over the same alignment. Clears `collapsed`,
3523
3573
  * `rotated` and `showOnly`, since path-derived node ids (node-0-0-1) from the old
3524
- * tree would match unrelated nodes in the new one.
3574
+ * tree would match unrelated nodes in the new one, and the tree's
3575
+ * filehandle, which the snapshot would otherwise reopen in place of
3576
+ * the new tree
3525
3577
  */
3526
3578
  replaceTree(newick: string) {
3527
3579
  transaction(() => {
3580
+ self.setTreeFilehandle(undefined)
3528
3581
  self.collapsed.clear()
3529
3582
  self.rotated.clear()
3530
3583
  self.setShowOnly(undefined)
@@ -3552,30 +3605,6 @@ function stateModelFactory({
3552
3605
  axis,
3553
3606
  )
3554
3607
  },
3555
- /**
3556
- * #action
3557
- */
3558
- zoomOutHorizontal() {
3559
- this.zoomAtCenter(0.75, 'horizontal')
3560
- },
3561
- /**
3562
- * #action
3563
- */
3564
- zoomInHorizontal() {
3565
- this.zoomAtCenter(1.5, 'horizontal')
3566
- },
3567
- /**
3568
- * #action
3569
- */
3570
- zoomInVertical() {
3571
- this.zoomAtCenter(1.5, 'vertical')
3572
- },
3573
- /**
3574
- * #action
3575
- */
3576
- zoomOutVertical() {
3577
- this.zoomAtCenter(0.75, 'vertical')
3578
- },
3579
3608
  /**
3580
3609
  * #action
3581
3610
  */
@@ -3668,16 +3697,6 @@ function stateModelFactory({
3668
3697
  self.annotations = annotations
3669
3698
  },
3670
3699
 
3671
- /**
3672
- * #action
3673
- * set the overlay from raw InterProScan results keyed by row name. Kept
3674
- * for downstream plugins that hold the EBI wire format; new code should
3675
- * adapt to Annotation[] and call setAnnotations.
3676
- */
3677
- setDomains(data?: Record<string, InterProScanResults>) {
3678
- this.setAnnotations(data ? interProScanToAnnotations(data) : [])
3679
- },
3680
-
3681
3700
  applyGFFText(gffText: string) {
3682
3701
  this.setAnnotations(gffToAnnotations(parseGFF(gffText)))
3683
3702
  },
@@ -3765,7 +3784,7 @@ function stateModelFactory({
3765
3784
  column: column + 1,
3766
3785
  row,
3767
3786
  residue: self.visibleColToSeqPosOneBased(row, visibleCol),
3768
- letter: self.rowMap.get(row)?.[column],
3787
+ letter: self.rowSequence(row)?.[column],
3769
3788
  }
3770
3789
  },
3771
3790
  /**
@@ -3954,7 +3973,7 @@ function stateModelFactory({
3954
3973
  : visibleDomainTypes.map(d => ({
3955
3974
  id: d.accession,
3956
3975
  label: d.name,
3957
- color: fillPalette[d.accession]!,
3976
+ color: d.color ?? fillPalette[d.accession]!,
3958
3977
  })),
3959
3978
  'domains',
3960
3979
  )
@@ -4221,7 +4240,7 @@ function stateModelFactory({
4221
4240
  * to `end`, gaps included
4222
4241
  */
4223
4242
  get selectionFasta() {
4224
- const { rowNames, rowMap } = self
4243
+ const { rowNames } = self
4225
4244
  const resolved = this.resolvedSelection
4226
4245
  const span = this.selectionFileSpan
4227
4246
  if (!resolved || !span) {
@@ -4230,7 +4249,7 @@ function stateModelFactory({
4230
4249
  const rows = resolved.rowRuns
4231
4250
  .flatMap(([first, last]) => rowNames.slice(first, last + 1))
4232
4251
  .flatMap(name => {
4233
- const seq = rowMap.get(name)
4252
+ const seq = self.rowSequence(name)
4234
4253
  return seq === undefined ? [] : [[name, seq] as const]
4235
4254
  })
4236
4255
  return blockFasta(rows, span.start, span.end)
@@ -4327,7 +4346,7 @@ function stateModelFactory({
4327
4346
  this.visibleDomainTypes.map(d => ({
4328
4347
  id: d.accession,
4329
4348
  label: d.name,
4330
- color: fillPalette[d.accession]!,
4349
+ color: d.color ?? fillPalette[d.accession]!,
4331
4350
  })),
4332
4351
  }
4333
4352
  }
@@ -4504,6 +4523,12 @@ function stateModelFactory({
4504
4523
  setHideHeader(arg: boolean) {
4505
4524
  self.hideHeader = arg
4506
4525
  },
4526
+ /**
4527
+ * #action
4528
+ */
4529
+ setImportForm(arg: boolean) {
4530
+ self.importForm = arg
4531
+ },
4507
4532
  /**
4508
4533
  * #action
4509
4534
  * focus the subtree a click `y` pixels down the tree overview lands on.
@@ -4638,6 +4663,27 @@ function stateModelFactory({
4638
4663
  )
4639
4664
  })
4640
4665
  },
4666
+ /**
4667
+ * #action
4668
+ * zoomToRegion, and for a region naming a row, also scroll that row to
4669
+ * the middle of the alignment, as Go to does. What `region` opens on; a
4670
+ * caller following a range as it moves wants zoomToRegion alone, which
4671
+ * leaves the reader's vertical scroll where they put it.
4672
+ */
4673
+ revealRegion(region: Region) {
4674
+ transaction(() => {
4675
+ this.zoomToRegion(region)
4676
+ const row =
4677
+ region.row === undefined
4678
+ ? undefined
4679
+ : self.rowNamesSet.get(region.row)
4680
+ if (row !== undefined && self.viewInitialized) {
4681
+ self.setScrollY(
4682
+ -(row + 0.5) * self.rowHeight + self.msaAreaHeight / 2,
4683
+ )
4684
+ }
4685
+ })
4686
+ },
4641
4687
  /**
4642
4688
  * #action
4643
4689
  * zoom and scroll so the selected columns fill the alignment's width,
@@ -4769,7 +4815,7 @@ function stateModelFactory({
4769
4815
  self.numColumns > 0 &&
4770
4816
  !(self.treeFilehandle && !self.data.tree)
4771
4817
  ) {
4772
- this.zoomToRegion(region)
4818
+ this.revealRegion(region)
4773
4819
  self.setRegion(undefined)
4774
4820
  }
4775
4821
  }),
@@ -4853,7 +4899,9 @@ function stateModelFactory({
4853
4899
  }
4854
4900
  try {
4855
4901
  setLoading?.(true)
4856
- self.setError(undefined)
4902
+ if (!optional) {
4903
+ self.setError(undefined)
4904
+ }
4857
4905
  const text = await fetchTextWithProgress(
4858
4906
  openLocation(filehandle),
4859
4907
  status => {