d3 การซิงโครไนซ์ 2 พฤติกรรมการซูมแยกต่างหาก


11

ฉันมีแผนภูมิ d3 / d3fc ต่อไปนี้

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

แผนภูมิมีพฤติกรรมการซูมสำหรับพื้นที่หลักและพฤติกรรมการซูมแยกต่างหากสำหรับแกน y คุณสามารถลากแกน y ไปยัง rescale ได้

ปัญหาที่ฉันมีปัญหาในการแก้ปัญหาคือหลังจากลากแกน y ไปยัง rescale แล้วจึงปรากฎว่ามีแผนภูมิ "กระโดด" ในแผนภูมิ

เห็นได้ชัดว่าพฤติกรรมการซูม 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


โคลินขอบคุณมากที่สละสิ่งนี้อีกครั้ง ฉันเปิด codepen ของคุณและสังเกตว่ามันไม่ทำงานอย่างที่คาดไว้ ใน codepen ของฉันให้ลากแกน Y อีกครั้งเพื่อปรับขนาดแผนภูมิ (นี่เป็นพฤติกรรมที่ต้องการ) ใน codepen ของคุณการลากแกนเพียงแค่วางแผนภูมิ
รัฐสภา

1
ฉันจัดการเพื่อสร้างหนึ่งที่ช่วยให้คุณลากทั้ง y-scale และพื้นที่พล็อตcodepen.io/colineberhardt-the-bashful/pen/mdeJyrK - แต่การซูมในพื้นที่แปลงค่อนข้างท้าทาย
ColinE

5

มีปัญหาสองสามข้อเกี่ยวกับรหัสของคุณปัญหาที่แก้ไขได้ง่ายและปัญหาที่ไม่ได้เป็น ...

อย่างแรกคือการซูม d3 ทำงานโดยการจัดเก็บการแปลงองค์ประกอบ DOM ที่เลือก - คุณสามารถดูได้ผ่าน__zoomคุณสมบัติ เมื่อผู้ใช้โต้ตอบกับองค์ประกอบ DOM การแปลงนี้จะถูกอัปเดตและเหตุการณ์ที่ปล่อยออกมา ดังนั้นหากคุณมีพฤติกรรมการซูมที่แตกต่างกันซึ่งทั้งสองอย่างควบคุมแพน / ซูมขององค์ประกอบเดียวคุณจะต้องทำให้การแปลงสภาพเหล่านี้ตรงกัน

คุณสามารถคัดลอกการแปลงดังนี้:

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

อย่างไรก็ตามสิ่งนี้จะทำให้เหตุการณ์การซูมถูกไล่ออกจากพฤติกรรมเป้าหมายด้วย

อีกทางเลือกหนึ่งคือการคัดลอกโดยตรงไปยังคุณสมบัติการแปลง 'stashed':

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

อย่างไรก็ตามมีปัญหาใหญ่กว่ากับสิ่งที่คุณพยายามที่จะบรรลุ การแปลงการซูม d3 ถูกเก็บไว้เป็น 3 องค์ประกอบของเมทริกซ์การแปลง:

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

เป็นผลให้การซูมสามารถแสดงมาตราส่วนสมมาตรพร้อมกับการแปลเท่านั้น การซูมแบบอสมมาตรของคุณในฐานะที่ใช้กับแกน x นั้นไม่สามารถนำไปใช้แทนการแปลงนี้และนำไปใช้กับพื้นที่การแปลงได้


ขอบคุณโคลินฉันต้องการสิ่งนี้ทำให้รู้สึกถึงฉัน แต่ฉันไม่เข้าใจว่าการแก้ปัญหาคืออะไร ฉันจะเพิ่ม 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
]

Sidenote: หมายเหตุว่ามีpadฟังก์ชั่นที่ควรจะทำอย่างนั้นaccessorsแต่ด้วยเหตุผลบางอย่างมันทำงานได้เพียงครั้งเดียวและไม่เคยปรับปรุงอีกครั้งว่าทำไมมันจะเพิ่มเป็น

Sidenote 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's หรือแม้แต่ทั้งสองxExtentและyExtentเพื่อหลีกเลี่ยงการใช้เมทริกซ์แปลงเลย


ขอบคุณ. น่าเสียดายที่พฤติกรรมการซูมที่นี่เกิดความสับสน การซูมรู้สึกกระตุกมากและในบางกรณีถึงกับกลับทิศทางหลายครั้ง (โดยใช้แทร็คแพด macbook) พฤติกรรมการซูมจะต้องคงอยู่เหมือนปากกาเดิม
รัฐสภา

ฉันได้อัปเดตปากกาด้วยการปรับปรุงบางอย่างโดยเฉพาะการซูมกลับด้าน
adelriosantiago

1
ฉันจะให้รางวัลคุณสำหรับความพยายามของคุณและฉันจะเริ่มรับรางวัลที่สองเพราะฉันยังต้องการคำตอบด้วยการซูม / แพนที่ดีขึ้น น่าเสียดายที่วิธีนี้ตามการเปลี่ยนแปลงเดลต้ายังคงกระตุกและไม่ราบรื่นเมื่อซูมและเมื่อแพนมันแพนเร็วเกินไป ฉันสงสัยว่าคุณอาจปรับปัจจัยได้ไม่ จำกัด และยังไม่ได้รับพฤติกรรมที่ราบรื่น ฉันกำลังมองหาคำตอบที่ไม่เปลี่ยนพฤติกรรมการซูม / การแพนกล้องของปากกาดั้งเดิม
รัฐสภา

1
ฉันยังได้ปรับปากกาต้นฉบับเพื่อซูมตามแกน X เท่านั้น นั่นคือพฤติกรรมที่สมควรได้รับและตอนนี้ฉันรู้ว่ามันอาจทำให้คำตอบซับซ้อนขึ้น
รัฐสภา
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.