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);
    });

目的の動作は、軸をズーム、パン、または再スケーリングして、「ジャンプ」することなく、前のアクションが終了したところから常に変換を適用することです。

回答:


10

さて、私はこれでもう一度やりました- 以前の回答で述べたように、あなたが克服する必要がある最大の問題は、d3-zoomは対称的なスケーリングしか許可しないということです。これは広く議論てきたことであり、マイクボストックは次のリリースでこれに対処していると思います。

したがって、この問題を解決するには、複数のズーム動作を使用する必要があります。各軸に1つ、プロットエリアに1つ、計3つのチャートを作成しました。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


コリン、これをもう一度やってくれてありがとう。私はあなたのコードペンを開けて、それが期待通りに動かないことに気づきました。私のコードペンでは、Y軸をドラッグすると、グラフのサイズが変更されます(これは望ましい動作です)。コードペンでは、軸をドラッグするだけでチャートをパンできます。
国会

1
yスケールとプロットエリアの両方にドラッグできるコードを作成できましたcodepen.io/colineberhardt-the-bashful/pen/mdeJyrK-プロットエリアでのズームは非常に困難です
ColinE

5

コードにはいくつかの問題があります。1つは簡単に解決でき、もう1つは解決できません...

まず、d3-zoomは、選択したDOM要素に変換を保存することで機能し__zoomます。これは、プロパティで確認できます。ユーザーがDOM要素を操作すると、この変換が更新され、イベントが発生します。したがって、単一の要素のパン/ズームを制御する異なるズーム動作が必要な場合は、これらの変換を同期させておく必要があります。

次のように変換をコピーできます。

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

ただし、これにより、ターゲットイベントからズームイベントも発生します。

別の方法は、 'stashed'変換プロパティに直接コピーすることです。

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

しかし、あなたが達成しようとしていることにはもっと大きな問題があります。d3-zoom変換は、変換行列の3つのコンポーネントとして格納されます。

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

その結果、ズームは、平行移動とともに対称的なスケーリングのみを表すことができます。x軸に適用された非対称ズームは、この変換では忠実に表現できず、プロット領域に再適用できません。


Colinに感謝します。これが私にとって理にかなっているといいのですが、解決策が何なのかわかりません。私ができる限り、そしてできれば誰かが私のコードペンの修正を手伝ってくれるとすぐに、私はこの質問に500バウンティを追加します。
国会

心配する必要はありません。簡単に修正できる問題ではありませんが、報奨金はいくつかの助けを提供する可能性があります。
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これを実行する必要がある関数があることに注意してください。ただし、何らかの理由で1度しか機能せず、2度と更新されないため、として追加されます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.highand を追加することで調整することもできますd.low。あるいは両方xExtentyExtent、すべての変換行列を使用しないように。


ありがとうございました。残念ながら、ここでのズーム動作は本当にめちゃくちゃです。ズームは非常にぎくしゃくしているように感じられ、場合によっては(macbookトラックパッドを使用して)方向を数回反転させます。ズーム動作は、元のペンと同じである必要があります。
国会

ペンを更新し、特に反転ズームをいくつか改善しました。
adelriosantiago

1
私はあなたの努力に対する報奨金をあなたに授与するつもりです、そして私はまだより良いズーム/パンでの答えが必要なので、私は2番目の報奨金を始めます。残念ながら、デルタの変更に基づくこの方法は、ズーム時やパン時のパン速度が速すぎる場合でも、ぎくしゃくしてスムーズではありません。おそらく、係数を無限に調整しても、スムーズな動作が得られないのではないかと思います。元のペンのズーム/パン動作を変更しない回答を探しています。
国会

1
また、X軸に沿ってのみズームするように元のペンを調整しました。それは当然の行動であり、今やそれが答えを複雑にするかもしれないことに気づきました。
国会
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.