Ändern Sie die Größe von svg, wenn die Fenstergröße in d3.js geändert wird

183

Ich zeichne ein Streudiagramm mit d3.js. Mit Hilfe dieser Frage:
Ermitteln Sie die Größe des Bildschirms, der aktuellen Webseite und des Browserfensters

Ich benutze diese Antwort:

var w = window,
    d = document,
    e = d.documentElement,
    g = d.getElementsByTagName('body')[0],
    x = w.innerWidth || e.clientWidth || g.clientWidth,
    y = w.innerHeight|| e.clientHeight|| g.clientHeight;

So kann ich mein Diagramm wie folgt an das Fenster des Benutzers anpassen:

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

Jetzt möchte ich, dass sich etwas um die Größenänderung des Plots kümmert, wenn der Benutzer die Fenstergröße ändert.

PS: Ich verwende jQuery nicht in meinem Code.

mthpvg
quelle

Antworten:

294

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")
   // Container class to make it responsive.
   .classed("svg-container", true) 
   .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)
   // Fill with a rectangle for visualization.
   .append("rect")
   .classed("rect", true)
   .attr("width", 600)
   .attr("height", 400);
.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;
}

svg .rect {
  fill: gold;
  stroke: steelblue;
  stroke-width: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<div id="chartId"></div>

Hinweis: Alles im SVG-Bild wird mit der Fensterbreite skaliert. Dies umfasst Strichbreite und Schriftgrößen (auch solche, die mit CSS festgelegt wurden). Wenn dies nicht erwünscht ist, gibt es unten weitere alternative Lösungen.

Weitere Infos / Tutorials:

http://thenewcode.com/744/Make-SVG-Responsive

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

cminatti
quelle
8
Dies ist viel eleganter und verwendet auch einen Ansatz zur Containerskalierung, der in jedem Toolkit eines Front-End-Entwicklers enthalten sein sollte (kann zum Skalieren von Elementen in einem bestimmten Seitenverhältnis verwendet werden). Sollte die beste Antwort sein.
Kontur
13
Nur um dies hinzuzufügen: Das viewBoxAttribut sollte auf die relevante Höhe und Breite Ihres SVG-Diagramms festgelegt werden: .attr("viewBox","0 0 " + width + " " + height) wo widthund heightwoanders definiert. Das heißt, es sei denn, Sie möchten, dass Ihr SVG durch das Ansichtsfeld abgeschnitten wird. Möglicherweise möchten Sie auch den padding-bottomParameter in Ihrem CSS aktualisieren , um ihn an das Seitenverhältnis Ihres SVG-Elements anzupassen. Hervorragende Resonanz - sehr hilfreich und ein Vergnügen. Vielen Dank!
Andrew Guy
13
Basierend auf der gleichen Idee wäre es interessant, eine Antwort zu geben, bei der das Seitenverhältnis nicht beibehalten wird. Die Frage war, ob es ein SVG-Element gibt, das beim Ändern der Größe das gesamte Fenster abdeckt, was in den meisten Fällen ein anderes Seitenverhältnis bedeutet.
Nicolas Le Thierry d'Ennequin
37
Nur ein Hinweis zu UX: In einigen Anwendungsfällen ist es nicht ideal, Ihr SVG-basiertes d3-Diagramm wie ein Asset mit einem festen Seitenverhältnis zu behandeln. Diagramme, die Text anzeigen oder dynamisch sind oder sich im Allgemeinen eher wie eine ordnungsgemäße Benutzeroberfläche als wie ein Asset anfühlen, können durch erneutes Rendern mit aktualisierten Abmessungen mehr gewinnen. Beispielsweise erzwingt viewBox, dass Achsenschriftarten und -ticks vergrößert / verkleinert werden, was im Vergleich zu HTML-Text möglicherweise unangenehm aussieht. Im Gegensatz dazu ermöglicht ein erneutes Rendern dem Diagramm, die Beschriftung in einer einheitlichen Größe beizubehalten, und bietet die Möglichkeit, Ticks hinzuzufügen oder zu entfernen.
Karim Hernandez
1
Warum top: 10px;? Scheint für mich falsch und bietet Offset. Das Setzen auf 0px funktioniert einwandfrei.
TimZaman
43

Verwenden Sie window.onresize :

function updateWindow(){
    x = w.innerWidth || e.clientWidth || g.clientWidth;
    y = w.innerHeight|| e.clientHeight|| g.clientHeight;

    svg.attr("width", x).attr("height", y);
}
d3.select(window).on('resize.updatesvg', updateWindow);

http://jsfiddle.net/Zb85u/1/

Adam Pearce
quelle
11
Dies ist keine gute Antwort, da es sich um das Biden von DOM-Ereignissen handelt ... Eine viel bessere Lösung ist die Verwendung von nur d3 und css
alem0lars
@ alem0lars lustig genug, die CSS / D3-Version hat bei mir nicht funktioniert und diese hat ...
Christian
32

UPDATE benutze einfach den neuen Weg von @cminatti


alte Antwort für historische Zwecke

IMO ist es besser, select () und on () zu verwenden, da Sie auf diese Weise mehrere Event-Handler für die Größenänderung haben können ... werden Sie einfach nicht zu verrückt

d3.select(window).on('resize', resize); 

function resize() {
    // update width
    width = parseInt(d3.select('#chart').style('width'), 10);
    width = width - margin.left - margin.right;

    // resize the chart
    x.range([0, width]);
    d3.select(chart.node().parentNode)
        .style('height', (y.rangeExtent()[1] + margin.top + margin.bottom) + 'px')
        .style('width', (width + margin.left + margin.right) + 'px');

    chart.selectAll('rect.background')
        .attr('width', width);

    chart.selectAll('rect.percent')
        .attr('width', function(d) { return x(d.percent); });

    // update median ticks
    var median = d3.median(chart.selectAll('.bar').data(), 
        function(d) { return d.percent; });

    chart.selectAll('line.median')
        .attr('x1', x(median))
        .attr('x2', x(median));


    // update axes
    chart.select('.x.axis.top').call(xAxis.orient('top'));
    chart.select('.x.axis.bottom').call(xAxis.orient('bottom'));

}

http://eyeseast.github.io/visible-data/2013/08/28/responsive-charts-with-d3/

slf
quelle
Ich kann nicht weniger zustimmen. Die Methode von cminatti funktioniert mit allen d3js-Dateien, mit denen wir uns befassen. Diese Methode zum Transformieren mit einer Größenänderung pro Diagramm ist übertrieben. Außerdem muss es für jedes mögliche Diagramm, das wir uns ausdenken könnten, neu geschrieben werden. Die oben erwähnte Methode funktioniert für alles. Ich habe 4 Rezepte ausprobiert, einschließlich der von Ihnen erwähnten Art des Nachladens, und keines davon funktioniert für mehrere Flächendiagramme, wie sie im Kubismus verwendet werden.
Eamonn Kenny
1
@EamonnKenny Ich kann dir nicht mehr zustimmen. Die andere Antwort 7 Monate in der Zukunft ist überlegen :)
slf
Für diese Methode: "d3.select (Fenster) .on" Ich konnte "d3.select (Fenster) .off" oder "d3.select (Fenster) .unbind" nicht finden
sea-kg
Diese Antwort ist keineswegs minderwertig. Ihre Antwort skaliert jeden Aspekt des Diagramms (einschließlich Häkchen, Achsen, Linien und Textanmerkungen), der bei einigen Bildschirmgrößen unangenehm, bei anderen schlecht und bei extremen Größen unlesbar aussehen kann. Ich finde es besser, die Größe mit dieser Methode (oder für modernere Lösungen mit @Angu Agarwal) zu ändern.
Rúnar Berg
12

Es ist etwas hässlich, wenn der Größenänderungscode fast so lang ist wie der Code zum Erstellen des Diagramms. Anstatt also die Größe jedes Elements des vorhandenen Diagramms zu ändern, sollten Sie es einfach neu laden. So hat es bei mir funktioniert:

function data_display(data){
   e = document.getElementById('data-div');
   var w = e.clientWidth;
   // remove old svg if any -- otherwise resizing adds a second one
   d3.select('svg').remove();
   // create canvas
   var svg = d3.select('#data-div').append('svg')
                                   .attr('height', 100)
                                   .attr('width', w);
   // now add lots of beautiful elements to your graph
   // ...
}

data_display(my_data); // call on page load

window.addEventListener('resize', function(event){
    data_display(my_data); // just call it again...
}

Die entscheidende Linie ist d3.select('svg').remove();. Andernfalls wird bei jeder Größenänderung ein weiteres SVG-Element unter dem vorherigen hinzugefügt.

Raik
quelle
Dies wird die Leistung absolut
beeinträchtigen,
2
Dies ist jedoch richtig: Beim Rendern können Sie festlegen, ob Sie eine eingefügte Achse haben sollen, eine Legende ausblenden und andere Dinge basierend auf dem verfügbaren Inhalt ausführen. Bei Verwendung einer reinen CSS / Viewbox-Lösung sieht die Visualisierung lediglich gequetscht aus. Gut für Bilder, schlecht für Daten.
Chris Knoll
8

In Force-Layouts funktioniert das einfache Festlegen der Attribute 'height' und 'width' nicht, um das Diagramm neu zu zentrieren / in den SVG-Container zu verschieben. Es gibt jedoch eine sehr einfache Antwort, die für Force Layouts funktioniert, die hier zu finden sind . Zusammenfassend:

Verwenden Sie das gleiche (beliebige) Ereignis, das Sie mögen.

window.on('resize', resize);

Angenommen, Sie haben svg & force-Variablen:

var svg = /* D3 Code */;
var force = /* D3 Code */;    

function resize(e){
    // get width/height with container selector (body also works)
    // or use other method of calculating desired values
    var width = $('#myselector').width(); 
    var height = $('#myselector').height(); 

    // set attrs and 'resume' force 
    svg.attr('width', width);
    svg.attr('height', height);
    force.size([width, height]).resume();
}

Auf diese Weise rendern Sie das Diagramm nicht vollständig neu, wir legen die Attribute fest und d3 berechnet die Dinge nach Bedarf neu. Dies funktioniert zumindest, wenn Sie einen Schwerpunkt verwenden. Ich bin mir nicht sicher, ob dies eine Voraussetzung für diese Lösung ist. Kann jemand bestätigen oder leugnen?

Prost, g

gavs
quelle
Dies funktionierte perfekt bei meinem Force-Layout mit ungefähr 100 Verbindungen. Vielen Dank.
Tribe84
1

Für diejenigen, die kraftgerichtete Diagramme in D3 v4 / v5 verwenden, existiert die sizeMethode nicht mehr. So etwas wie das Folgende hat bei mir funktioniert (basierend auf diesem Github-Problem ):

simulation
    .force("center", d3.forceCenter(width / 2, height / 2))
    .force("x", d3.forceX(width / 2))
    .force("y", d3.forceY(height / 2))
    .alpha(0.1).restart();
Justin Lewis
quelle
1

Wenn Sie eine benutzerdefinierte Logik binden möchten, um die Größe des Ereignisses zu ändern, können Sie heutzutage die ResizeObserver-Browser-API für den Begrenzungsrahmen eines SVGElement verwenden.
Dies wird auch der Fall sein, wenn die Größe des Containers aufgrund der Größenänderung der Elemente in der Nähe geändert wird.
Es gibt eine Polyfüllung für eine breitere Browserunterstützung.

So kann es in der UI-Komponente funktionieren:

function redrawGraph(container, { width, height }) {
  d3
    .select(container)
    .select('svg')
    .attr('height', height)
    .attr('width', width)
    .select('rect')
    .attr('height', height)
    .attr('width', width);
}

// Setup observer in constructor
const resizeObserver = new ResizeObserver((entries, observer) => {
  for (const entry of entries) {
    // on resize logic specific to this component
    redrawGraph(entry.target, entry.contentRect);
  }
})

// Observe the container
const container = document.querySelector('.graph-container');
resizeObserver.observe(container)
.graph-container {
  height: 75vh;
  width: 75vw;
}

.graph-container svg rect {
  fill: gold;
  stroke: steelblue;
  stroke-width: 3px;
}
<script src="https://unpkg.com/[email protected]/dist/ResizeObserver.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<figure class="graph-container">
  <svg width="100" height="100">
    <rect x="0" y="0" width="100" height="100" />
  </svg>
</figure>

// unobserve in component destroy method
this.resizeObserver.disconnect()
Anbu Agarwal
quelle