web-dev-qa-db-fra.com

Quel est le meilleur moyen de rendre une présentation de visualisation d3.js adaptée?

Supposons que j'ai un script d'histogramme qui construit un graphique de 960 500 svg. Comment puis-je rendre cette réponse sensible afin de redimensionner les graphiques et les hauteurs 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> 

Exemple d'histogramme complet Gist est: https://Gist.github.com/993912

207
Matt Alcock

Il existe une autre façon de faire cela qui ne nécessite pas de redessiner le graphique, et implique de modifier les attributs 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 le rapport de format des éléments SVG à partir de la viewBox, de sorte que vous n'aurez peut-être pas besoin de maintenir la taille du graphique à jour. Si vous devez prendre en charge des navigateurs plus anciens, vous pouvez redimensionner votre élément lorsque la fenêtre est redimensionnée de la manière suivante:

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 de svg sera mis à l'échelle automatiquement. Vous pouvez en voir un exemple de travail (avec quelques modifications) ici : redimensionnez simplement la fenêtre ou le volet en bas à droite pour voir comment il réagit.

296
Shawn Allen

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

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'infos/tutoriels: 

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

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

29
cminatti

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

La solution générale est la suivante:

  1. Divisez le script en fonctions de calcul et de dessin. 
  2. Assurez-vous que la fonction de dessin dessine de manière dynamique et qu'elle est guidée par les variables de largeur et de hauteur de visualisation. 
  3. Liez/chaînez le dessin à un élément référence Dans le balisage. (J'ai utilisé jquery pour cela, je l'ai donc importé).
  4. N'oubliez pas de l'enlever si c'est déjà dessiné. Obtenez les dimensions de L'élément référencé à l'aide de jquery. 
  5. Lier/chaîner la fonction de dessin à La fonction de redimensionnement de la fenêtre. Introduisez un anti-rebond (délai d'attente) dans cette chaîne Afin de garantir que nous ne redessinons qu'après un délai d'attente 

J'ai également ajouté le script minified d3.js pour la vitesse . Le Gist est ici: https://Gist.github.com/2414111

code retour jquery:

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

Prendre plaisir!

20
Matt Alcock

Sans utiliser ViewBox

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

La clé consiste à mettre à jour la plage des échelles qui servent à placer des données. 

Tout d’abord, calculez votre format d’image original:

var ratio = width / height;

Ensuite, à chaque redimensionnement, mettez à jour les 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 de format, de sorte que les proportions d'origine sont conservées.

Enfin, "redessinez" le graphique - mettez à jour tout attribut dépendant 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 la rects, vous pouvez utiliser la limite supérieure de la range de y au lieu 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>

4
Paul Murray

Si vous utilisez d3.js via c3.js , la solution au problème de 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>
3
vanna

Dans le cas où vous utilisez un wrapper d3 comme plottable.js , sachez que la solution la plus simple pourrait être ajouter un écouteur d'événement puis appeler une fonction redraw ( redraw in plottable.js). Dans le cas de plottable.js, cela fonctionnera à merveille (cette approche est mal documentée):

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

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 l'iframe responsive 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 construis cette fonctionnalité à vida.io .

2
Phuoc Do

Si les gens reviennent encore sur cette question, voici ce qui a fonctionné pour moi:

  • Placez l'iframe dans une div et utilisez css pour ajouter un remplissage de, disons, 40% à cette div (le pourcentage dépendant du rapport de format souhaité). Définissez ensuite la largeur et la hauteur de l'iframe lui-même sur 100%.

  • Dans le document html contenant le graphique à charger dans l'iframe, définissez width sur la largeur du div auquel le svg est ajouté (ou sur la largeur du corps) et définissez height sur width * ratio.

  • Ecrivez une fonction qui recharge le contenu de l'iframe lors du redimensionnement de la fenêtre, de manière à adapter la taille du graphique lorsque les utilisateurs font pivoter leur téléphone. 

Exemple ici sur mon site web: http://dirkmjk.nl/en/2016/05/embedding-d3js-charts-responsive-website

UPDATE 30 déc 2016

L’approche que j’ai décrite ci-dessus présente certains inconvénients, notamment le fait qu’elle ne prend pas en compte la hauteur des titres et des légendes qui ne font pas partie du svg créé par D3. J'ai depuis rencontré ce que je pense être une meilleure approche:

  • Définissez la largeur du diagramme D3 sur celle du div auquel il est attaché et utilisez le rapport de format pour définir sa hauteur en conséquence;
  • Demandez à la page incorporée d’envoyer sa hauteur et son URL à la page parente à l’aide de postMessage de HTML5;
  • Sur la page parent, utilisez l'URL pour identifier l'iframe correspondant (utile si vous avez plusieurs iframe sur votre page) et mettez à jour sa hauteur à la hauteur de la page incorporée.

Exemple ici sur mon site Web: http://dirkmjk.nl/en/2016/12/embedding-d3js-charts-responsive-website-better-solution

1
dirkmjk

L'un des principes de base de la jointure de données D3 est qu'il est idempotent. En d'autres termes, si vous évaluez de manière répétée une jointure de données avec les mêmes données, la sortie rendue est la même. Par conséquent, tant que vous restituez correctement votre graphique, en prenant soin de votre sélection d'entrée, de mise à jour et de sortie, tout ce que vous avez à faire lorsque la taille change est de re-rendre le graphique dans son intégralité.

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

En guise d'alternative, voici votre diagramme rendu avec d3fc , qui est un ensemble de composants D3 qui gèrent correctement les jointures de données. Il possède également un graphique cartésien qui le mesure et contient des éléments facilitant 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 restitué.

Veuillez noter que, en tant que divulgation complète, je suis l’un des responsables de d3fc.

0
ColinE

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 à afficher dans le redimensionnement de la fenêtre, puis un instant plus tard, votre graphique mises en page et maintenant votre info-bulle est à nouveau faux).

Vous pouvez simuler ce comportement dans certains navigateurs plus anciens qui ne le prennent pas correctement en charge, comme IE11, à l'aide d'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>
0
Dominic