Improved zoom support

This commit is contained in:
Przemek Wiech
2020-03-14 16:51:07 +01:00
parent 67c78b5982
commit 442de4c7c5

View File

@@ -21,19 +21,17 @@ import {
* @param size the size of the chart * @param size the size of the chart
*/ */
function zoomed(size: [number, number], enableZoom: boolean) { function zoomed(size: [number, number], enableZoom: boolean) {
const svg = d3.select('#chart'); const parent = d3.select('#svgContainer').node() as Element;
const parent = (svg.node() as HTMLElement).parentElement!;
if (enableZoom) { if (enableZoom) {
const scale = d3.event.transform.k; const scale = d3.event.transform.k;
const offsetX = d3.max([0, (parent.clientWidth / scale - size[0]) / 2])!; const offsetX = d3.max([0, (parent.clientWidth - size[0] * scale) / 2]);
const offsetY = d3.max([0, (parent.clientHeight / scale - size[1]) / 2])!; const offsetY = d3.max([0, (parent.clientHeight - size[1] * scale) / 2]);
svg.attr( d3.select('#chartSvg')
'transform', .attr('width', size[0] * scale)
`translate(${-size[0] / 2}, ${-size[1] / 2}) .attr('height', size[1] * scale)
scale(${scale}) .attr('transform', `translate(${offsetX}, ${offsetY})`);
translate(${size[0] / 2 + offsetX}, ${size[1] / 2 + offsetY})`, d3.select('#chart').attr('transform', `scale(${scale})`);
);
} }
parent.scrollLeft = -d3.event.transform.x; parent.scrollLeft = -d3.event.transform.x;
@@ -42,8 +40,7 @@ function zoomed(size: [number, number], enableZoom: boolean) {
/** Called when the scrollbars are used. */ /** Called when the scrollbars are used. */
function scrolled(enableZoom: boolean) { function scrolled(enableZoom: boolean) {
const svg = d3.select('#chart'); const parent = d3.select('#svgContainer').node() as Element;
const parent = (svg.node() as HTMLElement).parentElement as Element;
const x = parent.scrollLeft + parent.clientWidth / 2; const x = parent.scrollLeft + parent.clientWidth / 2;
const y = parent.scrollTop + parent.clientHeight / 2; const y = parent.scrollTop + parent.clientHeight / 2;
const scale = enableZoom ? d3.zoomTransform(parent).k : 1; const scale = enableZoom ? d3.zoomTransform(parent).k : 1;
@@ -200,8 +197,10 @@ export class Chart extends React.PureComponent<ChartProps, {}> {
startIndi: this.props.selection.id, startIndi: this.props.selection.id,
baseGeneration: this.props.selection.generation, baseGeneration: this.props.selection.generation,
}); });
const svg = d3.select('#chart'); const svg = d3.select('#chartSvg');
const parent = (svg.node() as HTMLElement).parentElement as Element; const parent = d3.select('#svgContainer').node() as Element;
const scale = this.props.enableZoom ? d3.zoomTransform(parent).k : 1;
d3.select(parent) d3.select(parent)
.on('scroll', () => scrolled(this.props.enableZoom)) .on('scroll', () => scrolled(this.props.enableZoom))
@@ -230,10 +229,16 @@ export class Chart extends React.PureComponent<ChartProps, {}> {
}; };
}; };
const dx = parent.clientWidth / 2 - chartInfo.origin[0]; const dx = parent.clientWidth / 2 - chartInfo.origin[0] * scale;
const dy = parent.clientHeight / 2 - chartInfo.origin[1]; const dy = parent.clientHeight / 2 - chartInfo.origin[1] * scale;
const offsetX = d3.max([0, (parent.clientWidth - chartInfo.size[0]) / 2]); const offsetX = d3.max([
const offsetY = d3.max([0, (parent.clientHeight - chartInfo.size[1]) / 2]); 0,
(parent.clientWidth - chartInfo.size[0] * scale) / 2,
]);
const offsetY = d3.max([
0,
(parent.clientHeight - chartInfo.size[1] * scale) / 2,
]);
const svgTransition = svg const svgTransition = svg
.transition() .transition()
.delay(200) .delay(200)
@@ -241,8 +246,8 @@ export class Chart extends React.PureComponent<ChartProps, {}> {
const transition = args.initialRender ? svg : svgTransition; const transition = args.initialRender ? svg : svgTransition;
transition transition
.attr('transform', `translate(${offsetX}, ${offsetY})`) .attr('transform', `translate(${offsetX}, ${offsetY})`)
.attr('width', chartInfo.size[0]) .attr('width', chartInfo.size[0] * scale)
.attr('height', chartInfo.size[1]); .attr('height', chartInfo.size[1] * scale);
if (args.initialRender) { if (args.initialRender) {
parent.scrollLeft = -dx; parent.scrollLeft = -dx;
parent.scrollTop = -dy; parent.scrollTop = -dy;
@@ -280,20 +285,40 @@ export class Chart extends React.PureComponent<ChartProps, {}> {
render() { render() {
return ( return (
<div id="svgContainer"> <div id="svgContainer">
<svg id="chart" /> <svg id="chartSvg">
<g id="chart" />
</svg>
</div> </div>
); );
} }
private getSvgContents() { /** Return a copy of the SVG chart but without scaling and positioning. */
const svg = document.getElementById('chart')!.cloneNode(true) as Element; private getStrippedSvg() {
const svg = document.getElementById('chartSvg')!.cloneNode(true) as Element;
svg.removeAttribute('transform'); svg.removeAttribute('transform');
return new XMLSerializer().serializeToString(svg); if (this.props.enableZoom) {
const parent = d3.select('#svgContainer').node() as Element;
const scale = d3.zoomTransform(parent).k;
svg.setAttribute(
'width',
String(Number(svg.getAttribute('width')) / scale),
);
svg.setAttribute(
'height',
String(Number(svg.getAttribute('height')) / scale),
);
svg.querySelector('#chart')!.removeAttribute('transform');
}
return svg;
}
private getSvgContents() {
return new XMLSerializer().serializeToString(this.getStrippedSvg());
} }
private async getSvgContentsWithInlinedImages() { private async getSvgContentsWithInlinedImages() {
const svg = document.getElementById('chart')!.cloneNode(true) as Element; const svg = this.getStrippedSvg();
svg.removeAttribute('transform');
await inlineImages(svg); await inlineImages(svg);
return new XMLSerializer().serializeToString(svg); return new XMLSerializer().serializeToString(svg);
} }