graphic-chart
根据数据生成 PNG 格式的数据可视化图表。使用 Apache ECharts v6,通过 Playwright 在无头 Chromium 中渲染 HTML 并以 2× Retina 质量截图。
CDN:https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js
关键规则(每次生成前必读)
area类型 →type: 'line'+areaStyle: {}— ECharts 没有type: 'area'。doughnut→type: 'pie'+radius: ['40%', '70%']— ECharts 没有type: 'doughnut'。- 就绪信号:在
chart.setOption()之前注册chart.on('finished', fn)— ECharts bug #14101/#17500:如果在 setOption 之后注册监听器,它将静默地永不触发。务必在 setOption 之前同时注册finished和rendered事件。 - 必须显式声明
xAxis.type: 'category'— ECharts 不会从数据中推断。忘记此项会产生空白图表。 - 分类标签放在
xAxis.data中,而不是放在data.labels数组中。ECharts 结构是扁平的:{ xAxis, yAxis, series, grid, title, legend }— 不要嵌套在data或options下。 - 数据标签完全内置 — 在任何系列上使用
label: { show: true }即可。无需插件。 - 通过逐项
itemStyle高亮特定柱/点 — 将{ value: N, itemStyle: { color: '#...' } }直接放在data数组中。不要使用 Chart.js 风格的backgroundColor数组。 - ECharts 初始化使用
<div>容器,而不是<canvas>—echarts.init(document.getElementById('chart'))。容器 div 需要显式尺寸。 - 在选项中设置
animation: false— 禁用动画以实现即时渲染。仍需在 setOption 之前注册finished和rendered事件以获得就绪信号。 - 切勿在聊天中转储 HTML。 保存到文件,仅显示摘要。
- 标题陈述洞察,而非主题。 "收入在 12 个月内增长了 3 倍",而不是"月收入"。
- 饼图/环形图:使用 body
padding: 64px 80px和.chart-container { max-height: 860px }— 防止无标题时图表充满边缘。
步骤 1:接收
必需:chart_type、data
可选参数和默认值:
| 参数 | 默认值 | 描述 |
|---|---|---|
| chart_type | — | bar / line / area / pie / doughnut / scatter / radar / treemap |
| data | — | JSON 数组或 CSV — 必需 |
| title | — | 陈述洞察,不超过 10 个字 |
| subtitle | — | 1 句上下文说明 |
| style | clean-slate | clean-slate / midnight-editorial / matt-gray / electric-burst / brutalist |
| dimensions | 1080x1080 | 宽 x 高 像素(输出 PNG 通过 deviceScaleFactor 为 2×) |
| x_label | — | X 轴标签文本 |
| y_label | — | Y 轴标签文本 |
| source | — | 显示在页脚的数据来源 |
| highlight | — | 要高亮的数据标签(例如 "Q4"、"Dec"、索引 3) |
如果缺少 chart_type 或 data,请确切询问:
"要创建图表,我需要:
- 图表类型 — bar / line / area / pie / doughnut / scatter / radar / treemap
- 数据 — 以 JSON 数组或 CSV 格式提供(例如
[12, 18, 22, 25, 31]以及标签['Q1','Q2','Q3','Q4','Q5'])可选:标题、样式(默认:clean-slate)、尺寸(默认:1080×1080)、高亮特定数据点"
如果两者都存在 → 跳到步骤 2。
步骤 2:内部架构(永不向用户展示)
1. 规范化图表类型:
area→line+ 系列上的areaStyle: {}doughnut→pie+ 系列上的radius: ['40%', '70%']horizontal bar→bar+ 交换 xAxis/yAxis(分类轴在 y 上)- 所有其他类型:按原样使用
2. 读取 references/chart-library.md — 加载此图表类型的完整配置规范。
3. 读取 references/style-presets.md — 为所选样式加载 CSS 代币和数据调色板。
4. 确定设计方向:
| 决策 | 来源于 |
|---|---|
| 基调 | 专业 / 编辑 / 大胆 / 技术 — 与数据的受众匹配 |
| 数据故事 | 本图表证明的单一洞察(将成为标题) |
| 高亮策略 | 哪个数据点需要视觉强调以及为什么? |
| 背景 | 浅色(clean-slate、matt-gray)或深色(midnight-editorial、electric-burst、brutalist) |
5. 解析数据:
- 简单数组
[12, 18, 22]→ series.data,标签单独提供 - 对象数组
[{x: 'Jan', y: 12}]→ 从 x 键提取 xAxis.data,从 y 值提取 series.data - CSV:将标题行解析为 xAxis.data,将值行解析为 series.data
- 多系列:多个
series条目,每个包含type、name、data - 散点图:
series.data: [[x1,y1], [x2,y2], ...]格式
6. 解析尺寸: "1080x1080" → W=1080, H=1080。Body = WxH。输出 PNG = 2W × 2H。
步骤 3:HTML 生成
在生成前读取所有内容:
references/chart-library.md获取此图表类型的完整 ECharts 配置规范references/style-presets.md获取所选样式的 CSS 代币和调色板
必需的 HTML 结构:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
[样式预设中的字体 CDN 链接]
<style>
:root {
[样式预设中的所有 CSS 代币]
}
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: [W]px; height: [H]px;
overflow: hidden;
background: var(--bg);
font-family: var(--font-body);
}
body {
display: flex;
flex-direction: column;
padding: 40px 48px 32px; /* 饼图/环形图:使用 64px 80px */
}
/* ECharts 容器必须有显式尺寸 */
.chart-container {
flex: 1;
min-height: 0;
/* 仅饼图/环形图: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>
[如果存在标题或副标题:<div class="chart-header"><div class="chart-title">...</div>...</div>]
<div id="chart" class="chart-container"></div>
[如果存在来源:<div class="chart-footer">来源:[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' });
// 关键:在 setOption 之前注册事件 — ECharts bug #14101/#17500
// 如果在 animation:false 的 setOption 之后注册,'finished' 可能静默不触发
chart.on('finished', () => {
window.__chartReady = true;
});
// 通过 'rendered' 事件提供双重保障退路
chart.on('rendered', () => {
clearTimeout(window.__renderDebounce);
window.__renderDebounce = setTimeout(() => { window.__chartReady = true; }, 100);
});
const palette = [样式预设中的调色板];
const option = {
animation: false, // 即时渲染以进行截图
backgroundColor: 'transparent', // body CSS 处理背景颜色
textStyle: {
fontFamily: '[--font-body 值]',
color: '[--text-muted 值]',
},
[如果提供了标题参数,则为标题配置],
[根据图表类型的图例配置],
[根据图表类型的网格配置],
[根据图表类型的 xAxis 配置],
[根据图表类型的 yAxis 配置],
series: [{
[来自 chart-library.md 的此类型完整系列配置]
[根据图表类型应用调色板颜色]
[如果存在高亮:在高亮数据点上的逐项 itemStyle]
}]
};
chart.setOption(option); // setOption 总是位于事件注册之后
});
</script>
</body>
</html>
设计质量标准:
- 标题字体:
fontWeight: 'bold'、fontSize: 20–24— 不要细体标题 - 网格线:低不透明度(0.06–0.10)— 从属于数据
- 柱子:
barMaxWidth: 60,通过itemStyle.borderRadius: [4,4,0,0]实现圆角 - 线条:
smooth: true用于自然曲线,symbolSize: 8用于数据点 - 饼图/环形图:
label.formatter: '{b}\n{d}%'用于内置切块标签 - 暗色预设:网格
rgba(255,255,255,0.07),轴线/刻度颜色rgba(255,255,255,0.15) - 提示框(Tooltip):
show: false— 静态 PNG,无悬停交互 - 当未提供标题时:跳过
.chart-header,从 ECharts 选项中省略标题
步骤 4:自检(在步骤 5 之前修复所有失败)
结构:
- 容器是
<div>,不是<canvas> -
window.__chartReady = false在document.fonts.ready之前声明 -
chart.on('finished', ...)在chart.setOption()之前注册 -
chart.on('rendered', ...)防抖退路在chart.setOption()之前注册 - 选项对象中有
animation: false -
chart.setOption(option)是初始化块中的最后一个调用
类型特定:
- 面积图:
type: 'line'+areaStyle: {}— 不是type: 'area' - 环形图:
type: 'pie'+radius: ['40%', '70%']— 不是type: 'doughnut' - 柱状图:显式设置
xAxis.type: 'category' - 分类数据在
xAxis.data中(不是data.labels) - 饼图/环形图:body
padding: 64px 80px+.chart-container { max-height: 860px } - 高亮:数据数组中的逐项
{ value: N, itemStyle: { color } }
设计:
- 所有调色板颜色来自
references/style-presets.md - 标题陈述洞察(不只是主题)
-
tooltip: { show: false }或省略(静态 PNG 上无悬停) - 暗色预设:网格/轴颜色使用
rgba(255,255,255,...) - 如果提供了
source参数,在页脚显示来源 - 数据标签可见(系列上的内置
label: { show: true })
步骤 5:导出
根据标题或图表类型+数据上下文确定 slug(kebab-case,不超过 30 个字符):
mkdir -p chart/[slug]
保存 HTML:chart/[slug]/chart.html
快速浏览器检查:
open chart/[slug]/chart.html
运行导出(将 [skill-root] 替换为此技能目录的实际路径):
bash [skill-root]/scripts/export-chart.sh \
chart/[slug]/chart.html \
chart/[slug]/chart.png \
--width [W] \
--height [H]
该脚本在首次运行时安装 Playwright(约 200MB Chromium 下载),然后以 deviceScaleFactor: 2 捕获图表。
步骤 6:输出摘要
## 图表:[title]
日期:[YYYY-MM-DD] | 类型:[chart_type] | 样式:[style]
尺寸:[W×H]px → PNG:[2W×2H]px @2× Retina
文件
源文件: chart/[slug]/chart.html
输出: chart/[slug]/chart.png
大小: [X] KB
检查清单
- [ ] 标题清晰陈述洞察
- [ ] 数据标签在显示尺寸下清晰可读
- [ ] 正确数据点上的高亮可见
- [ ] 页脚中存在来源归属
提示技巧(当用户请求指导时显示)
"提供结构化数据 — JSON 或 CSV,而不是文字描述。"
"明确说明图表类型。'比较 Q1–Q4 的柱状图',而不是'显示季度的图表'。"
"指定数据故事。'高亮 Q4,它优于所有其他季度'提供了标注上下文。"
✅ 好的:"创建一个折线图。标题:'从 $12k 到 $95k ARR 在 12 个月内'。数据:[12, 18, 22, 25, 31, 38, 44, 52, 61, 68, 78, 95](2024 年 1 月至 12 月)。高亮 12 月。来源:内部 CRM。样式:electric-burst。"
❌ 不好的:"制作一个关于我们公司增长的图表"