D3JS - Bulles (Bubble Chart)

Population française des nouvelles régions

Dernière mise à jour le 13/06/2018

Ce tutoriel présente une visualisation hiérarchique de la population française par département mais aussi par région. En passant la souris sur un département sa population apparait sous forme de tooltip. Il est également possible de voir la population d'une région en passant la souris sur le bleu foncé du cercle la représentant ou même du pays tout entier en la passant sur le plus gros cercle bleu clair. Les données proviennent d'un fichier JSON que nous avons nous même réalisé à partir des données de population par département : population.json.

Cette visualisation étant hiérarchique, les données le sont également :

{
	"name": "France",
	"children": [
			{
			"name": "Auvergne-Rhône-Alpes",
			"children": [
				{"name": "Ain", "population": 614331},
				{"name": "Allier", "population": 353124},
				{"name": "Ardèche", "population": 324885},
				{"name": "Cantal", "population": 154135},
				{"name": "Drôme", "population": 499313},
				{"name": "Isère", "population": 1233759},
				{"name": "Loire", "population": 766729},
				{"name": "Haute-Loire", "population": 231877},
				{"name": "Puy-de-Dôme", "population": 649643},
				{"name": "Rhône", "population": 1756069},
				{"name": "Savoie", "population": 428751},
				{"name": "Haute-Savoie", "population": 760979}
			]
		}, ...
	]
}

Nous partons d'un nœud racine, ici la France, puis nous en décrivons les fils à commencer par la région Auvergne-Rhône-Alpes. Pour chaque région nous listons à nouveau ses fils et cette fois-ci nous donnons une valeur numérique à ces nœuds fils, en l'occurence la population du département.

Le SVG construit dans ce tutoriel est un cercle, nous définissons donc uniquement un diamètre pour le construire. La fonction format nous permet de représenter les chiffres de la population de manière plus lisible (par exemple 11,938,714).

const diameter = 700,
	format = d3.format(",d");

const svg = d3.select("#chart").append("svg")
	.attr("width", diameter)
	.attr("height", diameter)
	.attr("id", "svg");

Nous commençons par ajouter un groupe légèrement décalé pour éviter les problèmes de bordure aux extrémités du plus grand cercle. Nous construisons ensuite notre Layout de type pack (il en existe bien d'autres pour représenter de manière hiérarchique des données : d3-hierarchy). La taille définie est légèrement inférieure à celle déclarée précédemment (toujours pour les mêmes raisons) et un padding (l'espacement souhaité entre les cercles) est ajouté.

const g = svg.append("g").attr("transform", "translate(2,2)");

const pack = d3.pack()
	.size([diameter - 4, diameter - 4]) // Related to previous translation
	.padding(1.5);

Le reste du code réalise le traitement du fichier JSON et la construction des cercles, textes et tooltip.

d3.json("d3js/bubblechart/population.json").then(function(error, root) {
	// Le reste du code va ici
}

Notre fichier est bien formé pour constituer une hiérarchie au sens ou D3JS l'entend, c'est-à-dire qu'il possède un nœud racine (et un seul), que ce nœud racine possède un tableau de nœuds fils dénommé children et que les feuilles se terminent par un libellé et une valeur numérique. Il est bien sûr possible de définir ce type de données à partir de n'importe qu'elle autre en écrivant une fonction de conversion. La construction de la hiérarchie consiste simplement à définir la fonction sum (pour calculer la taille de chaque cercle) et donc à mentionner l'attribut population. La fonction sort permet l'organisation des cercles à l'intérieur d'un cercle plus grand.

root = d3.hierarchy(root)
	.sum(function(d) { return d.population; })
	.sort(function(a, b) { return b.value - a.value; });

C'est la partie un peu difficile de ce tutoriel. Nous ajoutons autant de groupes g au SVG qu'il y a de nœuds dans notre arborescence. Nous passons à la fonction pack définie plus haut notre JSON. Après avoir attribué une classe en fonction des descendants du nœud nous réalisons une mystérieuse transformation en utilisant les attributs x et y que nous n'avons pourtant pas déclarés.

var node = g.selectAll(".node")
	.data(pack(root).descendants())
	.enter().append("g")
		.attr("class", function(d) { return d.children ? "node" : "leaf node"; })
		.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

Après l'appel de la fonction pack(root).descendants() les nœuds sont enrichis de plusieurs attributs. En fait cette fonction va réaliser la répartition des nœuds g (attributs x,y pour la position, r pour le diamètre en pixels) de manière à ce qu'il n'y est aucun chevauchement et en tenant compte du diamètre global que nous avons fourni à l'initialisation de notre objet pack. Il y aussi un attribut indiquant la profondeur à laquelle se trouve le nœud (depth) et une référence vers le nœud parent. Ce qui est génial, c'est que maintenant il ne nous reste plus qu'à prendre ces informations pour construire les cercles et ajouter textes et tooltips.

{
	data: Object { name="Paris",  population=2268265},
	depth: 2,
	height: 0,
	parent: // Object, lien vers le parent
	r: 43.19412010935609,
	value: 2268265,
	x: 323.7945204077783,
	y: 285.23659422163723
}

Ainsi, pour les cercles et les tooltips, ces lignes suffisent. Nous récupérons simplement le rayon de notre objet enrichi. Noter que nous ne définissons pas de coordonnées pour le cercle, celui-ci, comme les objets qui vont suivre, est contenu dans un nœud g que nous avons déjà placé au bon endroit. Le tooltip renvoi le nom du département ou de la région et sa population.

node.append("circle")
	.attr("r", function(d) { return d.r; });
	
node.append("title")
	.text(function(d) { return d.data.name + "\n" + format(d.value); });

Il y a une petite subtilité pour le texte contenu dans les cercles car nous réalisons un substring pour éviter qu'il ne déborde du cercle. Celui-ci est lié au diamètre et doit être testé en fonction de la police de caractère (le texte n'est défini que pour les nœuds feuilles et qui ne possèdent donc pas d'enfant).

node.filter(function(d) { return !d.children; })
	.append("text")
	.attr("dy", "0.3em")
	.text(function(d) { return d.data.name.substring(0, d.r / 3); });

Voici le CSS utilisé :

circle {
	fill: rgb(31, 119, 180);
	fill-opacity: .25;
	stroke: rgb(31, 119, 180);
	stroke-width: 1px;
}

.leaf circle {
	fill: #ff7f0e;
	fill-opacity: 1;
}

text {
	font: 10px sans-serif;
	text-anchor: middle;
	cursor: pointer; cursor: hand
}

Nous avons donné un maximum d'explications pour ce tutoriel mais le code javascript ne fait que 35 lignes (en comptant les espaces). N'oubliez pas de regarder le code source pour être sûr qu'il ne manque rien (principalement pour le CSS). Il est aussi important de debugger le code javascript pour comprendre ce qui se passe et enfin de regarder la structure HTML qui est générée.

A suivre : Tooltip Voir le tutoriel

comments powered by Disqus