Итак, я попробовал еще раз - как уже упоминалось в моем предыдущем ответе , самая большая проблема, которую вам нужно преодолеть, заключается в том, что d3-zoom допускает только симметричное масштабирование. Это то, что широко обсуждалось , и я считаю, что Майк Босток решит эту проблему в следующем выпуске.
Таким образом, чтобы преодолеть проблему, вам нужно использовать режим многократного увеличения. Я создал диаграмму с тремя, по одному для каждой оси и по одному для области графика. Режимы масштабирования X & Y используются для масштабирования осей. Всякий раз, когда событие масштабирования вызывается режимами масштабирования X & Y, их значения перевода копируются в область графика. Аналогично, когда перевод происходит в области графика, компоненты x & y копируются в соответствующие поведения оси.
Масштабирование области графика немного сложнее, так как нам нужно поддерживать соотношение сторон. Чтобы добиться этого, я сохраняю предыдущее преобразование масштаба и использую дельту шкалы, чтобы определить подходящий масштаб для применения к режимам масштабирования X & Y.
Для удобства я обернул все это в компонент диаграммы:
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;
};
Вот ручка с рабочим примером:
https://codepen.io/colineberhardt-the-bashful/pen/qBOEEGJ
Есть несколько проблем с вашим кодом, один из которых легко решить, а другой нет ...
Во-первых, d3-zoom работает, сохраняя преобразование на выбранных элементах DOM - вы можете увидеть это через
__zoom
свойство. Когда пользователь взаимодействует с элементом DOM, это преобразование обновляется и события генерируются. Поэтому, если вам нужно использовать разные способы масштабирования, которые управляют панорамированием / масштабированием одного элемента, вам необходимо синхронизировать эти преобразования.Вы можете скопировать преобразование следующим образом:
Однако это также приведет к тому, что события масштабирования также будут запускаться из целевого поведения.
Альтернативой является копирование непосредственно в свойство преобразования stashed:
Однако есть большая проблема с тем, чего вы пытаетесь достичь. Преобразование d3-zoom сохраняется как 3 компонента матрицы преобразования:
https://github.com/d3/d3-zoom#zoomTransform
В результате масштабирование может представлять только симметричное масштабирование вместе с переводом. Ваш асимметричный зум, примененный к оси x, не может быть точно представлен этим преобразованием и повторно применен к области графика.
источник
Это предстоящая особенность новая , как уже отмечалось @ColinE. Исходный код всегда выполняет «временное масштабирование», которое не синхронизируется с матрицей преобразования.
Лучший обходной путь - настроить
xExtent
диапазон так, чтобы график полагал, что по бокам есть дополнительные свечи. Это может быть достигнуто путем добавления колодок по бокам.accessors
, Вместо того,становится,
Примечание: обратите внимание, что есть
pad
функция, которая должна это делать, но по какой-то причине она работает только один раз и никогда не обновляется, поэтому она добавляется какaccessors
.Sidenote 2: Функция
addDays
добавлена в качестве прототипа (не самое лучшее, что нужно сделать) просто для простоты.Теперь событие масштабирования изменяет наш коэффициент увеличения X
xZoom
,Важно прочитать дифференциал непосредственно из
wheelDelta
. Вот где неподдерживаемая функция: мы не можем читать сt.x
как она изменится, даже если вы перетащите ось Y.Наконец, пересчитать
chart.xDomain(xExtent(data.series));
чтобы новый экстент был доступен.Смотрите рабочую демонстрацию без перехода здесь: https://codepen.io/adelriosantiago/pen/QWjwRXa?editors=0011
Исправлено: изменение масштаба, улучшено поведение трекпада.
Технически вы также можете настроить
yExtent
, добавив дополнительныеd.high
иd.low
. Или даже и тоxExtent
и другое,yExtent
чтобы вообще не использовать матрицу преобразования.источник