it-swarm.dev

Jaki jest najlepszy sposób na uaktywnienie układu wizualizacji d3.js?

Załóżmy, że mam skrypt histogramu, który tworzy grafikę 960 500 svg. Jak sprawić, by ta funkcja reagowała tak, aby przy zmianie rozmiaru szerokość i wysokość grafiki były dynamiczne?

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

Pełny przykładowy histogram Gist to: https://Gist.github.com/993912

207
Matt Alcock

Jest inny sposób na zrobienie tego, który nie wymaga przerysowywania wykresu i polega na modyfikacji viewBox i preserveAspectRatio attributes w elemencie <svg>

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

Aktualizacja 11/24/15: większość nowoczesnych przeglądarek może wnioskować o współczynniku proporcji elementów SVG z zmiennej viewBox, więc może nie być konieczne aktualizowanie rozmiaru wykresu. Jeśli potrzebujesz obsługi starszych przeglądarek, możesz zmienić rozmiar elementu, gdy okno zmieni rozmiar tak:

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

A zawartość svg zostanie automatycznie skalowana. Możesz zobaczyć działający przykład tego (z pewnymi modyfikacjami) tutaj : po prostu zmień rozmiar okna lub prawego dolnego panelu, aby zobaczyć, jak reaguje.

296
Shawn Allen

Poszukaj „responsywnego SVG”, jest bardzo prosty, aby sprawić, by SVG był responsywny i nie musisz się już martwić rozmiarami. 

Oto jak to zrobiłem: 

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

Kod CSS:

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

Więcej informacji/samouczków: 

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

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

29
cminatti

Zakodowałem mały Gist, aby rozwiązać ten problem.

Ogólny wzór rozwiązania jest następujący:

  1. Podziel skrypt na funkcje obliczeniowe i rysunkowe. 
  2. Upewnij się, że funkcja rysowania rysuje dynamicznie i jest napędzana przez zmienne szerokości i wysokości wizualizacji .__ (Najlepszym sposobem na zrobienie tego jest, aby użyć api d3.scale) 
  3. Powiąż/połącz rysunek z elementem Znacznika. (Użyłem do tego jquery, więc zaimportowałem go).
  4. Pamiętaj, aby go usunąć, jeśli jest już narysowany. Uzyskaj wymiary z Elementu przywoływanego za pomocą jquery. 
  5. Powiąż/połącz funkcję rysowania z Funkcja zmiany rozmiaru okna. Wprowadź debounce (timeout) do tego łańcucha , Aby upewnić się, że przerysujemy tylko po przekroczeniu limitu czasu. 

Dodałem także skrócony skrypt d3.js dla szybkości . Gist jest tutaj: https://Gist.github.com/2414111

kod referencyjny jquery:

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

Kod odrzucenia:

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

Cieszyć się!

20
Matt Alcock

Bez użycia ViewBox

Oto przykład rozwiązania, które nie polega na użyciu zmiennej viewBox:

Kluczem jest aktualizacja range z skal które są używane do umieszczania danych. 

Najpierw oblicz swój oryginalny współczynnik proporcji:

var ratio = width / height;

Następnie przy każdej zmianie rozmiaru zaktualizuj zmienną range z x i y:

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

Należy pamiętać, że wysokość zależy od szerokości i współczynnika kształtu, dzięki czemu zachowane są oryginalne proporcje.

Na koniec „przerysuj” wykres - zaktualizuj dowolny atrybut zależny od skali x lub y:

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

Zauważ, że w ponownym wymiarowaniu zmiennej rects możesz użyć górnego ograniczenia zmiennej range z y, a nie jawnie używać wysokości:

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

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 ratio = width / height;

var x = d3.scale.ordinal()
  .domain(histogram.map(function(d) {
    return d.x;
  }))

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

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

var rects = svg.selectAll("rect").data(histogram);
rects.enter().append("rect");

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

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

d3.select(window).on('resize', function() {
  resize();
  redraw();
})

resize();
redraw();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

4
Paul Murray

Jeśli używasz d3.js przez c3.js rozwiązanie problemu odpowiedzi jest całkiem proste:

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

gdzie wygenerowany kod HTML wygląda tak:

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

W przypadku, gdy używasz wrappera d3 jak plottable.js , pamiętaj, że najłatwiejszym rozwiązaniem może być dodanie detektora zdarzeń, a następnie wywołanie funkcji redraw ( redraw w plottable.js). W przypadku plottable.js będzie to działać doskonale (takie podejście jest słabo udokumentowane):

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

Odpowiedź Shawna Allena była świetna. Ale może nie chcesz tego robić za każdym razem. Jeśli go hostujesz na vida.io , otrzymasz automatyczną odpowiedź na wizualizację svg.

Możesz uzyskać responsywną ramkę iframe za pomocą tego prostego kodu osadzania:

<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/

Ujawnienie: buduję tę funkcję w vida.io .

2
Phuoc Do

W przypadku, gdy ludzie nadal odwiedzają to pytanie - oto, co zadziałało dla mnie:

  • Dołącz iframe do div i użyj css, aby dodać dopełnienie, powiedzmy, 40% do tego div (procent w zależności od pożądanego współczynnika proporcji). Następnie ustaw zarówno szerokość, jak i wysokość samego iframe na 100%.

  • W dokumencie HTML zawierającym wykres, który ma zostać załadowany w ramce iframe, ustaw szerokość na szerokość div, do której svg jest dołączany (lub na szerokość ciała) i ustaw współczynnik proporcji wysokość na szerokość *.

  • Napisz funkcję, która ponownie ładuje zawartość iframe po zmianie rozmiaru okna, aby dostosować rozmiar wykresu, gdy ludzie obracają swój telefon. 

Przykład tutaj na mojej stronie internetowej: http://dirkmjk.nl/en/2016/05/embedding-d3js-charts-responsive-website

AKTUALIZACJA 30 grudnia 2016 r.

Podejście, które opisałem powyżej, ma pewne wady, zwłaszcza że nie bierze pod uwagę wysokości tytułu i podpisów, które nie są częścią svg utworzonego przez D3. Od tamtej pory natknąłem się na to, co uważam za lepsze podejście:

  • Ustaw szerokość wykresu D3 na szerokość div, do którego jest dołączony, i użyj współczynnika proporcji, aby odpowiednio ustawić jego wysokość;
  • Czy osadzona strona przesyła wysokość i adres URL do strony nadrzędnej za pomocą postMessage HTML5;
  • Na stronie nadrzędnej użyj adresu URL, aby zidentyfikować odpowiednią ramkę iframe (przydatne, jeśli masz więcej niż jedną ramkę iframe na swojej stronie) i zaktualizuj jej wysokość do wysokości osadzonej strony.

Przykład tutaj na mojej stronie internetowej: http://dirkmjk.nl/en/2016/12/embedding-d3js-charts-responsive-website-better-solution

1
dirkmjk

Jedną z podstawowych zasad łączenia danych D3 jest to, że jest ono idempotentne. Innymi słowy, jeśli wielokrotnie oceniasz połączenie danych z tymi samymi danymi, renderowane dane wyjściowe są takie same. Dlatego, o ile poprawnie renderujesz swój wykres, zwracając uwagę na opcje wprowadzania, aktualizacji i wyjścia - wszystko, co musisz zrobić, gdy rozmiar się zmienia, jest ponownie renderowany w całości.

Jest kilka innych rzeczy, które powinieneś zrobić, jednym jest odrzucenie programu obsługi zmiany rozmiaru okna, aby go zdławić. Ponadto, zamiast twardych szerokości/wysokości, powinno to zostać osiągnięte poprzez pomiar elementu zawierającego.

Alternatywnie, tutaj jest twój wykres renderowany za pomocą d3fc , który jest zestawem komponentów D3, które poprawnie obsługują łączenie danych. Posiada również wykres kartezjański, który mierzy go zawierający element ułatwiający tworzenie wykresów „responsywnych”:

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

Możesz zobaczyć to w akcji w tym kodzie:

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

gdzie można zmienić rozmiar okna i sprawdzić, czy wykres jest prawidłowo ponownie renderowany.

Proszę zauważyć, że jako pełne ujawnienie jestem jednym z opiekunów d3fc.

0
ColinE

Unikałbym rozwiązań zmiany rozmiaru/kleszczy, takich jak plaga, ponieważ są one nieefektywne i mogą powodować problemy w aplikacji (np. Podpowiedź ponownie oblicza pozycję, w jakiej powinna pojawić się w oknie zmiany rozmiaru, a chwilę później również zmienia się rozmiar wykresu i strona ponownie układy i teraz twoja etykieta ponownie się myli).

Możesz symulować to zachowanie w niektórych starszych przeglądarkach, które nie obsługują go poprawnie, podobnie jak IE11, używając elementu <canvas>, który utrzymuje jego aspekt.

Biorąc pod uwagę 960x540, czyli aspekt 16: 9:

<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
Dominic