d3 đồng bộ hóa 2 hành vi zoom riêng biệt


11

Tôi có biểu đồ d3 / d3fc sau đây

https://codepen.io/parferences718/pen/BaNQPXx

Biểu đồ có hành vi thu phóng cho khu vực chính và hành vi thu phóng riêng cho trục y. Trục y có thể được kéo để bán lại.

Vấn đề tôi gặp khó khăn khi giải quyết là sau khi kéo trục y để hủy và sau đó xoay biểu đồ, có một "bước nhảy" trong biểu đồ.

Rõ ràng là 2 hành vi thu phóng có sự ngắt kết nối và cần được đồng bộ hóa nhưng tôi đang cố gắng khắc phục điều này.

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

Hành vi mong muốn là phóng to, xoay và / hoặc thay đổi kích thước trục để luôn áp dụng chuyển đổi từ bất kỳ nơi nào hành động trước đó kết thúc mà không có bất kỳ "bước nhảy" nào.

Câu trả lời:


10

OK, vì vậy tôi đã có một bước đi khác về vấn đề này - như đã đề cập trong câu trả lời trước đây của tôi , vấn đề lớn nhất bạn cần khắc phục là zoom d3 chỉ cho phép chia tỷ lệ đối xứng. Đây là điều đã được thảo luận rộng rãi và tôi tin rằng Mike Bostock đang giải quyết vấn đề này trong phiên bản tiếp theo.

Vì vậy, để khắc phục sự cố, bạn cần sử dụng nhiều hành vi thu phóng. Tôi đã tạo một biểu đồ có ba, một cho mỗi trục và một cho khu vực cốt truyện. Các hành vi thu phóng X & Y được sử dụng để chia tỷ lệ các trục. Bất cứ khi nào một sự kiện thu phóng được nâng lên bởi các hành vi thu phóng X & Y, các giá trị dịch của chúng sẽ được sao chép qua khu vực cốt truyện. Tương tự, khi một bản dịch xảy ra trên vùng vẽ, các thành phần x & y được sao chép sang các hành vi trục tương ứng.

Chia tỷ lệ trên khu vực cốt truyện phức tạp hơn một chút vì chúng ta cần duy trì tỷ lệ khung hình. Để đạt được điều này, tôi lưu trữ biến đổi thu phóng trước đó và sử dụng thang đo tỷ lệ để tìm ra một tỷ lệ phù hợp để áp dụng cho các hành vi thu phóng X & Y.

Để thuận tiện, tôi đã gói tất cả những thứ này vào một thành phần biểu đồ:


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

Đây là một cây bút với ví dụ hoạt động:

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


Colin, cảm ơn rất nhiều vì đã cho điều này một lần nữa. Tôi đã mở codepen của bạn và nhận thấy rằng nó không hoạt động như mong đợi. Trong codepen của tôi, kéo trục Y lại chia tỷ lệ biểu đồ (đây là hành vi mong muốn). Trong codepen của bạn, kéo trục chỉ đơn giản là quét biểu đồ.
Quốc hội

1
Tôi đã quản lý để tạo một cái cho phép bạn kéo theo cả quy mô y và khu vực cốt truyện codepen.io/colineberhardt-the-bashful/pen/mdeJyrK - nhưng việc phóng to trong khu vực cốt truyện khá khó khăn
ColinE

5

Có một số vấn đề với mã của bạn, một vấn đề dễ giải quyết và một vấn đề không ...

Đầu tiên, zoom d3 hoạt động bằng cách lưu trữ một biến đổi trên (các) phần tử DOM đã chọn - bạn có thể thấy điều này thông qua thuộc __zoomtính. Khi người dùng tương tác với phần tử DOM, biến đổi này được cập nhật và các sự kiện được phát ra. Do đó, nếu bạn phải thực hiện các hành vi thu phóng khác nhau, cả hai điều khiển pan / zoom của một yếu tố duy nhất, bạn cần giữ cho các biến đổi này được đồng bộ hóa.

Bạn có thể sao chép biến đổi như sau:

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

Tuy nhiên, điều này cũng sẽ khiến các sự kiện thu phóng cũng bị loại bỏ khỏi hành vi đích.

Một cách khác là sao chép trực tiếp vào thuộc tính biến đổi 'stash':

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

Tuy nhiên, có một vấn đề lớn hơn với những gì bạn đang cố gắng đạt được. Biến đổi thu phóng d3 được lưu trữ dưới dạng 3 thành phần của ma trận biến đổi:

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

Do đó, thu phóng chỉ có thể biểu thị tỷ lệ đối xứng cùng với bản dịch. Thu phóng không đối xứng của bạn dưới dạng áp dụng cho trục x có thể được biểu diễn một cách trung thực bằng biến đổi này và được áp dụng lại cho khu vực cốt truyện.


Cảm ơn Colin, tôi ước bất kỳ điều này có ý nghĩa với tôi nhưng tôi không hiểu giải pháp là gì. Tôi sẽ thêm 500 tiền thưởng cho câu hỏi này ngay khi tôi có thể và hy vọng ai đó có thể giúp sửa chữa codepen của tôi.
Quốc hội

Không phải lo lắng, không phải là một vấn đề dễ khắc phục, nhưng tiền thưởng có thể thu hút một số lời đề nghị giúp đỡ.
ColinE

2

Đây là một tính năng sắp tới , như đã được ghi nhận bởi @ColinE. Mã ban đầu luôn thực hiện "thu phóng tạm thời" không được đồng bộ hóa khỏi ma trận biến đổi.

Cách giải quyết tốt nhất là điều chỉnh xExtentphạm vi để biểu đồ tin rằng có thêm nến ở hai bên. Điều này có thể đạt được bằng cách thêm miếng đệm vào các bên. Các accessors, thay vì hạnh phúc,

[d => d.date]

trở thành

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

Sidenote: Lưu ý rằng có một padchức năng nên làm điều đó nhưng vì một số lý do, nó chỉ hoạt động một lần và không bao giờ cập nhật lại, đó là lý do tại sao nó được thêm vào như một accessors.

Sidenote 2: Chức năng addDaysđược thêm dưới dạng nguyên mẫu (không phải là điều tốt nhất để làm) chỉ vì đơn giản.

Bây giờ sự kiện thu phóng sửa đổi hệ số thu phóng X của chúng tôi xZoom,

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

Điều quan trọng là phải đọc vi sai trực tiếp từwheelDelta . Đây là nơi có tính năng không được hỗ trợ: Chúng tôi không thể đọc từt.x vì nó sẽ thay đổi ngay cả khi bạn kéo trục Y.

Cuối cùng, tính toán lại chart.xDomain(xExtent(data.series)); để mức độ mới có sẵn.

Xem bản demo hoạt động mà không cần nhảy tại đây: https://codepen.io/adelriosantiago/pen/QWjwRXa?editors=0011

Đã sửa lỗi: Thu phóng đảo ngược, cải thiện hành vi trên trackpad.

Về mặt kỹ thuật, bạn cũng có thể điều chỉnh yExtentbằng cách thêm d.highvà thêm d.low. Hoặc thậm chí cả hai xExtentyExtentđể tránh sử dụng ma trận biến đổi cả.


Cảm ơn bạn. Thật không may, hành vi phóng to ở đây thực sự bị rối tung. Thu phóng cho cảm giác rất giật và trong một số trường hợp thậm chí đảo ngược hướng nhiều lần (sử dụng bàn di chuột macbook). Hành vi thu phóng cần giữ nguyên như bút gốc.
Quốc hội

Tôi đã cập nhật bút với một vài cải tiến, đặc biệt là zoom đảo ngược.
adelriosantiago

1
Tôi sẽ thưởng cho bạn tiền thưởng cho nỗ lực của bạn và tôi đang bắt đầu tiền thưởng thứ hai vì tôi vẫn cần một câu trả lời với khả năng thu phóng / pan tốt hơn. Thật không may, phương pháp này dựa trên các thay đổi delta vẫn bị giật và không mượt mà khi phóng to, và khi xoay nó quá nhanh. Tôi nghi ngờ rằng bạn có thể có thể điều chỉnh các yếu tố vô tận mà vẫn không có được hành vi trơn tru. Tôi đang tìm kiếm một câu trả lời không thay đổi hành vi phóng to / xoay của bút gốc.
Quốc hội

1
Tôi cũng đã điều chỉnh bút gốc để chỉ thu phóng dọc theo trục X. Đó là hành vi xứng đáng và bây giờ tôi nhận ra nó có thể làm phức tạp câu trả lời.
Quốc hội
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.