文章

模型驱动视图:LLM 生成可视化代码的路径与挑战

从 D3 到 ECharts,LLM 生成可视化代码的几种路径、Prompt 工程策略,以及 Scott Logic 和 Observable 的实战经验。

模型驱动视图:LLM 生成可视化代码的路径与挑战

让模型”画图”有两种主流路径:多模态直接生成图像,或者让模型写代码来渲染。

前者的上限受限于图像模型对”准确性”的理解——柱状图的高度、饼图的比例,很难保证精确。后者的本质是让模型写代码,渲染交给专业库,准确性由代码逻辑保证。

这条路走得通吗?能走多远?

代码生成的三种模式

模型生成可视化代码,大致有三种模式:

1. 直接生成库代码(D3/ECharts/Chart.js)

这是最直接的方式:告诉模型你要什么图,它输出 JavaScript/Python 代码。

// 模型输出的 D3 代码
const svg = d3.select("body").append("svg")
  .attr("width", 960).attr("height", 500);

svg.selectAll("rect")
  .data(data)
  .enter().append("rect")
  .attr("x", d => x(d.category))
  .attr("y", d => y(d.value))
  .attr("width", x.bandwidth())
  .attr("height", d => height - y(d.value));

优点:灵活性最高,理论上能生成任何图表。 问题:代码量大(D3 一张基础图表 200-300 行),语法错误率高,版本混乱(D3 v4 vs v7 API 差异大)。

2. 生成配置对象(ECharts/Vega-Lite)

这种方式让模型输出 JSON 配置,而不是命令式代码。

// 模型输出的 ECharts 配置
{
  xAxis: { type: 'category', data: ['A', 'B', 'C'] },
  yAxis: { type: 'value' },
  series: [{ type: 'bar', data: [10, 20, 30] }]
}

优点:结构化输出,语法错误少,模型更容易”填空”。 问题:受限于库的能力边界,高度定制化的需求难以满足。

3. 生成领域特定语言(DSL)

AntV 的 GPT-Vis 和 Infographic 走的是这条路:设计一种专门给 LLM 用的语法。

vis line
data
  - time 2020
    value 100
  - time 2021
    value 120
style
  lineWidth 3

优点:语法极简,模型生成准确率高,支持流式渲染。 问题:需要额外开发 DSL 解析器和渲染引擎,能力受 DSL 设计限制。

实战:LLM 写 D3 代码到底靠不靠谱

Scott Logic 团队做过一组系统性的实验:用不同 LLM 生成 D3 代码,看成功率。

简单 Prompt 的表现

直接问:

写一段 D3 代码,展示 2022 赛季 F1 每位车手的获胜场次。

结果:

  • GPT-4:50% 成功,其余有坐标轴裁切、元素不存在等问题
  • GPT-3.5-turbo:30% 成功
  • text-davinci-003:60% 成功,但输出不稳定

典型失败模式:

  1. 版本混淆:调用了 D3 v4 的 d3.nest(),但页面加载的是 v7
  2. 容器不存在d3.select("#chart"),但 HTML 里没有这个元素
  3. 边距问题:坐标轴渲染在 SVG 可视区域之外
  4. 假数据:模型自己编了一组数据,而不是加载真实数据

改进:提供数据结构

把 Prompt 改成:

数据在 data-sources/2022_f1.csv,字段包括 Circuit、Date、1st Place Driver…

成功率明显提升。模型能理解”1st Place Driver 字段出现次数 = 获胜场次”,并生成正确的聚合逻辑。

这说明:模型对数据语义的理解比预期强,但需要你明确告诉它数据长什么样

再改进:One-shot Prompting

在 Prompt 里塞一个完整示例:

### INSTRUCTION
写 D3 代码展示每个班级的学生人数

### DATA
CSV 路径:data-sources/classes.csv
字段:Class, Pupils
示例数据:Biology,23 / History,28 / Latin,5

### RESPONSE
d3.csv('data-sources/classes.csv').then((data) => {
  // 完整的 D3 代码...
  // 包含正确的 margin、坐标轴、标签
});

### INSTRUCTION
写 D3 代码展示 2022 赛季每位 F1 车手的获胜场次

### DATA
CSV 路径:data-sources/2022_f1.csv
字段:Circuit, Date, 1st Place Driver...

### RESPONSE

使用 One-shot Prompting 后:

  • GPT-4:80% 成功率
  • GPT-3.5-turbo:40% 成功率
  • text-davinci-003:50% 成功率

示例代码里的 margin 设置、坐标轴标签、元素选择方式,都被模型”学会”了。

关键挑战

版本碎片化

D3 的 v3、v4、v5、v6、v7 API 都有差异。训练数据里混杂着不同版本的代码,模型经常”缝合”出跨版本的代码。

Scott Logic 的实验里,有输出同时调用了 d3.nest()(v5)和 d3.group()(v6+),在任何版本都无法运行。

执行上下文缺失

模型不知道你的页面长什么样:

  • SVG 容器存在吗?
  • <svg> 还是 <div>
  • 需要响应式吗?

这些信息如果不写在 Prompt 里,模型只能猜。

数据变换逻辑

“展示每位车手的获胜场次”——这句话对人类很简单,对模型意味着:

  1. 遍历所有记录
  2. 1st Place Driver 分组
  3. 计算每组的 count
  4. 按计数排序

模型能理解这个逻辑,但如果数据结构复杂(嵌套、多表关联),错误率会显著上升。

Observable 的发现

Observable 的 Robert Kosara 用 Claude Sonnet 3.7 做了类似实验。他的观察:

  1. 基础图表很稳:折线图、柱状图、散点图,一次性成功率很高
  2. 代码质量不错:结构清晰、注释完整,像人类写的
  3. 有些”奇怪”的实现:比如用一串 if-else 计算季度,而不是 Math.floor(month/3) + 1
  4. 对数据量的假设:给 12 行数据,模型会假设是完整数据集,加上数据点标记;给完整数据后,这些标记就显得冗余

Prompt 工程策略

基于这些实战经验,一套有效的 Prompt 结构:

### 任务描述
你要生成一段 [D3/ECharts/Chart.js] 代码,在 [浏览器/Node.js] 环境运行。

### 数据
- 来源:[CSV 路径 / 内嵌 JSON / API 端点]
- 字段:[列出所有字段及含义]
- 示例:[3-5 行真实数据]

### 执行环境
- 容器:页面已有 `<svg id="chart">` 元素
- 库版本:D3 v7.9.0
- 尺寸:800×600

### 图表要求
- 类型:[折线图/柱状图/...]
- X 轴:[字段名],标签为"[中文标签]"
- Y 轴:[字段名],标签为"[中文标签]"
- 交互:[tooltip/zoom/...]

### 代码注释
在数据变换的关键步骤添加注释,说明你在做什么。

核心原则:

  1. 明确执行环境:容器、版本、尺寸
  2. 提供数据结构:字段名 + 含义 + 示例数据
  3. 要求代码注释:强制模型”思考”数据变换逻辑
  4. 用示例引导:One-shot 比零样本强很多

DSL vs 原生代码

AntV 的 GPT-Vis 选择设计 DSL,而不是让模型直接写 JavaScript。这个选择的逻辑:

维度原生代码DSL
语法复杂度高(引号、括号、分号)低(缩进 + 关键词)
错误类型语法错误 + 运行时错误主要是语义错误
流式渲染困难(代码不完整时无法解析)容易(逐行解析)
灵活性最高受 DSL 设计限制
模型准确率60-80%90%+(根据 AntV 公布的数据)

DSL 的本质是把”模型的输出空间”压缩到一个更小、更可控的范围。

小结

让模型生成可视化代码这条路是走得通的,但有几个前提:

  1. Prompt 要足够具体:执行环境、数据结构、示例代码,缺一不可
  2. 选对目标语言:ECharts 配置比 D3 代码更容易生成;DSL 又比配置更容易
  3. 接受 20% 的失败率:即使是最强的 GPT-4,one-shot 模式下也只有 80% 成功率
  4. 人工兜底:生成后需要 review,尤其是数据变换逻辑

更深层的问题是:代码生成本质上是把”理解需求”的负担转嫁给模型。如果需求本身模糊(“做一个好看的销售图表”),模型只能猜。如果需求精确(“X 轴是月份,Y 轴是销售额,用柱状图”),模型的表现会很稳定。

这不是模型的问题,是需求表达的问题。好的可视化工具,应该让精确表达需求的成本足够低。

讨论

留下你的想法

欢迎补充观点、指出问题,或分享与你类似的实践经验。

💬 留言评论

欢迎交流讨论,提问或分享你的想法。