You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Le pipeline source → normalize → query → (chart|list|kpi) suppose un format long/tidy : une observation par ligne. Parfait pour alimenter un graphe, mais insuffisant dès qu'on veut un tableau croisé (cross-tab) : entités en lignes, valeurs d'un champ catégoriel en colonnes, mesures en cellules.
Beaucoup de jeux ODS / Grist sont servis en long :
entite | critere | valeur
A | Critère 1 | 24
A | Critère 2 | 39
B | Critère 1 | 0
B | Critère 2 | 0
…alors que l'utilisateur attend la forme wide (lisible, comparable ligne à ligne) :
entite | Critère 1 | Critère 2
A | 24 | 39
B | 0 | 0
Aujourd'hui cette bascule long → wide n'a aucun composant. On la fait en JavaScript écrit à la main : group-by sur l'identité de ligne, éclatement du champ pivot en colonnes, remplissage des trous. Cet epic la rend déclarative.
L'asymétrie à corriger
La lib possède déjà dsfr-data-unpivot (#225) qui fait wide → long. Sa propre doc le dit : « c'est l'inverse exact d'un pivot ». Le sens retour long → wide manque. Cet epic ajoute le pendant symétrique : dsfr-data-pivot.
Cas d'usage cible : un agent publie un jeu en format long (entité × critère → valeur) et veut un tableau croisé déclaratif — sans savoir coder.
Vérifications faites sur l'état de la lib
dsfr-data-unpivot = wide → long uniquement, unidirectionnel (packages/core/src/components/dsfr-data-unpivot.ts). Pas de mode retour.
dsfr-data-join assemble N sources sur une clé, mais ne déplie pas un champ catégoriel en colonnes.
dsfr-data-list : si colonnes est omis, toutes les clés du 1er objet deviennent colonnes → un pivot produisant des clés dynamiques s'affiche tout seul ; avec colonnes, on choisit/ordonne.
Réactivité : comme unpivot/join, le composant doit recalculer quand la source amont ré-émet.
Décision de placement
Nouveau composant invisible, pur transformateur, frère de dsfr-data-query / dsfr-data-join / dsfr-data-unpivot. Pas dans normalize (préparation ligne-à-ligne) ni query (agrégation pure) : le pivot remodèle la forme du tableau, il mérite son propre poste. La valeur est laissée brute ; le typage reste délégué à dsfr-data-normalize (numeric-auto) — exactement comme unpivot.
API proposée
Attribut
Type
Défaut
Requis
Description
id
String
–
oui
Identifiant de la sortie.
source
String
""
oui
Source amont (format long).
id-cols
String
""
oui
Colonnes formant l'identité de ligne (l'index), virgule-séparées. Les lignes sont regroupées par leur combinaison. Ex : "entite, type".
pivot-col
String
""
oui
Colonne dont les valeurs deviennent des noms de colonnes. Ex : "critere". (alias accepté : names-from)
value-col
String
""
oui
Colonne dont les valeurs remplissent les cellules. Ex : "valeur". (alias : values-from)
agg
String
"first"
non
Réduction quand plusieurs lignes tombent dans la même cellule : first, last, sum, mean, min, max, count, concat.
fill
String
""
non
Valeur des cellules absentes (entité sans relevé pour ce critère). Ex : "NC".
pivot-values
String
""
non
Liste blanche ordonnée des valeurs de pivot-col à matérialiser en colonnes (et leur ordre). Vide = dérivées des données. Permet de figer / choisir / ordonner les colonnes (sélecteur de colonnes déclaratif).
sort
String
"first-seen"
non
Ordre des colonnes dérivées quand pivot-values est vide : first-seen, asc, desc.
prefix
String
""
non
Préfixe des colonnes générées (évite la collision avec id-cols). Ex : "c_".
Décisions / points ouverts à trancher dans le cœur
Schéma dynamique : le jeu de colonnes dépend des données. Documenter le contrat pour les consommateurs aval (dsfr-data-list sans colonnes = tout ; avec colonnes ou pivot-values = stable).
Collisionpivot value ↔ id-col : préfixer (prefix) ou erreur explicite (data-dsfr-config-error, pattern existant).
Cellule multi-valeurs sans agg compatible : défaut first + warning, ou erreur ?
Valeur nulle côté pivot-col : ligne ignorée vs colonne "(vide)".
✅ Critère d'acceptation n°1 — cas-exemple en HTML pur (ZÉRO JS)
Tableau croisé depuis une source long, sans aucun <script> :
Contexte
Le pipeline
source → normalize → query → (chart|list|kpi)suppose un format long/tidy : une observation par ligne. Parfait pour alimenter un graphe, mais insuffisant dès qu'on veut un tableau croisé (cross-tab) : entités en lignes, valeurs d'un champ catégoriel en colonnes, mesures en cellules.Beaucoup de jeux ODS / Grist sont servis en long :
…alors que l'utilisateur attend la forme wide (lisible, comparable ligne à ligne) :
Aujourd'hui cette bascule long → wide n'a aucun composant. On la fait en JavaScript écrit à la main : group-by sur l'identité de ligne, éclatement du champ pivot en colonnes, remplissage des trous. Cet epic la rend déclarative.
L'asymétrie à corriger
La lib possède déjà
dsfr-data-unpivot(#225) qui faitwide → long. Sa propre doc le dit : « c'est l'inverse exact d'un pivot ». Le sens retourlong → widemanque. Cet epic ajoute le pendant symétrique :dsfr-data-pivot.Vérifications faites sur l'état de la lib
dsfr-data-unpivot=wide → longuniquement, unidirectionnel (packages/core/src/components/dsfr-data-unpivot.ts). Pas de mode retour.pivot / spread / cast / crosstabn'existe (recherche repo : 0 occurrence dedsfr-data-pivot).dsfr-data-normalizene fabrique pas de colonnes par éclatement (lecomputede feat(core): attribut compute sur dsfr-data-normalize (colonnes calculées, version simple) #226 est ligne-à-ligne, pas un group-by/spread).dsfr-data-joinassemble N sources sur une clé, mais ne déplie pas un champ catégoriel en colonnes.dsfr-data-list: sicolonnesest omis, toutes les clés du 1er objet deviennent colonnes → un pivot produisant des clés dynamiques s'affiche tout seul ; aveccolonnes, on choisit/ordonne.unpivot/join, le composant doit recalculer quand la source amont ré-émet.Décision de placement
Nouveau composant invisible, pur transformateur, frère de
dsfr-data-query/dsfr-data-join/dsfr-data-unpivot. Pas dansnormalize(préparation ligne-à-ligne) niquery(agrégation pure) : le pivot remodèle la forme du tableau, il mérite son propre poste. La valeur est laissée brute ; le typage reste délégué àdsfr-data-normalize(numeric-auto) — exactement commeunpivot.API proposée
idsource""id-cols"""entite, type".pivot-col"""critere". (alias accepté :names-from)value-col"""valeur". (alias :values-from)agg"first"first,last,sum,mean,min,max,count,concat.fill"""NC".pivot-values""pivot-colà matérialiser en colonnes (et leur ordre). Vide = dérivées des données. Permet de figer / choisir / ordonner les colonnes (sélecteur de colonnes déclaratif).sort"first-seen"pivot-valuesest vide :first-seen,asc,desc.prefix""id-cols). Ex :"c_".Décisions / points ouverts à trancher dans le cœur
dsfr-data-listsanscolonnes= tout ; aveccolonnesoupivot-values= stable).pivot value↔id-col: préfixer (prefix) ou erreur explicite (data-dsfr-config-error, pattern existant).aggcompatible : défautfirst+ warning, ou erreur ?pivot-col: ligne ignorée vs colonne"(vide)".✅ Critère d'acceptation n°1 — cas-exemple en HTML pur (ZÉRO JS)
Tableau croisé depuis une source long, sans aucun
<script>:Sous-issues
dsfr-data-pivot(group-byid-cols,pivot-col→ colonnes,value-col→ cellules,agg,fill)pivot-values(liste blanche ordonnée) +sort+prefixfirst / last / sum / mean / min / max / count / concat+ gestion collisions / erreurspivot-col→ nouvelles colonnespipeline-helper; éventuelle actionbuilder-IAcolumn-pickersurdsfr-data-listoupivot-valuespilotable, pour choisir dynamiquement les colonnes affichéesCritères d'acceptation de l'epic
<script>.fill;aggrespecté quand plusieurs relevés tombent dans la même cellule.pivot-col(ex."Critère 3") ajoutée à la source apparaît comme nouvelle colonne sans modifier le HTML (symétrie avec le critère « nouveau mois » de feat(core): dsfr-data-unpivot — bascule tableur wide → tidy (melt déclaratif) #225).pivot-valuesfige / ordonne / sélectionne le jeu de colonnes ; consommé proprement pardsfr-data-list(colonnes) etdsfr-data-chart.pivotverts ;tests/apps/builder-ia/skills.test.tsvert ; CI verte ; changesetminorprésent.Liens
dsfr-data-unpivot,wide → long).dsfr-data-context) :pivotremodèle,contextfiltre — ensemble ils couvrent les tableaux croisés filtrés en déclaratif.