OK, also habe ich es noch einmal versucht - wie in meiner vorherigen Antwort erwähnt , ist das größte Problem, das Sie überwinden müssen, dass der d3-Zoom nur eine symmetrische Skalierung zulässt. Dies wurde viel diskutiert , und ich glaube, Mike Bostock wird dies in der nächsten Version ansprechen.
Um das Problem zu beheben, müssen Sie das Verhalten des Mehrfachzooms verwenden. Ich habe ein Diagramm erstellt, das drei enthält, eines für jede Achse und eines für den Plotbereich. Das X- und Y-Zoomverhalten wird zum Skalieren der Achsen verwendet. Immer wenn ein Zoomereignis durch das X- und Y-Zoomverhalten ausgelöst wird, werden deren Übersetzungswerte in den Plotbereich kopiert. Wenn eine Übersetzung im Plotbereich erfolgt, werden die x & y-Komponenten ebenfalls in das jeweilige Achsenverhalten kopiert.
Die Skalierung des Plotbereichs ist etwas komplizierter, da das Seitenverhältnis beibehalten werden muss. Um dies zu erreichen, speichere ich die vorherige Zoomtransformation und verwende das Skalendelta, um eine geeignete Skalierung für das X- und Y-Zoomverhalten zu erarbeiten.
Der Einfachheit halber habe ich all dies in eine Diagrammkomponente zusammengefasst:
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;
};
Hier ist ein Stift mit dem Arbeitsbeispiel:
https://codepen.io/colineberhardt-the-bashful/pen/qBOEEGJ
Es gibt einige Probleme mit Ihrem Code, eines, das leicht zu lösen ist, und eines, das nicht ...
Erstens speichert der d3-Zoom eine Transformation für die ausgewählten DOM-Elemente. Sie können dies über die
__zoom
Eigenschaft anzeigen. Wenn der Benutzer mit dem DOM-Element interagiert, wird diese Transformation aktualisiert und Ereignisse ausgegeben. Wenn Sie unterschiedliche Zoomverhaltensweisen haben, die beide das Schwenken / Zoomen eines einzelnen Elements steuern, müssen Sie diese Transformationen synchronisieren.Sie können die Transformation wie folgt kopieren:
Dies führt jedoch auch dazu, dass Zoomereignisse auch vom Zielverhalten ausgelöst werden.
Eine Alternative besteht darin, direkt in die Transformationseigenschaft "Stashed" zu kopieren:
Es gibt jedoch ein größeres Problem mit dem, was Sie erreichen möchten. Die d3-Zoom-Transformation wird als 3 Komponenten einer Transformationsmatrix gespeichert:
https://github.com/d3/d3-zoom#zoomTransform
Infolgedessen kann der Zoom nur eine symmetrische Skalierung zusammen mit einer Übersetzung darstellen. Ihr asymmetrischer Zoom als auf die x-Achse angewendet kann durch diese Transformation nicht originalgetreu dargestellt und erneut auf den Plotbereich angewendet werden.
quelle
Dies ist eine bevorstehende Funktion , wie bereits von @ColinE erwähnt. Der ursprüngliche Code führt immer einen "zeitlichen Zoom" durch, der nicht mit der Transformationsmatrix synchronisiert ist.
Die beste Problemumgehung besteht darin, den
xExtent
Bereich so anzupassen , dass in der Grafik davon ausgegangen wird, dass sich an den Seiten zusätzliche Kerzen befinden. Dies kann durch Hinzufügen von Pads an den Seiten erreicht werden. Dasaccessors
, anstatt zu sein,wird,
Nebenbemerkung: Beachten Sie, dass es eine
pad
Funktion gibt, die dies tun sollte, aber aus irgendeinem Grund nur einmal funktioniert und nie wieder aktualisiert wird. Deshalb wird sie als hinzugefügtaccessors
.Nebenbemerkung 2: Der
addDays
Einfachheit halber wurde eine Funktion als Prototyp hinzugefügt (nicht das Beste).Jetzt ändert das Zoomereignis unseren X-Zoomfaktor
xZoom
.Es ist wichtig, das Differential direkt aus zu lesen
wheelDelta
. Hier ist die nicht unterstützte Funktion: Wir können nicht lesen,t.x
da sie sich ändert, selbst wenn Sie die Y-Achse ziehen.Berechnen Sie abschließend neu,
chart.xDomain(xExtent(data.series));
damit der neue Umfang verfügbar ist.Die funktionierende Demo ohne Sprung finden Sie hier: https://codepen.io/adelriosantiago/pen/QWjwRXa?editors=0011
Behoben: Zoomumkehr, verbessertes Verhalten auf dem Trackpad.
Technisch können Sie auch
yExtent
durch Hinzufügen von Extrad.high
undd.low
's optimieren . Oder sogar beidesxExtent
undyExtent
um die Transformationsmatrix überhaupt nicht zu verwenden.quelle