d3同步2个单独的缩放行为


11

我有以下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);
    });

所需的行为是缩放,平移和/或重新缩放轴,以始终从上一个动作完成的位置开始应用变换,而不会发生“跳跃”。

Answers:


10

好的,因此我要再处理一次-正如我之前的回答中提到的那样,您需要克服的最大问题是d3缩放仅允许对称缩放。这已被广泛讨论,我相信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;
};

这是带有工作示例的笔:

https://codepen.io/colineberhardt-the-bashful/pen/qBOEEGJ


科林,非常感谢您再次提出建议。我打开了您的Codepen,发现它无法按预期工作。在我的代码笔中,拖动Y轴会重新缩放图表(这是所需的行为)。在您的代码笔中,拖动轴只会平移图表。
议会

1
我设法创建了一个允许您在y比例尺和绘图区域上拖动的代码pen.io/colineberhardt-the-bashful/pen/mdeJyrK-但是放大绘图区域是一个很大的挑战
ColinE

5

您的代码有两个问题,一个很容易解决,另一个不...

首先,d3-zoom通过将变换存储在选定的DOM元素上而起作用-您可以通过__zoom属性来看到此变换。当用户与DOM元素交互时,此转换将更新并发出事件。因此,如果必须使用不同的缩放行为,而这两种行为都控制单个元素的平移/缩放,则需要使这些变换保持同步。

您可以按如下方式复制转换:

selection.call(zoom.transform, d3.event.transform);

但是,这也会导致也从目标行为中触发缩放事件。

另一种选择是直接复制到“隐藏”转换属性:

selection.node().__zoom = d3.event.transform;

但是,您要实现的目标存在更大的问题。d3-zoom转换存储为转换矩阵的3个分量:

https://github.com/d3/d3-zoom#zoomTransform

结果,缩放只能代表平移和对称缩放。应用到x轴的不对称缩放无法如实地由此变换表示,并重新应用于绘图区域。


感谢Colin,我希望以上任何一项对我都有意义,但我不知道解决方案是什么。我将尽快为这个问题增加500英镑的奖金,希望有人可以帮助修复我的Codepen。
议会

不用担心,不是一个容易解决的问题,但是赏金可能会吸引一些帮助。
ColinE

2

正如@ColinE所指出的,这是一项即将推出的功能。原始代码始终会执行与转换矩阵不同步的“时间缩放”。

最好的解决方法是调整xExtent范围,以便该图认为侧面还有其他蜡烛。这可以通过在侧面增加垫片来实现。而accessors不是

[d => d.date]

变成

[
  () => new Date(taken[0].date.addDays(-xZoom)), // Left pad
  d => d.date,
  () => new Date(taken[taken.length - 1].date.addDays(xZoom)) // Right pad
]

旁注:请注意,应该有一个pad函数可以执行此操作,但是由于某种原因,它只能运行一次,并且永远不会再更新,这就是为什么将其添加为accessors

旁注2:addDays为简单起见,将功能添加为原型(不是最好的做法)。

现在,缩放事件会修改我们的X缩放系数xZoom

zoomFactor = Math.sign(d3.event.sourceEvent.wheelDelta) * -5;
if (zoomFactor) xZoom += zoomFactor;

直接从中读取差异非常重要wheelDelta。这是不支持的功能所在的位置:我们无法读取,t.x因为即使您拖动Y轴,它也会改变。

最后,重新计算chart.xDomain(xExtent(data.series));以使新范围可用。

请在此处查看工作示例,而无需跳转:https : //codepen.io/adelriosantiago/pen/QWjwRXa?editors=0011

修正:反向缩放,改善了触控板上的行为。

从技术上讲,您还可以yExtent通过添加额外的d.high和来进行调整d.low。甚至两者兼而有之xExtentyExtent完全避免使用变换矩阵。


谢谢。不幸的是,这里的缩放行为确实混乱了。缩放感觉非常不稳定,在某些情况下甚至可以多次反转方向(使用Macbook触控板)。缩放行为需要保持与原始笔相同。
议会

我对笔进行了一些改进,尤其是可逆缩放。
adelriosantiago

1
我将奖励您所做的努力,并开始提供第二笔赏金,因为我仍然需要更好的缩放/平移答案。不幸的是,这种基于增量变化的方法在缩放时以及在平移时的平移速度都太快时仍然不稳定且不平滑。我怀疑您可能会无休止地调整因子,而仍然无法获得平稳的行为。我正在寻找一个不会改变原始笔的缩放/平移行为的答案。
议会

1
我还调整了原始笔,使其仅沿X轴缩放。那是应有的行为,我现在意识到这可能使答案复杂化。
议会
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.