我有以下 d3/d3fc 图表
https://codepen.io/parliament718/pen/BaNQPXx
该图表具有主区域的缩放行为和 y 轴的单独缩放行为。可以拖动 y 轴以重新缩放。
我无法解决的问题是,在拖动 y 轴以重新缩放然后平移图表后,图表中有一个“跳跃”。
显然,这 2 个缩放行为有断开连接,需要同步,但我正在绞尽脑汁试图解决这个问题。
const mainZoom = zoom()
.on('zoom', () => {
xScale.domain(t.rescaleX(x2).domain());
yScale.domain(t.rescaleY(y2).domain());
});
const yAxisZoom = zoom()
.on('zoom', () => {
const t = event.transform;
yScale.domain(t.rescaleY(y2).domain());
render();
});
const yAxisDrag = drag()
.on('drag', (args) => {
const factor = Math.pow(2, -event.dy * 0.01);
plotArea.call(yAxisZoom.scaleBy, factor);
});
Run Code Online (Sandbox Code Playgroud)
所需的行为是缩放、平移和/或重新缩放轴,以始终从上一个操作完成的位置应用变换,而不会出现任何“跳跃”。
Col*_*inE 16
好的,所以我又做了一次 - 正如我之前的回答中提到的,您需要克服的最大问题是 d3-zoom 只允许对称缩放。这是一个已经被广泛讨论的问题,我相信 Mike Bostock 会在下一个版本中解决这个问题。
因此,为了克服这个问题,您需要使用多个缩放行为。我创建了一个图表,其中包含三个,每个轴一个,绘图区域一个。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;
};
Run Code Online (Sandbox Code Playgroud)
这是带有工作示例的笔:
https://codepen.io/colineberhardt-the-bashful/pen/qBOEEGJ
你的代码有几个问题,一个很容易解决,一个不是......
首先,d3-zoom 通过在选定的 DOM 元素上存储变换来工作 - 您可以通过__zoom属性看到这一点。当用户与 DOM 元素交互时,此转换会更新并发出事件。因此,如果您有不同的缩放行为,这两者都控制单个元素的平移/缩放,则需要保持这些变换同步。
您可以按如下方式复制转换:
selection.call(zoom.transform, d3.event.transform);
Run Code Online (Sandbox Code Playgroud)
但是,这也会导致缩放事件也从目标行为中触发。
另一种方法是直接复制到“隐藏”转换属性:
selection.node().__zoom = d3.event.transform;
Run Code Online (Sandbox Code Playgroud)
但是,您要实现的目标存在更大的问题。d3-zoom 变换存储为变换矩阵的 3 个分量:
https://github.com/d3/d3-zoom#zoomTransform
因此,缩放只能表示对称缩放和平移。您应用于 x 轴的非对称缩放无法通过此变换忠实地表示并重新应用于绘图区域。
正如@ColinE 已经指出的那样,这是一项即将推出的功能。原始代码始终执行与变换矩阵不同步的“时间缩放”。
最好的解决方法是调整xExtent范围,使图表相信两侧有额外的蜡烛。这可以通过在侧面添加衬垫来实现。的accessors,而不是,
[d => d.date]
Run Code Online (Sandbox Code Playgroud)
变成,
[
() => new Date(taken[0].date.addDays(-xZoom)), // Left pad
d => d.date,
() => new Date(taken[taken.length - 1].date.addDays(xZoom)) // Right pad
]
Run Code Online (Sandbox Code Playgroud)
旁注:请注意,有一个pad函数应该这样做,但由于某种原因它只能工作一次并且永远不会再次更新,这就是为什么它被添加为accessors.
旁注 2:addDays为了简单起见,作为原型添加的函数(不是最好的做法)。
现在缩放事件修改了我们的 X 缩放因子xZoom,
zoomFactor = Math.sign(d3.event.sourceEvent.wheelDelta) * -5;
if (zoomFactor) xZoom += zoomFactor;
Run Code Online (Sandbox Code Playgroud)
直接从 读取差值很重要wheelDelta。这就是不受支持的功能所在:我们无法读取,t.x因为即使您拖动 Y 轴它也会改变。
最后重新计算 chart.xDomain(xExtent(data.series));以便新范围可用。
修正:缩放反转,改进了触控板上的行为。
从技术上讲,您还可以yExtent通过添加额外的d.highand 来进行调整d.low。甚至都xExtent和yExtent避免使用在所有的变换矩阵。