react-msaview 6.1.1 → 6.2.1
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 +92 -92
- package/bundle/index.js.map +4 -4
- package/dist/components/dialogs/ExportSVGDialog.js +48 -20
- package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
- package/dist/components/getVisibleLeaves.d.ts +8 -0
- package/dist/components/getVisibleLeaves.js +13 -6
- package/dist/components/getVisibleLeaves.js.map +1 -1
- package/dist/components/minimap/Minimap.js +1 -1
- package/dist/components/minimap/Minimap.js.map +1 -1
- package/dist/components/minimap/MinimapSVG.d.ts +4 -4
- package/dist/components/minimap/MinimapSVG.js +16 -10
- package/dist/components/minimap/MinimapSVG.js.map +1 -1
- package/dist/components/minimap/minimapLayout.d.ts +5 -8
- package/dist/components/minimap/minimapLayout.js +10 -11
- package/dist/components/minimap/minimapLayout.js.map +1 -1
- package/dist/components/msa/msaRaster.d.ts +32 -2
- package/dist/components/msa/msaRaster.js +93 -15
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/{renderTestEnv.d.ts → headlessRenderEnv.d.ts} +15 -13
- package/dist/{renderTestEnv.js → headlessRenderEnv.js} +22 -18
- package/dist/headlessRenderEnv.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/model.d.ts +25 -0
- package/dist/model.js +62 -13
- package/dist/model.js.map +1 -1
- package/dist/renderToSvg.js +115 -36
- package/dist/renderToSvg.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/src/components/dialogs/ExportSVGDialog.tsx +55 -20
- package/src/components/getVisibleLeaves.ts +25 -9
- package/src/components/minimap/Minimap.tsx +1 -4
- package/src/components/minimap/MinimapSVG.tsx +50 -35
- package/src/components/minimap/minimapLayout.test.ts +16 -5
- package/src/components/minimap/minimapLayout.ts +10 -11
- package/src/components/msa/msaRaster.ts +124 -17
- package/src/{renderTestEnv.ts → headlessRenderEnv.ts} +23 -19
- package/src/impgRender.test.tsx +2 -2
- package/src/index.ts +6 -0
- package/src/model.ts +77 -13
- package/src/modelReset.test.ts +120 -0
- package/src/renderDomainsSvg.test.tsx +2 -2
- package/src/renderMinimapSvg.test.tsx +29 -15
- package/src/renderRasterSvg.test.tsx +216 -0
- package/src/renderSequenceLogoSvg.test.tsx +6 -4
- package/src/renderToSvg.test.tsx +79 -0
- package/src/renderToSvg.tsx +201 -65
- package/src/version.ts +1 -1
- package/dist/renderTestEnv.js.map +0 -1
package/dist/renderToSvg.js
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { renderToStaticMarkup } from '@jbrowse/core/util';
|
|
4
4
|
import { when } from 'mobx';
|
|
5
|
+
import { visibleRowRange } from './components/getVisibleLeaves.js';
|
|
5
6
|
import MinimapSVG from './components/minimap/MinimapSVG.js';
|
|
7
|
+
import { rasterImageHref, rasterSupported } from './components/msa/msaRaster.js';
|
|
6
8
|
import { renderBoxFeatureCanvasBlock } from './components/msa/renderBoxFeatureCanvasBlock.js';
|
|
7
9
|
import { renderMSABlock } from './components/msa/renderMSABlock.js';
|
|
10
|
+
import { visibleColRange } from './components/msa/visibleColRange.js';
|
|
8
11
|
import { renderAllTracks } from './components/tracks/drawTracks.js';
|
|
9
12
|
import { renderTreeCanvas } from './components/tree/renderTreeCanvas.js';
|
|
10
13
|
import { colorContrast } from './util.js';
|
|
@@ -15,26 +18,35 @@ const LEGEND_ROW_H = 16;
|
|
|
15
18
|
const LEGEND_SWATCH = 12;
|
|
16
19
|
const LEGEND_FONT = 12;
|
|
17
20
|
const LEGEND_CHAR_W = 7;
|
|
21
|
+
const LEGEND_MAX_W = 360;
|
|
22
|
+
const LEGEND_TEXT_X = LEGEND_PAD + LEGEND_SWATCH + 6;
|
|
18
23
|
function getLegendWidth(model) {
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
return Math.min(360, Math.max(120, w));
|
|
24
|
+
const maxLen = model.visibleDomainTypes.reduce((a, d) => Math.max(a, d.name.length), 0);
|
|
25
|
+
const w = LEGEND_TEXT_X + LEGEND_PAD + maxLen * LEGEND_CHAR_W;
|
|
26
|
+
return Math.min(LEGEND_MAX_W, Math.max(120, w));
|
|
23
27
|
}
|
|
24
28
|
function getLayout(model, opts) {
|
|
25
|
-
const {
|
|
29
|
+
const { scrollX, scrollY, totalWidth, totalHeight, treeAreaWidth, totalTrackAreaHeight, minimapHeight, msaAreaHeight, msaCanvasWidth, showHorizontalScrollbar, } = model;
|
|
26
30
|
const trackHeight = opts.includeTracks ? totalTrackAreaHeight : 0;
|
|
27
31
|
// the minimap reflects the live viewport scroll position, so it's only
|
|
28
|
-
// meaningful for a viewport export, never for the entire alignment
|
|
29
|
-
|
|
32
|
+
// meaningful for a viewport export, never for the entire alignment -- and
|
|
33
|
+
// only when the live view shows one at all, since a minimap over an alignment
|
|
34
|
+
// that already fits marks a viewport wider than the bar
|
|
35
|
+
const includeMinimap = opts.exportType === 'viewport' &&
|
|
36
|
+
!!opts.includeMinimap &&
|
|
37
|
+
showHorizontalScrollbar;
|
|
30
38
|
const legendWidth = model.actuallyShowDomains && model.visibleDomainTypes.length > 0
|
|
31
39
|
? getLegendWidth(model)
|
|
32
40
|
: 0;
|
|
33
|
-
// width stays content-only
|
|
34
|
-
//
|
|
41
|
+
// width stays content-only; the legend occupies an extra column added at the
|
|
42
|
+
// svg root in MsaSvg. The viewport export takes the alignment canvas's own
|
|
43
|
+
// size rather than the widget's -- the widget box also covers the header, the
|
|
44
|
+
// resize handle and the scrollbars, and rendering that box exports rows and
|
|
45
|
+
// columns the scrollbars hide on screen
|
|
35
46
|
return opts.exportType === 'entire'
|
|
36
47
|
? {
|
|
37
48
|
width: totalWidth + treeAreaWidth,
|
|
49
|
+
msaAreaWidth: totalWidth,
|
|
38
50
|
height: totalHeight + trackHeight,
|
|
39
51
|
contentHeight: totalHeight,
|
|
40
52
|
trackHeight,
|
|
@@ -44,9 +56,10 @@ function getLayout(model, opts) {
|
|
|
44
56
|
legendWidth,
|
|
45
57
|
}
|
|
46
58
|
: {
|
|
47
|
-
width,
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
width: msaCanvasWidth + treeAreaWidth,
|
|
60
|
+
msaAreaWidth: msaCanvasWidth,
|
|
61
|
+
height: msaAreaHeight + (includeMinimap ? minimapHeight : 0) + trackHeight,
|
|
62
|
+
contentHeight: msaAreaHeight,
|
|
50
63
|
trackHeight,
|
|
51
64
|
offsetX: -scrollX,
|
|
52
65
|
offsetY: -scrollY,
|
|
@@ -64,38 +77,43 @@ function MsaSvg({ model, theme, Context, layout, }) {
|
|
|
64
77
|
const { treeAreaWidth, minimapHeight } = model;
|
|
65
78
|
const totalWidth = width + legendWidth;
|
|
66
79
|
const legendTop = includeMinimap ? minimapHeight : 0;
|
|
80
|
+
const contrastScheme = colorContrast(model.colorScheme, theme);
|
|
81
|
+
const props = { Context, model, theme, layout, contrastScheme };
|
|
67
82
|
const body = (React.createElement(React.Fragment, null,
|
|
68
|
-
trackHeight > 0 ?
|
|
83
|
+
trackHeight > 0 ? React.createElement(TrackRendering, { ...props }) : null,
|
|
69
84
|
React.createElement("g", { transform: trackHeight > 0 ? `translate(0 ${trackHeight})` : undefined },
|
|
70
|
-
React.createElement(CoreRendering, {
|
|
85
|
+
React.createElement(CoreRendering, { ...props }))));
|
|
71
86
|
return (React.createElement("svg", { width: totalWidth, height: height, xmlns: "http://www.w3.org/2000/svg", xmlnsXlink: "http://www.w3.org/1999/xlink", viewBox: `0 0 ${totalWidth} ${height}` },
|
|
72
|
-
React.createElement("rect", { width: "100%", height: "100%", fill:
|
|
87
|
+
React.createElement("rect", { width: "100%", height: "100%", fill: theme.palette.background.default }),
|
|
73
88
|
includeMinimap ? (React.createElement(React.Fragment, null,
|
|
74
89
|
React.createElement("g", { transform: `translate(${treeAreaWidth} 0)` },
|
|
75
|
-
React.createElement(MinimapSVG, { model: model,
|
|
90
|
+
React.createElement(MinimapSVG, { model: model, theme: theme })),
|
|
76
91
|
React.createElement("g", { transform: `translate(0 ${minimapHeight})` }, body))) : (body),
|
|
77
92
|
legendWidth > 0 ? (React.createElement("g", { transform: `translate(${width} ${legendTop})` },
|
|
78
|
-
React.createElement(LegendSVG, { model: model, width: legendWidth }))) : null));
|
|
93
|
+
React.createElement(LegendSVG, { model: model, theme: theme, width: legendWidth }))) : null));
|
|
79
94
|
}
|
|
80
95
|
// the domain color key drawn as a reserved column to the right of the
|
|
81
96
|
// alignment, mirroring the on-screen AnnotationLegend overlay
|
|
82
|
-
function LegendSVG({ model, width }) {
|
|
97
|
+
function LegendSVG({ model, theme, width, }) {
|
|
83
98
|
const { visibleDomainTypes, fillPalette, strokePalette } = model;
|
|
84
99
|
const boxHeight = LEGEND_PAD * 2 + visibleDomainTypes.length * LEGEND_ROW_H;
|
|
100
|
+
// the column is capped at LEGEND_MAX_W, so a name too long for it has to be
|
|
101
|
+
// clipped here -- text that overruns the reserved width runs off the figure
|
|
102
|
+
const maxChars = Math.floor((width - LEGEND_TEXT_X - LEGEND_PAD) / LEGEND_CHAR_W);
|
|
85
103
|
return (React.createElement("g", null,
|
|
86
|
-
React.createElement("rect", { x: 0, y: 0, width: width - 4, height: boxHeight, fill:
|
|
104
|
+
React.createElement("rect", { x: 0, y: 0, width: width - 4, height: boxHeight, fill: theme.palette.background.paper, stroke: "#ccc", rx: 2 }),
|
|
87
105
|
visibleDomainTypes.map((d, i) => {
|
|
88
106
|
const y = LEGEND_PAD + i * LEGEND_ROW_H;
|
|
89
107
|
return (React.createElement("g", { key: d.accession },
|
|
90
108
|
React.createElement("rect", { x: LEGEND_PAD, y: y, width: LEGEND_SWATCH, height: LEGEND_SWATCH, fill: fillPalette[d.accession], stroke: strokePalette[d.accession] }),
|
|
91
|
-
React.createElement("text", { x:
|
|
109
|
+
React.createElement("text", { x: LEGEND_TEXT_X, y: y + LEGEND_SWATCH - 1, fontSize: LEGEND_FONT, fill: theme.palette.text.primary }, d.name.length > maxChars
|
|
110
|
+
? `${d.name.slice(0, Math.max(1, maxChars - 1))}…`
|
|
111
|
+
: d.name)));
|
|
92
112
|
})));
|
|
93
113
|
}
|
|
94
|
-
function CoreRendering({ model, theme, layout, Context, }) {
|
|
95
|
-
const { contentHeight, offsetX, offsetY,
|
|
96
|
-
const { treeAreaWidth,
|
|
97
|
-
const msaAreaWidth = width - treeAreaWidth;
|
|
98
|
-
const contrastScheme = colorContrast(colorScheme, theme);
|
|
114
|
+
function CoreRendering({ model, theme, layout, Context, contrastScheme, }) {
|
|
115
|
+
const { contentHeight, offsetX, offsetY, msaAreaWidth } = layout;
|
|
116
|
+
const { treeAreaWidth, id } = model;
|
|
99
117
|
const treeCtx = new Context(treeAreaWidth, contentHeight);
|
|
100
118
|
renderTreeCanvas({
|
|
101
119
|
model,
|
|
@@ -105,6 +123,7 @@ function CoreRendering({ model, theme, layout, Context, }) {
|
|
|
105
123
|
blockSizeYOverride: contentHeight,
|
|
106
124
|
highResScaleFactorOverride: 1,
|
|
107
125
|
});
|
|
126
|
+
const raster = rasterBackground({ model, theme, layout });
|
|
108
127
|
const msaCtx = new Context(msaAreaWidth, contentHeight);
|
|
109
128
|
renderBoxFeatureCanvasBlock({
|
|
110
129
|
model,
|
|
@@ -127,16 +146,54 @@ function CoreRendering({ model, theme, layout, Context, }) {
|
|
|
127
146
|
blockSizeXOverride: msaAreaWidth,
|
|
128
147
|
blockSizeYOverride: contentHeight,
|
|
129
148
|
highResScaleFactorOverride: 1,
|
|
149
|
+
rasterTiles: !!raster,
|
|
130
150
|
});
|
|
131
151
|
return (React.createElement(React.Fragment, null,
|
|
132
152
|
React.createElement(ClipGroup, { clipId: `tree-${id}`, width: treeAreaWidth, height: contentHeight, ctx: treeCtx }),
|
|
133
|
-
React.createElement(ClipGroup, { clipId: `msa-${id}`, width: msaAreaWidth, height: contentHeight, transform: `translate(${treeAreaWidth} 0)`, ctx: msaCtx })));
|
|
153
|
+
React.createElement(ClipGroup, { clipId: `msa-${id}`, width: msaAreaWidth, height: contentHeight, transform: `translate(${treeAreaWidth} 0)`, ctx: msaCtx, underlay: raster })));
|
|
134
154
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
155
|
+
/**
|
|
156
|
+
* The alignment background as a single <image>, on the same terms the live
|
|
157
|
+
* canvas uses it: no domain overlay painting its own boxes, background coloring
|
|
158
|
+
* on, and a canvas we can actually read back.
|
|
159
|
+
*
|
|
160
|
+
* One node replaces the <rect>-per-cell the vector path emits, which is what
|
|
161
|
+
* lets an export of a real alignment finish at all. The image is one pixel per
|
|
162
|
+
* cell drawn across the cells' own rectangle, so it upscales by whole cells --
|
|
163
|
+
* image-rendering keeps those edges hard rather than smearing them, making it
|
|
164
|
+
* pixel-for-pixel what the rects drew.
|
|
165
|
+
*/
|
|
166
|
+
function rasterBackground({ model, theme, layout, }) {
|
|
167
|
+
const { contentHeight, offsetX, offsetY, msaAreaWidth } = layout;
|
|
168
|
+
const { colWidth, rowHeight, actuallyShowDomains, bgColor } = model;
|
|
169
|
+
if (actuallyShowDomains || !bgColor || !rasterSupported()) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
const { xStart, xEnd } = visibleColRange({
|
|
173
|
+
offsetX,
|
|
174
|
+
blockWidth: msaAreaWidth,
|
|
175
|
+
colWidth,
|
|
176
|
+
});
|
|
177
|
+
const { yStart, yEnd } = visibleRowRange({
|
|
178
|
+
model,
|
|
179
|
+
offsetY,
|
|
180
|
+
blockSizeY: contentHeight,
|
|
181
|
+
});
|
|
182
|
+
const numCols = Math.min(xEnd, model.numColumns) - xStart;
|
|
183
|
+
const numRows = Math.min(yEnd, model.leaves.length) - yStart;
|
|
184
|
+
const href = rasterImageHref({
|
|
185
|
+
model,
|
|
186
|
+
theme,
|
|
187
|
+
col0: xStart,
|
|
188
|
+
row0: yStart,
|
|
189
|
+
numCols,
|
|
190
|
+
numRows,
|
|
191
|
+
});
|
|
192
|
+
return href ? (React.createElement("image", { href: href, x: xStart * colWidth - offsetX, y: yStart * rowHeight - offsetY, width: numCols * colWidth, height: numRows * rowHeight, imageRendering: "pixelated", preserveAspectRatio: "none" })) : undefined;
|
|
193
|
+
}
|
|
194
|
+
function TrackRendering({ model, theme, layout, Context, contrastScheme, }) {
|
|
195
|
+
const { trackHeight, offsetX, msaAreaWidth } = layout;
|
|
196
|
+
const { treeAreaWidth, id } = model;
|
|
140
197
|
const ctx = new Context(msaAreaWidth, trackHeight);
|
|
141
198
|
renderAllTracks({
|
|
142
199
|
model,
|
|
@@ -147,16 +204,38 @@ function TrackRendering({ model, theme, layout, Context, }) {
|
|
|
147
204
|
blockSizeXOverride: msaAreaWidth,
|
|
148
205
|
highResScaleFactorOverride: 1,
|
|
149
206
|
});
|
|
150
|
-
return (React.createElement(
|
|
151
|
-
React.createElement(
|
|
207
|
+
return (React.createElement(React.Fragment, null,
|
|
208
|
+
React.createElement(TrackLabelsSVG, { model: model, theme: theme }),
|
|
209
|
+
React.createElement("g", { transform: `translate(${treeAreaWidth} 0)` },
|
|
210
|
+
React.createElement(ClipGroup, { clipId: `tracks-${id}`, width: msaAreaWidth, height: trackHeight, ctx: ctx }))));
|
|
211
|
+
}
|
|
212
|
+
// Each track's name in the tree-width column beside it, where the live view puts
|
|
213
|
+
// it (see TrackLabel). Without these the figure has an unlabeled bar chart and
|
|
214
|
+
// an unlabeled logo, and nothing says which is which.
|
|
215
|
+
function TrackLabelsSVG({ model, theme, }) {
|
|
216
|
+
const { turnedOnTracks, treeAreaWidth, fontSize, drawLabels } = model;
|
|
217
|
+
if (!drawLabels) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
let y = 0;
|
|
221
|
+
return (React.createElement(React.Fragment, null, turnedOnTracks.map(track => {
|
|
222
|
+
const { id, name, height } = track.model;
|
|
223
|
+
const size = Math.min(height, fontSize);
|
|
224
|
+
const baseline = y + height / 2 + size / 3;
|
|
225
|
+
y += height;
|
|
226
|
+
return size < 5 ? null : (React.createElement("text", { key: id, x: treeAreaWidth - 4, y: baseline, fontSize: size, textAnchor: "end", fill: theme.palette.text.primary }, name));
|
|
227
|
+
})));
|
|
152
228
|
}
|
|
153
229
|
// Clips a svgcanvas Context to its box and injects its markup verbatim (it is
|
|
154
|
-
// already serialized SVG, not React).
|
|
155
|
-
|
|
230
|
+
// already serialized SVG, not React). `underlay` draws beneath that markup,
|
|
231
|
+
// inside the same clip, for the parts of a layer React emits directly.
|
|
232
|
+
function ClipGroup({ clipId, width, height, transform, ctx, underlay, }) {
|
|
156
233
|
return (React.createElement(React.Fragment, null,
|
|
157
234
|
React.createElement("defs", null,
|
|
158
235
|
React.createElement("clipPath", { id: clipId },
|
|
159
236
|
React.createElement("rect", { x: 0, y: 0, width: width, height: height }))),
|
|
160
|
-
React.createElement("g", { clipPath: `url(#${clipId})`, transform: transform
|
|
237
|
+
React.createElement("g", { clipPath: `url(#${clipId})`, transform: transform },
|
|
238
|
+
underlay,
|
|
239
|
+
React.createElement("g", { dangerouslySetInnerHTML: { __html: ctx.getSvg().innerHTML } }))));
|
|
161
240
|
}
|
|
162
241
|
//# sourceMappingURL=renderToSvg.js.map
|
package/dist/renderToSvg.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderToSvg.js","sourceRoot":"","sources":["../src/renderToSvg.tsx"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAE3B,OAAO,UAAU,MAAM,oCAAqC,CAAA;AAC5D,OAAO,EAAE,2BAA2B,EAAE,MAAM,iDAAiD,CAAA;AAC7F,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAA;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAA;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAA;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAazC,8EAA8E;AAC9E,iEAAiE;AACjE,MAAM,UAAU,GAAG,CAAC,CAAA;AACpB,MAAM,YAAY,GAAG,EAAE,CAAA;AACvB,MAAM,aAAa,GAAG,EAAE,CAAA;AACxB,MAAM,WAAW,GAAG,EAAE,CAAA;AACtB,MAAM,aAAa,GAAG,CAAC,CAAA;AAEvB,
|
|
1
|
+
{"version":3,"file":"renderToSvg.js","sourceRoot":"","sources":["../src/renderToSvg.tsx"],"names":[],"mappings":"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"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "6.
|
|
1
|
+
export declare const version = "6.2.1";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const version = '6.
|
|
1
|
+
export const version = '6.2.1';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-msaview",
|
|
3
3
|
"author": "Colin",
|
|
4
|
-
"version": "6.
|
|
4
|
+
"version": "6.2.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"@gmod/newick": "^1.0.0",
|
|
44
44
|
"colord": "^2.9.3",
|
|
45
45
|
"flatbush": "^4.6.2",
|
|
46
|
-
"
|
|
47
|
-
"
|
|
46
|
+
"msa-parsers": "6.2.1",
|
|
47
|
+
"@jbrowse/svgcanvas": "6.2.1"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"clean": "rimraf dist bundle",
|
|
@@ -4,6 +4,7 @@ import { Dialog, ErrorMessage } from '@jbrowse/core/ui'
|
|
|
4
4
|
import {
|
|
5
5
|
Alert,
|
|
6
6
|
Button,
|
|
7
|
+
CircularProgress,
|
|
7
8
|
DialogActions,
|
|
8
9
|
DialogContent,
|
|
9
10
|
FormControl,
|
|
@@ -19,6 +20,16 @@ import Checkbox2 from '../Checkbox2.tsx'
|
|
|
19
20
|
|
|
20
21
|
import type { MsaViewModel } from '../../model.ts'
|
|
21
22
|
|
|
23
|
+
// Run after the browser has painted. A requestAnimationFrame callback still
|
|
24
|
+
// runs *before* the frame it was queued for, so one alone hands the main thread
|
|
25
|
+
// to the render with the spinner not yet on screen; the second fires once that
|
|
26
|
+
// frame is out.
|
|
27
|
+
function afterPaint(fn: () => void) {
|
|
28
|
+
requestAnimationFrame(() => {
|
|
29
|
+
requestAnimationFrame(fn)
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
22
33
|
export default function ExportSVGDialog({
|
|
23
34
|
model,
|
|
24
35
|
onClose,
|
|
@@ -32,6 +43,7 @@ export default function ExportSVGDialog({
|
|
|
32
43
|
'viewport',
|
|
33
44
|
)
|
|
34
45
|
const [error, setError] = useState<unknown>()
|
|
46
|
+
const [exporting, setExporting] = useState(false)
|
|
35
47
|
const theme = useTheme()
|
|
36
48
|
const {
|
|
37
49
|
totalWidth,
|
|
@@ -39,12 +51,20 @@ export default function ExportSVGDialog({
|
|
|
39
51
|
treeAreaWidth,
|
|
40
52
|
turnedOnTracks,
|
|
41
53
|
totalTrackAreaHeight,
|
|
54
|
+
numColumns,
|
|
55
|
+
leaves,
|
|
56
|
+
showMsaLetters,
|
|
42
57
|
} = model
|
|
43
58
|
const hasTracks = turnedOnTracks.length > 0
|
|
44
59
|
const entireWidth = totalWidth + treeAreaWidth
|
|
45
60
|
const entireHeight = totalHeight + (includeTracks ? totalTrackAreaHeight : 0)
|
|
46
|
-
|
|
47
|
-
|
|
61
|
+
// the background is one raster image whatever its size, so what the figure
|
|
62
|
+
// actually costs is its residue letters: each is an svg element of its own.
|
|
63
|
+
// Past roughly this many the export runs to tens of seconds and can exhaust
|
|
64
|
+
// the tab's memory outright
|
|
65
|
+
const glyphs =
|
|
66
|
+
showMsaLetters && exportType === 'entire' ? numColumns * leaves.length : 0
|
|
67
|
+
const isLargeExport = glyphs > 100_000
|
|
48
68
|
return (
|
|
49
69
|
<Dialog
|
|
50
70
|
onClose={() => {
|
|
@@ -100,8 +120,11 @@ export default function ExportSVGDialog({
|
|
|
100
120
|
</div>
|
|
101
121
|
{isLargeExport ? (
|
|
102
122
|
<Alert severity="warning" style={{ marginTop: 8 }}>
|
|
103
|
-
The entire MSA is
|
|
104
|
-
{Math.round(entireHeight)} pixels
|
|
123
|
+
The entire MSA is {Math.round(entireWidth)}x
|
|
124
|
+
{Math.round(entireHeight)} pixels and draws{' '}
|
|
125
|
+
{Math.round(glyphs / 1000)}k residue letters, each its own SVG
|
|
126
|
+
element. Export may be slow or fail — zooming out far enough to hide
|
|
127
|
+
the letters exports the same alignment as an image instead.
|
|
105
128
|
</Alert>
|
|
106
129
|
) : null}
|
|
107
130
|
</DialogContent>
|
|
@@ -109,25 +132,37 @@ export default function ExportSVGDialog({
|
|
|
109
132
|
<Button
|
|
110
133
|
variant="contained"
|
|
111
134
|
color="primary"
|
|
135
|
+
// rendering runs on the main thread and a large figure holds it for
|
|
136
|
+
// seconds; without this the button stays live through the freeze and
|
|
137
|
+
// a second click starts a second export
|
|
138
|
+
disabled={exporting}
|
|
139
|
+
startIcon={
|
|
140
|
+
exporting ? <CircularProgress size={16} color="inherit" /> : null
|
|
141
|
+
}
|
|
112
142
|
onClick={() => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
143
|
+
setExporting(true)
|
|
144
|
+
afterPaint(() => {
|
|
145
|
+
void model
|
|
146
|
+
.exportSVG({
|
|
147
|
+
theme,
|
|
148
|
+
includeMinimap,
|
|
149
|
+
includeTracks: hasTracks && includeTracks,
|
|
150
|
+
exportType,
|
|
151
|
+
})
|
|
152
|
+
.then(() => {
|
|
153
|
+
onClose()
|
|
154
|
+
})
|
|
155
|
+
.catch((e: unknown) => {
|
|
156
|
+
console.error(e)
|
|
157
|
+
setError(e)
|
|
158
|
+
})
|
|
159
|
+
.finally(() => {
|
|
160
|
+
setExporting(false)
|
|
161
|
+
})
|
|
162
|
+
})
|
|
128
163
|
}}
|
|
129
164
|
>
|
|
130
|
-
Submit
|
|
165
|
+
{exporting ? 'Exporting…' : 'Submit'}
|
|
131
166
|
</Button>
|
|
132
167
|
<Button
|
|
133
168
|
variant="contained"
|
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
import type { MsaViewModel } from '../model.ts'
|
|
2
2
|
|
|
3
|
-
// The
|
|
4
|
-
// vertical offset offsetY, padded by one row on each side.
|
|
3
|
+
// The half-open range of rows [yStart, yEnd) whose leaves intersect a block of
|
|
4
|
+
// height blockSizeY at vertical offset offsetY, padded by one row on each side.
|
|
5
|
+
// Y-axis counterpart to visibleColRange.
|
|
6
|
+
export function visibleRowRange({
|
|
7
|
+
model,
|
|
8
|
+
offsetY,
|
|
9
|
+
blockSizeY,
|
|
10
|
+
}: {
|
|
11
|
+
model: MsaViewModel
|
|
12
|
+
offsetY: number
|
|
13
|
+
blockSizeY: number
|
|
14
|
+
}) {
|
|
15
|
+
const { rowHeight } = model
|
|
16
|
+
return {
|
|
17
|
+
yStart: Math.max(0, Math.floor((offsetY - rowHeight) / rowHeight)),
|
|
18
|
+
yEnd: Math.max(
|
|
19
|
+
0,
|
|
20
|
+
Math.ceil((offsetY + blockSizeY + rowHeight) / rowHeight),
|
|
21
|
+
),
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// The leaves in that range.
|
|
5
26
|
export function getVisibleLeaves({
|
|
6
27
|
model,
|
|
7
28
|
offsetY,
|
|
@@ -11,11 +32,6 @@ export function getVisibleLeaves({
|
|
|
11
32
|
offsetY: number
|
|
12
33
|
blockSizeY: number
|
|
13
34
|
}) {
|
|
14
|
-
const {
|
|
15
|
-
|
|
16
|
-
const yEnd = Math.max(
|
|
17
|
-
0,
|
|
18
|
-
Math.ceil((offsetY + blockSizeY + rowHeight) / rowHeight),
|
|
19
|
-
)
|
|
20
|
-
return leaves.slice(yStart, yEnd)
|
|
35
|
+
const { yStart, yEnd } = visibleRowRange({ model, offsetY, blockSizeY })
|
|
36
|
+
return model.leaves.slice(yStart, yEnd)
|
|
21
37
|
}
|
|
@@ -13,10 +13,7 @@ import type { MsaViewModel } from '../../model.ts'
|
|
|
13
13
|
const Minimap = observer(function ({ model }: { model: MsaViewModel }) {
|
|
14
14
|
const { minimapHeight, msaCanvasWidth, highResScaleFactor } = model
|
|
15
15
|
const theme = useTheme()
|
|
16
|
-
const { unit, s, w, polygonHeight, polygonPoints } = getMinimapLayout(
|
|
17
|
-
model,
|
|
18
|
-
msaCanvasWidth,
|
|
19
|
-
)
|
|
16
|
+
const { unit, s, w, polygonHeight, polygonPoints } = getMinimapLayout(model)
|
|
20
17
|
|
|
21
18
|
// the whole alignment, sampled down to the bar: it says where the conserved
|
|
22
19
|
// blocks and the gappy stretches are, which is what a scroll aims at
|
|
@@ -1,46 +1,61 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { scrollbarThumbFill } from '../DragHandle.tsx'
|
|
4
|
+
import { canvasHref, msaThumbnail } from '../msa/msaRaster.ts'
|
|
5
5
|
import { MINIMAP_BAR_HEIGHT, getMinimapLayout } from './minimapLayout.ts'
|
|
6
6
|
|
|
7
7
|
import type { MsaViewModel } from '../../model.ts'
|
|
8
|
+
import type { Theme } from '@mui/material'
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
// The static counterpart to Minimap, for the SVG export. renderToSvg drives it
|
|
11
|
+
// through renderToStaticMarkup, so it reads the model once and never re-renders
|
|
12
|
+
// -- no observer wrapper.
|
|
13
|
+
export default function MinimapSVG({
|
|
14
|
+
model,
|
|
15
|
+
theme,
|
|
16
|
+
}: {
|
|
17
|
+
model: MsaViewModel
|
|
18
|
+
theme: Theme
|
|
19
|
+
}) {
|
|
20
|
+
const { msaCanvasWidth } = model
|
|
21
|
+
const { s, w, polygonPoints } = getMinimapLayout(model)
|
|
22
|
+
// the same downsampled alignment the live minimap draws behind its thumb;
|
|
23
|
+
// undefined wherever a canvas cannot be read back, leaving a bare outline
|
|
24
|
+
const href = canvasHref(
|
|
25
|
+
msaThumbnail({ model, theme, height: MINIMAP_BAR_HEIGHT }),
|
|
26
|
+
)
|
|
14
27
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
return (
|
|
29
|
+
<>
|
|
30
|
+
{href ? (
|
|
31
|
+
<image
|
|
32
|
+
href={href}
|
|
18
33
|
x={0}
|
|
19
34
|
y={0}
|
|
20
|
-
width={
|
|
35
|
+
width={msaCanvasWidth}
|
|
21
36
|
height={MINIMAP_BAR_HEIGHT}
|
|
22
|
-
|
|
23
|
-
fill="none"
|
|
37
|
+
preserveAspectRatio="none"
|
|
24
38
|
/>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
) : null}
|
|
40
|
+
<rect
|
|
41
|
+
x={0}
|
|
42
|
+
y={0}
|
|
43
|
+
width={msaCanvasWidth}
|
|
44
|
+
height={MINIMAP_BAR_HEIGHT}
|
|
45
|
+
stroke="#555"
|
|
46
|
+
fill="none"
|
|
47
|
+
/>
|
|
48
|
+
<rect
|
|
49
|
+
x={Math.max(0, s)}
|
|
50
|
+
y={0}
|
|
51
|
+
width={w}
|
|
52
|
+
height={MINIMAP_BAR_HEIGHT}
|
|
53
|
+
fill={scrollbarThumbFill}
|
|
54
|
+
stroke="#555"
|
|
55
|
+
/>
|
|
56
|
+
<g transform={`translate(0 ${MINIMAP_BAR_HEIGHT})`}>
|
|
57
|
+
<polygon fill={scrollbarThumbFill} points={polygonPoints} />
|
|
58
|
+
</g>
|
|
59
|
+
</>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
@@ -23,7 +23,7 @@ test('the viewport rectangle scales by the canvas width, not the msa area', () =
|
|
|
23
23
|
expect(model.showVerticalScrollbar).toBe(true)
|
|
24
24
|
expect(model.msaCanvasWidth).toBe(model.msaAreaWidth - 20)
|
|
25
25
|
|
|
26
|
-
const { unit, s, w } = getMinimapLayout(model
|
|
26
|
+
const { unit, s, w } = getMinimapLayout(model)
|
|
27
27
|
expect(unit).toBeCloseTo(model.msaCanvasWidth / model.totalWidth)
|
|
28
28
|
// unscrolled, the rectangle starts at the left edge and covers the fraction
|
|
29
29
|
// of the alignment on screen. Sizing it by msaAreaWidth instead overstated
|
|
@@ -34,12 +34,12 @@ test('the viewport rectangle scales by the canvas width, not the msa area', () =
|
|
|
34
34
|
)
|
|
35
35
|
})
|
|
36
36
|
|
|
37
|
-
test('the rectangle tracks scrollX and the trapezoid spans the
|
|
37
|
+
test('the rectangle tracks scrollX and the trapezoid spans the bar', () => {
|
|
38
38
|
const model = makeModel()
|
|
39
39
|
model.setScrollX(-300)
|
|
40
40
|
|
|
41
|
-
const width =
|
|
42
|
-
const { s, polygonHeight, polygonPoints } = getMinimapLayout(model
|
|
41
|
+
const { msaCanvasWidth: width } = model
|
|
42
|
+
const { s, polygonHeight, polygonPoints } = getMinimapLayout(model)
|
|
43
43
|
expect(s).toBeCloseTo((300 / model.totalWidth) * width)
|
|
44
44
|
expect(polygonHeight).toBe(model.minimapHeight - MINIMAP_BAR_HEIGHT)
|
|
45
45
|
// bottom edge spans the full minimap, top edge is the viewport rectangle
|
|
@@ -48,11 +48,22 @@ test('the rectangle tracks scrollX and the trapezoid spans the given width', ()
|
|
|
48
48
|
).toBe(true)
|
|
49
49
|
})
|
|
50
50
|
|
|
51
|
+
test('an alignment that already fits never overflows the bar', () => {
|
|
52
|
+
const model = MSAModelF().create({
|
|
53
|
+
type: 'MsaView',
|
|
54
|
+
msaFormat: 'fasta',
|
|
55
|
+
data: { msa: '>a\nACGT\n>b\nACGT' },
|
|
56
|
+
})
|
|
57
|
+
model.setWidth(1000)
|
|
58
|
+
expect(model.totalWidth).toBeLessThan(model.msaCanvasWidth)
|
|
59
|
+
expect(getMinimapLayout(model).w).toBe(model.msaCanvasWidth)
|
|
60
|
+
})
|
|
61
|
+
|
|
51
62
|
test('an empty alignment collapses to a zero-width unit instead of dividing by zero', () => {
|
|
52
63
|
const model = MSAModelF().create({ type: 'MsaView' })
|
|
53
64
|
model.setWidth(1000)
|
|
54
65
|
expect(model.totalWidth).toBe(0)
|
|
55
|
-
const { unit, s, w } = getMinimapLayout(model
|
|
66
|
+
const { unit, s, w } = getMinimapLayout(model)
|
|
56
67
|
expect(unit).toBe(0)
|
|
57
68
|
expect(s).toBeCloseTo(0)
|
|
58
69
|
// still clamped to the minimum grabbable width
|