prez = FileAttachment("data/worldbank_meta.csv").csv()
Inputs.table(prez, { columns: [
"shortcode",
"indicator",
"indicator_desc",
],
header: {
shortcode: "id",
indicator: "indicateur",
indicator_desc: "description",
}})TP2 - Réaliser une single page application avec Quarto et bertin
L’objectif de ce TP est d’apprendre à créer des cartes interactives avec Quarto et la bibliothèque JavaScript bertin.
Responsables pédagogiques
Manhamady OUEDRAOGO (Burkina Faso) & Nicolas LAMBERT (France)
Ont participé à l’élaboration de ce module
Claude GRASLAND (France), Souleymane Sidi TRAORE (Mali), Malika MADELIN (France), Sébastien REY-COYREHOURCQ (France), Vakaramoko BAMBA (Côte d’Ivoire), Hugues PECOUT (France), Yentougle MOUTORE (Togo), Bénédicte GARNIER (France), Côomlan Charles HOUNTON (Bénin), Pauline GLUSKI (France)
Introduction
Objectifs
Le but de ce TP est de réaliser une application de cartographie à l’échelle mondiale sur une page web pleine page. d’un point de vue technique, on parlera de single page application (SPA).
Les bases et prérequis nécessaires à la bonne compréhension de ce TP sont disponibles via ce cours introductif et ce premier TP. Merci de procéder dans cet ordre.
Données
Pour cet exercice, nous nous basons sur un tableau de données contenant 6 variables issues de la banque mondiale, disponibles de 1990 à 2021. Elles ont été harmonisées par Claude Grasland. Les voici ci-dessous.
1. Créer un document Quarto
Dans Rstudio, créez un document Quarto avec l’entête suivante :
---
title: "World Explorer"
format:
html:
echo: false
code-tools: true
page-layout: full
---
Le format de sortie est le format html.
echo: falsepermet de ne pas afficher le code dans le document finalcode-tools: truepermet de donner accès au code source en cliquant sur</> Codeen haut à droite de la page.page-layout: fullpermet de définir une disposition pleine page
À présent, copiez et collez en dessous l’ensemble du bloc suivant :
```{ojs}
//| panel: sidebar
"Le menu"
```::: {.panel-tabset}
## Carte
```{ojs}
"La carte"
```## Données
```{ojs}
"Les données"
```## Top 10
```{ojs}
"le graphique"
```:::
```{ojs}
"Annexe technique"
```Cliquez sur Render (ou Ctrl+Shift+K) pour voir apparaitre la structure de votre application. Elle est composée de 6 parties :
- Le titre (défini dans l’entête de votre document
title: "World Explorer"). À vous de remplacer le titre par la chaîne de caractère de votre choix. - Le menu (sur la gauche). C’est dans ce chunk que nous allons placer des
Inputspermettant à l’utilisateur d’interagir avec la carte. - La carte (sur la droite). C’est dans ce chunk que nous allons dessiner une carte avec la bibliothèque
bertin. - Les données (sur la droite). C’est dans ce chunk que nous allons afficher le tableau de données derrière la carte.
- Un graphique (sur la droite). C’est dans ce chunk que nous allons afficher un bar plot.
- Les annexes techniques (en bas). Dans ce chunk, on écrira tout ce qui est nécéssaire à l’élaboration de l’appli (import des données, etc, mais qu’on ne souhaite pas afficher).
Rappelez-vous qu’avec Observable JavaScript, l’ordre d’écriture n’a pas d’importance. On peut donc écrire du code à la fin du document, comme une annexe technique.
La classe {.panel-tabset} permet de positionner la carte, les données et le graphique dans 3 onglets différents.
- Sauvegardez.
2. Les données
Téléchargez les données et mettez-les dans un repertoire data.
Dans le chunck de l’annexe technique, importez les données géométriques et attributaires qui se trouvent dans le répertoire data.
world = FileAttachment("data/world.json").json()
stats = FileAttachment("data/worldbank_data.csv").csv()Comme dit précédement, les données contiennent des indicateurs à plusieurs dates.
4. Mise en forme des données
Avant de construire la carte, nous avons besoin de manipuler un peu les données. On effectue ces opérations dans la partie annexe technique. Plusieurs opérations son nécessaires:
Tout d’abord, on réalise une généralisation du fond de carte avec la fonction simplify de la bibliothèque geotoolbox qui permet de faire simplement la plupart des opérations SIG utiles en cartographie (voir). On peut la charger directement ou la télécharger ici pour travailler sans connexion internet. Le niveau de simplification sera déterminé par l’utilisateur dans le slider simpl.
geo = require("geotoolbox@latest")ou (si vous souhaitez travailler sans connexion internet)
geo = require("./lib/geotoolbox.js")Puis
world2 = geo.simplify(world, {k: simpl})Puis, avec l’instruction JavaScript filter, on crée un tableau contenant uniquement l’année sélectionnée et on effectue une jointure entre les données et le fond de carte grâce à la bibliothèque bertin (on peut aussi télécharger la bibliothèque ici).
bertin = require("bertin@latest")ou (si vous souhaitez travailler sans connexion internet)
bertin = require("./lib/bertin.js")statsyear = stats.filter(d => d.date == year)
data = bertin.merge(world2, "id", statsyear, "iso3c")On a aussi besoin de récupérer une valeur de référence pour chaque indicateur pour rendre comparables la taille des symboles d’une année à l’année sur l’autre. On récupère la valeur maximale de l’année 2019.
varmax = d3.max(stats.filter(d => d.date == 2019), d => +d[indicator])Après ces opérations, l’objet data contient les géométries généralisées et les données pour l’année sélectionnée
5. Réalisation de la carte
Ici, on réalise la carte dans la chuck carte avec la bibliothèque bertin.
Tout d’abord, on fabrique le titre en concaténant le nom de la variable et l’année.
title = meta.map((d) => [d.indicator, d.shortcode]).find((d) => d[1] == indicator)[0] + " in " + yearPuis, on dessine la carte avec la fonction draw de la bibliothèque bertin. Libre à vous de personnaliser la mise en page et les couleurs en modifiant/ajoutant quelques paramètres.
bertin.draw({
params: {projection: proj + `.rotate([${x}, ${y}])`, clip: true },
layers:[
{type: "header", text: title},
{type: "bubble",
geojson: data,
values: indicator,
fill: color,
fixmax: varmax,
k,
tooltip: ["$name",d => d.properties[indicator]]
},
{geojson: world, fill: "#CCC"},
{type: "graticule"},
{type: "outline"}
]})6. Affichage des données
Ici, on ajoute un tableau de données dans la chuck données. On sélectionne les colonnes à afficher.
Inputs.table(statsyear, { columns: [
"country",
"capital_city",
"region",
indicator
]})7. Réalisation du graphique
On souhaite réaliser un diagramme en barres avec les N pays qui ont les plus fortes valeurs sur l’indicateur séléctionné. Nous récupérons donc l’objet stats qui contient les données, nous le trions, et ne gardons que les N premières valeurs. N est défini dans un slider. Le graphique est réalisé avec la bibliothèque Plot.
Ajoutez le code ci-dessous chuck graphique.
viewof topnb = Inputs.range([5, 30], {label: "Nombre de pays représentés", step: 1})
top = statsyear.sort((a, b) => d3.descending(+a[indicator], +b[indicator]))
.slice(0, topnb)
Plot.plot({
marginLeft: 60,
marks: [
Plot.barY(top, {
x: "iso3c",
y: indicator,
sort: { x: "y", reverse: true },
fill: color
}),
Plot.ruleY([0])
]
})7. C’est fini !
Appuyez sur Render pour voir le résultat. La solution est disponible ici.
8. Aller plus loin
Cette application est largement perfectible. Une piste possible d’amélioration serait de remplacer le slider des années par un bouton play pour faire une vraie carte animée. Une solution consiste à utiliser l’input scrubber dévelopé par Mike Bostock : https://observablehq.com/@mbostock/scrubber.
Deux options sont possibles. Vous pouvez importer la fonction depuis Observable.
import {Scrubber} from "@mbostock/scrubber"Ou copier le code de la fonction qui se trouve sur cette page.
Puis, remplacez le slider des années par :
viewof year = Scrubber(d3.range(1990, 2019), { autoplay: false })Et pour encore plus de fun 🥳, vous pouvez vous amusez à faire varier le centre de projection automatiquement. Pour cela, vous pouvez ajouter la fonction suivante dans la partie Annexe technique.
function* timer({ speed = 1000, step = 1, interval = [-180, 180] } = {}) {
let i = interval[0];
while (true) {
yield Promises.delay(speed, (i = i + step));
if (i >= interval[1]) {
i = interval[0];
}
}
}Puis, dans la partie menu, remplacer
viewof y = Inputs.range( [-90, 90], {value: 0, step: 1, label: "Rotation (y)"} )par
y = timer({ speed: 10, step: 9, interval: [-90, 90] })Vous pouvez faire varier les paramètres speed et step pour changer la vitesse (qui dépend des capacités de votre ordinateur). Vous pouvez aussi remplacer le slider Rotation (x) en utilisant interval: [-180, 180]