Ridimensiona svg quando la finestra viene ridimensionata in d3.js


183

Sto disegnando un diagramma a dispersione con d3.js. Con l'aiuto di questa domanda:
ottieni le dimensioni dello schermo, della pagina Web corrente e della finestra del browser

Sto usando questa risposta:

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;

Quindi sono in grado di adattare la mia trama alla finestra dell'utente in questo modo:

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

Ora vorrei che qualcosa si occupasse di ridimensionare la trama quando l'utente ridimensionava la finestra.

PS: Non sto usando jQuery nel mio codice.


Risposte:


295

Cerca 'SVG reattivo' è abbastanza semplice rendere reattivo un SVG e non devi più preoccuparti delle dimensioni.

Ecco come l'ho fatto:

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>

Nota: tutto nell'immagine SVG verrà ridimensionato con la larghezza della finestra. Ciò include la larghezza del tratto e le dimensioni del carattere (anche quelle impostate con CSS). Se questo non è desiderato, ci sono più soluzioni alternative coinvolte di seguito.

Ulteriori informazioni / tutorial:

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

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


8
Questo è molto più elegante e utilizza anche un approccio di ridimensionamento del contenitore che dovrebbe essere presente nel toolkit di ogni sviluppatore front-end (può essere utilizzato per ridimensionare qualsiasi cosa in un particolare formato). Dovrebbe essere la risposta migliore.
Kontur,

13
Solo per aggiungere a questo: l' viewBoxattributo dovrebbe essere impostato sull'altezza e sulla larghezza pertinenti del diagramma SVG: .attr("viewBox","0 0 " + width + " " + height) dove widthe heightsono definiti altrove. Cioè, a meno che tu non voglia ritagliare il tuo SVG dalla casella di visualizzazione. Potresti anche voler aggiornare il padding-bottomparametro nel tuo css in modo che corrisponda alle proporzioni dell'elemento svg. Ottima risposta però - molto utile e funziona a meraviglia. Grazie!
Andrew Guy,

13
Sulla base della stessa idea, sarebbe interessante dare una risposta che non implichi il mantenimento delle proporzioni. La domanda era sull'avere un elemento svg che continua a coprire l'intera finestra al ridimensionamento, che nella maggior parte dei casi significa un rapporto di aspetto diverso.
Nicolas Le Thierry d'Ennequin,

37
Solo una nota relativa a UX: per alcuni casi d'uso, trattare il tuo grafico d3 basato su SVG come una risorsa con un rapporto di aspetto fisso non è l'ideale. I grafici che visualizzano il testo, o sono dinamici, o in generale sembrano più un'interfaccia utente corretta che una risorsa, hanno più da guadagnare ri-rendering con dimensioni aggiornate. Ad esempio, viewBox forza il ridimensionamento dei caratteri e dei segni di spunta degli assi su / giù, potenzialmente apparentemente scomodo rispetto al testo HTML. Al contrario, un nuovo rendering consente al grafico di mantenere l'etichettatura di dimensioni coerenti, oltre a offrire l'opportunità di aggiungere o rimuovere i segni di spunta.
Karim Hernandez,

1
Perché top: 10px;? Sembra errato per me e fornisce offset. Mettere su 0px funziona bene.
TimZaman,

43

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


11
Questa non è una buona risposta dal momento che implica la partecipazione agli eventi DOM ... Una soluzione molto migliore sta usando solo d3 e css
alem0lars

@ alem0lars abbastanza stranamente, la versione css / d3 non ha funzionato per me e questo ha funzionato ...
Christian

32

AGGIORNAMENTO basta usare il nuovo modo da @cminatti


vecchia risposta per scopi storici

IMO è meglio usare select () e on () poiché in questo modo puoi avere più gestori di eventi di ridimensionamento ... non impazzire troppo

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/


Non posso essere meno d'accordo. Il metodo di cminatti funzionerà su tutti i file d3js che trattiamo. Questo metodo di trasformazione con un ridimensionamento per grafico è eccessivo. Inoltre, deve essere riscritto per ogni possibile grafico a cui potremmo pensare. Il metodo sopra menzionato funziona per tutto. Ho provato 4 ricette tra cui il tipo di ricarica che hai citato e nessuna di esse funziona per trame di aree multiple come il tipo usato nel cubismo.
Eamonn Kenny,

1
@EamonnKenny Non posso essere più d'accordo con te. L'altra risposta 7 mesi in futuro è superiore :)
slf

Per questo metodo: "d3.select (window) .on" Impossibile trovare "d3.select (window) .off" o "d3.select (window) .unbind"
sea-kg


Questa risposta non è affatto inferiore. La loro risposta ridimensionerà ogni aspetto del grafico (inclusi segni di spunta, assi, linee e annotazioni di testo) che può apparire imbarazzante, in alcune dimensioni dello schermo, cattivo in altri e illeggibile in dimensioni estreme. Trovo che sia meglio ridimensionare usando questo metodo (o, per soluzioni più moderne, @Angu Agarwal).
Rúnar Berg,

12

È un po 'brutto se il codice di ridimensionamento è lungo quasi quanto il codice per costruire il grafico al primo posto. Quindi, invece di ridimensionare ogni elemento del grafico esistente, perché non ricaricarlo? Ecco come ha funzionato per me:

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

La linea cruciale è d3.select('svg').remove();. Altrimenti, ogni ridimensionamento aggiungerà un altro elemento SVG al di sotto di quello precedente.


Questo ucciderà assolutamente le prestazioni se stai ridisegnando l'intero elemento su ogni evento di ridimensionamento della finestra
sdemurjian

2
Ma questo è corretto: mentre esegui il rendering puoi determinare se dovresti avere un asse inserito, nascondere una legenda, fare altre cose in base al contenuto disponibile. Usando una soluzione puramente CSS / viewbox, tutto ciò che fa è far sembrare la visualziation schiacciata. Buono per le immagini, cattivo per i dati.
Chris Knoll,

8

Nei layout di forzatura, semplicemente l'impostazione degli attributi 'height' e 'width' non funzionerà per ricentrare / spostare il grafico nel contenitore svg. Tuttavia, c'è una risposta molto semplice che funziona con Force Layouts qui . In sintesi:

Usa lo stesso (qualsiasi) evento che ti piace.

window.on('resize', resize);

Supponendo quindi di avere le variabili svg & force:

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

In questo modo, non ricalcolare completamente il grafico, impostiamo gli attributi e d3 ricalcola le cose come necessario. Questo funziona almeno quando si utilizza un punto di gravità. Non sono sicuro che sia un prerequisito per questa soluzione. Qualcuno può confermare o negare?

Saluti, g


Questo ha funzionato perfettamente sul mio layout di forza che ha circa 100 connessioni. Grazie.
tribe84,

1

Per coloro che usano grafici a forza forzata in D3 v4 / v5, il sizemetodo non esiste più. Qualcosa di simile al seguente ha funzionato per me (basato su questo problema github ):

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

1

Se si desidera associare la logica personalizzata al ridimensionamento dell'evento, al giorno d'oggi è possibile iniziare a utilizzare l' API del browser ResizeObserver per il riquadro di delimitazione di un SVGElement.
Questo gestirà anche il caso in cui il contenitore viene ridimensionato a causa della modifica della dimensione degli elementi vicini.
Esiste un polyfill per un supporto più ampio del browser.

Ecco come potrebbe funzionare nel componente UI:

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/resize-observer-polyfill@1.5.1/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()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.