AntV 做了十几年图表,为什么还要再写一个信息图引擎
- 设计面向 AI 的 DSL 语法,缩进+键值对,AI 按模式填空即可生成
- 流式渲染支持 AI 逐 token 输出时实时渲染,解析器容错不崩溃
- 内置关系表达式和 200+模板,模板名支持模糊匹配,降低 AI 生成门槛
- 提供完整编辑器系统,支持 AI 生成后人工微调,实现 AI+人工协作
六年前我在一家数据公司写前端,接到一个需求:把季度运营报告做成一张信息图,要让老板一眼看完。
当时我打开 Sketch,画了三个小时。画完我就在想,这活儿要是能让 AI 干就好了。
六年后的今天,AI 确实能干了。但一个新的问题冒出来了:AI 画出来的东西,怎么让它不丑?
你给 GPT-4o 说「帮我画一个时间线」,它给你一段 HTML+CSS,渲染出来要么布局错位,要么颜色辣眼。你给 Claude 说「画一个 SWOT 分析图」,它给你一段 Mermaid 代码,渲染出来就是个方框加几行字,谈不上「信息图」。
AntV 团队大概也看到了这个 gap。2025 年 9 月,他们发布了一个叫 Infographic 的项目,到现在 11 个月,攒了 5600 多星。
说真的,我第一反应是:AntV 已经有了 G2(统计图表)、G6(图分析)、Graphin(图可视化应用)、S2(多维交叉分析表),为什么还要再写一个信息图引擎?
读完源码我才明白,这不是又一个图表库。这是一套从底层语法到渲染引擎到编辑器,全部为 AI 生成而设计的系统。
图表库和 AI 之间,有一道语法鸿沟
先聊聊为什么 G2 不够用。
G2 的语法是声明式的,没错。但它的声明式面向的是「开发者写配置」,不是「AI 写配置」。举个例子,G2 画一个柱状图,你得写几十行配置:
chart.interval().position('genre*sold').color('genre')
这段代码本身没问题。问题在于,当 AI 生成时,它需要理解 interval 是什么、position 是什么、数据格式是什么。如果 AI 的上下文窗口不够大,或者训练数据里 G2 的样本不够多,生成的配置基本就是错的。
Infographic 的做法是,从头设计一个 DSL。
这个 DSL 长这样:
infographic list-row-simple-horizontal-arrow
data
lists
- label Step 1
desc Start
- label Step 2
desc In Progress
- label Step 3
desc Complete
看到区别了吗?没有嵌套的 JSON 对象,没有花括号,没有逗号。就是缩进 + 键值对。AI 生成这种格式,几乎不需要「理解」什么,照着模式填空就行。
打开 src/syntax/parser.ts,这个缩进解析器写得很有意思。它用了一个栈帧来追踪缩进层级:
interface StackFrame {
indent: number;
node: ObjectNode | ArrayNode;
parent?: ObjectNode | ArrayNode | null;
key?: string | null;
}
每一行解析时,先算缩进层级,然后 while (stack.length > 1 && indent <= stack[stack.length - 1].indent) { stack.pop() },回到正确的父节点。这个模式跟 Python 的缩进解析、Starlark 的解析器思路是一样的。
但有一个细节值得说:它还支持点号路径。assignObjectEntry 函数里,如果 key 包含 .,它会按路径逐级创建对象。比如 a.b.c value 会生成 {a: {b: {c: value}}}。这个设计对 AI 很友好,因为 AI 有时候会「偷懒」,想在一行里表达嵌套结构。
流式渲染,是 AI 生成内容的关键技术
AI 生成内容是一个 token 一个 token 吐出来的。如果每次都要等完整输出才渲染,用户等不了。
Infographic 的流式渲染能力,是它跟传统图表库最大的区别。
用法很简单:
let buffer = '';
for (const chunk of chunks) {
buffer += chunk;
infographic.render(buffer);
}
每收到一个 chunk,就调一次 render。问题在于,AI 输出到一半的时候,语法是不完整的。比如只输出了 data 还没输出数据,或者只输出了模板名还没输出配置。这时候如果解析器报错,整个渲染就断了。
Infographic 的解法在 src/syntax/mapper.ts 里。它的 mapWithSchema 函数对每个字段都做独立验证,遇到不完整的字段不会抛异常,而是返回 undefined 并记录一条错误。渲染器看到 undefined 就跳过,不阻塞流程。
src/runtime/Infographic.tsx 的 render 方法也做了同样的容错:
private performRender() {
const parsedOptions = this.parsedOptions;
if (!isCompleteParsedInfographicOptions(parsedOptions)) {
this.emitter.emit('error', new Error('Incomplete options'));
return;
}
// ...render
}
如果选项不完整,它 emit 一个 error 事件,但不会崩溃。下一次 render 调用会继续。这意味着 AI 可以一边吐一边渲染,每次渲染都是「当前最佳状态」。
坦白讲,这个设计思路跟不少 AI 流式渲染方案很像,但 Infographic 做得更彻底——它的 DSL 解析器本身就有容错能力,不是靠外层 try-catch 兜底。
一行关系表达式,背后是完整的图解析
Infographic 的 DSL 里有一个让我眼前一亮的特性:关系表达式。
你可以直接在 data 的 relations 字段里写类似这样的语法:
A -> B[label: depends on] -> C
或者更复杂的双向箭头:
A <-> B[label: peer]
A - B[label: connects]
这段解析逻辑在 src/syntax/relations.ts 里。parseRelationLine 函数用了一段手写的状态机来解析箭头表达式。它支持:
->单向箭头<->双向箭头-无向连接A -- B长连接- 节点标签用
[label]或(label)标注 - 管道符
|分隔的双向标签
readEdge 函数里有一段处理双向箭头的代码很有意思:
// Detect split bidirectional arrow pattern: <- label ->
{
const leftHasLeft = directionToken.includes('<');
const leftHasRight = directionToken.includes('>');
if (leftHasLeft && !leftHasRight) {
// look ahead for a matching right arrow
}
}
这个模式匹配的是 A <- label -> B 这种写法。左边有一个向左的箭头,右边有一个向右的箭头,中间是标签。这种写法在 ASCII 图里很常见,但在 DSL 解析器里实现起来还挺麻烦的。
模板系统:200 个模板,每个都是精心编排
Infographic 内置了大约 200 个模板。这些模板不是简单的「名字 + 配置」映射,每一层都有设计。
看 src/templates/built-in.ts,每个模板的定义都是一个 TemplateOptions 对象,包含 design(结构 + 组件 + 数据项)和 themeConfig(主题配置)。比如 list-row-simple-horizontal-arrow 的定义:
'list-row-simple-horizontal-arrow': {
design: {
title: 'default',
structure: { type: 'list-row', gap: 0, zigzag: true },
items: [{ type: 'simple-horizontal-arrow' }],
},
},
模板名本身就是一个「三层命名」:布局类型-子类型-组件名。list-row 是布局,simple-horizontal-arrow 是数据项组件。这种命名方式让模板名本身就有了可读性。
模板注册系统在 src/templates/registry.ts 里,用了一个 Map<string, TemplateOptions>。同时有一个 resolveTemplateKey 函数,它会做模糊匹配——如果用户输入的模板名不完全匹配,它会用 findClosestTemplateKey 找最接近的。这个功能对 AI 很友好,因为 AI 经常记不住完整模板名。
编辑器:AI 生成之后,人可以微调
AI 生成的信息图,不可能一次就完美。用户需要手动调整。
Infographic 内置了一个完整的编辑器系统,在 src/editor/ 目录下,代码量不小。它包含:
- 命令系统:
src/editor/commands/下的UpdateElement、UpdateText、UpdateOptions、Batch,支持撤销/重做 - 交互系统:
src/editor/interactions/下的 ClickSelect、DragElement、DragCanvas、ZoomWheel、BrushSelect、DblClickEditText - 插件系统:
src/editor/plugins/下的 EditBar、ResizeElement、ResetViewBox、CoreSync - 状态管理:
src/editor/managers/下的 CommandManager、InteractionManager、PluginManager、StateManager、SyncRegistry
这个编辑器不是摆设。EditBar 插件提供了完整的编辑工具栏,支持字体、颜色、对齐等操作。CoreSync 插件负责同步编辑状态和渲染状态。
AI 生成 + 人工微调,是目前最务实的工作流。纯 AI 生成的信息图,在可预见的未来里,都还需要人来把关。
诚实的边界
项目还在 0.x 版本。这本身就是一个重要的「限」。我翻了 issue 区,有几个问题值得关注。
第一,很多模板的 value 字段支持不完整。
Issue #211 里有人反馈:Skill.md 的示例里写了 value 字段,但很多模板渲染时根本不显示这个字段。我看了 src/syntax/schema.ts 里的 itemDatumSchema,value 确实是 union(number(), string()),属于 schema 支持但具体模板实现没跟上的情况。对于想用 value 显示数值的用户来说,这个坑不小。
第二,图表类模板的维度支持有限。
Issue #140 说,折线图等图表只支持一维数据。对比 ECharts 的 dataset 多维度支持,差距明显。在 src/designs/structures/chart-line.tsx 里,确实只处理了单条线的情况。
第三,缺少交互能力。
Issue #143 问怎么开启 hover 交互效果。目前的信息图是静态的,没有 tooltip 或 hover 高亮。对于习惯了 ECharts 交互的用户来说,这算是一个功能缺失。
第四,项目不到一年,版本迭代快。
从 2025 年 9 月到现在,发布了 20 个版本(0.2.14 到 0.2.19),几乎每两周一个版本。活跃度是好事,但也意味着 API 还在快速变化。如果你现在写一套基于 Infographic 的系统,半年后可能需要大改。
第五,包体积不算小。
package.json 里 size-limit 配置的目标是 500KB(gzip)。对比 G2 的 ~300KB,D3 的 ~250KB,这个体积在中大型项目中可以接受,但在移动端或首屏加载场景下,需要考虑按需加载。
隐式与显式之间的设计平衡
读源码的过程中,我发现了一个微妙的设计决策。
在 src/syntax/index.ts 的 parseSyntax 函数里,有一个 inferTemplateFromBareFirstLine 函数。它的作用是:如果用户没有显式写 infographic 或 template 关键字,解析器会尝试把第一行当作模板名来推断。
比如用户写了:
list-row-simple-horizontal-arrow
data
lists
- label Step 1
解析器会识别出 list-row-simple-horizontal-arrow 不是已知的根级关键字,把它当作模板名处理,并 emit 一条 warning:
Inferred template from a bare first line. Prefix it with "infographic" or "template" to make the syntax explicit.
这个设计很聪明。对 AI 来说,少写一个关键字就少一分出错的可能。但对人类开发者来说,隐式推断可能让人困惑——为什么我写了一个不存在的关键字,它不报错反而正常运行?
这就是「隐式与显式」的经典权衡。Infographic 选择了 AI 优先,用 warning 而非 error 来处理。这个取舍,值得每个做 AI 工具的开发者思考。
给 AI 配一门 DSL,比配一个 API 有用
写到最后,我想提炼一个模式。
过去两年,AI 工具的主流做法是给 AI 配 API 调用能力。让 AI 调 G2 的 API 画图,调 ECharts 的 API 画图,调 Canva 的 API 做设计。但 API 调用的问题是:AI 需要用对函数名、传对参数、处理好返回值。任何一个环节出错,输出就废了。
Infographic 代表的思路是:给 AI 配一门 DSL,让 AI 用 DSL 描述意图,再由引擎渲染。
DSL 比 API 更接近 AI 的生成模式。AI 本质上是语言模型,它的强项是生成语言,不是调用函数。一门好的 DSL,应该让 AI 的「语言生成」能力直接映射到「内容生成」结果。
这个模式不只适用于信息图。Mermaid 用 DSL 让 AI 画流程图,Tailwind 用 CSS class 让 AI 做样式,SQL 用声明式查询让 AI 做数据分析。它们的共同点是:DSL 降低了 AI 的「行为空间」,让 AI 不需要理解复杂的 API 调用链,只需要在 DSL 的语法框架内填空。
Infographic 把 DSL 的容错性做到了流式级别,这是它跟 Mermaid 这些前辈最大的区别。AI 输出到一半,渲染器已经开始工作了。
一点想法
AntV 做了 G2、G6、S2、L7,十几年的积累。但面对 AI 时代,他们没有选择「改造 G2 让它支持 AI」,而是从零写了一个新引擎。
这个决策本身就有意思。改造一个成熟的库,看似省力,但架构约束会让你寸步难行。G2 的语法是面向开发者的,它的设计假设是「写配置的人能看懂文档」。Infographic 的语法是面向 AI 的,它的设计假设是「写配置的人可能根本不看文档,直接给个例子它就照着写」。
两个假设,两条路。
Infographic 选择了更难的那条路,但方向是对的。

评论互动