引言
G2 是 AntV 体系里最底层、也最「语法化」的图表库。它不像 ECharts 那样以「图表类型」为中心(你要 bar 就配 type: 'bar'),而是以图形语法为中心:你声明数据、几何标记(mark)、视觉通道(channel)、坐标系(coordinate),由 G2 推导出具体图形。这种范式的代价是上手曲线更陡,回报是表达能力不受内置图表类型限制。
G2 5.x 相比 4.x 是一次架构级重写。核心变化有三:其一,标记(Mark)成为一等公民,interval、point、line、area、cell、polygon 等标记可以自由组合;其二,通道(Channel)显式化,x、y、color、size、shape、opacity 都可独立指定字段与比例尺;其三,交互与动画拆成独立规格,不再耦合在标记里。这些变化让 G2 5 的配置更接近 Vega-Lite 的图形语法模型。
工程上的实际价值在于三点:需要多标记组合(同一坐标系里柱 + 线 + 标注)时比 ECharts 更自然;需要自定义通道映射(用 size 编码第四维、用 shape 区分系列)时不必绕过框架;需要规格可序列化(存进数据库、由后端生成)时配置本身就是纯 JSON 化的对象。本文按「标记 → 变换 → 坐标系 → 交互 → 组合」的顺序展开。
目录
- G2 的定位与 5.x 架构变化
- 标记 Mark 与视觉通道 Channel
- 数据变换与统计聚合
- 坐标系与分面
- 比例尺、图例与提示
- 交互语法的声明式配置
- 组合视图与多标记协同
- 主题、样式与自定义图形
- G2、G2Plot 与 Ant Design Charts 的分层
- 与 React 集成与状态管理
- 大数据量与渲染性能
- 迁移路径与生态协同
1. G2 的定位与 5.x 架构变化
AntV 是蚂蚁金服的图表体系,包含 G2(图形语法引擎)、G6(图可视化)、F2(移动端)、L7(地理)、S2(表格)。G2 是其中最底层的一个,G2Plot 与 Ant Design Charts 都构建在它之上。
G2 5.x 的核心抽象是 Spec(规格)。一份完整的图表规格由五部分组成:data(数据)、encode(通道映射)、transform(数据变换)、scale(比例尺)、coordinate(坐标系),再加上 interaction、animate、theme 等横切配置。
import { Chart } from '@antv/g2';
const chart = new Chart({ container: 'container', autoFit: true });
chart
.interval() // 标记:矩形
.data(sales) // 数据
.encode('x', 'region') // 通道映射
.encode('y', 'amount')
.encode('color', 'category')
.transform({ type: 'sortX', by: 'y', reverse: true }) // 变换
.scale('y', { nice: true })
.coordinate({ type: 'cartesian' })
.interaction('elementHighlight', { background: true });
chart.render();
这与 ECharts 的差别是根本性的:ECharts 里「柱状图」是一个预定义的渲染器,而 G2 里「柱状图」是 interval 标记 + cartesian 坐标系 + x/y 通道的自然结果。换成 polar 坐标系,同一个 interval 就变成了玫瑰图;换成 theta 坐标系就是饼图。图形类型是坐标系与标记的组合,而不是枚举值。
2. 标记 Mark 与视觉通道 Channel
G2 5 的标记清单覆盖了常见几何对象:
| 标记 | 几何形态 | 典型用途 |
|---|---|---|
interval | 矩形/柱/扇区 | 柱状图、直方图、饼图 |
point | 点 | 散点图、气泡图 |
line | 折线 | 趋势图 |
area | 面积 | 面积图、河流图 |
cell | 单元格 | 热力图、日历图 |
rect | 任意矩形 | 甘特图、区间图 |
box | 箱体 | 箱线图 |
text | 文本 | 标签、标注 |
path | 路径 | 自定义形状 |
polygon | 多边形 | 地图区域、自定义区域 |
通道是把数据字段映射到视觉属性的桥梁。G2 5 支持的通道包括 x、y、color、size、shape、opacity、stroke、text、key、series、enterType 等。
chart
.point()
.data(rows)
.encode('x', 'gdp') // 位置编码
.encode('y', 'lifeExpectancy')
.encode('size', 'population') // 面积编码(自动用 sqrt 比例尺)
.encode('color', 'continent') // 色相编码
.encode('shape', 'continent') // 冗余编码,提升可访问性
.scale('size', { range: [4, 40] }); // 限定气泡半径范围
冗余编码(同一字段映射到 color 与 shape)是 G2 的实用特性,它让图表在色盲或黑白打印下仍可读。这与视觉编码原理中「不要用颜色作为唯一区分」的结论一致,详见 视觉编码与感知原理 。
encode('key', ...) 指定数据的主键,用于数据更新时的元素复用——这与 D3 的 key 函数是同一个作用,缺省按索引匹配会导致重排时动画错乱。
3. 数据变换与统计聚合
G2 5 的 transform 是声明式的数据变换层,它在渲染前对数据做加工,不需要手写预处理代码。
// 按 x 分组求均值
chart.line().data(measurements).encode('x', 'day').encode('y', 'value')
.transform({ type: 'groupX', y: 'mean' });
// 分箱直方图
chart.interval().data(values.map((v) => ({ v }))).encode('x', 'v')
.transform({ type: 'binX', y: 'count', thresholds: 20 }).scale('y', { nice: true });
// 堆叠 + 归一化
chart.interval().data(sales)
.encode('x', 'month').encode('y', 'amount').encode('color', 'channel')
.transform({ type: 'stackY' }) // 普通堆叠;换 normalizeY 即 100% 堆叠
.coordinate({ type: 'cartesian' });
常用的 transform 包括:stackY/stackX(堆叠)、dodgeX(分组避让)、normalizeY(归一化)、binX/binY(分箱)、groupX/groupY(分组聚合)、sortX/sortY(排序)、diffY(差值)、filter(过滤)、fold(宽转长)、jitter(抖动)。
变换的顺序有意义。stackY 必须在 normalizeY 之前(先堆叠再归一化),sortX 应该在 dodgeX 之前(先排序再分组避让)。G2 按声明顺序依次应用,顺序错了会得到反直觉的结果。另外,fold 是宽表转长表的关键——它把多列数值转成「字段名 + 值」两列,从而可以用一个通道映射多列。
变换该在前端做的边界与 ECharts 一致:行数不超过几万、不涉及跨表关联。真正的聚合应下沉到 OLAP 侧,前端只做展示层的轻量加工。
4. 坐标系与分面
坐标系决定了通道如何映射到屏幕。G2 支持的坐标系包括 cartesian(直角)、polar(极坐标)、theta(极坐标变体,用于饼图)、radial(径向)、helix(螺旋)、transpose(转置)、parallel(平行)。
// 同一份 interval 标记,换坐标系得到完全不同的图形
chart.interval().data(sales).encode('x', 'region').encode('y', 'amount');
chart.coordinate({ type: 'cartesian' }); // 柱状图
// chart.coordinate({ type: 'transpose' }); // 条形图(转置)
// chart.coordinate({ type: 'polar' }); // 玫瑰图
// chart.coordinate({ type: 'theta' }); // 饼图/环形图
// 坐标系的变换可叠加:转置 + 极坐标 = 径向条形图
chart.coordinate({ type: 'polar', innerRadius: 0.3, transform: [{ type: 'transpose' }] });
transpose 是最实用的坐标系变换——它交换 X 与 Y 轴,让「柱状图」秒变「条形图」而无需改任何编码。这在类别名很长、需要横向排列时非常方便。
分面(facet)把数据按某字段拆成多个子图。G2 5 用 facet 方法声明:
chart
.facet({ type: 'rect', data: { fields: ['region'] } }) // 按 region 拆子图
.rect()
.encode('x', 'month').encode('y', 'amount');
// 也可以直接给标记指定分面
chart.interval().data(sales).encode('x', 'month').encode('y', 'amount')
.facet({ type: 'rect', data: { fields: ['region'] }, shareSize: true });
shareSize: true 让所有子图共享 Y 轴范围,这是小倍数图的关键——不共享的话,子图之间的量级差异就被各自的缩放抹掉了。
5. 比例尺、图例与提示
比例尺决定数据域到视觉范围的映射。G2 支持 linear、log、sqrt、pow、time、band、point、ordinal、quantize、quantile、threshold、sequential 等。
chart
.point()
.data(rows)
.encode('x', 'income').encode('y', 'spend')
.encode('color', 'score')
.scale('x', { type: 'log', domain: [100, 100000] }) // 对数轴
.scale('color', { type: 'sequential', range: ['#e8f0fe', '#1a56db'] })
.scale('y', { nice: true, zero: true }); // 零基线
scale('y', { zero: true }) 强制 Y 轴包含零,这对柱状图是必须的——柱状图截断基线会严重误导,理由见 数据可视化原理与图表选型
。折线图则可以设 zero: false 以放大趋势。
图例与提示由通道自动派生:给 color 通道映射了字段,G2 自动生成图例;给 tooltip 配置项声明了字段,G2 自动生成提示内容。
chart
.interval()
.data(sales)
.encode('x', 'region').encode('y', 'amount').encode('color', 'category')
.scale('color', { range: ['#2f6fed', '#22a06b', '#e8a33d'] }) // 自定义色板
.legend('color', { position: 'top', layout: { justifyContent: 'center' } })
.tooltip({ title: 'region', items: [{ field: 'amount', valueFormatter: (v) => `${v} 万` }] })
.axis('y', { labelFormatter: (v) => `${v / 10000}万` });
.legend()、.tooltip()、.axis() 都是通道级的配置方法,可以按通道分别定制。这套 API 的一致性比 ECharts 的顶层 legend/tooltip/xAxis 更统一——不会出现「图例配置在顶层但样式在 series 里」的割裂。
6. 交互语法的声明式配置
G2 5 把交互抽象成可组合的规格。内置交互包括 elementHighlight(元素高亮)、elementSelect(选择)、legendFilter(图例过滤)、brushX/brushY(框选)、tooltip、slider(缩放滑块)、scrollbar、elementPointMove 等。
chart
.line()
.data(trend)
.encode('x', 'date').encode('y', 'value').encode('color', 'series')
// 悬停时高亮当前系列,其余系列变淡
.interaction('elementHighlight', { background: true, link: true })
// 图例点击过滤
.interaction('legendFilter')
// 缩放滑块
.interaction('slider', { x: { labelFormatter: (v) => new Date(v).toLocaleDateString() } });
// 框选后触发自定义逻辑
chart.interaction('brushXHighlight', {
series: true,
// 通过事件回调拿到选中范围
});
chart.on('brush:highlight', (e) => { console.log(e.data.selection); });
交互可以自定义。G2 提供了 chart.interaction 的注册机制,可以基于内置的交互基类组合出新的交互行为。这比 ECharts 的 dispatchAction 命令式模型更声明化——交互是一份可序列化的规格,而不是一堆事件监听代码。
elementHighlight 的 link: true 选项特别实用:悬停某个数据点时,同一 X 位置的其他系列点会被连起来,方便读者对照。这是「改变前注意特征以引导注意」原则的典型应用。
7. 组合视图与多标记协同
G2 5 最强大的能力是在同一个坐标系里自由叠加多个标记。这在 ECharts 里需要指定 xAxisIndex/yAxisIndex 并小心处理 grid,而 G2 直接写多个标记即可。
const chart = new Chart({ container: 'container' });
chart.interval().data(sales).encode('x', 'month').encode('y', 'amount')
.style('fill', '#dbeafe').axis('y', { title: '销售额' }); // 柱:销售额
chart.line().data(growth).encode('x', 'month').encode('y', 'rate')
.style('stroke', '#d64545').style('lineWidth', 2); // 线:同比增长率
chart.text().data([peakPoint]).encode('x', 'month').encode('y', 'amount')
.encode('text', 'label').style('dy', -12); // 文本:峰值标注
chart.render();
这种「一个 chart 实例 + 多个 mark」的模型天然支持分层绘制:先声明的标记在下层,后声明的在上层。对比 ECharts 需要把所有 series 塞进一个 series 数组、靠 z 属性控制层级,G2 的顺序即层级更直观。
需要双 Y 轴时,G2 通过给不同标记指定不同的 y 通道比例尺实现,但要注意双 Y 轴本身是误导性设计——两条曲线的斜率对比完全由轴缩放决定。如果确实需要,用上下分面(facet)比双轴更诚实。
8. 主题、样式与自定义图形
G2 5 的主题系统基于 design token。内置 classic、classicDark、academy 等主题,也可以用 theme 覆盖任意 token。
chart.theme({
type: 'classic',
// 覆盖设计令牌
category10: ['#2f6fed', '#22a06b', '#e8a33d', '#d64545', '#7a5af8'],
axis: {
labelFontSize: 12,
labelFill: '#666',
gridStroke: '#f0f0f0',
gridStrokeOpacity: 1,
},
legend: { itemLabelFontSize: 12 },
});
// 样式可以按标记、按通道、按数据项覆盖
chart.interval().data(sales).encode('x', 'region').encode('y', 'amount')
.style('fill', (d) => (d.amount > 5000 ? '#d64545' : '#9ca3af')) // 按数据条件着色
.style('radiusTopLeft', 4).style('radiusTopRight', 4);
自定义图形用 path 或 polygon 标记。当内置标记无法表达时(如自定义的漏斗、仪表盘指针),可以用 path 标记配合通道映射:
chart.path().data(funnelStages)
.encode('d', 'pathD') // 直接给出 SVG 路径字符串
.encode('color', 'stage').style('fillOpacity', 0.8);
更复杂的需求可以注册自定义 shape。G2 5 的图形系统基于 @antv/g,支持 Canvas、SVG、WebGL 三种渲染器,通过 chart.options({ renderer: 'svg' }) 切换。
9. G2、G2Plot 与 Ant Design Charts 的分层
AntV 的图表能力分三层,理解分层能避免「用 G2 写本该用 G2Plot 的图」的过度工程。
| 层次 | 包 | 定位 | 适合 |
|---|---|---|---|
| 语法层 | @antv/g2 | 图形语法引擎 | 自定义图形、多标记组合、规格化 |
| 图表层 | @antv/g2plot | 开箱即用图表 | 标准图表快速接入 |
| 组件层 | @ant-design/charts | React 组件封装 | React 项目、Ant Design 生态 |
| 生态 | @antv/g6 / L7 / S2 | 图/地理/表格 | 专用领域 |
判断标准:能用 G2Plot 的配置表达就用 G2Plot,需要多标记组合或自定义通道时才降到 G2。G2Plot 的 API 是 new Bar({ ... }) 这样的图表级配置,上手成本低得多。Ant Design Charts 则把 G2Plot 包成 React 组件,用 props 传配置。
这三层共享同一套底层(G2),所以主题与色板可以跨层统一——这是相比混用 ECharts + D3 的一个优势。
10. 与 React 集成与状态管理
G2 是命令式的(new Chart() + .render()),与 React 的声明式模型需要一层适配。
import { useEffect, useRef } from 'react';
import { Chart } from '@antv/g2';
export function G2Chart({ spec, height = 320 }: { spec: any; height?: number }) {
const ref = useRef<HTMLDivElement>(null);
const chartRef = useRef<Chart>();
useEffect(() => {
if (!ref.current) return;
const chart = new Chart({ container: ref.current, autoFit: true, height });
chartRef.current = chart;
return () => { chart.destroy(); };
}, [height]);
// 规格变化时用 changeData / options 更新,而不是重建实例
useEffect(() => {
const chart = chartRef.current;
if (!chart) return;
chart.options(spec); // 全量替换规格
chart.render();
}, [spec]);
return <div ref={ref} />;
}
三个要点。其一,chart.destroy() 必须调用,否则 React StrictMode 的双调用会留下僵尸实例。其二,更新时优先用 chart.changeData(newData)(只换数据,复用已计算的布局),只有规格变化才用 chart.options() 全量替换。其三,spec 应该是稳定引用(用 useMemo 包裹),否则每次渲染都触发重绘。
因为 G2 的规格本身就是普通对象,它天然适合存在 Redux/Zustand 里,或者由后端生成下发——这是 BI 平台用 G2 做渲染引擎的基础。
11. 大数据量与渲染性能
G2 5 的渲染基于 @antv/g,默认 Canvas。大数据量下的优化手段与 ECharts 类似但 API 不同。
chart.options({
renderer: 'canvas', // 或 'svg'(小数据量 + 无障碍)
animate: false, // 大数据量关闭动画
interaction: { tooltip: false }, // 关闭逐点提示
});
chart
.point()
.data(hugeData)
.encode('x', 'x').encode('y', 'y')
.style('opacity', 0.3)
.style('fillOpacity', 0.3);
| 数据量 | 策略 |
|---|---|
| < 2k | 默认配置 |
| 2k ~ 10k | 关动画、降不透明度、shape: 'point' 用矩形近似 |
| 10k ~ 100k | 分箱聚合(binX/binY)或热力替代 |
| > 100k | 服务端聚合后下发 |
G2 5 没有 ECharts 那样的 large/progressive 开关,因此大数据量下的正确策略是聚合而非硬渲染。transform({ type: 'binX' }) 可以在前端做二维分箱,把十万点压成几千个格子,配合 cell 标记画热力图——这既解决了性能,也解决了过度绘制(overplotting)带来的可读性问题。
12. 迁移路径与生态协同
从 G2 4.x 升到 5.x 是不兼容的大版本升级,几乎所有配置都要改写。主要的映射关系:
| G2 4 | G2 5 |
|---|---|
new Chart({ ... }) + chart.interval().position('x*y') | chart.interval().encode('x', 'x').encode('y', 'y') |
chart.data() | .data() 挂在 mark 上 |
geometry.adjust('stack') | .transform({ type: 'stackY' }) |
chart.coordinate('theta') | .coordinate({ type: 'theta' }) |
chart.interaction('active-region') | .interaction('elementHighlight') |
geometry.color('field') | .encode('color', 'field') |
迁移的收益是明确的:编码与变换分离后,配置更接近图形语法模型,可读性与可组合性都更好。代价是所有既有图表都要改,建议按「先迁标准图表、再迁复杂组合」的顺序推进,并用视觉回归测试兜底。
生态协同方面,G2 与 G6(图可视化)、L7(地理)、S2(表格)共享 @antv/g 渲染层,可以在一个页面里混用而不用担心渲染器冲突。这与 地理关系与网络图可视化
中讨论的地理与关系图场景直接相关——那些场景用 G6 或 L7 会比 G2 更合适。
权衡取舍
| 决策点 | 选项 A | 选项 B | 何时选 A | 何时选 B |
|---|---|---|---|---|
| 库层次 | G2 语法层 | G2Plot 图表层 | 多标记组合、自定义通道 | 标准图表快速接入 |
| 范式 | G2 声明式 | ECharts 配置式 | 需要规格化/后端生成 | 团队熟悉 ECharts |
| 坐标系 | cartesian | polar/theta | 比较与趋势 | 构成与周期 |
| 变换位置 | 前端 transform | OLAP 聚合 | 行数 <1 万 | 需要关联/去重 |
| 大数据 | 前端分箱热力 | 服务端聚合 | 10 万点以内 | 超过 10 万点 |
| 更新方式 | changeData | options 全量 | 只换数据 | 规格变化 |
| 渲染器 | Canvas | SVG | 大数据量 | 无障碍/矢量导出 |
常见坑清单
- 变换顺序写反——
normalizeY放在stackY之前会得到错误结果;按「先堆叠后归一化、先排序后避让」的顺序。 - 柱状图不设 zero: true——Y 轴自动缩放到数据范围,柱长比例失真;柱状图必须含零基线。
- 忘记 destroy()——React StrictMode 双调用留下僵尸实例;卸载时调
chart.destroy()。 - spec 引用不稳定——每次渲染都重建图表,交互状态丢失;用 useMemo 固定 spec 引用。
- 分面不设 shareSize——子图各自缩放,量级差异被抹掉;小倍数图必须共享轴范围。
- encode key 缺失——数据重排时元素复用错位,动画跳变;指定
encode('key', ...)。 - 大数据量开动画——逐点动画导致掉帧;数据超 2000 点关
animate。 - 图例配置写在顶层——G2 的图例是通道级的,顶层配置无效;用
.legend('color', {...})。 - 双 Y 轴做指标对比——斜率对比由轴缩放决定,制造假相关;改用上下分面。
- 用 G2 写标准柱状图——过度工程,代码量是 G2Plot 的数倍;标准图用 G2Plot。
小结
G2 的核心价值是把图表从「类型枚举」提升为「语法组合」:interval 标记配 polar 坐标系就是玫瑰图,配 theta 就是饼图;color 通道映射字段就自动生成图例,映射 size 就变成气泡图。这套模型让 G2 在多标记组合与自定义通道映射上比配置式库更自然,代价是上手成本与迁移成本。
工程落地上的关键判断是分层选型:标准图表用 G2Plot 或 Ant Design Charts,需要多标记叠加或自定义通道时才降到 G2 语法层。数据变换应尽量下沉到 OLAP 侧,前端只保留展示层的轻量加工;大数据量下用分箱聚合而非硬渲染。
下一步可以对照 D3.js 与图形语法 看命令式与声明式的边界,或者在 ECharts 图表工程实战 里对比另一种主流配置式方案;把这些单图组合成完整界面见 仪表盘与数据大屏设计 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。