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: false
permet de ne pas afficher le code dans le document finalcode-tools: true
permet de donner accès au code source en cliquant sur</> Code
en haut à droite de la page.page-layout: full
permet 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
Inputs
permettant à 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.
= FileAttachment("data/world.json").json()
world = FileAttachment("data/worldbank_data.csv").csv() stats
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.
= require("geotoolbox@latest") geo
ou (si vous souhaitez travailler sans connexion internet)
= require("./lib/geotoolbox.js") geo
Puis
= geo.simplify(world, {k: simpl}) world2
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).
= require("bertin@latest") bertin
ou (si vous souhaitez travailler sans connexion internet)
= require("./lib/bertin.js") bertin
= stats.filter(d => d.date == year)
statsyear = bertin.merge(world2, "id", statsyear, "iso3c") data
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.
= d3.max(stats.filter(d => d.date == 2019), d => +d[indicator]) varmax
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.
= meta.map((d) => [d.indicator, d.shortcode]).find((d) => d[1] == indicator)[0] + " in " + year title
Puis, 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.
.draw({
bertinparams: {projection: proj + `.rotate([${x}, ${y}])`, clip: true },
layers:[
type: "header", text: title},
{type: "bubble",
{geojson: data,
values: indicator,
fill: color,
fixmax: varmax,
,
ktooltip: ["$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.
.table(statsyear, { columns: [
Inputs"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.
= Inputs.range([5, 30], {label: "Nombre de pays représentés", step: 1})
viewof topnb = statsyear.sort((a, b) => d3.descending(+a[indicator], +b[indicator]))
top .slice(0, topnb)
.plot({
PlotmarginLeft: 60,
marks: [
.barY(top, {
Plotx: "iso3c",
y: indicator,
sort: { x: "y", reverse: true },
fill: color
,
}).ruleY([0])
Plot
] })
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 :
= Scrubber(d3.range(1990, 2019), { autoplay: false }) viewof year
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]) {
= interval[0];
i
}
} }
Puis, dans la partie menu, remplacer
= Inputs.range( [-90, 90], {value: 0, step: 1, label: "Rotation (y)"} ) viewof y
par
= timer({ speed: 10, step: 9, interval: [-90, 90] }) y
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]