react-msaview 8.4.1 → 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/MSAView.js +2 -2
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +1892 -7
- package/dist/components/MSAViewer.js +144 -202
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/ResizeHandles.js +4 -3
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/Track.js +3 -2
- 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 +48 -1
- 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/TreeCanvas.js +4 -4
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +4 -4
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/TreeOverview.js +10 -5
- package/dist/components/tree/TreeOverview.js.map +1 -1
- package/dist/components/tree/TreePanel.js +6 -2
- package/dist/components/tree/TreePanel.js.map +1 -1
- package/dist/components/tree/TreeRuler.js +2 -2
- package/dist/components/tree/TreeRuler.js.map +1 -1
- package/dist/components/tree/cladeBrackets.d.ts +22 -17
- package/dist/components/tree/cladeBrackets.js +40 -9
- package/dist/components/tree/cladeBrackets.js.map +1 -1
- package/dist/components/tree/clickMap.d.ts +2 -0
- package/dist/components/tree/clickMap.js.map +1 -1
- package/dist/components/tree/ellipsize.d.ts +8 -0
- package/dist/components/tree/ellipsize.js +20 -0
- package/dist/components/tree/ellipsize.js.map +1 -0
- package/dist/components/tree/renderTreeCanvas.js +30 -32
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/tree/renderTreeMouseover.js +1 -1
- package/dist/components/tree/renderTreeMouseover.js.map +1 -1
- package/dist/components/tree/renderTreeOverview.js +4 -11
- package/dist/components/tree/renderTreeOverview.js.map +1 -1
- package/dist/constants.d.ts +6 -3
- package/dist/constants.js +16 -6
- package/dist/constants.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 +10 -8
- package/dist/measureTextCanvas.js.map +1 -1
- package/dist/model/treeModel.d.ts +14 -25
- package/dist/model/treeModel.js +16 -30
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +108 -79
- package/dist/model.js +258 -209
- package/dist/model.js.map +1 -1
- package/dist/renderToSvg.d.ts +2 -7
- package/dist/renderToSvg.js +49 -135
- package/dist/renderToSvg.js.map +1 -1
- package/dist/svgTestUtil.d.ts +48 -48
- package/dist/tipLabels.d.ts +8 -0
- package/dist/tipLabels.js +46 -0
- package/dist/tipLabels.js.map +1 -0
- 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/MSAView.tsx +2 -2
- package/src/components/MSAViewer.tsx +276 -256
- package/src/components/ResizeHandles.tsx +10 -3
- package/src/components/Track.tsx +3 -2
- 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 +67 -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/TreeCanvas.tsx +10 -4
- package/src/components/tree/TreeCanvasBlock.tsx +4 -4
- package/src/components/tree/TreeOverview.tsx +10 -5
- package/src/components/tree/TreePanel.tsx +8 -2
- package/src/components/tree/TreeRuler.tsx +7 -3
- package/src/components/tree/cladeBrackets.ts +59 -19
- package/src/components/tree/clickMap.ts +2 -0
- package/src/components/tree/ellipsize.ts +24 -0
- package/src/components/tree/renderTreeCanvas.ts +37 -36
- package/src/components/tree/renderTreeMouseover.ts +1 -1
- package/src/components/tree/renderTreeOverview.ts +7 -14
- package/src/constants.ts +17 -6
- 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 +10 -8
- package/src/model/treeModel.ts +17 -34
- package/src/model.ts +296 -243
- package/src/renderToSvg.tsx +72 -209
- package/src/tipLabels.ts +51 -0
- 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.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 {
|
|
@@ -56,15 +54,19 @@ import {
|
|
|
56
54
|
defaultShowDomainLegend,
|
|
57
55
|
defaultShowDomains,
|
|
58
56
|
defaultSubFeatureRows,
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
fontSizeFor,
|
|
58
|
+
labelFitFontSize,
|
|
61
59
|
maxCellSize,
|
|
60
|
+
maxFitTreeWidth,
|
|
61
|
+
maxTreeAreaShare,
|
|
62
62
|
maxInlineSnapshotBytes as defaultMaxInlineSnapshotBytes,
|
|
63
63
|
maxNeighborJoiningRows,
|
|
64
64
|
minColWidth,
|
|
65
65
|
minLetterColWidth,
|
|
66
66
|
minLetterRowHeight,
|
|
67
67
|
minRowHeight,
|
|
68
|
+
minTreeWidth,
|
|
69
|
+
pxPerTreeDepth,
|
|
68
70
|
rowPanelHeaderHeight,
|
|
69
71
|
rowTintAlpha,
|
|
70
72
|
scrollZoomAxes,
|
|
@@ -79,8 +81,6 @@ import { featuresToAnnotations } from './featuresLayer.ts'
|
|
|
79
81
|
import { fetchTextWithProgress, isAbortError } from './fetchUtils.ts'
|
|
80
82
|
import { flatToTree } from './flatToTree.ts'
|
|
81
83
|
import {
|
|
82
|
-
calcDepthToLeaf,
|
|
83
|
-
clusterLayout,
|
|
84
84
|
collapse,
|
|
85
85
|
find,
|
|
86
86
|
forEachDescendant,
|
|
@@ -88,14 +88,11 @@ import {
|
|
|
88
88
|
layoutTree,
|
|
89
89
|
leafIndex,
|
|
90
90
|
leaves,
|
|
91
|
-
maxLength,
|
|
92
91
|
mrca,
|
|
93
92
|
nodeCoveringRows,
|
|
94
|
-
setBrLength,
|
|
95
93
|
sort,
|
|
96
94
|
sum as hierarchySum,
|
|
97
95
|
} from './hierarchy.ts'
|
|
98
|
-
import { measureTextCanvas } from './measureTextCanvas.ts'
|
|
99
96
|
import { DataModelF } from './model/DataModel.ts'
|
|
100
97
|
import { DialogQueueSessionMixin } from './model/DialogQueue.ts'
|
|
101
98
|
import { MSAModelF } from './model/msaModel.ts'
|
|
@@ -114,6 +111,7 @@ import { resolveScale } from './scales.ts'
|
|
|
114
111
|
import { buildSeqPosIndex } from './seqPosToGlobalCol.ts'
|
|
115
112
|
import { maxBitsFor } from './sequenceLogo.ts'
|
|
116
113
|
import { stripDefault } from './stripDefault.ts'
|
|
114
|
+
import { treeTips, widestLabel } from './tipLabels.ts'
|
|
117
115
|
import {
|
|
118
116
|
computeRowInsertions,
|
|
119
117
|
dropBlanks,
|
|
@@ -175,7 +173,6 @@ import type {
|
|
|
175
173
|
import type { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
|
|
176
174
|
import type { Instance } from '@jbrowse/mobx-state-tree'
|
|
177
175
|
import type { IComputedValue } from 'mobx'
|
|
178
|
-
import type { InterProScanResults } from 'msa-parsers'
|
|
179
176
|
|
|
180
177
|
function parseTreeText(text: string) {
|
|
181
178
|
if (text.startsWith('BioTreeContainer')) {
|
|
@@ -528,7 +525,6 @@ export const preservedOnReset = new Set([
|
|
|
528
525
|
'drawNodeLabels',
|
|
529
526
|
'showTreeOverview',
|
|
530
527
|
'overviewHeight',
|
|
531
|
-
'autoTreeAreaWidth',
|
|
532
528
|
'turnedOffTracks',
|
|
533
529
|
'trackHeights',
|
|
534
530
|
'hideGaps',
|
|
@@ -556,40 +552,80 @@ const seedingMarks = new Set<CladeMark>(['collapse', 'focus', 'rotate'])
|
|
|
556
552
|
const noDomains: Annotation[] = []
|
|
557
553
|
const noClades: ResolvedClade[] = []
|
|
558
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
|
+
|
|
559
592
|
/**
|
|
560
593
|
* The rows a clade covers and the node an `mrca` names, or undefined when the
|
|
561
594
|
* clade does not resolve: a tip name the tree does not have or has twice, or a
|
|
562
|
-
* leaf count `tips` disagrees with. `
|
|
563
|
-
*
|
|
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.
|
|
564
598
|
*/
|
|
565
599
|
function cladeRows(
|
|
566
600
|
clade: Clade,
|
|
567
601
|
root: HierarchyNode<NodeWithIds>,
|
|
568
602
|
index: Map<string, HierarchyNode<NodeWithIds> | undefined>,
|
|
569
|
-
|
|
603
|
+
tips: Map<string, { order: number; row: number }>,
|
|
570
604
|
): { rows: [number, number]; nodeId?: string } | undefined {
|
|
571
605
|
if (clade.range) {
|
|
572
606
|
const [a, b] = clade.range.map(name =>
|
|
573
|
-
index.get(name) ?
|
|
607
|
+
index.get(name) ? tips.get(name) : undefined,
|
|
574
608
|
)
|
|
575
609
|
if (a === undefined || b === undefined) {
|
|
576
610
|
return undefined
|
|
577
611
|
}
|
|
578
|
-
const
|
|
579
|
-
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
|
|
580
616
|
}
|
|
581
617
|
const node = clade.mrca ? mrca(root, clade.mrca, index) : undefined
|
|
582
618
|
if (!node) {
|
|
583
619
|
return undefined
|
|
584
620
|
}
|
|
585
|
-
const
|
|
586
|
-
if (
|
|
621
|
+
const nodeTips = leaves(node)
|
|
622
|
+
if (nodeTips.length !== clade.tips) {
|
|
587
623
|
return undefined
|
|
588
624
|
}
|
|
589
625
|
let first = Infinity
|
|
590
626
|
let last = -Infinity
|
|
591
|
-
for (const tip of
|
|
592
|
-
const row =
|
|
627
|
+
for (const tip of nodeTips) {
|
|
628
|
+
const row = tips.get(tip.data.name)?.row
|
|
593
629
|
if (row !== undefined) {
|
|
594
630
|
first = Math.min(first, row)
|
|
595
631
|
last = Math.max(last, row)
|
|
@@ -601,19 +637,21 @@ function cladeRows(
|
|
|
601
637
|
}
|
|
602
638
|
|
|
603
639
|
/**
|
|
604
|
-
* Every clade that resolves, with its fill color settled, against the
|
|
605
|
-
*
|
|
606
|
-
* rows and the overview against its own, so both take the tree they
|
|
607
|
-
* `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.
|
|
608
645
|
*/
|
|
609
646
|
function resolveClades(
|
|
610
647
|
clades: Clade[],
|
|
611
648
|
root: HierarchyNode<NodeWithIds>,
|
|
612
|
-
|
|
649
|
+
displayed: HierarchyNode<NodeWithIds>,
|
|
613
650
|
): ResolvedClade[] {
|
|
614
651
|
const index = leafIndex(root)
|
|
652
|
+
const tips = tipRows(displayed)
|
|
615
653
|
return clades.flatMap(clade => {
|
|
616
|
-
const resolved = cladeRows(clade, root, index,
|
|
654
|
+
const resolved = cladeRows(clade, root, index, tips)
|
|
617
655
|
const seeding = seedingMarks.has(clade.mark)
|
|
618
656
|
return resolved && !(seeding && resolved.nodeId === undefined)
|
|
619
657
|
? [
|
|
@@ -747,8 +785,8 @@ function rootTree(tree: NodeWithIds, treeRoot: TreeRoot) {
|
|
|
747
785
|
: tree
|
|
748
786
|
}
|
|
749
787
|
|
|
750
|
-
// the inclusive tip indices a node covers, from
|
|
751
|
-
//
|
|
788
|
+
// the inclusive tip indices a node covers, from its xMin/xMax in a layout one
|
|
789
|
+
// row per tip
|
|
752
790
|
function tipRange(node: HierarchyNode): [number, number] {
|
|
753
791
|
return [Math.round(node.xMin! - 0.5), Math.round(node.xMax! - 0.5)]
|
|
754
792
|
}
|
|
@@ -762,6 +800,16 @@ const featureChannels = new Set<EncodingChannel>([
|
|
|
762
800
|
// seqPos -> column indexes per row, keyed on the parse so they are garbage
|
|
763
801
|
// collected with it. A computed would rebuild every row's index when read
|
|
764
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
|
+
|
|
765
813
|
const seqPosIndexCache = new WeakMap<object, Map<string, Int32Array>>()
|
|
766
814
|
|
|
767
815
|
// A segment is a 1:1 run. A segment whose sides differ in length is malformed,
|
|
@@ -1093,9 +1141,10 @@ function stateModelFactory({
|
|
|
1093
1141
|
/**
|
|
1094
1142
|
* #property
|
|
1095
1143
|
* where the view opens, in `highlights` coordinates: `{row, start,
|
|
1096
|
-
* end}` zooms onto residues of that row
|
|
1097
|
-
*
|
|
1098
|
-
* 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.
|
|
1099
1148
|
*/
|
|
1100
1149
|
region: types.frozen<Region | undefined>(),
|
|
1101
1150
|
/**
|
|
@@ -1137,6 +1186,12 @@ function stateModelFactory({
|
|
|
1137
1186
|
* of the snapshot so a link opened in the full app shows the toolbar.
|
|
1138
1187
|
*/
|
|
1139
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,
|
|
1140
1195
|
/**
|
|
1141
1196
|
* #volatile
|
|
1142
1197
|
*/
|
|
@@ -1648,20 +1703,18 @@ function stateModelFactory({
|
|
|
1648
1703
|
.views(self => ({
|
|
1649
1704
|
/**
|
|
1650
1705
|
* #getter
|
|
1651
|
-
* 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
|
|
1652
1709
|
*/
|
|
1653
1710
|
get hideGapsEffective() {
|
|
1654
1711
|
return (
|
|
1655
1712
|
self.hideGaps &&
|
|
1656
|
-
(self.collapsed.length > 0 ||
|
|
1713
|
+
(self.collapsed.length > 0 ||
|
|
1714
|
+
self.showOnly !== undefined ||
|
|
1715
|
+
self.allowedGappyness < 100)
|
|
1657
1716
|
)
|
|
1658
1717
|
},
|
|
1659
|
-
/**
|
|
1660
|
-
* #getter
|
|
1661
|
-
*/
|
|
1662
|
-
get realAllowedGappyness() {
|
|
1663
|
-
return this.hideGapsEffective ? self.allowedGappyness : 100
|
|
1664
|
-
},
|
|
1665
1718
|
/**
|
|
1666
1719
|
* #getter
|
|
1667
1720
|
* the annotations the overlay, the legend and the encodings draw: the
|
|
@@ -1804,10 +1857,10 @@ function stateModelFactory({
|
|
|
1804
1857
|
* the row table: extra fields per row, keyed by row name, which the
|
|
1805
1858
|
* `encodings` channels read. It is stored as the JSON string
|
|
1806
1859
|
* `data.treeMetadata`, the name that travels in existing links, so the
|
|
1807
|
-
* inline size limit and `treeMetadataFilehandle` cover it.
|
|
1808
|
-
* reads it on every layout, so a malformed user-supplied file
|
|
1809
|
-
* instead of throwing out of rendering, and every field is a
|
|
1810
|
-
* the time a scale sorts it.
|
|
1860
|
+
* inline size limit and `treeMetadataFilehandle` cover it. The tree area
|
|
1861
|
+
* fit reads it on every layout, so a malformed user-supplied file
|
|
1862
|
+
* returns {} instead of throwing out of rendering, and every field is a
|
|
1863
|
+
* string by the time a scale sorts it.
|
|
1811
1864
|
*/
|
|
1812
1865
|
get rowData(): Record<string, Record<string, string> | undefined> {
|
|
1813
1866
|
const text = self.data.treeMetadata
|
|
@@ -1928,7 +1981,7 @@ function stateModelFactory({
|
|
|
1928
1981
|
return resolveClades(
|
|
1929
1982
|
self.clades,
|
|
1930
1983
|
hierarchy(this.tree, d => d.children),
|
|
1931
|
-
this.
|
|
1984
|
+
this.root,
|
|
1932
1985
|
)
|
|
1933
1986
|
},
|
|
1934
1987
|
/**
|
|
@@ -1938,11 +1991,107 @@ function stateModelFactory({
|
|
|
1938
1991
|
* where no clade draws a bar or a label.
|
|
1939
1992
|
*/
|
|
1940
1993
|
get cladeGutterWidth() {
|
|
1941
|
-
return cladeGutterWidth(
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1994
|
+
return cladeGutterWidth(self.clades)
|
|
1995
|
+
},
|
|
1996
|
+
/**
|
|
1997
|
+
* #getter
|
|
1998
|
+
* whether a tree draws beside the labels: false for a file with no
|
|
1999
|
+
* tree, and with drawTree off
|
|
2000
|
+
*/
|
|
2001
|
+
get treeDrawn() {
|
|
2002
|
+
return self.drawTree && !this.noTree
|
|
2003
|
+
},
|
|
2004
|
+
/**
|
|
2005
|
+
* #getter
|
|
2006
|
+
* the leaf names of the whole tree and its depth in levels. The fit
|
|
2007
|
+
* reads these, so a collapse or a focus leaves the tree area as it is
|
|
2008
|
+
*/
|
|
2009
|
+
get treeTips() {
|
|
2010
|
+
return treeTips(this.tree)
|
|
2011
|
+
},
|
|
2012
|
+
/**
|
|
2013
|
+
* #getter
|
|
2014
|
+
* the widest tip label at labelFitFontSize, measured whether or not a
|
|
2015
|
+
* zoom hides the labels
|
|
2016
|
+
*/
|
|
2017
|
+
get maxTipLabelWidth() {
|
|
2018
|
+
return widestLabel(
|
|
2019
|
+
this.treeTips.names.map(name => this.rowDataOf(name)?.genome || name),
|
|
2020
|
+
labelFitFontSize,
|
|
2021
|
+
)
|
|
2022
|
+
},
|
|
2023
|
+
/**
|
|
2024
|
+
* #getter
|
|
2025
|
+
* the px the tip labels take across the tree area: the widest label at
|
|
2026
|
+
* labelFitFontSize and the gaps either side of it, or 0 with drawLabels
|
|
2027
|
+
* off
|
|
2028
|
+
*/
|
|
2029
|
+
get labelColumnWidth() {
|
|
2030
|
+
return self.drawLabels
|
|
2031
|
+
? this.maxTipLabelWidth + (this.treeDrawn ? 10 : 6)
|
|
2032
|
+
: 0
|
|
2033
|
+
},
|
|
2034
|
+
/**
|
|
2035
|
+
* #getter
|
|
2036
|
+
* the tree area width while treeAreaWidth is undefined: the label column
|
|
2037
|
+
* beside a tree that widens with its depth, up to maxTreeAreaShare of the
|
|
2038
|
+
* view, or the room the row panels leave where there is no alignment.
|
|
2039
|
+
* Where that binds, the tree keeps minTreeWidth and the renderer
|
|
2040
|
+
* ellipsizes the labels
|
|
2041
|
+
*/
|
|
2042
|
+
get fittedTreeAreaWidth() {
|
|
2043
|
+
const chrome = self.marginLeft + this.cladeGutterWidth
|
|
2044
|
+
const tree = this.treeDrawn
|
|
2045
|
+
? (self.treeWidth ??
|
|
2046
|
+
clamp(
|
|
2047
|
+
this.treeTips.depth * pxPerTreeDepth,
|
|
2048
|
+
minTreeWidth,
|
|
2049
|
+
maxFitTreeWidth,
|
|
2050
|
+
))
|
|
2051
|
+
: 0
|
|
2052
|
+
const fit = chrome + tree + this.labelColumnWidth
|
|
2053
|
+
const room = self.viewInitialized
|
|
2054
|
+
? this.numColumns > 0
|
|
2055
|
+
? self.width * maxTreeAreaShare
|
|
2056
|
+
: self.width - this.rowPanelsWidth - self.resizeHandleWidth
|
|
2057
|
+
: Infinity
|
|
2058
|
+
const cap = Math.max(room, chrome + Math.min(tree, minTreeWidth))
|
|
2059
|
+
return Math.ceil(Math.min(fit, cap))
|
|
2060
|
+
},
|
|
2061
|
+
/**
|
|
2062
|
+
* #getter
|
|
2063
|
+
* the width of the area the tree and its labels draw in, px:
|
|
2064
|
+
* treeAreaWidth where set, the fit otherwise
|
|
2065
|
+
*/
|
|
2066
|
+
get effectiveTreeAreaWidth() {
|
|
2067
|
+
return self.treeAreaWidth ?? this.fittedTreeAreaWidth
|
|
2068
|
+
},
|
|
2069
|
+
/**
|
|
2070
|
+
* #getter
|
|
2071
|
+
* the width of the tree, px: treeWidth where set, otherwise the tree
|
|
2072
|
+
* area less the label column, which a zoom hiding the labels gives to
|
|
2073
|
+
* the tree
|
|
2074
|
+
*/
|
|
2075
|
+
get effectiveTreeWidth() {
|
|
2076
|
+
const labels = this.showTreeText ? this.labelColumnWidth : 0
|
|
2077
|
+
return (
|
|
2078
|
+
self.treeWidth ??
|
|
2079
|
+
Math.max(
|
|
2080
|
+
minTreeWidth,
|
|
2081
|
+
Math.floor(
|
|
2082
|
+
this.effectiveTreeAreaWidth -
|
|
2083
|
+
self.marginLeft -
|
|
2084
|
+
this.cladeGutterWidth -
|
|
2085
|
+
labels,
|
|
2086
|
+
),
|
|
2087
|
+
)
|
|
2088
|
+
)
|
|
2089
|
+
},
|
|
2090
|
+
/**
|
|
2091
|
+
* #getter
|
|
2092
|
+
*/
|
|
2093
|
+
get showTreeText() {
|
|
2094
|
+
return self.drawLabels && self.rowHeight >= minLetterRowHeight
|
|
1946
2095
|
},
|
|
1947
2096
|
/**
|
|
1948
2097
|
* #getter
|
|
@@ -2021,21 +2170,19 @@ function stateModelFactory({
|
|
|
2021
2170
|
if (!self.showTreeOverview) {
|
|
2022
2171
|
return undefined
|
|
2023
2172
|
}
|
|
2024
|
-
const
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
const extent = maxLength(root) - rootLen
|
|
2033
|
-
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
|
+
)
|
|
2034
2181
|
return {
|
|
2035
|
-
root,
|
|
2036
|
-
numTips,
|
|
2037
|
-
maxDepthToLeaf:
|
|
2038
|
-
showBranchLen: self.showBranchLen &&
|
|
2182
|
+
root: layout.root,
|
|
2183
|
+
numTips: layout.leaves.length,
|
|
2184
|
+
maxDepthToLeaf: layout.root.depthToLeaf,
|
|
2185
|
+
showBranchLen: self.showBranchLen && layout.rootToTipLength > 0,
|
|
2039
2186
|
}
|
|
2040
2187
|
},
|
|
2041
2188
|
|
|
@@ -2049,10 +2196,11 @@ function stateModelFactory({
|
|
|
2049
2196
|
if (!layout || self.clades.length === 0) {
|
|
2050
2197
|
return noClades
|
|
2051
2198
|
}
|
|
2052
|
-
|
|
2053
|
-
|
|
2199
|
+
return resolveClades(
|
|
2200
|
+
self.clades,
|
|
2201
|
+
hierarchy(this.tree, d => d.children),
|
|
2202
|
+
layout.root,
|
|
2054
2203
|
)
|
|
2055
|
-
return resolveClades(self.clades, layout.root, rowNames)
|
|
2056
2204
|
},
|
|
2057
2205
|
|
|
2058
2206
|
/**
|
|
@@ -2125,7 +2273,7 @@ function stateModelFactory({
|
|
|
2125
2273
|
get msaAreaWidth() {
|
|
2126
2274
|
return (
|
|
2127
2275
|
self.width -
|
|
2128
|
-
|
|
2276
|
+
this.effectiveTreeAreaWidth -
|
|
2129
2277
|
this.rowPanelsWidth -
|
|
2130
2278
|
self.resizeHandleWidth
|
|
2131
2279
|
)
|
|
@@ -2137,13 +2285,15 @@ function stateModelFactory({
|
|
|
2137
2285
|
* margin and the bracket gutter
|
|
2138
2286
|
*/
|
|
2139
2287
|
get treeAreaWidthMinusMargin() {
|
|
2140
|
-
return
|
|
2288
|
+
return (
|
|
2289
|
+
this.effectiveTreeAreaWidth - self.marginLeft - this.cladeGutterWidth
|
|
2290
|
+
)
|
|
2141
2291
|
},
|
|
2142
2292
|
/**
|
|
2143
2293
|
* #getter
|
|
2144
2294
|
*/
|
|
2145
2295
|
get blanks() {
|
|
2146
|
-
const { hideGapsEffective,
|
|
2296
|
+
const { hideGapsEffective, allowedGappyness } = self
|
|
2147
2297
|
if (!hideGapsEffective) {
|
|
2148
2298
|
return []
|
|
2149
2299
|
}
|
|
@@ -2159,7 +2309,7 @@ function stateModelFactory({
|
|
|
2159
2309
|
// row that stops early counts as gapped for the remainder
|
|
2160
2310
|
const numCols = this.MSA?.getWidth() ?? 0
|
|
2161
2311
|
const numRows = strs.length
|
|
2162
|
-
const threshold = Math.ceil((
|
|
2312
|
+
const threshold = Math.ceil((allowedGappyness / 100) * numRows)
|
|
2163
2313
|
const blankCounts = new Uint32Array(numCols)
|
|
2164
2314
|
for (let j = 0; j < numRows; j++) {
|
|
2165
2315
|
const str = strs[j]!
|
|
@@ -2243,11 +2393,19 @@ function stateModelFactory({
|
|
|
2243
2393
|
return index
|
|
2244
2394
|
},
|
|
2245
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
|
+
},
|
|
2246
2405
|
/**
|
|
2247
2406
|
* #getter
|
|
2248
|
-
* every sequence in the alignment, keyed by
|
|
2249
|
-
*
|
|
2250
|
-
* 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
|
|
2251
2409
|
*/
|
|
2252
2410
|
get rowMap() {
|
|
2253
2411
|
const MSA = this.MSA
|
|
@@ -2282,7 +2440,7 @@ function stateModelFactory({
|
|
|
2282
2440
|
* #getter
|
|
2283
2441
|
*/
|
|
2284
2442
|
get fontSize() {
|
|
2285
|
-
return
|
|
2443
|
+
return fontSizeFor(self.rowHeight)
|
|
2286
2444
|
},
|
|
2287
2445
|
/**
|
|
2288
2446
|
* #getter
|
|
@@ -2298,10 +2456,11 @@ function stateModelFactory({
|
|
|
2298
2456
|
*/
|
|
2299
2457
|
get sequenceType(): 'dna' | 'rna' | 'amino' {
|
|
2300
2458
|
const letters = this.colStats.lettersPresent
|
|
2301
|
-
//
|
|
2302
|
-
//
|
|
2303
|
-
const
|
|
2304
|
-
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])
|
|
2305
2464
|
const isDna = letters.size > 0 && [...letters].every(l => dna.has(l))
|
|
2306
2465
|
const isRna = letters.size > 0 && [...letters].every(l => rna.has(l))
|
|
2307
2466
|
return isDna ? 'dna' : isRna ? 'rna' : 'amino'
|
|
@@ -2388,17 +2547,18 @@ function stateModelFactory({
|
|
|
2388
2547
|
/**
|
|
2389
2548
|
* #getter
|
|
2390
2549
|
* `root` laid out on new nodes in row and branch-length units, read in
|
|
2391
|
-
* pixels through `rowHeight` and `
|
|
2392
|
-
* node in it as they are, and an observer reading a node's `x`
|
|
2393
|
-
* observes the size it scales by.
|
|
2550
|
+
* pixels through `rowHeight` and `effectiveTreeWidth`. A zoom leaves it
|
|
2551
|
+
* and every node in it as they are, and an observer reading a node's `x`
|
|
2552
|
+
* or `len` observes the size it scales by.
|
|
2394
2553
|
*/
|
|
2395
2554
|
get treeLayout() {
|
|
2555
|
+
const treeWidth = () => this.effectiveTreeWidth
|
|
2396
2556
|
return layoutTree(this.root, {
|
|
2397
2557
|
get rowHeight() {
|
|
2398
2558
|
return self.rowHeight
|
|
2399
2559
|
},
|
|
2400
2560
|
get treeWidth() {
|
|
2401
|
-
return
|
|
2561
|
+
return treeWidth()
|
|
2402
2562
|
},
|
|
2403
2563
|
})
|
|
2404
2564
|
},
|
|
@@ -2434,15 +2594,6 @@ function stateModelFactory({
|
|
|
2434
2594
|
return this.treeLayout.rootToTipLength
|
|
2435
2595
|
},
|
|
2436
2596
|
|
|
2437
|
-
/**
|
|
2438
|
-
* #getter
|
|
2439
|
-
* x-position of the farthest tip in a phylogram, px: treeWidth, or 0 for
|
|
2440
|
-
* a tree with no branch lengths (drawn as a cladogram)
|
|
2441
|
-
*/
|
|
2442
|
-
get maxBranchLength() {
|
|
2443
|
-
return this.rootToTipLength ? self.treeWidth : 0
|
|
2444
|
-
},
|
|
2445
|
-
|
|
2446
2597
|
/**
|
|
2447
2598
|
* #getter
|
|
2448
2599
|
* pixels per unit of branch length in the phylogram layout, 0 in
|
|
@@ -2450,7 +2601,9 @@ function stateModelFactory({
|
|
|
2450
2601
|
*/
|
|
2451
2602
|
get pxPerBranchLength() {
|
|
2452
2603
|
const max = this.rootToTipLength
|
|
2453
|
-
return this.showBranchLenEffective && max
|
|
2604
|
+
return this.showBranchLenEffective && max
|
|
2605
|
+
? this.effectiveTreeWidth / max
|
|
2606
|
+
: 0
|
|
2454
2607
|
},
|
|
2455
2608
|
|
|
2456
2609
|
/**
|
|
@@ -2497,12 +2650,6 @@ function stateModelFactory({
|
|
|
2497
2650
|
self.colWidth > self.rowHeight / 2
|
|
2498
2651
|
)
|
|
2499
2652
|
},
|
|
2500
|
-
/**
|
|
2501
|
-
* #getter
|
|
2502
|
-
*/
|
|
2503
|
-
get showTreeText() {
|
|
2504
|
-
return self.drawLabels && self.rowHeight >= minLetterRowHeight
|
|
2505
|
-
},
|
|
2506
2653
|
}))
|
|
2507
2654
|
.views(self => {
|
|
2508
2655
|
// a spec is a frozen value, so it keys its computed until setColumnTracks
|
|
@@ -2623,7 +2770,6 @@ function stateModelFactory({
|
|
|
2623
2770
|
data: content?.data,
|
|
2624
2771
|
arcs: content?.arcs,
|
|
2625
2772
|
},
|
|
2626
|
-
ReactComponent: TrackBlocks,
|
|
2627
2773
|
}
|
|
2628
2774
|
})
|
|
2629
2775
|
columnTrackModels.set(track, computedModel)
|
|
@@ -2639,53 +2785,6 @@ function stateModelFactory({
|
|
|
2639
2785
|
}
|
|
2640
2786
|
})
|
|
2641
2787
|
.views(self => ({
|
|
2642
|
-
/**
|
|
2643
|
-
* #getter
|
|
2644
|
-
*/
|
|
2645
|
-
get labelWidthMap() {
|
|
2646
|
-
const { showTreeText, leaves } = self
|
|
2647
|
-
// gated on the renderer's label condition, so hidden labels reserve no
|
|
2648
|
-
// gutter. Measured once at a reference size and scaled by
|
|
2649
|
-
// labelWidthScale: re-measuring per vertical-zoom frame cost ~200ms on a
|
|
2650
|
-
// 50k-leaf tree
|
|
2651
|
-
return showTreeText
|
|
2652
|
-
? new Map(
|
|
2653
|
-
leaves.map(node => {
|
|
2654
|
-
const { name } = node.data
|
|
2655
|
-
// `||`, matching renderTreeLabels: an empty genome falls back
|
|
2656
|
-
// to the row name
|
|
2657
|
-
const displayName = self.rowDataOf(name)?.genome || name
|
|
2658
|
-
return [
|
|
2659
|
-
name,
|
|
2660
|
-
measureTextCanvas(displayName, labelReferenceFontSize),
|
|
2661
|
-
] as const
|
|
2662
|
-
}),
|
|
2663
|
-
)
|
|
2664
|
-
: new Map<string, number>()
|
|
2665
|
-
},
|
|
2666
|
-
|
|
2667
|
-
/**
|
|
2668
|
-
* #getter
|
|
2669
|
-
* factor turning a labelWidthMap entry into its width at the current
|
|
2670
|
-
* font size
|
|
2671
|
-
*/
|
|
2672
|
-
get labelWidthScale() {
|
|
2673
|
-
return self.fontSize / labelReferenceFontSize
|
|
2674
|
-
},
|
|
2675
|
-
|
|
2676
|
-
get labelsWidth() {
|
|
2677
|
-
// a loop, not Math.max(...): spreading passes one argument per leaf,
|
|
2678
|
-
// and the ~125k argument limit throws RangeError on the 230k-tip COVID
|
|
2679
|
-
// tree
|
|
2680
|
-
let max = 0
|
|
2681
|
-
for (const width of this.labelWidthMap.values()) {
|
|
2682
|
-
if (width > max) {
|
|
2683
|
-
max = width
|
|
2684
|
-
}
|
|
2685
|
-
}
|
|
2686
|
-
return max * this.labelWidthScale
|
|
2687
|
-
},
|
|
2688
|
-
|
|
2689
2788
|
/**
|
|
2690
2789
|
* #getter
|
|
2691
2790
|
*/
|
|
@@ -2732,7 +2831,6 @@ function stateModelFactory({
|
|
|
2732
2831
|
data: hideGapsEffective ? skipBlanks(blanks, t.data!) : t.data,
|
|
2733
2832
|
height: rowHeight,
|
|
2734
2833
|
},
|
|
2735
|
-
ReactComponent: TrackBlocks,
|
|
2736
2834
|
}))
|
|
2737
2835
|
},
|
|
2738
2836
|
|
|
@@ -2755,7 +2853,6 @@ function stateModelFactory({
|
|
|
2755
2853
|
height: self.trackHeight('arc'),
|
|
2756
2854
|
arcs,
|
|
2757
2855
|
},
|
|
2758
|
-
ReactComponent: TrackBlocks,
|
|
2759
2856
|
},
|
|
2760
2857
|
]
|
|
2761
2858
|
: []
|
|
@@ -2803,7 +2900,6 @@ function stateModelFactory({
|
|
|
2803
2900
|
heightKey: resizableKinds.has(model.kind) ? model.kind : undefined,
|
|
2804
2901
|
height: self.trackHeight(model.kind),
|
|
2805
2902
|
},
|
|
2806
|
-
ReactComponent: TrackBlocks,
|
|
2807
2903
|
}))
|
|
2808
2904
|
},
|
|
2809
2905
|
|
|
@@ -2846,7 +2942,7 @@ function stateModelFactory({
|
|
|
2846
2942
|
* @returns The character at that position
|
|
2847
2943
|
*/
|
|
2848
2944
|
visibleColToRowLetter(rowName: string, visibleCol: number) {
|
|
2849
|
-
return self.
|
|
2945
|
+
return self.rowSequence(rowName)?.[
|
|
2850
2946
|
this.visibleColToGlobalCol(visibleCol)
|
|
2851
2947
|
]
|
|
2852
2948
|
},
|
|
@@ -2931,7 +3027,7 @@ function stateModelFactory({
|
|
|
2931
3027
|
* @returns The global column index in the full MSA, or undefined
|
|
2932
3028
|
*/
|
|
2933
3029
|
seqPosToGlobalCol(rowName: string, seqPos: number) {
|
|
2934
|
-
const seq = self.
|
|
3030
|
+
const seq = self.rowSequence(rowName)
|
|
2935
3031
|
if (seq === undefined) {
|
|
2936
3032
|
return undefined
|
|
2937
3033
|
}
|
|
@@ -3180,7 +3276,7 @@ function stateModelFactory({
|
|
|
3180
3276
|
get treeScaleBar() {
|
|
3181
3277
|
return scaleBarLength(
|
|
3182
3278
|
self.pxPerBranchLength,
|
|
3183
|
-
self.
|
|
3279
|
+
self.effectiveTreeAreaWidth - self.marginLeft * 2,
|
|
3184
3280
|
)
|
|
3185
3281
|
},
|
|
3186
3282
|
/**
|
|
@@ -3239,7 +3335,10 @@ function stateModelFactory({
|
|
|
3239
3335
|
)
|
|
3240
3336
|
},
|
|
3241
3337
|
get annotationsByRow() {
|
|
3242
|
-
|
|
3338
|
+
const { rowNamesSet } = self
|
|
3339
|
+
return groupBy(this.filteredAnnotations, r =>
|
|
3340
|
+
annotationRowName(r.id, rowNamesSet),
|
|
3341
|
+
)
|
|
3243
3342
|
},
|
|
3244
3343
|
}))
|
|
3245
3344
|
.views(self => ({
|
|
@@ -3330,7 +3429,9 @@ function stateModelFactory({
|
|
|
3330
3429
|
const { annotations, rowNamesSet } = self
|
|
3331
3430
|
return self.dataInitialized &&
|
|
3332
3431
|
annotations.length > 0 &&
|
|
3333
|
-
!annotations.some(a =>
|
|
3432
|
+
!annotations.some(a =>
|
|
3433
|
+
rowNamesSet.has(annotationRowName(a.id, rowNamesSet)),
|
|
3434
|
+
)
|
|
3334
3435
|
? [
|
|
3335
3436
|
`0 of ${annotations.length} annotations name a row in this alignment`,
|
|
3336
3437
|
]
|
|
@@ -3470,10 +3571,13 @@ function stateModelFactory({
|
|
|
3470
3571
|
* #action
|
|
3471
3572
|
* swap in a different tree over the same alignment. Clears `collapsed`,
|
|
3472
3573
|
* `rotated` and `showOnly`, since path-derived node ids (node-0-0-1) from the old
|
|
3473
|
-
* 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
|
|
3474
3577
|
*/
|
|
3475
3578
|
replaceTree(newick: string) {
|
|
3476
3579
|
transaction(() => {
|
|
3580
|
+
self.setTreeFilehandle(undefined)
|
|
3477
3581
|
self.collapsed.clear()
|
|
3478
3582
|
self.rotated.clear()
|
|
3479
3583
|
self.setShowOnly(undefined)
|
|
@@ -3501,30 +3605,6 @@ function stateModelFactory({
|
|
|
3501
3605
|
axis,
|
|
3502
3606
|
)
|
|
3503
3607
|
},
|
|
3504
|
-
/**
|
|
3505
|
-
* #action
|
|
3506
|
-
*/
|
|
3507
|
-
zoomOutHorizontal() {
|
|
3508
|
-
this.zoomAtCenter(0.75, 'horizontal')
|
|
3509
|
-
},
|
|
3510
|
-
/**
|
|
3511
|
-
* #action
|
|
3512
|
-
*/
|
|
3513
|
-
zoomInHorizontal() {
|
|
3514
|
-
this.zoomAtCenter(1.5, 'horizontal')
|
|
3515
|
-
},
|
|
3516
|
-
/**
|
|
3517
|
-
* #action
|
|
3518
|
-
*/
|
|
3519
|
-
zoomInVertical() {
|
|
3520
|
-
this.zoomAtCenter(1.5, 'vertical')
|
|
3521
|
-
},
|
|
3522
|
-
/**
|
|
3523
|
-
* #action
|
|
3524
|
-
*/
|
|
3525
|
-
zoomOutVertical() {
|
|
3526
|
-
this.zoomAtCenter(0.75, 'vertical')
|
|
3527
|
-
},
|
|
3528
3608
|
/**
|
|
3529
3609
|
* #action
|
|
3530
3610
|
*/
|
|
@@ -3617,16 +3697,6 @@ function stateModelFactory({
|
|
|
3617
3697
|
self.annotations = annotations
|
|
3618
3698
|
},
|
|
3619
3699
|
|
|
3620
|
-
/**
|
|
3621
|
-
* #action
|
|
3622
|
-
* set the overlay from raw InterProScan results keyed by row name. Kept
|
|
3623
|
-
* for downstream plugins that hold the EBI wire format; new code should
|
|
3624
|
-
* adapt to Annotation[] and call setAnnotations.
|
|
3625
|
-
*/
|
|
3626
|
-
setDomains(data?: Record<string, InterProScanResults>) {
|
|
3627
|
-
this.setAnnotations(data ? interProScanToAnnotations(data) : [])
|
|
3628
|
-
},
|
|
3629
|
-
|
|
3630
3700
|
applyGFFText(gffText: string) {
|
|
3631
3701
|
this.setAnnotations(gffToAnnotations(parseGFF(gffText)))
|
|
3632
3702
|
},
|
|
@@ -3714,7 +3784,7 @@ function stateModelFactory({
|
|
|
3714
3784
|
column: column + 1,
|
|
3715
3785
|
row,
|
|
3716
3786
|
residue: self.visibleColToSeqPosOneBased(row, visibleCol),
|
|
3717
|
-
letter: self.
|
|
3787
|
+
letter: self.rowSequence(row)?.[column],
|
|
3718
3788
|
}
|
|
3719
3789
|
},
|
|
3720
3790
|
/**
|
|
@@ -3903,7 +3973,7 @@ function stateModelFactory({
|
|
|
3903
3973
|
: visibleDomainTypes.map(d => ({
|
|
3904
3974
|
id: d.accession,
|
|
3905
3975
|
label: d.name,
|
|
3906
|
-
color: fillPalette[d.accession]!,
|
|
3976
|
+
color: d.color ?? fillPalette[d.accession]!,
|
|
3907
3977
|
})),
|
|
3908
3978
|
'domains',
|
|
3909
3979
|
)
|
|
@@ -4170,7 +4240,7 @@ function stateModelFactory({
|
|
|
4170
4240
|
* to `end`, gaps included
|
|
4171
4241
|
*/
|
|
4172
4242
|
get selectionFasta() {
|
|
4173
|
-
const { rowNames
|
|
4243
|
+
const { rowNames } = self
|
|
4174
4244
|
const resolved = this.resolvedSelection
|
|
4175
4245
|
const span = this.selectionFileSpan
|
|
4176
4246
|
if (!resolved || !span) {
|
|
@@ -4179,7 +4249,7 @@ function stateModelFactory({
|
|
|
4179
4249
|
const rows = resolved.rowRuns
|
|
4180
4250
|
.flatMap(([first, last]) => rowNames.slice(first, last + 1))
|
|
4181
4251
|
.flatMap(name => {
|
|
4182
|
-
const seq =
|
|
4252
|
+
const seq = self.rowSequence(name)
|
|
4183
4253
|
return seq === undefined ? [] : [[name, seq] as const]
|
|
4184
4254
|
})
|
|
4185
4255
|
return blockFasta(rows, span.start, span.end)
|
|
@@ -4276,7 +4346,7 @@ function stateModelFactory({
|
|
|
4276
4346
|
this.visibleDomainTypes.map(d => ({
|
|
4277
4347
|
id: d.accession,
|
|
4278
4348
|
label: d.name,
|
|
4279
|
-
color: fillPalette[d.accession]!,
|
|
4349
|
+
color: d.color ?? fillPalette[d.accession]!,
|
|
4280
4350
|
})),
|
|
4281
4351
|
}
|
|
4282
4352
|
}
|
|
@@ -4453,6 +4523,12 @@ function stateModelFactory({
|
|
|
4453
4523
|
setHideHeader(arg: boolean) {
|
|
4454
4524
|
self.hideHeader = arg
|
|
4455
4525
|
},
|
|
4526
|
+
/**
|
|
4527
|
+
* #action
|
|
4528
|
+
*/
|
|
4529
|
+
setImportForm(arg: boolean) {
|
|
4530
|
+
self.importForm = arg
|
|
4531
|
+
},
|
|
4456
4532
|
/**
|
|
4457
4533
|
* #action
|
|
4458
4534
|
* focus the subtree a click `y` pixels down the tree overview lands on.
|
|
@@ -4587,6 +4663,27 @@ function stateModelFactory({
|
|
|
4587
4663
|
)
|
|
4588
4664
|
})
|
|
4589
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
|
+
},
|
|
4590
4687
|
/**
|
|
4591
4688
|
* #action
|
|
4592
4689
|
* zoom and scroll so the selected columns fill the alignment's width,
|
|
@@ -4718,7 +4815,7 @@ function stateModelFactory({
|
|
|
4718
4815
|
self.numColumns > 0 &&
|
|
4719
4816
|
!(self.treeFilehandle && !self.data.tree)
|
|
4720
4817
|
) {
|
|
4721
|
-
this.
|
|
4818
|
+
this.revealRegion(region)
|
|
4722
4819
|
self.setRegion(undefined)
|
|
4723
4820
|
}
|
|
4724
4821
|
}),
|
|
@@ -4802,7 +4899,9 @@ function stateModelFactory({
|
|
|
4802
4899
|
}
|
|
4803
4900
|
try {
|
|
4804
4901
|
setLoading?.(true)
|
|
4805
|
-
|
|
4902
|
+
if (!optional) {
|
|
4903
|
+
self.setError(undefined)
|
|
4904
|
+
}
|
|
4806
4905
|
const text = await fetchTextWithProgress(
|
|
4807
4906
|
openLocation(filehandle),
|
|
4808
4907
|
status => {
|
|
@@ -4942,52 +5041,6 @@ function stateModelFactory({
|
|
|
4942
5041
|
self.columns
|
|
4943
5042
|
}),
|
|
4944
5043
|
)
|
|
4945
|
-
|
|
4946
|
-
// with autoTreeAreaWidth and no tree drawn, size the tree area to the
|
|
4947
|
-
// labels. Gated on noTree/!drawTree so it does not loop with the
|
|
4948
|
-
// treeWidth autorun below
|
|
4949
|
-
addDisposer(
|
|
4950
|
-
self,
|
|
4951
|
-
autorun(() => {
|
|
4952
|
-
if (
|
|
4953
|
-
self.autoTreeAreaWidth &&
|
|
4954
|
-
(self.noTree || !self.drawTree) &&
|
|
4955
|
-
self.labelsWidth
|
|
4956
|
-
) {
|
|
4957
|
-
self.setTreeAreaWidth(
|
|
4958
|
-
self.labelsWidth + self.marginLeft + 12 + self.cladeGutterWidth,
|
|
4959
|
-
)
|
|
4960
|
-
}
|
|
4961
|
-
}),
|
|
4962
|
-
)
|
|
4963
|
-
|
|
4964
|
-
// treeWidth follows the tree area less the labels. A non-default
|
|
4965
|
-
// snapshot treeWidth (jbrowse-plugin-msaview opens at 100 in a 200px
|
|
4966
|
-
// area) is kept until the tree area changes. Not a getter, because
|
|
4967
|
-
// labelsWidth is measured off leaves laid out against treeWidth.
|
|
4968
|
-
let pinnedAreaWidth =
|
|
4969
|
-
self.treeWidth === defaultTreeWidth ? undefined : self.treeAreaWidth
|
|
4970
|
-
addDisposer(
|
|
4971
|
-
self,
|
|
4972
|
-
autorun(() => {
|
|
4973
|
-
const areaWidth = self.treeAreaWidth
|
|
4974
|
-
const labelsWidth = self.labelsWidth
|
|
4975
|
-
if (pinnedAreaWidth === areaWidth) {
|
|
4976
|
-
return
|
|
4977
|
-
}
|
|
4978
|
-
pinnedAreaWidth = undefined
|
|
4979
|
-
self.setTreeWidth(
|
|
4980
|
-
Math.max(
|
|
4981
|
-
50,
|
|
4982
|
-
areaWidth -
|
|
4983
|
-
labelsWidth -
|
|
4984
|
-
10 -
|
|
4985
|
-
self.marginLeft -
|
|
4986
|
-
self.cladeGutterWidth,
|
|
4987
|
-
),
|
|
4988
|
-
)
|
|
4989
|
-
}),
|
|
4990
|
-
)
|
|
4991
5044
|
},
|
|
4992
5045
|
}))
|
|
4993
5046
|
.postProcessSnapshot(({ data, columnTracks, ...rest }) => ({
|