Niveau | Débutant |
Logiciels utilisés |
D3 |
Plateforme | Windows | Mac | Linux | FreeBSD |
Les techniques de représentation interactive des données, ou data visualization (dataviz) connaissent depuis quelques années un fort développement sur le support Internet, grâce aux fonctionnalités nouvelles apportées par le standard HTML5 et la manipulation d'objets vectoriels au format SVG. Des bibliothèques de fonctions JavaScript permettent de rendre accessible la création de ce type de graphismes interactifs sur Internet, en simplifiant la manipulation des objets graphiques et des données.
Bibliographie :
- Scott Murray, "Interactive Data Visualization", paru aux éditions O'Reilly en 2013 (gratuit voir lien ci-dessous).
- Mike Dewar, "Getting started with D3", O'Reilly, 2012.
- Michael Bostock, Jason Davies, "Code as cartography", revue The Cartograhic Journal, vol. 50 n°2, mai 2013.
- Nick Qi Zhu, "Data Visualization with D3.js Cookbook", éditions Pakt Publishing, 2013 (cf site avec les codes sources en lien ci-dessous).
Les fichiers utilisés dans ce tutoriel sont disponibles en ligne à l'adresse suivante :
http://www.geotests.net/cours/sigma/webmapping/2013/d3/
Les exemples sont inspirés de ceux proposés par Scott Murray dans l'ouvrage mentionné en bibliographie.
Présentation de Data-Driven Documents (D3)
D3 est une bibliothèque de fonctions JavaScript créée par Mike Bostock, employé dans l'équipe du site Internet du New York Times. Projet open-source, cette bibliothèque a connu un grand développement grâce à la participation de nombreux développeurs et testeurs. Aujourd'hui de nombreux exemples d'utilisations, tutoriels et mêmes ouvrages sont disponibles pour découvrir son utilisation.
Structure du fichier HTML de base
D3 étant une bibliothèque de fonctions JavaScript, on l'utilise côté client en l'appelant depuis un fichier HTML. D3 va servir à créer des graphismes vectoriels au format SVG, à partir de données fournies directement ou en provenance c'un fichier ou d'un flux externe et d'un programme qui va choisir le type de représentation et d'interactivité à fournir. Le fichier HTML de base aura la forme suivante :
Code source HTML :
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>Fichier de base D3</title> 6 <script src="http://d3js.org/d3.v3.js"type="text/javascript"></script> 7 <script type="text/javascript"> 8 function draw(data) { 9 // le code de dessin viendra se placer ici 10 } 11 </script> 12 </head> 13 <body> 14 <script type="text/javascript"> 15 d3.json("data/data.json", draw); 16 </script> 17 </body> 18 <html>
Description :
Ligne 6 : Appel de la bibliothèque.
Lignes 7 à 11 : Le script de dessin proprement-dit.
Lignes 14 à 16 : Le script de chargement des données, qui lancera le dessin une fois terminé. Notez son positionnement dans le <body>.
Avec cette organisation, la page HTML va se charger, lancer la récupération des données (qui peut être longue s'il s'agit d'un flux) puis seulement exécuter le code de réalisation du graphisme. Ainsi, le programme se lancera une fois que tous les éléments nécessaires (éléments de la page et données) seront disponibles.
Première réalisation : un histogramme interactif
Les données
Tout d'abord, prévoir le jeu de données, au format JSON (JavaScript Object Notation).
[
{"mois":"janvier", "valeur":5},
{"mois":"février", "valeur":10},
{"mois":"mars", "valeur":13},
{"mois":"avril", "valeur":19},
{"mois":"mai", "valeur":21},
{"mois":"juin", "valeur":25},
{"mois":"juillet", "valeur":22},
{"mois":"août", "valeur":18},
{"mois":"septembre", "valeur":15},
{"mois":"octobre", "valeur":13},
{"mois":"novembre", "valeur":11},
{"mois":"décembre", "valeur":12}
]
Ce format est en fait la notation objet JavaScript, avec ici un tableau [] qui contient des objets {} eux-mêmes contenant deux couples variable: valeur. Le fichier data/data.json du serveur contiendra les données selon ce format. Ensuite, on peut créer une première version simple de notre histogramme.
L'histogramme : première version
1 <!DOCTYPE html> 2<html>3<head> 4 <metacharset="utf-8"> 5 <title>Fichier de base D3</title> 6 <scriptsrc="http://d3js.org/d3.v3.js"type="text/javascript"></script> 7 <scripttype="text/javascript"> 8 functiondraw(dataset){ 9 //Largeur et hauteur du graphe 10 var larg =500; 11 var haut =100; 12 var barPadding =1;//Padding des barres 13 14 //Creation de l'élément SVG 15 var svg = d3.select("body") 16 .append("svg") 17 .attr("width", larg) 18 .attr("height", haut); 19 20 svg.selectAll("rect") 21 .data(dataset) 22 .enter() 23 .append("rect") 24 .attr("x", function(d, i){ 25 return i *(20+ barPadding);//Largeur de 20 + 1 de padding 26 }) 27 .attr("y", function(d){ 28 return haut -(d.valeur *2);// Hauteur - valeur 29 }) 30 .attr("width", 20) 31 .attr("height", function(d){ 32 return(d.valeur *2);// Valeur de la donnée 33 }) 34 .attr("fill", "teal"); 35 } 36 37 </script> 38</head> 39<body> 40 <scripttype="text/javascript"> 41 d3.json("data/data.json", draw); 42 </script> 43</body> 44</html>
Description :
La fonction de dessin (lignes 8 à 12), va d'abord établir les valeurs de variables globales : les dimensions du graphes et la largeur de l'intervalle entre deux barres (padding).
Le bloc entre les lignes 15 et 18 va ajouter un élément de type SVG au <body> de la page en précisant ses dimensions.
Le bloc entre les lignes 20 et 34 dessine les barres de l'histogramme, par une boucle qui va générer des objets SVG <rect> à partir des données issues du fichier json chargé précédemment, et contenus dans la variable dataset. A l'intérieur de la boucle, cette variable prendra le nom "d".
Cette boucle crée des rectangles de la manière suivante :
- ligne 23 : ajout d'un objet SVG <rect>
- ligne 24 : attribut de position x selon le n° de barre indiqué par la variable i, plus la largeur de barre (20 pixels) et le décalage entre barres (barPadding)
- ligne 28 : attribut de position y selon la hauteur de barre, valeur d * 2, ôté de la valeur de hauteur générale du graphe car la position verticale 0 du format SVG se trouve en haut.
- ligne 32 : attribut de hauteur de la barre en cours selon la valeur d * 2
- ligne 34 : couleur de remplissage de la barre : bleu.
Exercices :
- faire varier les dimensions du graphe dans la page.
- changer la hauteur des barres pour que la plus grande barre tienne dans toute la hauteur du graphique moins 20 pixels pour placer les libellés.
- changer la largeur individuelle des barres pour que le graphique occupe toute la largeur réservée pour lui.
- colorer les barres en fonction de leur valeur.
L'histogramme : deuxième version, avec des libellés
1<!DOCTYPE html>2<html> 3<head> 4 <metacharset="utf-8"> 5 <title>Premier histogramme</title> 6 <scriptsrc="http://d3js.org/d3.v3.js"type="text/javascript"></script> 7 <scripttype="text/javascript"> 8 functiondraw(dataset){ 9 //Largeur et hauteur du graphe 10 var larg =500; 11 var haut =100; 12 var barPadding =2;//Padding des barres 13 var nbb = dataset.length;//Nb de barres 14 var lb =((larg - nbb) / nbb);//Largeur barre 15 var ch =(haut / d3.max(dataset, function(d){ 16 return d.valeur //Coef. hauteur. 17 })); 18 19 //Creation élément SVG 20 var svg = d3.select("body") 21 .append("svg") 22 .attr("width", larg) 23 .attr("height", haut); 24 25 svg.selectAll("rect") 26 .data(dataset) 27 .enter() 28 .append("rect") 29 .attr("x", function(d, i){ 30 return(i * lb); 31 }) 32 .attr("y", function(d){ 33 return haut -(d.valeur * ch); 34 }) 35 .attr("width", lb - barPadding) 36 .attr("height", function(d){ 37 return(d.valeur * ch); 38 }) 39 .attr("fill", function(d){ 40 return"rgb(0, 0, "+(d.valeur *10)+")"; 41 }); 42 43 svg.selectAll("text") 44 .data(dataset) 45 .enter() 46 .append("text") 47 .text(function(d){ 48 return d.valeur; 49 }) 50 .attr("x", function(d, i){ 51 return(i * lb)+ 12); 52 }) 53 .attr("y", function(d){ 54 return haut -((d.valeur * ch)-12); 55 }) 56 .attr("font-family", "sans-serif") 57 .attr("font-size", "11px") 58 .attr("fill", "white"); 59 } 60 </script> 61</head> 62<body> 63 <scripttype="text/javascript"> 64 d3.json("data/data.json", draw); 65 </script> 66</body> 67</html>
Description :
L'histogramme est désormais adaptatif selon les dimensions du rectangle global : les positions et les tailles des barres sont proportionnelles à l'espace disponible.
L'ajout des libellés est réalisé par le bloc en fin de fonction, de la ligne 43 à la ligne 58. Il s'agit d'une nouvelle boucle selectAll, qui va créer des objets <text>, qui vont être positionnés dans les barres de l'histogramme par leurs attributs x et y. Le style de ces libellés est précisé par les attributs "font-family", "font-size" et "fill" (lignes 56 à 58).
Un peu d'interactivité
L'interaction la plus simple à créer consiste à réagir au survol des barres par le curseur de la souris. Il suffit d'ajouter un changement de couleur dans la classe CSS rect
58<style> 59 rect:hover { 60 fill: orange; 61 } 62</style>63</head>
Deuxième réalisation : un diagramme de points XY
Diagramme simple
1<!DOCTYPE html> 2<html> 3<head> 4 <metacharset="utf-8"> 5 <title>Diagramme de points</title> 6 <scriptsrc="http://d3js.org/d3.v3.js"type="text/javascript"></script> 7 <scripttype="text/javascript"> 8 functiondraw(dataset){ 9 //Largeur et hauteur du graphe 10 var larg =600; 11 var haut =200; 12 var padding =20; 13 14 //Création de l'élément SVG 15 var svg = d3.select("body") 16 .append("svg") 17 .attr("width", larg) 18 .attr("height", haut); 19 20 var xScale = d3.scale.linear() 21 .domain([0, d3.max(dataset, function(d){return d[0];})]) 22 .range([padding, larg - padding *2]); 23 24 var yScale = d3.scale.linear() 25 .domain([0, d3.max(dataset, function(d){return d[1];})]) 26 .range([haut - padding , padding]); 27 28 var rScale = d3.scale.linear() 29 .domain([0, d3.max(dataset, function(d){return d[1];})]) 30 .range([2, 5]); 31 32 svg.selectAll("circle") 33 .data(dataset) 34 .enter() 35 .append("circle") 36 .attr("cx", function(d){ 37 return xScale(d[0]); 38 }) 39 .attr("cy", function(d){ 40 return yScale(d[1]); 41 }) 42 .attr("r", function(d){ 43 return rScale(d[1]); 44 }); 45 46 svg.selectAll("text") 47 .data(dataset) 48 .enter() 49 .append("text") 50 .text(function(d){ 51 return d[0]+","+ d[1]; 52 }) 53 .attr("x", function(d){ 54 return xScale(d[0]); 55 }) 56 .attr("y", function(d){ 57 return yScale(d[1]); 58 }) 59 .attr("font-family", "sans-serif") 60 .attr("font-size", "11px") 61 .attr("fill", "grey"); 62 } 63 </script> 64</head> 65<body> 66 <scripttype="text/javascript"> 67 d3.json("data/points.json", draw); 68 </script> 69</body> 70</html>
Description :
Cette fois-ci, nous allons dessiner un diagramme de points selon leurs coordonnées x et y, disponibles dans un fichier json contenant des valeurs formatées en tableaux
[ [5, 20], [480, 90], [250, 50], [100, 33], [330, 95], [410, 12], [475, 44], [25, 67], [85, 21], [220, 88] ]
De plus nous allons utiliser un outil de mise à l'échelle automatique de D3, les "scales". Les blocs des lignes 21 et suivantes, puis 24 et suivantes, créent des fonctions de proportionnalités entre des limites fournies. Ici on va utiliser les largeur et hauteur maximales, moins une valeur de "padding", c'est à dire d'enrobage, pour éviter que les éléments graphiques soient rognés.
Le bloc entre les lignes 32 et 44 dessine les cercles, en les positionnant selon leur valeur x et y pondérée par les fonction "scale" décrites ci-dessus. De même, le bloc entre les lignes 46 et 61 ajoute les libellés sur les points.
On ajoute des axes
On modifie le fichier précédent pour ajouter, à la fin du code JavaScript :
63 var xAxis = d3.svg.axis() 64 .scale(xScale) 65 .ticks(5)//Nombre approx. de barbules 66 .orient("bottom"); 67 68 var yAxis = d3.svg.axis() 69 .scale(yScale) 70 .orient("left") 71 .ticks(5); 72 73 svg.append("g") 74 .attr("class", "axis")//Assigne la classe CSS "axis" 75 .attr("transform", "translate(0,"+(haut - padding)+")") 76 .call(xAxis); 77 78 svg.append("g") 79 .attr("class", "axis") 80 .attr("transform", "translate("+ padding +",0)") 81 .call(yAxis); 82 } 83 </script> 84 <style> 85 .axis path, 86 .axis line { 87 fill:none; 88 stroke:black; 89 shape-rendering: crispEdges; 90 } 91 .axis text { 92 font-family:sans-serif; 93 font-size:11px; 94 } 95 </style> 96</head>
Les axes sont créés automatiquement par la fonction d3.svg.axis (lignes 63 et 68). Ils sont dimensionnés par les fonctions "scale" utilisées précédemment. On les ajoute au graphe en créant un nouvel élément SVG de type "g" pour chaque axe (groupe, lignes 73 et 78), qui en plus leur affecte une classe de style CSS dédiée, "axis". Cette classe est définie par la suite dans un bloc <style>, ajouté au code en suivant le bloc <script>.
Exercice :
- décaler les libellés des points pour mieux les visualiser
- changer la couleur des points en fonction de leur valeur
- ajouter un changement de couleur au survol par le curseur de la souris
Troisième réalisation : une carte
D3 montre toute sa puissance dans le dessin des graphismes complexes que sont les cartes vectorielles, notamment en gérant un très grand nombre de projections. Créer une carte thématique peut se révéler aussi simple que charger un fond de carte, des données localisées et choisir un mode de représentation.
D3 n'est pas capable de lire directement les formats SIG courants, il utilise là encore le format JSON et plus spécifiquement un format adapté aux entités spatiales munies d'attributs, les GeoJSON.
La bibliothèque de fonctions OGR (parente vectorielle de GDAL) possède un programme utilitaire qui permet de faire des conversions entre formats SIG courants, ogr2ogr. Cet utilitaire sait écrire le format GeoJSON et il est donc simple de convertir un fond au format shapefile en GeoJSON.
Par exemple, voici le début du fond IGN Géofla des départements, converti en GéoJSON :
{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "ID_GEOFLA": 1, "CODE_DEPT": "01", "NOM_DEPT": "AIN", "CODE_CHF": "053", "NOM_CHF": "BOURG-EN-BRESSE", "X_CHF_LIEU": 8717, "Y_CHF_LIEU": 65696, "X_CENTROID": 8814, "Y_CENTROID": 65582, "CODE_REG": "82", "NOM_REGION": "RHONE-ALPES" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 919315.0, 6541572.0 ], [ 918646.0, 6540637.0],
Le format TopoJSON est une évolution qui permet, comme son nom l'indique, de gérer la topologie. Un utilitaire de conversion à partir des shapefiles est disponible : https://github.com/mbostock/topojson/wiki On peut généraliser le fond avant la conversion pour simplifier et alléger les transferts de données.
Code source :
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>Carte</title> 6 <script src="http://d3js.org/d3.v3.js" type="text/javascript"></script> 7 <script src="http://d3js.org/topojson.v1.min.js"></script> 8 <script type="text/javascript"> 9 function draw(dataset) { 10 //Largeur et hauteur du graphe 11 var larg = 600; 12 var haut = 600; 13 14 var projection = d3.geo.orthographic() 15 .scale(280) //Échelle 16 .translate([larg / 2, haut / 2]) //Dimensions 17 .rotate([-4, -48]) //France au centre 18 .clipAngle(90) 19 .precision(.1); 20 21 var path = d3.geo.path() 22 .projection(projection); 23 24 //Création de l'élément SVG 25 var svg = d3.select("body") 26 .append("svg") 27 .attr("width", larg) 28 .attr("height", haut); 29 30 svg.insert("path") 31 .datum(topojson.feature(dataset, dataset.objects.land)) 32 .attr("class", "land") 33 .attr("d", path); 34 35 svg.insert("path") 36 .datum(topojson.mesh(dataset, dataset.objects.countries, function(a, b) { return a !== b; })) 37 .attr("class", "boundary") 38 .attr("d", path); 39 } 40 </script> 41 <style> 42 body { 43 background: #fcfcfa; 44 } 45 46 .land { 47 fill: #262; 48 } 49 50 .boundary { 51 fill: none; 52 stroke: #fff; 53 stroke-width: .5px; 54 } 55 </style> 56 </head> 57 <body> 58 <script type="text/javascript"> 59 d3.json("data/world-50m.json", draw); 60 </script> 61 </body> 62 </html>
Description :
Le fichier HTML de base est le même, le fichier TopoJSON est chargé comme précédemment les fichiers JSON (ligne 59). À partir de la ligne 14 on définit la projection qui va permettre de visualiser les données brutes du fond de carte. Ici une projection orthographique hémisphérique, pivotés pour que la France soit positionnée au centre de l'image.
Le fond possède deux types d'entités : les continents et les frontières entre États. Ces deux entités sont ajoutées à la page aux lignes 30 à 38 et leurs styles d'affichages sont gérés par le bloc <style> entre les lignes 40 et 55.
Exercices :
- Modifier les paramètres de la projection : zoom, centrage.
- Changer de projection (cf. la référence ici : https://github.com/mbostock/d3/wiki/Geo-Projections)
- Changer de fond de carte en convertissant le fond IGN Géofla des départements.
Conclusion
Dans cette introduction nous avons donc utilisé des fonctionnalités basiques de D3, pour réaliser des graphiques statistiques progressivement plus complexes, en mettant en avant les capacités de cette bibliothèque pour automatiser le travail. Grâce à D3, on peut se focaliser sur le design, la conception de la représentation, en lui laissant le travail fastidieux de la mise en page HTML et du dessin des informations annexes comme les axes et la gestion des interactions.
Mais ce n'est qu'une introduction, la cartographie notamment n'est qu'effleurée et il est possible d'aller bien plus loin dans la réalisation de représentations interactives. Il est intéressant d'aller examiner les très nombreux exemples disponibles sur le site officiel de D3, pour pouvoir les reprendre et les adapter à des besoins plus spécifiques. Des tutoriels complémentaires et des ressources sont disponibles sur le site https://www.dashingd3js.com/ (mais en anglais).
Site officiel : D3