graphic-chart
Génère des graphiques de visualisation de données au format PNG. Rend le HTML avec Apache ECharts v6 dans Chromium headless via Playwright → captures d'écran en qualité rétine 2×.
CDN : https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js
Règles critiques (à lire avant chaque génération)
- type
area→type: 'line'+areaStyle: {}— ECharts n'a pas detype: 'area'. doughnut→type: 'pie'+radius: ['40%', '70%']— ECharts n'a pas detype: 'doughnut'.- Signal de préparation : enregistrer
chart.on('finished', fn)AVANTchart.setOption()— Bug ECharts #14101/#17500 : si l'écouteur est enregistré après setOption, il ne se déclenche jamais silencieusement. Toujours enregistrer les événementsfinishedetrenderedavant setOption. xAxis.type: 'category'doit être explicite — ECharts ne l'infère pas à partir des données. L'oublier produit un graphique vide.- Les étiquettes de catégories vont dans
xAxis.data, pas dans un tableaudata.labels. La structure ECharts est plate :{ xAxis, yAxis, series, grid, title, legend }— pas imbriquée sousdataouoptions. - Les étiquettes de données sont entièrement intégrées — utiliser
label: { show: true }sur n'importe quelle série. Aucun plugin nécessaire. - Mettre en surbrillance une barre/point spécifique via
itemStylepar élément — placer{ value: N, itemStyle: { color: '#...' } }directement dans le tableaudata. Ne PAS utiliser de tableauxbackgroundColorde type Chart.js. - L'initialisation ECharts utilise un conteneur
<div>, pas<canvas>—echarts.init(document.getElementById('chart')). Le div conteneur nécessite des dimensions explicites. animation: falsedans l'option — désactive l'animation pour un rendu instantané. Toujours enregistrer les événementsfinished+renderedavant setOption pour le signal de préparation.- Ne jamais déverser le HTML dans le chat. Sauvegarder dans un fichier, afficher uniquement le résumé.
- Le titre énonce l'information clé, pas le sujet. « Les revenus ont triplé en 12 mois » pas « Revenus mensuels ».
- Camembert/Anneaux : utiliser le
padding: 64px 80pxdu corps et.chart-container { max-height: 860px }— empêche le remplissage bord à bord lorsqu'il n'y a pas de titre.
Étape 1 : Réception
Requis : chart_type, data
Paramètres optionnels et valeurs par défaut :
| Paramètre | Défaut | Description |
|---|---|---|
| chart_type | — | bar / line / area / pie / doughnut / scatter / radar / treemap |
| data | — | Tableau JSON ou CSV — requis |
| title | — | Énonce l'information clé, ≤10 mots |
| subtitle | — | Ligne de contexte d'une phrase |
| style | clean-slate | clean-slate / midnight-editorial / matt-gray / electric-burst / brutalist |
| dimensions | 1080x1080 | Pixels LxH (PNG de sortie = 2× via deviceScaleFactor) |
| x_label | — | Texte de l'étiquette de l'axe X |
| y_label | — | Texte de l'étiquette de l'axe Y |
| source | — | Source de données affichée dans le pied de page |
| highlight | — | Étiquette de données à mettre en surbrillance (ex. "T4", "Dec", index 3) |
Si chart_type ou data est manquant, demander exactement :
"Pour créer le graphique, j'ai besoin de :
- Type de graphique — bar / line / area / pie / doughnut / scatter / radar / treemap
- Données — fournir sous forme de tableau JSON ou CSV (ex.
[12, 18, 22, 25, 31]avec les étiquettes['Q1','Q2','Q3','Q4','Q5'])Optionnel : titre, style (défaut : clean-slate), dimensions (défaut : 1080×1080), mettre en surbrillance un point de données spécifique"
Si les deux sont présents → passer à l'Étape 2.
Étape 2 : Architecture interne (jamais montrée à l'utilisateur)
1. Normaliser le type de graphique :
area→line+areaStyle: {}sur la sériedoughnut→pie+radius: ['40%', '70%']sur la sériehorizontal bar→bar+ échanger xAxis/yAxis (axe des catégories sur y)- Tous les autres : utiliser tels quels
2. Lire references/chart-library.md — charger la spécification de configuration complète pour ce type de graphique.
3. Lire references/style-presets.md — charger les tokens CSS + la palette de données pour le style choisi.
4. S'engager sur la direction de conception :
| Décision | Dériver de |
|---|---|
| Ton | Professionnel / éditorial / audacieux / technique — correspondre au public des données |
| Histoire des données | Information unique que ce graphique prouve (devient le titre) |
| Stratégie de surbrillance | Quel point de données a besoin d'emphase visuelle et pourquoi ? |
| Arrière-plan | Clair (clean-slate, matt-gray) ou foncé (midnight-editorial, electric-burst, brutalist) |
5. Analyser les données :
- Tableau simple
[12, 18, 22]→ series.data, les étiquettes fournies séparément - Tableau d'objets
[{x: 'Jan', y: 12}]→ xAxis.data à partir des clés x, series.data à partir des valeurs y - CSV : analyser la ligne d'en-tête comme xAxis.data, la ligne de valeurs comme series.data
- Multi-séries : entrées
seriesmultiples chacune avectype,name,data - Nuage de points : format
series.data: [[x1,y1], [x2,y2], ...]
6. Analyser les dimensions : "1080x1080" → L=1080, H=1080. Corps = LxH. PNG de sortie = 2L × 2H.
Étape 3 : Génération HTML
Tout lire AVANT de générer :
references/chart-library.mdpour la spécification complète de configuration ECharts de ce type de graphiquereferences/style-presets.mdpour les tokens CSS + la palette du style choisi
Structure HTML requise :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
[lien CDN de la police depuis le préréglage de style]
<style>
:root {
[tous les tokens CSS du préréglage de style]
}
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: [L]px; height: [H]px;
overflow: hidden;
background: var(--bg);
font-family: var(--font-body);
}
body {
display: flex;
flex-direction: column;
padding: 40px 48px 32px; /* camembert/anneau : utiliser 64px 80px */
}
/* Le conteneur ECharts doit avoir une taille explicite */
.chart-container {
flex: 1;
min-height: 0;
/* camembert/anneau uniquement : max-height: 860px; */
}
.chart-header { margin-bottom: 24px; }
.chart-title {
font-family: var(--font-display);
font-size: clamp(1.1rem, 2.5vw, 1.6rem);
font-weight: 700;
color: var(--text);
line-height: 1.2;
}
.chart-subtitle {
font-family: var(--font-body);
font-size: clamp(0.75rem, 1.2vw, 0.9rem);
color: var(--text-muted);
margin-top: 6px;
line-height: 1.5;
}
.chart-footer {
margin-top: 14px;
font-family: var(--font-body);
font-size: 10px;
color: var(--text-muted);
opacity: 0.65;
}
</style>
</head>
<body>
[si titre ou sous-titre : <div class="chart-header"><div class="chart-title">...</div>...</div>]
<div id="chart" class="chart-container"></div>
[si source : <div class="chart-footer">Source : [source]</div>]
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js"></script>
<script>
window.__chartReady = false;
document.fonts.ready.then(() => {
const container = document.getElementById('chart');
const chart = echarts.init(container, null, { renderer: 'canvas' });
// CRITIQUE : enregistrer les événements AVANT setOption — bug ECharts #14101/#17500
// 'finished' peut ne pas se déclencher silencieusement s'il est enregistré après setOption avec animation:false
chart.on('finished', () => {
window.__chartReady = true;
});
// Mécanisme de secours via 'rendered'
chart.on('rendered', () => {
clearTimeout(window.__renderDebounce);
window.__renderDebounce = setTimeout(() => { window.__chartReady = true; }, 100);
});
const palette = [palette du préréglage de style];
const option = {
animation: false, // rendu instantané pour la capture d'écran
backgroundColor: 'transparent', // le CSS du corps gère la couleur de fond
textStyle: {
fontFamily: '[valeur --font-body]',
color: '[valeur --text-muted]',
},
[configuration titre si le paramètre titre est fourni],
[configuration légende selon le type de graphique],
[configuration grille selon le type de graphique],
[configuration xAxis selon le type de graphique],
[configuration yAxis selon le type de graphique],
series: [{
[configuration complète de la série depuis chart-library.md pour ce type]
[couleurs de la palette appliquées selon le type de graphique]
[si surbrillance : itemStyle par élément sur le point de données en surbrillance]
}]
};
chart.setOption(option); // setOption VIENT TOUJOURS après l'enregistrement des événements
});
</script>
</body>
</html>
Règles de qualité de conception :
- Police du titre :
fontWeight: 'bold',fontSize: 20–24— pas de titres fins - Lignes de grille : faible opacité (0.06–0.10) — subordonnées aux données
- Barres :
barMaxWidth: 60, arrondies viaitemStyle.borderRadius: [4,4,0,0] - Lignes :
smooth: truepour des courbes naturelles,symbolSize: 8pour les points - Camembert/Anneau :
label.formatter: '{b}\n{d}%'pour les étiquettes intégrées sur les parts - Préréglages sombres : grille
rgba(255,255,255,0.07), couleur des lignes/repères d'axergba(255,255,255,0.15) - Infobulle :
show: false— PNG statique, pas d'interaction au survol - Lorsqu'aucun titre n'est fourni : ignorer
.chart-header, omettre le titre de l'option ECharts
Étape 4 : Auto-QA (corriger chaque échec avant l'Étape 5)
Structure :
- Le conteneur est un
<div>, pas un<canvas> -
window.__chartReady = falsedéclaré avantdocument.fonts.ready -
chart.on('finished', ...)enregistré AVANTchart.setOption() - Mécanisme de secours
chart.on('rendered', ...)enregistré AVANTchart.setOption() -
animation: falsedans l'objet option -
chart.setOption(option)est le DERNIER appel dans le bloc d'initialisation
Spécifique au type :
- Aire :
type: 'line'+areaStyle: {}— pas detype: 'area' - Anneau :
type: 'pie'+radius: ['40%', '70%']— pas detype: 'doughnut' - Barres :
xAxis.type: 'category'défini explicitement - Données de catégorie dans
xAxis.data(pasdata.labels) - Camembert/Anneau : body
padding: 64px 80px+.chart-container { max-height: 860px } - Surbrillance :
{ value: N, itemStyle: { color } }par élément dans le tableau data
Conception :
- Toutes les couleurs de la palette proviennent de
references/style-presets.md - Le titre énonce l'information clé (pas seulement le sujet)
-
tooltip: { show: false }ou omis (pas de survol sur PNG statique) - Préréglage sombre : les couleurs de la grille/des axes utilisent
rgba(255,255,255,...) - Source dans le pied de page si le paramètre
sourceest fourni - Étiquettes de données visibles (intégré
label: { show: true }sur la série)
Étape 5 : Exportation
Déterminer le slug à partir du titre ou du type de graphique + contexte des données (kebab-case, ≤30 caractères) :
mkdir -p chart/[slug]
Sauvegarder le HTML : chart/[slug]/chart.html
Vérification rapide dans le navigateur :
open chart/[slug]/chart.html
Lancer l'exportation (remplacer [skill-root] par le chemin réel vers le répertoire de cette compétence) :
bash [skill-root]/scripts/export-chart.sh \
chart/[slug]/chart.html \
chart/[slug]/chart.png \
--width [L] \
--height [H]
Le script installe Playwright lors de la première exécution (~200 Mo de téléchargement Chromium), puis capture le graphique à deviceScaleFactor: 2.
Étape 6 : Résumé de sortie
## Graphique : [titre]
Date : [AAAA-MM-JJ] | Type : [chart_type] | Style : [style]
Dimensions : [L×H]px → PNG : [2L×2H]px @2× retina
Fichiers
Source : chart/[slug]/chart.html
Sortie : chart/[slug]/chart.png
Taille : [X] Ko
Liste de vérification
- [ ] Le titre énonce clairement l'information clé
- [ ] Les étiquettes de données sont lisibles à la taille d'affichage
- [ ] La surbrillance est visible sur le point de données correct
- [ ] L'attribution de la source est présente dans le pied de page
Conseils (à montrer lorsque l'utilisateur demande des conseils)
"Fournissez des données structurées — JSON ou CSV, pas de descriptions en prose."
"Nommez le type de graphique explicitement. 'diagramme en barres comparant T1–T4' pas 'un graphique montrant les trimestres'."
"Spécifiez l'histoire des données. 'mettre en surbrillance T4 qui a surpassé tous les autres' donne le contexte d'annotation."
✅ Bon : "Créez un graphique en lignes. Titre : 'De 12k$ à 95k$ de ARR en 12 mois'. Données : [12, 18, 22, 25, 31, 38, 44, 52, 61, 68, 78, 95] (Jan–Déc 2024). Mettre en surbrillance Décembre. Source : CRM interne. Style : electric-burst."
❌ Mauvais : "faites un graphique sur la croissance de notre entreprise"