Replie un tableau « long » en tableau « wide » (tableau croisé) : une ligne par valeur distincte de row, une colonne par valeur distincte de column, et dans chaque cellule l'agrégat des valeurs de value. Symétrique exact de <dsfr-data-unpivot>.

Invisible : transformateur pur, frère de <dsfr-data-query>, <dsfr-data-join> et <dsfr-data-unpivot> — aucun fetch HTTP. Il écoute une source, croise ses lignes, puis redistribue le résultat via le système d'événements.

Le problème résolu

Une source « tidy » porte une observation par ligne :

region              | annee | montant
Bretagne            | 2023  | 1200
Bretagne            | 2024  | 1450
Normandie           | 2023  | 980

Un tableau croisé se lit ligne à ligne, une colonne par période :

region              | 2023 | 2024
Bretagne            | 1200 | 1450
Normandie           | 980  | null

Une cellule sans observation vaut null, jamais 0 — l'absence de mesure et une mesure nulle ne se confondent pas. Toutes les lignes émises portent toutes les colonnes générées : un <dsfr-data-list> en aval sans attribut columns dérive ses colonnes des données, et suit donc une facette ou un filtre posé en amont.

Position dans le pipeline

dsfr-data-source (long) ──► dsfr-data-pivot ──► dsfr-data-list / dsfr-data-chart

Attributs

AttributTypeDéfautDescription
sourceString""ID de la source de données à écouter (format long : une observation par ligne).
rowString""Champs formant l'identité de ligne, virgule-séparés : une ligne émise par combinaison distincte. Ex : "commune" ou "etab, dep".
columnString""Champ dont chaque valeur distincte devient une colonne. Ex : "annee".
valueString""Champ dont les valeurs remplissent les cellules. Ex : "montant".
aggregateStringsumRéduction quand plusieurs lignes tombent dans la même cellule : sum (défaut), count, avg, min, max, first, last. Une cellule sans valeur numérique reste null (pas de 0 silencieux).
column-orderString""Ordre des colonnes générées : vide = ordre d'apparition dans les données, asc / desc = tri des valeurs (numérique si elles le sont toutes).
column-formatString""Gabarit des noms de colonnes générées, {value} = valeur brute. Ex : "annee_{value}" donne annee_2023 — un identifiant utilisable dans compute. Défaut : la valeur brute (2023).
labelsString""Libellés des colonnes générées, par valeur brute : "2022:Année 2022 | 2023:Année 2023". Une valeur libellée prend son libellé pour nom de colonne (prime sur column-format). Un : ou | littéral s'échappe en %3A / %7C.
max-columnsStringPIVOT_DEFAULT_MAX_COLUMNSPlafond de colonnes générées (défaut 50). Au-delà, erreur de configuration explicite : un pivot sur un champ à 10 000 valeurs distinctes est une erreur de page, pas un tableau.

Le schéma de sortie dépend des données

Les noms de colonnes proviennent des valeurs rencontrées dans column : ils changent donc avec le jeu de données. Pour des noms stables et sûrs dans un compute — écrire annee_2023 - annee_2022 suppose de connaître les noms — poser column-format="annee_{value}".

Au-delà de max-columns valeurs distinctes (50 par défaut), le composant signale une erreur de configuration (data-dsfr-config-error) plutôt que de produire un tableau de 10 000 colonnes.

Exemples

Les trois exemples partent du même jeu de données : les crédits de paiement du budget de l'État, une ligne par mission et par année, en millions d'euros.

mission              | annee | credits_pa
Écologie             | 2023  | 21400
Écologie             | 2024  | 23900
Enseignement scolaire| 2023  | 60200
Enseignement scolaire| 2024  | 62100
Justice              | 2023  | 11600
Justice              | 2024  | 12200

Budget par mission et par année

Le croisement le plus simple : une ligne par mission, une colonne par année, la somme des crédits dans chaque cellule. column-order="asc" range les années dans l'ordre plutôt que dans celui d'apparition dans les données.

<dsfr-data-source id="budget" api-type="tabular"
  base-url="https://tabular-api.data.gouv.fr"
  resource="RESOURCE_ID">
</dsfr-data-source>

<dsfr-data-pivot id="croise" source="budget"
  row="mission" column="annee" value="credits_pa"
  aggregate="sum" column-order="asc">
</dsfr-data-pivot>

<dsfr-data-list source="croise"></dsfr-data-list>

Sans attribut columns, la liste dérive ses colonnes des données : elle affiche mission, 2023 et 2024. Une mission sans crédits une année donnée porte null, pas 0.

Des noms de colonnes sûrs avec column-format

Un nom de colonne issu des données peut commencer par un chiffre (2023) : il n'est alors pas un identifiant utilisable dans un compute en aval, ni dans un attribut value-field. Le gabarit column-format préfixe la valeur brute et rend le schéma de sortie prévisible.

<dsfr-data-pivot id="croise" source="budget"
  row="mission" column="annee" value="credits_pa"
  aggregate="sum" column-order="asc"
  column-format="annee_{value}">
</dsfr-data-pivot>

<dsfr-data-chart source="croise" type="bar"
  label-field="mission" value-field="annee_2024">
</dsfr-data-chart>

Les colonnes générées deviennent annee_2023 et annee_2024. Pour un libellé lisible à l'affichage sans perdre ces identifiants, préférer labels sur le composant qui restitue les données plutôt que sur le pivot.

Écart entre deux années

C'est le motif qui justifie column-format : une fois les deux années devenues deux colonnes aux noms connus, <dsfr-data-normalize> dérive l'écart en aval du croisement. Le pivot ne calcule pas de colonne dérivée lui-même : il croise, et le reste du pipeline s'applique normalement.

<dsfr-data-pivot id="croise" source="budget"
  row="mission" column="annee" value="credits_pa"
  aggregate="sum" column-order="asc"
  column-format="annee_{value}">
</dsfr-data-pivot>

<dsfr-data-normalize id="evolution" source="croise"
  numeric-auto
  compute="ecart = annee_2024 - annee_2023">
</dsfr-data-normalize>

<dsfr-data-list source="evolution"
  columns="mission, annee_2023, annee_2024, ecart">
</dsfr-data-list>

numeric-auto n'est pas facultatif ici : une source qui rend ses montants sous forme de chaînes ferait concaténer compute au lieu de soustraire. Une mission absente d'une des deux années donne un écart null, cohérent avec la cellule manquante.

Voir aussi