d3 2 ayrı zum davranışını senkronize etme


11

Aşağıdaki d3 / d3fc grafiğim var

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

Grafiğin ana alan için bir yakınlaştırma davranışı ve y ekseni için ayrı bir yakınlaştırma davranışı vardır. Y ekseni yeniden ölçeklendirmek için sürüklenebilir.

Sorun çözme sorunum yeniden ölçeklendirmek için y eksenini sürükledikten sonra grafik sonra panning, grafikte bir "atlama" olmasıdır.

Açıkçası 2 yakınlaştırma davranışı bir bağlantı kesilmesi ve senkronize edilmesi gerekiyor, ancak bunu düzeltmeye çalışırken beynimi rafa atıyorum.

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

İstenen davranış, yakınlaştırma, yatay kaydırma ve / veya yeniden ölçekleme işlemidir. Dönüşümü her zaman bir önceki eylemin bittiği yerden herhangi bir "atlama" olmadan uygulamaktır.

Yanıtlar:


10

Tamam, bu yüzden başka bir şey yaptım - önceki cevabımda belirtildiği gibi , üstesinden gelmeniz gereken en büyük sorun d3-zoom'un sadece simetrik ölçeklendirmeye izin vermesidir. Bu çok tartışılan bir şey ve sanırım Mike Bostock bir sonraki sürümde bunu ele alıyor.

Bu nedenle, sorunun üstesinden gelmek için birden fazla yakınlaştırma davranışı kullanmanız gerekir. Her eksen için üç, arsa alanı için bir tane olan bir grafik oluşturdum. X ve Y zum davranışları eksenleri ölçeklemek için kullanılır. Bir yakınlaştırma olayı X & Y yakınlaştırma davranışları tarafından her yükseltildiğinde, çeviri değerleri çizim alanına kopyalanır. Benzer şekilde, çizim alanında bir çeviri meydana geldiğinde, x & y bileşenleri ilgili eksen davranışlarına kopyalanır.

En-boy oranını korumamız gerektiğinden, arsa alanında ölçeklendirme biraz daha karmaşıktır. Bunu başarmak için önceki yakınlaştırma dönüşümünü saklıyorum ve X ve Y yakınlaştırma davranışlarına uygulamak üzere uygun bir ölçek oluşturmak için ölçek deltasını kullanıyorum.

Kolaylık sağlamak için, tüm bunları bir grafik bileşenine tamamladım:


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

İşte çalışan örneği olan bir kalem:

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


Colin, bunu bir kez daha verdiğin için çok teşekkürler. Kodunu açtım ve beklendiği gibi çalışmadığını fark ettim. Kod satırımda Y eksenini sürüklemek grafiği yeniden ölçeklendirir (bu istenen davranıştır). Kod açıklığınızda, ekseni sürüklemek yalnızca grafiği tarar.
parlamento

1
Hem y ölçeğini hem de arsa alanı codepen.io/colineberhardt-the-bashful/pen/mdeJyrK'yı sürüklemenizi sağlayan bir tane oluşturmayı başardım - ancak arsa alanını yakınlaştırmak oldukça zor
ColinE

5

Kodunuzla ilgili bir kaç sorun var, biri çözülmesi kolay, diğeri ise ...

İlk olarak, d3-zoom seçilen DOM öğelerine bir dönüşüm kaydederek çalışır - bunu __zoomözellik üzerinden görebilirsiniz . Kullanıcı DOM öğesiyle etkileşime girdiğinde, bu dönüşüm güncellenir ve olaylar gönderilir. Bu nedenle, her ikisi de tek bir öğenin kaydırma / yakınlaştırmasını denetleyen farklı yakınlaştırma davranışlarınız varsa, bu dönüşümleri eşitlemeniz gerekir.

Dönüştürmeyi aşağıdaki gibi kopyalayabilirsiniz:

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

Ancak bu, yakınlaştırma olaylarının da hedef davranıştan tetiklenmesine neden olur.

Bir alternatif doğrudan 'stashed' dönüşüm özelliğine kopyalamaktır:

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

Ancak, elde etmeye çalıştığınız şeyle ilgili daha büyük bir sorun var. D3-zoom dönüşümü, bir dönüşüm matrisinin 3 bileşeni olarak saklanır:

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

Sonuç olarak, zoom yalnızca bir çeviriyle birlikte simetrik bir ölçeklendirme gösterebilir. X eksenine uygulanan asimetrik zumunuz bu dönüşümle sadakatle temsil edilemez ve çizim alanına yeniden uygulanamaz.


Teşekkürler Colin, bunlardan herhangi birinin bana mantıklı olmasını dilerdim ama çözümün ne olduğunu anlamıyorum. En kısa zamanda ben bu soruya 500 lütuf ekleyeceğim ve umarım birisi kod kodumu düzeltmek yardımcı olabilir.
parlamento

Endişeye gerek yok, düzeltilmesi kolay bir sorun değil, ancak bir ödül bazı yardım teklifleri çekebilir.
ColinE

2

Bu, @ColinE tarafından zaten belirtildiği gibi yaklaşan bir özelliktir . Orijinal kod her zaman dönüştürme matrisinden senkronize edilmemiş bir "geçici zum" yapar.

En iyi geçici çözüm, xExtentgrafiğin yanlarda ek mumlar olduğuna inanması için aralığı değiştirmektir . Bu, kenarlara pedler eklenerek elde edilebilir. accessorsYerine varlığının,

[d => d.date]

, olur

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

Sidenote: padBunu yapması gereken bir fonksiyon olduğunu unutmayın, ancak bir nedenle sadece bir kez çalışır ve bir daha asla güncellenmez, bu yüzden bir olarak eklenir accessors.

Sidenote 2: addDaysSadece basitlik için prototip olarak (yapılacak en iyi şey değil) fonksiyon eklendi.

Şimdi zoom olayı X zoom faktörünüzü değiştiriyor xZoom,

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

Diferansiyelin doğrudan okunması önemlidirwheelDelta . Bu, desteklenmeyen özelliğin olduğu yerdir: t.xY eksenini sürükleseniz bile değişeceği için okuyamıyoruz .

Son olarak, chart.xDomain(xExtent(data.series));yeni kapsamın kullanılabilir olması için yeniden hesaplayın .

Atlamadan çalışma demosuna bakın: https://codepen.io/adelriosantiago/pen/QWjwRXa?editors=0011

Düzeltildi: Yakınlaştırma tersine çevirme, izleme dörtgeninde daha iyi davranış.

Teknik olarak yExtentekstra d.highve d.low's ekleyerek de değiştirebilirsiniz . Ya da her ikisi de xExtentve yExtentdönüşüm matrisini kullanmaktan kaçınmak için.


Teşekkür ederim. Ne yazık ki buradaki zum davranışı gerçekten berbat durumda. Zum çok sarsıntılı ve bazı durumlarda yönü birkaç kez tersine çeviriyor (macbook izleme dörtgenini kullanarak). Yakınlaştırma davranışının orijinal kalemle aynı kalması gerekir.
parlamento

Kalemi birkaç iyileştirme ile güncelledim, özellikle geri zum.
adelriosantiago

1
Çabalarınız için size ödül vereceğim ve ikinci bir ödül kazanacağım çünkü hala daha iyi yakınlaştırma / kaydırma ile bir cevaba ihtiyacım var. Ne yazık ki delta değişikliklerine dayanan bu yöntem hala sarsıntılı ve yakınlaştırma ve kaydırma sırasında çok hızlı değil. Faktörü sonsuza kadar ayarlayabileceğinizden ve yine de düzgün davranış elde edemediğinizden şüpheleniyorum. Orijinal kalemin yakınlaştırma / kaydırma davranışını değiştirmeyen bir cevap arıyorum.
parlamento

1
Orijinal kalemi sadece X ekseni boyunca yakınlaştırmak için ayarladım. Bu hak edilen davranış ve şimdi bunun cevabı zorlaştırabileceğinin farkındayım.
parlamento
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.