Faire un graphique linéaire en D3.js v.5
Le moment est venu d’intensifier notre jeu et de créer un graphique linéaire à partir de zéro. Et pas n’importe quel graphique linéaire : un graphique multi-séries pouvant accueillir n’importe quel nombre de lignes. En plus de gérer plusieurs lignes, nous travaillerons avec des échelles de temps et linéaires, des axes et des étiquettes – ou plutôt, nous les ferons travailler pour nous. Il y a beaucoup à faire, alors je vous suggère de déclencher votre serveur D3 et de craquer.
Préparation du document
Comme première étape, nous devons préparer les données et la structure du fichier pour la visualisation. Créer un diagramme de ligne.html, styles.css et données.csv dans votre dossier de projet et remplissez-les avec les extraits qui suivent. Cela nous permettra de commencer.
Collez ceci dans le line_chart.fichier html. Le code définit l’élément svg pour nous afin que nous puissions commencer à dessiner immédiatement. J’ai également créé une structure de base à l’avance afin qu’il soit plus facile de naviguer dans le document lorsque nous travaillons sur ses sections particulières.
Laissez les styles.document css vide pour l’instant. Collez les lignes suivantes dans les données.csv. Le graphique en courbes comportera plusieurs séries : correspondant aux colonnes A, B et C.
date,A,B,C20-Jul-2019,10,20,1621-Jul-2019,11,22,1822-Jul-2019,13,19,2123-Jul-2019,11,17,2224-Jul-2019,15,16,2025-Jul-2019,16,19,1826-Jul-2019,19,21,1827-Jul-2019,22,25,1528-Jul-2019,18,24,1229-Jul-2019,14,20,1630-Jul-2019,14,18,1831-Jul-2019,16,18,2101-Aug-2019,15,20,2202-Aug-2019,14,21,19
Préparation des données
La première étape – et une étape cruciale pour toute la visualisation – consiste à lire correctement les données. J’ai utilisé un exemple multi-séries pour une raison: bien qu’il soit assez simple de tracer un seul chemin, la gestion de plusieurs lignes (en particulier un nombre indéfini d’entre elles) nécessite un peu plus de finesse D3. Collez les éléments suivants dans la section DONNÉES, rechargez le document HTML et consultez le journal de la console dans votre navigateur:
const timeConv = d3.timeParse("%d-%b-%Y");const dataset = d3.csv("data.csv");dataset.then(function(data) { const slices = data.columns.slice(1).map(function(id) { return { id: id, values: data.map(function(d){ return { date: timeConv(d.date), measurement: +d }; }) }; });console.log("Column headers", data.columns);console.log("Column headers without date", data.columns.slice(1));// returns the sliced datasetconsole.log("Slices",slices); // returns the first sliceconsole.log("First slice",slices);// returns the array in the first sliceconsole.log("A array",slices.values); // returns the date of the first row in the first sliceconsole.log("Date element",slices.values.date); // returns the array's lengthconsole.log("Array length",(slices.values).length);
Passons en revue les transformations appelées sur notre jeu de données une par une: – data.columns renvoie les données d’en–têtes csv.colonne.slice(1) renvoie les en–têtes csv sans la colonne date (la tranche commence à la colonne indexée à 1) – map() appelle une fonction sur chaque élément du tableau (composé de A, B et C) – appelons chacun de ces éléments une ‘slice’ – map() attribue le nom de la colonne en tant qu’élément id à chaque tranche – puis il assigne un tableau de valeurs à chaque tranche – notez comment l’élément values évoque une fonction. Ici, nous cartographions les informations de l’ensemble de données d’origine: le tableau sera composé de 2 colonnes, de la date et de la mesure. La date est dérivée de la première colonne (et transformée en un format de date), et la mesure est prise à partir de la colonne correspondant à la tranche id.At à la fin de ces transformations, nous obtenons 3 tableaux: A, B et C, avec 2 colonnes chacune: date et mesure. C’est un moyen incroyablement flexible de découper un ensemble de données : quel que soit le nombre de colonnes qu’il contient ! Tout est fait dans ces quelques lignes. J’ai imprimé des informations sur la console pour vous aider à revoir l’extrait de code.
Préparation des échelles
Une fois les données lues, nous devons configurer le mécanisme de mise à l’échelle. Ceci est fait afin d’imprimer le graphique conformément à l’immobilier du svg. Les échelles transforment l’entrée de données (nos dates et valeurs) en coordonnées sur le plan svg. Collez les lignes suivantes dans la section ÉCHELLES.
const xScale = d3.scaleTime().range();const yScale = d3.scaleLinear().rangeRound();xScale.domain(d3.extent(data, function(d){ return timeConv(d.date)}));yScale.domain();
Nous allons tracer les dates sur l’axe des abscisses et les valeurs sur l’axe des ordonnées. D3 fournit une méthode scaleTime() pour les dates de mise à l’échelle et une méthode scaleLinear() pour les valeurs continues. Nous décidons d’abord de la plage de l’échelle: à quoi les valeurs d’entrée doivent être traduites. Dans ce cas, nous allons étirer les valeurs de données de 0 à la largeur du svg, et les valeurs numériques de la hauteur du svg à 0. Comme deuxième étape, nous spécifions le domaine de données d’entrée. Un domaine comprend toutes les valeurs entre un minimum et un maximum spécifiés qu’un ensemble de données peut prendre. Au lieu de rechercher manuellement ces valeurs, nous les transmettons aux fonctions D3 intégrées: –d3.extent() renvoie une valeur minimale et maximale d’un tableau (dans un ordre naturel) – cela fonctionnera parfaitement sur notre jeu de dates – d3.max() renvoie une valeur maximale du tableau. Notez comment dans cet exemple, nous extrayons d’abord une valeur maximale de chaque tableau pour ensuite sélectionner un maximum des trois. J’ai également ajouté 4 à la valeur maximale pour des raisons esthétiques purement subjectives: je voulais avoir un peu d’espace au-dessus du graphique.
Les échelles sont maintenant mises en place. Si vous n’avez pas assez d’échelles et que vous souhaitez voir plus d’exemples, jetez un œil à mon tutoriel précédent.
Une échelle bien configurée nous permet de commencer à tracer des valeurs sur le svg. Chaque fois que nous évoquons l’ensemble de données, il nous suffit d’appeler une échelle appropriée.
Axes
Assez de discussion – dessinons déjà quelque chose! Les axes sont un bon point de départ: s’ils sont tracés correctement, ils nous assureront que les données ont été lues comme prévu et qu’elles évoluent aussi bien que nous l’avions imaginé.
Collez ceci sur des AXES sous la section Préparation:
const yaxis = d3.axisLeft().scale(yScale); const xaxis = d3.axisBottom().scale(xScale);
Et ceci aux AXES sous la section Dessin:
svg.append("g") .attr("class", "axis") .attr("transform", "translate(0," + height + ")") .call(xaxis);svg.append("g") .attr("class", "axis") .call(yaxis);
Et aussi simple que cela, nous avons tracé les axes x et y!
Certes, les haches ne sont pas les plus élégantes du monde (il y a de jolies haches là-bas) mais elles sont là ! Il y a quelques ajustements supplémentaires que nous pouvons appliquer pour les rendre plus conviviaux pour le lecteur.
Regardons d’abord l’axe des X: il se passe quelque chose de drôle avec les dates. En lisant de gauche, nous obtenons « Sam 20 », « Juil 21 », « Lun 22 », et à un moment donné, nous atteignons juste « Août ». On dirait que les mois et les jours sont venus dans un mélange insubordonné de variations. Nous devons mettre fin à ce freestyle, et je veux dire par là que nous devrions décider du format de date que nous aimerions imprimer à l’écran. Le d3.la méthode axis() nous permet d’ajuster toutes sortes de choses pour les tiques – leur nombre, l’intervalle entre les points, le format d’affichage, etc. Configurons certains d’entre eux pour les deux axes.
Remplacez la définition des axes dans la section Préparation par l’extrait de code suivant et actualisez la visualisation:
const yaxis = d3.axisLeft() .ticks((slices.values).length) .scale(yScale);const xaxis = d3.axisBottom() .ticks(d3.timeDay.every(1)) .tickFormat(d3.timeFormat('%b %d')) .scale(xScale);
Le code ci-dessus spécifie un nombre défini de ticks pour l’axe y (14, ou autant qu’il y a d’éléments de tableau /lignes csv). Dans le cas de l’axe X, une coche sera affichée avec une granularité d’un jour, tous les jours. Cela a été réalisé en définissant la propriété tick sur d3.Un jour.tous les (1). Le format des dates affichées affichera le jour et le mois abrégé pour chaque coche. Après ces changements, nous nous retrouvons avec des axes quelque peu améliorés:
Les dates désobéissantes ne sont plus un problème!
Pour le rendre encore meilleur (est-ce même possible!!!) nous pouvons ajouter une étiquette à l’axe y pour montrer ce que représentent les valeurs. Bien que les dates soient explicites, les chiffres seuls ne contiennent aucune information. Ajoutez une étiquette (appelez-la comme vous voulez – je suis allé avec la fréquence) en ajoutant ce qui suit au dessin de l’axe y:
//this you hadsvg.append("g") .attr("class", "axis") .call(yaxis)//this you append .append("text") .attr("transform", "rotate(-90)") .attr("dy", ".75em") .attr("y", 6) .style("text-anchor", "end") .text("Frequency");
( Il n’y a pas de style défini pour l’étiquette, il ne s’affichera donc pas sur le graphique – mais croyez-moi et les outils de développement de Google Chrome, il est là)
Enfin, améliorons l’apparence des axes. Par amélioration, je veux dire: définissez les couleurs, les largeurs et le rendu de chaque élément et décidez de la police à utiliser. Collez ce qui suit dans le fichier css et n’hésitez pas à prendre vos propres décisions de style:
/* AXES *//* ticks */.axis line{stroke: #706f6f;stroke-width: 0.5;shape-rendering: crispEdges;}/* axis contour */.axis path {stroke: #706f6f;stroke-width: 0.7;shape-rendering: crispEdges;}/* axis text */.axis text {fill: #2b2929;font-family: Georgia;font-size: 120%;}
Les tiques sont contrôlées par le.élément de ligne de l’axe, tandis que l’axe réel est défini avec le .élément de chemin. Les axes semblent nets (l’humble opinion de l’auteur) et prêts à accueillir certaines données:
Sans plus tarder, tracons le graphique !
Graphique linéaire
Les lignes sont essentiellement d3.chemins () qui connectent un tas de coordonnées (x, y) sur un plan 2D. Pour construire une ligne, vous devez lui dire où trouver ses coordonnées x et y, puis l’ajouter au svg. Collez les extraits suivants dans les espaces réservés précédemment créés et examinons le code ensemble.
Cela devrait arriver au bit de LIGNES sous la section Préparation:
const line = d3.line() .x(function(d) { return xScale(d.date); }) .y(function(d) { return yScale(d.measurement); });
Dans cet extrait, nous avons appelé un constructeur de ligne, d3.line() qui utilise deux accesseurs : x pour les valeurs sur le plan horizontal et y pour l’axe vertical. Ici, nous pointons simplement les valeurs les plus granulaires de notre tableau, de la date et de la mesure (ce n’est pas le moment de s’inquiéter de la structure csv imbriquée). Une fois cela fait, collez ce qui suit sur les LIGNES sous la section Dessin:
const lines = svg.selectAll("lines") .data(slices) .enter() .append("g"); lines.append("path") .attr("d", function(d) { return line(d.values); });
Cela nécessite quelques explications. La variable lines sélectionne un nombre non identifié de lignes dans le svg – et indique immédiatement à D3 qu’il y aura 3 lignes en pointant vers les tranches définies (lignes A, B et C). Il ajoute ensuite un élément g à chacun d’eux: un élément de regroupement qui nous facilitera la vie en temps voulu. L’élément g collectera tout ce qui concerne une série de graphiques particulière (c’est-à-dire une tranche du tableau): la ligne (représentée ci-dessus comme un chemin), ses points de données que nous pourrons survoler et les étiquettes de séries.
La première chose à ajouter aux lignes (qui sont en fait 3 conteneurs g vides) sont les lignes du graphique elles-mêmes. Nous appelons le d3.constructeur line() sur les données pour dessiner un chemin. Voyez comment nous devons d’abord accéder aux valeurs sous chaque tranche. Ceci est ensuite transmis au constructeur qui extrait les dates et les mesures selon les besoins.
Une fois les modifications enregistrées, la visualisation est mise à jour:
D’accord, ce n’est pas parfait mais croyez-moi, nous y arrivons! Appliquons quelques correctifs esthétiques sur le graphique et observons comment il se forme. Ajoutez ce qui suit aux styles.css:
/* LINE CHART */path {fill: none;stroke: #ed3700;}
Nous devons définir le remplissage sur aucun pour que les formes réapparaissent sous forme de lignes. Actualiser le graphique:
Qu’est-ce qui sépare un graphique linéaire d’un groupe de lignes collées ensemble sur un graphique? La capacité de différencier les séries. Pour le moment, nous n’avons que le premier.
Pour commencer, nous devons faire une distinction entre les lignes du code. Ajoutons un ID à chaque classe de ligne – ajoutez ce qui suit à la section LIGNES dans la partie Préparation:
let id = 0;const ids = function () { return "line-"+id++;}
Ce petit morceau de code crée un compteur que nous pouvons utiliser pour attribuer automatiquement un identifiant de ligne à chaque ligne ajoutée. Référençons le compteur dans la propriété de classe des chemins. Ajustez le code dans la section LIGNES pour ajouter la propriété de classe:
const lines = svg.selectAll("lines") .data(slices) .enter() .append("g"); lines.append("path") .attr("class", ids) .attr("d", function(d) { return line(d.values); });
Et comme par magie, chaque chemin a sa propre classe!
Ce qu’il nous reste à faire est de référencer ces classes dans le css et de donner à chaque ligne son propre caractère unique. Modifiez la section Graphique en courbes du css pour dire:
/* LINE CHART */path.line-0 {fill: none;stroke: #ed3700;}path.line-1 {fill: none;stroke: #2b2929;stroke-dasharray: 2;}path.line-2 {fill: none;stroke: #9c9c9c;stroke-dasharray: 6;}
Notez que je modifie non seulement la couleur, mais aussi le trait de chaque ligne. Rappelez-vous qu’environ 10% de toutes les personnes ont un certain degré de daltonisme et, en toute justice, différencier les couleurs peut être difficile pour chacun d’entre nous. Les couleurs se mélangeront simplement s’il y a trop de séries de données et leur teinte s’affichera différemment sur chaque moniteur.
Une fois les modifications appliquées, les lignes sont clairement séparées sur le graphique, comme indiqué ci-dessous:
Maintenant, les séries sont différenciées mais il est toujours impossible de savoir laquelle est laquelle à moins d’avoir mémorisé les données sous-jacentes et d’avoir une imagination visuelle assez malade, auquel cas je me demande pourquoi vous aviez besoin d’un graphique en premier lieu. Pour aider la majorité d’entre nous dans la reconnaissance de la série, je propose d’ajouter le nom de la série sur le côté droit du graphique. Ajoutez ce qui suit à la section dessin des LIGNES:
lines.append("text") .attr("class","serie_label") .datum(function(d) { return { id: d.id, value: d.values}; }) .attr("transform", function(d) { return "translate(" + (xScale(d.value.date) + 10) + "," + (yScale(d.value.measurement) + 5 ) + ")";}) .attr("x", 5) .text(function(d) { return ("Serie ") + d.id; });
L’extrait localise la fin de chaque ligne et y ajoute un élément de texte. Le texte sera imprimé en Série A, Série B ou Série C, selon la ligne. Ajoutez ce qui suit au document css pour ajuster les étiquettes de la série:
.serie_label {fill: #2b2929;font-family: Georgia;font-size: 80%;}
Les étiquettes sont jointes! Bons moments.
Nous pouvons tous convenir que c’est un beau graphique linéaire! J’ai collé le code complet ci-dessous. Assurez-vous de consulter la deuxième partie du tutoriel qui présente deux scénarios d’ajout d’interactivité au graphique.
Suivez-moi sur Twitter pour plus de projets de data-sciency / data visualisation !
Exemples de code
tableau de ligne.html:
styles.css:
/* AXES *//* ticks */.axis line{stroke: #706f6f;stroke-width: 0.5;shape-rendering: crispEdges;}/* axis contour */.axis path {stroke: #706f6f;stroke-width: 0.7;shape-rendering: crispEdges;}/* axis text */.axis text {fill: #2b2929;font-family: Georgia;font-size: 120%;}/* LINE CHART */path.line-0 { fill: none; stroke: #ed3700;}path.line-1 { fill: none; stroke: #2b2929; stroke-dasharray: 2;}path.line-2 { fill: none; stroke: #9c9c9c; stroke-dasharray: 6;}.serie_label { fill: #2b2929; font-family: Georgia; font-size: 80%;}
données.csv: