République
française
dsfr-data
Documentation — Web Components DSFR pour la dataviz
Composant intermediaire de normalisation et nettoyage des données.
Se place entre <dsfr-data-source> et <dsfr-data-query>
pour nettoyer les données avant traitement.
Invisible : Ce composant ne rend rien visuellement. Il normalise les données et les redistribue aux composants en aval via le système d'événements.
dsfr-data-source → dsfr-data-normalize → dsfr-data-query → dsfr-data-chart
Normaliser avant dsfr-data-query permet aux filtres et agrégations de travailler
sur des données propres (evite les comparaisons string vs number).
"1234" au lieu de 1234)"12,5" vs "12.5")numeric lit une valeur entière ou rien ("2026-09-25" et "75A" deviennent absents, pas 2026 ni 75) ; pour lire exprès le nombre qui ouvre une période ("1922-1930" → 1922), numeric-prefixN/A, n.d., - par des valeurs exploitables32073247.269999996 a 32073247| Attribut | Type | Défaut | Description |
|---|---|---|---|
id | String | - | Requis. Identifiant de la sortie. |
source | String | "" | ID de la source de données a ecouter |
numeric | String | "" | Champs a convertir en nombre (virgule-séparés). Ex: "population, surface" Lecture STRICTE (#1200) : la valeur entière doit être un nombre (décimale française, milliers, symbole d'unité final « % » « € » acceptés), sinon null — « 2026-09-25 » ou « 75A » ne valent plus 2026 ni 75. Pour lire le nombre en tête exprès : numeric-prefix. |
numeric-prefix | String | "" | Champs dont on lit le nombre qui OUVRE la valeur (#1200), en connaissance de cause : « 1922-1930 » → 1922, « 75A » → 75. numeric lit désormais une valeur ENTIÈRE ou rien (« 2026-09-25 » n'est plus 2026) ; ce mode-ci est l'ancienne lecture, devenue un choix explicite. Aucun nombre en tête → absent (null). Ex: numeric-prefix="periode". |
numeric-auto | Boolean | false | Detection automatique des champs numériques via looksLikeNumber() |
rename | String | "" | Renommage de clés. Format : "ancien:nouveau | ancien2:nouveau2". Un : ou | littéral dans un nom s'échappe en percent (%3A, %7C), comme dans where. |
trim | Boolean | false | Supprime les espaces en debut/fin de toutes les clés et valeurs string |
strip-html | Boolean | false | Supprime les balises HTML des valeurs string |
replace | String | "" | Remplacement de valeurs, sur tous les champs. Format : "pattern:remplacement | pattern2:remplacement2". Le pattern est comparé à la valeur entière (égalité stricte, pas de regex) ; un remplacement vide supprime la valeur. Un :, |, , ou % littéral dans le pattern ou le remplacement s'échappe en percent (%3A, %7C, %2C, %25), comme dans where (#676) : replace="10%3A00:10h" récrit « 10:00 » en « 10h ». La comparaison porte sur la forme chaîne de la valeur : une colonne numérique est concernée aussi (#730). Un champ TABLEAU (multivalué venu de la source) est remplacé élément par élément, longueur conservée et sans dédoublonnage (#774). Limite : le remplacement s'exécute AVANT split, il ne voit donc pas les tableaux fabriqués par split — il agit sur la chaîne entière avant la découpe. Pour un recodage plus riche (sous-chaîne, année d'une date ISO), utiliser compute avec replace() ou year(). |
replace-fields | String | "" | Remplacement ciblé par champ. Format : "CHAMP:pattern:remplacement | CHAMP2:p:r". Les deux premiers : sont des délimiteurs, le remplacement peut contenir des : bruts. Un : littéral dans le nom du champ ou dans le pattern s'échappe en %3A (%7C, %2C et %25 sont aussi décodés), comme dans where (#676) : replace-fields="h:10%3A00:10h". La comparaison porte sur la forme chaîne de la valeur : une colonne numérique est concernée aussi, replace-fields="annee:2024:2024-2025" fonctionne (#730). Sur un champ tableau venu de la source, élément par élément ; pas sur un tableau fabriqué par split, découpé après (#774, même limite que replace). Pas de regex : pour un recodage plus riche, voir compute (replace(), year()). |
flatten | String | "" | Clé du sous-objet a aplatir au premier niveau. Supporte la dot notation (ex: "data.attributes"). |
split | String | "" | Découpe des champs multivalués (chaîne avec séparateur) en vrais tableaux, comme une ChoiceList Grist. Format : "champ:sep, champ2:sep2" ; séparateur par défaut : la virgule ("champ" seul). Ex : "Axes:|, Cibles:;". Chaque élément est trimé, les éléments vides sont écartés, une chaîne vide donne un tableau vide. Les valeurs non-string (tableau déjà forme, null, nombre) sont laissées telles quelles. Les composants aval traitent ces tableaux comme des champs multi-valeurs (facettes : une valeur par élément). |
round | String | "" | Arrondit les champs numériques à l'entier (ou à N décimales). Format: "champ1, champ2" ou "champ1:2, champ2:0" |
lowercase-keys | Boolean | false | Met toutes les clés en minuscules |
compute | String | "" | Colonnes calculées, ligne à ligne, en dernier (sur les valeurs déjà typées par numeric / round / rename). Format : cible = expression; cible2 = expression2 (une assignation suivante peut relire une colonne calculée avant elle). Grammaire (ADR-105, #671) : - arithmétique + - * /, parenthèses, moins unaire ; + concatène dès qu'un côté n'est pas numérique ; littéraux texte 'entre quotes simples', nombres à point ; une apostrophe DANS un littéral s'écrit doublée, comme en SQL et en ODSQL (AM-090) : when libelle = 'J''en ai' then 1 else 0 compare au libellé « J'en ai », 'Provence-Alpes-Côte d''Azur' est un seul littéral. Pas d'échappement par barre oblique. Seule l'apostrophe droite (ASCII) délimite : une apostrophe typographique (’) dans les données s'écrit telle quelle ; - littéraux null, true, false ; - fonctions en liste blanche, appel f(a, b) : dates year(d), month(d), day(d) (ISO ou Date, sinon null) ; nombres round(x, n), abs(x), floor(x), ceil(x), sqrt(x) (non numérique → null ; sqrt d'un nombre négatif → null, jamais NaN ; sqrt(0) vaut 0) ; texte lower(s), upper(s), trim(s), len(s), concat(a, b, …), replace(s, 'de', 'vers') (littéral, toutes les occurrences, pas de regex), sous-chaînes left(s, n) (les n premiers caractères) et substr(s, debut, n) (n caractères à partir de la position debut, comptée À PARTIR DE 1 comme en SQL et en ODSQL ; sans n, jusqu'au bout) ; absence coalesce(a, b, …) (première valeur non nulle), is_null(x), is_empty(x) (null, '' ou tableau vide) ; tableaux join(arr, ', '), contains(arr_ou_texte, v), element_at(arr, n) (l'élément de rang n, compté À PARTIR DE 1 ; un rang négatif compte depuis la fin), array_min(arr) et array_max(arr) (le plus petit et le plus grand élément) ; - conditions when COND then EXPR [when … then …]… else EXPR — le else est obligatoire (erreur de configuration sinon) ; comparaisons d'égalité = et != et d'ordre (inférieur, inférieur ou égal, supérieur, supérieur ou égal, avec les signes usuels — grammaire complète dans le guide « Colonnes calculées » de la skill), and, or, not. L'égalité est lâche comme celle de where (nombre ↔ chaîne numérique) : when cat = 'A' et where="cat:eq:A" gardent les mêmes lignes ; une cellule vide n'égale jamais un nombre (montant = 0 ne classe pas les montants non renseignés en zéro). Les comparaisons d'ordre se font en nombre quand les deux côtés sont numériques, en texte sinon (dates ISO comprises) ; null, undefined et '' ne matchent jamais. CHAMP TABLEAU (#953, ex-#842) : = regarde DANS le tableau — when tags = 'urgent' est VRAI pour ['urgent','social'], comme contains(tags, 'urgent'), et comme le portail sur une clause déléguée. != en est la négation, donc faux sur cette même ligne. contains() garde son utilité sur du TEXTE, où il cherche une sous-chaîne ; sur un tableau, les deux se rejoignent. Dériver ici un booléen puis filtrer where="a_urgent:eq:1" en aval reste valide — et utile, le filtre portant alors sur un scalaire regroupable et délégable — mais n'est plus NÉCESSAIRE pour filtrer un champ tableau : where="tags:eq:urgent" le fait directement. - arithmétique - * / et moins unaire : un opérande absent ou non numérique rend null (jamais un 0 plausible), une division par zéro rend null (jamais Infinity). - sous-chaînes (AM-103) : left(siret, 9) rend le SIREN, substr(code_insee, 1, 2) le département — sauf outre-mer, dont le code de département tient sur TROIS caractères (971…976) : écrire dep = when left(code_insee, 2) = '97' then left(code_insee, 3) else left(code_insee, 2). Le résultat est toujours du TEXTE ; une valeur numérique est lue par sa forme texte (left(75056, 2) rend '75'), mais un code stocké en NOMBRE a déjà perdu ses zéros de tête en amont (1004 pour « 01004 ») — à corriger à la source, pas par une sous-chaîne. Valeur absente : null. Position ou longueur absente ou non numérique : null. Longueur nulle ou négative, début au-delà de la fin : chaîne vide. Début inférieur à 1 : erreur de configuration s'il est écrit en dur (substr(s, 0, 2)), null s'il est calculé. left et substr lisent du TEXTE : sur un tableau, ils portent sur sa forme texte (a,b), pas sur ses éléments. - éléments d'un tableau (#1237, suite de AM-103) : element_at(denominations, 1) rend le PREMIER élément, element_at(denominations, -1) le dernier ; les rangs se comptent à partir de 1, comme les positions de substr et comme element_at en SQL (Spark, Trino). L'élément est rendu tel quel, sans conversion. Tout ce qui n'est pas un vrai tableau rend null — y compris un scalaire, et une cellule « collée » ('maison ; immeuble'), qui est UN texte : la découper d'abord avec split, dans le même normalize (split s'exécute avant compute) : split="datation:;" compute="premiere = element_at(datation, 1)". Rang hors du tableau, tableau vide, rang absent ou non numérique : null. Rang 0 ou non entier : erreur de configuration s'il est écrit en dur (element_at(arr, 0)), null s'il est calculé. array_min(arr) et array_max(arr) rendent le plus petit et le plus grand élément — le plus ANCIEN poste d'une liste de dates n'est pas forcément le premier listé : plus_ancien = array_min(prises_de_poste), annee = year(array_min(prises_de_poste)). Les éléments absents (null, '') sont ignorés ; la comparaison est NUMÉRIQUE quand tous les éléments restants sont des nombres (« 950 » avant « 1050 », décimale française comprise), TEXTUELLE quand aucun ne l'est (les dates ISO se rangent juste). L'élément gagnant est rendu tel quel ('01004' garde son zéro), le premier en cas d'égalité ; pas un tableau, ou rien à comparer : null. Ce ne sont pas les agrégations min / max de dsfr-data-query, qui réduisent des LIGNES : celles-ci réduisent les éléments d'UNE cellule. TABLEAU MIXTE — des éléments numériques ET non numériques (950 ; 1050 ; vers 1970, ou des codes 2A004 parmi des codes en chiffres) : array_min et array_max rendent null sur cette ligne, avec un avertissement en console, car aucun des deux ordres n'y est juste (celui du texte répondrait « 1050 »). Les éléments se nettoient par attributs, en DEUX normalize chaînés — replace et replace-fields comparent la cellule entière, et le replace() de compute passe après split : le premier recolle et nettoie le texte, compute="datation = replace(join(datation, ';'), 'vers ', '')", le second découpe et calcule, split="datation:;" compute="premiere = array_min(datation)". La recette vaut pour une cellule collée comme pour un vrai tableau (join le recolle). Sur un vrai tableau, replace-fields="datation:vers 1970:1970" suffit dans un seul normalize quand les valeurs à corriger sont connues une à une (il remplace élément par élément, à valeur exacte). AVERTISSEMENTS : une colonne calculée ENTIÈREMENT vide parce que le champ ne porte aucun tableau (cellule collée non découpée, valeur seule) est signalée une fois en console, avec le champ et le split à poser ; rien n'est dit si la colonne est réellement vide, ni si une partie des lignes porte un tableau. Exemples : solde = actif - passif (null si l'un des deux manque), tranche = when montant = 0 then 'Nul' when is_empty(montant) then 'Inconnu' else 'Renseigné', type = coalesce(type_entreprise, 'Non renseigné'), annee = year(date_notification), pct = round(part * 100, 1), serie = Indicateurs + ' / ' + Sous_theme, principale = element_at(denominations, 1) ; une tranche par seuils s'écrit avec les opérateurs d'ordre (voir le guide). Fonction hors liste, arité fausse, when sans else, expression trop longue ou trop imbriquée : erreur de configuration (console + data-dsfr-config-error), état d'erreur en aval — jamais une colonne silencieusement vide. Aucun eval : tokenizer, parseur, AST ; seuls les champs de la ligne sont accessibles. Hors périmètre : valeurs agrégées (query / kpi), ligne précédente, cumul. |
fold | String | "" | Repli de colonnes booléennes parallèles en un champ multi-valeurs (#677) — le motif open data « une colonne Oui/Non par modalité » (handicap_moteur, handicap_visuel…). Format : "motif:cible, motif2:cible2". Le joker * n'est accepté qu'en début ou en fin de motif (handicap_*, *_ok) ; un motif sans joker désigne une colonne exacte ; plusieurs motifs peuvent viser la même cible. Chaque ligne reçoit dans cible le tableau des colonnes dont la valeur est vraie au sens de toBoolean (Oui/Non, 1/0, true/false, X/vide…), étiquetées par la partie variable du motif (handicap_moteur donne « moteur ») ou par le nom complet de la colonne pour un motif sans joker. Les colonnes sources sont conservées (voir fold-drop). S'exécute après rename et lowercase-keys, avant compute : les motifs se lisent sur les noms renommés, qui servent donc d'étiquettes (rename="handicap_moteur:handicap_Moteur" donne « Moteur »). Le tableau obtenu se filtre avec dsfr-data-facets comme un champ split (une valeur par élément). Ex : fold="handicap_*:handicaps". |
fold-drop | Boolean | false | Avec fold : retire du résultat les colonnes sources repliées. |
Separateurs : rename et replace utilisent le pipe |
pour separer les paires, et : pour separer la clé de la valeur.
replace-fields utilise le format CHAMP:pattern:remplacement (les 2 premiers : sont des delimiteurs).
numeric utilise la virgule.
split utilise la virgule entre entrées et champ:séparateur pour chacune (le séparateur à découper est souvent |).
Les données brutes RappelConso contiennent des noms de colonnes longs et techniques. On renomme les colonnes pour la lisibilité et on nettoie les espaces superflus.
{
"modeles_ou_references": " Lot 2024-ABC ",
"categorie_produit": "Alimentation",
"marque_produit": "Ma Marque",
"date_publication": "2024-06-15",
"motif_rappel": "Presence de Listeria"
}
{
"Produit": "Lot 2024-ABC", // trim + rename
"Catégorie": "Alimentation", // rename
"Marque": "Ma Marque", // rename
"Date": "2024-06-15", // rename
"Motif": "Presence de Listeria" // rename
}
<dsfr-data-source id="raw" api-type="opendatasoft"
dataset-id="rappelconso-v2-gtin-trie"
base-url="https://data.economie.gouv.fr"
server-side page-size="50">
</dsfr-data-source>
<dsfr-data-normalize id="clean" source="raw"
trim
rename="modeles_ou_references:Produit | categorie_produit:Catégorie | marque_produit:Marque | date_publication:Date | motif_rappel:Motif">
</dsfr-data-normalize>
<!-- Les données normalisees sont pretes pour query/chart -->
<dsfr-data-list source="clean" pagination="5"></dsfr-data-list>
Source → Normalize → Query → Chart
<dsfr-data-source id="raw" url="..." transform="results"></dsfr-data-source>
<dsfr-data-normalize id="clean" source="raw"
numeric="population, budget"
rename="pop_tot:Population totale | lib_dep:Departement"
trim
replace="N/A: | n.d.:">
</dsfr-data-normalize>
<dsfr-data-query id="stats" source="clean"
group-by="Departement"
aggregate="population:sum"
order-by="population__sum:desc"
limit="10">
</dsfr-data-query>
<dsfr-data-chart source="stats" type="bar"
label-field="Departement"
value-field="population__sum">
</dsfr-data-chart>
L'API INSEE Melodi retourne des codes de dimension (AGE: "Y30T39", PCS: "3").
replace-fields permet de les decoder sans affecter les autres champs
qui pourraient contenir les mêmes valeurs.
<dsfr-data-source id="raw" api-type="insee"
base-url="https://api.insee.fr/melodi"
dataset-id="DS_POPULATIONS_REFERENCE"
where="POPREF_MEASURE:eq:PMUN, TIME_PERIOD:eq:2023">
</dsfr-data-source>
<dsfr-data-normalize id="decoded" source="raw"
replace-fields="AGE:Y30T39:30-39 ans | AGE:Y_LT30:Moins de 30 ans | PCS:3:Cadres | PCS:5:Employes"
replace="N/A:">
</dsfr-data-normalize>
<dsfr-data-chart source="decoded" type="bar"
label-field="AGE" value-field="OBS_VALUE">
</dsfr-data-chart>