Skip to content

fix: cohérence graphique / tableau équivalent — alias de label-field, nombres de la DataBox, chemin pointé, color-map par libellé, tri sur une part, podium (#1244) - #1260

Merged
bmatge merged 9 commits into
mainfrom
fix/1244-coherence-graphique-tableau
Oct 4, 2026
Merged

bmatge merged 9 commits into
mainfrom
fix/1244-coherence-graphique-tableau

Conversation

@bmatge

@bmatge bmatge commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Cohérence entre le graphique et son tableau équivalent : les six défauts voisins relevés en traitant les lots 3 et 5 du banc d'essai (#1239, #1238). Un commit par point, puis les artefacts régénérés.

Closes #1244

1. label-field de dsfr-data-chart accepte champ:Libellé — suite de PG-032

  • Reproduit : label-field="dep:Département" rendait tous les libellés « Non renseigné » (test unitaire rouge, 5 cas).
  • Le graphique et dsfr-data-a11y passent par le même analyseur (resolveAliasedColumn, autour de parseAliasedColumn). Une colonne dont le nom contient réellement un deux-points reste lue telle quelle.
  • Où sert le libellé : en-tête de la colonne de libellé du tableau de la DataBox (formats large et long). Nulle part ailleurs — DSFR Chart n'a pas de titre d'axe. C'est le pendant de value-field, dont l'alias nomme la légende et l'en-tête de sa colonne.
  • @champ liste-alias : le bloc libre du Studio valide les attributs-champs par cette grammaire, un alias n'y est donc plus pris pour un champ inconnu.
  • Contrôle affichages/graphique-label-field-alias, vu rouge deux fois (libellés de l'axe, en-tête).

2. Le tableau de la DataBox écrit ses nombres en fr-FR — suite de BUG-035

  • Reproduit : 1234567.891 affiché tel quel, « 1 234 567,89 » dans dsfr-data-a11y.
  • Une seule fonction pour les deux tableaux (formatTableCell) ; les valeurs passées à DSFR Chart ne changent pas (test).
  • La colonne de libellé porte ce que l'axe affiche : une année numérique reste « 2024 », une catégorie vide porte empty-label.
  • Contrôle affichages/databox-tableau-nombres-fr, vu rouge.

3. dsfr-data-a11y lit par chemin pointé — suite de PG-032

  • Reproduit : value-field="fields.total" rendait une colonne vide.
  • Tableau, pivot du format long, CSV et avertissement « colonne introuvable » lisent par chemin (readColumn).
  • Colonne à plat dont le nom contient un point : voir « Choix à trancher ».
  • Contrôle affichages/a11y-chemin-pointe-comme-le-graphique (communes servies imbriquées sous fields), vu rouge deux fois (cellules, CSV).

4. color-map par libellé d'axe sur un jeu à points — suite de BUG-033

  • Reproduit au navigateur, et plus large que l'issue. Sur une courbe à color-map="1:#ff0000,3:#00aa00" : le TRAIT entier passait au rouge (652 px), la pastille de légende aussi, aucun point n'était recoloré (0 px vert) ; l'aire d'un radar devenait opaque (6 325 px) ; le nuage ne changeait pas.
  • Chaque point nommé prend sa couleur ; le trait, l'aire et la légende gardent celle de la série. Barres et camemberts inchangés.
  • La transition de fix: graphique, tableau équivalent et podium — lot 3 du banc d'essai (BUG-033, BUG-035, PG-032, AM-088) #1239 n'est pas nécessaire ici (mesuré : update('none') suffit, une couleur par point étant un tableau non partagé) ; elle est tenue quand même, c'est la règle d'ARCHITECTURE.md §12.
  • Preuve : e2e/chart-legend.spec.ts, comptage de pixels sur le vrai DSFR Chart, trois types, vu rouge sur le code d'avant.

5. order-by sur une colonne produite côté client

  • Reproduit sur chaque adaptateur, contre les faux serveurs du dépôt, aggregate="population:share_percent" order-by="population__share_percent:asc" :
Adaptateur Ce qui partait Ce qui s'affichait
Opendatasoft order_by=population__share_percent ASC (le faux serveur l'accepte) lignes dans l'ordre reçu
Tabular population__share_percent__sort=asc, refusé comme par l'API aucune ligne
Grist sort=Population__share_percent (ignoré) lignes dans l'ordre reçu
sans adaptateur, INSEE rien lignes dans l'ordre reçu
tous, après group-by rien groupes dans l'ordre du regroupement
  • Le défaut avait donc deux moitiés : la délégation, et le tri client qui passait AVANT le calcul de la colonne — il ne triait rien, sur aucun adaptateur.
  • Un tri dont une clé nomme une part, un cumul ou un écart (ou leur alias) n'est plus délégué, et il est rejoué après le calcul des fenêtres, avant limit.
  • Cumuls (running_sum, diff) : même défaut, même correction. Un cumul trié sur sa seule colonne est calculé dans l'ordre reçu, et la query le dit (avertissement).
  • Colonne issue d'un compute : pas en défaut — le relais déclare qu'il transforme le schéma, la query ne délègue rien à travers lui. Un contrôle le garde.
  • Six contrôles delegation/tri-sur-* (URL + ordre des lignes rendues), vus rouges sur trois mutations ; tests/query-order-by-client-column.test.ts sur cinq adaptateurs.

6. Podium : value-unit suit une insécable

  • Reproduit : « 812 hab. » avec U+0020, quand subtitle-unit et le KPI écrivent U+00A0.
  • Deux tests dépendaient de l'espace ordinaire : une assertion de dsfr-data-podium-format.test.ts, et le témoin de rétrocompatibilité 0.33 (aria-label) — comparé désormais à cette seule exception près. Aucun export n'en dépendait (export-html-api-recette : 67 verts).
  • Le filet ramenait toute espace à l'espace ordinaire : ExpectTexts gagne keepNbsp. Contrôle affichages/podium-unite-insecable, vu rouge.

Ce qui change pour une page existante

  • Tableau de la DataBox : nombres en fr-FR et arrondis à deux décimales au plus ; cellule de libellé vide → empty-label.
  • color-map par libellé d'axe sur courbe, radar ou nuage : le trait et la légende reviennent à la couleur de la série, les points nommés prennent la leur.
  • order-by sur une part ou un cumul : les lignes sont désormais triées ; une page Tabular qui n'affichait rien affiche ses lignes.
  • Podium : espace insécable avant value-unit, texte et aria-label.

Choix à trancher

  • Colonne à plat dont le nom contient un point. Le graphique ne sait pas la lire (getByPath découpe toujours) ; dsfr-data-a11y la lisait. Faire « pareil que le graphique » aurait vidé des tableaux existants : la colonne à plat est lue d'abord, le chemin ensuite. Le graphique reste inchangé — l'écart subsiste dans ce seul cas.
  • Cumul trié sur lui-même : calculé dans l'ordre reçu puis trié, avec avertissement, plutôt qu'une erreur de configuration.

Vérifications locales

npm run build, test:run (9 383 tests), typecheck:tests, lint, check:accents, check:specs-tables, check:studio-couverture, vérification des données (307 contrôles verts, 1 en attente, 0 échec) : verts.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FrLwzPx5CG4VsPHEdo5ZVs

bmatge and others added 9 commits October 4, 2026 21:30
…a-a11y (#1244)

label-field="dep:Département", recopié du tableau équivalent, cherchait une
colonne « dep:Département » : tous les libellés de l'axe devenaient
« Non renseigné ». Le graphique passe par le même analyseur que
dsfr-data-a11y (resolveAliasedColumn, autour de parseAliasedColumn) ; le
libellé devient l'en-tête de la colonne de libellé du tableau de la DataBox,
seul endroit où il sert. Une colonne dont le nom contient réellement un
deux-points reste lue telle quelle.

Contrôle affichages/graphique-label-field-alias, vu rouge deux fois (libellés
de l'axe, en-tête du tableau). Suite du constat PG-032 du banc d'essai.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FrLwzPx5CG4VsPHEdo5ZVs
… dsfr-data-a11y (#1244)

Le tableau de la DataBox écrivait 2.27 là où le tableau équivalent de
dsfr-data-a11y écrit « 2,27 », pour la même ligne. Les deux passent par une
seule fonction (formatTableCell) : nombres en fr-FR, au plus 2 décimales,
chaînes intactes, absence vide. La colonne de libellé porte ce que l'axe
affiche (une année numérique reste « 2024 », une catégorie vide porte
empty-label, comme le faisait déjà le format long). Les valeurs passées à
DSFR Chart ne changent pas.

Contrôle affichages/databox-tableau-nombres-fr, vu rouge sur la cellule
rendue brute. Suite du constat BUG-035 du banc d'essai.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FrLwzPx5CG4VsPHEdo5ZVs
…inté, comme le graphique (#1244)

value-field="fields.total", recopié de dsfr-data-chart, cherchait une colonne
à plat nommée « fields.total » : le tableau gardait ses lignes et rendait une
colonne vide. Le tableau, le pivot du format long, le CSV et le contrôle
« colonne introuvable » lisent désormais par chemin (readColumn). Une colonne
à plat dont le nom contient réellement un point reste lue telle quelle :
c'était la seule lecture avant ce correctif.

Contrôle affichages/a11y-chemin-pointe-comme-le-graphique, vu rouge deux
fois (cellules du tableau, fichier CSV). Suite du constat PG-032 du banc
d'essai.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FrLwzPx5CG4VsPHEdo5ZVs
…t et le KPI (#1244)

Le podium écrivait value-unit après une espace ordinaire et subtitle-unit
après une insécable : sur une colonne étroite, le nombre restait en fin de
ligne et son unité passait à la suivante. Les deux unités suivent désormais
U+00A0, dans le texte comme dans l'aria-label.

Le filet ramenait toute espace à l'espace ordinaire : ExpectTexts gagne
keepNbsp, qui lit le texte sans replier les insécables, pour qu'un motif
puisse exiger le codet. Contrôle affichages/podium-unite-insecable, vu rouge
sur l'espace ordinaire remise. Le témoin de rétrocompatibilité 0.33 est
comparé à cette seule exception près, et un test exige l'insécable. Suite du
constat AM-088 du banc d'essai.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FrLwzPx5CG4VsPHEdo5ZVs
…e, d'un radar, d'un nuage (#1244)

Sur un jeu à points, la branche « modalité » posait un tableau de couleurs
sur le trait et le fond. Mesuré au navigateur sur quatre points et
color-map="1:#ff0000,3:#00aa00" : le trait entier d'une courbe passait au
rouge (Chart.js lit le premier élément du tableau), la pastille de légende
aussi, l'aire d'un radar devenait opaque, le nuage ne changeait pas — et
aucun point n'était recoloré.

Chaque point nommé prend désormais sa couleur ; le trait, l'aire et la
légende gardent celle de la série. La transition de #1239 n'est pas requise
ici : une couleur par point est un tableau, que Chart.js ne partage pas d'un
point à l'autre — update('none') suffit, mesuré sur les trois types.

Preuve : e2e/chart-legend.spec.ts compte les pixels sur le vrai DSFR Chart
(courbe, radar, nuage), vu rouge sur le code d'avant (0 pixel vert).
Suite du constat BUG-033 du banc d'essai.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FrLwzPx5CG4VsPHEdo5ZVs
…t il est appliqué (#1244)

aggregate="n:share_percent" order-by="n__share_percent:desc", sans group-by :
la query déléguait le tri au serveur sur une colonne qu'il ne connaît pas.
Mesuré contre les faux serveurs du dépôt : Opendatasoft recevait
order_by=n__share_percent, Grist sort=…, Tabular refusait la colonne (comme
l'API) et n'affichait plus aucune ligne. Et sur aucun adaptateur — source
sans adaptateur et group-by compris — les lignes n'étaient triées : le tri
client passait avant le calcul de la colonne.

Un tri dont une clé nomme une part, un cumul ou un écart (ou leur alias)
n'est plus délégué, et il est rejoué après le calcul des fenêtres, avant
limit. Les autres clés du même order-by ordonnent toujours les lignes avant
un cumul ; un cumul trié sur sa seule colonne est calculé dans l'ordre reçu,
et la query le dit. Une colonne issue d'un compute n'était pas en défaut
(le relais déclare qu'il transforme le schéma) : un contrôle le garde.

Six contrôles delegation/tri-sur-*, vus rouges sur trois mutations
(délégation rétablie, tri d'après fenêtre retiré, relais muet sur compute).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FrLwzPx5CG4VsPHEdo5ZVs
…ion de #1239 (#1244)

Le commit précédent redessinait par update('none'), mesuré suffisant ici (une
couleur par point est un tableau, que Chart.js ne partage pas). La règle
écrite dans ARCHITECTURE.md §12 demande pourtant que toute écriture sur les
couleurs d'un jeu à points passe par la transition de durée nulle : elle est
tenue, pour ne pas dépendre de ce détail. Les trois tests de pixels restent
verts dans les deux formes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FrLwzPx5CG4VsPHEdo5ZVs
…ion et changeset (#1244)

- build:skills (manifeste, référence générée, contrat des composants,
  FIELD_ATTRS : label-field de dsfr-data-chart passe en liste-alias, ce qui
  le fait accepter par le bloc libre du Studio), build:specs-tables et
  prose des pages chart et a11y ;
- verif:attendus : 504 attentes sur 678 recalculées par l'oracle Python ;
- tools/oracle/README.md : décompte (308 contrôles), option keepNbsp, les
  neuf mutations éprouvées ; ARCHITECTURE.md §12 : deux couplages ;
- les contrôles tri-sur-* et podium-unite-insecable ne citent aucun constat
  du registre du banc (le défaut n'en vient pas) ; typage de deux tests ;
- changeset dsfr-data en patch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FrLwzPx5CG4VsPHEdo5ZVs
Intègre #1259 (dates du Studio, select Tabular de l'export). Aucun conflit de
texte. Artefacts régénérés après fusion : inchangés. Décompte du README de
l'oracle remis au mesuré : 311 contrôles, 685 observations, 508 attentes à
la troisième voix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FrLwzPx5CG4VsPHEdo5ZVs
@bmatge
bmatge merged commit 4597f65 into main Oct 4, 2026
16 checks passed
@github-actions github-actions Bot mentioned this pull request Oct 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant