République
française
dsfr-data
Documentation — Web Components DSFR pour la dataviz
Composant de transformation de données.
Filtre, groupe, agrège et trie les données reçues d'une <dsfr-data-source>
avant de les transmettre aux composants de visualisation.
Ne fait aucun fetch HTTP : c'est un pur transformateur.
Invisible : Ce composant ne rend rien visuellement. Il transforme les données et les redistribue aux composants de visualisation via le système d'événements.
Voir les exemples live dans le Guide utilisateur
<dsfr-data-query> est un pur transformateur de données. Il recoit les données
d'une <dsfr-data-source> (ou <dsfr-data-normalize>) via l'attribut source
et applique des transformations client-side (filter, group-by, aggregate, sort, limit).
| Attribut | Type | Format / Valeurs | Description |
|---|---|---|---|
id | String | id="mon-query" | Identifiant unique (requis). Les composants de visualisation l'utilisent pour s'abonner aux données transformees. |
source | String | source="id-source" | ID de la source de données (dsfr-data-source ou dsfr-data-normalize) |
where | String | "champ:op:val, ..." | Clause WHERE / Filtres — syntaxe colon UNIQUEMENT : "champ:opérateur:valeur, champ2:opérateur:valeur2" (opérateurs : eq, neq, gt, gte, lt, lte, contains, notcontains, in, notin, isnull, isnotnull — multi-valeurs séparées par |). La syntaxe ODSQL n'est PAS supportee ici (elle l'est sur le where de dsfr-data-source) : une clause non parsable est signalee via reportConfigError (#277). **La clause part au serveur** dès lors que l'amont a un adaptateur qui sait la traduire et que cette requête est seule lectrice de sa chaîne (#856) — avec ou sans group-by. Elle est traduite au dialecte de l'adaptateur (#275) et posée en overlay clé par émetteur (ADR-031) : elle se fusionne avec les clauses des facettes, de la recherche et du contexte au lieu de les écraser, et elle lève l'attente d'un require-where posé sur la source (#854). Elle reste calculée dans le navigateur quand la chaîne est partagée (#765), quand un transformateur amont renomme des colonnes (#394), quand une clause est intraduisible, ou avec explode (#736). contains ET notcontains SONT UNE SOUS-CHAÎNE, PARTOUT (#1285, ADR-159) : champ:contains:tation retient les lignes dont la valeur porte « tation » n'importe où, casse ignorée, accents respectés — « tation » trouve « alimentation », « ecole » ne trouve pas « École ». C'est la bibliothèque qui dit ce que le filtre retient, pas le serveur. Un portail Opendatasoft ne sait pas chercher une sous-chaîne : son like compare des MOTS (like "%aliment%" rend 0 ligne pour 14 366 « alimentation », mesuré le 2026-10-12 ; il ne trouve qu'un mot entier, ou un début de mot avec like "alim*"). Sur Opendatasoft, ces deux opérateurs ne sont donc JAMAIS confiés au portail : ils sont calculés dans le navigateur, sur les lignes que le reste du filtre a laissées passer — les autres clauses (eq, in, comparaisons…) partent comme avant. Ce que cela coûte : la source charge toutes ces lignes au lieu des seules lignes retenues, dans la limite de max-records (au-delà, la troncature est signalée et le filtre ne porte que sur les lignes lues — fetch-mode="export" et un max-records relevé pour un grand jeu). Le regroupement suit la clause : group-by n'est alors plus délégué (le serveur regrouperait des lignes non filtrées), il est calculé ici. Quand cette requête est seule lectrice de sa chaîne, elle confie la clause à la source, qui la calcule sur le jeu chargé — et la REFUSE si elle est en server-side, où elle ne reçoit qu'une page : erreur de configuration, posée sur la source, qui nomme la clause et la correction (retirer server-side ; pour une saisie du visiteur, dsfr-data-search server-search). Sur une chaîne partagée rien n'est délégué, ni cette clause ni les autres : le filtre porte sur les lignes reçues, comme pour tout opérateur (#765). Tabular et Grist gardent leur délégation de contains (sous-chaîne sur ces deux API) : %, _ et \ de la valeur y sont échappés, chaque caractère vaut pour lui-même (#1285, lot 2). Sur ces deux API, notcontains est calculé dans le navigateur, comme sur Opendatasoft (leur NOT LIKE écarterait les valeurs absentes, que le filtre garde) ; sur Tabular, un contains dont la valeur porte * aussi (l'API le lit comme un joker). LISTE À PARENTHÈSE OU À VIRGULE (#1233) : champ:in:a|b (c) part au serveur comme toute liste — l'adaptateur Tabular écrit entre guillemets la valeur que l'API écarterait nue, et calcule la clause lui-même si l'API refuse cette forme (détail : where de dsfr-data-source). CHAMPS MULTIPLES (#1026) : nom|commune:contains:martin applique le MÊME opérateur et la MÊME valeur à plusieurs champs, reliés par un OU — la ligne passe dès qu'un des champs satisfait la clause ; les clauses entre elles restent en ET. Délégué en or=(…) sur Tabular (une seule clause multi-champs par requête, valeur sans , . ( ) " &, ni in / notin), en (… OR …) sur Opendatasoft et Grist (SQL) — sauf contains / notcontains sur Opendatasoft, calculés dans le navigateur (nom|commune:contains:martin, #1285) ; INSEE et les sources sans adaptateur le calculent dans le navigateur. CHAMP TABLEAU (#953, ex-#842) : eq / neq / in / notin regardent DANS le tableau. tags: ['urgent','social'] matche tags:eq:urgent, comme le compte déjà value="count:tags:urgent" de dsfr-data-kpi, et comme le retient le portail quand la clause lui est déléguée. C'est ce qui a été mesuré le 2026-09-19 sur deux portails et deux endpoints — keyword du catalogue de data.economie.gouv.fr, themes_attendus de retours-formulaire-votre-avis-copie sur data.education.gouv.fr : where=champ = "x" trouve la ligne sur n'importe quel ÉLÉMENT, et jamais sur le rendu texte complet du tableau. La règle exacte, côté client : eq(valeur, v) = (valeur est un tableau et un élément vaut v) OU String(valeur) === String(v) Le second terme est un repli que le portail n'a pas (['a','b'] matche 'a,b' en local, le portail rend 0) : il est gardé pour que eq / in ne puissent que GAGNER des correspondances, jamais en perdre. neq / notin en sont la négation, donc eux en perdent — et le portail fait pareil. VALEURS ABSENTES (#958) : une ligne dont le champ est nul ne satisfait **ni eq ni neq** — la logique SQL à trois valeurs qu'applique Opendatasoft. Mesuré le 2026-09-20 sur themes_attendus de retours-formulaire-votre-avis-copie (176 lignes dont 21 nulles) : = "Elèves" -> 124, != "Elèves" -> **31** (= 155 renseignées − 124), et non 52. eq les excluait déjà ; neq les gardait, d'où le même champ:neq:valeur rendant 31 lignes délégué et 52 au client. Pour retrouver les lignes absentes, les nommer : champ:isnull. ⚠️ notin et notcontains gardent, eux, les valeurs absentes — et c'est aligné aussi : ODSQL n'a pas d'infixe not in, donc notin se délègue en NOT champ in (…), une négation booléenne qui garde les nulles (mesuré : 52). Seul != est à trois valeurs. notcontains n'est plus délégué à Opendatasoft (#1285) ; calculé dans le navigateur, il garde les valeurs absentes partout. Les serveurs SQL sont jugés sur ce même sort des absents (#1312) : Tabular reçoit neq en __differs + __isnotnull (__differs seul les garde : 34 467 élus au lieu de 139, mesuré) et calcule notin dans le navigateur (not.in les écarte : 139 au lieu de 34 467) ; Grist reçoit notin en (champ IS NULL OR champ NOT IN (…)). ⚠️ tags:contains:urgent n'est toujours PAS un équivalent d'eq : il cherche une sous-chaîne dans String(tableau), donc « non-urgent » y matche « urgent », et la recherche traverse la virgule entre deux éléments. Pour éclater un multivalué avant un group-by, c'est explode (#736). Le booléen dérivé en amont (dsfr-data-normalize compute="a_urgent = when contains(tags,'urgent') then 1 else 0" puis where="a_urgent:eq:1") reste valide — le filtre final porte sur un scalaire, donc regroupable et délégable — mais il n'est plus NÉCESSAIRE pour obtenir le même compte des deux côtés. Pendant une version mineure, un avertissement de transition nomme le champ et la valeur des lignes qui se mettent à compter (dédupliqué par couple champ/valeur, jamais par ligne). |
filter | String | (même format que where) | Alias pour where (compatibilite) |
group-by | String | "champ1, champ2" | Champs de regroupement (séparés par virgule). Ordre d'application : le filtre (where de la query, de la source ou d'un dsfr-data-context) passe AVANT le regroupement — c'est aussi l'ordre ODSQL quand le regroupement est délégué au serveur. Un filtre ne peut donc pas viser un alias d'agrégat (montant__sum) : la colonne n'existe pas encore, l'API répond 400. Pour filtrer un résultat agrégé, poser une seconde dsfr-data-query en aval avec son propre where. Délégation au serveur seulement si la query est la SEULE lectrice de sa source (#765) : la source n'a qu'un regroupement, servi à tous ses abonnés. Source partagée (un KPI, un autre graphique…) : calcul côté client sur les lignes chargées, avec un avertissement. Pour garder l'agrégation serveur, donner à la query sa propre dsfr-data-source. Un regroupement resté côté client ne se calcule pas sur une source en pagination serveur (server-side), qui ne livre qu'une page : la requête passe en erreur de configuration (#1242). Retirer server-side de la source, ou donner à la requête sa propre source sans server-side. |
aggregate | String | "champ:fn" ou "champ:fn:alias" | Agrégations pour mode generic/tabular Format: "field:function, field2:function" Ex: "population:sum, count:count" Une absence n'est pas un zéro (#1198) : un groupe sans aucune valeur numérique rend null pour sum, avg, min et max ; count reste le nombre de lignes. running_sum (#738) n'est pas une réduction de groupe mais un CUMUL : il produit une ligne par ligne de sortie, chacune portant la somme des précédentes, calculée APRÈS order-by. Sans order-by, l'ordre des lignes reçues fait foi et le résultat n'a en général pas de sens : un avertissement console le signale. Le cumul reste toujours côté client. Ex. group-by="mois" aggregate="montant:sum, montant__sum:running_sum" avec order-by="mois:asc". diff (#775) en est l'inverse : l'écart de chaque ligne avec la précédente, pour retrouver le flux d'une série publiée déjà cumulée (aggregate="cumul:diff" → colonne cumul__diff). Mêmes règles : après order-by, jamais délégué, avertissement sans order-by. La première ligne vaut null, jamais 0 — un incrément inconnu n'est pas un incrément nul ; une valeur non numérique donne null pour elle et pour la suivante. ## share et share_percent — la part du total (#926) champ:share rend, pour chaque ligne de sortie, **la valeur de la ligne divisée par la somme de cette colonne sur toutes les lignes de sortie** — une répartition, sans seconde source ni jointure. Ex. group-by="typologie" aggregate="lics:sum, lics__sum:share" produit lics__sum__share (0,334 pour 33,4 %). share_percent rend la même part **en points de pourcentage** (33,4), la forme qu'attend un axe de graphique : une fraction dessinée sur un axe intitulé « % » y afficherait 0,33. Réserver share à ce qui sera formaté (dsfr-data-kpi format="pourcentage", qui met une fraction à l'échelle, comme le ratio de #673). Pour une part **au sein d'un groupe** (la part de chaque réponse parmi les répondants d'une année), poser en plus share-by : le dénominateur devient la somme de la colonne sur les lignes qui partagent les mêmes valeurs de ces champs. **Le dénominateur, et ce qu'il signifie.** C'est la somme de la colonne sur les lignes de sortie **avant limit** — pas sur le jeu entier. Trois conséquences, qui sont le piège de cette fonction bien plus que sa syntaxe : - une part est toujours une part **de l'ensemble filtré** : where, facettes, recherche et dsfr-data-context déplacent le dénominateur. C'est presque toujours ce qu'on veut (« part des licences de cette région »), mais il faut le dire en page : le même graphique montre 33,4 % sans filtre et 16,3 % en Bretagne, et les deux sont justes ; - avec limit, les parts affichées **ne somment pas à 100 %** : un top 10 montre la part de chaque ligne dans le TOUT, pas dans le top 10. C'est volontaire — l'inverse ferait d'une troncature d'affichage une redéfinition silencieuse du total ; - si la source est tronquée (max-records, pagination), le dénominateur l'est aussi. Un total faux ne se voit pas : les parts somment quand même à 100 %. **Ce qu'une part suppose.** Que les lignes soient une partition — chaque unité comptée une fois et une seule. Après explode, une ligne multivaluée compte dans N groupes : les parts somment alors à plus de 100 %, et il faut écrire au lecteur « part des licences portant ce label », pas « répartition ». Une colonne qui mêle des signes opposés n'a pas de part : la somme peut s'annuler. **Règles de calcul.** Total nul, absent ou non numérique : la part vaut null, jamais l'infini ni un zéro de complaisance. Une valeur non numérique donne null pour sa ligne et ne compte pas au dénominateur (même règle que sum, #301). Comme les cumulées : calcul toujours côté client, jamais délégué — et, comme elles, **demander une part empêche la délégation serveur du regroupement** : la query regroupe alors sur les lignes chargées, donc relever max-records avant de poser l'attribut sur un jeu volumineux. L'ordre des lignes, lui, est indifférent : pas d'order-by requis, pas d'avertissement. ## Jamais sur une seule page (#1242) Un agrégat calculé côté client — part, cumul, agrégat sans group-by (jamais délégué), ou regroupement que la source n'a pas pu prendre — porte sur les lignes reçues. Derrière une source en pagination serveur (server-side), la requête n'en reçoit qu'une page : elle passe en **erreur de configuration** et n'émet aucun chiffre. Le message nomme la source et dit la correction : retirer server-side (le jeu est chargé en entier, dans la limite de max-records) ; ou, si le jeu dépasse ce plafond ou si un tableau paginé lit la même source, donner à la requête sa propre source sans server-side, qui porte le regroupement délégable (group-by="region" aggregate="population:sum" sur la source, puis aggregate="population__sum:share_percent" ici, sur les groupes). Derrière une source en mode URL avec paginate, qu'aucun attribut ne fait charger en entier, le calcul a lieu sur la page reçue : un avertissement en console et le volet Diagnostic le disent. |
order-by | String | "champ:asc" ou "champ:desc" | Tri des résultats Format: "field:direction" ou "field__function:direction" Ex: "total_pop:desc" ou "population__sum:desc" Une clé peut nommer une colonne de FENÊTRE produite par aggregate — une part (n__share_percent), un cumul (montant__running_sum), un écart (cumul__diff), ou leur alias (#1244). Le tri porte alors sur les lignes de sortie : il est appliqué ici, après le calcul de la colonne et avant limit (« les cinq plus grandes parts »), et n'est jamais délégué au serveur, qui ne connaît pas cette colonne. Les autres clés du même order-by gardent leur rôle : ce sont elles qui ordonnent les lignes AVANT un cumul. Un cumul trié sur sa seule colonne est donc calculé dans l'ordre des lignes reçues — un avertissement console le signale. |
limit | Number | Entier positif. 0 = illimite | Limite de résultats |
Attributs retires (#277, #279) : transform, page-size et refresh se configurent sur <dsfr-data-source> ; server-side est sans objet (le relais de commandes vers la source est toujours actif).
where / filter (modes generic et tabular)Format : champ:operateur:valeur. Plusieurs filtres séparés par , (logique ET).
where="population:gt:5000" where="population:gt:5000, region:in:IDF|OCC|BRE" where="email:isnull" <!-- pas de valeur pour isnull/isnotnull --> where="region:in:IDF|OCC|BRE" <!-- separateur | pour in/notin -->
Les valeurs "true"/"false" sont parsées en booléen, les chaînes numériques en nombre.
aggregateFormat de base : champ:fonction — le champ de sortie est nomme champ__fonction.
Avec alias : champ:fonction:alias — le champ de sortie porte le nom de l'alias.
aggregate="population:sum" <!-- sortie : population__sum --> aggregate="population:sum:total" <!-- sortie : total --> aggregate="pop:sum:total, pop:count:nb" <!-- deux agrégations -->
Fonctions : count (nombre de lignes), sum, avg, min, max.
order-byFormat : champ:direction ou champ (défaut : asc).
Pour trier des résultats agrégés, utiliser le nom du champ de sortie.
order-by="nom:asc" order-by="population__sum:desc" <!-- champ agrège sans alias --> order-by="total:desc" <!-- champ agrège avec alias -->
explode
Une cellule qui contient une liste (["a", "b"], une ChoiceList Grist, un champ
découpé par split sur dsfr-data-normalize)
n'est pas une modalité. Sans explode, elle est ramenée en chaîne pour former la clé
de groupe : "a,b" devient une modalité à part entière, une COMBINAISON comptée pour
une valeur — là où dsfr-data-facets éclate le même champ.
Deux composants branchés sur le même champ donnaient donc des chiffres différents sans rien
signaler (#736). explode aligne le regroupement sur les facettes.
| Attribut | Type | Description |
|---|---|---|
explode | String | Champs multivalués à éclater avant le regroupement (séparés par virgule). Sans cet attribut, une cellule tableau est ramenée en chaîne pour la clé de groupe : ["a", "b"] devient la modalité "a,b", une COMBINAISON comptée comme une valeur — là où dsfr-data-facets éclate le même champ (#421). Les deux composants branchés sur le même champ donnaient donc des chiffres différents, sans rien signaler (#736). Avec explode="tags", chaque élément de la cellule produit sa propre ligne : les modalités du regroupement sont exactement celles de la facette du même champ, et une ligne portant N valeurs compte dans N groupes (les agrégats la comptent donc N fois). **Le compte est par ÉLÉMENT, pas par valeur distincte.** Une cellule qui répète un élément, ["Patrimoine", "Patrimoine"], produit DEUX lignes « Patrimoine » : count vaut 2 pour cette seule ligne d'origine, et sum ajoute deux fois sa mesure. C'est voulu — un éclatement rend ce que la cellule contient — et c'est le SEUL écart avec la facette du même champ, qui compte une ligne une fois par valeur distincte (son compteur annonce des lignes, #1227). Sur un champ où le doublon est possible (tableaux recollés, saisie libre), count ne se lit donc pas « nombre de lignes portant la valeur » : dédoublonner la cellule en amont, ou compter les identifiants distincts (aggregate="id:distinct"). Règles, alignées sur les facettes : les éléments vides sont ignorés, et une cellule sans aucune valeur (tableau vide, null, chaîne vide) ne produit AUCUNE ligne — pas de groupe « non renseigné », comme la facette n'a pas de modalité vide. Une cellule scalaire est inchangée. Chaque champ listé doit figurer dans group-by (sinon erreur de configuration et champ ignoré : éclater un champ hors regroupement dupliquerait les lignes et gonflerait les sommes). L'éclatement force le regroupement CÔTÉ CLIENT : aucune API du pipeline ne sait éclater un champ multivalué, déléguer produirait à nouveau des combinaisons. Sur une source volumineuse, penser au plafond de lignes rapatriées ; sur une source en pagination serveur (server-side), qui ne livre qu'une page, la requête passe en erreur de configuration (#1242) — retirer server-side de la source. Par défaut vide : le comportement historique est conservé. |
<dsfr-data-query id="par-theme" source="src" group-by="themes" explode="themes" aggregate="montant:sum" order-by="montant__sum:desc"> </dsfr-data-query>
Une ligne portant N valeurs compte dans N groupes : les agrégats la comptent donc N fois — c'est
voulu, et c'est ce que fait déjà la facette. Les éléments vides sont ignorés, et une cellule sans
aucune valeur (tableau vide, null, chaîne vide) ne produit aucune ligne : pas de
groupe « non renseigné ». Une cellule scalaire est inchangée. Chaque champ listé doit figurer dans
group-by, sinon la configuration est refusée et le champ ignoré : éclater un champ
hors regroupement dupliquerait les lignes et gonflerait les sommes. L'éclatement force le
regroupement côté client — aucune API du pipeline ne sait éclater un champ
multivalué : sur une source volumineuse, penser au plafond de lignes rapatriées.
share-by
share et share_percent rapportent chaque ligne au total de
toutes les lignes de sortie. C'est la bonne lecture d'une répartition, pas
celle d'une enquête : la part d'une réponse parmi les répondants d'une question, une année
donnée, se rapporte au total de cette question et de cette année.
share-by nomme cette partition — sans second regroupement, ni jointure, ni
compute.
| Attribut | Type | Description |
|---|---|---|
share-by | String | Partition de share et share_percent (AM-110) : champs, séparés par virgule, **au sein desquels** la part est calculée. Sans cet attribut, une part rapporte chaque ligne au total de TOUTES les lignes de sortie. Avec share-by="annee, question", elle la rapporte au total des lignes qui portent la même année ET la même question : les parts de chaque couple (année, question) somment à 100 %, sans second regroupement, ni jointure, ni compute. Ex. group-by="annee, question, reponse" aggregate="n:sum, n__sum:share_percent" share-by="annee, question". Le nom suit group-by et order-by : un attribut qui dit PAR QUOI. Il s'applique à toutes les parts de aggregate, et à elles seules — les cumulées (running_sum, diff) ne sont pas partitionnées. Règles : - avec un group-by (ou un agrégat global), chaque champ listé doit figurer dans group-by : après le regroupement, les lignes de sortie ne portent plus les autres colonnes, et la part retomberait en silence sur le total général. C'est une erreur de configuration, et la requête passe en erreur au lieu d'émettre un chiffre plausible. Sans regroupement, tout champ des lignes convient ; - share-by sans aucune part dans aggregate est signalé (erreur de configuration) et reste sans effet ; - une valeur absente (null, chaîne vide) forme sa propre partition, comme elle forme son propre groupe ; - le reste est inchangé : dénominateur pris AVANT limit (un top N par groupe ne somme pas à 100 %), valeur non numérique → null et hors dénominateur, total de partition nul → null ; - jamais délégué : comme toute part, il garde le regroupement CÔTÉ CLIENT, sur les lignes chargées, quel que soit l'adaptateur (Opendatasoft, Tabular, Grist). Sur un jeu volumineux, relever max-records — un dénominateur tronqué ne se voit pas. Sur une source en server-side, qui ne charge qu'une page, la requête passe en erreur de configuration (#1242) : retirer server-side, ou porter le regroupement sur une source sans server-side et garder ici la part. Par défaut vide : une part reste une part du total. |
<dsfr-data-query id="parts" source="enquete" group-by="annee, question, reponse" aggregate="n:sum, n__sum:share_percent:part" share-by="annee, question"> </dsfr-data-query>
Les parts de chaque couple (année, question) somment à 100 %. Avec un group-by,
chaque champ de share-by doit y figurer : après le regroupement, les lignes ne
portent plus les autres colonnes, et la part retomberait sur le total général — la
configuration est refusée et la requête passe en erreur, plutôt que d'émettre un chiffre
plausible. Une valeur absente forme sa propre partition, comme elle forme son propre groupe.
Le dénominateur reste pris avant limit : un top N par groupe ne somme pas à
100 %. Comme toute part, share-by garde le regroupement
côté client, quel que soit le type de source (Opendatasoft, Tabular,
Grist) : sur un jeu volumineux, relever max-records. Sans l'attribut, rien ne
change.
Un regroupement ou un agrégat gardé côté client — part, cumul, explode, agrégat
sans group-by, ou regroupement que la source n'a pas pu prendre — porte sur les
lignes reçues. Derrière une source en server-side, la requête n'en reçoit qu'une
page : elle passe en erreur de configuration
(data-dsfr-config-error, état d'erreur en aval) et n'émet aucun chiffre. Le
message nomme la source et la correction : retirer server-side (le jeu est
chargé en entier, dans la limite de max-records), ou donner à la requête sa
propre source sans server-side, qui porte le regroupement délégable — la part
se calcule alors ici, sur les groupes
(exemple). Derrière une source
en mode URL avec paginate, qu'aucun attribut ne fait charger en entier, le
calcul a lieu sur la page reçue ; un avertissement en console et le volet Diagnostic le
disent.
require-where
Sur une page d'exploration, afficher le jeu entier avant tout choix de l'utilisateur est au mieux
inutile, au pire coûteux. require-where met la requête en attente : elle n'émet
aucune ligne, publie dsfr-data-idle, et les afficheurs en aval rendent leur
idle-message au lieu du jeu complet.
| Attribut | Type | Description |
|---|---|---|
require-where | Boolean | N'émettre aucune ligne tant qu'aucun filtre n'est posé (#690). Pendant de require-where sur dsfr-data-source, pour les pages d'exploration : la requête reste en attente, émet dsfr-data-idle, et les afficheurs en aval rendent « choisissez un filtre » au lieu du jeu entier. Ce qui compte comme filtre : le where (ou filter) de CETTE requête — sur un query, c'est la surface de filtrage que la page pilote — et toute clause where non vide reçue par commande (facettes, recherche, dsfr-data-context). Tout retirer fait repasser la requête en attente. |
Ce qui compte comme filtre : le where (ou filter) de CETTE requête —
sur un query, c'est la surface de filtrage que la page pilote — et toute clause where
non vide reçue par commande (facettes, recherche,
dsfr-data-context). Tout retirer fait repasser la requête en
attente : jamais de requête « tout » implicite. C'est le pendant client de
require-where sur
<dsfr-data-source>, qui, lui, empêche le fetch.
Utilisables dans les attributs where / filter (format "champ:operateur:valeur") :
| Opérateur | Description | Exemple |
|---|---|---|
eq | Egal a | status:eq:active |
neq | Different de | status:neq:archived |
gt | Strictement superieur | population:gt:5000 |
gte | Superieur ou egal | population:gte:5000 |
lt | Strictement inférieur | population:lt:1000 |
lte | Inférieur ou egal | population:lte:1000 |
contains | Contient : sous-chaîne, n'importe où dans la valeur (casse ignorée, accents respectés) | nom:contains:paris |
notcontains | Ne contient pas | nom:notcontains:test |
in | Dans une liste (separateur |) | region:in:IDF|OCC|BRE |
notin | Pas dans une liste | region:notin:IDF|OCC |
isnull | Est null/undefined | email:isnull |
isnotnull | N'est pas null | email:isnotnull |
contains est une sous-chaîne, sur toutes les sources
champ:contains:tation retient les lignes dont la valeur porte « tation » n'importe où —
« tation » trouve « alimentation ». La casse est ignorée, les accents sont respectés.
notcontains en est la négation, et garde les valeurs absentes. C'est la bibliothèque qui dit
ce que le filtre retient, pas le serveur.
Un portail OpenDataSoft ne sait pas calculer une sous-chaîne : son like compare des mots
(like "%aliment%" rend 0 ligne pour 14 366 « alimentation », voir
la page OpenDataSoft). Sur une source
api-type="opendatasoft", ces deux opérateurs sont donc calculés
dans le navigateur ; les autres clauses du filtre partent au portail comme avant.
Sur OpenDataSoft, avec un contains | Ce qui se passe |
|---|---|
| Lignes téléchargées | Toutes celles que laissent passer les autres clauses, dans la limite de max-records (1 000 par défaut ; au-delà, la troncature est signalée). Pour un grand jeu : fetch-mode="export" et max-records à la taille du jeu, ou une égalité de plus dans le filtre. |
group-by de la même requête | Calculé dans le navigateur, sur les lignes filtrées (il n'est plus délégué). |
Source en server-side | Erreur de configuration : la source ne reçoit qu'une page. Retirer server-side, ou, pour une saisie du visiteur, dsfr-data-search server-search. |
dsfr-data-facets server-facets à côté | Erreur de configuration sur les facettes : leurs compteurs ignoreraient la clause. Retirer server-facets. |
group-by, aggregate ou select calculé posés sur la source | Erreur de configuration : porter le calcul par une dsfr-data-query. |
Quand la valeur cherchée est connue, champ:eq:valeur ou champ:in:a|b reste le
filtre le moins cher : il part au portail. Tabular et Grist gardent contains délégué,
jokers de la valeur échappés (50% trouve « 50% », pas « 500 ») ; ils calculent
notcontains dans le navigateur, avec les mêmes conséquences qu'ici (leur
NOT LIKE écarterait les lignes dont la valeur est absente), et Tabular aussi un
contains dont la valeur porte *, qu'il lirait comme un joker (#1285, lot 2).
« serveur » : la clause part à l'API, qui la calcule avec sa casse et ses accents. « navigateur » : la
source la calcule (sous-chaîne, casse ignorée, accents respectés) sur les lignes que le reste du filtre
laisse passer, dans la limite de max-records ; une source en server-side, un
agrégat posé sur la source ou des server-facets voisines passent alors en erreur de
configuration. Tableau tenu par un test contre la règle de la bibliothèque (ADR-159).
Valeurs absentes (null, champ manquant ; une chaîne vide est une valeur) :
neq les écarte, notin et notcontains les gardent, et une
négation n'est confiée à un serveur que s'il leur fait le même sort (#1312).
| Clause ou usage | Opendatasoft | Tabular | Grist |
|---|---|---|---|
contains (champ:contains:aliment) | navigateur : le like du portail compare des mots | serveur : ilike, \ % _ échappés | serveur : LIKE ? ESCAPE '\', \ % _ échappés |
contains, valeur à étoile (champ:contains:a*b) | navigateur | navigateur : l'API lit * comme un joker | serveur |
notcontains (champ:notcontains:aliment) | navigateur | navigateur : NOT ILIKE écarte les valeurs absentes | navigateur : NOT LIKE écarte les valeurs absentes |
neq (champ:neq:archive) | serveur : != écarte les valeurs absentes, comme le navigateur | serveur : __differs et __isnotnull (__differs seul garde les valeurs absentes) | serveur : != écarte les valeurs absentes, comme le navigateur |
notin (champ:notin:archive) | serveur : NOT … in (…) garde les valeurs absentes, comme le navigateur | navigateur : not.in écarte les valeurs absentes | serveur : (champ IS NULL OR champ NOT IN (…)) garde les valeurs absentes |
gt gte lt lte | serveur | serveur | serveur : (champ != '' AND champ < ?) écarte une case Text vide, que SQLite range avant tout texte (#1321) ; borne numérique sur une colonne Text, Choice ou Any : navigateur, SQLite y comparerait des textes (#1325) |
eq in isnull isnotnull | serveur | serveur | serveur |
| casse, clause confiée | sans objet | ignorée | ignorée pour les lettres ASCII seulement (É n'est pas é) ; sous gt lt, ordre par octets (a après B), toléré comme les accents (ADR-159, 2026-10-15) |
| accents, clause confiée | sans objet | respectés | respectés |
recherche de dsfr-data-search en mode context | serveur : search(champ, "q"), début de mot, casse et accents ignorés | serveur : champ:contains:q, sous-chaîne, accents respectés ; terme à étoile : navigateur | serveur : champ:contains:q, sous-chaîne, casse ASCII ignorée, accents respectés |
Récupère les données d'une <dsfr-data-source> et les filtre/trie côté client.
Ici on filtre les 10 premieres lignes triees par nom.
Voir l'exemple live
<dsfr-data-source id="raw-data" url="https://mon-api.gouv.fr/records" transform="results" ></dsfr-data-source> <dsfr-data-query id="filtered-data" source="raw-data" where="population:gt:5000" order-by="nom:asc" limit="10" ></dsfr-data-query> <dsfr-data-chart source="filtered-data" type="bar" label-field="nom" value-field="population" ></dsfr-data-chart>
Regroupe les données par région et calcule la somme de la population et le nombre d'enregistrements par groupe. Voir l'exemple live
<dsfr-data-source id="communes" url="https://mon-api.gouv.fr/communes" transform="results" ></dsfr-data-source> <dsfr-data-query id="stats-region" source="communes" group-by="region" aggregate="population:sum, population:count" order-by="population__sum:desc" limit="10" ></dsfr-data-query> <dsfr-data-chart source="stats-region" type="bar" label-field="region" value-field="population__sum" ></dsfr-data-chart>
Utilise <dsfr-data-source> pour interroger une API OpenDataSoft,
puis <dsfr-data-query> pour le tri et la limite côté client.
Voir l'exemple live
<dsfr-data-source id="ods-src" api-type="opendatasoft" dataset-id="demographyref-communes-2021" base-url="https://data.opendatasoft.com" select="sum(pop_municipale) as total_pop, nom_reg" where="pop_municipale > 5000" group-by="nom_reg" ></dsfr-data-source> <dsfr-data-query id="ods-stats" source="ods-src" order-by="total_pop:desc" limit="15" ></dsfr-data-query> <dsfr-data-chart source="ods-stats" type="bar" label-field="nom_reg" value-field="total_pop" ></dsfr-data-chart>
Utilise <dsfr-data-source> pour interroger une API OpenDataSoft,
puis <dsfr-data-query> pour le groupement et l'agrégation.
Voir l'exemple live
<dsfr-data-source id="ods-src" api-type="opendatasoft" dataset-id="rappelconso-v2-gtin-trie" base-url="https://data.economie.gouv.fr" server-side page-size="100" ></dsfr-data-source> <dsfr-data-query id="ods-stats" source="ods-src" group-by="categorie_produit" aggregate="libelle:count:nombre" order-by="nombre:desc" limit="8" ></dsfr-data-query> <dsfr-data-chart source="ods-stats" type="pie" label-field="categorie_produit" value-field="nombre" ></dsfr-data-chart>
Plusieurs filtres peuvent être combinés en les separant par des virgules. Tous les filtres doivent être satisfaits (logique ET). Voir l'exemple live
<dsfr-data-query id="filtered" source="raw-data" where="population:gte:10000, region:in:IDF|OCC|BRE, status:eq:active" order-by="population:desc" limit="20" ></dsfr-data-query>
L'attribut headers sur <dsfr-data-source> permet de passer des headers HTTP
(API key, Bearer token) pour acceder a des datasets prives.
Voir l'exemple live
<dsfr-data-source id="private-src" api-type="opendatasoft" dataset-id="mon-dataset-prive" base-url="https://mon-instance.opendatasoft.com" headers='{"apikey":"ma-clé-api"}' ></dsfr-data-source> <dsfr-data-query id="private-data" source="private-src" group-by="region" aggregate="population:sum:total" order-by="total:desc" ></dsfr-data-query> <dsfr-data-chart source="private-data" type="bar" label-field="region" value-field="total" ></dsfr-data-chart>
Securite : les headers sont visibles dans le code source HTML. Ne les utilisez que pour des clés a acces restreint (lecture seule) ou dans des contextes proteges (intranet, applications internes).
L'attribut refresh sur <dsfr-data-source> permet de re-exécuter
la requête a intervalles reguliers.
<dsfr-data-source id="live-src" api-type="opendatasoft" dataset-id="mon-dataset" base-url="https://data.opendatasoft.com" select="count(*) as total" refresh="30" ></dsfr-data-source> <dsfr-data-query id="live-stats" source="live-src" ></dsfr-data-query>
Le composant émet les mêmes événements que <dsfr-data-source> :
dsfr-data-loaded : Données transformees disponiblesdsfr-data-loading : Traitement en coursdsfr-data-error : Erreur de traitement ou de requête| Méthode | Retour | Description |
|---|---|---|
reload() | void | Force le rechargement / re-traitement des données |
getData() | Array | Retourne les données transformees actuelles |
isLoading() | Boolean | Indique si un traitement est en cours |
getError() | Error | null | Retourne l'erreur eventuelle |
Pipeline recommande : Utilisez <dsfr-data-source> pour le fetch HTTP
et <dsfr-data-query> pour les transformations client-side (filter, group-by, aggregate, sort).
Pour les gros volumes, deleguer les agrégations a la source via les attributs de <dsfr-data-source>.
Nommage des champs agrégés : En mode generic, les champs agrégés sont nommes
automatiquement champ__fonction (ex: population__sum, population__count).
Vous pouvez definir un alias avec le format "champ:fonction:alias".
Chainabilite : Un <dsfr-data-query> peut servir de source a un autre
<dsfr-data-query> en utilisant son id comme attribut source,
permettant de construire des pipelines de transformation.