D3.js: les essentiels, partie 1

Un pense-bête autant qu'un tutoriel

Bienvenue.

Cette page a été réalisée avec les notes prises lors du visionnage des excellents tutoriels sur D3 par l'excellent Vienno, consultables (et je vous y encourage) sur Youtube.

Côté pré-requis: il est plutôt recommandé d'avoir une idée de CSS, de bien comprendre comment fonctionne une page web (les éléments du DOM, principalement). Vous trouverez d'abord le code et en dessous, si nécessaire, le résultat en D3.

Pour commencer

Insérer une version stable de D3.js dans la balise

< head >
de votre page html. Par exemple:
< script src="http://d3js.org/d3.v3.min.js"> </script >

et dans le corps de votre page html, entourez votre futur code la balise

<script>

</script>

Comment créer un lieu d'affichage pour vos graphiques?

Dans ce tutoriel, on présentera les graphiques dans une le corps d'une page html. Si vous reproduisez les exemples, partez donc une page html, contenant les balises <body> et </body> au sein de laquelle vous insérerez le code indiqué ensuite.

var canvas=d3.select("body")
		.append("svg")
		.attr("width",100).attr("height",100);

Si rien ne s'affiche dans la console, on aperçoit un svg dans la console du navigateur, aux bonnes dimensions. Pour vérifier cela, cliquer droit sur votre page web, puis "examiner l'élément" et "inspecteur".

Comment créer un cercle, un rectangle, une ligne?

Pour créer un cercle, on a besoin de lui donner un positionnement horizontal ("cx"), un positionnement vertical ("cy"), un rayon ("r") et une couleur ("fill").

var canvas=d3.select("body")
		.append("svg")
		.attr("width",140).attr("height",80);
var circle= canvas.append("circle") 
		.attr("cx", 60).attr("cy",50)
		.attr("r", 20)
		.attr("fill", "red");

Nous avons donc à présent un cercle rouge qui est apparu. Pour créer un rectangle, la procédure est très similaire, sauf que l'on a besoin d'une largeur ("width") et d'une hauteur ("height"), et pour son positionnement, on ne parle plus de son centre mais du coin en haut à gauche dont les coordonnées seront ("x") et ("y").

Pour montrer une autre notation possible, les coordonnées nécessaires pour la construction de la ligne sont indiquées différemment, au sein de { }.

On crée également une ligne: qui part d'un point aux coordonnées "x1", "y1" pour arriver à "x2" "y2".

var canvas=d3.select("body")
		.append("svg")
		.attr("width",500).attr("height",80);
var rectangle= canvas.append("rect") 
		.attr("x", 30).attr("y",10)
		.attr("width", 100)
		.attr("height", 47)
		.attr("fill", "#3333CC");
var ligne = canvas.append('line')
  .attr({
    x1: 120,
    y1: 10,
    x2: 450,
    y2: 77,
    stroke: 'white',
    'stroke-width': '3'
  });

Pour les autres formes, on peut créer une ellipse qui prend pour paramètre "cx", "cy", "rx" (le rayon horizontal) et "ry" (le rayon vertical).

Comment relier des formes à des données ?

C'est en effet la question principale lorsqu'on s'intéresse à d3. On a un tableau contenant quatre valeurs: 8, 15, 3, 7.

var canvas=d3.select("body")
		.append("svg")
		.attr("width",500).attr("height",200);
var donnees = [ 8, 15, 3, 7];
var bars=canvas.selectAll("rect")
	.data(donnees)
	.enter()
		.append("rect")
			.attr("width", function(i){return i*20;})
			.attr("height", 50)
			.attr("y", function(i,j){return j*50;})
			.attr("fill", "#3399FF");

Comment comprendre ce code? On crée un canvas, plus large qu'auparavant. On indique un tableau de données. On crée ensuite une variable "bars" à laquelle on assigne un ensemble de rectangles (non encore existant), autant qu'il y a de données dans notre tableau "donnees". L'opération .data permet de relier cette opération à notre liste de données. Avec enter(), on indique que l'on va commencer à créer des objets, en l'occurence des rectangles, dont la largeur sera une fonction des données (ici multipliée par 20), dont la hauteur sera 50px, dont la position verticale sera égale à leur place dans l'index (1ere, 2nde, 3eme, ou 4eme position dans la liste) fois 50 (ce qui permet d'avoir les barres les unes en dessous des autres) et dont la couleur sera ce joli bleu.

Mettre à l'échelle

Evidemment, on n'a pas envie de faire du bricolage sur la taille de notre zone. On souhaite que les données soient représentées de façon proportionnelle à la taille de la zone dédiée à la représentation graphique. On va créer une variable largeur et une hauteur et utiliser la capacité de d3 à gérer les échelles.

Les échelles sous d3 sont d'autant plus intéressantes qu'elles permettent de travailler aussi bien avec des formes qu'avec des couleurs. Deux éléments doivent être assignés: domain et range. Le premier indique les valeurs extrêmes de notre jeu de données et le second les valeurs extrêmes pour la représentation. Si l'on veut que notre rectangle 15 ait la taille maximum, on indique que son "range" doit être égal à la variable largeur. Reprenons le code précédent.

var donnees = [ 8, 15, 3, 7];
var largeur= 500;
var hauteur=200;
var var canvas=d3.select("body")
		.append("svg")
		.attr("width",largeur).attr("height",hauteur);
var widthScale= d3.scale.linear()
		.domain([0,15])
		.range([0,largeur]);
var colorScale=d3.scale.linear()
		.domain([0,15])
		.range(["white",  "#3399FF"]);
var bars=canvas.selectAll("rect")
	.data(donnees)
	.enter()
		.append("rect")
			.attr("width", function(i){return widthScale(i);})
			.attr("height", 50)
			.attr("y", function(i,j){return j*50;})
			.attr("fill", function (i) {return colorScale (i);});

Côté couleur, on a créé colorScale qui prend comme "domain" les valeurs extrêmes de notre tableau et comme "range" les couleurs extrêmes de notre représentation. Si la valeur minimale était 0.2 (au lieu de 3), la bande de couleur minimale serait encore plus proche du blanc.

Si l'on souhaite ajouter un axe, il est nécessaire de déclarer une variable qui appelle d3.svg.axis() avec son nombre de nombre présents sur l'axe des abscisses, ici 5, et qui suive l'échelle.

var donnees = [8,15,3,7,34,54,12,5,6,9,43,23,35,12];
var largeur= 500;
var hauteur=200;

var widthScale= d3.scale.linear()
	.domain([0, 60])
	.range([0,largeur]);

var colorScale=d3.scale.linear()
	.domain([0, 54])
	.range(["white", "#3399FF"]);

var axis=d3.svg.axis()
	.ticks(5)
	.scale(widthScale);
	
var canvas=d3.select("body")
	.append("svg")
	.attr("width",largeur).attr("height",hauteur)
	.append("g")
	.attr("transform", "translate(20,0)");

var bars=canvas.selectAll("rect")
	.data(donnees)
	.enter()
		.append("rect")
			.attr("width", function(i){return widthScale(i);})
			.attr("height", 10)
			.attr("y", function(i,j){return j*10;})
			.attr("fill", function (i) {return colorScale (i);});
			
canvas.append("g")
			.attr("transform", "translate(0,150)")
  	   		.call(axis);

Tant qu'on y est, refaisons ce graphique avec quelques opérations sur les tableaux (ou jeux de données). Si l'on veut trier les données, il suffit de créer une autre variable ou de remplacer la variable "données", en y ajoutant la fonction .sort(d3.descending) ou bien .sort(d3.ascending).

Parmi les autres fonctions qui existent, d3.extent(donnees) extraira un tableau contenant les valeurs minimales et maximales issues du tableau, en l'occurence, [3,54]. A savoir donc: d3.min(donnees), d3.max(donnees), d3.mean(donnees), d3.sum(donnees), d3.median(donnees) ou encore d3.suffle(donnees) qui mélangera les données et les sortira dans un ordre aléatoire.

var donneesTopDown=donnees.sort(d3.descending);
var bars=canvas.selectAll("rect")
	.data(donneesTopDown)
d3.min(donnees);
d3.max(donnees);
d3.mean(donnees);
d3.sum(donnees);
d3.median(donnees);
d3.shuffle(donnees);

Questions de transitions

Gros avantage de d3: les transitions permettent à un objet de changer de position, d'apparence dans le temps ou lors d'un événement. Un excellent tutoriel a été réalisé par Jérôme Cukier, consultable ici. Le principe n'est pas compliqué. On appelle l'objet auquel on souhaite appliquer la transition, puis le mot "transition()" suivi des nouvelles caractéristiques que l'on souhaite lui donner.

Parmi les mots clés intéressants, "duration" indiquera la durée de la transition, "delay" le temps avant que la transition ne se lance.

var canvas=d3.select("body")
	.append("svg")
	.attr("width",500).attr("height",400);
var circle= canvas.append("circle") 
	.attr("cx", 160).attr("cy",150)
	.attr("r", 100)
	.attr("fill", "red");
var rectangle= canvas.append("rect") 
	.attr("x", 300).attr("y",100)
	.attr("width", 100)
	.attr("height", 47)
	.attr("fill", "#3333CC");
var ligne = canvas.append('line')
  .attr({
    x1: 120,
    y1: 10,
    x2: 450,
    y2: 77,
    stroke: 'white',
    'stroke-width': '3'});
circle.transition()
	.attr("cy",150)
	.attr("cx",240)
	.duration(1500);
rectangle.transition()
	.attr("x", 220).attr("y",250)
	.attr("width", 40).attr("height",150)
	.duration(1500)
	.delay(1000)
	.attr("fill","black");
ligne.transition()
	.attr("x1",180).attr("y1",155)
	.attr("x2",300).attr("y2",155)
	.attr("stroke-width",'40')
	.delay(2000).duration(1500);
});

Rien de contre-intuitif ici: on appelle l'objet créé, auquel on applique une transition en lui donnant de nouvelles coordonnées.

Pour se faire la main, travaillez et faites des variations avec ces différentes variables. Toutefois, un élément majeur de d3 n'est pas encore présenté ici: les "paths", ces formes, automatiquement créées, d'une élégance incontestable. Coup de bol, on s'y met.

Paths: Laisse, c'est d3 qui s'en occupe.

Pour faire en sorte que des points soient reliés entre eux sans avoir à réaliser à la main la procédure qui a aboutit à notre "ligne" de l'exemple précédent, d3 propose un path generator.

var canvas=d3.select("body")
	.append("svg")
	.attr("width",500).attr("height",300);

var donnees= [{x:10,y:20},{x:20,y:60}, {x:30,y:70},
{x:40,y:202},{x:50,y:260}, {x:60,y:70},
{x:70,y:75},{x:80,y:70}, {x:90,y:0},
{x:100,y:20},{x:110,y:20},{x:120,y:60}, {x:130,y:70},
{x:140,y:32},{x:150,y:60}, {x:160,y:90},
{x:170,y:75},{x:180,y:100}, {x:190,y:20}];

var groupe= canvas.append("g")
    .attr("transform", "translate(20,20)");
var line= d3.svg.line()
    .x (function(d){return d.x})
    .y (function(d){return d.y});
groupe.selectAll("path")
    .data([donnees])
    .enter()
    .append("path")
    .attr("d",line)
    .attr("fill", "none")
    .attr("stroke", "#3399FF")
    .attr("stroke-width","1");

On appelle d3.svg.line pour générer les lignes et on appelle le path generator pour tous les éléments du "groupe". Pour chaque élément de "donnees", le path generator va chercher dans "groupe" le x et le y.

On peut poursuivre avec des choses encore plus intéressantes ici.