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
Hay un par de problemas con su código, uno que es fácil de resolver y otro que no ...
En primer lugar, el zoom d3 funciona almacenando una transformación en los elementos DOM seleccionados; puede ver esto a través de la
__zoom
propiedad. Cuando el usuario interactúa con el elemento DOM, esta transformación se actualiza y se emiten eventos. Por lo tanto, si tiene diferentes comportamientos de zoom que controlan la panorámica / zoom de un solo elemento, debe mantener estas transformaciones sincronizadas.Puede copiar la transformación de la siguiente manera:
Sin embargo, esto también hará que se disparen eventos de zoom desde el comportamiento del objetivo también.
Una alternativa es copiar directamente a la propiedad de transformación 'escondida':
Sin embargo, hay un problema mayor con lo que está tratando de lograr. La transformación d3-zoom se almacena como 3 componentes de una matriz de transformación:
https://github.com/d3/d3-zoom#zoomTransform
Como resultado, el zoom solo puede representar una escala simétrica junto con una traducción. Su zoom asimétrico como aplicado al eje x no puede ser fielmente representado por esta transformación y reaplicado al área de trazado.
fuente
Esta es una característica próxima , como ya señaló @ColinE. El código original siempre está haciendo un "zoom temporal" que no está sincronizado desde la matriz de transformación.
La mejor solución es ajustar el
xExtent
rango para que el gráfico crea que hay velas adicionales en los lados. Esto se puede lograr agregando almohadillas a los lados. Elaccessors
, en lugar de ser,se convierte,
Nota al margen: Tenga en cuenta que hay una
pad
función que debería hacer eso, pero por alguna razón funciona solo una vez y nunca se actualiza nuevamente, por eso se agrega como unaccessors
.Sidenote 2: Función
addDays
agregada como prototipo (no es lo mejor que se puede hacer) solo por simplicidad.Ahora el evento de zoom modifica nuestro factor de zoom X
xZoom
,Es importante leer el diferencial directamente desde
wheelDelta
. Aquí es donde está la característica no compatible: no podemos leer,t.x
ya que cambiará incluso si arrastra el eje Y.Finalmente, vuelva a calcular
chart.xDomain(xExtent(data.series));
para que la nueva extensión esté disponible.Vea la demostración de trabajo sin el salto aquí: https://codepen.io/adelriosantiago/pen/QWjwRXa?editors=0011
Corregido: inversión de zoom, comportamiento mejorado en el panel táctil.
Técnicamente, también podría ajustar
yExtent
agregando extrad.high
yd.low
's. O incluso ambos,xExtent
yyExtent
evitar usar la matriz de transformación.fuente