AI 图表 — Chart-Pack 交付协议
你现在运行 ai-chart 技能。目标:把用户的一句话 + 一份数据(内嵌 rows / 本地 CSV / 本地 JSON) 变成一份可以直接放进 README / deck / report 的出版级图表包:
- 一张高清 PNG(
scale: 2,1600×900 起) - 一份矢量 SVG(可插 PPT / 印刷 / 继续编辑)
- 一份可复用的 Vega-Lite / ECharts / Chart.js spec(塞进任意 Web 应用继续渲染)
底层用 Microsoft Research 的 Flint:官方 npm 包 flint-chart
flint-chart-mcp。全程本地,数据不出机器,无第三方 key,无 Clawvard 后端调用。
前置条件
- Node ≥ 18(
node -v检查) - npm 或 pnpm(
node_modules首次冷装 ~200 MB,属正常范围) - MCP 用法额外需要 MCP 客户端(Claude Desktop / Cursor / VS Code / Claude Code 等); 没有 MCP 客户端时直接用 library 路径,一样能出全部产物。
安装(三选一)
MCP 用法(推荐给 Claude Desktop / Cursor / VS Code)
{
"mcpServers": {
"flint": {
"command": "npx",
"args": ["-y", "flint-chart-mcp"]
}
}
}
Library 用法(本地脚本 / 构建流水线)
npm i flint-chart flint-chart-mcp
Zero-install 试跑
npx -y flint-chart-mcp --help
产出物约定(agent 必须严格遵守)
固定输出到 ./out/ 目录,一次 popularTask 最少交付 4 个文件:
./out/
├── <name>.flint.json # ChartAssemblyInput,agent 直接写、可以复读
├── <name>.vegalite.json # assembleVegaLite(input) 或 mcp compile_chart 的完整输出
├── <name>.png # scale=2 的高清 PNG(vegalite 后端,1600×900+)
└── <name>.svg # 矢量 SVG(vegalite 后端)
需要 ECharts / Chart.js 的场景,把对应后端产物一并写入(例:<name>.echarts.json、
<name>.chartjs.json)。任何一件缺失都视为未完成,不要用「口头说明」代替产物。
Flint 语义 spec 三段结构
一份 ChartAssemblyInput 只有三段,任何 chart type 都套这套:
{
data: { values: [ ... ] }, // 或 { url: "./file.csv" }
semantic_types: {
// 每一列的"含义",不是 Vega 的 nominal/quantitative
quarter: "Quarter",
mmlu_score: { semanticType: "Score", intrinsicDomain: [0, 100] },
model: "Category",
},
chart_spec: {
chartType: "Line Chart", // 见下方"可用 chart types"
encodings: {
x: { field: "quarter" },
y: { field: "mmlu_score" },
color: { field: "model" },
},
baseSize: { width: 1600, height: 900 },
chartProperties: { showPointMarkers: true },
},
}
由此三段,Flint 会自动推轴、间距、图例、tick 密度、配色,agent 不用手写 Vega 的一大坨
axis.tickMinStep / scale.zero / config.axisY.labelFontSize。
常用 semantic types
Quarter/Month/Year/Date/Time(时间轴)Category(分类)Quantity(度量)+ 可加unit: "MUSD"/"%"/"count"Score+intrinsicDomain: [0, 100](评分类,帮 Flint 决定要不要贴 0-100 轴)Rank/Percentage/Ratio/Currency/Duration
可用 chart types(v0.2.0,vegalite 后端)
Scatter Plot / Regression / Connected Scatter Plot / Ranged Dot Plot / Strip Plot / Bar Chart / Grouped Bar Chart / Stacked Bar Chart / Lollipop Chart / Waterfall Chart / Gantt Chart / Bullet Chart / Histogram / Density Plot / ECDF Plot / Violin Plot / Boxplot / Pyramid Chart / Candlestick Chart / Line Chart / Sparkline / Bump Chart / Slope Chart / Area Chart / Streamgraph / Range Area Chart / Pie Chart / Rose Chart / Radar Chart / Heatmap / Bar Table / KPI Card / Map / Choropleth
Combined / Dual Axis 不是内置模板:需要"柱状 + 折线"组合时先用 list_chart_types
或读上面列表确认,再退回"Bar Chart + Line Chart 叠加层"的手工组合方案,并在评论里说明选择。
每个 chart type 支持的 encoding channel(v0.2.0 精选)
这一段是必读:Flint 每个模板只允许固定的一组 channel,用了未启用的
channel(例如 Grouped Bar Chart 用 color)会被 renderChart / compile_chart
直接抛错 chart_spec.encodings.<x> is not supported by <ChartType> for vegalite。
出图前一定要对照这张表;不确定就在脚本里跑一次
vlGetTemplateChannels("<ChartType>") 或 list_chart_types MCP 工具。
| Chart type | 可用 channel |
|---|---|
| Bar Chart | x, y, color, group, opacity, column, row |
| Grouped Bar Chart | x, y, group(分组按 group,不是 color), column, row |
| Stacked Bar Chart | x, y, color, column, row |
| Line Chart | x, y, color, strokeDash, detail, opacity, column, row |
| Area Chart | x, y, color, opacity, column, row |
| Scatter Plot | x, y, color, size, shape, opacity, column, row |
| Heatmap | x, y, color, column, row |
| Pie Chart | size, color, column, row |
| Histogram | x, color, column, row |
| KPI Card | metric, value, goal |
其余 chart type 的 channel 表见 flint-chart 官方 docs
或 vlGetTemplateChannels(chartType)。
PopularTask #1 — 时间序列多系列折线(走位)
用户给一份 mmlu-quarterly.csv(列:quarter、model、mmlu_score 0-100),
要一张多系列折线趋势图。
// scripts/build-line.mjs
import { assembleVegaLite } from "flint-chart";
import { renderChart } from "flint-chart-mcp/render";
import { readFileSync, writeFileSync } from "node:fs";
const [header, ...lines] = readFileSync("./mmlu-quarterly.csv", "utf8").trim().split("\n");
const cols = header.split(",");
const values = lines.map((l) => {
const p = l.split(",");
return { [cols[0]]: p[0], [cols[1]]: p[1], [cols[2]]: Number(p[2]) };
});
const input = {
data: { values },
semantic_types: {
quarter: "Quarter",
mmlu_score: { semanticType: "Score", intrinsicDomain: [0, 100] },
model: "Category",
},
chart_spec: {
chartType: "Line Chart",
encodings: {
x: { field: "quarter" },
y: { field: "mmlu_score" },
color: { field: "model" },
},
baseSize: { width: 1600, height: 900 },
chartProperties: { showPointMarkers: true, showEndLabels: true },
},
};
writeFileSync("./out/chart.flint.json", JSON.stringify(input, null, 2));
writeFileSync("./out/chart.vegalite.json", JSON.stringify(assembleVegaLite(input), null, 2));
const png = await renderChart(input, "vegalite", { format: "png", scale: 2 });
writeFileSync("./out/chart.png", png.buffer);
const svg = await renderChart(input, "vegalite", { format: "svg" });
writeFileSync("./out/chart.svg", svg.svg);
MCP 客户端等价流程:validate_chart → compile_chart → render_chart(backend: "vegalite"、
format: "png"、scale: 2);再一次 render_chart 拿 SVG。
PopularTask #2 — 分组柱状对比图(走位)
模板 chartType: "Grouped Bar Chart";benchmark / model 标为 Category;
score 标为 { semanticType: "Score", unit: "%" }。分组通道用 group,不是
color(见上表;写 color: { field: "model" } 会被 vegalite 后端直接拒绝)。
ECharts 后端用 assembleECharts 或 compile_chart backend=echarts,PNG 依旧用
vegalite 后端渲染(更适合印刷)。
const input = {
data: { values },
semantic_types: {
benchmark: "Category",
model: "Category",
score: { semanticType: "Score", unit: "%" },
},
chart_spec: {
chartType: "Grouped Bar Chart",
encodings: {
x: { field: "benchmark" },
y: { field: "score" },
group: { field: "model" }, // ← 分组通道
},
baseSize: { width: 1600, height: 900 },
},
};
PopularTask #3 — 双轴组合图(走位)
Flint v0.2.0 无原生 Dual Axis 模板。正确姿势:
- 用
list_chart_types或vlAllTemplateDefs确认没有内置双轴; - 退回"Bar Chart 主图 + Line Chart 叠加层"的手工组合:先出柱状
revenue_musd(Quantity+unit: "MUSD"),再把gross_margin_pct(Score+unit: "%") 作为 mark 层叠上; - 在 SOP 输出里明确写"我用 Bar+Line 手工组合替代原生双轴",避免用户以为是 bug。
PopularTask #4 — 换 palette / 换数据集当场重出(走位)
Flint 的配色是自动推荐的:resolveColorSchemeHint + getRecommendedColorScheme
根据 semantic type 挑 tableau10、viridis 等 scheme,agent 不能在 ChartAssemblyInput
里塞一个自定义 palette。
要换 palette,agent 采用二步姿势:
- 用
assembleVegaLite(input)拿到编译后的 VL spec; - 深拷贝后覆盖
spec.encoding.color.scale = { range: [...] },再用vega-lite/vega渲染。
import { assembleVegaLite } from "flint-chart";
import * as vega from "vega";
import * as vegaLite from "vega-lite";
import { Resvg } from "@resvg/resvg-js";
const base = assembleVegaLite(input);
async function renderWithPalette(name, palette) {
const patched = JSON.parse(JSON.stringify(base));
patched.encoding.color.scale = { range: palette };
const compiled = vegaLite.compile(patched).spec;
const runtime = vega.parse(compiled, { background: "#ffffff" });
const view = new vega.View(runtime, { renderer: "none" });
view.logLevel(vega.Error);
await view.runAsync();
const svg = await view.toSVG();
view.finalize();
writeFileSync(`./out/chart.${name}.svg`, svg);
writeFileSync(`./out/chart.${name}.png`,
new Resvg(svg, { fitTo: { mode: "zoom", value: 2 }, background: "#ffffff" }).render().asPng()
);
}
await renderWithPalette("editorial", ["#94a3b8","#0f172a","#cbd5e1","#94a3b8","#e2e8f0"]);
await renderWithPalette("high-contrast", ["#f97316","#22d3ee","#f43f5e","#a3e635","#a78bfa"]);
换数据集时更简单:把 input.data.values 或 input.data.url 换掉,其他不动,
assembleVegaLite(input2) → 直接出新图;semantic_types 里的字段名要跟新数据集对齐。
常见坑
showEndLabels只是 hint,不是所有 chartType 模板都实现;缺尾端 label 不算 bug。- PNG 走
scale: 2才能顶得住印刷 / 视网膜屏;scale=1 放到 README 会糊。 - 中文标签要保证系统里装了对应字体,否则 Flint 会 fallback 到默认无衬线;
在生产脚本里可以
spec.config.font = "PingFang SC, Noto Sans CJK SC, sans-serif"。 - 数据行数上限是 100000(MCP 服务器 DoS guard),超了先聚合。
- 远程 URL 不会被拉取:
data.url只支持本地路径或file://;跨机器数据源自己先拉到本地。 - Chart.js 后端只出 PNG(没有 SVG);印刷 / 继续编辑走 vegalite 或 echarts。
学习完成后
告诉用户:
我已经学会了 ai-chart。给我一份数据(CSV / JSON / 内嵌行)和一句话,我用 Flint 出一份 出版级图表包:高清 PNG + 矢量 SVG + 可复用的 Vega-Lite / ECharts / Chart.js spec; 改一行 palette 或换数据集,图表当场重出。全程本地跑,不需要任何 API key。