D3JS - Carte choroplèthe

Comment créer une carte dont les régions sont colorées en fonction de données statistiques et ajouter une échelle. Comment utiliser un jeu de couleurs pertinant et le rendre dynamique
d3js7.x
Sources :
Avertissement sur la qualité

Cette carte a été optimisé pour l'usage qui en est fait dans ce tutoriel, merci de consulter le tutoriel Map - Optimisation pour plus d'information.

Un meilleur tutoriel est disponible !

Nous avons réalisé un nouveau tutoriel plus avancé pour les cartes choroplètes, il permet notamment de mieux gérer les couleurs : D3JS - Map Avancée. N'hésitez pas à le parcourir après avoir lu celui-ci.

Parsing du CSV

Cet exemple est un complément du précédent et vise surtout à introduire les cartes choroplèthes. Ces cartes permettent la représentation d'un thème donnée (ici la densité de population française par département) grâce à des couleurs. D3JS facilite particulièrement la création de telles cartes. C'est aussi l'occasion de faire une carte plus propre avec un tooltip plus lisible, la possibilité de choisir un jeu de couleur qui a été travaillé et une échelle associée aux couleurs.

Le code est strictement identique à celui de l'exemple précédent pour la création de la carte (il faut juste ajouter l'attribut class avec pour valeur Blues sur le SVG pour initier les couleurs et passer la width de 550 à 700 pour afficher la légende de l'échelle).

const width = 700, height = 550;

const path = d3.geoPath();

const projection = d3.geoConicConformal() // Lambert-93
	.center([2.454071, 46.279229]) // Center on France
	.scale(2600)
	.translate([width / 2 - 50, height / 2]);

path.projection(projection);

const svg = d3.select('#map').append("svg")
	.attr("id", "svg")
	.attr("width", width)
	.attr("height", height)
	.attr("class", "Blues");

const deps = svg.append("g");

Cette carte présente la répartition de la population française. Elle est définit par département dans un fichier qui peut-être récupéré via firebug dans l'onglet réseau ou directement sur ce lien : population.csv.

Pour charger nos données (le JSON et le CSV) nous utilisons les promesses qui ont été introduites avec D3JS v5. Les promesses permettent de gérer facilement des requêtes asynchrones. Ici elles nous permettent de synchroniser le chargement de nos deux fichiers. Le fichier CSV est parsé comme on avait précédement parsé du JSON mais ici on utilise la méthode d3.csv() (notez que le séparateur utilisé doit être la virgule). La création des path ne varie quasiment pas, on ajoute seulement un id à chaque path, ce qui nous permettra de le retrouver ultérieurement. Notez que l'id doit commencer par une lettre. Il est ici composé d'un 'd' concaténé avec le code du département que l'on trouve dans notre fichier geoJSON.

var promises = [];
promises.push(d3.json('/tutorials/d3js/map-population/departments.json'));
promises.push(d3.csv("/tutorials/d3js/map-population/population.csv"));

Promise.all(promises).then(function(values) {
	const geojson = values[0]; // Récupération de la première promesse : le contenu du fichier JSON
	const csv = values[1]; // Récupération de la deuxième promesse : le contenu du fichier csv
	
	var features = deps
		.selectAll("path")
		.data(geojson.features)
		.enter()
		.append("path")
		.attr('id', d => "d" + d.properties.CODE_DEPT)
		.attr("d", path);
	
		// Ici on insèrera tout le code qui va suivre dans cette partie.

Nous commençons par définir la fonction qui nous permettra d'obtenir la couleur de notre département. Pour cela, nous utilisons une projection discontinue d3.scaleQuantile() entre 0 et le max de notre population vers le range de 1 à 9. Cela implique que nous aurons 9 variations de couleurs pour représenter la population de chaque département.

var quantile = d3.scaleQuantile()
	.domain([0, d3.max(csv, e => +e.POP)])
	.range(d3.range(9));

Comme nous l'avons vu dans le premier tutoriel, nous ajoutons ensuite un groupe qui contientra nos rectangle de différentes couleurs. Ce groupe est décalé via la méthode translate pour être positionné correctement.

var legend = svg.append('g')
	.attr('transform', 'translate(525, 150)')
	.attr('id', 'legend');

Nous ajoutons ensuite 9 rectangles aux dimensions de 20px par 20px et nous décalons chaque rectangle de 20px sur l'axe vertical. La coloration se fait simplement avec la ligne qui ajoute l'attribut class à chaque rectangle. Nous reviendrons sur sa définition à la fin du chapitre, l'attribut CSS aura pour valeur q1-9, q2-9... .

legend.selectAll('.colorbar')
	.data(d3.range(9))
	.enter().append('svg:rect')
		.attr('y', d => d * 20 + 'px')
		.attr('height', '20px')
		.attr('width', '20px')
		.attr('x', '0px')
		.attr("class", d => "q" + d + "-9")

Ici, nous attaquons une partie qui nous sera fort utile dans les chapitres suivant : la construction d'un axe gradué. Cela se fait en deux étapes : la définition du domaine et la définition de l'axe. Le domaine utilisé (scale) représente encore une projection d'un nombre compris entre 0 et le max de notre population. Le range de destination en revanche tient compte de la hauteur de nos rectangle (20px) et de leur nombre (9) pour que l'échelle soit de la même taille que l'ensemble de nos rectangles.

var legendScale = d3.scaleLinear()
	.domain([0, d3.max(csv, e => +e.POP)])
	.range([0, 9 * 20]);

Nous définissons ensuite l'axe qui utilise le domaine précédement définit. L'axe est orienté sur la droite (et du coup automatiquement vertical, les autres valeurs possible sont axisLeft toujours vertical et axisBottom ou axisTop pour un axe horizontal). ticks indique le nombre de tiret que nous souhaitons (D3JS fait au mieux). L'ensemble est décalé pour se placer correctement à droite des rectangles.

var legendAxis = svg.append("g")
	.attr('transform', 'translate(550, 150)')
	.call(d3.axisRight(legendScale).ticks(6));

Pour chaque entrée de notre CSV, nous faisons comme pour la succession de rectangle et ajoutons l'attribut class pour définir notre couleur. Celle-ci utilise la fonction quantile que nous avons précédemment définie. Le code gérant l'affichage du tooltip n'a pas beaucoup varié, par contre le CSS a été un peu amélioré pour plus de lisibilité.

csv.forEach(function(e,i) {
	d3.select("#d" + e.CODE_DEPT)
		.attr("class", d => "department q" + quantile(+e.POP) + "-9")
		.on("mouseover", function(event, d) {
			div.transition()        
				.duration(200)      
				.style("opacity", .9);
			div.html("<b>Région : </b>" + e.NOM_REGION + "<br>"
					+ "<b>Département : </b>" + e.NOM_DEPT + "<br>"
					+ "<b>Population : </b>" + e.POP + "<br>")
				.style("left", (event.pageX + 30) + "px")     
				.style("top", (event.pageY - 30) + "px");
		})
		.on("mouseout", function(event, d) {
				div.style("opacity", 0);
				div.html("")
					.style("left", "-500px")
					.style("top", "-500px");
		});
});

Finalement on rajoute un listener sur notre liste de choix de couleurs pour rafraichir l'ensemble.

d3.select("select").on("change", function() {
	d3.selectAll("svg").attr("class", this.value);
});

Le jeu de couleur utilisé pour ce graphique est connu sous le nom de Color Brewer. Ici, c'est la version CSS qui est utilisée. Le selecteur permet de choisir une couleur associée à une value comme 'Blues' ensuite et comme nous l'avons vu nous ajoutons une classe à chaque département du type 'q4-9' ce qui nous construit la class '.Blues .q4-9' et signifie que ce département aura pour couleur la 4ième variation sur une échelle de 9 du bleu.

La partie CSS

Vous trouverez ci-dessous le code complet CSS associé à cette visualisation.

#svg {
	display: block;
	margin: auto;
}

/** La souris change pour un curseur sur un département et le contour est noir avec une épaisseur assez fine */
.department {
	cursor: pointer;
	stroke-linecap: round;
	stroke-linejoin: round;
	stroke: black;
	stroke-width: .5px;
}

/** Au survol d'un département le contour devient plus épais */
.department:hover {
	stroke-width: 2px;
}

/** Le tooltip est défini avec des propriétés statiques qui conviennent à nos données */
div.tooltip {
	position: absolute;
	opacity:0.8;
	z-index:1000;
	text-align:left;
	border-radius:4px;
	-moz-border-radius:4px;
	-webkit-border-radius:4px;
	padding:8px;
	color:#fff;
	background-color:#000;
	font: 12px sans-serif;
	max-width: 300px;
	height: 60px;
}

Autres fichiers geoJSON pour la France

Suite à la remarque d'une internaute, nous rajoutons un lien vers le github suivant : https://github.com/gregoiredavid/france-geojson. Il permet de récupérer au format geoJSON les communes, départements et régions au niveau de la France entière ainsi que les communes et départements spécifiques à chaque région ou département.

Mise à jour des données

Si votre fichier CSV possède plusieurs données, vous pouvez vouloir mettre à jour automatiquement la carte en choisissant la colonne à afficher. En supposant que vous disposez d'une liste de choix comme pour les couleurs dans notre exemple, le code nécessaire est assez court.

d3.select("#data").on("change", function() {
	selectedData = this.value;
		
	quantile = d3.scale.scaleQuantile
		.domain([0, d3.max(csv, e => +e[selectedData])])
		.range(d3.range(9));
		
	legendScale.domain([0, d3.max(csv, e => +e[selectedData])]);
	legendAxis.call(d3.axisRight(legendScale).ticks(6));
		
	csv.forEach(function(e,i) {
		d3.select("#d" + e.CODE_DEPT)
			.attr("class", d => "department q" + quantile(+e[selectedData]) + "-9");
	});
});

La variable selectedData est déclarée juste avant la lecture du CSV et initialisé à 'POP', c'est-à-dire la colonne population de notre fichier CSV. La 1ère ligne de ce code récupère la valeur sélectionnée par l'utilisateur, elle correspond à la colonne qu'il a choisi. Ensuite nous mettons à jour notre projection pour tenir compte du nouvel intervalle de données. Le domaine de la légende est lui aussi recalculé et un simple appel à la fonction call permet de rafraichir les libellés. Enfin pour chaque entrée du CSV on met à jour nos départements. Ce code doit être inclu dans la fonction qui charge le CSV.

COMMENTAIRES

PlG_BENIS


Merci pour ce travail!

PierreV


Bonjour,

Je reviens vers vous pour des questions d'optimisation de la taille des fichiers.
Je travaille sur le fichier des communes françaises qui est très volumineux à charger, j'ai donc voulu remplacer le format GEOJSON par du TOPOJSON.
La taille du fichier a été grandement réduite (divisée par 3 environ), seulement je connais mal ce nouveau format et je n'arrive pas à adapter votre code. Malgré des tutoriels sur internet je ne réussis pas à y connecter le json et les données.
Sauriez vous comment faire ?

Merci,
Pierre

ericfrigot


Bonjour, normalement ce n'est pas très différent. Est-ce que vous avez le moyen de me mettre à disposition votre fichier TOPOJSON ? Eric

PierreV


Bonjour,
Vous devriez y avoir accès par le lien suivant :
https://gist.github.com/Pie...
Merci, Pierre

Greg Hor


Bonjour, super tutoriel! J ai à nouveau beaucoup appris ;).
J'ai adapté la carte des populations à d'autres données départementales. J aimerai mettre cette carte sur mon blog (hébergé sur blogger) pour en faire profiter mes amis. Si j ai bien compris, il faut dans un premier temps que mes ressources (le .json des departements français ainsi que le .csv contenant les données) soient stockés dans une adresse accessible sur le web. J ai naïvement tenté de uploader le "data.csv" dans un repertoire github pour l appeler dans le script via d3.csv("https://github.com/.../data...") . Visiblement sans succès. Après quelques recherches sur Google, j ai l impression que le problème résulte d un conflit same-origin policy ,
qui m empêche d aller lire les données stockées dans une url extérieure.

Sachant que je n ai pas de serveur, y a t il un moyen simple d associer les données au script afin qu il soit fonctionnel en ligne? Dans une réponse postée dans le tuto précédent, vous aviez évoqué la possibilité d inclure les données .json directement dans le script via la déclaration d 'une variable. Est-il possible de faire la même chose avec des données .csv ? Cordialement, Grégoire

ericfrigot


Bonjour, c'est bien sûr possible. Voici ce que ça donne : http://www.datavis.fr/d3js/...
Pour information, j'ai construit l'objet de la façon suivante : Utilisation de Firefox avec Firebug, ajout d'un point d'arrêt juste après le chargement du fichier CSV, ajout d'un espion : JSON.stringify(csv). Ensuite il suffit de copier le contenu de la chaine de caractère et ça correspond à l'objet à déclarer. Eric

marooned


Bonjour, je suis débutant sur JS et j'essaie de suivre ton tuto, j'avoue c'est bien explicite, mais je n'utilise pas de server et je voulais avoir la version dans la quelle les fichiers .json et .CSV sont stockés dans des variables. tu as mis le lien : https://www.datavis.fr/d3js..., mais il ne marche pas, est-il possible de récupérer le code source?

marooned


Super, je viens de réussir à faire le tuto d'après. le tout avec Angular 7. Merci pour votre coup de main et pour la qualité de vos tutos.
ps : j'ai transformé le fichier csv en json sur le net.

ericfrigot


Bonjour, le lien est de nouveau actif, j'ai dû recréer le fichier car je l'avais supprimé chez moi. Pour information tout est dans le fichier, même la gestion de la couleur qui utilise un tableau de couleurs contrairement à cette page. Pour information je me suis appuyé sur un autre tutoriel pour gérer la couleur: https://www.datavis.fr/inde...

Greg Hor


Bonjour Eric, cela fonctionne très bien, merci beaucoup pour le tuyau! Grégoire

PierreV


Bonjour, je vous remercie pour votre tutoriel très détaillé qui m'a bien aidé pour réaliser ma première carte interactive avec d3.
Je souhaiterais compléter le résultat en ajoutant la possibilité avec une liste déroulante (comme pour le choix des couleurs) d'afficher différents jeux de données autres que la population. J'ai rajouté un second menu déroulant ainsi que des colonnes au csv mais je n'arrive pas à faire le lien entre les deux et à afficher différentes données. Auriez-vous des conseils pour réaliser cela ? Merci.

ericfrigot


Bonjour, je viens de mettre à jour le tutoriel avec une partie traitant de ce problème. En espérant que cela puisse vous être utile.

PierreV


Merci beaucoup, c'est exactement ce que je recherchais.
En y ajoutant l'outil de zoom de votre exemple à l'échelle communale, j'obtiens un résultat vraiment très complet.

Jiji


Bonjour, y-a t-il une limitation dans le nombre de "parties" colorées? En effet, j'ai réalisé ce tuto avec l'ensemble des communes d'Ile de France soit 1281 communes et "seules" environ 200 communes ont été colorées.

Merci d'avance pour votre retour.

ericfrigot


Bonjour, il n'y a d'autre limitation que celle de votre navigateur (sa capacité à traiter l'information). J'ai réalisé la même carte pour toutes les communes de France, ce qui fait plus de 36500 polygones (attention, c'est assez lourd, 11Mo à télécharger et un gros traitement pour le navigateur) : http://www.datavis.fr/index..., vous pouvez même zoomer avec votre molette. Votre problème peut avoir plusieurs origines, soit vos couleurs sont trop faibles et apparaissent presque blanches (raison du tutoriel sur les prix Nobel) soit il vous manque des données pour les associer à chaque commune. Si votre code est visible en ligne, vous pouvez me donner le lien, je jetterais un œil.

Jiji


Bonjour, je comprends tout à fait. Ce n'est donc pas une "limitation" qui empêchent la colorisation de mes communes car votre carte "lourde" s'affiche sans problèmes. Malheureusement, je travaille uniquement en local pour le moment.

Personnellement, je pense que c'est mon fichier csv que j'ai créé sur excel qui en est la cause. En effet, seules les 237 premières communes sont colorisées.

Merci pour votre disponibilité et votre réponse.

Sylia


Est-il possible d'avoir le découpage par région et non pas par département?

ericfrigot


Bonjour, je viens de rajouter un paragraphe sur les autres données geoJSON que l'on peut trouver sur Internet pour la France.

Sylia


Merci de votre réponse!

Ina


Bonjour,
j'ai une question j'ai suivit le tuto du début à la fin mais ça marche pas et j'ai pas d'erreur non plus. J'ai été même récuperer le fichier json mais ça marche. Est ce que je dois ajouter autre chose qui n'est pas dit ici. Je vous remercie de votre aide

ericfrigot


Bonjour. Pas évident pour moi de savoir ce qui ne marche pas dans votre code. Du coup, j'ai repris ce tutoriel et le précédent pour voir si j'avais oublié quelque chose. Le résultat est visible sur cette page : http://www.datavis.fr/d3js/... (je me suis contenté de reprendre les différentes portions de code et de récupérer le CSS de cette page). Vous pouvez récupérer tout le code source et avec ça vous aurez votre exemple qui fonctionne. Par ailleurs, si vous me donnez le lien de votre site ou un lien pour télécharger vos sources je pourrais vous dire ce qui ne fonctionne pas dans votre code. Au plaisir de vous recroisez par ici.

Ina


je vous remercie de votre votre réponse, c'était bien le problème de CSS, mais par contre j'arrive pas à changer la couleur j'ai téléchargé le fichier colorbrewer mais la couleur ne change pas, je travaille en local je ne sais pas trop comment je vais faire pour que vous puissiez voir mon code . Je travaille sur un projet actuellement donc je veux me référer sur votre exemple pour le faire. j'ai vu que vous afficher la population, j'aimerai faire pareil sauf que pour moi c'est sur une certaine nombre d'année(5 ans) pouvez vous me donnez une idée pour cette partie. Je vous remercie d'avance

ericfrigot


Bonjour. Je vous répond un peu tardivement mais si vous avez toujours des soucis pour colorer votre carte, je vous invite à zipper votre travail dans un fichier que vous déposerez sur un service d'hébergement en ligne, ainsi vous pourrez me fournir le lien. Par ailleurs pour représenter plusieurs années vous pouvez jetez un oeil à cet exemple : http://bl.ocks.org/darrenja.... La partie importante du code est celle qui commence par d3.selectAll("input").on("change", function change(). A partir de cette ligne on récupère l'année sélectionnée et on change la colonne de notre fichier pour remplir la couleur de chaque path (chaque département pour nous). Par rapport à ce tutoriel, li faut redéfinir la class et non le path. Si j'ai du temps je mettrais à jour l'exemple que j'avais réalisé pour vous.

Guest


Bonjour, ce tuto est vraiment bien fait merci ! Serait-il possible d'avoir la structure du geoJson associé ? (d3js/map-population/departments.json)

ericfrigot


Bonjour et merci pour ce commentaire.
Les spécifications complètes du GeoJSON sont détaillées à cette URL : http://geojson.org/geojson-...
Seule la partie properties varie et ici elle contient les informations concernant le département.
N'hésitez-pas si vous avez besoin d'autres détails.