Domanda

Supponiamo di avere uno script di istogramma che crea un grafico SVG 960 500. Come posso rendere questo reattivo in modo da ridimensionare le larghezze grafiche e le altezze sono dinamiche?

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

L'istogramma di esempio completo è:https://gist.github.com/993912

È stato utile?

Soluzione

C'è un altro modo per farlo che non richiede la ridisegna del grafico e implica la modifica del Viewbox e PreserveSpectRao attributi sul <svg> elemento:

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

AGGIORNAMENTO 24/11/15: La maggior parte dei browser moderni può inferire le proporzioni di elementi SVG dal viewBox, quindi potrebbe non essere necessario mantenere aggiornate le dimensioni del grafico. Se è necessario supportare i browser più vecchi, puoi ridimensionare il tuo elemento quando la finestra si ridimensiona così:

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

E il contenuto di SVG verrà ridimensionato automaticamente. Puoi vedere un esempio funzionante di questo (con alcune modifiche) qui: Basta ridimensionare la finestra o il riquadro in basso a destra per vedere come reagisce.

Altri suggerimenti

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

Ecco come l'ho fatto:

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

Il codice CSS:

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

Maggiori informazioni / tutorial:

http://demostenes.info/blog/744/make-svg-responsive

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

Ho codificato una piccola e per risolvere questo problema.

Il modello di soluzione generale è questo:

  1. Breakout lo script in funzioni di calcolo e disegno.
  2. Assicurarsi che la funzione di disegno si disegna in modo dinamico ed è guidato dalla larghezza di visualizzazione e dalle variabili di altezza (il modo migliore per farlo è utilizzare l'API D3.Scale)
  3. Lega/catena il disegno a un elemento di riferimento nel markup. (Ho usato jQuery per questo, quindi l'ho importato).
  4. Ricorda di rimuoverlo se è già disegnato. Ottieni le dimensioni dall'elemento di riferimento usando jQuery.
  5. Lega/catena la funzione Disegna sulla funzione di ridimensionare la finestra. Introdurre una deboa (timeout) a questa catena per assicurarci di ridisegnare solo dopo un timeout.

Ho anche aggiunto la sceneggiatura D3.JS minimizzata per la velocità. L'essenza è qui: https://gist.github.com/2414111

Codice back di riferimento jQuery:

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

Codice di deboa:

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

Divertiti!

Senza usare ViewBox

Ecco un esempio di una soluzione che non si basa sull'uso di viewBox:

La chiave è nell'aggiornamento del gamma del bilancia che vengono utilizzati per posizionare i dati.

Innanzitutto, calcola il tuo proporzione originale:

var ratio = width / height;

Quindi, su ogni ridimensionamento, aggiorna il range di x e y:

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

Si noti che l'altezza si basa sulla larghezza e sulle proporzioni, in modo da mantenere le proporzioni originali.

Infine, "ridisegnare" il grafico - aggiorna qualsiasi attributo che dipende da uno dei due x o y bilancia:

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

Si noti che nel ridimensionare il rects puoi usare la parte superiore del file range di y, piuttosto che usare esplicitamente l'altezza:

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

Se stai usando d3.js attraverso C3.JS La soluzione al problema della reattività è abbastanza semplice:

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

Dove appare l'HTML generato:

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

La risposta di Shawn Allen è stata fantastica. Ma potresti non voler farlo ogni volta. Se lo ospiti Vida.io, Responsabile automatico per la tua visualizzazione SVG.

Puoi ottenere iFrame reattivo con questo semplice codice incorporato:

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

Divulgazione: costruisco questa funzione a Vida.io.

Nel caso in cui stai usando un file D3 wrapper piace plottable.js, tieni presente che la soluzione più semplice potrebbe essere Aggiunta di un ascoltatore di eventi e poi chiamare un Ridisegna funzione (redraw in plottable.js). Nel caso di Plottable.js questo funzionerà in modo eccellente (questo approccio è scarsamente documentato):

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

Nel caso in cui le persone stiano ancora visitando questa domanda - ecco cosa ha funzionato per me:

  • Allegare l'iframe in un div e utilizzare CSS per aggiungere un imbottitura, per esempio, del 40% a quel div (la percentuale a seconda delle proporzioni desiderate). Quindi impostare sia la larghezza che l'altezza dell'iframe stesso al 100%.

  • Nel documento HTML contenente la tabella da caricare nell'iframe, impostare la larghezza della larghezza del div che l'SVG è aggiunto a (o alla larghezza del corpo) e impostare l'altezza su una larghezza * Proprietà.

  • Scrivi una funzione che ricarica il contenuto iFrame al ridimensionamento della finestra, in modo da adattare le dimensioni del grafico quando le persone ruotano il telefono.

Esempio qui sul mio sito web:http://dirkmjk.nl/en/2016/05/embedding-d3js-charts-responsive-website

Aggiornamento 30 dic 2016

L'approccio che ho descritto sopra ha alcuni svantaggi, in particolare che non tiene in considerazione l'altezza di qualsiasi titolo e didascalie che non fanno parte dell'SVG creato da D3. Da allora mi sono imbattuto in quello che penso sia un approccio migliore:

  • Impostare la larghezza del grafico D3 sulla larghezza del Div è allegata e usa il rapporto Aspect per impostare la sua altezza di conseguenza;
  • Chiedi alla pagina incorporata di inviare la sua altezza e URL alla pagina principale utilizzando il postMessage di HTML5;
  • Nella pagina principale, utilizzare l'URL per identificare l'iFrame corrispondente (utile se hai più di un IFRAME nella tua pagina) e aggiornare l'altezza all'altezza della pagina incorporata.

Esempio qui sul mio sito web: http://dirkmjk.nl/en/2016/12/embedding-d3js-charts-responsive-website-better-solution

Uno dei principi di base del Data-Join D3 è che è idonetante. In altre parole, se si valuta ripetutamente un equilibrio di dati con gli stessi dati, l'output rendering è lo stesso. Pertanto, fintanto che esegui il tuo grafico correttamente, prendendosi cura con le selezioni di invio, aggiornamento e uscita - tutto ciò che devi fare quando le dimensioni cambiano, viene rientrata nel grafico nella sua interezza.

Ci sono un paio di altre cose che dovresti fare, una è rimbalzare il gestore della finestra per accelerarlo. Inoltre, piuttosto che larghezze / altezze di codifica dura, questo dovrebbe essere ottenuto misurando l'elemento contenente.

In alternativa, ecco il tuo grafico reso utilizzato d3fc, che è un insieme di componenti D3 che gestiscono correttamente i join di dati. Ha anche un grafico cartesiano che lo misura contenente elementi rendendo facile creare grafici "reattivi":

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

Puoi vederlo in azione in questo codepen:

https://codepen.io/colineberhardt/pen/dobvoy

dove è possibile ridimensionare la finestra e verificare che il grafico sia eseguito correttamente.

Si prega di notare, come divulgazione completa, sono uno dei manutentori di D3FC.

Eviterei le soluzioni di ridimensionamento/zecca come la peste poiché sono inefficienti e possono causare problemi nella tua app (ad esempio un tiro-calcolo riesamina la posizione che dovrebbe apparire sul ridimensionamento della finestra, quindi un momento dopo il grafico si ridimensiona e la pagina è Layout e ora il tuo titoli è di nuovo sbagliato).

Puoi simulare questo comportamento in alcuni browser più vecchi che non lo supportano correttamente anche come IE11 usando un <canvas> Elemento che mantiene il suo aspetto.

Dato 960x540 che è un aspetto di 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>

Puoi anche usare Bootstrap 3 Per adattare le dimensioni di una visualizzazione. Ad esempio, possiamo impostare il Html Codice come:

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

Ho impostato un'altezza fissa a causa delle mie esigenze, ma puoi anche lasciare la dimensione automatica. Il "Col-SM-6 Col-MD-4" rende il Div reattivo per diversi dispositivi. Puoi saperne di più a http://getbootstrap.com/css/#grid-example-basic

Possiamo accedere al grafico con l'aiuto dell'ID Month View.

Non entrerò in dettaglio sul codice D3, insererò solo la parte necessaria per adattarsi a diverse dimensioni dello schermo.

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

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

La larghezza è impostata ottenendo la larghezza del Div con l'ID Month View.

L'altezza nel mio caso non dovrebbe includere l'intera area. Ho anche del testo sopra la barra, quindi devo anche calcolare quell'area. Ecco perché ho identificato l'area del testo con l'ID ResponsiveText. Per calcolare l'altezza consentita della barra, ho sottratto l'altezza del testo dall'altezza del div.

Ciò ti consente di avere una barra che adotterà tutte le diverse dimensioni dello schermo/div. Potrebbe non essere il modo migliore per farlo, ma sicuramente funziona per le esigenze del mio progetto.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top