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.