it-swarm.com.de

Was ist der beste Weg, um ein d3.js-Visualisierungslayout ansprechend zu gestalten?

Angenommen, ich habe ein Histogramm-Skript, das eine 960 500 Svg-Grafik erstellt. Wie mache ich das so, dass die Breite und Höhe der Grafik dynamisch sind?

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

Das vollständige Beispiel für das Histogramm Gist ist: https://Gist.github.com/993912

207
Matt Alcock

Es gibt eine andere Möglichkeit, dies zu tun, ohne dass das Diagramm neu gezeichnet werden muss. Dazu müssen die Attribute viewBox und preserveAspectRatio des <svg>-Elements geändert werden: 

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

Update 11/24/15: Die meisten modernen Browser können das Seitenverhältnis von SVG-Elementen aus der viewBox ableiten, sodass Sie die Größe des Diagramms möglicherweise nicht auf dem neuesten Stand halten müssen. Wenn Sie ältere Browser unterstützen müssen, können Sie die Elementgröße ändern, wenn die Fenstergröße wie folgt geändert wird:

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

Die svg-Inhalte werden automatisch skaliert. Sie können ein funktionierendes Beispiel (mit einigen Änderungen) sehen. hier : Ändern Sie die Größe des Fensters oder des rechten unteren Bereichs, um zu sehen, wie es reagiert.

296
Shawn Allen

Suchen Sie nach 'responsive SVG'. Es ist ziemlich einfach, eine SVG-Funktion zu aktivieren, und Sie müssen sich nicht mehr um die Größe kümmern. 

So habe ich es gemacht: 

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

Der CSS-Code:

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

Weitere Infos/Tutorials: 

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

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

29
cminatti

Ich habe einen kleinen Gist programmiert, um dieses Problem zu lösen.

Das allgemeine Lösungsmuster lautet wie folgt:

  1. Teilen Sie das Skript in Berechnungs- und Zeichenfunktionen auf. 
  2. Stellen Sie sicher, dass die Zeichnungsfunktion dynamisch zeichnet und von den Variablen für die Breite und Höhe der Visualisierung gesteuert wird. (Am besten verwenden Sie dazu :.). 
  3. Binden/verketten Sie die Zeichnung mit einem Referenzelement Im Markup. (Ich habe dafür Jquery verwendet, also importiert).
  4. Denken Sie daran, es zu entfernen, wenn es bereits gezeichnet ist. Rufen Sie die Dimensionen von Des referenzierten Elements mithilfe von Jquery ab. 
  5. Binden/verketten Sie die Zeichenfunktion mit der Fensteränderungsfunktion. Führen Sie ein Debounce (Timeout) in diese -Kette ein, um sicherzustellen, dass wir erst nach einem Timeout neu zeichnen. 

Ich habe auch das minified d3.js-Skript für Geschwindigkeit hinzugefügt. Der Gist ist hier: https://Gist.github.com/2414111

jquery-Referenzcode zurück:

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

Entprellcode:

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

Genießen!

20
Matt Alcock

Ohne ViewBox zu verwenden

Hier ist ein Beispiel für eine Lösung, die nicht auf viewBox angewiesen ist:

Der Schlüssel liegt in der Aktualisierung des Bereichs der Skalen , die zum Platzieren von Daten verwendet werden. 

Berechnen Sie zunächst Ihr ursprüngliches Seitenverhältnis:

var ratio = width / height;

Aktualisieren Sie dann bei jeder Größe die range von x und y:

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

Beachten Sie, dass die Höhe auf der Breite und dem Seitenverhältnis basiert, sodass Ihre ursprünglichen Proportionen erhalten bleiben.

Schließlich "zeichnen" Sie das Diagramm "neu" - aktualisieren Sie jedes Attribut, das von den Skalen x oder y abhängt:

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

Beachten Sie, dass Sie beim Ändern der Größe von rects die Obergrenze von range von y verwenden können, anstatt die Höhe explizit zu verwenden:

.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

Wenn Sie d3.js über c3.js verwenden, ist die Lösung des Antwortproblems recht einfach:

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

wo das erzeugte HTML so aussieht:

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

Falls Sie einen d3-Wrapper wie plottable.js verwenden, sollten Sie beachten, dass die einfachste Lösung darin besteht, einen Ereignis-Listener hinzuzufügen und dann eine redraw - Funktion aufzurufen ( redraw in plottable.js). Im Fall von plottable.js wird dies hervorragend funktionieren (dieser Ansatz ist schlecht dokumentiert):

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

Shawn Allens Antwort war großartig. Möglicherweise möchten Sie dies jedoch nicht jedes Mal tun. Wenn Sie es auf vida.io hosten, erhalten Sie automatisch eine Antwort auf Ihre SVG-Visualisierung.

Mit diesem einfachen Einbettungscode können Sie ein ansprechendes iframe erhalten:

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

Offenlegung: Ich baue diese Funktion unter vida.io .

2
Phuoc Do

Für den Fall, dass die Leute diese Frage noch besuchen - das hat für mich funktioniert:

  • Schließen Sie den iframe in ein div ein und fügen Sie mit div eine Auffüllung von 40% zu diesem div hinzu (der Prozentsatz hängt von dem gewünschten Seitenverhältnis ab). Setzen Sie dann die Breite und Höhe des iframe auf 100%.

  • Legen Sie in dem HTML-Dokument, das das zu ladende Diagramm im Iframe enthält, die Breite auf die Breite des Div, an das die SVG angehängt wird (oder die Breite des Hauptteils) fest, und legen Sie das Seitenverhältnis Höhe zu Breite * fest.

  • Schreiben Sie eine Funktion, die den iframe-Inhalt bei der Größenänderung des Fensters neu lädt, um die Größe des Diagramms anzupassen, wenn Personen ihr Telefon drehen. 

Beispiel hier auf meiner Website: http://dirkmjk.nl/de/2016/05/embedding-d3js-charts-responsive-website

UPDATE 30. Dezember 2016

Der oben beschriebene Ansatz hat einige Nachteile, vor allem, dass er nicht die Höhe von Titeln und Bildunterschriften berücksichtigt, die nicht Teil der von D3 erstellten svg sind. Seitdem bin ich auf einen meiner Meinung nach besseren Ansatz gestoßen:

  • Stellen Sie die Breite des D3-Diagramms auf die Breite des Div ein, an das es angehängt ist, und verwenden Sie das Seitenverhältnis, um die Höhe entsprechend anzupassen.
  • Lassen Sie die eingebettete Seite ihre Höhe und URL mit der postMessage von HTML5 an die übergeordnete Seite senden.
  • Verwenden Sie auf der übergeordneten Seite die URL, um den entsprechenden Iframe zu identifizieren (nützlich, wenn Sie mehr als einen Iframe auf Ihrer Seite haben) und aktualisieren Sie die Höhe auf die Höhe der eingebetteten Seite.

Beispiel hier auf meiner Website: http://dirkmjk.nl/de/2016/12/embedding-d3js-charts-responsive-website-better-solution

1
dirkmjk

Eines der Grundprinzipien des D3-Daten-Joins ist, dass er idempotent ist. Wenn Sie also wiederholt einen Daten-Join mit denselben Daten auswerten, ist die gerenderte Ausgabe dieselbe. Solange Sie Ihr Diagramm korrekt rendern, müssen Sie beim Auswählen, Eingeben, Aktualisieren und Verlassen der Auswahl darauf achten, dass sich das Diagramm bei einer Änderung der Größe vollständig neu erstellt.

Es gibt noch ein paar andere Dinge, die Sie tun sollten. Eine davon ist, den Fenster-Größenänderungs-Handler zu deaktivieren, um ihn zu drosseln. Statt hart codierte Breiten/Höhen sollte dies auch durch Messen des enthaltenden Elements erreicht werden.

Als Alternative wird hier Ihr Diagramm mit d3fc gerendert. Hierbei handelt es sich um eine Gruppe von D3-Komponenten, die Daten-Joins korrekt verarbeiten. Es verfügt auch über ein kartesisches Diagramm, das das Element misst, wodurch das Erstellen von "responsiven" Diagrammen vereinfacht wird:

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

Sie können es in Aktion in diesem Codepen sehen:

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

hier können Sie die Größe des Fensters ändern und überprüfen, ob das Diagramm korrekt wiedergegeben wird.

Bitte beachten Sie, dass ich zur vollständigen Offenlegung einer der Verantwortlichen von d3fc bin.

0
ColinE

Ich würde vermeiden, dass Lösungen wie die Pest skaliert werden, da sie ineffizient sind und Probleme in Ihrer App verursachen können (z. B. berechnet ein Tooltip die Position neu, in der das Fenster angezeigt werden soll. Anschließend ändert sich die Größe des Diagramms und Layouts und jetzt ist Ihr Tooltip wieder falsch).

Sie können dieses Verhalten in einigen älteren Browsern simulieren, die es nicht ordnungsgemäß unterstützen, wie beispielsweise IE11, indem Sie ein <canvas>-Element verwenden, das dessen Aspekt beibehält.

Gegeben sei 960x540, was ein Aspekt von 16: 9 ist:

<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