Dans cette activité nous allons commencer à créer des diagrammes dynamiques. Avant de rentrer dans le vif du sujet, nous allons un peu revenir en arrière avec un exemple très simple :


var tab=[30,150,27,85,3,12];

var body=d3.select("body");
var para=body.selectAll("p").data(tab);
para.enter()
    .append("p")
    .text(function(d,i){
        return ("La valeur n°"+i+" du tableau est : "+d)
    });
            

Rien de difficile ici, mais je voudrai revenir en détail sur la méthode enter() :

selectAll("p").data(tab) permet d'associer les données contenues dans le tableau avec des balises <p>. Il y aura autant de balises <p> qu'il y a de données dans le tableau. Nous avons ici 6 données, il y aura donc, au bout du compte, 6 balises <p>.

Si ces balises <p> n'existent pas, la méthode enter() va se charger de les créer en appliquant les méthodes qui lui sont "associées" (dans notre exemple nous aurons donc .append("p").text(function(d,i){return ("La valeur n°"+i+" du tableau est : "+d)}); qui permet de créer une balise <p> (append("p")) et qui complète cette balise <p> avec le texte "La valeur n°... du tableau est : ..." (.text(function(d,i){return ("La valeur n°"+i+" du tableau est : "+d)});).

Notez bien que si les 6 balises <p> existaient déjà dans ma page, la méthode enter() ne serait pas "utilisée". Il est fondamental de bien comprendre que la méthode enter() est utilisée uniquement s'il manque des balises par rapport au nombre de données présentes dans le tableau.

À faire vous même 9.1

script.js


var tab=[30,150,27,85,3,12];

var body=d3.select("body");
body.append("p").text("coucou, je ne suis pas comme les autres");
var para=body.selectAll("p").data(tab);
para.enter()
    .append("p")
    .text(function(d,i){
        return ("La valeur n°"+i+" du tableau est : "+d)
    });
            

Analysez et testez ce code.


Il y a 6 données dans le tableau, vu le body.selectAll("p").data(tab), il y a donc besoin de 6 balises <p>. Il y déjà une balise <p> présente sur la page (body.append("p").text("coucou, je ne suis pas comme les autres"), il y a donc besoin de créer uniquement 5 balises. Ces 5 balises seront créées selon le modèle défini par la méthode enter() (.append("p").text(function(d,i){return ("La valeur n°"+i+" du tableau est : "+d)});. Comme vous pouvez le constater, la première valeur du tableau (30) est associée à la balise <p> préexistante. La première balise <p> créée par la méthode enter() est associée à la seconde valeur du tableau (150).

Nous allons voir maintenant comment actualiser la balise préexistante :

À faire vous même 9.2

script.js


var tab=[30,150,27,85,3,12];

var body=d3.select("body");
body.append("p").text("coucou, je ne suis pas comme les autres");
var para=body.selectAll("p").data(tab);
para.text(function(d,i){
        return ("coucou, la valeur n°"+i+" du tableau est : "+d);
    });
para.enter()
    .append("p")
    .text(function(d,i){
        return ("La valeur n°"+i+" du tableau est : "+d)
    });
        

Analysez et testez ce code.


La ligne située juste après var para=body.selectAll("p").data(tab); (para.text(function(d,i){return ("coucou, la valeur n°"+i+" du tableau est : "+d);});) permet de mettre à jour la balise <p> préexistante.

Évidemment, s'il existe plusieurs balises <p> préexistantes, elles seront toutes mises à jour.

À faire vous même 9.2

script.js


var tab=[30,150,27,85,3,12];

var body=d3.select("body");
body.append("p").text("coucou, je ne suis pas comme les autres");
body.append("p").text("coucou, je ne suis une autre balise pré-existante");
var para=body.selectAll("p").data(tab);
para.text(function(d,i){
        return ("coucou, la valeur n°"+i+" du tableau est : "+d);
    });
para.enter()
    .append("p")
    .text(function(d,i){
        return ("La valeur n°"+i+" du tableau est : "+d)
    });
        

Analysez et testez ce code.


Comme vous pouvez le constater, la méthode enter() a créé 3 balises et les 2 balises préexistantes ont bien été mis à jour.

Il est très important d'avoir bien compris tout ce qu'il y a ci-dessus avant de pouvoir poursuivre, n'hésitez pas à poser des questions si nécessaire.


Nous allons maintenant nous intéresser à la mise à jour des données.

À faire vous même 9.2

script.js


var tab_0=[30,150,27,85,3,12];
var tab_1=[25,120,40,75,15,23];
var tab_2=[40,110,80,76,26,18];
var tab=[tab_0,tab_1,tab_2]
var n=0;

var body=d3.select("body");
function update(data){
	var para=body.selectAll("p").data(data);
	para.text(function(d,i){
        	return ("La valeur n°"+i+" du tableau est : "+d);
    	});
	para.enter()
    	.append("p")
    	.text(function(d,i){
        	return ("La valeur n°"+i+" du tableau est : "+d);
    	});
}
update(tab[0]);
setInterval(function() {
	if (n==2){
		n=-1;
	}
	n=n+1;
	update(tab[n]);
}, 3000);
        

Analysez et testez ce code.


Avant de nous lancer dans l'analyse du code ci-dessus, il est nécessaire d'apporter quelques précisions sur la méthode JavaScript setInterval :

La méthode setInterval prend 2 paramètres : une fonction f et une durée dt. Le principe est simple, setInterval permet d'exécuter la fonction f tous les dt millisecondes.

Si nous prenons l'exemple ci-dessus :


setInterval(function() {
	if (n==2){
		n=-1;
	}
	n=n+1;
	update(tab[n]);
}, 3000);
        

la fonction anonyme function() {if (n==2){n=-1;}n=n+1;update(tab[n]); sera exécutée toutes les 3 secondes (3000 millisecondes).

Reprenons l'étude du code du "À faire vous même 9.2" en repartant du début :


var tab_0=[30,150,27,85,3,12];
var tab_1=[25,120,40,75,15,23];
var tab_2=[40,110,80,76,26,18];
var tab=[tab_0,tab_1,tab_2]
        

Nous avons 3 jeux de données, ces jeux de données sont regroupés dans le tableau tab (tab[0] correspond au tableau tab_0, tab[1] correspond au tableau tab_1...

Nous avons ensuite créé une fonction update(data) qui contient le code suivant :


function update(data){
	var para=body.selectAll("p").data(data);
	para.text(function(d,i){
        	return ("La valeur n°"+i+" du tableau est : "+d);
    	});
	para.enter()
    	.append("p")
    	.text(function(d,i){
        	return ("La valeur n°"+i+" du tableau est : "+d);
    	});
}
        

Rien de nouveau ici : nous associons les données passées en paramètre de la fonction update avec des balises <p> (var para=body.selectAll("p").data(data);).

Les paragraphes qui existent déjà sont mis à jour : para.text(function(d,i){return ("La valeur n°"+i+" du tableau est : "+d);});

enter() permet de créer les paragraphes manquant : para.enter().append("p").text(function(d,i){return ("La valeur n°"+i+" du tableau est : "+d);});

update(tab[0]); permet d'appeler la fonction update une première fois, les données passées en paramètre correspondent aux données présentes dans la tableau tab_0

Nous trouvons ensuite la méthode setInterval qui va nous permettre d'exécuter la fonction anonyme toutes les 3 secondes :


setInterval(function() {
	if (n==2){
		n=-1;
	}
	n=n+1;
	update(tab[n]);
}, 3000);
        

Cette fonction anonyme permet d'appeller la fonction update en changeant à chaque fois le tableau de données qui est passé en paramètre (tab_1, tab_2 puis de nouveau tab_0 et ainsi de suite...)

Résumons le déroulé des événements :

Mais que se passe-t-il si, par exemple, le tableau tab_2 contient seulement 5 données ?

À faire vous même 9.3

script.js


var tab_0=[30,150,27,85,3,12];
var tab_1=[25,120,40,75,15,23];
var tab_2=[40,110,80,76,26];
var tab=[tab_0,tab_1,tab_2]
var n=0;

var body=d3.select("body");
function update(data){
	var para=body.selectAll("p").data(data);
	para.text(function(d,i){
        	return ("La valeur n°"+i+" du tableau est : "+d);
    	});
	para.enter()
    	.append("p")
    	.text(function(d,i){
        	return ("La valeur n°"+i+" du tableau est : "+d);
    	});
}
update(tab[0]);
setInterval(function() {
	if (n==2){
		n=-1;
	}
	n=n+1;
	update(tab[n]);
}, 3000);
        

Analysez et testez ce code.


Comme vous pouvez le constater, lors du passage de tab_1 vers tab_2 le dernier paragraphe ("La valeur n°5 du tableau est : 23") reste en place : logique, le dernier tableau (tab_2) a besoin uniquement de 5 paragraphes, le 6e paragraphe reste en place sans modification.

Il est possible de supprimer un paragraphe quand aucune donnée ne lui est associé à l'aide de la méthode exit() :

À faire vous même 9.3

script.js


var tab_0=[30,150,27,85,3,12];
var tab_1=[25,120,40,75,15,23];
var tab_2=[40,110,80,76,26];
var tab=[tab_0,tab_1,tab_2]
var n=0;

var body=d3.select("body");
function update(data){
	var para=body.selectAll("p").data(data);
	para.text(function(d,i){
        	return ("La valeur n°"+i+" du tableau est : "+d);
    	});
	para.enter()
    	.append("p")
    	.text(function(d,i){
        	return ("La valeur n°"+i+" du tableau est : "+d);
    	});
    para.exit().remove();
}
update(tab[0]);
setInterval(function() {
	if (n==2){
		n=-1;
	}
	n=n+1;
	update(tab[n]);
}, 3000);
        

Analysez et testez ce code.


para.exit() permet de tenir compte des paragraphes non utilisés (quand il y a plus de paragraphes que de données).

para.exit().remove() permet de supprimer les paragraphes non utilisés.


Nous allons maintenant reprendre l'exemple traité dans le "À faire vous même 8.5" :


var tab=[2,6,8,23,30,27,8];
var body=d3.select("body");
var echelleX=d3.scale.ordinal()
                .domain(["lundi","mardi","mercredi","jeudi","vendredi","samedi","dimanche"])
                .rangeBands([30, 370]);
var echelleY=d3.scale.linear()
                .domain([0,30])
                .range([370,30]);
var xAxe = d3.svg.axis()
                  .scale(echelleX)
                  .orient("bottom");
var yAxe = d3.svg.axis()
                  .scale(echelleY)
                  .orient("left");
var svg=body.append("svg");
svg.attr({"width":"400px","height":"400px"})
svg.selectAll("rect")
    .data(tab)
    .enter()
    .append("rect")
    .attr({"fill":"blue","stroke":"black"})
    .attr("width",function(d,i){
        return (340/tab.length);
    })
    .attr("height",function(d,i){
        return (370-echelleY(d))
    })
    .attr("y",function(d,i){
        return (echelleY(d))
    })
    .attr("x",function(d,i){
        return (30+i*340/tab.length)
    });
svg.append("g")
    .style("font-family","sans-serif")
    .style("font-size","9px")
    .attr({"fill": "none","stroke": "black"})
    .attr("transform","translate(0,370)")
    .call(xAxe);
svg.append("g")
    .style("font-family","sans-serif")
    .style("font-size","11px")
    .attr({"fill": "none","stroke": "black"})
    .attr("transform","translate(30,0)")
    .call(yAxe);
        

Nous allons maintenant avoir 3 jeux de données (3 semaines de données) :

À faire vous même 9.4

script.js


var tab_0=[2,6,8,23,30,27,8];
var tab_1=[5,15,5,20,25,28,15];
var tab_2=[4,10,9,18,21,29,10];
var tab=[tab_0,tab_1,tab_2];
var n=0;

var body=d3.select("body");

var echelleX=d3.scale.ordinal()
                .domain(["lundi","mardi","mercredi","jeudi","vendredi","samedi","dimanche"])
                .rangeBands([30, 370]);
var echelleY=d3.scale.linear()
                .domain([0,30])
                .range([370,30]);
var xAxe = d3.svg.axis()
                  .scale(echelleX)
                  .orient("bottom");
var yAxe = d3.svg.axis()
                  .scale(echelleY)
                  .orient("left");
var svg=body.append("svg");
svg.attr({"width":"400px","height":"400px"});
svg.append("g")
    .style("font-family","sans-serif")
    .style("font-size": "9px")
    .attr({"fill": "none","stroke": "black"})
    .attr("transform","translate(0,370)")
    .call(xAxe);
svg.append("g")
    .style("font-family","sans-serif")
    .style("font-size","11px")
    .attr({"fill": "none","stroke": "black"})
    .attr("transform","translate(30,0)")
    .call(yAxe);
function update(data){
	var rect=svg.selectAll("rect").data(data);
	rect.attr("height",function(d,i){
        	return (370-echelleY(d))
    	})
    	.attr("y",function(d,i){
        	return (echelleY(d))
    	});
    rect.enter()
    	.append("rect")
    	.attr({"fill":"blue","stroke":"black"})
    	.attr("width",function(d,i){
        	return (340/data.length);
    	})
    	.attr("height",function(d,i){
        	return (370-echelleY(d))
    	})
    	.attr("y",function(d,i){
        	return (echelleY(d))
    	})
    	.attr("x",function(d,i){
        	return (30+i*340/data.length)
    	});
}
update(tab[0]);
setInterval(function() {
	if (n==2){
		n=-1;
	}
	n=n+1;
	update(tab[n]);
}, 3000);
        

Analysez et testez ce code.


Comme vous pouvez le constater, le principe est identique à ce que nous avons vu plus haut.

Il est possible d'ajouter un titre qui, lui aussi, changera dynamiquement :

À faire vous même 9.5

Réalisez un programme permettant d'obtenir ceci :

Pour vous aider : il est possible d'effacer le contenu d'une balise en écrivant text(""), par exemple :


titre=svg.append("text").text("coucou");
titre.text("") //permet d'effacer "coucou"
        

Afin de rendre les animations plus "douces", D3 propose d'ajouter des transitions. Cela n'est pas très compliqué à mettre en place et je vous laisse étudier la question avec la documentation officielle et une petite vidéo qui explique tout.

À faire vous même 9.6

Réalisez un programme permettant d'obtenir ceci :