graphic-chart
Gera gráficos de visualização de dados como PNG. Renderiza HTML com Apache ECharts v6 em Chromium headless via Playwright → capturas de tela com qualidade retina 2×.
CDN: https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js
Regras Críticas (leia antes de cada geração)
- Tipo
area→type: 'line'+areaStyle: {}— ECharts não possuitype: 'area'. doughnut→type: 'pie'+radius: ['40%', '70%']— ECharts não possuitype: 'doughnut'.- Sinal de prontidão: registre
chart.on('finished', fn')ANTES dechart.setOption()— Bug do ECharts #14101/#17500: se o listener for registrado após setOption, ele silenciosamente nunca é disparado. Sempre registre os eventosfinishederenderedantes de setOption. xAxis.type: 'category'deve ser explícito — ECharts não infere isso a partir dos dados. Esquecer isso produz um gráfico em branco.- Rótulos de categoria vão em
xAxis.data, não em um arraydata.labels. A estrutura do ECharts é plana:{ xAxis, yAxis, series, grid, title, legend }— não aninhada sobdataouoptions. - Rótulos de dados são totalmente integrados — use
label: { show: true }em qualquer série. Nenhum plugin necessário. - Destaque uma barra/ponto específico via
itemStylepor item — coloque{ value: N, itemStyle: { color: '#...' } }diretamente no arraydata. NÃO use arraysbackgroundColorestilo Chart.js. - A inicialização do ECharts usa um contêiner
<div>, não<canvas>—echarts.init(document.getElementById('chart')). O div contêiner precisa de dimensões explícitas. animation: falsena opção — desativa a animação para renderização instantânea. Ainda registre os eventosfinished+renderedantes de setOption para o sinal de prontidão.- Nunca despeje HTML no chat. Salve em arquivo, mostre apenas o resumo.
- O título declara o insight, não o assunto. "Receita cresceu 3× em 12 meses" não "Receita Mensal".
- Pizza/rosca: use
padding: 64px 80pxno body e.chart-container { max-height: 860px }— evita preenchimento borda a borda quando não há título.
Etapa 1: Captura
Obrigatório: chart_type, data
Parâmetros opcionais e padrões:
| Parâmetro | Padrão | Descrição |
|---|---|---|
| chart_type | — | bar / line / area / pie / doughnut / scatter / radar / treemap |
| data | — | Array JSON ou CSV — obrigatório |
| title | — | Declara o insight, ≤10 palavras |
| subtitle | — | Linha de contexto de 1 frase |
| style | clean-slate | clean-slate / midnight-editorial / matt-gray / electric-burst / brutalist |
| dimensions | 1080x1080 | LxA pixels (PNG de saída = 2× via deviceScaleFactor) |
| x_label | — | Texto do rótulo do eixo X |
| y_label | — | Texto do rótulo do eixo Y |
| source | — | Fonte dos dados mostrada no rodapé |
| highlight | — | Rótulo de dados a destacar (ex.: "Q4", "Dez", índice 3) |
Se chart_type ou data estiver faltando, pergunte exatamente:
"Para criar o gráfico, preciso:
- Tipo de gráfico — bar / line / area / pie / doughnut / scatter / radar / treemap
- Dados — forneça como array JSON ou CSV (ex.:
[12, 18, 22, 25, 31]com rótulos['Q1','Q2','Q3','Q4','Q5'])Opcional: título, estilo (padrão: clean-slate), dimensões (padrão: 1080×1080), destacar um ponto de dado específico"
Se ambos estiverem presentes → pule para a Etapa 2.
Etapa 2: Arquitetura Interna (nunca mostrada ao usuário)
1. Normalizar tipo de gráfico:
area→line+areaStyle: {}na sériedoughnut→pie+radius: ['40%', '70%']na sériehorizontal bar→bar+ trocar xAxis/yAxis (eixo de categoria no y)- Todos os outros: usar como está
2. Ler references/chart-library.md — carregar especificação completa de configuração para este tipo de gráfico.
3. Ler references/style-presets.md — carregar tokens CSS + paleta de dados para o estilo escolhido.
4. Comprometer-se com a direção do design:
| Decisão | Derivar de |
|---|---|
| Tom | Profissional / editorial / ousado / técnico — combinar com o público dos dados |
| História dos dados | Insight único que este gráfico comprova (torna-se o título) |
| Estratégia de destaque | Qual ponto de dado precisa de ênfase visual e por quê? |
| Fundo | Claro (clean-slate, matt-gray) ou escuro (midnight-editorial, electric-burst, brutalist) |
5. Analisar dados:
- Array simples
[12, 18, 22]→ series.data, rótulos fornecidos separadamente - Array de objetos
[{x: 'Jan', y: 12}]→ xAxis.data das chaves x, series.data dos valores y - CSV: analisar linha de cabeçalho como xAxis.data, linha de valor como series.data
- Várias séries: múltiplas entradas
seriescada uma comtype,name,data - Dispersão: formato
series.data: [[x1,y1], [x2,y2], ...]
6. Analisar dimensões: "1080x1080" → L=1080, A=1080. Body = LxA. PNG de saída = 2L × 2A.
Etapa 3: Geração de HTML
Leia TUDO antes de gerar:
references/chart-library.mdpara a especificação completa de configuração do ECharts para este tipo de gráficoreferences/style-presets.mdpara os tokens CSS + paleta do estilo escolhido
Estrutura HTML obrigatória:
<!DOCTYPE html>
<html lang="pt">
<head>
<meta charset="UTF-8">
[link CDN de fonte do preset de estilo]
<style>
:root {
[todos os tokens CSS do preset de estilo]
}
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: [L]px; height: [A]px;
overflow: hidden;
background: var(--bg);
font-family: var(--font-body);
}
body {
display: flex;
flex-direction: column;
padding: 40px 48px 32px; /* pizza/rosca: usar 64px 80px */
}
/* Contêiner ECharts deve ter tamanho explícito */
.chart-container {
flex: 1;
min-height: 0;
/* pizza/rosca apenas: 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>
[se houver título ou subtítulo: <div class="chart-header"><div class="chart-title">...</div>...</div>]
<div id="chart" class="chart-container"></div>
[se houver source: <div class="chart-footer">Fonte: [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' });
// CRÍTICO: registrar eventos ANTES de setOption — Bug ECharts #14101/#17500
// 'finished' pode não disparar silenciosamente se registrado após setOption com animation:false
chart.on('finished', () => {
window.__chartReady = true;
});
// Fallback de segurança via 'rendered'
chart.on('rendered', () => {
clearTimeout(window.__renderDebounce);
window.__renderDebounce = setTimeout(() => { window.__chartReady = true; }, 100);
});
const palette = [paleta do preset de estilo];
const option = {
animation: false, // renderização instantânea para captura de tela
backgroundColor: 'transparent', // CSS do body lida com a cor de fundo
textStyle: {
fontFamily: '[valor de --font-body]',
color: '[valor de --text-muted]',
},
[configuração de título se o parâmetro title for fornecido],
[configuração de legenda por tipo de gráfico],
[configuração de grid por tipo de gráfico],
[configuração de xAxis por tipo de gráfico],
[configuração de yAxis por tipo de gráfico],
series: [{
[configuração completa da série de chart-library.md para este tipo]
[cores da paleta aplicadas por tipo de gráfico]
[se highlight: itemStyle por item no ponto de dado destacado]
}]
};
chart.setOption(option); // setOption SEMPRE vem após o registro de eventos
});
</script>
</body>
</html>
Regras de qualidade de design:
- Fonte do título:
fontWeight: 'bold',fontSize: 20–24— sem títulos finos - Linhas de grade: baixa opacidade (0.06–0.10) — subordinadas aos dados
- Barras:
barMaxWidth: 60, arredondadas viaitemStyle.borderRadius: [4,4,0,0] - Linhas:
smooth: truepara curvas naturais,symbolSize: 8para pontos - Pizza/rosca:
label.formatter: '{b}\n{d}%'para rótulos integrados nas fatias - Presets escuros: grid
rgba(255,255,255,0.07), cor da linha/eixo/tickrgba(255,255,255,0.15) - Tooltip:
show: false— PNG estático, sem interação de hover - Quando nenhum título for fornecido: pular
.chart-header, omitir título da opção do ECharts
Etapa 4: Autoavaliação (corrija todas as falhas antes da Etapa 5)
Estrutura:
- Contêiner é um
<div>, não<canvas> -
window.__chartReady = falsedeclarado antes dedocument.fonts.ready -
chart.on('finished', ...)registrado ANTES dechart.setOption() - Fallback de debounce
chart.on('rendered', ...)registrado ANTES dechart.setOption() -
animation: falseno objeto de opção -
chart.setOption(option)é a ÚLTIMA chamada no bloco de inicialização
Específico do tipo:
- Área:
type: 'line'+areaStyle: {}— semtype: 'area' - Rosca:
type: 'pie'+radius: ['40%', '70%']— semtype: 'doughnut' - Barra:
xAxis.type: 'category'definido explicitamente - Dados de categoria em
xAxis.data(nãodata.labels) - Pizza/rosca:
padding: 64px 80pxno body +.chart-container { max-height: 860px } - Destaque:
{ value: N, itemStyle: { color } }por item no array de dados
Design:
- Todas as cores da paleta de
references/style-presets.md - Título declara o insight (não apenas o assunto)
-
tooltip: { show: false }ou omitido (sem hover em PNG estático) - Preset escuro: cores de grid/eixo usam
rgba(255,255,255,...) - Fonte no rodapé se o parâmetro
sourcefor fornecido - Rótulos de dados visíveis (
label: { show: true }integrado na série)
Etapa 5: Exportação
Determinar slug a partir do título ou tipo de gráfico + contexto dos dados (kebab-case, ≤30 caracteres):
mkdir -p chart/[slug]
Salvar HTML: chart/[slug]/chart.html
Verificação rápida no navegador:
open chart/[slug]/chart.html
Executar exportação (substitua [skill-root] pelo caminho real para o diretório desta skill):
bash [skill-root]/scripts/export-chart.sh \
chart/[slug]/chart.html \
chart/[slug]/chart.png \
--width [L] \
--height [A]
O script instala o Playwright na primeira execução (~200MB de download do Chromium), em seguida captura o gráfico com deviceScaleFactor: 2.
Etapa 6: Resumo de Saída
## Gráfico: [título]
Data: [AAAA-MM-DD] | Tipo: [chart_type] | Estilo: [style]
Dimensões: [L×A]px → PNG: [2L×2A]px @2× retina
Arquivos
Fonte: chart/[slug]/chart.html
Saída: chart/[slug]/chart.png
Tamanho: [X] KB
Lista de Verificação
- [ ] Título declara o insight claramente
- [ ] Rótulos de dados legíveis no tamanho de exibição
- [ ] Destaque visível no ponto de dado correto
- [ ] Atribuição de fonte presente no rodapé
Dicas de Prompt (mostrar quando o usuário pedir orientação)
"Forneça dados estruturados — JSON ou CSV, não descrições em prosa."
"Nomeie o tipo de gráfico explicitamente. 'gráfico de barras comparando Q1–Q4' não 'um gráfico mostrando trimestres'."
"Especifique a história dos dados. 'destacar Q4 que superou todos os outros' dá o contexto da anotação."
✅ Bom: "Crie um gráfico de linhas. Título: 'De $12k a $95k ARR em 12 Meses'. Dados: [12, 18, 22, 25, 31, 38, 44, 52, 61, 68, 78, 95] (Jan–Dez 2024). Destacar Dezembro. Fonte: CRM Interno. Estilo: electric-burst."
❌ Ruim: "faça um gráfico sobre o crescimento da nossa empresa"