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이 대칭 스케일링 만 허용한다는 것입니다. 이것은 광범위하게 논의 된 내용 이며 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


콜린, 이거 또 줘서 고마워 코드 펜을 열고 예상대로 작동하지 않는 것으로 나타났습니다. 내 코드 펜에서 Y 축을 드래그하면 차트의 크기가 조정됩니다 (원하는 동작입니다). 코드 펜에서 축을 드래그하면 차트가 이동합니다.
의회

1
y- 스케일과 플롯 영역 codepen.io/colineberhardt-the-bashful/pen/mdeJyrK 를 모두 드래그 할 수있는 것을 만들었습니다 . 그러나 플롯 영역을 확대하는 것은 매우 어려운 일입니다
ColinE

5

코드에는 몇 가지 문제가 있습니다. 하나는 해결하기 쉽고 하나는 해결되지 않습니다.

먼저 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 축에 적용된 비대칭 줌은이 변환으로 충실하게 표현할 수 없으며 플롯 영역에 다시 적용 할 수 없습니다.


감사합니다 콜린,이 중 하나가 나에게 의미가 있기를 원하지만 해결책이 무엇인지 이해하지 못합니다. 가능한 한 빨리 내 코드 펜을 수정하는 데 도움이되는 즉시이 질문에 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기능을 수행 해야하는 기능이 있지만 어떤 이유로 든 한 번만 작동하고 다시 업데이트되지 않으므로이 기능 이에 추가됩니다 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.highd.low의. 심지어 모두 xExtentyExtent피하기 위해 전혀 변환 매트릭스를 사용하여.


감사합니다. 불행히도 여기서 줌 동작은 실제로 엉망입니다. 확대 / 축소가 매우 불쾌하게 느껴지고 경우에 따라 방향이 여러 번 반전되기도합니다 (Macbook 트랙 패드 사용). 확대 / 축소 동작은 원래 펜과 동일하게 유지되어야합니다.
의회

펜을 몇 가지 개선 사항, 특히 반전 줌으로 업데이트했습니다.
adelriosantiago

1
나는 당신에게 당신의 노력에 대한 현상금을 수여 할 것이며, 더 나은 줌 / 팬에 대한 답변이 여전히 필요하기 때문에 두 번째 현상금을 시작하고 있습니다. 불행하게도 델타 변경을 기반으로하는이 방법은 확대 / 축소시 및 패닝시 너무 빠르게 패닝 될 때 여전히 흔들리지 않고 매끄럽지 않습니다. 나는 당신이 아마도 인자를 끝없이 조정할 수 있고 여전히 부드러운 행동을 얻지 못할 것이라고 생각합니다. 원본 펜의 줌 / 패닝 동작을 변경하지 않는 답변을 찾고 있습니다.
의회

1
또한 X 축을 따라 확대 / 축소하도록 원본 펜을 조정했습니다. 그것은 당연한 행동이며 이제는 답이 복잡해질 수 있음을 알고 있습니다.
의회
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.