# Composants de visualisation et gabarits

:::info{title="En bref"}
- **Pour qui** : toute personne qui ajoute un graphique au portail (équipes du hackathon, rédacteurs de l'onglet DataViz).
- **Ce que vous trouverez** : les 7 composants de graphique, une démo de chacun, le format JSON attendu et un squelette à copier dans une page `.mdx`.
- **Données utilisées** : des agrégats réels du bucket SSP Cloud (écocompteurs, nuitées, hôtellerie, lits, catalogue des sites), cités sous chaque démo.
- **Mise à jour** : 09/11/2026.
:::

Ces composants dessinent des graphiques **interactifs et accessibles** directement dans une page du portail. Vous n'écrivez pas de code de dessin : vous préparez un petit fichier JSON, puis vous écrivez une seule balise dans la page.

Chaque graphique affiche un titre, une légende dès qu'il y a deux séries, la source, une info-bulle au survol (et au clavier), et un bouton **« Voir les données »** qui ouvre le tableau des valeurs.

## Le chemin d'une donnée jusqu'au graphique

```mermaid
flowchart LR
    B[("Bucket SSP Cloud (données brutes)")]
    S("Script Python : calcule un agrégat")
    J["JSON dans docs/public/dataviz/nom-de-page/"]
    P["Page .mdx : une balise, ex. LineChart"]
    F(["Figure : graphique, légende, source, tableau"])
    B --> S --> J --> P --> F
```

Comment lire : de gauche à droite, la donnée brute reste dans le bucket ; seul un agrégat (une somme, une moyenne, un comptage) est copié dans le portail, sous forme de JSON.

:::caution{title="Jamais de données brutes ni personnelles"}
Le dossier `docs/public` est publié tel quel sur le site. N'y mettez que des **agrégats** (par mois, par commune, par catégorie). Aucune ligne individuelle, aucune adresse électronique, aucun numéro de téléphone, aucun identifiant. Le fichier des ventes de forfaits contient des données personnelles : uniquement des totaux.
:::

## Ajouter un graphique en 4 étapes

1. **Choisir la forme** avec le tableau ci-dessous.
2. **Écrire le JSON** au format du composant, dans `docs/public/dataviz/<nom-de-page>/<nom>.json`. Le plus simple est de le produire avec un script de `docs/scripts/`, pour pouvoir le recalculer.
3. **Insérer la balise** dans la page, par exemple `<LineChart src="/dataviz/ma-page/passages.json" />`. Aucun `import` n'est nécessaire.
4. **Écrire la phrase « Comment lire »** juste sous le graphique, puis contrôler : `node scripts/check-mdx.mjs 09-dataviz/ma-page.mdx`.

### Choisir le bon graphique

| La question du lecteur | Composant | Exemple |
|---|---|---|
| Quelques chiffres à retenir | `KpiRow` | passages comptés, nombre de compteurs |
| Comment ça évolue dans le temps ? | `LineChart` | passages mensuels 2021-2026 |
| Qui a le plus, qui a le moins ? | `BarChart` horizontal | communes les mieux dotées en lits |
| Comment se répartit un total, année après année ? | `BarChart` empilé | nuitées des résidents et des non-résidents |
| Quand est-ce le plus chargé (jour × heure, année × mois) ? | `Heatmap` | nuitées par mois et par année |
| Au-dessus ou au-dessous d'une référence ? | `Heatmap` ou `ChoroplethMap` en échelle divergente | écart à la moyenne |
| Deux mesures vont-elles ensemble ? | `ScatterPlot` | lits touristiques et manifestations |
| Où, sur le territoire, en valeur par commune ? | `ChoroplethMap` | sites recensés par commune |
| Où sont les lieux précis ? | `PointMap` | sites, compteurs, parkings |

Comment lire : partez de la question que se pose le lecteur, pas des données disponibles. Un seul chiffre important se montre en `KpiRow`, pas en graphique.

### Règles communes à tous les composants

- **`src` ou `data`** : `src` est l'adresse d'un JSON servi depuis `docs/public` (elle commence par `/`, sans `public`). `data` reçoit le même objet écrit directement dans la page. Si les deux sont donnés, `data` l'emporte.
- **Titre, sous-titre et source** viennent du JSON ; les attributs `title`, `subtitle` et `source` de la balise les remplacent si besoin.
- **Source obligatoire** : le nom du fichier du bucket (par exemple `Hébergements/Hébergements par commune.xlsx`), et « (calcul : portail) » quand le chiffre est recalculé.
- **« (déduit) »** dans le titre ou le nom de série pour toute valeur estimée ou calculée (écart, population présente, part…).
- **Couleurs** : ne choisissez pas de couleur. Elles viennent d'une palette fixe, vérifiée pour le daltonisme et pour les modes clair et sombre. Au-delà de 3 groupes dans un nuage ou une carte, les suivants passent en gris « Autres ».
- **Nombres** : écrivez des nombres bruts dans le JSON (`1160000`, pas `"1,16 M"`). Le composant les formate en français.
- **Au clavier** : la touche Tab donne le focus au graphique, les flèches parcourent les valeurs, Échap ferme l'info-bulle. Les lecteurs d'écran annoncent chaque valeur.

## KpiRow : chiffres clés

<KpiRow src="/dataviz/demo/kpi.json" />

Comment lire : chaque tuile donne un ordre de grandeur et, en petit, sa période ou sa portée. Ces chiffres viennent des analyses des écocompteurs et de la capacité d'hébergement (Insee 2026).

Format JSON (`value` est affiché tel quel : formatez-le vous-même) :

```json
{
  "items": [
    { "label": "Passages comptés", "value": "233 719 994", "hint": "du 01/01/2021 au 07/11/2026" },
    { "label": "Écocompteurs", "value": "408", "hint": "avec au moins un jour de données" }
  ]
}
```

Squelette à copier (les trois écritures sont équivalentes) :

```tsx
<KpiRow src="/dataviz/ma-page/kpi.json" />
<KpiRow items={[{ label: "Lits touristiques", value: "81 348", hint: "902 établissements" }]} />
<KpiRow data={{ items: [{ label: "Communes", value: 513 }] }} />
```

## LineChart : évolution dans le temps

<LineChart src="/dataviz/demo/ligne.json" />

Comment lire : chaque courbe est la somme des passages de tous les compteurs actifs ce mois-là. Survolez le graphique pour lire les deux valeurs d'un même mois. Le nombre de compteurs change dans le temps : une hausse peut venir d'un nouveau compteur.

Format JSON :

```json
{
  "title": "Passages mensuels comptés par les écocompteurs de l'Isère",
  "subtitle": "Somme de tous les compteurs actifs chaque mois",
  "x": ["2021-01", "2021-02", "2021-03"],
  "series": [
    { "name": "Piétons", "values": [1216562, 1484952, null] },
    { "name": "Cyclistes", "values": [437946, 555273, 759786] }
  ],
  "yLabel": "Passages par mois",
  "unit": "passages",
  "source": "Fréquentation/eco-compteur-Isère/comptages/historique_2021_2024.csv"
}
```

- `x` : libellés libres, ou dates ISO (`2024`, `2024-07`, `2024-07-14`) ; les dates sont alors écrites en français (« juillet 2024 ») et les graduations placées en début d'année ou de mois.
- `null` dans `values` : un trou dans les données, la courbe est coupée (jamais reliée à travers le vide).
- Options : `zero: false` pour ne pas partir de 0 (températures par exemple) ; `area: true` pour un aplat léger sous une courbe unique.
- 8 séries au maximum ; au-delà de 4, préférez plusieurs petits graphiques.

```tsx
<LineChart src="/dataviz/ma-page/evolution.json" />
<LineChart src="/dataviz/ma-page/evolution.json" height={200} title="Titre remplacé" />
```

## BarChart : comparer des quantités

### Barres groupées

<BarChart src="/dataviz/demo/barres-groupees.json" />

Comment lire : pour chaque jour, la barre bleue donne les piétons et la barre orange les cyclistes, en passages moyens par compteur. Les piétons culminent le samedi, les cyclistes en semaine.

### Barres horizontales

<BarChart src="/dataviz/demo/barres-horizontales.json" />

Comment lire : les communes sont classées de la plus dotée à la moins dotée ; la valeur est écrite au bout de chaque barre. Les deux stations de l'Oisans dépassent nettement Grenoble.

### Barres empilées

<BarChart src="/dataviz/demo/barres-empilees.json" />

Comment lire : la hauteur totale donne les nuitées hôtelières de l'année ; la part du bas, les résidents en France ; la part du haut, les non-résidents. Le creux de 2020-2021 correspond à la crise sanitaire.

Format JSON :

```json
{
  "title": "Nuitées hôtelières en Isère par an",
  "labels": ["2023", "2024", "2025"],
  "series": [
    { "name": "Résidents en France", "values": [1801610, 1749530, 1766500] },
    { "name": "Non-résidents", "values": [527820, 550300, 636530] }
  ],
  "orientation": "vertical",
  "stacked": true,
  "yLabel": "Nuitées",
  "unit": "nuitées",
  "source": "Hébergements/Atout France - Données de fréquentation hébergements collectifs - INSEE.xlsx"
}
```

- `orientation` : `"vertical"` (par défaut) ou `"horizontal"` (conseillé pour des libellés longs ou plus de 12 catégories).
- `stacked: true` empile les séries ; le total s'affiche dans l'info-bulle et le tableau.
- Les valeurs négatives sont acceptées (la barre descend sous zéro).
- Une seule série : une seule couleur, pas de légende (le titre dit ce qui est compté). Ne colorez jamais les barres selon leur valeur.

```tsx
<BarChart src="/dataviz/ma-page/classement.json" />
<BarChart src="/dataviz/ma-page/par-jour.json" height={220} />
```

## Heatmap : carte de chaleur

### Échelle séquentielle

<Heatmap src="/dataviz/demo/chaleur.json" />

Comment lire : une ligne par année, une colonne par mois ; plus la case est foncée (plus claire en mode sombre), plus il y a de nuitées. Deux saisons ressortent : l'hiver (décembre à février) et l'été (juillet-août).

### Échelle divergente

<Heatmap src="/dataviz/demo/chaleur-ecart.json" />

Comment lire : le gris veut dire « proche de la moyenne », le rouge « au-dessus », le bleu « au-dessous ». Plus la couleur est soutenue, plus l'écart est grand. L'écart est calculé par le portail (déduit).

Format JSON :

```json
{
  "title": "Nuitées touristiques en Isère, par mois",
  "rows": ["2022", "2023", "2024"],
  "cols": ["janv.", "févr.", "mars"],
  "values": [
    [1160000, 1996000, 1373000],
    [1173000, 1967000, null],
    [1369000, 1931000, 1560000]
  ],
  "unit": "nuitées",
  "scale": "sequential",
  "source": "Hébergements/Nuitées Touristiques par mois éch départ - Flux Vision.xlsx"
}
```

- `values` : un tableau par ligne, une valeur par colonne ; `null` donne une case « Sans donnée ».
- `scale` : `"sequential"` (une teinte, 5 classes calculées par quantiles) ou `"diverging"` (bleu, gris, rouge, 7 classes symétriques).
- Options : `midpoint` (point milieu de l'échelle divergente, 0 par défaut, par exemple 100 pour un indice), `breaks` (seuils imposés : 4 seuils en séquentiel, 3 écarts positifs en divergent), `legendTitle`.

```tsx
<Heatmap src="/dataviz/ma-page/jour-heure.json" />
```

## ScatterPlot : nuage de points

<ScatterPlot src="/dataviz/demo/nuage.json" />

Comment lire : chaque point est une commune ; plus il est à droite, plus elle a de lits touristiques ; plus il est haut, plus elle accueille de manifestations. La taille du point suit le nombre de sites recensés. Les axes sont logarithmiques : chaque graduation multiplie par 10.

Format JSON :

```json
{
  "title": "Hébergement et animation des communes de l'Isère",
  "xLabel": "Lits touristiques",
  "yLabel": "Manifestations Apidae",
  "xScale": "log",
  "yScale": "log",
  "sizeLabel": "Sites recensés",
  "annotate": ["Huez", "Grenoble"],
  "points": [
    { "x": 4950, "y": 2, "label": "Huez", "group": "Aucun site surfréquenté signalé", "size": 35 },
    { "x": 2377, "y": 935, "label": "Grenoble", "group": "Aucun site surfréquenté signalé", "size": 156 }
  ],
  "source": "Catalogue des sites du portail (scripts/build_sites_catalog.py)"
}
```

- `group` : 3 groupes colorés au plus, dans l'ordre d'apparition ; les suivants passent en « Autres ».
- `size` : l'aire du point est proportionnelle à la valeur (rayon de 3 à 12 px).
- `annotate` : libellés à écrire sur le graphique ; gardez-en 5 au plus, le reste se lit au survol.
- `xScale` / `yScale` : `"linear"` (par défaut) ou `"log"` ; en logarithmique, les valeurs nulles ou négatives sont exclues du dessin (elles restent dans le tableau).
- Options : `xUnit`, `yUnit` (unité affichée dans l'info-bulle).

```tsx
<ScatterPlot src="/dataviz/ma-page/relation.json" height={360} />
```

## ChoroplethMap : carte par commune

<ChoroplethMap src="/dataviz/demo/choroplethe.json" />

Comment lire : plus la commune est foncée (plus claire en mode sombre), plus elle compte de sites touristiques ; le gris clair signale une commune sans site recensé. Les communes entourées et nommées sont mises en avant par l'auteur.

Format JSON :

```json
{
  "title": "Sites touristiques recensés par commune du Grésivaudan",
  "base": "/dataviz/base/communes.json",
  "values": { "38567": 55, "38163": 24, "38006": 20, "38045": 3, "38062": null },
  "unit": "sites",
  "legendTitle": "Sites recensés",
  "scale": "sequential",
  "highlight": ["38567", "38002"],
  "source": "Catalogue des sites du portail"
}
```

- `values` : une valeur par **code INSEE** de commune (chaîne de 5 caractères, `"38185"`). Une commune du fond absente de `values` est « Sans donnée ».
- `base` : le fond de carte. Par défaut, `/dataviz/base/communes.json` (toutes les communes de l'Isère). La démo utilise un fond réduit au Grésivaudan : `/dataviz/demo/communes-gresivaudan.json`.
- `highlight` : codes INSEE à entourer et à nommer (3 au plus conseillé).
- `scale`, `midpoint`, `breaks` : comme pour `Heatmap`. Rapportez les valeurs à la population ou à la surface quand c'est pertinent : une grande commune a mécaniquement plus de tout.
- Format d'un fond de carte : `{ "viewBox": "0 0 L H", "features": [{ "insee", "nom", "d", "cx", "cy" }] }`, où `d` est le tracé SVG de la commune et `cx`, `cy` un point intérieur (pour les étiquettes).

```tsx
<ChoroplethMap src="/dataviz/ma-page/par-commune.json" />
<ChoroplethMap src="/dataviz/ma-page/par-commune.json" base="/dataviz/base/communes.json" maxHeight={480} />
```

## PointMap : carte de points

<PointMap src="/dataviz/demo/carte-points.json" />

Comment lire : chaque point est un site touristique dont les données sont riches (score d'au moins 7 sur 10), coloré selon sa catégorie. Les sites d'autres catégories sont en gris. Le trait est une liaison d'exemple, pas un trajet réel.

Format JSON :

```json
{
  "title": "Sites touristiques bien documentés du Grésivaudan",
  "base": "/dataviz/demo/communes-gresivaudan.json",
  "points": [
    { "x": 157.3, "y": 772.0, "label": "Itinéraire / exposition pédagogique (Chamrousse)", "group": "Nature et paysages" },
    { "x": 158.1, "y": 445.6, "label": "Site d'exemple à Crolles", "group": "Patrimoine et culture", "href": "/sites/nom-du-site", "size": 12 }
  ],
  "lines": [{ "x1": 158.1, "y1": 445.6, "x2": 181.1, "y2": 786.1, "label": "Liaison d'exemple" }],
  "sizeLabel": "Passages par jour",
  "source": "Catalogue des sites du portail"
}
```

- `x`, `y` : coordonnées **déjà projetées** dans le repère du fond de carte (pas des latitudes et longitudes). Le fond indique sa projection ; le script qui produit le JSON fait la conversion (Lambert 93, puis la même mise à l'échelle que le fond).
- `href` : un clic (ou Entrée au clavier) ouvre la page indiquée ; le lien figure aussi dans le tableau.
- `group` : 3 groupes colorés au plus ; `size` : aire proportionnelle (rayon de 3 à 10 px).
- `lines` : segments simples (liaisons, trajets schématiques), avec un libellé facultatif.

```tsx
<PointMap src="/dataviz/ma-page/lieux.json" />
```

## Gabarit d'une page DataViz

Copiez ce squelette dans `09-dataviz/NN-ma-page.mdx` (NN fixe l'ordre dans le menu ; l'adresse sera `/dataviz/ma-page`).

```tsx
---
title: "Titre complet de la page"
sidebar_label: "Titre court"
sidebar_icon: "chart-line"
description: "Une phrase : la question à laquelle la page répond."
---

:::info{title="En bref"}
- **Pour qui** : …
- **Ce que vous trouverez** : …
- **Données utilisées** : fichiers du bucket, cités sous chaque graphique.
- **Mise à jour** : JJ/MM/AAAA.
:::

<KpiRow src="/dataviz/ma-page/kpi.json" />

## Première question

<LineChart src="/dataviz/ma-page/evolution.json" />

Comment lire : une phrase qui dit quoi regarder en premier.

## Limites

- Ce que les données ne disent pas, les biais connus, les périodes manquantes.
```

## Limites

- **Rendu au pré-rendu** : un graphique chargé par `src` apparaît après le chargement de la page ; dans le HTML statique (moteur de recherche, impression sans JavaScript), seul son cadre est présent. Pour un graphique indispensable, passez les valeurs avec `data`.
- **Cartes** : les fonds de carte sont simplifiés (tracés allégés à environ 60 m) ; ils ne conviennent pas pour mesurer des distances ou des surfaces.
- **Classes de couleur** : les classes séquentielles sont calculées par quantiles ; deux cartes différentes n'ont donc pas les mêmes seuils. Pour comparer deux cartes, imposez les mêmes `breaks`.
- **Nombre de séries** : 8 couleurs au plus pour les courbes et les barres, 3 groupes pour les nuages et les cartes ; au-delà, regroupez ou faites plusieurs graphiques.
- **Démos** : les chiffres des démos sont réels mais servent d'exemple ; les analyses de référence sont dans l'onglet Analyses.
