D3JS - Migration vers D3JS v4

Migration pas à pas et optimisation

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

Introduction

Mike Bostock, le créateur de D3JS, a publié une nouvelle version de sa librairie le 28 juin 2016. En plus d'apporter de nouvelles fonctionnalités, celle-ci offre des améliorations intéressantes en terme de performance et de concision de code. Il parait donc naturel pour tout nouveau projet utilisant D3JS de partir sur cette nouvelle version et c'est ce que nous ferons dans nos futurs tutoriels. Par ailleurs un code écrit avec la version précédente sera potentiellement plus performant/lisible si on le migre vers la nouvelle version. Pour cette raison nous avons décidé de migrer tous nos tutoriaux vers la version 4. On en profite pour décrire ici la méthodologie utilisée et notre ressenti vis-à-vis de cette nouvelle version. Pour l'utiliser, nous référençons directement :

<script src="https://d3js.org/d3.v4.min.js">

Changements apportés

L'ensemble des modifications apportées à cette nouvelle version est listé dans le changelog (journal des modifications) et il est accessible sur Github à cette adresse. Il est en anglais mais nous présentons ici les principales modifications.

Retrocompatibilité

Il faut déjà savoir que la compatibilité descendante n'est pas assurée, c'est-à-dire qu'un code qui fonctionnait avec la version 3 peut ne pas fonctionner avec la version 4 (et ça sera surement vrai). Par exemple d3.geo.mercator a été remplacé par d3.geoMercator et c'est valable pour énormément de fonctions. Un autre exemple très utilisé d3.scale.linear est remplacé par d3.scaleLinear. Toutes ces modifications sont listées dans le lien donné juste au-dessus.

Syntaxe raccourci

De gros efforts ont été fait pour simplifier la syntaxe de certaines fonctions qui possèdent moins d'enchainements pour le même résultat. Ainsi pour obtenir l'axe suivant.

Avec la version 3

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.axis text {
  font: 10px sans-serif;
}
d3.select(".axis")
.call(d3.svg.axis()
.scale(x)
.orient("bottom"));

Avec la version 4

d3.select(".axis")
	.call(d3.axisBottom(x));

La nouvelle version fournit également un style CSS par défaut plutôt propre.

Modularité

Le dernier changement sur lequel nous nous penchons concerne la modularité de D3JS. Si vous ne voulez utiliser que les options de sélection de D3JS vous pouvez importer uniquement celles-ci.

<script src="https://d3js.org/d3-selection.v1.js">

Vous pouvez ainsi alléger la taille de vos pages et améliorer leur vitesse de chargement. Comme le changelog l'indique plusieurs méthodes existent pour concaténer toutes les sous-librairies dont vous pourriez avoir besoin.

Méthodologie de mise à niveau

Comme nous venons de le voir, les modifications sont assez importantes et de nature différentes. Si un simple Remplacer suffit pour changer un d3.scale.linear en d3.scaleLinear, ça sera plus délicat pour les améliorations de lisibilité et l'intégration d'un CSS par défaut. L'objectif est bien de simplifier notre code et nous ferons le maximum en ce sens. Nous choisissons donc de procéder de manière systématique et de vérifier le code de tous nos tutoriaux. Pour cela quatre étapes simples :

  1. Remplacer la librairie importer dans un tutoriel
  2. Vérifier et corriger toutes les erreurs de compilation
  3. Vérifier s'il n'y a pas d'améliorations/simplifications que nous pourrions apporter en nous référant au changelog
  4. Vérifier qu'aucune régression ne soit apparue

Ainsi pour le tutoriel Premiers Pas, seule la première étape est nécessaire. Sur le deuxième (Map), nous rencontrons une première erreur (il faut utiliser les outils de developpement des différents navigateurs) :

TypeError: d3.geo is undefined
	var path = d3.geo.path();

Sans même réfléchir, nous copions la fonction en erreur d3.geo.path et réalisons une recherche sur la page du changelog. On apprend que cette fonction est devenue d3.geoPath. Après correction, une nouvelle erreur pour d3.geo.conicConformal et une réponse de même nature dans le changelog (à transformer en d3.geoConicConformal). Maintenant la carte du tutoriel s'affiche bien et le comportement (apparition du tooltip) est correct. Néanmoins la carte affichée est plus grande qu'avec la version 3.x. Pour obtenir une carte de la même taille (ou presque) il faut modifier le scale de la projection.

var projection = d3.geoConicConformal()
	.center([2.454071, 46.279229])
	.scale(2600) // Précédement la valeur était 3000
	.translate([width / 2, height / 2]);

Passons à la partie un peu plus difficile qui vise à identifier des améliorations possibles. Pour cela regardons en détail les librairies de D3JS que nous utilisons dans ce tutoriel.

A ce stade nous pouvons considérer que nous avons terminé la migration de ces deux premiers tutoriels.

Liste complète des modification

Dans cette partie nous allons étudier toutes les modifications que l'on doit/peut apporter à chacun des tutoriels utilisant D3JS de ce site.

Carte choroplèthe

Nous commencons par reproduire les modifications du précédent tutoriel

L'erreur suivante concerne la fonction d3.scale.quantile, elle a été remplacé par d3.scaleQuantile.

var quantile = d3.scale.quantile()
	.domain([0, d3.max(csv, function(e) { return +e.POP; })])
	.range(d3.range(9));

Avant d'effectuer la correction, nous allons regarder le changelog concernant d3-scale. Il est assez complet mais outre un renommage il n'y a pas d'amélioration à apporter. Même chose pour d3.scale.linear qui devient d3.scaleLinear.

En revanche l'erreur suivante (et la dernière de ce tutoriel) est intéressante.

Avec la version 3

.axis {
	shape-rendering: crispEdges;
	font: 10px sans-serif;
}

.axis path, .axis line {
	fill: none;
	shape-rendering: crispedges;
	stroke: #000000;
}
var legendAxis = d3.svg.axis()
	.scale(legendScale)
	.orient('right')
	.tickSize(6)
	.ticks(6);

legendLabels = svg.append('g')
	.attr('transform', 'translate(550, 150)')
	.attr('class', 'y axis')
	.call(legendAxis);

Avec la version 4

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

Le résultat est strictement identique que ce soit pour la partie CSS (taille, police,...) que pour le comportement. On peut même se passer de l'appel à ticks(6) et obtenir une légende avec 14 valeurs. Nous supprimons donc 17 lignes de code, plutôt sympa. Notez que la variable legendAxis définit maintenant l'axe et les libellés.

Ce tutoriel ne l'utilise pas mais il peut être utile de mettre en oeuvre une fonctionnalité de zoom et de déplacement sur une carte. C'est le cas d'un exemple que nous avions réalisé au niveau des communes (attention c'est consommateur de ressources) : Zoom et déplacement sur les communes françaises. Voici le code avant/après et encore une fois on peut apprécier la simplicité de la version 4. Pour information, communes est un groupe contenant un path par commune.

Avec la version 3

var zoom = d3.behavior.zoom()
	.on("zoom", function() {
		communes.attr("transform", "translate(" +
			d3.event.translate.join(",") + ")scale(" + d3.event.scale + ")");
		communes.selectAll("path")
			.attr("d", path.projection(projection));
	});

svg.call(zoom);

Avec la version 4

var zoom = d3.zoom()
	.on("zoom", function() {
		communes.attr("transform", d3.event.transform);
	});

svg.call(zoom);

Histogramme (Bar Chart)

Avant de faire la modification habituel (d3.scale.linear -> d3.scaleLinear) la première erreur concerne le code suivant. ordinal.rangeBands a été remplacé par scaleBand.

d3.scale.ordinal()
	.rangeBands([0, width], .1);

On utilise directement la fonction range et non plus rangeBands et la fonction padding doit être ajoutée pour espacer les barres.

d3.scaleBand()
	.range([0, width])
	.padding(0.1);

La gestion des axes est toujours aussi simplifiée. Le code de la version 4 est présent après qu'on ait pu définir le domaine, c'est-à-dire après avoir charger nos données.

Avec la version 3

.axis path, .axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}
var xAxis = d3.svg.axis()
	.scale(x)
	.orient("bottom")
	.tickSize(0);

var yAxis = d3.svg.axis()
	.scale(y)
	.orient("left");

svg.append("g")
	.attr("class", "x axis")
	.attr("transform", "translate(0," + height + ")")
	.call(xAxis)
	.selectAll("text")  
		.style("text-anchor", "end")
		.attr("dx", "-5")
		.attr("dy", ".15em")
		.attr("transform", function(d) {return "rotate(-65)"});

svg.append("g")
	.attr("class", "y axis")
	.call(yAxis);

Avec la version 4

svg.append("g")
	.attr("transform", "translate(0," + height + ")")
	.call(d3.axisBottom(x).tickSize(0))
	.selectAll("text")	
		.style("text-anchor", "end")
		.attr("dx", "-.8em")
		.attr("dy", ".15em")
		.attr("transform", "rotate(-65)");

svg.append("g")
	.call(d3.axisLeft(y).ticks(6));

La dernière erreur à traiter pour ce tutoriel vient des modifications apportées au niveau de l'échelle horizontal. Lorsque l'on ajoute nos rectanges leur largeur ne se définit plus de la même façon, il faut remplacer .attr("width", x.rangeBand()) par .attr("width", x.bandwidth()). Après cette dernière correction tout fonctionne parfaitement. Il faut cependant noter que sans définition CSS de notre part la police et sa taille ont évolué entre les deux versions de D3JS.

Ligne (Linear Chart)

On se doute bien que le code de ce tutoriel va diminuer et devenir plus lisible. Commençons par les erreurs habituelles

Nous ne reproduisons pas ici la modification habituelle sur la construction des axes. Le code fonctionne ensuite correctement tout en offrant un résultat légèrement différent en terme de police, taille des caractères et nombre de lignes horizontales.

Lignes (Linear Chart)

Ce tutoriel reprend les modifications évoquées dans le précédent et répétées sur les deux courbes et axes.

Carte choroplèthe (non linéaire)

On commence par les modifications habituelles

Un élément important à noter est que cette fois-ci nous n'avons pas eu à modifier le scale de la projection. Nous retrouvons également la modification habituelle concernant l'axe gradué qui n'est pas reprise ici.

Bulles (Bubble Chart)

Nous avons décidé de réécrire entièrement ce tutoriel afin qu'il soit plus pertinent. Nous ne présentons donc pas ici les changements apportés.

Tooltip

Il n'y a rien à modifier pour faire fonctionner ce tutoriel avec D3JS en version 4.

Animation & Transition

Les transformations habituelles

Easing avec la version 3

d3.select(this)
	.transition()
	.duration(2000)
	.ease("linear")
	.attr("cx", 850);

Easing avec la version 4

d3.select(this)
	.transition()
	.duration(2000)
	.ease(d3.easeLinear)
	.attr("cx", 850);

La fonction ease ne prend plus en paramètre une chaine de caractères mais directement une fonction. Cela permet de développer soi-même des fonctions de transition basées sur d'autres formules mathématiques que celles disponibles dans la librairie.

Start & End avec la version 3

circleStartEnd.transition()
	.duration(2000)
	.delay(750)
	.attr("cx", 850)
	.each('start',function(){ d3.select(this).style("opacity", "0.2"); })
	.each('end',  function(){ d3.select(this).style("opacity", "1"); });

Start & End avec la version 4

circleStartEnd.transition()
	.duration(2000)
	.delay(750)
	.attr("cx", 850)
	.on('start',function(){ d3.select(this).style("opacity", "0.2"); })
	.on('end',  function(){ d3.select(this).style("opacity", "1"); });

La fonction each devient on pour être plus conforme à la nomenclature événementielle.

Enchainement avec la version 3

circleMultiDelay
	.transition()
	.duration(500)
	.attr("cx", 850)
	.transition()
	.duration(500)
	.delay(500)
	.attr("cx",150);

Enchainement avec la version 4

circleMultiDelay
	.transition()
	.duration(500)
	.attr("cx", 850)
	.transition()
	.duration(500)
	.attr("cx",150);

Dans la version 3 lorsque l'on devait enchainer plusieurs transitions il fallait ajouter la fonction delay afin que la transition suivante ne s'exécute qu'à la fin de la première transition. Avec la version 4 ce n'est plus nécessaire, les transitions s'enchainent les unes après les autres et non pas simultanément.

Ligne avancée (Linear Chart)

Il n'y a rien de très particulier pour ce tutoriel, nous reprenons simplement les modifications précédentes. Les spécificités (comme le dégradé sous la courbe ou le tooltip en SVG) fonctionnent sans modification.

In Progress

A suivre : D3JS - Map Avancée Voir le tutoriel

comments powered by Disqus