Introduction à [Link] et aux documents pilotés pa... [Link]
Miximum
Le blog d'un ingénieur Web freelance.
Découvrez mon nouveau projet : [Link], un cours de théorie
musicale complet, accessible et pas barbant.
Introduction à [Link] et aux documents pilotés par
les données
17 janvier 2014 Tutoriel
La sympathique [Link] est une bibliothèque qui permet de manipuler
1 sur 13 02/05/2020 à 19:39
Introduction à [Link] et aux documents pilotés pa... [Link]
des documents à partir de jeux de données de manière très puissante
et élégante. Elle est particulièrement adaptée à tout projet de
présentation graphique de données, mais pas que.
Pour vous faire une idée des possibilités de la bête, je vous encourage à
jeter un œil sur la liste d'exemples qui en devient presque ridicule tant
elle est immense. Impressionnant, n'est-ce pas ?
À quoi sert D3 et comment fonctionne-t-elle (car j'ai décidé qu'elle
serait féminine) ? Amusons-nous avec pour le découvrir.
Avertissement : si votre navigateur ne supporte pas svg, vous ne
pourrez pas profiter des chatoyants exemples de ce billet. Tant pis pour
vous.
Qu'est-ce que [Link] ?
Pour faire simple, D3 permet de transformer un document en fonction de données
que vous lui passez. D3 permet donc de représenter des données en utilisant les
standards du Web (SVG, HTML, CSS, Javascript). C'est en fait un outil de
transformation, non de présentation. D'ailleurs, « D3 » vient de « Data driven
documents ».
Prenons un exemple basique : imaginons que je dispose d'une API qui renvoie un
nombre aléatoire de paragraphes de faux texte (lorem ipsum…). Dans le cadre de la
construction d'un générateur de faux-texte minimaliste et efficace, je veux afficher
ces paragraphes dans des balises <p> dans une page HTML.
Accrochez-vous à votre siège et jetez un rapide coup d'œil à ce code que je me suis
2 sur 13 02/05/2020 à 19:39
Introduction à [Link] et aux documents pilotés pa... [Link]
forcé à commettre :
<div id="lorem-container"></div>
<script>
var container = $('#lorem-container');
[Link]('');
var data = ['bla bla bla', 'gloubigoulba', 'schtroumpf'];
$.each(data, function(paragraph) {
var p = $('');
[Link](paragraph);
[Link](p);
});
Ouh ! Bouh ! Comme c'est moche ! Et encore, je vous laisse imaginer la tronche de ce
code sans le sucre syntaxique offert par jQuery.
Voici maintenant la version D3 :
<div id="lorem-container"></div>
<script>
var data = ['bla bla bla', 'gloubigoulba', 'schtroumpf'];
var paragraphs = [Link]('#lorem-container').selectAll('p')
.data(data)
.enter()
.append('p')
.text(function(d) { return d; });
Ce code est confondant d'élégance et de simplicité, même s'il mérite une petite
explication.
3 sur 13 02/05/2020 à 19:39
Introduction à [Link] et aux documents pilotés pa... [Link]
Comprendre les sélections de D3
Déroulons le code précédent pas à pas pour en comprendre le fonctionnement.
var paragraphs = [Link]('#container').selectAll('p')
Les méthodes `select` et `selectAll` permettent de retourner une sélection d'éléments,
un peu à la manière de jQuery. On peut très facilement affecter des propriétés à tous
les éléments de la sélection d'un coup, par exemple :
var paragraphs = [Link]('#container').selectAll('p')
.style('background-color', 'black')
.text('initial text');
Ok, mais dans le cas présent, il n'existe pas d'éléments « p » dans mon document,
donc ma sélection est vide ? Oui, lisez la suite pour comprendre.
.data(data)
La fonction `data` permet de coupler une sélection d'éléments à un jeu de données,
c'est à dire que chaque entrée du tableau `data` aura une correspondance directe avec
un et un seul élément de la sélection. Certes, mais que se passe-t-il si le nombre
d'éléments de la sélection ne correspond pas à la quantité de données (c'est le cas ici,
notre sélection est toujours vide) ? Ou si des données disparaissent du tableau et que
d'autres y sont insérées entre deux mises à jour ?
En fait, cette opération va transformer notre unique sélection d'éléments en trois
sélections distinctes, portant les petits noms suivants :
4 sur 13 02/05/2020 à 19:39
Introduction à [Link] et aux documents pilotés pa... [Link]
1. update : les éléments de la sélection initiale qui sont liés à une entrée du tableau
`data` ;
2. enter : les éléments du tableau `data` « en trop » qui ne correspondent à aucun
nœud dans le DOM ;
3. exit : les éléments de la sélection qui étaient liés à une donnée qui a maintenant
disparu ;
D3 nous permet donc de répondre à trois questions :
1. Que fait-on des éléments déjà présent dans le document alors qu'ils représentent
des données qui ont peut-être été mises à jour ?
2. Que fait-on lorsque l'on reçoit de nouvelles données ?
3. Que fait-on des nœuds qui ne servent plus à rien ?
Dans notre exemple, notre fonction n'est appelée qu'une fois, un seul cas de figure va
se présenter. Poursuivons notre analyse.
.enter()
La fonction `enter` nous permet de travailler sur la sélection correspondante, i.e les
données sans nœud correspondant.
.append('p')
Pour chaque entrée du tableau `data`, on créé un nouveau nœud <p>
.text(function(d) { return d; });
5 sur 13 02/05/2020 à 19:39
Introduction à [Link] et aux documents pilotés pa... [Link]
Et pour chacun de ces nouveaux éléments, on déclare que le texte correspondra au
datum (entrée du tableau `data`) correspondant.
Pfiou !
Travailler avec les sélections
Si vous êtes comme moi, il vous faudra sans doute relire la section précédente trois
ou quatre fois avant de bien comprendre de quoi il retourne. Voici un exemple plus
complet qui fait intervenir les trois sélections. C'est très exactement ce code qui est
utilisé sur mon générateur de Lorem ipsum.
// Chaque fois que je clique sur un bouton, je récupère de nouveaux
// paragraphes de faux textes, que je veux afficher à la place
// des précédents.
function update(data) {
var paragraphs = [Link]('#text').selectAll('p')
.data(data);
// On créé les éléments "p" pour les nouvelles données
[Link]()
.append('p');
// `selectAll` renvoie la section "update"
// On configure le texte des nœuds
[Link](function (d) { return d; });
// On supprime les nœuds qui n'ont plus lieu d'être
[Link]().remove();
}
6 sur 13 02/05/2020 à 19:39
Introduction à [Link] et aux documents pilotés pa... [Link]
Et sinon, dans la vraie vie ?
J'avoue qu'utiliser D3 pour un besoin aussi simple, c'est un peu comme sortir les
Canadair pour éteindre une allumette. Mais si vous avez compris les sélections, vous
avez compris D3. Pour autant, c'est loin d'être la seule fonctionnalité de l'outil, et l'API
est longue comme le bras. Outils mathématique, gestion du temps, helpers
graphiques, chargement de ressources distantes, cartographie, géométrie, etc. Il-y-a
de quoi s'amuser.
Voici un exemple un peu plus complet. Nous souhaitons par exemple visualiser des
données économiques et sociales sur les pays émergents, même si pour faciliter la
démonstration nous nous contenterons de données générées aléatoirement.
10
20
30
40
50
60
70
80
90
100
0 10 20 30 40 50 60 70 80 90 100
Update data
Voici le code de cette petite merveille.
7 sur 13 02/05/2020 à 19:39
Introduction à [Link] et aux documents pilotés pa... [Link]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Data</title>
<style>
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<div id="chart" style="width: 600px; height: 400px;"></div>
<button id="update-data">Update data</button>
<script src="[Link]"></script>
<script src="[Link]"></script>
</body>
</html>
Et [Link]. Je vous préviens, ce n'est pas du très beau js, mais il est tard et j'ai la
flemme.
8 sur 13 02/05/2020 à 19:39
Introduction à [Link] et aux documents pilotés pa... [Link]
// Convention D3 pour gérer la marge du graphique
var margin = { top: 20, right: 20, bottom: 30, left: 40 },
width = 600 - [Link] - [Link],
height = 400 - [Link] - [Link];
// D3 nous propose des outils pour gérer les échelles de valeurs de n
// Ici, nos données seront réparties sur un intervalle continu.
// Nous savons que nos données d'entrées sont comprises entre 0 et 10
// En sortie, nous les étalerons sur l'axe des abscisses soit la larg
// du graphe.
var x = [Link]()
.domain([0, 100]) // domaine d'entrée
.range([0, width]); // domaine de sortie
// Même chose pour l'axe des ordonnées.
// Notez qu'en SVG, la coordonnée (0, 0) se trouve en haut à gauche
var y = [Link]()
.range([0, height])
.domain([0, 100]);
var r = [Link]()
.range([10, 50])
.domain([0, 10]);
// Helper qui renvoie des couleurs
// pour un rendu sympathique
var color = [Link].category20();
// Nous créons l'élément SVG nécessaire au rendu du graphique.
9 sur 13 02/05/2020 à 19:39
Introduction à [Link] et aux documents pilotés pa... [Link]
// Et hop ! direct dans le DOM.
var chart = [Link]('#chart').append('svg')
.attr("width", width + [Link] + [Link])
.attr("height", height + [Link] + [Link])
.append("g")
.attr("transform", "translate(" + [Link] + "," + margin.
// Que serait un graphique sans axes ?
// L'api est généreuse et contient tout ce qu'il faut
// pour produire un rendu élégant
var xAxis = [Link]()
.scale(x)
.orient("bottom");
[Link]("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var yAxis = [Link]()
.scale(y)
.orient("left");
[Link]("g")
.attr("class", "y axis")
.call(yAxis);
/*
* Notre fonction de génération de données aléatoires
* Retourne un résultat de la forme suivante :
* [
* { x: 10, y: 55, r: 5},
10 sur 13 02/05/2020 à 19:39
Introduction à [Link] et aux documents pilotés pa... [Link]
* { x: 87, y: 42, r: 2},
* …
* ]
*/
function getData() {
var _randomData = function() {
return {
x: [Link]([Link]() * 100),
y: [Link]([Link]() * 100),
r: [Link]([Link]() * 10),
};
};
var nbData = [Link]([Link]() * 8) + 3;
return [Link](nbData).map(_randomData);
};
/*
* C'est ici que nous allons afficher nos données sous
* forme de cercles chamarés et chatoyants.
*
* Le paramètre data est bien évidemment un tableau d'objets
*/
function redraw(data) {
// Créé la sélection initiale, et bind les données
var circles = [Link]('circle')
.attr("class", "chart")
.data(data);
// Lorsque l'on créé les nœuds, on les place directement au coord
11 sur 13 02/05/2020 à 19:39
Introduction à [Link] et aux documents pilotés pa... [Link]
// correctes, mais avec un rayon de 0, ce qui permettra une
// animation des plus primesautières.
[Link]().append('circle')
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return height - y(d.y); })
.attr("r", 0);
// Idem, lorsqu'une donnée n'existe plus, on fait disparaître le
// cercle correspondant en réduisant élégamment son rayon à 0
[Link]()
.transition()
.duration(750)
.attr("r", 0)
.remove();
// Voici maintenant le traitement effectués sur les nœuds liés à
// des données existantes. Notez que les nœuds de la sélection `e
// seront également concernés ici.
circles
.attr("fill", function(d, i) { return color(i); })
.transition()
.duration(750)
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return height - y(d.y); })
.attr("r", function(d) { return r(d.r); });
};
var updateButton = [Link]('update-data');
[Link]('click', function() {
data = getData();
redraw(data);
12 sur 13 02/05/2020 à 19:39
Introduction à [Link] et aux documents pilotés pa... [Link]
});
data = getData();
redraw(data);
Voilà pour l'exemple un peu plus complet. Ce n'est qu'un tout petit aperçu de ce qu'il
est possible de faire avec [Link]. Je laisse le reste à votre imagination fertile. Et bonne
année !
Vous aimez ce billet ? Partagez-le !
Twitter Facebook Reddit Linkedin Tumblr
Accueil Blog Photos Prestations Twitter
© Thibault Jouannic
13 sur 13 02/05/2020 à 19:39