डी 3 सिंक्रनाइज़ 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);
    });

वांछित व्यवहार धुरी, पैनिंग, और / या अक्ष को rescaling के लिए होता है ताकि किसी भी "छलांग" के बिना, जहां भी पिछली कार्रवाई समाप्त हो, वहां से हमेशा परिवर्तन लागू कर सकें।

जवाबों:


10

ठीक है, इसलिए मैंने इस पर एक और ध्यान दिया है - जैसा कि मेरे पिछले उत्तर में बताया गया है , सबसे बड़ा मुद्दा जिसे आपको दूर करने की आवश्यकता है वह यह है कि डी 3-ज़ूम केवल सममित स्केलिंग की अनुमति देता है। यह एक ऐसी चीज है जिस पर व्यापक रूप से चर्चा की गई है , और मुझे विश्वास है कि माइक बेसिकॉक इसे अगले रिलीज में संबोधित कर रहे हैं।

इसलिए, समस्या को दूर करने के लिए, आपको कई ज़ूम व्यवहार का उपयोग करने की आवश्यकता है। मैंने एक चार्ट बनाया है जिसमें तीन, प्रत्येक अक्ष के लिए एक और प्लॉट क्षेत्र के लिए एक है। एक्सएंडवाई जूम व्यवहार का उपयोग कुल्हाड़ियों को स्केल करने के लिए किया जाता है। जब भी 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

आपके कोड के साथ कुछ समस्याएं हैं, जिन्हें हल करना आसान है, और एक ऐसा नहीं है ...

सबसे पहले, डी 3-ज़ूम चयनित DOM एलिमेंट (एस) पर एक ट्रांसफॉर्मेशन करके काम करता है - आप इसे __zoomप्रॉपर्टी के माध्यम से देख सकते हैं। जब उपयोगकर्ता DOM तत्व के साथ इंटरैक्ट करता है, तो यह ट्रांसफ़ॉर्म अपडेट किया जाता है और ईवेंट उत्सर्जित होता है। इसलिए, यदि आपको अलग-अलग ज़ूम व्यवहार करने हैं, जो दोनों एकल तत्व के पैन / ज़ूम को नियंत्रित कर रहे हैं, तो आपको इन परिवर्तनों को सिंक्रनाइज़ रखने की आवश्यकता है।

आप परिवर्तन की प्रतिलिपि निम्नानुसार बना सकते हैं:

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

हालाँकि, इससे ज़ूम घटनाओं को लक्ष्य व्यवहार से भी निकाल दिया जाएगा।

एक विकल्प सीधे 'स्टैक्ड' रूपांतरित संपत्ति की प्रतिलिपि बनाना है:

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

हालाँकि, एक बड़ी समस्या यह है कि आप क्या हासिल करना चाहते हैं। D3- ज़ूम ट्रांसफ़ॉर्मेशन मैट्रिक्स के 3 घटकों के रूप में संग्रहीत किया जाता है:

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

नतीजतन, ज़ूम केवल एक अनुवाद के साथ एक सममित स्केलिंग का प्रतिनिधित्व कर सकता है। एक्स-एक्सिस पर लागू किए गए आपके विषम ज़ूम को इस ट्रांसफ़ॉर्म के अनुसार ईमानदारी से नहीं दिखाया जा सकता है और प्लॉट-एरिया पर फिर से लागू किया जा सकता है।


धन्यवाद कॉलिन, मैं इनमें से किसी भी समझदारी की कामना करता हूं, लेकिन मुझे समझ में नहीं आता है कि समाधान क्या है। मैं जितनी जल्दी हो सके इस सवाल के साथ एक 500 इनाम जोड़ूंगा और उम्मीद है कि कोई मेरे कोडपेन को ठीक करने में मदद कर सकता है।
संसद

कोई चिंता नहीं, ठीक करने के लिए एक आसान मुद्दा नहीं है, लेकिन एक इनाम मदद के कुछ प्रस्तावों को आकर्षित कर सकता है।
कॉलिन ईई

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केवल सादगी के लिए फंक्शन को एक प्रोटोटाइप (सबसे अच्छी बात नहीं) के रूप में जोड़ा गया।

अब ज़ूम ईवेंट हमारे एक्स जूम फ़ैक्टर को संशोधित करता है xZoom,

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

अंतर को सीधे से पढ़ना महत्वपूर्ण हैwheelDelta । यह वह जगह है जहां असमर्थित सुविधा है: हम इससे नहीं पढ़ सकते हैं t.xक्योंकि यह तब भी बदलेगा जब आप वाई अक्ष को खींचते हैं।

अंत में, पुनर्गणना करें chart.xDomain(xExtent(data.series));ताकि नई सीमा उपलब्ध हो।

यहां जंप के बिना कार्यशील डेमो देखें: https://codepen.io/adelriosantiago/pen/QWjwRXa?editors=0011

फिक्स्ड: ट्रैकपैड पर ज़ूम रिवर्स, बेहतर व्यवहार।

तकनीकी रूप से आप yExtentअतिरिक्त d.highऔर जोड़कर भी ट्विक कर सकते हैं d.low। या यहां तक ​​कि दोनों xExtentऔर yExtentट्रांसफॉर्म मैट्रिक्स का उपयोग करने से बचने के लिए।


धन्यवाद। दुर्भाग्य से यहाँ ज़ूम व्यवहार वास्तव में गड़बड़ है। ज़ूम बहुत झटकेदार लगता है और कुछ मामलों में यहां तक ​​कि कई बार दिशा को उलट देता है (मैकबुक ट्रैकपैड का उपयोग करके)। ज़ूम व्यवहार को मूल पेन के समान रहने की आवश्यकता है।
संसद

मैंने कुछ सुधारों के साथ कलम को अपडेट किया है, विशेष रूप से रिवर्स ज़ूम।
adelriosantiago 5

1
मैं आपको आपके प्रयास के लिए इनाम देने जा रहा हूं और मैं दूसरा इनाम शुरू कर रहा हूं क्योंकि मुझे अभी भी बेहतर जूमिंग / पैन के साथ उत्तर की आवश्यकता है। दुर्भाग्य से डेल्टा परिवर्तनों के आधार पर यह विधि अभी भी झटकेदार है और ज़ूम करते समय चिकनी नहीं है, और जब पैन करते हैं तो यह बहुत तेज़ हो जाता है। मुझे संदेह है कि आप शायद कारक को अंतहीन रूप से समायोजित कर सकते हैं और अभी भी सहज व्यवहार नहीं पा सकते हैं। मैं एक ऐसे उत्तर की तलाश में हूं जो मूल कलम के ज़ूमिंग / पैनिंग व्यवहार को नहीं बदलता है।
संसद

1
मैंने केवल एक्स-एक्सिस के साथ ज़ूम करने के लिए मूल पेन को भी समायोजित किया है। यह योग्य व्यवहार है और मुझे अब यह एहसास है कि इसका उत्तर जटिल हो सकता है।
संसद
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.