De acuerdo, he tenido otra oportunidad en esto: como se mencionó en mi respuesta anterior , el mayor problema que debe superar es que el zoom d3 solo permite el escalado simétrico. Esto es algo que ha sido ampliamente discutido , y creo que Mike Bostock está abordando esto en el próximo lanzamiento.
Entonces, para superar el problema, debe usar el comportamiento de zoom múltiple. He creado un gráfico que tiene tres, uno para cada eje y otro para el área de trazado. Los comportamientos de zoom X e Y se utilizan para escalar los ejes. Siempre que los comportamientos de zoom X e Y provocan un evento de zoom, sus valores de traducción se copian en el área de trazado. Del mismo modo, cuando se produce una traducción en el área de trazado, los componentes x e y se copian en los comportamientos de los ejes respectivos.
Escalar en el área de trazado es un poco más complicado ya que necesitamos mantener la relación de aspecto. Para lograr esto, almaceno la transformación de zoom anterior y uso el delta de escala para calcular una escala adecuada para aplicar a los comportamientos de zoom X e Y.
Para mayor comodidad, he incluido todo esto en un componente gráfico:
const interactiveChart = (xScale, yScale) => {
const zoom = d3.zoom();
const xZoom = d3.zoom();
const yZoom = d3.zoom();
const chart = fc.chartCartesian(xScale, yScale).decorate(sel => {
const plotAreaNode = sel.select(".plot-area").node();
const xAxisNode = sel.select(".x-axis").node();
const yAxisNode = sel.select(".y-axis").node();
const applyTransform = () => {
// apply the zoom transform from the x-scale
xScale.domain(
d3
.zoomTransform(xAxisNode)
.rescaleX(xScaleOriginal)
.domain()
);
// apply the zoom transform from the y-scale
yScale.domain(
d3
.zoomTransform(yAxisNode)
.rescaleY(yScaleOriginal)
.domain()
);
sel.node().requestRedraw();
};
zoom.on("zoom", () => {
// compute how much the user has zoomed since the last event
const factor = (plotAreaNode.__zoom.k - plotAreaNode.__zoomOld.k) / plotAreaNode.__zoomOld.k;
plotAreaNode.__zoomOld = plotAreaNode.__zoom;
// apply scale to the x & y axis, maintaining their aspect ratio
xAxisNode.__zoom.k = xAxisNode.__zoom.k * (1 + factor);
yAxisNode.__zoom.k = yAxisNode.__zoom.k * (1 + factor);
// apply transform
xAxisNode.__zoom.x = d3.zoomTransform(plotAreaNode).x;
yAxisNode.__zoom.y = d3.zoomTransform(plotAreaNode).y;
applyTransform();
});
xZoom.on("zoom", () => {
plotAreaNode.__zoom.x = d3.zoomTransform(xAxisNode).x;
applyTransform();
});
yZoom.on("zoom", () => {
plotAreaNode.__zoom.y = d3.zoomTransform(yAxisNode).y;
applyTransform();
});
sel
.enter()
.select(".plot-area")
.on("measure.range", () => {
xScaleOriginal.range([0, d3.event.detail.width]);
yScaleOriginal.range([d3.event.detail.height, 0]);
})
.call(zoom);
plotAreaNode.__zoomOld = plotAreaNode.__zoom;
// cannot use enter selection as this pulls data through
sel.selectAll(".y-axis").call(yZoom);
sel.selectAll(".x-axis").call(xZoom);
decorate(sel);
});
let xScaleOriginal = xScale.copy(),
yScaleOriginal = yScale.copy();
let decorate = () => {};
const instance = selection => chart(selection);
// property setters not show
return instance;
};
Aquí hay una pluma con el ejemplo de trabajo:
https://codepen.io/colineberhardt-the-bashful/pen/qBOEEGJ