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.
- package/README.md +19 -10
- package/bundle/index.js +74 -74
- package/bundle/index.js.map +4 -4
- package/dist/colorSchemes.js +1 -3
- package/dist/colorSchemes.js.map +1 -1
- package/dist/columnCounts.d.ts +0 -1
- package/dist/columnCounts.js +1 -1
- package/dist/columnCounts.js.map +1 -1
- package/dist/components/Loading.js +5 -5
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +1891 -4
- package/dist/components/MSAViewer.js +144 -201
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/ResizeHandles.js +3 -2
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/Track.js +2 -1
- package/dist/components/Track.js.map +1 -1
- package/dist/components/controls.d.ts +11 -0
- package/dist/components/controls.js +48 -0
- package/dist/components/controls.js.map +1 -0
- package/dist/components/dialogs/AnnotationFileDialog.js +5 -16
- package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -1
- package/dist/components/dialogs/ExportSVGDialog.js +5 -7
- package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
- package/dist/components/dialogs/HelpDialog.d.ts +6 -0
- package/dist/components/dialogs/HelpDialog.js +30 -0
- package/dist/components/dialogs/HelpDialog.js.map +1 -0
- package/dist/components/header/FileMenu.js +33 -7
- package/dist/components/header/FileMenu.js.map +1 -1
- package/dist/components/header/Header.js +4 -6
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/TreeSettingsMenu.d.ts +1 -1
- package/dist/components/header/TreeSettingsMenu.js +1 -32
- package/dist/components/header/TreeSettingsMenu.js.map +1 -1
- package/dist/components/header/ZoomControls.js +28 -44
- package/dist/components/header/ZoomControls.js.map +1 -1
- package/dist/components/header/settingsMenuItems.d.ts +1 -0
- package/dist/components/header/settingsMenuItems.js +44 -0
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/import/data/seq2.d.ts +0 -1
- package/dist/components/import/data/seq2.js +0 -17
- package/dist/components/import/data/seq2.js.map +1 -1
- package/dist/components/msa/MSACanvas.js +2 -1
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +2 -5
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/drawFeatureSpans.d.ts +50 -11
- package/dist/components/msa/drawFeatureSpans.js +99 -56
- package/dist/components/msa/drawFeatureSpans.js.map +1 -1
- package/dist/components/msa/msaRaster.d.ts +14 -1
- package/dist/components/msa/msaRaster.js +19 -9
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +9 -0
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +19 -8
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderHighlights.d.ts +2 -2
- package/dist/components/msa/renderHighlights.js +2 -3
- package/dist/components/msa/renderHighlights.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +2 -1
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/renderCtx.d.ts +1 -1
- package/dist/components/rowpanels/renderFeaturePanel.d.ts +5 -1
- package/dist/components/rowpanels/renderFeaturePanel.js +12 -5
- package/dist/components/rowpanels/renderFeaturePanel.js.map +1 -1
- package/dist/components/tracks/drawTracks.d.ts +1 -53
- package/dist/components/tracks/drawTracks.js +5 -5
- package/dist/components/tracks/drawTracks.js.map +1 -1
- package/dist/components/tree/CladeLabels.d.ts +1 -1
- package/dist/components/tree/CladeLabels.js +22 -34
- package/dist/components/tree/CladeLabels.js.map +1 -1
- package/dist/components/tree/cladeBrackets.d.ts +17 -10
- package/dist/components/tree/cladeBrackets.js +33 -3
- package/dist/components/tree/cladeBrackets.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.js +2 -8
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/tree/renderTreeOverview.js +4 -11
- package/dist/components/tree/renderTreeOverview.js.map +1 -1
- package/dist/element.js +3 -6
- package/dist/element.js.map +1 -1
- package/dist/exportLayout.d.ts +39 -0
- package/dist/exportLayout.js +78 -0
- package/dist/exportLayout.js.map +1 -0
- package/dist/headlessRenderEnv.d.ts +12 -39
- package/dist/headlessRenderEnv.js +18 -12
- package/dist/headlessRenderEnv.js.map +1 -1
- package/dist/hierarchy.d.ts +5 -8
- package/dist/hierarchy.js +5 -79
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/measureTextCanvas.d.ts +7 -0
- package/dist/measureTextCanvas.js +8 -6
- package/dist/measureTextCanvas.js.map +1 -1
- package/dist/model/treeModel.d.ts +0 -5
- package/dist/model/treeModel.js +0 -7
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +38 -44
- package/dist/model.js +153 -112
- package/dist/model.js.map +1 -1
- package/dist/renderToSvg.d.ts +2 -7
- package/dist/renderToSvg.js +32 -120
- package/dist/renderToSvg.js.map +1 -1
- package/dist/svgTestUtil.d.ts +12 -20
- package/dist/types.d.ts +0 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/viewerSnapshot.d.ts +169 -0
- package/dist/viewerSnapshot.js +100 -0
- package/dist/viewerSnapshot.js.map +1 -0
- package/package.json +3 -3
- package/src/colorSchemes.ts +1 -4
- package/src/columnCounts.ts +1 -1
- package/src/components/Loading.tsx +19 -12
- package/src/components/MSAViewer.tsx +275 -251
- package/src/components/ResizeHandles.tsx +9 -2
- package/src/components/Track.tsx +2 -1
- package/src/components/controls.ts +61 -0
- package/src/components/dialogs/AnnotationFileDialog.tsx +5 -21
- package/src/components/dialogs/ExportSVGDialog.tsx +8 -19
- package/src/components/dialogs/HelpDialog.tsx +79 -0
- package/src/components/header/FileMenu.tsx +33 -7
- package/src/components/header/Header.tsx +4 -6
- package/src/components/header/TreeSettingsMenu.tsx +2 -32
- package/src/components/header/ZoomControls.tsx +30 -66
- package/src/components/header/settingsMenuItems.ts +59 -0
- package/src/components/import/data/seq2.ts +0 -18
- package/src/components/msa/MSACanvas.tsx +2 -1
- package/src/components/msa/MSACanvasBlock.tsx +2 -6
- package/src/components/msa/drawFeatureSpans.ts +160 -79
- package/src/components/msa/msaRaster.ts +27 -9
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +39 -24
- package/src/components/msa/renderHighlights.ts +2 -3
- package/src/components/msa/renderMSAMouseover.ts +5 -1
- package/src/components/renderCtx.ts +0 -1
- package/src/components/rowpanels/renderFeaturePanel.ts +26 -7
- package/src/components/tracks/drawTracks.ts +5 -5
- package/src/components/tree/CladeLabels.tsx +30 -54
- package/src/components/tree/cladeBrackets.ts +50 -3
- package/src/components/tree/renderTreeCanvas.ts +2 -13
- package/src/components/tree/renderTreeOverview.ts +7 -14
- package/src/element.ts +3 -7
- package/src/exportLayout.ts +134 -0
- package/src/headlessRenderEnv.ts +24 -11
- package/src/hierarchy.ts +5 -88
- package/src/index.ts +5 -5
- package/src/measureTextCanvas.ts +8 -6
- package/src/model/treeModel.ts +0 -7
- package/src/model.ts +170 -122
- package/src/renderToSvg.tsx +55 -194
- package/src/types.ts +0 -1
- package/src/version.ts +1 -1
- package/src/viewerSnapshot.ts +147 -0
- package/dist/components/header/ZoomMenu.d.ts +0 -6
- package/dist/components/header/ZoomMenu.js +0 -37
- package/dist/components/header/ZoomMenu.js.map +0 -1
- package/src/components/header/ZoomMenu.tsx +0 -48
package/src/model/treeModel.ts
CHANGED
|
@@ -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. `
|
|
566
|
-
*
|
|
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
|
-
|
|
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) ?
|
|
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
|
|
582
|
-
return
|
|
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
|
|
589
|
-
if (
|
|
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
|
|
595
|
-
const row =
|
|
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
|
|
608
|
-
*
|
|
609
|
-
* rows and the overview against its own, so both take the tree they
|
|
610
|
-
* `range` record names no node, so the seeding marks, which need one,
|
|
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
|
-
|
|
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,
|
|
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
|
|
754
|
-
//
|
|
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
|
|
1100
|
-
*
|
|
1101
|
-
* then cleared, so a reloaded
|
|
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
|
|
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 ||
|
|
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.
|
|
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
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
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:
|
|
2137
|
-
showBranchLen: self.showBranchLen &&
|
|
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
|
-
|
|
2152
|
-
|
|
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,
|
|
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((
|
|
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
|
|
2350
|
-
*
|
|
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
|
-
//
|
|
2403
|
-
//
|
|
2404
|
-
const
|
|
2405
|
-
const
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 =>
|
|
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.
|
|
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
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
4902
|
+
if (!optional) {
|
|
4903
|
+
self.setError(undefined)
|
|
4904
|
+
}
|
|
4857
4905
|
const text = await fetchTextWithProgress(
|
|
4858
4906
|
openLocation(filehandle),
|
|
4859
4907
|
status => {
|