13 - Faire un graphique à barres


Lien vers le tutoriel original : http://alignedleft.com/tutorials/d3/making-a-bar-chart


Faire un graphique à barres

Dernière mise à jour le 30 décembre 2012

Intégrons maintenant tout ce que l’on a appris jusqu’ici pour générer un graphique à barres simple avec D3.

On commencera par revoir le graphique à barres que l’on a fait plutôt en utilisant des éléments div. Puis on adaptera ce code pour dessiner les barres avec SVG, nous donnant plus de flexibilité sur la présentation visuelle. Finalement, on ajoutera des étiquettes (labels), pour voir plus clairement les valeurs.

Le vieux graphique

Voilà ce que l’on avait la dernière fois, avec des nouvelles données.

var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
                11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];

d3.select("body").selectAll("div")
    .data(dataset)
    .enter()
    .append("div")
    .attr("class", "bar")
    .style("height", function(d) {
        var barHeight = d * 5;
        return barHeight + "px";
    });

Graphique à barres avec des divs

Ça peut être difficile à imaginer, mais on peut clairement améliorer ce simpe graphique à barres fait avec des divs.

Le nouveau graphique

En premier, on doit définir la taille du nouveau SVG :

// Largeur et hauteur
var w = 500;
var h = 100;

(Bien sûr, vous pourriez appeler w et h d’une autre manière, comme svgWidth et svgHeight. Utilisez ce qui vous semble le plus clair. JavaScript a une culture de l’efficacité, donc vous verrez souvent des variables à un seul caractère, du code écrit sans espaces, et d’autres syntaxes difficiles à lire, et pourtant programmatiquement efficace.)

Ensuite, on dit à D3 de créer un élément SVG vide et on l’ajoute au DOM :

// Crée l'élément SVG
var svg = d3.select("body")
            .append("svg")
            .attr("width", w)
            .attr("height", h);

Ça insert un nouvel élément <svg> juste avant la balise fermante </body>, et assigne au SVG les dimensions de 500 par 100 pixels. Cette déclaration met également le résultat dans une nouvelle variable appelée svg, pour que nous puissions facilement référencer SVG sans avoir à le resélectionner plus tard en utilisant quelque chose comme d3.select(“svg”).

Ensuite, plutôt que de créer des divs, on génère des rects et on les ajoute à svg.

svg.selectAll("rect")
   .data(dataset)
   .enter()
   .append("rect")
   .attr("x", 0)
   .attr("y", 0)
   .attr("width", 20)
   .attr("height", 100);

Ce code sélectionne tous les rects à l’intérieur de svg. Bien sûr, il n’y en a encore aucun, donc une sélection vide est retournée. (Bizarre, je vous l’accorde, mais restez avec moi. Avec D3, vous devez toujours sélectionner quoi que ce soit sur lequel vous voulez travailler, même si cette sélection est vide.)

Ensuite, data(dataset) voit que l’on a 20 valeurs dans l’ensemble de données, donc il appelle enter() 20 fois. enter(), à son tour, retourne une sélection placeholder pour chaque élément de données qui n’a pas de rect correspondant — ce qui veut dire tous les rects.

Pour chacun des 20 placeholders, append(“rect”) insert un rect dans le DOM. Comme nous l’avons appris dans l’introduction à SVG, chaque rect doit avoir les valeurs x, y, width, et height définies. On utilise attr() pour ajouter ces attributs sur chacun des rect nouvellement créés.

C’est beau, hein ?

Une barre solitaire

Okay, pas sûr. Toutes les barres sont présentes (regardez le DOM sur la page de démo avec votre inspecteur web), mais toutes partagent les mêmes valeurs de x, y, width, et height, avec pour résultat qu’elles se superposent toutes. Ce n’est pas encore de la visualisation de données.

Corrigeons d’abord le fait qu’elles se superposent. Plutôt que d’utiliser un x de zéro, on lui assignera une valeur dynamique qui correspond à i, la position de chaque élément dans l’ensemble de données. Donc la première barre sera à zéro, mais les suivantes seront à 21, puis 42, et ainsi de suite.

.attr("x", function(d, i) {
    return i * 21;  // Largeur de barre de 20 plus 1 pour la marge
})

Vingt barres

Voilà le code en action.

Ça fonctionne, mais c’est pas super flexible. Si nos données étaient plus nombreuses, les barres se suivraient sur la droite, même après la fin du SVG ! Comme chaque barre fait 20 pixels de large, plus 1 pixel de marge, un SVG de 500 pixels de large ne pourrait afficher que 23 éléments de données. Notez comment la 24ème barre se trouve coupée :

Vingt-quatre barres

Une bonne pratique est d’utiliser des coordonnées flexibles et dynamiques — hauteurs, largeurs, valeurs de x, et valeurs de y — de manière à ce que votre visualisation se redimensionne de manière appropriée en fonction de vos données.

Comme avec n’importe quel problème en programmation, il y a mille façons d’arriver à ce résultat. J’en utiliserai une simple. Premièrement, je vais changer la ligne où l’on définit la position x de chaque barre :

.attr("x", function(d, i) {
    return i * (w / dataset.length);
})

Notez comment la valeur de x est maintenant liée directement à la largeur de SVG (w) et au nombre de valeurs dans notre ensemble de données (dataset.length). C’est excitant, car maintenant nos barres seront espacées uniformément, que l’on ait vingt barres :

Vingt barres espacées uniformément

ou juste cinq :

Cinq barres espacées uniformément

Voilà le code jusqu’ici.

Maintenant on devrait rendre les largeurs des barres proportionnelles, elles aussi, pour qu’elles deviennent plus fines à mesure que des données sont ajoutées, ou plus large lorsqu’il y a moins de valeurs. Je vais ajouter une nouvelle variable à côté de là où l’on définit la hauteur et la largeur de SVG

// Largeur et hauteur
var w = 500;
var h = 100;
var barPadding = 1;  // <-- Nouveau !

puis faire référence à cette variable dans la ligne où l’on définit la largeur width de chaque barre. Plutôt qu’une valeur statique de 20, la largeur sera maintenant définit comme une fraction entre la largeur du SVG et le nombre de données, moins la valeur de la marge :

.attr("width", w / dataset.length - barPadding)

Vingt barres espacées avec des largeurs dynamiques

Ça fonctionne ! Les largeurs et les positions en x des barres se mettent à l’échelle correctement qu’il y ait vingt points, juste cinq

Cinq barres espacées avec des largeurs dynamiques

ou même cent :

Cent barres espacées avec des largeurs dynamiques

Pour finir, programmons la hauteur de chaque barre. Vous espériez que ce soit aussi facile que faire référence à la valeur d lorsque l’on définit la hauteur height :

.attr("height", function(d) {
    return d;
});

Hauteurs dynamiques

Hum, ça à l’air funky. On pourrait peut-être agrandir un peu nos nombres ?

.attr("height", function(d) {
    return d * 4;  // <-- Fois quatre !
});

Hauteurs dynamiques

Hélas, c’est pas aussi facile — on veut que nos barres grandissent depuis le bord bas, pas depuis le haut — mais ne blamez pas D3, blamez SVG.

Rappellez-vous de l’introduction à SVG : lorsque l’on dessine des rects, les valeurs x et y spécifient les coordonnées du coin en haut à gauche. Ce qui veut dire que l’origine ou point de référence de chaque rect est son point supérieur gauche. Pour nos besoins, il serait teeeeelllement plus facile que notre point de référence soit le point inférieur gauche, mais c’est juste pas comme ça que SVG fonctionne, et, franchement, SVG est plutôt indifférent à vos sentiments en la matière.

Étant donné que nos barres doivent “grandir depuis le haut”, où est “le haut” de chaque barre par rapport au haut de SVG ? Eh bien, le haut de chaque barre peut être définit comme une relation entre la hauteur de SVG et la valeur correspondante de la donnée, comme ça :

.attr("y", function(d) {
    return h - d;  // Hauteur moins la valeur de la donnée
})

Ensuite, pour mettre le “bas” de chaque barre au bas du SVG, chaque hauteur des rects doit être juste la valeur de la donnée elle-même :

.attr("height", function(d) {
    return d;  // Juste la valeur de la donnée
});

Grandir à partir du bas

Agrandissons le tout un peu en changeant d par d * 4. (Note : Plus tard on en apprendra plus sur les échelles de D3, qui offrent de meilleurs moyens d’accomplir cela.)

Grandir plus à partir du bas

Voilà le code de notre graphique où nos barres grandissent du bas.

Couleurs

Ajouter une couleur est facile. Utilisez juste attr() pour définir un remplissage fill :

.attr("fill", "teal");

Teal correspond à la couleur appelée sarcelle

Barres de couleur sarcelle

Voila un graphique à barres tout en sarcelle. Mais souvent vous voudrez ajuster la couleur de la forme pour refléter une des qualités de la donnée. Ce qui veut dire que vous voudrez programmer la donnée en couleur. (Pour notre graphique, ça donne une programmation double, dans laquelle la même valeur de donnée est programmée dans deux propriétés visuelles : la hauteur et la couleur.)

Utiliser une donnée pour définir une couleur revient à écrire une fonction qui elle aussi référence d. Ici nous remplaçons “teal” par une fonction personnalisée :

.attr("fill", function(d) {
    return "rgb(0, 0, " + (d * 10) + ")";
});

Barres bleues pilotées par des données

Voilà le code. C’est pas particulièrement un codage visuel utile, mais au moins vous avez une idée de comment traduire la donnée en couleur. Ici, d est multiplé par 10, puis il est utilisé dans la valeur du bleu dans une définition de couleur rgb(). Plus grande sera la valeur de d (barres plus grandes), plus bleue la barre sera. Pour des valeurs plus petites de d (barres plus petites) les barres seront moins bleues (plus proche du noir).

Étiquettes (ou labels)

Les visuels sont supers, mais quelques fois vous aurez besoin d’afficher les valeurs des données en texte dans votre visualisation. C’est la que les étiquettes de valeur entrent en jeu, et elles sont vraiment, vraiment faciles à générer avec D3.

Vous vous rappelez de l’introduction à SVG : vous pouvez ajouter des éléments text à l’élément SVG. On débutera avec :

svg.selectAll("text")
   .data(dataset)
   .enter()
   .append("text")

Ça semble familier ? Tout comme on a fait pour les rects, on fait de même pour les texts. D’abord, sélectionnez ce que vous voulez, amenez les données, ajoutez les nouveaux éléments avec enter() (qui sont juste des placeholders à ce moment), et finallement ajoutez les nouveaux éléments text elements au DOM.

Complétons ce code pour inclure la valeur de la donnée dans chaque élément de texte en utilisant la méthode text()

.text(function(d) {
        return d;
   })

et définissons les valeurs x et y pour positionner le texte. C’est plus facile si je copie/colle juste le même code que l’on a utilisé plus haut pour les barres :

.attr("x", function(d, i) {
        return i * (w / dataset.length);
   })
   .attr("y", function(d) {
        return h - (d * 4);
   });

Bébés étiquettes !

Aha ! Des étiquettes ! Mais certaines sont coupées en haut. Essayons de les déplacer vers le bas, à l’intérieur des barres, en ajoutant quelques pixels à x et y :

.attr("x", function(d, i) {
        return i * (w / dataset.length) + 5;  // +5
   })
   .attr("y", function(d) {
        return h - (d * 4) + 15;              // +15
   });

Étiquettes dans les barres

Mieux, mais pas lisible. Heureusement, on peut corriger ça :

.attr("font-family", "sans-serif")
   .attr("font-size", "11px")
   .attr("fill", "white");

Des étiquettes vraiment belles

Fantasti-code ! Si vous n’êtes pas un maniaque de typographie, alors ça suffit. Si, au contraire, vous êtes comme moi, vous noterez que les étiquettes ne sont pas parfaitement alignées dans leur barre. C’est assez facile de corriger ça. Utilisons l’attribut SVG text-anchor pour centrer le texte horizontalement par rapport à x :

.attr("text-anchor", "middle")

Ensuite, changeons la manière de calculer la position x en la définissant comme le côté gauche de chaque barre plus la moitié de la largeur de la barre :

.attr("x", function(d, i) {
        return i * (w / dataset.length) + (w / dataset.length - barPadding) / 2;
    })

Montons également les étiquettes d’un pixel pour un espacement parfait :

.attr("y", function(d) {
        return h - (d * 4) + 14;  // 15 vaut est maintenant 14
    })

Étiquettes centrées

Fini ! Maintenant, éloignons-nous un peu des graphiques à barres.