Quelle est la meilleure façon de rendre réactive une mise en page de visualisation d3.js?

StackOverflow https://stackoverflow.com/questions/9400615

Question

Supposons que j'ai un script d'histogramme qui crée un graphique 960 500 svg.Comment rendre cela réactif pour redimensionner les largeurs et hauteurs graphiques sont dynamiques?

<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'essentiel de l'histogramme de l'exemple complet est: https://gist.github.com/993912

Était-ce utile?

La solution

Il existe une autre façon de faire cela qui ne nécessite pas de redessiner le graphique, et cela implique de modifier la viewBox et preserveAspectRatio sur l'élément <svg>:

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

Mise à jour du 24/11/15 : la plupart des navigateurs modernes peuvent déduire l'aspectratio des éléments SVG du viewBox, vous n'aurez donc peut-être pas besoin de garder la taille du graphique à jour.Si vous avez besoin de prendre en charge des navigateurs plus anciens, vous pouvez redimensionner votre élément lorsque la fenêtre se redimensionne comme suit:

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

Et le contenu du svg sera mis à l'échelle automatiquement.Vous pouvez voir un exemple fonctionnel de ceci (avec quelques modifications) ici : il suffit de redimensionner la fenêtre ou le volet inférieur droit pour voir commentil réagit.

Autres conseils

Recherchez «SVG réactif», il est assez simple de rendre un SVG réactif et vous n'avez plus à vous soucier des tailles.

Voici comment je l'ai fait:

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

Le code 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;
}

Plus d'informations / tutoriels:

http://demosthenes.info/blog/744/Make-SVG-Responsive

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

J'ai codé un petit résumé pour résoudre ce problème.

Le modèle de solution général est le suivant:

  1. Décomposez le script en fonctions de calcul et de dessin.
  2. Assurez-vous que la fonction de dessin dessine dynamiquement et est variables de largeur et de hauteur de visualisation (la meilleure façon de procéder est pour utiliser l'API d3.scale)
  3. Lier / chaîner le dessin à une référence élément dans le balisage. (J'ai utilisé jquery pour cela, donc je l'ai importé).
  4. N'oubliez pas de le supprimer s'il est déjà dessiné. Obtenez les dimensions de l'élément référencé à l'aide de jquery.
  5. Liez / chaînez la fonction de dessin à la fonction de redimensionnement de la fenêtre. Introduire un anti-rebond (timeout) à ceci chaîne pour nous assurer que nous ne redessinerons qu'après un délai d'expiration.

J'ai également ajouté le script minifié d3.js pour la vitesse. L'essentiel est ici: https://gist.github.com/2414111

jquery code retour de référence:

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

Code anti-rebond:

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

Amusez-vous bien!

Sans utiliser ViewBox

Voici un exemple de solution qui ne repose pas sur l'utilisation d'un viewBox:

La clé est de mettre à jour la plage des échelles qui sont utilisées pour placer les données.

Commencez par calculer votre rapport hauteur / largeur d'origine:

var ratio = width / height;

Ensuite, à chaque redimensionnement, mettez à jour le range de x et y:

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

Notez que la hauteur est basée sur la largeur et le rapport hauteur / largeur, afin que vos proportions d'origine soient conservées.

Enfin, «redessinez» le graphique - mettez à jour tout attribut qui dépend de l’échelle x ou y:

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

Notez qu'en redimensionnant le rects, vous pouvez utiliser la limite supérieure du range de y, plutôt que d'utiliser explicitement la hauteur:

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

Si vous utilisez d3.js via C3.js La solution au problème de la réactivité est assez simple:

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

où le HTML généré ressemble à:

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

La réponse de Shawn Allen était excellente.Mais vous ne voudrez peut-être pas faire cela à chaque fois.Si vous l'hébergez sur vida.io , vous obtenez une réponse automatique pour votre visualisation svg.

Vous pouvez obtenir une iframe réactive avec ce code d'intégration simple:

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

Divulgation: je crée cette fonctionnalité à l'adresse vida.io .

Si vous utilisez un wrapper d3 comme plottable.js ,sachez que la solution la plus simple peut être d'ajouter un écouteur d'événements , puis d'appeler une fonction redraw (redraw dans plottable.js).Dans le cas de plottable.js, cela fonctionnera parfaitement (cette approche est mal documentée):

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

Si les gens visitent encore cette question - voici ce qui a fonctionné pour moi:

  • enfermez l'iframe dans une DIV et utilisez CSS pour ajouter un remplissage de, par exemple, 40% à ce div (le pourcentage en fonction du rapport de format que vous souhaitez). Puis réglez la largeur et la hauteur de l'iframe lui-même à 100%.

  • dans la DOC HTML contenant le graphique à charger dans l'IFrame, définissez la largeur sur la largeur de la DIV que le SVG est ajouté à (ou à la largeur du corps) et définissez la hauteur de la largeur * ratio.

  • Écrivez une fonction qui recharge la teneur en iframe lors de la rédigation de la fenêtre, de manière à adapter la taille du graphique lorsque les gens tournent leur téléphone.

    Exemple ici sur mon site web: http://dirkmjk.nl/fr/2016/05/ Intégration-D3J-Graphiques-Responsif-Site Web

    Mise à jour 30 déc. 2016

    L'approche que j'ai décrite ci-dessus présente des inconvénients, notamment qu'elle ne prend pas la hauteur en tenant compte de tout titre et des légendes qui ne font pas partie du SVG créé par D3. Depuis que je suis tombé sur ce que je pense est une meilleure approche:

    • Définissez la largeur du graphique D3 sur la largeur de la DIV, elle est attachée et utilisez le rapport de format pour définir sa hauteur en conséquence;
    • Demandez à la page intégrée envoi de sa hauteur et de sa URL à la page parent à l'aide de la postMessage de HTML5;
    • sur la page parent, utilisez l'URL pour identifier l'iframe correspondant (utile si vous avez plus d'une iframe sur votre page) et mettez à jour sa hauteur à la hauteur de la page intégrée.

      Exemple ici sur mon site Web: HTTP : //dirkmjk.nl/fr/2016/12/MedDingding-D3js-chatts-responsive-website-better-solution

L'un des principes de base de la jointure de données D3 est qu'elle est idempotente. En d'autres termes, si vous évaluez à plusieurs reprises une jointure de données avec les mêmes données, la sortie rendue est la même. Par conséquent, tant que vous effectuez le rendu de votre graphique correctement, en prenant soin de vos sélections d'entrée, de mise à jour et de sortie - tout ce que vous avez à faire lorsque la taille change, est de restituer le graphique dans son intégralité.

Il y a deux autres choses que vous devriez faire, l'une est de dé-rebondir le gestionnaire de redimensionnement de fenêtre afin de l'étrangler. De plus, plutôt que de coder en dur les largeurs / hauteurs, cela devrait être réalisé en mesurant l'élément contenant.

Comme alternative, voici votre graphique rendu à l'aide de d3fc , qui est un ensemble de composants D3 qui gèrent correctement les données -joint. Il a également un graphique cartésien qui mesure l'élément contenant l'élément, ce qui facilite la création de graphiques `` réactifs '':

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

Vous pouvez le voir en action dans ce codepen:

https://codepen.io/ColinEberhardt/pen/dOBvOy

où vous pouvez redimensionner la fenêtre et vérifier que le graphique est correctement re-rendu.

Veuillez noter que, en guise de divulgation complète, je suis l'un des mainteneurs de d3fc.

J'éviterais les solutions de redimensionnement / cocher comme la peste car elles sont inefficaces et peuvent causer des problèmes dans votre application (par exemple, une info-bulle recalcule la position à laquelle elle devrait apparaître lors du redimensionnement de la fenêtre, puis un instant plus tard, votre graphique se redimensionne également et leremises en page et maintenant votre info-bulle est à nouveau erronée).

Vous pouvez simuler ce comportement dans certains navigateurs plus anciens qui ne le prennent pas correctement en charge comme IE11 également en utilisant un élément <canvas> qui conserve son aspect.

Étant donné 960x540 qui est un aspect de 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>

Vous pouvez également utiliser bootstrap 3 pour adapter la taille d'une visualisation. Par exemple, nous pouvons configurer le code HTML comme suit:

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

J'ai défini une hauteur fixe en raison de mes besoins, mais vous pouvez également laisser la taille automatique. Le "col-sm-6 col-md-4" rend le div réactif pour différents appareils. Vous pouvez en savoir plus sur http://getbootstrap.com/css/#grid-example-basic

Nous pouvons accéder au graphique à l'aide de l'ID vue mensuelle .

Je n'entrerai pas dans les détails du code d3, je n'entrerai que la partie nécessaire pour s'adapter à différentes tailles d'écran.

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

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

La largeur est définie en obtenant la largeur du div avec l'id month-view.

La hauteur dans mon cas ne doit pas inclure toute la zone. J'ai également du texte au-dessus de la barre, donc je dois également calculer cette zone. C'est pourquoi j'ai identifié la zone du texte avec l'id responsivetext. Pour calculer la hauteur autorisée de la barre, j'ai soustrait la hauteur du texte de la hauteur du div.

Cela vous permet d'avoir une barre qui adoptera toutes les différentes tailles d'écran / div. Ce n'est peut-être pas la meilleure façon de le faire, mais cela fonctionne sûrement pour les besoins de mon projet.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top