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/renderToSvg.tsx
CHANGED
|
@@ -6,7 +6,7 @@ import { when } from 'mobx'
|
|
|
6
6
|
import { visibleRowRange } from './components/getVisibleLeaves.ts'
|
|
7
7
|
import MinimapSVG from './components/minimap/MinimapSVG.tsx'
|
|
8
8
|
import { legendRows } from './components/msa/legendRows.ts'
|
|
9
|
-
import {
|
|
9
|
+
import { rasterApplies, rasterImageHref } from './components/msa/msaRaster.ts'
|
|
10
10
|
import { renderBoxFeatureCanvasBlock } from './components/msa/renderBoxFeatureCanvasBlock.ts'
|
|
11
11
|
import { renderPersistentHighlights } from './components/msa/renderHighlights.ts'
|
|
12
12
|
import { renderMSABlock } from './components/msa/renderMSABlock.ts'
|
|
@@ -15,141 +15,32 @@ import { headerRunsAcross } from './components/rowpanels/headerLayout.ts'
|
|
|
15
15
|
import { renderRowPanels } from './components/rowpanels/renderRowPanel.ts'
|
|
16
16
|
import { renderAllTracks } from './components/tracks/drawTracks.ts'
|
|
17
17
|
import ScaleBarSVG from './components/tree/ScaleBarSVG.tsx'
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
cladeHeight,
|
|
21
|
-
cladeInGutter,
|
|
22
|
-
cladeLabelHorizontal,
|
|
23
|
-
cladeLabelOffset,
|
|
24
|
-
} from './components/tree/cladeBrackets.ts'
|
|
18
|
+
import { cladeLabelBoxes } from './components/tree/cladeBrackets.ts'
|
|
19
|
+
import { ellipsize } from './components/tree/ellipsize.ts'
|
|
25
20
|
import { renderTreeCanvas } from './components/tree/renderTreeCanvas.ts'
|
|
26
21
|
import { renderTreeOverview } from './components/tree/renderTreeOverview.ts'
|
|
27
22
|
import { treeScaleBarHeight } from './constants.ts'
|
|
23
|
+
import {
|
|
24
|
+
LEGEND_CHAR_W,
|
|
25
|
+
LEGEND_FONT,
|
|
26
|
+
LEGEND_PAD,
|
|
27
|
+
LEGEND_ROW_H,
|
|
28
|
+
LEGEND_SWATCH,
|
|
29
|
+
LEGEND_TEXT_X,
|
|
30
|
+
exportPageSize,
|
|
31
|
+
getLayout,
|
|
32
|
+
legendHeight,
|
|
33
|
+
} from './exportLayout.ts'
|
|
28
34
|
import { measureTextCanvas } from './measureTextCanvas.ts'
|
|
29
35
|
import { renderToStaticMarkup, svgSafeColors } from './renderToStaticMarkup.ts'
|
|
30
36
|
import { outlineColor } from './util.ts'
|
|
31
37
|
|
|
32
|
-
import type {
|
|
38
|
+
import type { ExportSvgOptions, Layout } from './exportLayout.ts'
|
|
33
39
|
import type { MsaViewModel } from './model.ts'
|
|
34
40
|
import type { Context as ContextType } from '@jbrowse/svgcanvas'
|
|
35
41
|
import type { Theme } from '@mui/material'
|
|
36
42
|
|
|
37
|
-
export
|
|
38
|
-
theme: Theme
|
|
39
|
-
includeMinimap?: boolean
|
|
40
|
-
includeTracks?: boolean
|
|
41
|
-
exportType: 'entire' | 'viewport'
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// legend geometry, shared by the width calculation and the renderer so the
|
|
45
|
-
// reserved column on the right exactly fits the drawn legends
|
|
46
|
-
const LEGEND_PAD = 8
|
|
47
|
-
const LEGEND_ROW_H = 16
|
|
48
|
-
const LEGEND_SWATCH = 12
|
|
49
|
-
const LEGEND_FONT = 12
|
|
50
|
-
const LEGEND_CHAR_W = 7
|
|
51
|
-
|
|
52
|
-
// the width of a character as a fraction of the font size, close enough for a
|
|
53
|
-
// sans-serif average
|
|
54
|
-
const CHAR_WIDTH = 0.6
|
|
55
|
-
|
|
56
|
-
const LEGEND_MAX_W = 360
|
|
57
|
-
const LEGEND_TEXT_X = LEGEND_PAD + LEGEND_SWATCH + 6
|
|
58
|
-
|
|
59
|
-
function getLegendWidth(rows: LegendRow[]) {
|
|
60
|
-
const maxLen = rows.reduce((a, r) => Math.max(a, r.label.length), 0)
|
|
61
|
-
const w = LEGEND_TEXT_X + LEGEND_PAD + maxLen * LEGEND_CHAR_W
|
|
62
|
-
return Math.min(LEGEND_MAX_W, Math.max(120, w))
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function legendHeight(rows: LegendRow[]) {
|
|
66
|
-
return LEGEND_PAD * 2 + rows.length * LEGEND_ROW_H
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// resolved sizes/offsets (in svg user units) for the chosen export, shared by
|
|
70
|
-
// every layer; 'entire' renders the whole alignment unscrolled, 'viewport'
|
|
71
|
-
// mirrors the live scroll position
|
|
72
|
-
interface Layout {
|
|
73
|
-
width: number
|
|
74
|
-
msaAreaWidth: number
|
|
75
|
-
height: number
|
|
76
|
-
contentHeight: number
|
|
77
|
-
trackHeight: number
|
|
78
|
-
// the band the minimap, the row panel headers and the tree overview stacked
|
|
79
|
-
// on the scale bar share across the top
|
|
80
|
-
topHeight: number
|
|
81
|
-
offsetX: number
|
|
82
|
-
offsetY: number
|
|
83
|
-
includeMinimap: boolean
|
|
84
|
-
legendWidth: number
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function getLayout(model: MsaViewModel, opts: ExportSvgOptions): Layout {
|
|
88
|
-
const {
|
|
89
|
-
scrollX,
|
|
90
|
-
scrollY,
|
|
91
|
-
totalWidth,
|
|
92
|
-
totalHeight,
|
|
93
|
-
treeAreaWidth,
|
|
94
|
-
rowPanelsWidth,
|
|
95
|
-
rowPanelsHeaderHeight,
|
|
96
|
-
totalTrackAreaHeight,
|
|
97
|
-
minimapHeight,
|
|
98
|
-
msaAreaHeight,
|
|
99
|
-
msaCanvasWidth,
|
|
100
|
-
showHorizontalScrollbar,
|
|
101
|
-
treeOverviewHeight,
|
|
102
|
-
treeScaleBar,
|
|
103
|
-
} = model
|
|
104
|
-
const trackHeight = opts.includeTracks ? totalTrackAreaHeight : 0
|
|
105
|
-
// the minimap reflects the live viewport scroll position, so it's only
|
|
106
|
-
// meaningful for a viewport export, never for the entire alignment -- and
|
|
107
|
-
// only when the live view shows one at all, since a minimap over an alignment
|
|
108
|
-
// that already fits marks a viewport wider than the bar
|
|
109
|
-
const includeMinimap =
|
|
110
|
-
opts.exportType === 'viewport' &&
|
|
111
|
-
!!opts.includeMinimap &&
|
|
112
|
-
showHorizontalScrollbar
|
|
113
|
-
const rows = legendRows(model.legends)
|
|
114
|
-
const legendWidth = rows.length > 0 ? getLegendWidth(rows) : 0
|
|
115
|
-
const topHeight = Math.max(
|
|
116
|
-
includeMinimap ? minimapHeight : 0,
|
|
117
|
-
rowPanelsHeaderHeight,
|
|
118
|
-
treeOverviewHeight + (treeScaleBar ? treeScaleBarHeight : 0),
|
|
119
|
-
)
|
|
120
|
-
const gutterWidth = treeAreaWidth + rowPanelsWidth
|
|
121
|
-
|
|
122
|
-
// width stays content-only; the legend occupies an extra column added at the
|
|
123
|
-
// svg root in MsaSvg. The viewport export takes the alignment canvas's own
|
|
124
|
-
// size rather than the widget's -- the widget box also covers the header, the
|
|
125
|
-
// resize handle and the scrollbars, and rendering that box exports rows and
|
|
126
|
-
// columns the scrollbars hide on screen
|
|
127
|
-
return opts.exportType === 'entire'
|
|
128
|
-
? {
|
|
129
|
-
width: totalWidth + gutterWidth,
|
|
130
|
-
msaAreaWidth: totalWidth,
|
|
131
|
-
height: totalHeight + trackHeight + topHeight,
|
|
132
|
-
contentHeight: totalHeight,
|
|
133
|
-
trackHeight,
|
|
134
|
-
topHeight,
|
|
135
|
-
offsetX: 0,
|
|
136
|
-
offsetY: 0,
|
|
137
|
-
includeMinimap,
|
|
138
|
-
legendWidth,
|
|
139
|
-
}
|
|
140
|
-
: {
|
|
141
|
-
width: msaCanvasWidth + gutterWidth,
|
|
142
|
-
msaAreaWidth: msaCanvasWidth,
|
|
143
|
-
height: msaAreaHeight + topHeight + trackHeight,
|
|
144
|
-
contentHeight: msaAreaHeight,
|
|
145
|
-
trackHeight,
|
|
146
|
-
topHeight,
|
|
147
|
-
offsetX: -scrollX,
|
|
148
|
-
offsetY: -scrollY,
|
|
149
|
-
includeMinimap,
|
|
150
|
-
legendWidth,
|
|
151
|
-
}
|
|
152
|
-
}
|
|
43
|
+
export type { ExportSvgOptions } from './exportLayout.ts'
|
|
153
44
|
|
|
154
45
|
export async function renderToSvg(model: MsaViewModel, opts: ExportSvgOptions) {
|
|
155
46
|
// dataInitialized stays false while the model holds a load error, so waiting
|
|
@@ -173,12 +64,13 @@ export async function renderToSvg(model: MsaViewModel, opts: ExportSvgOptions) {
|
|
|
173
64
|
layers={layers}
|
|
174
65
|
/>,
|
|
175
66
|
)
|
|
67
|
+
// innerHTML serializes as HTML, and an XML parser rejects its
|
|
176
68
|
return svgSafeColors(
|
|
177
69
|
markup.replaceAll(/<g data-layer="([^"]+)"><\/g>/g, (match, id: string) => {
|
|
178
70
|
const ctx = layers.get(id)
|
|
179
71
|
return ctx ? `<g id="${id}">${ctx.getSvg().innerHTML}</g>` : match
|
|
180
72
|
}),
|
|
181
|
-
)
|
|
73
|
+
).replaceAll(' ', ' ')
|
|
182
74
|
}
|
|
183
75
|
|
|
184
76
|
function MsaSvg({
|
|
@@ -195,18 +87,9 @@ function MsaSvg({
|
|
|
195
87
|
layers: LayerMap
|
|
196
88
|
}) {
|
|
197
89
|
const { width, trackHeight, includeMinimap, legendWidth, topHeight } = layout
|
|
198
|
-
const {
|
|
199
|
-
const totalWidth =
|
|
90
|
+
const { effectiveTreeAreaWidth, rowPanelsWidth } = model
|
|
91
|
+
const { width: totalWidth, height } = exportPageSize(model, layout)
|
|
200
92
|
const legendTop = topHeight
|
|
201
|
-
// a short alignment with many legend entries has a key taller than its rows,
|
|
202
|
-
// and the page has to hold the whole key
|
|
203
|
-
const height =
|
|
204
|
-
legendWidth > 0
|
|
205
|
-
? Math.max(
|
|
206
|
-
layout.height,
|
|
207
|
-
legendTop + legendHeight(legendRows(model.legends)),
|
|
208
|
-
)
|
|
209
|
-
: layout.height
|
|
210
93
|
const props = { Context, model, theme, layout, layers }
|
|
211
94
|
|
|
212
95
|
const body = (
|
|
@@ -227,6 +110,9 @@ function MsaSvg({
|
|
|
227
110
|
xmlns="http://www.w3.org/2000/svg"
|
|
228
111
|
xmlnsXlink="http://www.w3.org/1999/xlink"
|
|
229
112
|
viewBox={`0 0 ${totalWidth} ${height}`}
|
|
113
|
+
// the canvas layers name sans-serif on each glyph; the legend, labels
|
|
114
|
+
// and headers inherit it here instead of an editor's default serif
|
|
115
|
+
fontFamily="sans-serif"
|
|
230
116
|
>
|
|
231
117
|
{/* every layer below draws in the theme's colors, so the page has to be
|
|
232
118
|
the theme's background too -- a hardcoded white one turns a dark-theme
|
|
@@ -241,7 +127,7 @@ function MsaSvg({
|
|
|
241
127
|
{includeMinimap ? (
|
|
242
128
|
<g
|
|
243
129
|
id="minimap-panel"
|
|
244
|
-
transform={`translate(${
|
|
130
|
+
transform={`translate(${effectiveTreeAreaWidth + rowPanelsWidth} 0)`}
|
|
245
131
|
>
|
|
246
132
|
<MinimapSVG model={model} theme={theme} />
|
|
247
133
|
</g>
|
|
@@ -288,20 +174,20 @@ function TreeOverviewSVG({
|
|
|
288
174
|
Context: typeof ContextType
|
|
289
175
|
layers: LayerMap
|
|
290
176
|
}) {
|
|
291
|
-
const {
|
|
292
|
-
const ctx = new Context(
|
|
177
|
+
const { effectiveTreeAreaWidth, overviewHeight, id } = model
|
|
178
|
+
const ctx = new Context(effectiveTreeAreaWidth, overviewHeight)
|
|
293
179
|
renderTreeOverview({
|
|
294
180
|
model,
|
|
295
181
|
ctx,
|
|
296
182
|
theme,
|
|
297
|
-
width:
|
|
183
|
+
width: effectiveTreeAreaWidth,
|
|
298
184
|
height: overviewHeight,
|
|
299
185
|
})
|
|
300
186
|
return (
|
|
301
187
|
<ClipGroup
|
|
302
188
|
panelId="tree-overview"
|
|
303
189
|
clipId={`tree-overview-${id}`}
|
|
304
|
-
width={
|
|
190
|
+
width={effectiveTreeAreaWidth}
|
|
305
191
|
height={overviewHeight}
|
|
306
192
|
ctx={ctx}
|
|
307
193
|
layers={layers}
|
|
@@ -349,17 +235,16 @@ function RowPanelHeadersSVG({
|
|
|
349
235
|
theme: Theme
|
|
350
236
|
bandHeight: number
|
|
351
237
|
}) {
|
|
352
|
-
const { resolvedRowPanels,
|
|
238
|
+
const { resolvedRowPanels, effectiveTreeAreaWidth, fontSize } = model
|
|
353
239
|
const y = bandHeight - 4
|
|
354
240
|
return resolvedRowPanels.length === 0 ? null : (
|
|
355
241
|
<g id="rowpanel-headers">
|
|
356
242
|
{resolvedRowPanels.map(panel => {
|
|
357
243
|
const across = headerRunsAcross(panel.width)
|
|
358
244
|
const size = across ? fontSize : Math.min(fontSize, panel.width)
|
|
359
|
-
const center =
|
|
245
|
+
const center = effectiveTreeAreaWidth + panel.offsetX + panel.width / 2
|
|
360
246
|
const x = across ? center : center + size / 3
|
|
361
247
|
const room = across ? panel.width : y - LEGEND_PAD
|
|
362
|
-
const maxChars = Math.floor(room / (size * CHAR_WIDTH))
|
|
363
248
|
return size < 5 ? null : (
|
|
364
249
|
<text
|
|
365
250
|
key={panel.id}
|
|
@@ -371,9 +256,10 @@ function RowPanelHeadersSVG({
|
|
|
371
256
|
fontSize={size}
|
|
372
257
|
fill={theme.palette.text.primary}
|
|
373
258
|
>
|
|
374
|
-
{
|
|
375
|
-
|
|
376
|
-
|
|
259
|
+
{
|
|
260
|
+
ellipsize(t => measureTextCanvas(t, size), panel.header, room)
|
|
261
|
+
.text
|
|
262
|
+
}
|
|
377
263
|
</text>
|
|
378
264
|
)
|
|
379
265
|
})}
|
|
@@ -381,21 +267,11 @@ function RowPanelHeadersSVG({
|
|
|
381
267
|
)
|
|
382
268
|
}
|
|
383
269
|
|
|
384
|
-
// A label past the room it has, cut to what fits with an ellipsis. The measure
|
|
385
|
-
// is the one cladeGutterWidth sizes the gutter with, so a label the gutter
|
|
386
|
-
// holds keeps every character.
|
|
387
|
-
function clipCladeLabel(label: string, room: number, fontSize: number) {
|
|
388
|
-
const width = measureTextCanvas(label, fontSize)
|
|
389
|
-
if (width <= room) {
|
|
390
|
-
return label
|
|
391
|
-
}
|
|
392
|
-
const fits = Math.floor((label.length * room) / width)
|
|
393
|
-
return `${label.slice(0, Math.max(1, fits - 1))}…`
|
|
394
|
-
}
|
|
395
|
-
|
|
396
270
|
// Each bracket's label in the gutter beside its bar, where the live view puts
|
|
397
271
|
// it (see CladeLabels). The canvas layer draws the bar; a label longer than the
|
|
398
272
|
// gutter or the clade is clipped, since text that overruns runs off the figure.
|
|
273
|
+
// cladeGutterWidth sizes the gutter with the same measureTextCanvas, so a label
|
|
274
|
+
// the gutter holds keeps every character.
|
|
399
275
|
function CladeLabelsSVG({
|
|
400
276
|
model,
|
|
401
277
|
theme,
|
|
@@ -405,41 +281,32 @@ function CladeLabelsSVG({
|
|
|
405
281
|
theme: Theme
|
|
406
282
|
offsetY: number
|
|
407
283
|
}) {
|
|
408
|
-
const {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
rowHeight,
|
|
412
|
-
treeAreaWidth,
|
|
413
|
-
fontSize,
|
|
414
|
-
} = model
|
|
415
|
-
if (cladeGutterWidth === 0) {
|
|
416
|
-
return null
|
|
417
|
-
}
|
|
418
|
-
const gutterLeft = treeAreaWidth - cladeGutterWidth
|
|
419
|
-
return (
|
|
284
|
+
const { fontSize } = model
|
|
285
|
+
const boxes = cladeLabelBoxes(model)
|
|
286
|
+
return boxes.length === 0 ? null : (
|
|
420
287
|
<g id="clade-labels">
|
|
421
|
-
{
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
const top = rows[0] * rowHeight - offsetY
|
|
428
|
-
const height = cladeHeight(clade, rowHeight)
|
|
429
|
-
const horizontal = cladeLabelHorizontal(clade, rowHeight, fontSize)
|
|
430
|
-
const room = horizontal ? treeAreaWidth - left - bracketGap : height
|
|
431
|
-
const x = horizontal ? left : left + fontSize
|
|
432
|
-
const y = horizontal ? top + height / 2 + fontSize / 3 : top + height
|
|
288
|
+
{boxes.map(box => {
|
|
289
|
+
const top = box.top - offsetY
|
|
290
|
+
const x = box.horizontal ? box.left : box.left + fontSize
|
|
291
|
+
const y = box.horizontal
|
|
292
|
+
? top + box.height / 2 + fontSize / 3
|
|
293
|
+
: top + box.height
|
|
433
294
|
return (
|
|
434
295
|
<text
|
|
435
|
-
key={
|
|
296
|
+
key={box.key}
|
|
436
297
|
x={x}
|
|
437
298
|
y={y}
|
|
438
|
-
transform={horizontal ? undefined : `rotate(-90 ${x} ${y})`}
|
|
299
|
+
transform={box.horizontal ? undefined : `rotate(-90 ${x} ${y})`}
|
|
439
300
|
fontSize={fontSize}
|
|
440
|
-
fill={
|
|
301
|
+
fill={box.color ?? theme.palette.text.primary}
|
|
441
302
|
>
|
|
442
|
-
{
|
|
303
|
+
{
|
|
304
|
+
ellipsize(
|
|
305
|
+
t => measureTextCanvas(t, fontSize),
|
|
306
|
+
box.label,
|
|
307
|
+
box.horizontal ? box.width : box.height,
|
|
308
|
+
).text
|
|
309
|
+
}
|
|
443
310
|
</text>
|
|
444
311
|
)
|
|
445
312
|
})}
|
|
@@ -462,9 +329,7 @@ function LegendSVG({
|
|
|
462
329
|
const boxHeight = legendHeight(rows)
|
|
463
330
|
// the column is capped at LEGEND_MAX_W, so a name too long for it has to be
|
|
464
331
|
// clipped here -- text that overruns the reserved width runs off the figure
|
|
465
|
-
const
|
|
466
|
-
(width - LEGEND_TEXT_X - LEGEND_PAD) / LEGEND_CHAR_W,
|
|
467
|
-
)
|
|
332
|
+
const room = width - LEGEND_TEXT_X - LEGEND_PAD
|
|
468
333
|
return (
|
|
469
334
|
<g>
|
|
470
335
|
<rect
|
|
@@ -497,9 +362,7 @@ function LegendSVG({
|
|
|
497
362
|
fontWeight={row.color ? undefined : 'bold'}
|
|
498
363
|
fill={theme.palette.text.primary}
|
|
499
364
|
>
|
|
500
|
-
{row.label.
|
|
501
|
-
? `${row.label.slice(0, Math.max(1, maxChars - 1))}…`
|
|
502
|
-
: row.label}
|
|
365
|
+
{ellipsize(t => t.length * LEGEND_CHAR_W, row.label, room).text}
|
|
503
366
|
</text>
|
|
504
367
|
</g>
|
|
505
368
|
)
|
|
@@ -520,9 +383,9 @@ interface LayerProps {
|
|
|
520
383
|
|
|
521
384
|
function CoreRendering({ model, theme, layout, Context, layers }: LayerProps) {
|
|
522
385
|
const { contentHeight, offsetX, offsetY, msaAreaWidth } = layout
|
|
523
|
-
const {
|
|
386
|
+
const { effectiveTreeAreaWidth, rowPanelsWidth, id } = model
|
|
524
387
|
|
|
525
|
-
const treeCtx = new Context(
|
|
388
|
+
const treeCtx = new Context(effectiveTreeAreaWidth, contentHeight)
|
|
526
389
|
renderTreeCanvas({
|
|
527
390
|
model,
|
|
528
391
|
theme,
|
|
@@ -588,7 +451,7 @@ function CoreRendering({ model, theme, layout, Context, layers }: LayerProps) {
|
|
|
588
451
|
<ClipGroup
|
|
589
452
|
panelId="tree-panel"
|
|
590
453
|
clipId={`tree-${id}`}
|
|
591
|
-
width={
|
|
454
|
+
width={effectiveTreeAreaWidth}
|
|
592
455
|
height={contentHeight}
|
|
593
456
|
ctx={treeCtx}
|
|
594
457
|
layers={layers}
|
|
@@ -600,7 +463,7 @@ function CoreRendering({ model, theme, layout, Context, layers }: LayerProps) {
|
|
|
600
463
|
clipId={`rowpanels-${id}`}
|
|
601
464
|
width={rowPanelsWidth}
|
|
602
465
|
height={contentHeight}
|
|
603
|
-
transform={`translate(${
|
|
466
|
+
transform={`translate(${effectiveTreeAreaWidth} 0)`}
|
|
604
467
|
ctx={rowPanelsCtx}
|
|
605
468
|
layers={layers}
|
|
606
469
|
/>
|
|
@@ -610,7 +473,7 @@ function CoreRendering({ model, theme, layout, Context, layers }: LayerProps) {
|
|
|
610
473
|
clipId={`msa-${id}`}
|
|
611
474
|
width={msaAreaWidth}
|
|
612
475
|
height={contentHeight}
|
|
613
|
-
transform={`translate(${
|
|
476
|
+
transform={`translate(${effectiveTreeAreaWidth + rowPanelsWidth} 0)`}
|
|
614
477
|
ctx={msaCtx}
|
|
615
478
|
layers={layers}
|
|
616
479
|
underlay={raster}
|
|
@@ -620,9 +483,8 @@ function CoreRendering({ model, theme, layout, Context, layers }: LayerProps) {
|
|
|
620
483
|
}
|
|
621
484
|
|
|
622
485
|
/**
|
|
623
|
-
* The alignment background as a single <image>,
|
|
624
|
-
*
|
|
625
|
-
* that reads back.
|
|
486
|
+
* The alignment background as a single <image>, wherever the live canvas
|
|
487
|
+
* draws the raster.
|
|
626
488
|
*
|
|
627
489
|
* The vector path's <rect> per cell exhausts the heap on a real alignment. The
|
|
628
490
|
* image is one pixel per cell, and image-rendering keeps the upscaled cell
|
|
@@ -638,8 +500,8 @@ function rasterBackground({
|
|
|
638
500
|
layout: Layout
|
|
639
501
|
}) {
|
|
640
502
|
const { contentHeight, offsetX, offsetY, msaAreaWidth } = layout
|
|
641
|
-
const { colWidth, rowHeight
|
|
642
|
-
if (
|
|
503
|
+
const { colWidth, rowHeight } = model
|
|
504
|
+
if (!rasterApplies(model)) {
|
|
643
505
|
return undefined
|
|
644
506
|
}
|
|
645
507
|
const { xStart, xEnd } = visibleColRange({
|
|
@@ -677,7 +539,7 @@ function rasterBackground({
|
|
|
677
539
|
|
|
678
540
|
function TrackRendering({ model, theme, layout, Context, layers }: LayerProps) {
|
|
679
541
|
const { trackHeight, offsetX, msaAreaWidth } = layout
|
|
680
|
-
const {
|
|
542
|
+
const { effectiveTreeAreaWidth, rowPanelsWidth, id } = model
|
|
681
543
|
|
|
682
544
|
const ctx = new Context(msaAreaWidth, trackHeight)
|
|
683
545
|
renderAllTracks({
|
|
@@ -692,7 +554,7 @@ function TrackRendering({ model, theme, layout, Context, layers }: LayerProps) {
|
|
|
692
554
|
return (
|
|
693
555
|
<>
|
|
694
556
|
<TrackLabelsSVG model={model} theme={theme} />
|
|
695
|
-
<g transform={`translate(${
|
|
557
|
+
<g transform={`translate(${effectiveTreeAreaWidth + rowPanelsWidth} 0)`}>
|
|
696
558
|
<ClipGroup
|
|
697
559
|
panelId="tracks-panel"
|
|
698
560
|
clipId={`tracks-${id}`}
|
|
@@ -716,7 +578,7 @@ function TrackLabelsSVG({
|
|
|
716
578
|
model: MsaViewModel
|
|
717
579
|
theme: Theme
|
|
718
580
|
}) {
|
|
719
|
-
const { turnedOnTracks,
|
|
581
|
+
const { turnedOnTracks, effectiveTreeAreaWidth, fontSize, drawLabels } = model
|
|
720
582
|
if (!drawLabels) {
|
|
721
583
|
return null
|
|
722
584
|
}
|
|
@@ -726,7 +588,8 @@ function TrackLabelsSVG({
|
|
|
726
588
|
const top = turnedOnTracks
|
|
727
589
|
.slice(0, i)
|
|
728
590
|
.reduce((acc, t) => acc + t.model.height, 0)
|
|
729
|
-
|
|
591
|
+
// pinned to the top of the track, as the live view's flex row pins it
|
|
592
|
+
return { id, name, size, baseline: top + size * 0.8 }
|
|
730
593
|
})
|
|
731
594
|
return (
|
|
732
595
|
<>
|
|
@@ -734,7 +597,7 @@ function TrackLabelsSVG({
|
|
|
734
597
|
return size < 5 ? null : (
|
|
735
598
|
<text
|
|
736
599
|
key={id}
|
|
737
|
-
x={
|
|
600
|
+
x={effectiveTreeAreaWidth - 4}
|
|
738
601
|
y={baseline}
|
|
739
602
|
fontSize={size}
|
|
740
603
|
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/types.ts
CHANGED
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '
|
|
1
|
+
export const version = '9.1.0'
|