OK, jadi saya sudah mencoba lagi - seperti yang disebutkan dalam jawaban saya sebelumnya , masalah terbesar yang perlu Anda atasi adalah bahwa d3-zoom hanya memungkinkan penskalaan simetris. Ini adalah sesuatu yang telah dibahas secara luas , dan saya percaya Mike Bostock sedang membahas ini di rilis berikutnya.
Jadi, untuk mengatasi masalah ini, Anda perlu menggunakan beberapa perilaku zoom. Saya telah membuat bagan yang memiliki tiga, satu untuk setiap sumbu dan satu untuk area plot. Perilaku pembesaran X & Y digunakan untuk menskalakan sumbu. Setiap kali acara zoom dinaikkan oleh perilaku zoom X & Y, nilai terjemahannya disalin ke area plot. Demikian juga, ketika terjemahan terjadi pada area plot, komponen x & y disalin ke perilaku sumbu masing-masing.
Penskalaan pada area plot sedikit lebih rumit karena kita perlu mempertahankan aspek rasio. Untuk mencapai ini, saya menyimpan transformasi zoom sebelumnya dan menggunakan skala delta untuk menentukan skala yang sesuai untuk diterapkan pada perilaku zoom X & Y.
Untuk kenyamanan, saya telah membungkus semua ini menjadi komponen bagan:
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;
};
Ini pulpen dengan contoh kerja:
https://codepen.io/colineberhardt-the-bashful/pen/qBOEEGJ
Ada beberapa masalah dengan kode Anda, yang mudah dipecahkan, dan yang tidak ...
Pertama, zoom d3 berfungsi dengan menyimpan transformasi pada elemen DOM yang dipilih - Anda dapat melihatnya melalui
__zoom
properti. Saat pengguna berinteraksi dengan elemen DOM, transformasi ini diperbarui dan acara dipancarkan. Oleh karena itu, jika Anda harus memiliki perilaku zoom yang berbeda, yang keduanya mengendalikan pan / zoom elemen tunggal, Anda harus menjaga agar transformasi ini tetap tersinkronisasi.Anda dapat menyalin transformasi sebagai berikut:
Namun, ini juga akan menyebabkan acara zoom dipecat dari perilaku target juga.
Alternatifnya adalah menyalin langsung ke properti transformasi 'simpanan':
Namun, ada masalah yang lebih besar dengan apa yang ingin Anda capai. Transformasi d3-zoom disimpan sebagai 3 komponen dari matriks transformasi:
https://github.com/d3/d3-zoom#zoomTransform
Akibatnya, zoom hanya dapat mewakili penskalaan simetris bersama dengan terjemahan. Zoom asimetris Anda sebagai diterapkan pada sumbu x tidak dapat dengan setia diwakili oleh transformasi ini dan diterapkan kembali ke area plot.
sumber
Ini adalah fitur yang akan datang , seperti yang sudah dicatat oleh @ColinE. Kode asli selalu melakukan "temporal zoom" yang tidak disinkronkan dari matriks transformasi.
Solusi terbaik adalah mengubah
xExtent
rentang sehingga grafik percaya bahwa ada lilin tambahan di samping. Ini dapat dicapai dengan menambahkan pembalut ke samping. Theaccessors
, bukannya menjadi,menjadi,
Sidenote: Perhatikan bahwa ada
pad
fungsi yang harus melakukan itu tetapi karena beberapa alasan ia berfungsi hanya sekali dan tidak pernah memperbarui lagi itu sebabnya ditambahkan sebagaiaccessors
.Sidenote 2: Fungsi
addDays
ditambahkan sebagai prototipe (bukan hal terbaik untuk dilakukan) hanya untuk kesederhanaan.Sekarang acara zoom memodifikasi faktor zoom X kami
xZoom
,,Penting untuk membaca diferensial langsung dari
wheelDelta
. Di sinilah fitur yang tidak didukung: Kami tidak dapat membaca darit.x
karena akan berubah bahkan jika Anda menyeret sumbu Y.Akhirnya, hitung ulang
chart.xDomain(xExtent(data.series));
sehingga tingkat baru tersedia.Lihat demo kerja tanpa lompatan di sini: https://codepen.io/adelriosantiago/pen/QWjwRXa?editors=0011
Tetap: Pembalikan zoom, peningkatan perilaku pada trackpad.
Secara teknis Anda juga dapat mengubah
yExtent
dengan menambahkan ekstrad.high
dand.low
. Atau bahkan keduanyaxExtent
danyExtent
untuk menghindari menggunakan matriks transformasi sama sekali.sumber