react-msaview 8.4.1 → 9.0.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/bundle/index.js +66 -66
- package/bundle/index.js.map +4 -4
- package/dist/components/MSAView.js +2 -2
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +1 -3
- package/dist/components/MSAViewer.js +1 -2
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/ResizeHandles.js +1 -1
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/Track.js +1 -1
- package/dist/components/Track.js.map +1 -1
- package/dist/components/dialogs/ExportSVGDialog.js +2 -2
- package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
- package/dist/components/header/settingsMenuItems.js +4 -1
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/tree/CladeLabels.js +3 -3
- 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 +8 -10
- package/dist/components/tree/cladeBrackets.js +7 -6
- 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 -26
- 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/constants.d.ts +6 -3
- package/dist/constants.js +16 -6
- package/dist/constants.js.map +1 -1
- package/dist/measureTextCanvas.js +2 -2
- package/dist/measureTextCanvas.js.map +1 -1
- package/dist/model/treeModel.d.ts +14 -20
- package/dist/model/treeModel.js +16 -23
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +72 -37
- package/dist/model.js +108 -100
- package/dist/model.js.map +1 -1
- package/dist/renderToSvg.js +24 -22
- package/dist/renderToSvg.js.map +1 -1
- package/dist/svgTestUtil.d.ts +36 -28
- package/dist/tipLabels.d.ts +8 -0
- package/dist/tipLabels.js +46 -0
- package/dist/tipLabels.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/src/components/MSAView.tsx +2 -2
- package/src/components/MSAViewer.tsx +1 -5
- package/src/components/ResizeHandles.tsx +1 -1
- package/src/components/Track.tsx +1 -1
- package/src/components/dialogs/ExportSVGDialog.tsx +2 -2
- package/src/components/header/settingsMenuItems.ts +8 -0
- package/src/components/tree/CladeLabels.tsx +3 -3
- 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 +10 -17
- package/src/components/tree/clickMap.ts +2 -0
- package/src/components/tree/ellipsize.ts +24 -0
- package/src/components/tree/renderTreeCanvas.ts +42 -30
- package/src/components/tree/renderTreeMouseover.ts +1 -1
- package/src/constants.ts +17 -6
- package/src/measureTextCanvas.ts +2 -2
- package/src/model/treeModel.ts +17 -27
- package/src/model.ts +129 -124
- package/src/renderToSvg.tsx +24 -22
- package/src/tipLabels.ts +51 -0
- package/src/version.ts +1 -1
package/src/model.ts
CHANGED
|
@@ -56,15 +56,19 @@ import {
|
|
|
56
56
|
defaultShowDomainLegend,
|
|
57
57
|
defaultShowDomains,
|
|
58
58
|
defaultSubFeatureRows,
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
fontSizeFor,
|
|
60
|
+
labelFitFontSize,
|
|
61
61
|
maxCellSize,
|
|
62
|
+
maxFitTreeWidth,
|
|
63
|
+
maxTreeAreaShare,
|
|
62
64
|
maxInlineSnapshotBytes as defaultMaxInlineSnapshotBytes,
|
|
63
65
|
maxNeighborJoiningRows,
|
|
64
66
|
minColWidth,
|
|
65
67
|
minLetterColWidth,
|
|
66
68
|
minLetterRowHeight,
|
|
67
69
|
minRowHeight,
|
|
70
|
+
minTreeWidth,
|
|
71
|
+
pxPerTreeDepth,
|
|
68
72
|
rowPanelHeaderHeight,
|
|
69
73
|
rowTintAlpha,
|
|
70
74
|
scrollZoomAxes,
|
|
@@ -95,7 +99,6 @@ import {
|
|
|
95
99
|
sort,
|
|
96
100
|
sum as hierarchySum,
|
|
97
101
|
} from './hierarchy.ts'
|
|
98
|
-
import { measureTextCanvas } from './measureTextCanvas.ts'
|
|
99
102
|
import { DataModelF } from './model/DataModel.ts'
|
|
100
103
|
import { DialogQueueSessionMixin } from './model/DialogQueue.ts'
|
|
101
104
|
import { MSAModelF } from './model/msaModel.ts'
|
|
@@ -114,6 +117,7 @@ import { resolveScale } from './scales.ts'
|
|
|
114
117
|
import { buildSeqPosIndex } from './seqPosToGlobalCol.ts'
|
|
115
118
|
import { maxBitsFor } from './sequenceLogo.ts'
|
|
116
119
|
import { stripDefault } from './stripDefault.ts'
|
|
120
|
+
import { treeTips, widestLabel } from './tipLabels.ts'
|
|
117
121
|
import {
|
|
118
122
|
computeRowInsertions,
|
|
119
123
|
dropBlanks,
|
|
@@ -528,7 +532,6 @@ export const preservedOnReset = new Set([
|
|
|
528
532
|
'drawNodeLabels',
|
|
529
533
|
'showTreeOverview',
|
|
530
534
|
'overviewHeight',
|
|
531
|
-
'autoTreeAreaWidth',
|
|
532
535
|
'turnedOffTracks',
|
|
533
536
|
'trackHeights',
|
|
534
537
|
'hideGaps',
|
|
@@ -1804,10 +1807,10 @@ function stateModelFactory({
|
|
|
1804
1807
|
* the row table: extra fields per row, keyed by row name, which the
|
|
1805
1808
|
* `encodings` channels read. It is stored as the JSON string
|
|
1806
1809
|
* `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.
|
|
1810
|
+
* inline size limit and `treeMetadataFilehandle` cover it. The tree area
|
|
1811
|
+
* fit reads it on every layout, so a malformed user-supplied file
|
|
1812
|
+
* returns {} instead of throwing out of rendering, and every field is a
|
|
1813
|
+
* string by the time a scale sorts it.
|
|
1811
1814
|
*/
|
|
1812
1815
|
get rowData(): Record<string, Record<string, string> | undefined> {
|
|
1813
1816
|
const text = self.data.treeMetadata
|
|
@@ -1938,11 +1941,107 @@ function stateModelFactory({
|
|
|
1938
1941
|
* where no clade draws a bar or a label.
|
|
1939
1942
|
*/
|
|
1940
1943
|
get cladeGutterWidth() {
|
|
1941
|
-
return cladeGutterWidth(
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1944
|
+
return cladeGutterWidth(self.clades)
|
|
1945
|
+
},
|
|
1946
|
+
/**
|
|
1947
|
+
* #getter
|
|
1948
|
+
* whether a tree draws beside the labels: false for a file with no
|
|
1949
|
+
* tree, and with drawTree off
|
|
1950
|
+
*/
|
|
1951
|
+
get treeDrawn() {
|
|
1952
|
+
return self.drawTree && !this.noTree
|
|
1953
|
+
},
|
|
1954
|
+
/**
|
|
1955
|
+
* #getter
|
|
1956
|
+
* the leaf names of the whole tree and its depth in levels. The fit
|
|
1957
|
+
* reads these, so a collapse or a focus leaves the tree area as it is
|
|
1958
|
+
*/
|
|
1959
|
+
get treeTips() {
|
|
1960
|
+
return treeTips(this.tree)
|
|
1961
|
+
},
|
|
1962
|
+
/**
|
|
1963
|
+
* #getter
|
|
1964
|
+
* the widest tip label at labelFitFontSize, measured whether or not a
|
|
1965
|
+
* zoom hides the labels
|
|
1966
|
+
*/
|
|
1967
|
+
get maxTipLabelWidth() {
|
|
1968
|
+
return widestLabel(
|
|
1969
|
+
this.treeTips.names.map(name => this.rowDataOf(name)?.genome || name),
|
|
1970
|
+
labelFitFontSize,
|
|
1971
|
+
)
|
|
1972
|
+
},
|
|
1973
|
+
/**
|
|
1974
|
+
* #getter
|
|
1975
|
+
* the px the tip labels take across the tree area: the widest label at
|
|
1976
|
+
* labelFitFontSize and the gaps either side of it, or 0 with drawLabels
|
|
1977
|
+
* off
|
|
1978
|
+
*/
|
|
1979
|
+
get labelColumnWidth() {
|
|
1980
|
+
return self.drawLabels
|
|
1981
|
+
? this.maxTipLabelWidth + (this.treeDrawn ? 10 : 6)
|
|
1982
|
+
: 0
|
|
1983
|
+
},
|
|
1984
|
+
/**
|
|
1985
|
+
* #getter
|
|
1986
|
+
* the tree area width while treeAreaWidth is undefined: the label column
|
|
1987
|
+
* beside a tree that widens with its depth, up to maxTreeAreaShare of the
|
|
1988
|
+
* view, or the room the row panels leave where there is no alignment.
|
|
1989
|
+
* Where that binds, the tree keeps minTreeWidth and the renderer
|
|
1990
|
+
* ellipsizes the labels
|
|
1991
|
+
*/
|
|
1992
|
+
get fittedTreeAreaWidth() {
|
|
1993
|
+
const chrome = self.marginLeft + this.cladeGutterWidth
|
|
1994
|
+
const tree = this.treeDrawn
|
|
1995
|
+
? (self.treeWidth ??
|
|
1996
|
+
clamp(
|
|
1997
|
+
this.treeTips.depth * pxPerTreeDepth,
|
|
1998
|
+
minTreeWidth,
|
|
1999
|
+
maxFitTreeWidth,
|
|
2000
|
+
))
|
|
2001
|
+
: 0
|
|
2002
|
+
const fit = chrome + tree + this.labelColumnWidth
|
|
2003
|
+
const room = self.viewInitialized
|
|
2004
|
+
? this.numColumns > 0
|
|
2005
|
+
? self.width * maxTreeAreaShare
|
|
2006
|
+
: self.width - this.rowPanelsWidth - self.resizeHandleWidth
|
|
2007
|
+
: Infinity
|
|
2008
|
+
const cap = Math.max(room, chrome + Math.min(tree, minTreeWidth))
|
|
2009
|
+
return Math.ceil(Math.min(fit, cap))
|
|
2010
|
+
},
|
|
2011
|
+
/**
|
|
2012
|
+
* #getter
|
|
2013
|
+
* the width of the area the tree and its labels draw in, px:
|
|
2014
|
+
* treeAreaWidth where set, the fit otherwise
|
|
2015
|
+
*/
|
|
2016
|
+
get effectiveTreeAreaWidth() {
|
|
2017
|
+
return self.treeAreaWidth ?? this.fittedTreeAreaWidth
|
|
2018
|
+
},
|
|
2019
|
+
/**
|
|
2020
|
+
* #getter
|
|
2021
|
+
* the width of the tree, px: treeWidth where set, otherwise the tree
|
|
2022
|
+
* area less the label column, which a zoom hiding the labels gives to
|
|
2023
|
+
* the tree
|
|
2024
|
+
*/
|
|
2025
|
+
get effectiveTreeWidth() {
|
|
2026
|
+
const labels = this.showTreeText ? this.labelColumnWidth : 0
|
|
2027
|
+
return (
|
|
2028
|
+
self.treeWidth ??
|
|
2029
|
+
Math.max(
|
|
2030
|
+
minTreeWidth,
|
|
2031
|
+
Math.floor(
|
|
2032
|
+
this.effectiveTreeAreaWidth -
|
|
2033
|
+
self.marginLeft -
|
|
2034
|
+
this.cladeGutterWidth -
|
|
2035
|
+
labels,
|
|
2036
|
+
),
|
|
2037
|
+
)
|
|
2038
|
+
)
|
|
2039
|
+
},
|
|
2040
|
+
/**
|
|
2041
|
+
* #getter
|
|
2042
|
+
*/
|
|
2043
|
+
get showTreeText() {
|
|
2044
|
+
return self.drawLabels && self.rowHeight >= minLetterRowHeight
|
|
1946
2045
|
},
|
|
1947
2046
|
/**
|
|
1948
2047
|
* #getter
|
|
@@ -2125,7 +2224,7 @@ function stateModelFactory({
|
|
|
2125
2224
|
get msaAreaWidth() {
|
|
2126
2225
|
return (
|
|
2127
2226
|
self.width -
|
|
2128
|
-
|
|
2227
|
+
this.effectiveTreeAreaWidth -
|
|
2129
2228
|
this.rowPanelsWidth -
|
|
2130
2229
|
self.resizeHandleWidth
|
|
2131
2230
|
)
|
|
@@ -2137,7 +2236,9 @@ function stateModelFactory({
|
|
|
2137
2236
|
* margin and the bracket gutter
|
|
2138
2237
|
*/
|
|
2139
2238
|
get treeAreaWidthMinusMargin() {
|
|
2140
|
-
return
|
|
2239
|
+
return (
|
|
2240
|
+
this.effectiveTreeAreaWidth - self.marginLeft - this.cladeGutterWidth
|
|
2241
|
+
)
|
|
2141
2242
|
},
|
|
2142
2243
|
/**
|
|
2143
2244
|
* #getter
|
|
@@ -2282,7 +2383,7 @@ function stateModelFactory({
|
|
|
2282
2383
|
* #getter
|
|
2283
2384
|
*/
|
|
2284
2385
|
get fontSize() {
|
|
2285
|
-
return
|
|
2386
|
+
return fontSizeFor(self.rowHeight)
|
|
2286
2387
|
},
|
|
2287
2388
|
/**
|
|
2288
2389
|
* #getter
|
|
@@ -2388,17 +2489,18 @@ function stateModelFactory({
|
|
|
2388
2489
|
/**
|
|
2389
2490
|
* #getter
|
|
2390
2491
|
* `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.
|
|
2492
|
+
* pixels through `rowHeight` and `effectiveTreeWidth`. A zoom leaves it
|
|
2493
|
+
* and every node in it as they are, and an observer reading a node's `x`
|
|
2494
|
+
* or `len` observes the size it scales by.
|
|
2394
2495
|
*/
|
|
2395
2496
|
get treeLayout() {
|
|
2497
|
+
const treeWidth = () => this.effectiveTreeWidth
|
|
2396
2498
|
return layoutTree(this.root, {
|
|
2397
2499
|
get rowHeight() {
|
|
2398
2500
|
return self.rowHeight
|
|
2399
2501
|
},
|
|
2400
2502
|
get treeWidth() {
|
|
2401
|
-
return
|
|
2503
|
+
return treeWidth()
|
|
2402
2504
|
},
|
|
2403
2505
|
})
|
|
2404
2506
|
},
|
|
@@ -2436,11 +2538,11 @@ function stateModelFactory({
|
|
|
2436
2538
|
|
|
2437
2539
|
/**
|
|
2438
2540
|
* #getter
|
|
2439
|
-
* x-position of the farthest tip in a phylogram, px:
|
|
2440
|
-
* a tree with no branch lengths (drawn as a cladogram)
|
|
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)
|
|
2441
2543
|
*/
|
|
2442
2544
|
get maxBranchLength() {
|
|
2443
|
-
return this.rootToTipLength ?
|
|
2545
|
+
return this.rootToTipLength ? this.effectiveTreeWidth : 0
|
|
2444
2546
|
},
|
|
2445
2547
|
|
|
2446
2548
|
/**
|
|
@@ -2450,7 +2552,9 @@ function stateModelFactory({
|
|
|
2450
2552
|
*/
|
|
2451
2553
|
get pxPerBranchLength() {
|
|
2452
2554
|
const max = this.rootToTipLength
|
|
2453
|
-
return this.showBranchLenEffective && max
|
|
2555
|
+
return this.showBranchLenEffective && max
|
|
2556
|
+
? this.effectiveTreeWidth / max
|
|
2557
|
+
: 0
|
|
2454
2558
|
},
|
|
2455
2559
|
|
|
2456
2560
|
/**
|
|
@@ -2497,12 +2601,6 @@ function stateModelFactory({
|
|
|
2497
2601
|
self.colWidth > self.rowHeight / 2
|
|
2498
2602
|
)
|
|
2499
2603
|
},
|
|
2500
|
-
/**
|
|
2501
|
-
* #getter
|
|
2502
|
-
*/
|
|
2503
|
-
get showTreeText() {
|
|
2504
|
-
return self.drawLabels && self.rowHeight >= minLetterRowHeight
|
|
2505
|
-
},
|
|
2506
2604
|
}))
|
|
2507
2605
|
.views(self => {
|
|
2508
2606
|
// a spec is a frozen value, so it keys its computed until setColumnTracks
|
|
@@ -2639,53 +2737,6 @@ function stateModelFactory({
|
|
|
2639
2737
|
}
|
|
2640
2738
|
})
|
|
2641
2739
|
.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
2740
|
/**
|
|
2690
2741
|
* #getter
|
|
2691
2742
|
*/
|
|
@@ -3180,7 +3231,7 @@ function stateModelFactory({
|
|
|
3180
3231
|
get treeScaleBar() {
|
|
3181
3232
|
return scaleBarLength(
|
|
3182
3233
|
self.pxPerBranchLength,
|
|
3183
|
-
self.
|
|
3234
|
+
self.effectiveTreeAreaWidth - self.marginLeft * 2,
|
|
3184
3235
|
)
|
|
3185
3236
|
},
|
|
3186
3237
|
/**
|
|
@@ -4942,52 +4993,6 @@ function stateModelFactory({
|
|
|
4942
4993
|
self.columns
|
|
4943
4994
|
}),
|
|
4944
4995
|
)
|
|
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
4996
|
},
|
|
4992
4997
|
}))
|
|
4993
4998
|
.postProcessSnapshot(({ data, columnTracks, ...rest }) => ({
|
package/src/renderToSvg.tsx
CHANGED
|
@@ -90,7 +90,7 @@ function getLayout(model: MsaViewModel, opts: ExportSvgOptions): Layout {
|
|
|
90
90
|
scrollY,
|
|
91
91
|
totalWidth,
|
|
92
92
|
totalHeight,
|
|
93
|
-
|
|
93
|
+
effectiveTreeAreaWidth,
|
|
94
94
|
rowPanelsWidth,
|
|
95
95
|
rowPanelsHeaderHeight,
|
|
96
96
|
totalTrackAreaHeight,
|
|
@@ -117,7 +117,7 @@ function getLayout(model: MsaViewModel, opts: ExportSvgOptions): Layout {
|
|
|
117
117
|
rowPanelsHeaderHeight,
|
|
118
118
|
treeOverviewHeight + (treeScaleBar ? treeScaleBarHeight : 0),
|
|
119
119
|
)
|
|
120
|
-
const gutterWidth =
|
|
120
|
+
const gutterWidth = effectiveTreeAreaWidth + rowPanelsWidth
|
|
121
121
|
|
|
122
122
|
// width stays content-only; the legend occupies an extra column added at the
|
|
123
123
|
// svg root in MsaSvg. The viewport export takes the alignment canvas's own
|
|
@@ -195,7 +195,7 @@ function MsaSvg({
|
|
|
195
195
|
layers: LayerMap
|
|
196
196
|
}) {
|
|
197
197
|
const { width, trackHeight, includeMinimap, legendWidth, topHeight } = layout
|
|
198
|
-
const {
|
|
198
|
+
const { effectiveTreeAreaWidth, rowPanelsWidth } = model
|
|
199
199
|
const totalWidth = width + legendWidth
|
|
200
200
|
const legendTop = topHeight
|
|
201
201
|
// a short alignment with many legend entries has a key taller than its rows,
|
|
@@ -241,7 +241,7 @@ function MsaSvg({
|
|
|
241
241
|
{includeMinimap ? (
|
|
242
242
|
<g
|
|
243
243
|
id="minimap-panel"
|
|
244
|
-
transform={`translate(${
|
|
244
|
+
transform={`translate(${effectiveTreeAreaWidth + rowPanelsWidth} 0)`}
|
|
245
245
|
>
|
|
246
246
|
<MinimapSVG model={model} theme={theme} />
|
|
247
247
|
</g>
|
|
@@ -288,20 +288,20 @@ function TreeOverviewSVG({
|
|
|
288
288
|
Context: typeof ContextType
|
|
289
289
|
layers: LayerMap
|
|
290
290
|
}) {
|
|
291
|
-
const {
|
|
292
|
-
const ctx = new Context(
|
|
291
|
+
const { effectiveTreeAreaWidth, overviewHeight, id } = model
|
|
292
|
+
const ctx = new Context(effectiveTreeAreaWidth, overviewHeight)
|
|
293
293
|
renderTreeOverview({
|
|
294
294
|
model,
|
|
295
295
|
ctx,
|
|
296
296
|
theme,
|
|
297
|
-
width:
|
|
297
|
+
width: effectiveTreeAreaWidth,
|
|
298
298
|
height: overviewHeight,
|
|
299
299
|
})
|
|
300
300
|
return (
|
|
301
301
|
<ClipGroup
|
|
302
302
|
panelId="tree-overview"
|
|
303
303
|
clipId={`tree-overview-${id}`}
|
|
304
|
-
width={
|
|
304
|
+
width={effectiveTreeAreaWidth}
|
|
305
305
|
height={overviewHeight}
|
|
306
306
|
ctx={ctx}
|
|
307
307
|
layers={layers}
|
|
@@ -349,14 +349,14 @@ function RowPanelHeadersSVG({
|
|
|
349
349
|
theme: Theme
|
|
350
350
|
bandHeight: number
|
|
351
351
|
}) {
|
|
352
|
-
const { resolvedRowPanels,
|
|
352
|
+
const { resolvedRowPanels, effectiveTreeAreaWidth, fontSize } = model
|
|
353
353
|
const y = bandHeight - 4
|
|
354
354
|
return resolvedRowPanels.length === 0 ? null : (
|
|
355
355
|
<g id="rowpanel-headers">
|
|
356
356
|
{resolvedRowPanels.map(panel => {
|
|
357
357
|
const across = headerRunsAcross(panel.width)
|
|
358
358
|
const size = across ? fontSize : Math.min(fontSize, panel.width)
|
|
359
|
-
const center =
|
|
359
|
+
const center = effectiveTreeAreaWidth + panel.offsetX + panel.width / 2
|
|
360
360
|
const x = across ? center : center + size / 3
|
|
361
361
|
const room = across ? panel.width : y - LEGEND_PAD
|
|
362
362
|
const maxChars = Math.floor(room / (size * CHAR_WIDTH))
|
|
@@ -409,13 +409,13 @@ function CladeLabelsSVG({
|
|
|
409
409
|
resolvedClades,
|
|
410
410
|
cladeGutterWidth,
|
|
411
411
|
rowHeight,
|
|
412
|
-
|
|
412
|
+
effectiveTreeAreaWidth,
|
|
413
413
|
fontSize,
|
|
414
414
|
} = model
|
|
415
415
|
if (cladeGutterWidth === 0) {
|
|
416
416
|
return null
|
|
417
417
|
}
|
|
418
|
-
const gutterLeft =
|
|
418
|
+
const gutterLeft = effectiveTreeAreaWidth - cladeGutterWidth
|
|
419
419
|
return (
|
|
420
420
|
<g id="clade-labels">
|
|
421
421
|
{resolvedClades.map((clade, index) => {
|
|
@@ -427,7 +427,9 @@ function CladeLabelsSVG({
|
|
|
427
427
|
const top = rows[0] * rowHeight - offsetY
|
|
428
428
|
const height = cladeHeight(clade, rowHeight)
|
|
429
429
|
const horizontal = cladeLabelHorizontal(clade, rowHeight, fontSize)
|
|
430
|
-
const room = horizontal
|
|
430
|
+
const room = horizontal
|
|
431
|
+
? effectiveTreeAreaWidth - left - bracketGap
|
|
432
|
+
: height
|
|
431
433
|
const x = horizontal ? left : left + fontSize
|
|
432
434
|
const y = horizontal ? top + height / 2 + fontSize / 3 : top + height
|
|
433
435
|
return (
|
|
@@ -520,9 +522,9 @@ interface LayerProps {
|
|
|
520
522
|
|
|
521
523
|
function CoreRendering({ model, theme, layout, Context, layers }: LayerProps) {
|
|
522
524
|
const { contentHeight, offsetX, offsetY, msaAreaWidth } = layout
|
|
523
|
-
const {
|
|
525
|
+
const { effectiveTreeAreaWidth, rowPanelsWidth, id } = model
|
|
524
526
|
|
|
525
|
-
const treeCtx = new Context(
|
|
527
|
+
const treeCtx = new Context(effectiveTreeAreaWidth, contentHeight)
|
|
526
528
|
renderTreeCanvas({
|
|
527
529
|
model,
|
|
528
530
|
theme,
|
|
@@ -588,7 +590,7 @@ function CoreRendering({ model, theme, layout, Context, layers }: LayerProps) {
|
|
|
588
590
|
<ClipGroup
|
|
589
591
|
panelId="tree-panel"
|
|
590
592
|
clipId={`tree-${id}`}
|
|
591
|
-
width={
|
|
593
|
+
width={effectiveTreeAreaWidth}
|
|
592
594
|
height={contentHeight}
|
|
593
595
|
ctx={treeCtx}
|
|
594
596
|
layers={layers}
|
|
@@ -600,7 +602,7 @@ function CoreRendering({ model, theme, layout, Context, layers }: LayerProps) {
|
|
|
600
602
|
clipId={`rowpanels-${id}`}
|
|
601
603
|
width={rowPanelsWidth}
|
|
602
604
|
height={contentHeight}
|
|
603
|
-
transform={`translate(${
|
|
605
|
+
transform={`translate(${effectiveTreeAreaWidth} 0)`}
|
|
604
606
|
ctx={rowPanelsCtx}
|
|
605
607
|
layers={layers}
|
|
606
608
|
/>
|
|
@@ -610,7 +612,7 @@ function CoreRendering({ model, theme, layout, Context, layers }: LayerProps) {
|
|
|
610
612
|
clipId={`msa-${id}`}
|
|
611
613
|
width={msaAreaWidth}
|
|
612
614
|
height={contentHeight}
|
|
613
|
-
transform={`translate(${
|
|
615
|
+
transform={`translate(${effectiveTreeAreaWidth + rowPanelsWidth} 0)`}
|
|
614
616
|
ctx={msaCtx}
|
|
615
617
|
layers={layers}
|
|
616
618
|
underlay={raster}
|
|
@@ -677,7 +679,7 @@ function rasterBackground({
|
|
|
677
679
|
|
|
678
680
|
function TrackRendering({ model, theme, layout, Context, layers }: LayerProps) {
|
|
679
681
|
const { trackHeight, offsetX, msaAreaWidth } = layout
|
|
680
|
-
const {
|
|
682
|
+
const { effectiveTreeAreaWidth, rowPanelsWidth, id } = model
|
|
681
683
|
|
|
682
684
|
const ctx = new Context(msaAreaWidth, trackHeight)
|
|
683
685
|
renderAllTracks({
|
|
@@ -692,7 +694,7 @@ function TrackRendering({ model, theme, layout, Context, layers }: LayerProps) {
|
|
|
692
694
|
return (
|
|
693
695
|
<>
|
|
694
696
|
<TrackLabelsSVG model={model} theme={theme} />
|
|
695
|
-
<g transform={`translate(${
|
|
697
|
+
<g transform={`translate(${effectiveTreeAreaWidth + rowPanelsWidth} 0)`}>
|
|
696
698
|
<ClipGroup
|
|
697
699
|
panelId="tracks-panel"
|
|
698
700
|
clipId={`tracks-${id}`}
|
|
@@ -716,7 +718,7 @@ function TrackLabelsSVG({
|
|
|
716
718
|
model: MsaViewModel
|
|
717
719
|
theme: Theme
|
|
718
720
|
}) {
|
|
719
|
-
const { turnedOnTracks,
|
|
721
|
+
const { turnedOnTracks, effectiveTreeAreaWidth, fontSize, drawLabels } = model
|
|
720
722
|
if (!drawLabels) {
|
|
721
723
|
return null
|
|
722
724
|
}
|
|
@@ -734,7 +736,7 @@ function TrackLabelsSVG({
|
|
|
734
736
|
return size < 5 ? null : (
|
|
735
737
|
<text
|
|
736
738
|
key={id}
|
|
737
|
-
x={
|
|
739
|
+
x={effectiveTreeAreaWidth - 4}
|
|
738
740
|
y={baseline}
|
|
739
741
|
fontSize={size}
|
|
740
742
|
textAnchor="end"
|
package/src/tipLabels.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { measureTextCanvas } from './measureTextCanvas.ts'
|
|
2
|
+
|
|
3
|
+
import type { NodeWithIds } from './types.ts'
|
|
4
|
+
|
|
5
|
+
// A label's width follows its length closely enough that the widest one is
|
|
6
|
+
// among the longest few, so a 230k-tip tree measures this many strings.
|
|
7
|
+
const measuredLabels = 100
|
|
8
|
+
|
|
9
|
+
/** the leaf names of a tree, and its depth in levels from the root */
|
|
10
|
+
export function treeTips(tree: NodeWithIds) {
|
|
11
|
+
const names: string[] = []
|
|
12
|
+
let depth = 0
|
|
13
|
+
const stack: [NodeWithIds, number][] = [[tree, 0]]
|
|
14
|
+
for (let entry = stack.pop(); entry; entry = stack.pop()) {
|
|
15
|
+
const [node, level] = entry
|
|
16
|
+
const { children } = node
|
|
17
|
+
if (children.length > 0) {
|
|
18
|
+
for (const child of children) {
|
|
19
|
+
stack.push([child, level + 1])
|
|
20
|
+
}
|
|
21
|
+
} else {
|
|
22
|
+
names.push(node.name)
|
|
23
|
+
depth = Math.max(depth, level)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return { names, depth }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** the width of the widest label at `fontSize`, measuring the longest few */
|
|
30
|
+
export function widestLabel(labels: string[], fontSize: number) {
|
|
31
|
+
const counts: number[] = []
|
|
32
|
+
for (const label of labels) {
|
|
33
|
+
counts[label.length] = (counts[label.length] ?? 0) + 1
|
|
34
|
+
}
|
|
35
|
+
let shortest = counts.length
|
|
36
|
+
for (let seen = 0; shortest > 0 && seen < measuredLabels;) {
|
|
37
|
+
shortest--
|
|
38
|
+
seen += counts[shortest] ?? 0
|
|
39
|
+
}
|
|
40
|
+
let widest = 0
|
|
41
|
+
let ties = 0
|
|
42
|
+
for (const label of labels) {
|
|
43
|
+
if (
|
|
44
|
+
label.length > shortest ||
|
|
45
|
+
(label.length === shortest && ties++ < measuredLabels)
|
|
46
|
+
) {
|
|
47
|
+
widest = Math.max(widest, measureTextCanvas(label, fontSize))
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return widest
|
|
51
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '
|
|
1
|
+
export const version = '9.0.0'
|