2. Graphique D3.js pour afficher les futures formations
La deuxiĂšme tĂąche qui mâa Ă©tĂ© attribuĂ©e est la suivante : faire un graphique avec D3.js pour afficher les futures sessions de formations de lâentreprise.
Il mâa Ă©tĂ© imposĂ© dâutiliser tree of life pour rĂ©aliser ce graphique.
Jâai commencĂ© par comprendre les concepts de D3.js puis Ă comprendre le fonctionnement du tree of life.
1. Mise en place dâun serveur node.js
Pour afficher le graphique dans mon navigateur, jâai mis en place un serveur avec node.js.
;
'assets/html';
'assets/js';
'assets/json';
'/',
port,
Lâobjectif de ce serveur est juste de rendre accessible les ressources : html, js et autres fichiers externes utiles au graphique.
2. Création du graphique
Afin dâafficher le graphique, jâai eu besoin dâun fichier html pour appeler le code javascript dans le navigateur.
d3js - Bashroom
Maintenant que tout Ă©tait prĂȘt pour afficher le graphique dans mon navigateur, il Ă©tait temps de le rĂ©aliser.
Pour utiliser D3.js, jâai utilisĂ© le CDN pour importer D3 dans mon script.
;
Ensuite, pour plus de simplicitĂ©, jâai placĂ© les donnĂ©es en brut dans le script. Voici un exemple de ce Ă quoi ressemblaient les donnĂ©es :
;
Câest donc un tableau contenant des objets avec comme propriĂ©tĂ© category
et technos
, qui est un tableau contenant des objets pour définir la formation avec name
et la difficulté défini par difficulty
qui contient la difficulté, les dates et la durée des formations.
Ensuite, il a fallu créer une fonction pour transformer ces données au format JSON en un format compréhensible pour D3.js.
La fonction sâappelle transformData
et prend inputData
en paramÚtre, qui correspond à nos données sous format JSON.
Nous commençons par initialiser result
, qui est un objet contenant le nom du nĆud, un tableau contenant les nĆuds enfants de ce nĆud, et un Ă©tat collapsed pour savoir si le nĆud en question est repliĂ© ou non.
Tip
Il faut savoir quâun tree of life est constituĂ© de noeud (node) et de liens (links).
;
Maintenant que notre format dâobjet est dĂ©fini avec notre premier nĆud central sâappelant âFormationsâ.
Nous pouvons itérer avec des boucles sur chacun des tableaux que nous avons dans nos données, donc inputData
, technos
, difficulty
et dates
. Cela nous permet dâajouter en tant que noeud enfant chaque valeur Ă son noeud parent.
for of inputData
return result;
}
Nous initialisons ensuite deux variables pour stocker les données transformées. Une premiÚre constante qui contient le résultat de notre fonction transformData
afin dâavoir accĂšs aux donnĂ©es transformĂ©es intactes. Puis une deuxiĂšme variable qui contient notre premiĂšre valeur, mais qui sera modifiable pour agir sur les nĆuds.
;
;
La fonction updateTree
, comme son nom lâindique, permet de mettre Ă jour lâarbre du graphique. Cette fonction gĂ©nĂšre, Ă partir des donnĂ©es de la variable treeData, les nĆuds, les liens et les textes pour afficher le nom des nĆuds.
AprĂšs la fonction pour mettre Ă jour lâarbre, nous avons les deux fonctions permettant de dĂ©velopper et de rĂ©duire les nĆuds, appelĂ©es respectivement expand
et collapse
en anglais. Elles permettent de mettre Ă jour lâarbre en modifiant les nĆuds dĂ©veloppĂ©s et rĂ©duits.
Ă la toute fin du script, nous trouvons une ligne pour appeler la fonction updateTree
et afficher le graphique pour la premiĂšre fois.
Voir le script en entier
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
let jsonData = [
{
"category": "front-end",
"technos": [
{
"name": "angular",
"difficulty": [
{
"name": "speed-run",
"duration": 1,
"dates": ["2024-01-08"]
},
{
"name": "basic",
"duration": 3,
"dates": ["2024-01-17", "2024-01-18", "2024-01-19"]
},
{
"name": "intermediate",
"duration": 4,
"dates": ["2024-02-05", "2024-02-06", "2024-02-07", "2024-02-08"]
},
{
"name": "advanced",
"duration": 5,
"dates": ["2024-02-26", "2024-02-27", "2024-02-28", "2024-02-29", "2024-02-30"]
}
]
},
]
}
];
/**
* Transform JSON data into an object for the tree
* @param {JSON} inputData
* @returns An object for the tree
*/
function transformData(inputData) {
const result = { "name": "Formations", "children": [], "collapsed": true };
for (const categoryData of inputData) {
const categoryNode = { "name": categoryData.category, "children": [], "collapsed": true };
for (const techno of categoryData.technos) {
const technoNode = { "name": techno.name, "children": [], "collapsed": true };
for (const difficulty of techno.difficulty) {
const difficultyNode = { "name": difficulty.name, "value": difficulty.duration, "children": [], "collapsed": true };
for (const date of difficulty.dates) {
const dateNode = { "name": date };
difficultyNode.children.push(dateNode);
}
technoNode.children.push(difficultyNode);
}
categoryNode.children.push(technoNode);
}
result.children.push(categoryNode);
}
return result;
}
const globalData = transformData(jsonData);
let treeData = globalData;
function updateTree() {
d3.select("body").select("svg").remove();
// Constant for tree length
const width = 928;
const height = width;
const cx = width * 0.5;
const cy = height * 0.59;
const radius = Math.min(width, height) / 2 - 120;
// Tree instance with custom options and our data
const tree = d3.tree()
.size([2 * Math.PI, radius])
.separation((a, b) => (a.parent == b.parent ? 1 : 2) / a.depth);
const root = tree(d3.hierarchy(treeData)
.sort((a, b) => d3.ascending(a.data.name, b.data.name)));
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-cx, -cy, width, height])
.attr("style", "width: 75%; height: auto; font: 10px sans-serif;");
// Append links between each node
svg.append("g")
.attr("fill", "none")
.attr("stroke", "#156")
.attr("stroke-opacity", 0.4)
.attr("stroke-width", 1.5)
.selectAll()
.data(root.links())
.join("path")
.attr("d", d3.linkRadial()
.angle(d => d.x)
.radius(d => d.y));
// Append each node with mouse handlers
svg.append("g")
.selectAll()
.data(root.descendants())
.join("circle")
.attr("transform", d => `rotate(${d.x * 180 / Math.PI - 90}) translate(${d.y},0)`)
.attr("fill", d => d.children ? "#555" : "#999")
.attr("r", 2.5)
.on("click", (e, d) => {
d.data.collapsed ? expand(d.data) : collapse(d.data);
updateTree();
});
// Append text for formation's name with mouse handlers
svg.append("g")
.attr("stroke-linejoin", "round")
.attr("stroke-width", 3)
.selectAll()
.data(root.descendants())
.join("text")
.attr("transform", d => `rotate(${d.x * 180 / Math.PI - 90}) translate(${d.y},0) rotate(${d.x >= Math.PI ? 180 : 0})`)
.attr("dy", "0.31em")
.attr("x", d => d.x < Math.PI === !d.children ? 6 : -6)
.attr("text-anchor", d => d.x < Math.PI === !d.children ? "start" : "end")
.attr("paint-order", "stroke")
.attr("stroke", "white")
.attr("fill", "currentColor")
.text(d => d.data.name)
.on("click", (e, d) => {
d.data.collapsed ? expand(d.data) : collapse(d.data);
updateTree();
});
}
function expand(d) {
if (d._children) {
d.children = d._children;
d._children = null;
} else if (d.children) {
d._children = d.children;
d.children = null;
}
if (d._children) d._children.forEach(expand);
}
function collapse(d) {
if (d._children) {
d.children = d._children;
d._children = null;
}
if (d.children) d.children.forEach(collapse);
}
updateTree()
Voici le résultat de ce script dans le navigateur :
Nous pouvons voir que certains nĆuds sont repliĂ©s, comme : nestjs, rust, typescript, ansible, aws et cloud. Nous pouvons aussi voir que des dates sont disponibles uniquement pour les formations en bas Ă droite du graphique.