AntV G2 声明式图表体系

以 G2 5.x 为基准讲解图形语法的工程实现,包括 Mark 与 Channel 的映射、数据变换、坐标系与分面、比例尺与图例、交互语法,以及 G2/G2Plot/Ant Design Charts 三层的分工与 React 集成要点。

引言

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 化的对象。本文按「标记 → 变换 → 坐标系 → 交互 → 组合」的顺序展开。

目录

  1. G2 的定位与 5.x 架构变化
  2. 标记 Mark 与视觉通道 Channel
  3. 数据变换与统计聚合
  4. 坐标系与分面
  5. 比例尺、图例与提示
  6. 交互语法的声明式配置
  7. 组合视图与多标记协同
  8. 主题、样式与自定义图形
  9. G2、G2Plot 与 Ant Design Charts 的分层
  10. 与 React 集成与状态管理
  11. 大数据量与渲染性能
  12. 迁移路径与生态协同

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/chartsReact 组件封装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 4G2 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
坐标系cartesianpolar/theta比较与趋势构成与周期
变换位置前端 transformOLAP 聚合行数 <1 万需要关联/去重
大数据前端分箱热力服务端聚合10 万点以内超过 10 万点
更新方式changeDataoptions 全量只换数据规格变化
渲染器CanvasSVG大数据量无障碍/矢量导出

常见坑清单

  1. 变换顺序写反——normalizeY 放在 stackY 之前会得到错误结果;按「先堆叠后归一化、先排序后避让」的顺序。
  2. 柱状图不设 zero: true——Y 轴自动缩放到数据范围,柱长比例失真;柱状图必须含零基线。
  3. 忘记 destroy()——React StrictMode 双调用留下僵尸实例;卸载时调 chart.destroy()。
  4. spec 引用不稳定——每次渲染都重建图表,交互状态丢失;用 useMemo 固定 spec 引用。
  5. 分面不设 shareSize——子图各自缩放,量级差异被抹掉;小倍数图必须共享轴范围。
  6. encode key 缺失——数据重排时元素复用错位,动画跳变;指定 encode('key', ...)。
  7. 大数据量开动画——逐点动画导致掉帧;数据超 2000 点关 animate。
  8. 图例配置写在顶层——G2 的图例是通道级的,顶层配置无效;用 .legend('color', {...})。
  9. 双 Y 轴做指标对比——斜率对比由轴缩放决定,制造假相关;改用上下分面。
  10. 用 G2 写标准柱状图——过度工程,代码量是 G2Plot 的数倍;标准图用 G2Plot。

小结

G2 的核心价值是把图表从「类型枚举」提升为「语法组合」:interval 标记配 polar 坐标系就是玫瑰图,配 theta 就是饼图;color 通道映射字段就自动生成图例,映射 size 就变成气泡图。这套模型让 G2 在多标记组合与自定义通道映射上比配置式库更自然,代价是上手成本与迁移成本。

工程落地上的关键判断是分层选型:标准图表用 G2Plot 或 Ant Design Charts,需要多标记叠加或自定义通道时才降到 G2 语法层。数据变换应尽量下沉到 OLAP 侧,前端只保留展示层的轻量加工;大数据量下用分箱聚合而非硬渲染。

下一步可以对照 D3.js 与图形语法 看命令式与声明式的边界,或者在 ECharts 图表工程实战 里对比另一种主流配置式方案;把这些单图组合成完整界面见 仪表盘与数据大屏设计 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「数据可视化」更多文章

  1. WebGL 与三维数据可视化
  2. 数据叙事与图表沟通
  3. 嵌入式分析与白标集成