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

224

Angenommen, ich habe ein Histogrammskript, das eine 960 500 svg-Grafik erstellt. Wie kann ich darauf reagieren, um die Größe der Grafikbreiten und -höhen dynamisch zu ändern?

<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 lautet: https://gist.github.com/993912

Matt Alcock
quelle
5
Ich fand die Methode in dieser Antwort einfacher: stackoverflow.com/a/11948988/511203
Mike Gorski
Der einfachste Weg, den ich gefunden habe, ist, die Breite und Höhe des SVG
mtx

Antworten:

316

Es gibt einen anderen Weg , dies zu tun, erfordert nicht die Grafik neu gezeichnet, und es beinhaltet die Modifizierung viewBox und preserveAspectRatio Attribute auf dem <svg>Element:

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

Update 24.11.15 : Die meisten modernen Browser können das Seitenverhältnis von SVG-Elementen aus dem ableitenviewBox , 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 Größe Ihres Elements ändern, wenn die Größe des Fensters 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);
  });

Und der SVG-Inhalt wird automatisch skaliert. Ein funktionierendes Beispiel hierfür (mit einigen Änderungen) finden Sie hier : Ändern Sie einfach die Größe des Fensters oder des unteren rechten Bereichs, um zu sehen, wie es reagiert.

Shawn Allen
quelle
1
Ich mag diesen Ansatz wirklich, 2 Fragen. 1 Funktioniert die Viewbox browserübergreifend? 2. In Ihrem Beispiel werden die Kreise in der Größe geändert, passen aber nicht in das Fenster. Geht etwas mit dem SVG-Ansichtsfeld oder dem Seitenverhältnis schief?
Matt Alcock
4
Ich liebe diesen Ansatz, bin mir nicht sicher, ob das richtig funktioniert. Wie Matt sagt, wird die Größe der Kreise geändert, aber der Abstand von der linken Seite des Diagramms zum ersten Kreis ändert sich nicht mit der Geschwindigkeit der Kreise. Ich habe versucht, ein bisschen in jsfiddle herumzuspielen, konnte es aber mit Google Chrome nicht wie erwartet zum Laufen bringen. Mit welchen Browsern sind viewBox underveAspectRatio kompatibel?
Chris Withers
9
Wenn Sie nur viewBox underveAspectRatio auf eine SVG-Datei mit einer Breitenhöhe von 100% des übergeordneten Elements setzen, funktioniert das
übergeordnete Element
2
Bestätigen, dass Deepu korrekt ist. Funktioniert mit Bootstrap / Flex Grid. Danke @Deepu.
Mike O'Connor
3
Hinweis: In mehreren d3-Tutorials 1.) werden im HTML die SVG-Breite und -Höhe eingestellt und 2.) die Visualisierung erfolgt über einen onload Funktionsaufruf. Wenn die SVG-Breite und -Höhe im tatsächlichen HTML-Code festgelegt sind und Sie die oben beschriebene Technik verwenden, ist das Bild nicht skalierbar.
SumNeuron
34

Suchen Sie nach "Responsive SVG". Es ist ziemlich einfach, eine SVG reaktionsfähig zu machen, und Sie müssen sich keine Gedanken mehr über Größen machen.

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

cminatti
quelle
1
In meinem Fall, Polsterung unten: 100% bewirkt, dass mein Behälter unten zusätzliche Polsterung hat, daher habe ich ihn auf 50% geändert, um ihn zu entfernen. Verwenden Sie Ihren Code und machen Sie das SVG erfolgreich ansprechend, danke.
V-SHY
20

Ich habe einen kleinen Kern zusammengestellt, um dies 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 Zeichenfunktion dynamisch zeichnet und von den Variablen für die Breite und Höhe der Visualisierung gesteuert wird. (Der beste Weg, dies zu tun, ist die Verwendung der API d3.scale.)
  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 mit jquery aus dem referenzierten Element ab.
  5. Binden / verketten Sie die Zeichenfunktion an die Fenstergrößenä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 minimierte d3.js-Skript für die Geschwindigkeit hinzugefügt. Das Wesentliche ist hier: https://gist.github.com/2414111

jquery reference back code:

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

Entprellungscode:

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!

Matt Alcock
quelle
Dies scheint nicht zu funktionieren; Wenn ich die Größe des Fensters der HTML-Datei ändere, die in der korrigierten Liste enthalten ist, wird das Histogramm immer noch abgeschnitten, wenn das Fenster kleiner wird ...
Chris Withers
1
SVG ist ein Vektorformat. Sie können eine beliebige Größe der virtuellen Ansichtsbox festlegen und diese dann auf reale Dimensionen skalieren. JS muss nicht verwendet werden.
Phil Pirozhkov
4

Ohne ViewBox zu verwenden

Hier ist ein Beispiel für eine Lösung, die nicht auf der Verwendung von a basiert viewBox:

Der Schlüssel ist , die im Aktualisierungsbereich 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ößenänderung das rangevon xund 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, damit Ihre ursprünglichen Proportionen erhalten bleiben.

Zum Schluss "zeichnen" Sie das Diagramm neu - aktualisieren Sie alle Attribute, die entweder von der xoder der ySkala abhängen :

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 bei der Größenänderung von die rectsObergrenze von rangevon verwenden können y, anstatt die Höhe explizit zu verwenden:

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

Paul Murray
quelle
3

Wenn Sie d3.js bis c3.js verwenden, ist die Lösung des Problems der Reaktionsfähigkeit recht einfach:

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

wo der generierte HTML aussieht:

<div id="chart">
    <svg>...</svg>
</div>
vanna
quelle
2

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

Mit diesem einfachen Einbettungscode können Sie auf iframe reagieren:

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

Disclosure: Ich habe dieses Feature bei bauen vida.io .

Phuoc Do.
quelle
2

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

    window.addEventListener("resize", function() {
      table.redraw();
    });
Riley Steele Parsons
quelle
Es wäre wahrscheinlich am besten, diese Funktion zu entprellen, um zu vermeiden, dass das Neuzeichnen so schnell wie möglich ausgelöst wird
Ian
1

Falls die Leute diese Frage immer noch besuchen - hier ist, was für mich funktioniert hat:

  • Fügen Sie den Iframe in ein Div ein und verwenden Sie CSS, um diesem Div einen Abstand von beispielsweise 40% hinzuzufügen (der Prozentsatz hängt vom gewünschten Seitenverhältnis ab). Stellen Sie dann sowohl die Breite als auch die Höhe des Iframes selbst auf 100% ein.

  • Stellen Sie im HTML-Dokument mit dem Diagramm, das in den Iframe geladen werden soll, die Breite auf die Breite des Divs ein, an das das SVG angehängt wird (oder auf die Breite des Körpers), und legen Sie das Seitenverhältnis Höhe zu Breite * fest.

  • Schreiben Sie eine Funktion, mit der der Iframe-Inhalt beim Ändern der Fenstergröße neu geladen wird, um die Größe des Diagramms anzupassen, wenn Benutzer 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, insbesondere, dass die Höhe keine Titel und Beschriftungen berücksichtigt, die nicht Teil des von D3 erstellten SVG sind. Ich bin seitdem auf einen meiner Meinung nach besseren Ansatz gestoßen:

  • Stellen Sie die Breite des D3-Diagramms auf die Breite des Divs ein, an das es angehängt ist, und verwenden Sie das Seitenverhältnis, um seine Höhe entsprechend einzustellen.
  • Lassen Sie die eingebettete Seite ihre Höhe und URL mithilfe von 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 seine 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

dirkmjk
quelle
0

Eines der Grundprinzipien des D3-Daten-Joins ist, dass er idempotent ist. Mit anderen Worten, wenn Sie wiederholt eine Datenverknüpfung mit denselben Daten auswerten, ist die gerenderte Ausgabe dieselbe. Solange Sie Ihr Diagramm korrekt rendern und dabei Ihre Auswahl zum Eingeben, Aktualisieren und Beenden beachten - alles, was Sie tun müssen, wenn sich die Größe ändert, ist, das Diagramm in seiner Gesamtheit neu zu rendern.

Es gibt noch ein paar andere Dinge, die Sie tun sollten. Eine davon ist, den Handler für die Größenänderung des Fensters zu entprellen, um ihn zu drosseln. Anstatt Breiten / Höhen hart zu codieren, sollte dies auch durch Messen des enthaltenen Elements erreicht werden.

Als Alternative wird hier Ihr Diagramm mit d3fc gerendert. Hierbei handelt es sich um eine Reihe von D3-Komponenten, die Datenverknüpfungen korrekt verarbeiten. Es hat auch ein kartesisches Diagramm, das das Element misst, wodurch es einfach ist, "reaktionsfähige" Diagramme zu erstellen:

// 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 Fenstergröße ändern und überprüfen, ob das Diagramm korrekt neu gerendert wurde.

Bitte beachten Sie, dass ich als vollständige Offenlegung einer der Betreuer von d3fc bin.

ColinE
quelle
Vielen Dank, dass Sie Ihre Antworten auf dem neuesten Stand halten! Ich habe die letzten Aktualisierungen einiger Ihrer Beiträge gesehen und ich schätze es wirklich, wenn Leute ihre eigenen Inhalte erneut besuchen! Zur Vorsicht, diese Überarbeitung passt jedoch nicht mehr vollständig zur Frage, da Sie in D3 v4 bearbeiten , während OP eindeutig auf v3 verweist , was zukünftige Leser verwirren könnte. Persönlich neige ich für meine eigenen Antworten dazu, einen v4- Abschnitt hinzuzufügen, während der ursprüngliche v3- Abschnitt beibehalten wird. Ich denke, v4 ist möglicherweise eine eigene Antwort wert, die einen gegenseitigen Verweis auf beide Beiträge enthält. Aber das sind nur meine zwei Cent ...
Altocumulus
Das ist ein wirklich guter Punkt! Es ist so einfach, sich mitreißen zu lassen und sich nur auf die Zukunft und das Neue zu konzentrieren. Nach den neuesten d3-Fragen zu urteilen, denke ich, dass viele Leute immer noch v3 verwenden. Es ist auch frustrierend, wie subtil einige der Unterschiede sind! Ich werde meine Änderungen noch einmal
überprüfen
0

Ich würde Größenänderungs- / Häkchenlösungen wie die Pest vermeiden, da sie ineffizient sind und Probleme in Ihrer App verursachen können (z. B. berechnet ein Tooltip die Position neu, die beim Ändern der Fenstergröße angezeigt werden soll, und einen Moment später ändert sich auch die Größe Ihres Diagramms und die Seite ändert sich neu Layouts und jetzt ist dein Tooltip wieder falsch).

Sie können dieses Verhalten in einigen älteren Browsern simulieren, die es nicht richtig unterstützen, wie z. B. IE11, indem Sie a verwenden <canvas> Element verwenden, das seinen Aspekt beibehält.

Bei 960x540 ist dies ein Aspekt von 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>
Dominic
quelle
-1

Sie können auch Bootstrap 3 verwenden , um die Größe einer Visualisierung anzupassen. Zum Beispiel können wir den HTML- Code wie folgt einrichten :

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

Ich habe aufgrund meiner Bedürfnisse eine feste Höhe eingestellt, aber Sie können die Größe auch automatisch belassen. Das "col-sm-6 col-md-4" macht das div für verschiedene Geräte ansprechbar. Weitere Informationen finden Sie unter http://getbootstrap.com/css/#grid-example-basic

Wir können mit Hilfe der ID- Monatsansicht auf das Diagramm zugreifen .

Ich werde nicht sehr detailliert auf den d3-Code eingehen, sondern nur den Teil eingeben, der für die Anpassung an verschiedene Bildschirmgrößen erforderlich ist.

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

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

Die Breite wird festgelegt, indem die Breite des Div mit der ID-Monatsansicht ermittelt wird.

Die Höhe sollte in meinem Fall nicht den gesamten Bereich umfassen. Ich habe auch Text über der Leiste, daher muss ich auch diesen Bereich berechnen. Deshalb habe ich den Textbereich mit der ID responsivetext identifiziert. Um die zulässige Höhe des Balkens zu berechnen, habe ich die Höhe des Textes von der Höhe des Div abgezogen.

Auf diese Weise können Sie eine Leiste erstellen, die alle unterschiedlichen Bildschirm- / Div-Größen übernimmt. Es ist vielleicht nicht der beste Weg, es zu tun, aber es funktioniert sicherlich für die Bedürfnisse meines Projekts.

arlindmusliu
quelle
1
Hallo, ich habe es versucht, aber die Größe des SVG-Inhalts wird nicht geändert, wie im Kalender oder in den Diagrammen.