Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/databox-name-map-level.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'dsfr-data': patch
---

Alignement DSFR Chart 2.1.x (correctifs) : la DataBox pose désormais `name` (renommage upstream de `title` en 2.1.0 — les titres de DataBox étaient invisibles en preview/prod) tout en conservant `title` pour les hôtes 2.0.x ; les types `map` et `map-reg` routent vers `<map-chart level="dep|reg">` (API cartes unifiée), ce qui corrige la limitation connue de la carte régionale nationale (`<map-chart-reg>` sans `region`).
5 changes: 5 additions & 0 deletions .changeset/map-aca-monde.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'dsfr-data': minor
---

Nouveaux types de cartes `map-aca` (académies, clés = noms en majuscules) et `map-monde` (mondiale, clés ISO 3166-1 — les codes alpha-3 et numériques sont convertis automatiquement en alpha-2 via `toIsoA2`) sur `dsfr-data-chart`, apportés par l'API cartes unifiée `<map-chart level>` de DSFR Chart 2.1. `dsfr-data-world-map` est déprécié au profit de `type="map-monde"` (warn console ; retrait prévu à la prochaine version majeure, #402).
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@gouvfr/dsfr@1.14.4/dist/dsfr.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@gouvfr/dsfr-chart@2.0.4/dist/DSFRChart/DSFRChart.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@gouvfr/dsfr-chart@2.0.4/dist/DSFRChart/DSFRChart.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@gouvfr/dsfr-chart@2.1.1/dist/DSFRChart/DSFRChart.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@gouvfr/dsfr-chart@2.1.1/dist/DSFRChart/DSFRChart.js"></script>
<script src="https://unpkg.com/dsfr-data/dist/dsfr-data.core.umd.js"></script>
```

Expand Down Expand Up @@ -104,6 +104,12 @@ Pour le detail du monorepo, des conventions, du workflow de release Changesets e
- [Fiche produit](docs/DATASHEET.md) — positionnement, comparatif, cibles
- [Politique de securite](docs/SECURITY.md) + [baseline](docs/security-baseline.md) — signalement, pipeline CI/CD, defenses

## Credits

- Les graphiques et cartes sont rendus par **[DSFR Chart](https://github.com/GouvernementFR/dsfr-chart)** (`@gouvfr/dsfr-chart`), la bibliotheque officielle de datavisualisation de l'Etat — merci a ses mainteneurs. `dsfr-data` se concentre sur la couche **donnees** (sources, requetes, transformations) et delegue le rendu graphique a DSFR Chart, dont il suit les evolutions (API cartes unifiee `level` depuis la 2.1).
- Le design et les composants d'interface suivent le **[Systeme de design de l'Etat (DSFR)](https://www.systeme-de-design.gouv.fr/)**.
- Dependances tierces detaillees : [THIRD-PARTY-LICENSES](docs/THIRD-PARTY-LICENSES.md).

## Licence

MIT.
4 changes: 2 additions & 2 deletions apps/builder-ia/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js" integrity="sha384-9nhczxUqK87bcKHh20fSQcTGD4qq5GhayNYSYWqwBkINBhOfQLg/P5HG5lF1urn4" crossorigin="anonymous"></script>

<!-- DSFR Chart (pour les cartes) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@gouvfr/dsfr-chart@2.0.4/dist/DSFRChart/DSFRChart.css" integrity="sha384-987+TUcyVaQAI/PaEqYIGNqku/bHSkNVAqOfNxyS1eE9SxeeH2Ue4FQZ3kKtnWfM" crossorigin="anonymous">
<script type="module" src="https://cdn.jsdelivr.net/npm/@gouvfr/dsfr-chart@2.0.4/dist/DSFRChart/DSFRChart.js" integrity="sha384-xYrR55uYb2Y55H6eELrLA9ARM8YsjB1ksG6QHJZrhL49lzax9l7rHuQPjsZGiTYk" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@gouvfr/dsfr-chart@2.1.1/dist/DSFRChart/DSFRChart.css" integrity="sha384-5m9vgfA+uOR3qcY14HhRzWDK17cwEVXO75RSwv2KJXignSdIrOvz1xNaaDpWXCeu" crossorigin="anonymous">
<script type="module" src="https://cdn.jsdelivr.net/npm/@gouvfr/dsfr-chart@2.1.1/dist/DSFRChart/DSFRChart.js" integrity="sha384-OJTIJmv0zL1Ym2Bd6hA0oHB25gTqveSogoyVtnN3cEJcm4qE28qzTEp56vJeeKIS" crossorigin="anonymous"></script>

<!-- Composants dsfr-data -->
<script type="module" src="../../packages/core/dist/dsfr-data.esm.js"></script>
Expand Down
4 changes: 4 additions & 0 deletions apps/builder-ia/src/chat/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,10 @@ function repairAction(parsed: Record<string, unknown>): Record<string, unknown>
map: 'map',
carte: 'map',
'map-reg': 'map-reg',
'map-aca': 'map-aca',
'map-monde': 'map-monde',
monde: 'map-monde',
world: 'map-monde',
'bar-line': 'bar-line',
barline: 'bar-line',
};
Expand Down
2 changes: 2 additions & 0 deletions apps/builder-ia/src/ia/action-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export const CHART_TYPES = [
'map',
'bar-line',
'map-reg',
'map-aca',
'map-monde',
'datalist',
'podium',
] as const;
Expand Down
10 changes: 8 additions & 2 deletions apps/builder-ia/src/ia/data-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,14 @@ export function diagnoseConfig(config: Partial<ChartConfig>, data: Row[]): Diagn
if (type === 'datalist' && !config.colonnes) {
warnings.push('datalist sans "colonnes" : toutes les colonnes seront affichees.');
}
if ((type === 'map' || type === 'map-reg') && !config.codeField && !config.labelField) {
errors.push('Carte sans codeField : un champ code INSEE (departement/region) est requis.');
if (
(type === 'map' || type === 'map-reg' || type === 'map-aca' || type === 'map-monde') &&
!config.codeField &&
!config.labelField
) {
errors.push(
'Carte sans codeField : un champ code est requis (INSEE departement/region, nom d academie ou code pays ISO).'
);
}

// 3) Filtre WHERE.
Expand Down
63 changes: 51 additions & 12 deletions apps/builder-ia/src/skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
| labelField | String | selon type | Champ pour les labels / axe X |
| valueField | String | oui | Champ pour les valeurs / axe Y |
| valueField2 | String | non | 2e série (bar-line, comparaisons) |
| codeField | String | non | Champ code departement/region (map, map-reg) |
| codeField | String | non | Champ code : departement/region (map, map-reg), nom d'academie (map-aca), code pays ISO (map-monde) |
| aggregation | String | non | Fonction : sum, avg, count, min, max |
| where | String | non | Filtre pre-agrégation (voir syntaxe ci-dessous) |
| limit | Number | non | Nombre max de resultats |
Expand Down Expand Up @@ -94,13 +94,15 @@
| kpi | non | oui | Indicateur chiffre clé unique |
| map | non (codeField) | oui | Données par departement francais |
| map-reg | non (codeField) | oui | Données par region francaise |
| map-aca | non (codeField) | oui | Données par academie (noms en majuscules : PARIS, LYON...) |
| map-monde | non (codeField) | oui | Données par pays (ISO 3166-1 : FR, US... — a3/num convertis) |
| datalist | non | non (colonnes) | Tableau de données filtrable |

IMPORTANT :
- \`doughnut\` = \`pie\` (le composant pie est un anneau par défaut)
- \`horizontalBar\` = \`bar\` (le renderer le convertit automatiquement)
- Pour KPI et gauge : PAS de labelField
- Pour map/map-reg : utiliser codeField (pas labelField)
- Pour map/map-reg/map-aca/map-monde : utiliser codeField (pas labelField)

### Syntaxe du filtre (config.where)
Format : \`"champ:operateur:valeur"\`
Expand Down Expand Up @@ -1069,6 +1071,9 @@
'gauge',
'departement',
'region',
'academie',
'monde',
'pays',
'databox',
'habillage',
'encadrer',
Expand Down Expand Up @@ -1105,8 +1110,10 @@
| scatter | scatter-chart | Nuage de points |
| gauge | gauge-chart | Jauge circulaire 0-100% |
| bar-line | bar-chart + line-chart | Combine barres et ligne (2 séries) |
| map | map-chart | Carte par departement francais |
| map-reg | map-chart-reg | Carte par region francaise |
| map | map-chart (level="dep") | Carte par departement francais |
| map-reg | map-chart (level="reg") | Carte par region francaise |
| map-aca | map-chart (level="aca") | Carte par academie |
| map-monde | map-chart (level="monde") | Carte mondiale par pays |

### Attributs
| Attribut | Type | Défaut | Requis | Description |
Expand All @@ -1131,7 +1138,7 @@
| y-min | String | \`""\` | non | Limite min axe Y |
| y-max | String | \`""\` | non | Limite max axe Y |
| gauge-value | Number | \`null\` | type gauge | Valeur de la jauge (0-100) |
| code-field | String | \`""\` | type map/map-reg | Champ contenant le code departement ou region (prioritaire sur label-field) |
| code-field | String | \`""\` | types map* | Champ contenant le code : departement/region (map, map-reg), nom d'academie en majuscules (map-aca), code pays ISO 3166-1 alpha-2/alpha-3/numerique (map-monde, converti en alpha-2) — prioritaire sur label-field |
| map-highlight | String | \`""\` | non | Departements/regions a surligner |
| reference-lines | String | \`""\` | non | Lignes de reference (overlay) en JSON. Cartesiens uniquement (line, bar, bar-line, scatter). Chaque item : \`{ axis: "x" ou "y", value (string ou number), label?, color?, dash?, position? }\`. \`axis:"x"\` → ligne verticale a une categorie/date ; \`axis:"y"\` → ligne horizontale a un seuil. Ex : \`reference-lines='[{"axis":"x","value":"2026-02","label":"Lancement","color":"#c9191e","dash":true},{"axis":"y","value":3000,"label":"Objectif"}]'\`. |
| targets | String | \`""\` | non | Cibles / objectifs futurs (overlay) en JSON. Types line et bar-line uniquement. Chaque item : \`{ x (echeance, string ou number, requis), value (number, requis), series? (nom de dataset ou index, defaut 0), label?, color? }\`. L'axe X est etendu automatiquement si l'echeance depasse les donnees : trait plein jusqu'au dernier point reel, trajectoire pointillee vers un losange a l'echeance, zone future grisee. Ex : \`targets='[{"x":2030,"value":26,"label":"Cible 2030 : 26 %"}]'\`. |
Expand All @@ -1150,6 +1157,8 @@
| bar-line | source, type, label-field, value-field, value-field-2 | name, unit-tooltip, unit-tooltip-bar |
| map | source, type, code-field, value-field | selected-palette, map-highlight |
| map-reg | source, type, code-field, value-field | selected-palette, map-highlight |
| map-aca | source, type, code-field, value-field | selected-palette, map-highlight |
| map-monde | source, type, code-field, value-field | selected-palette, map-highlight |

### Exemples
\`\`\`html
Expand Down Expand Up @@ -1194,6 +1203,17 @@
code-field="code_reg" value-field="valeur">
</dsfr-data-chart>

<!-- Carte par academie (noms en majuscules : PARIS, LYON...) -->
<dsfr-data-chart source="aca-data" type="map-aca"
code-field="academie" value-field="valeur">
</dsfr-data-chart>

<!-- Carte mondiale (codes pays ISO : FR, US ou FRA, USA ou 250, 840) -->
<dsfr-data-chart source="pays-data" type="map-monde"
code-field="code_pays" value-field="valeur"
selected-palette="sequentialAscending">
</dsfr-data-chart>

<!-- Jauge -->
<dsfr-data-chart type="gauge" gauge-value="73"></dsfr-data-chart>
\`\`\`
Expand Down Expand Up @@ -1502,15 +1522,20 @@
### <radar-chart>
- Multi-séries pour comparer des profils

### <map-chart> (carte par departement)
- data='{"75": 95, "69": 78, "2A": 60}' : JSON code_dept -> valeur
- Codes departements valides : 01-95, 2A, 2B, 971-976
### <map-chart> (cartes choroplethes — API unifiee DSFR Chart 2.1)
- level : decoupage — "dep" (défaut), "reg", "aca", "monde"
- data : JSON code -> valeur, selon le level :
- level="dep" : data='{"75": 95, "69": 78, "2A": 60}' (codes 01-95, 2A, 2B, 971-976)
- level="reg" : data='{"11": 95, "84": 78}' (codes region INSEE)
- level="aca" : data='{"PARIS": 95, "LYON": 78}' (noms d'academie majuscules)
- level="monde" : data='{"FR": 95, "US": 78}' (ISO 3166-1 alpha-2)
- name : nom de l'indicateur
- value-nat : valeur nationale de reference
- selected-palette : palette de couleurs

### <map-chart-reg> (carte par region)
- Même format que map-chart avec codes region
### <map-chart-reg region="..."> (zoom sur UNE region)
- Zoome sur une region donnee, data par departement de cette region
- Ex : region="11" data='{"75": 95, "92": 78}'

### Attributs communs
- selected-palette : categorical, sequentialAscending, sequentialDescending, divergentAscending, divergentDescending, neutral, default
Expand Down Expand Up @@ -1929,6 +1954,15 @@
- **Quand** : données geographiques par region francaise
- **Champs** : code-field (code region), value-field

### Carte academies (map-aca)
- **Quand** : données education par academie
- **Champs** : code-field (nom d'academie en majuscules : PARIS, LYON, STRASBOURG...), value-field

### Carte mondiale (map-monde)
- **Quand** : données internationales par pays
- **Champs** : code-field (code pays ISO 3166-1 : alpha-2 "FR", alpha-3 "FRA" ou numerique "250" — convertis automatiquement en alpha-2), value-field
- **Palette recommandee** : sequentialAscending

### Séries multiples (bar, line, bar-line, radar)
Utiliser \`value-field-2\` pour une seconde série, ou \`value-fields\` pour plusieurs séries supplementaires (separees par virgules).
Definir les noms avec \`name='["Série 1","Série 2","Série 3"]'\`.
Expand Down Expand Up @@ -1964,7 +1998,7 @@

### Bonnes pratiques
- Utiliser \`categorical\` pour pie, radar et comparaisons multi-catégories
- Utiliser \`sequentialAscending\` pour les cartes (map, map-reg)
- Utiliser \`sequentialAscending\` pour les cartes (map, map-reg, map-aca, map-monde)
- Utiliser \`neutral\` + \`highlight-index\` pour mettre en avant une valeur
- Assurer un contraste suffisant (conformite RGAA)
- Eviter le rouge/vert seuls (daltonisme) - les palettes DSFR sont concues pour ca`,
Expand Down Expand Up @@ -2262,7 +2296,12 @@
'planisphere',
'carte pays',
],
content: `## dsfr-data-world-map — Carte choroplèthe mondiale
content: `## dsfr-data-world-map — Carte choroplèthe mondiale (DEPRECIE)

⚠️ DEPRECIE : preferer \`<dsfr-data-chart type="map-monde">\` (carte mondiale
DSFR Chart native, aucun bundle supplementaire). Ce composant sera retire
dans une prochaine version majeure. Il reste pertinent uniquement si le zoom
continent interactif est requis.

Composant qui affiche une carte du monde SVG (projection Natural Earth)
où chaque pays est colorié selon une valeur numérique.
Expand Down Expand Up @@ -2689,7 +2728,7 @@
|----------|------|--------|--------|-------------|
| sources | String | \`""\` | oui | Ids des sources cibles, separes par des espaces |
| url-sync | Boolean | \`false\` | non | Serialisation URL des filtres (#231, opt-in) : lecture au chargement (pre-remplit les UI), ecriture replaceState, parametres voisins preserves |
| url-param-map | String | \`""\` | non | Renommage des parametres URL : \`"param:field \| param2:field2"\` |

Check warning on line 2731 in apps/builder-ia/src/skills.ts

View workflow job for this annotation

GitHub Actions / quality

Unnecessary escape character: \|

### Pattern

Expand Down
2 changes: 2 additions & 0 deletions apps/builder-ia/src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export interface ChartConfig {
| 'map'
| 'bar-line'
| 'map-reg'
| 'map-aca'
| 'map-monde'
| 'datalist'
| 'podium';
labelField?: string;
Expand Down
28 changes: 19 additions & 9 deletions apps/builder-ia/src/ui/chart-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@
* Chart rendering - applies chart configuration to generate visual output
*/

import { escapeHtml, DSFR_COLORS, PALETTE_COLORS, isValidDeptCode } from '@dsfr-data/shared';
import {
escapeHtml,
DSFR_COLORS,
PALETTE_COLORS,
isValidDeptCode,
MAP_LEVEL_MAP,
} from '@dsfr-data/shared';
import { state } from '../state.js';
import type { ChartConfig, AggregatedResult } from '../state.js';
import { addMessage } from '../chat/chat.js';
Expand Down Expand Up @@ -197,7 +203,7 @@ export function applyChartConfig(config: ChartConfig): void {

// Aggregate data for charts
const aggregated: Record<string, { values: number[]; count: number; code: string | null }> = {};
const isMap = config.type === 'map' || config.type === 'map-reg';
const isMap = config.type in MAP_LEVEL_MAP;
const codeField = config.codeField || config.labelField;

workingData.forEach((record) => {
Expand Down Expand Up @@ -333,33 +339,37 @@ function renderChart(config: ChartConfig, data: AggregatedResult[]): void {
return;
}

// Handle map type (uses DSFR map-chart / map-chart-reg)
if (config.type === 'map' || config.type === 'map-reg') {
// Handle map types (uses DSFR <map-chart level="…">, API unifiee 2.1.0)
if (config.type in MAP_LEVEL_MAP) {
canvas.style.display = 'none';
emptyState.style.display = 'none';

// Transform data to DSFR format: {"code": value, ...}
const mapData: Record<string, number> = {};
data.forEach((d) => {
let code = String(d.code || d.label || '').trim();
if (/^\d+$/.test(code) && code.length < 3) {
if (config.type === 'map-aca' || config.type === 'map-monde') {
// aca : noms d'academie majuscules ; monde : ISO alpha-2
// (la conversion a3/num est faite par dsfr-data-chart, pas ici)
code = code.toUpperCase();
} else if (/^\d+$/.test(code) && code.length < 3) {
code = code.padStart(2, '0');
}
const value = d.value || 0;
if (isValidDeptCode(code)) {
if (config.type === 'map' ? isValidDeptCode(code) : code !== '') {
mapData[code] = Math.round(value * 100) / 100;
}
});

const mapTag = config.type === 'map-reg' ? 'map-chart-reg' : 'map-chart';
const mapCard = document.createElement('div');
mapCard.className = 'map-card';
mapCard.innerHTML = `
<${mapTag}
<map-chart
level="${MAP_LEVEL_MAP[config.type]}"
data='${JSON.stringify(mapData)}'
name="${escapeHtml(config.title || 'Carte')}"
selected-palette="${config.palette || 'sequentialAscending'}"
></${mapTag}>
></map-chart>
`;
chartWrapper.appendChild(mapCard);
return;
Expand Down
19 changes: 12 additions & 7 deletions apps/builder-ia/src/ui/code-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
extractResourceIds,
filterToOdsql,
formatKPIValue,
MAP_LEVEL_MAP,
} from '@dsfr-data/shared';
import { state } from '../state.js';
import type { ChartConfig, AggregatedResult } from '../state.js';
Expand Down Expand Up @@ -99,8 +100,8 @@ export function generateCode(config: ChartConfig, data: AggregatedResult[]): voi
return;
}

// Handle map type (department or region)
if (config.type === 'map' || config.type === 'map-reg') {
// Handle map types (departments, regions, academies, world)
if (config.type in MAP_LEVEL_MAP) {
codeEl.textContent = generateMapCode(config, data);
return;
}
Expand Down Expand Up @@ -322,11 +323,15 @@ function generateMapCode(config: ChartConfig, data: AggregatedResult[]): string
const mapData: Record<string, number> = {};
data.forEach((d) => {
let code = String(d.code || d.label || '').trim();
if (/^\d+$/.test(code) && code.length < 3) {
if (config.type === 'map-aca' || config.type === 'map-monde') {
// aca : noms d'academie majuscules ; monde : ISO alpha-2
// (la conversion a3/num est faite par dsfr-data-chart, pas ici)
code = code.toUpperCase();
} else if (/^\d+$/.test(code) && code.length < 3) {
code = code.padStart(2, '0');
}
const value = d.value || 0;
if (isValidDeptCode(code)) {
if (config.type === 'map' ? isValidDeptCode(code) : code !== '') {
mapData[code] = Math.round(value * 100) / 100;
}
});
Expand Down Expand Up @@ -479,7 +484,6 @@ function generateMapCode(config: ChartConfig, data: AggregatedResult[]): string
}

// Embedded-data variant
const mapTagEmbed = config.type === 'map-reg' ? 'map-chart-reg' : 'map-chart';
return `<!-- Carte generee avec dsfr-data Builder IA -->
<!-- Source : ${state.source?.name || 'Données locales'} -->

Expand All @@ -492,11 +496,12 @@ function generateMapCode(config: ChartConfig, data: AggregatedResult[]): string
<div class="fr-container fr-my-4w">
<h2>${escapeHtml(config.title || 'Carte de France')}</h2>
${config.subtitle ? `<p class="fr-text--sm fr-text--light">${escapeHtml(config.subtitle)}</p>` : ''}
<${mapTagEmbed}
<map-chart
level="${MAP_LEVEL_MAP[config.type]}"
data='${JSON.stringify(mapData)}'
name="${escapeHtml(config.title || 'Carte')}"
selected-palette="${config.palette || 'sequentialAscending'}"
></${mapTagEmbed}>
></map-chart>
</div>`;
}

Expand Down
Loading
Loading