Bir d3.js görselleştirme düzenini duyarlı hale getirmenin en iyi yolu nedir?


224

Bir 960 500 svg grafiği oluşturan bir histogram komut dosyanız olduğunu varsayalım. Grafik genişliklerini ve yüksekliklerini dinamik olarak yeniden boyutlandırmak için bunu nasıl duyarlı hale getirebilirim?

<script> 

var n = 10000, // number of trials
    m = 10,    // number of random variables
    data = [];

// Generate an Irwin-Hall distribution.
for (var i = 0; i < n; i++) {
  for (var s = 0, j = 0; j < m; j++) {
    s += Math.random();
  }
  data.push(s);
}

var histogram = d3.layout.histogram()
    (data);

var width = 960,
    height = 500;

var x = d3.scale.ordinal()
    .domain(histogram.map(function(d) { return d.x; }))
    .rangeRoundBands([0, width]);

var y = d3.scale.linear()
    .domain([0, d3.max(histogram.map(function(d) { return d.y; }))])
    .range([0, height]);

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

svg.selectAll("rect")
    .data(histogram)
  .enter().append("rect")
    .attr("width", x.rangeBand())
    .attr("x", function(d) { return x(d.x); })
    .attr("y", function(d) { return height - y(d.y); })
    .attr("height", function(d) { return y(d.y); });

svg.append("line")
    .attr("x1", 0)
    .attr("x2", width)
    .attr("y1", height)
    .attr("y2", height);

</script> 

Tam örnek histogram özeti: https://gist.github.com/993912


5
Bu cevabın yöntemini daha kolay buldum: stackoverflow.com/a/11948988/511203
Mike Gorski

bulduğum en basit yol svg genişliği ve yüksekliği yapmaktır:% 100 ve DOM konteynerine (div gibi) boyutlandırma uygulamak stackoverflow.com/questions/47076163/…
mtx

Yanıtlar:


316

Grafiğin yeniden çizilmesini gerektirmeyen bunu yapmanın başka bir yolu daha vardır ve öğedeki viewBox ve preserveAspectRatio özniteliklerini değiştirmeyi içerir <svg>:

<svg id="chart" width="960" height="500"
  viewBox="0 0 960 500"
  preserveAspectRatio="xMidYMid meet">
</svg>

Güncelleme 11/24/15 : Çoğu modern tarayıcı, SVG öğelerinin en boy oranını ' dan çıkarabilirviewBox , bu nedenle grafiğin boyutunu güncel tutmanıza gerek olmayabilir. Eski tarayıcıları desteklemeniz gerekiyorsa, pencere şu şekilde yeniden boyutlandırıldığında öğenizi yeniden boyutlandırabilirsiniz:

var aspect = width / height,
    chart = d3.select('#chart');
d3.select(window)
  .on("resize", function() {
    var targetWidth = chart.node().getBoundingClientRect().width;
    chart.attr("width", targetWidth);
    chart.attr("height", targetWidth / aspect);
  });

Svg içeriği otomatik olarak ölçeklendirilir. Bunun (bazı değişikliklerle) çalışan bir örneğini burada görebilirsiniz: nasıl tepki vereceğini görmek için pencereyi veya sağ alt bölmeyi yeniden boyutlandırın.


1
Bu yaklaşımın çok hoşuma gittiğini düşünüyorum, 2 soru. 1 Görüntüleme kutusu çapraz tarayıcıda çalışıyor mu? 2. Örneğinizde daireler yeniden boyutlandırılır ancak pencereye sığmaz. Svg görünüm kutusu veya en boy oranı ile bir şeyler ters gidiyor.
Matt Alcock

4
Bu yaklaşımı sevin, yukarıdakilerin doğru çalıştığından emin değilim. Matt'in dediği gibi, daireler yeniden boyutlandırılır ancak grafiğin solundan ilk daireye olan mesafe, dairelerle aynı oranda yeniden boyutlandırılmaz. Jsfiddle'da oldukça az uğraşmayı denedim, ancak Google Chrome'u kullanarak beklendiği gibi çalışamadım. ViewBox ve preserveAspectRatio hangi tarayıcılarla uyumludur?
Chris Withers

9
Aslında, yalnızca viewBox ve preserveAspectRatio öğesinin genişliği% 100 üst yüksekliğe ayarlanmış bir SVG'ye ayarlanıyor ve üst
öğe

2
Deepu'nun doğru olduğunu onaylamak. Bootstrap / flex grid ile çalışır. Teşekkürler @Deepu.
Mike O'Connor

3
Not: Birkaç d3 dersinde 1.) html'de svg genişliği ve yüksekliği ayarlanır ve 2.) görselleştirme bir onload işlev çağrısı yoluyla yapılır . Svg genişliği ve yüksekliği gerçek html'de ayarlanırsa ve yukarıdaki tekniği kullanırsanız, görüntü ölçeklenebilir olmaz.
SumNeuron

34

'Duyarlı SVG'yi arayın Bir SVG'yi duyarlı hale getirmek oldukça basittir ve artık boyutlar hakkında endişelenmenize gerek yok.

İşte nasıl yaptım:

d3.select("div#chartId")
   .append("div")
   .classed("svg-container", true) //container class to make it responsive
   .append("svg")
   //responsive SVG needs these 2 attributes and no width and height attr
   .attr("preserveAspectRatio", "xMinYMin meet")
   .attr("viewBox", "0 0 600 400")
   //class to make it responsive
   .classed("svg-content-responsive", true); 

CSS kodu:

.svg-container {
    display: inline-block;
    position: relative;
    width: 100%;
    padding-bottom: 100%; /* aspect ratio */
    vertical-align: top;
    overflow: hidden;
}
.svg-content-responsive {
    display: inline-block;
    position: absolute;
    top: 10px;
    left: 0;
}

Daha fazla bilgi / eğitim:

http://demosthenes.info/blog/744/Make-SVG-Responsive

http://soqr.fr/testsvg/embed-svg-liquid-layout-responsive-web-design.php


1
Benim durumumda, dolgu alt:% 100 kabımın altta ekstra dolgu olmasını sağlayacak, bu yüzden kaldırmak için% 50 olarak değiştirdim. Kodunuzu kullandı ve svg'yi başarıyla yanıtlayın, teşekkürler.
V-SHY

20

Bunu çözmek için küçük bir özü kodladım.

Genel çözüm modeli şudur:

  1. Komut dosyasını hesaplama ve çizim işlevlerine ayırın.
  2. Çizim işlevinin dinamik olarak çizildiğinden ve görselleştirme genişliği ve yükseklik değişkenleriyle yönlendirildiğinden emin olun (Bunu yapmanın en iyi yolu d3.scale api'yi kullanmaktır)
  3. Çizimi, işaretlemedeki bir referans öğeye bağlayın / zincirleyin. (Bunun için jquery kullandım, bu yüzden içe aktardı).
  4. Zaten çekilmişse çıkarmayı unutmayın. Jquery kullanarak başvurulan öğeden boyutları alın.
  5. Çizim işlevini pencere yeniden boyutlandırma işlevine bağlayın / zincirleyin. Yalnızca bir zaman aşımından sonra yeniden çizdiğimizden emin olmak için bu zincire bir geri dönme (zaman aşımı) ekleyin.

Ayrıca hız için küçültülmüş d3.js komut dosyasını da ekledim. Gist burada: https://gist.github.com/2414111

jquery referans geri kodu:

$(reference).empty()
var width = $(reference).width();

Hata kodu:

var debounce = function(fn, timeout) 
{
  var timeoutID = -1;
  return function() {
     if (timeoutID > -1) {
        window.clearTimeout(timeoutID);
     }
   timeoutID = window.setTimeout(fn, timeout);
  }
};

var debounced_draw = debounce(function() {
    draw_histogram(div_name, pos_data, neg_data);
  }, 125);

 $(window).resize(debounced_draw);

Zevk almak!


Bu işe yaramıyor; düzeltilmiş gist dahil html dosyasının penceresini yeniden boyutlandırırsanız, pencere küçüldükçe histogram hala kesilir ...
Chris Withers

1
SVG bir vektör biçimidir. Herhangi bir sanal görünüm kutusu boyutunu ayarlayabilir ve ardından gerçek boyutlara ölçeklendirebilirsiniz. JS kullanmaya gerek yok.
phil pirozhkov

4

ViewBox Kullanmadan

İşte a kullanmaya dayanmayan bir çözüm örneği viewBox:

Anahtar güncellenmesinde olan aralık içinde terazi verileri yerleştirmek için kullanılır.

İlk olarak, orijinal en boy oranınızı hesaplayın:

var ratio = width / height;

Sonra, her boyutlandırmak üzerinde, güncellemek rangeve xve y:

function resize() {
  x.rangeRoundBands([0, window.innerWidth]);
  y.range([0, window.innerWidth / ratio]);
  svg.attr("height", window.innerHeight);
}

Yüksekliğin genişlik ve en boy oranına bağlı olduğunu ve böylece orijinal oranlarınızın korunacağını unutmayın.

Son olarak, grafiği "yeniden çiz" - xveya yölçeklerine bağlı olan herhangi bir özelliği güncelleyin :

function redraw() {
    rects.attr("width", x.rangeBand())
      .attr("x", function(d) { return x(d.x); })
      .attr("y", function(d) { return y.range()[1] - y(d.y); })
      .attr("height", function(d) { return y(d.y); });
}

Not içinde yeniden boyutlandırma olduğunu rectssize üst bağlı kullanabilirsiniz rangearasında yaçıkça yüksekliğini kullanmak yerine,:

.attr("y", function(d) { return y.range()[1] - y(d.y); })


3

Eğer aracılığıyla d3.js kullanıyorsanız c3.js yanıt sorununa çözüm oldukça basittir:

var chart = c3.generate({bindTo:"#chart",...});
chart.resize($("#chart").width(),$("#chart").height());

oluşturulan HTML şöyle görünür:

<div id="chart">
    <svg>...</svg>
</div>

2

Shawn Allen'ın cevabı harikaydı. Ancak bunu her seferinde yapmak istemeyebilirsiniz. Vida.io'da barındırıyorsanız , svg görselleştirmeniz için otomatik yanıt alırsınız.

Bu basit gömme koduyla duyarlı iframe alabilirsiniz:

<div id="vida-embed">
<iframe src="http://embed.vida.io/documents/9Pst6wmB83BgRZXgx" width="auto" height="525" seamless frameBorder="0" scrolling="no"></iframe>
</div>

#vida-embed iframe {
  position: absolute;
  top:0;
  left: 0;
  width: 100%;
  height: 100%;
}

http://jsfiddle.net/dnprock/npxp3v9d/1/

Açıklama: Bu özelliği vida.io'da yapıyorum .


2

Plottable.js gibi bir d3 sarmalayıcısı kullanıyorsanız , en kolay çözümün bir olay dinleyicisi ekleyip ardından yeniden çizim işlevini ( plottable.js'de) çağırıyor olabileceğini unutmayın. Plottable.js durumunda, bu mükemmel bir şekilde çalışacaktır (bu yaklaşım zayıf bir şekilde belgelenmiştir):redraw

    window.addEventListener("resize", function() {
      table.redraw();
    });

Yeniden çizmeyi olabildiğince hızlı ateşlemekten kaçınmak için muhtemelen bu işlevi geri almak en iyisidir
Ian

1

İnsanlar hala bu soruyu ziyaret ediyorsa - işte benim için işe yarayan:

  • İframe'i bir div içine alın ve söz konusu div'e% 40'lık bir dolgu eklemek için css kullanın (istediğiniz en boy oranına bağlı olarak yüzde). Ardından iframe'in genişliğini ve yüksekliğini% 100 olarak ayarlayın.

  • İframe'e yüklenecek grafiği içeren html belgesinde, genişliği svg'nin eklendiği div genişliğine (veya gövdenin genişliğine) ayarlayın ve yükseklik / genişlik * en boy oranını ayarlayın.

  • Kişiler telefonlarını döndürdüğünde grafiğin boyutunu uyarlamak için iframe içeriğini pencere yeniden boyutlandırıldığında yeniden yükleyen bir işlev yazın.

Web sitemde örnek: http://dirkmjk.nl/en/2016/05/embedding-d3js-charts-responsive-website

GÜNCELLEŞTİRME 30 Ara 2016

Yukarıda tarif ettiğim yaklaşımın bazı dezavantajları vardır, özellikle de D3 tarafından oluşturulan svg'nin parçası olmayan herhangi bir başlık ve altyazıyı dikkate almaz. O zamandan beri daha iyi bir yaklaşım olduğunu düşündüğüm şeyle karşılaştım:

  • D3 grafiğinin genişliğini bağlı olduğu divın genişliğine ayarlayın ve yüksekliğini buna göre ayarlamak için en boy oranını kullanın;
  • Gömülü sayfanın yüksekliğini ve URL'sini HTML5'in postMessage kullanarak üst sayfaya göndermesini sağlayın;
  • Üst sayfada, ilgili iframe'i (sayfanızda birden fazla iframe varsa faydalıdır) tanımlamak ve yüksekliğini katıştırılmış sayfanın yüksekliğine güncellemek için url'yi kullanın.

Web sitemde örnek: http://dirkmjk.nl/en/2016/12/embedding-d3js-charts-responsive-website-better-solution


0

D3 veri birleştirmesinin temel ilkelerinden biri, iktidarsız olmasıdır. Başka bir deyişle, aynı verilerle bir veri birleştirmeyi tekrar tekrar değerlendirirseniz, oluşturulan çıktı aynı olur. Bu nedenle, grafiğinizi doğru bir şekilde oluşturduğunuz sürece, giriş, güncelleme ve çıkış seçimlerinize dikkat edin - boyut değiştiğinde yapmanız gereken tek şey grafiği tamamen yeniden oluşturmaktır.

Yapmanız gereken birkaç şey daha var, biri gaz vermek için pencere yeniden boyutlandırma işleyicisini sektiriyor. Ayrıca, sabit kodlama genişlikleri / yükseklikleri yerine, bunu içeren eleman ölçülerek gerçekleştirilmelidir.

Alternatif olarak, grafiğiniz , veri birleşimlerini doğru şekilde işleyen bir D3 bileşenleri kümesi olan d3fc kullanılarak işlenen grafiğinizdir . Ayrıca, 'duyarlı' grafikler oluşturmayı kolaylaştıran öğe içerdiğini ölçen kartezyen bir grafik vardır:

// create some test data
var data = d3.range(50).map(function(d) {
  return {
    x: d / 4,
    y: Math.sin(d / 4),
    z: Math.cos(d / 4) * 0.7
  };
});

var yExtent = fc.extentLinear()
  .accessors([
    function(d) { return d.y; },
    function(d) { return d.z; }
  ])
  .pad([0.4, 0.4])
  .padUnit('domain');

var xExtent = fc.extentLinear()
  .accessors([function(d) { return d.x; }]);

// create a chart
var chart = fc.chartSvgCartesian(
    d3.scaleLinear(),
    d3.scaleLinear())
  .yDomain(yExtent(data))
  .yLabel('Sine / Cosine')
  .yOrient('left')
  .xDomain(xExtent(data))
  .xLabel('Value')
  .chartLabel('Sine/Cosine Line/Area Chart');

// create a pair of series and some gridlines
var sinLine = fc.seriesSvgLine()
  .crossValue(function(d) { return d.x; })
  .mainValue(function(d) { return d.y; })
  .decorate(function(selection) {
    selection.enter()
      .style('stroke', 'purple');
  });

var cosLine = fc.seriesSvgArea()
  .crossValue(function(d) { return d.x; })
  .mainValue(function(d) { return d.z; })
  .decorate(function(selection) {
    selection.enter()
      .style('fill', 'lightgreen')
      .style('fill-opacity', 0.5);
  });

var gridlines = fc.annotationSvgGridline();

// combine using a multi-series
var multi = fc.seriesSvgMulti()
  .series([gridlines, sinLine, cosLine]);

chart.plotArea(multi);

// render
d3.select('#simple-chart')
  .datum(data)
  .call(chart);

Bu kod alanında eylem halinde görebilirsiniz:

https://codepen.io/ColinEberhardt/pen/dOBvOy

burada pencereyi yeniden boyutlandırabilir ve grafiğin doğru şekilde yeniden oluşturulduğunu doğrulayabilirsiniz.

Tam bir açıklama olarak, d3fc'nin koruyucularından biri olduğumu lütfen unutmayın.


Yanıtlarınızı güncel tuttuğunuz için teşekkür ederiz! Bazı yayınlarınızdaki son güncellemeleri gördüm ve insanların kendi içeriklerini tekrar ziyaret etmelerini gerçekten takdir ediyorum! Bununla birlikte, bir uyarı kelimesi olarak, bu revizyon artık soruya tam olarak uymuyor, çünkü D3 v4'te düzenleme yapıyorsunuz , OP ise gelecekteki okuyucuları karıştırabilecek v3'e açıkça atıfta bulunuyor . Şahsen, kendi cevaplarım için, orijinal v3 bölümünü korurken bir v4 bölümü ekleme eğilimindeyim . Bence v4 her iki mesaja da karşılıklı bir referans ekleyerek kendi cevabına değer olabilir. Ama bu sadece iki
sentim

Bu gerçekten iyi bir nokta! Taşınmak ve sadece geleceğe ve yeniliklere odaklanmak çok kolay. En yeni d3 sorularına bakılırsa birçok insan hala v3 kullanıyor. Aynı zamanda bazı farklılıkların ne kadar ince olduğu da sinir bozucu! Düzenlemelerimi tekrar ziyaret edeceğim :-)
ColinE

0

Verimsiz olduklarından ve uygulamanızda sorunlara neden olabileceğinden veba gibi yeniden boyutlandırma / kene çözümlerinden kaçınırım (örn. ve araç ipucunuz tekrar yanlış).

IE11 gibi düzgün bir şekilde desteklemeyen bazı eski tarayıcılarda bu davranışı, <canvas> yönünü koruyan öğe .

16: 9'un bir yönü olan 960x540 verildi:

<div style="position: relative">
  <canvas width="16" height="9" style="width: 100%"></canvas>
  <svg viewBox="0 0 960 540" preserveAspectRatio="xMidYMid meet" style="position: absolute; top: 0; right: 0; bottom: 0; left: 0; -webkit-tap-highlight-color: transparent;">
  </svg>
</div>

0

Burada bir sürü karmaşık cevap var.

Temel olarak yapmanız gereken tek şey widthve heightözellikleri viewBoxatfetmektir:

width = 500;
height = 500;

const svg = d3
  .select("#chart")
  .append("svg")
  .attr("viewBox", `0 0 ${width} ${height}`)

Kenar boşluklarınız varsa, bunları yalnızca genişliğe / yüksekliğe ekleyebilirsiniz, gardından onu ekleyin ve normalde yaptığınız gibi dönüştürebilirsiniz.


-1

Bir görselleştirmenin boyutunu uyarlamak için bootstrap 3'ü de kullanabilirsiniz . Örneğin, HTML kodunu şu şekilde ayarlayabiliriz :

<div class="container>
<div class="row">

<div class='col-sm-6 col-md-4' id="month-view" style="height:345px;">
<div id ="responsivetext">Something to write</div>
</div>

</div>
</div>

Gereksinimlerim nedeniyle sabit bir yükseklik ayarladım, ancak boyutu otomatik olarak da bırakabilirsiniz. "Col-sm-6 col-md-4", div'i farklı cihazlar için duyarlı hale getirir. Daha fazla bilgiyi http://getbootstrap.com/css/#grid-example-basic adresinde bulabilirsiniz.

Grafiğe id aylık görünüm yardımı ile erişebiliriz .

D3 kodu hakkında fazla ayrıntıya girmeyeceğim, sadece farklı ekran boyutlarına uyum sağlamak için gerekli olan kısmı gireceğim.

var width = document.getElementById('month-view').offsetWidth;

var height = document.getElementById('month-view').offsetHeight - document.getElementById('responsivetext2').offsetHeight;

Genişlik, id ay görünümü ile div'in genişliği alınarak ayarlanır.

Benim durumumdaki yükseklik tüm alanı içermemelidir. Ayrıca çubuğun üzerinde bazı metin var, bu yüzden o alanı da hesaplamak gerekir. Bu yüzden metnin alanını id Responsivetext ile tanımladım. Çubuğun izin verilen yüksekliğini hesaplamak için metnin yüksekliğini div yüksekliğinden çıkardım.

Bu, tüm farklı ekran / div boyutlarını benimseyecek bir çubuğa sahip olmanızı sağlar. Bunu yapmanın en iyi yolu olmayabilir, ama kesinlikle projemin ihtiyaçları için çalışıyor.


1
Merhaba, bunu denedim, ancak svg içeriği, takvim veya grafikler gibi yeniden boyutlandırılmıyor.
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.