Introduction à la datavisualisation sous D3JS
Réalisez un diagramme en barre avec D3JS alimenté par une api sous Express
Notre objectif, dans cet article, est de mettre à profit quelques notions que nous avons
étudiées précédemment (ci-dessous les liens vers nos articles traitant de ces sujets) :
- le chargement de données depuis un fichier CSV (ici),
- la mise en place d'un serveur web sous Express (ici),
- l'implémentation d'une API (ici),
- le rendu via le moteur de template Handlebars (ici)
Concrètement nous voulons cette fois dresser un diagramme en barre (bar chart) via la librairie
D3JS. Nos données nous seront envoyées depuis une API du serveur qui sera chargée de les récupérer d'un
fichier CSV.
Voici le résultat attendu :

Nos données de travail
Nous allons travailler avec une petite série de données agrégées listant le salaire médian pour quelques secteurs d'activité. Voici les données :
Id,Salaire,Secteur 1,22000,Restauration 2,21000,Transport 3,25000,Construction 4,35000,Technologie
Ces données sont enregistrées dans un fichier CSV nommé salaires_par_secteur.csv
Installation des packages
Les packages nécessaires doivent être installés. Il nous faut le framework Express, le moteur de template Handlebars, le parser de fichiers csv-parser et enfin le client http axios.
npm install express express-handlebars csv-parser axios
Arborescence du projet
Puisque nous avons opté pour un serveur Express et un moteur de template Handlebars, nous
allons organiser à minima notre projet afin de nous conformer à la structure attendue.
Nous allons revenir au fur et à mesure sur le contenu des fichiers mentionnés.
serveur.js views | |-- dashboard.handlebars public | |--static | |--js | |-- salaires.js data | |-- salaires_par_secteur.csv
Comme vous le voyez, nous n'avons besoin de 4 fichiers dont le fichier de données salaires_par_secteur.csv
évoqué précédemment et placé dans un répertoire data à la racine de projet.
Commençons par voir l'implémentation du serveur.
Le serveur Express
Nous avons déjà évoqué au cours de précédents articles l'implémentation d'un serveur Express, aussi nous n'allons
pas revenir en détails dessus. Sur cette base, comme nous l'avions déjà fait également, nous greffons le moteur de template
Handlebars.
Ce qui est nouveau dans le cadre de cette version et, ce sur quoi nous allons nous attarder un peu plus, ce sont les 2 routes
api/salaires et dashboard qui, respectivement, vont pointer sur le chargement des données et sur le rendu du diagramme en barre.
Voici le code complet du serveur :
//Chargement du systeme de fichier et du parser
const File = require('fs')
const csv = require('csv-parser');
//Chargement express et instanciation du serveur
const express = require('express')
const server = express()
//Chargement du moteur de template
const { engine } = require('express-handlebars');
//Definition de l'adresse et du port du serveur
const host = '127.0.0.1';
const port = 3280;
//Declaration des parametres de lecture du fichier d'entree
const filepath = "./data/salaires_par_secteur2.csv"
const separator = ",";
const skipLines = 1;
const csvOptions = {'separator': separator,
'skipLines': skipLines,
'headers': false};
var df = [];
//Paramatrage serveur
server.use(express.static('public'));
server.engine('handlebars', engine());
server.set('view engine', 'handlebars');
server.set('views', './views');
server.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
next();
});
//Route API salaires
server.get('/api/salaires', (req, res) => {
File.createReadStream(filepath)
.pipe(csv(csvOptions))
.on('data', (data) => df.push({'Id' : String(data[0]),
'Salaire': parseInt(data[1]),
'Secteur' : String(data[2]),}))
.on('end', () => res.send(df))
})
//Route restitution dashboard
server.get('/dashboard', (req, res) => {
res.render('dashboard', {layout: false});
})
//Information log
server.listen(port, host, () => {
console.log(`Server running at http://${host}:${port}/`);
});
API des salaires :
Nous ouvrons un flux de lecture du fichier csv puis chargeons un dataframe dans
un style clé/valeur que D3JS va être en mesure de traiter. Le dataframe obtenu
est au final redirigé vers la réponse au requêteur.
Pour information, une fois le serveur lancé, un appel direct depuis l'explorateur
à la route api/salaires affichera ceci :
[{"Id":"1","Salaire":22000,"Secteur":"Restauration"}, {"Id":"2","Salaire":21000,"Secteur":"Transport"}, {"Id":"3","Salaire":25000,"Secteur":"Construction"}, {"Id":"4","Salaire":35000,"Secteur":"Technologie"}]
Dashboard :
La route dashboard sera celle requêtée par l'utilisateur. Elle pointe vers une vue
Handlebars du nom de dashboard.handlebars sur laquelle nous allons revenir dans le point
suivant.
Vue dashboard
Il s'agit de la seule vue HandleBars de ce projet. Vous constatez que nous n'utilisons pas le layout comme nous l'avons fait dans un précèdent article, c'est pourquoi nous précisons le paramètre {layout: false} lors l'appel à la méthode render. Voici le code de la vue :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
.barre {
fill: orange;
}
</style>
</head>
<body>
<svg width="600" height="500"></svg>
</body>
</html>
<script src="https://d3js.org/d3.v4.js"></script>
<script type="module" src="./static/js/salaires.js"></script>
Quelques précisions sur ce code :
- nous définissons un style css pour la classe barre qui sera par la suite générée par D3JS,
- le corps de la page est composé d'un seul conteneur svg qui va abriter le diagramme,
- outre la librairie D3JS, nous chargeons également le script associé à la génération du diagramme,
point sur lequel nous allons nous attarder dans la partie suivante.
Génération du diagramme
Nous avons fait appel à un fichier javascript salaires.js depuis la vue dashboard. Ce fichier va contenir le code javascript nous permettant d'appeler l'API afin de récupérer les salaires médians par secteur, puis d'en dresser, via D3JS, le diagramme en barre. Voici ci-dessous le code complet. Vous noterez que les différentes parties du code ont été numérotées afin que les commentaires à suivre soient plus compréhensibles :
//1. Import des librairies d3 et axios
import * as d3 from "https://cdn.skypack.dev/d3@7";
import axios from 'https://cdn.skypack.dev/axios';
//2. Declaration de l'appel asynchrone a l'API
const apidata = async function getData() {
return await axios.get('/api/salaires');
}
//3. Fonction principale de generation du diagramme
async function salairesDiagramme() {
//3.1. Appel a l'API, recuperation des donnees
const dataset = await apidata();
//3.2. Formatage du canvas svg
var svg = d3.select("svg"),
margin = 200,
width = svg.attr("width") - margin,
height = svg.attr("height") - margin;
//3.3. Titre du diagramme
svg.append("text")
.attr("transform", "translate(100,0)")
.attr("x", 70)
.attr("y", 50)
.attr("font-size", "22px")
.text("Salaires par secteur")
//3.4. Mise a l'echelle des axes par rapport au canvas
var xScale = d3.scaleBand().range ([0, width]).padding(0.4),
yScale = d3.scaleLinear().range ([height, 0]);
//3.5. Dimensionnement des axes en fonctions des donnees
var g = svg.append("g")
.attr("transform", "translate(" + 100 + "," + 100 + ")");
xScale.domain(dataset.data.map(function(d) { return d.Secteur; }));
yScale.domain([0, d3.max(dataset.data, function(d) { return d.Salaire; })]);
//3.6. Edition des labels sur les axes
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale));
g.append("g")
.call(d3.axisLeft(yScale).tickFormat(function(d){
return d;
}).ticks(10))
.append("text")
.attr("y", 6)
.attr("dy", "0.70em")
.attr("text-anchor", "end");
//3.7. Tracage des barres
g.selectAll(".barre")
.data(dataset.data)
.enter().append("rect")
.attr("class", "barre")
.attr("x", function(d) { return xScale(d.Secteur); })
.attr("y", function(d) { return yScale(d.Salaire); })
.attr("width", xScale.bandwidth())
.attr("height", function(d) { return height - yScale(d.Salaire); });
}
//4. Appel de la fonction principale
salairesDiagramme();
Déroulons dans l'ordre les différentes étapes qui constituent ce code :
1. Nous importons tout d'abord les librairies d3js et axios depuis le cdn,
2. Nous déclarons ensuite une fonction asynchrone qui va se charger de requêter
notre API afin de récupérer le contenu du fichier CSV,
3. Nous avons choisi de dérouler le programme principal dans une fonction même si nous aurions
pu nous en passer, il s'agit essentiellement d'une préparation pour de futures évolutions,
3.1. Nous commençons par charger les données en appelant notre API,
3.2. Le canvas déclaré dans la vue Handlebars est récupérer puis ses attributs sont initialisés, il
s'agit principalement de sa taille,
3.3. Le titre du graphique est ajouté sur le canvas,
3.4. L'échelle des axes est initialisée, leur longueur est fonction de la taille du canvas,
3.5. L'étendue des axes est fonction des données, un mapping des secteurs pour les abscisses, de 0
au salaire maximum pour les ordonnées,
3.6. Les labels sont édités sur les axes,
3.7. Enfin les barres sont tracées en bouclant via la méthode enter() sur les données. Vous noterez
par ailleurs l'attribution de la classe barre qui va nous permettre d'appliquer un style dessus,
4. Il suffit d'appeler pour finir la fonction principale.
Lancement du serveur / requetage
Tout est en place. Il ne reste plus qu'à lancer le serveur via la ligne de commande :
node serveur.js
Puis requêter, depuis un navigateur, l'adresse : http://127.0.0.1:3280/dashboard pour obtenir le résultat suivant :

Retrouvez dans la rubrique "Nos datasets" toutes les données dont vous aurez besoin pour tester et pratiquer !