返回 Skills
antvis/chart-visualization-skills· MIT 内容可用

antv-g2-chart

Generate G2 v5 chart code. Use when user asks for G2 charts, bar charts, line charts, pie charts, scatter plots, area charts, or any data visualization with G2 library.

安装

与 skills.sh 相同的 Command / Prompt 安装方式


name: antv-g2-chart description: Generate G2 v5 chart code. Use when user asks for G2 charts, bar charts, line charts, pie charts, scatter plots, area charts, or any data visualization with G2 library.

G2 v5 Chart Code Generator

You are an expert in AntV G2 v5 charting library. Generate accurate, runnable code following G2 v5 best practices.


1. Core Constraints / 核心约束 (MUST follow)

  1. container is mandatory: new Chart({ container: 'container', ... })
  2. Use Spec Mode ONLY: chart.options({ type: 'interval', data, encode: {...} })(V4 链式 API 见 Forbidden Patterns)
  3. chart.options() 只能调用一次:多次调用会完整覆盖前一次配置,只有最后一次生效。多 mark 叠加必须用 type: 'view' + children 数组,而不是多次调用 chart.options()
  4. encode object: encode: { x, y }(禁止 V4 的 .position('x*y')
  5. transform must be array: transform: [{ type: 'stackY' }]
  6. labels is plural: Use labels: [{ text: 'field' }] not label: {}
  7. coordinate 规则
    • 坐标系类型直接写:coordinate: { type: 'theta' }coordinate: { type: 'polar' }
    • transpose 是变换不是坐标系类型,必须写在 transform 数组里:coordinate: { transform: [{ type: 'transpose' }] }
    • ❌ 禁止:coordinate: { type: 'transpose' }
  8. 范围编码(甘特图、candlestick 等):encode: { y: 'start', y1: 'end' },禁止 y: ['start', 'end']
  9. 样式原则:用户描述中提到的样式(radius、fillOpacity、color、fontSize 等)必须完整保留;用户未提及的装饰性样式(shadowBlurshadowColorshadowOffsetX/Y 等)不要自行添加
  10. animate 规则:用户未明确要求动画时不要添加 animate 配置(G2 自带默认动画),只有用户明确描述动画需求时才添加
  11. scale.color.palette 只能用合法值:palette 通过 d3-scale-chromatic 查找,非法名称会抛 Unknown palette 错误。不要推断或创造不存在的名称(如 'blueOrange''redGreen''hot''jet''coolwarm' 等均非法)。合法的常用值:顺序色阶 'blues'|'greens'|'reds'|'ylOrRd'|'viridis'|'plasma'|'turbo';发散色阶 'rdBu'|'rdYlGn'|'spectral';不确定时用 range: ['#startColor', '#endColor'] 自定义替代
  12. 禁止在用户代码中使用 d3.*:G2 内部使用 d3,但 d3 对象不会暴露到用户代码作用域,调用 d3.sum() 等会抛 ReferenceError: d3 is not defined。如需聚合,优先使用 G2 内置选项(如 sortXreducer: 'sum'),不得不自定义时用原生 JS:d3.sum(arr, d=>d.v)arr.reduce((s,d)=>s+d.v,0)d3.max(arr, d=>d.v)Math.max(...arr.map(d=>d.v))
  13. 用户未指定配色时,禁止使用白色或近白色作为图形填充色style: { fill: '#fff' }style: { fill: 'white' }style: { fill: '#ffffff' } 等在白色背景下会让图形完全不可见。未指定配色时应依赖 G2 的 encode.color 自动分配主题色,或使用有明确视觉区分度的颜色(如 '#5B8FF9')。以下是合法例外:label 文字 fill: '#fff'(深色背景内标签)、分隔线 stroke: '#fff'(堆叠/pack/treemap 的分隔白线)
  14. padding 只接受 number | 'auto',禁止数组形式padding: [40, 30, 40, 50] 在 G2 v5 中无效(会被忽略或报错)。四边统一用 padding: 40;分方向控制用 paddingTop / paddingRight / paddingBottom / paddingLeft 单独设置;默认 'auto' 已自动为坐标轴/图例预留空间,大多数情况无需手动配置。禁止设置 padding: 0——会关闭自动计算,导致坐标轴/图例被截断;只需调整某一方向时单独设置对应方向即可
  15. autoFit: true 时禁止同时设置 widthautoFit 会完全忽略 width,同时出现时 width 无效。autoFit: true 时只设 height;需要固定宽高时去掉 autoFit 改用 width + height
  16. 用户未指定容器时container 默认为 'container',不要通过 document.createElement('div') 进行创建,代码末尾必须有 chart.render();
  17. 禁止在数据中存放 hex 色值并通过 encode.color 映射encode.color 映射到数据中包含 hex 字符串(如 '#1e3a5f')的字段时,Ordinal scale 会将 hex 字符串当作「类别 key」而非颜色值处理——最终渲染颜色来自 G2 默认调色板而非数据中的 hex 值,且图例会显示无意义的 hex 字符串。正确做法:移除数据中的 color 字段,将 hex 色值放入 scale.color.rangeencode.color 指向有业务含义的字段(如 'group'),通过 scale.color.domain + range 精确配对。例外情况:若必须直接使用数据中的动态颜色,需显式配置 scale: { color: { type: 'identity' } }
  18. Label 可见性与防重叠:柱状图 position: 'inside' 的 label 必须添加 transform: [{ type: 'contrastReverse' }];数据密集图表(折线图多系列、散点图、分组柱状图)label 必须添加 overlapDodgeYoverlapHide;堆叠图/TreeMap/旭日图等空间有限 mark 的 label 必须添加 overflowHide禁止使用 dx 偏移定位 label,应使用 position 控制位置。详见 标签配置
  19. 深色背景文本对比度:容器背景为深色/黑色时必须使用 theme: 'classicDark'(或 theme: { type: 'classicDark', view: { viewFill: '色值' } }),G2 会自动将所有组件文本切换为浅色;浅色背景下禁止将文本设为浅灰色(如 labelFill: '#ccc');饼图 scale.color.range禁止包含与背景相同或近似的颜色。详见 深色主题适配

1.1 Forbidden Patterns / 禁止使用的写法

禁止使用 V4 语法,必须使用 V5 Spec 模式:

// ❌ 禁止:V4 createView
const view = chart.createView();
view.options({...});

// ❌ 禁止:V4 链式 API 调用
chart.interval()
  .data([...])
  .encode('x', 'genre')
  .encode('y', 'sold')
  .style({ radius: 4 });

// ❌ 禁止:V4 链式 encode
chart.line().encode('x', 'date').encode('y', 'value');

// ❌ 禁止:V4 source
chart.source(data);

// ❌ 禁止:V4 position
chart.interval().position('genre*sold');

// ✅ 正确:V5 Spec 模式
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'genre', y: 'sold' },
  style: { radius: 4 },
});

原因:V5 使用 Spec 模式,结构清晰,易于序列化、动态生成和调试。

createView 的正确 V5 替代方案

chart.createView() 在 V4 中用于"多视图共享容器但数据各异",V5 中对应两种场景:

场景 A:同一坐标系内叠加多个 mark(最常见) → 用 type: 'view' + children 数组,children 中不能再嵌套 view 或者 children

// ✅ 多 mark 叠加(折线 + 散点)
chart.options({
  type: 'view',
  data,
  children: [
    { type: 'line',  encode: { x: 'date', y: 'value' } },
    { type: 'point', encode: { x: 'date', y: 'value' } },
  ],
});

场景 B:多个独立坐标系并排/叠加(如人口金字塔、butterfly 图) → 用 type: 'spaceLayer' + children(各子 view 有独立数据和坐标系):

// ✅ 人口金字塔:左右两侧独立视图叠加,共享 Y 轴
chart.options({
  type: 'spaceLayer',
  children: [
    {
      type: 'interval',
      data: leftData,                              // 左侧数据(负值或翻转)
      coordinate: { transform: [{ type: 'transpose' }, { type: 'reflectX' }] },
      encode: { x: 'age', y: 'male' },
      axis: { y: { position: 'right' } },
    },
    {
      type: 'interval',
      data: rightData,                             // 右侧数据
      coordinate: { transform: [{ type: 'transpose' }] },
      encode: { x: 'age', y: 'female' },
      axis: { y: false },
    },
  ],
});

// ✅ 更简单方案:单一视图 + 负值技巧(数据可在一个数组里)
chart.options({
  type: 'interval',
  data: combinedData,                              // 合并数据,用负值区分方向
  coordinate: { transform: [{ type: 'transpose' }] },
  encode: {
    x: 'age',
    y: (d) => d.sex === 'male' ? -d.population : d.population,
    color: 'sex',
  },
  axis: {
    y: { labelFormatter: (d) => Math.abs(d) },     // 显示绝对值
  },
});

选择原则

  • 两侧数据结构相同、只是方向相反 → 优先用负值技巧(单 interval,代码最简洁)
  • 两侧需要完全独立的坐标系/比例尺 → 用 spaceLayer

1.2 禁止使用的幻觉 Mark 类型 / Hallucinated Mark Types

以下类型来自其他图表库(如 ECharts、Vega),G2 中不存在,使用将导致运行时报错:

❌ 错误写法✅ 正确替换
type: 'ruleX'type: 'lineX'(垂直参考线)
type: 'ruleY'type: 'lineY'(水平参考线)
type: 'regionX'type: 'rangeX'(X 轴区间高亮)
type: 'regionY'type: 'rangeY'(Y 轴区间高亮)
type: 'venn'type: 'path' + data.transform: [{ type: 'venn' }]

G2 合法 mark 类型完整列表(不得凭空创造其他 type):

  • 基础:intervallineareapointrectcelltextimagepathpolygonshape
  • 连接:linkconnectorvector
  • 参考线/区域:lineXlineYrangeXrangeYrange(极少用,仅在 x/y 均需限定二维矩形时使用,且数据的 x/y 字段必须是 [start,end] 数组)
  • 统计:boxboxplotdensityheatmapbeeswarm
  • 层次/关系:treemappackpartitiontreesankeychord
  • 特殊:wordCloudgaugeliquid
  • 需引入扩展包:sunburst(需 @antv/g2-extension-plot,见 旭日图

2. Common Mistakes / 常见错误

⚠️ 最高频错误:禁止多次调用 chart.options()

chart.options()全量替换,不是合并。多次调用时只有最后一次生效,前面的配置全部丢失。每个图表只能调用一次 chart.options()

// ❌ Wrong: 多次调用 chart.options() —— 每次完整替换前一次,只有最后一次生效
chart.options({ type: 'interval', data, encode: { x: 'x', y: 'y' } });  // ❌ 被覆盖,不渲染
chart.options({ type: 'line',     data, encode: { x: 'x', y: 'y' } });  // ❌ 被覆盖,不渲染
chart.options({ type: 'text',     data, encode: { x: 'x', y: 'y', text: 'label' } });  // 只有这个生效

// ✅ Correct: 多 mark 叠加必须用 type: 'view' + children,一次 chart.options() 搞定
chart.options({
  type: 'view',
  data,
  children: [
    { type: 'interval', encode: { x: 'x', y: 'y' } },
    { type: 'line',     encode: { x: 'x', y: 'y' } },
    { type: 'text',     encode: { x: 'x', y: 'y', text: 'label' } },
  ],
});

// ✅ 子 mark 需要不同数据时,在 children 里单独指定 data
chart.options({
  type: 'view',
  data: mainData,
  children: [
    { type: 'interval', encode: { x: 'x', y: 'y' } },
    { type: 'text', data: labelData, encode: { x: 'x', text: 'label' } },
  ],
});

多 mark 组合规则:

  • 只能使用 children,禁止 markslayers 等属性
  • children 不能嵌套(children 内不能再有 type: 'view' + children
  • 复杂多坐标系组合用 spaceLayer/spaceFlex
// ❌ Wrong: 使用 marks/layers(禁止)
chart.options({ type: 'view', data, marks: [...] });   // ❌
chart.options({ type: 'view', data, Layers: [...] });  // ❌

// ❌ Wrong: children 嵌套(禁止)
chart.options({ type: 'view', children: [{ type: 'view', children: [...] }] });  // ❌

// ✅ Correct: 复杂多坐标系组合用 spaceLayer
chart.options({
  type: 'spaceLayer',
  children: [
    { type: 'view', children: [...] },
    { type: 'line', encode: { x: 'x', y: 'y' } },
  ],
});

其他常见错误

// ❌ Wrong: padding 数组形式(CSS 简写),G2 v5 不支持,会被忽略
const chart = new Chart({ container: 'container', padding: [40, 30, 40, 50] });  // ❌

// ✅ Correct: 四边统一
const chart = new Chart({ container: 'container', padding: 40 });

// ✅ Correct: 分方向控制
const chart = new Chart({ container: 'container', paddingTop: 40, paddingLeft: 60 });

// ❌ Wrong: missing container
const chart = new Chart({ width: 640, height: 480 });

// ✅ Correct: container required
const chart = new Chart({ container: 'container', width: 640, height: 480 });

// ❌ Wrong: transform as object
chart.options({ transform: { type: 'stackY' } });

// ✅ Correct: transform as array
chart.options({ transform: [{ type: 'stackY' }] });

// ❌ Wrong: label (singular)
chart.options({ label: { text: 'value' } });

// ✅ Correct: labels (plural)
chart.options({ labels: [{ text: 'value' }] });

// ❌ Wrong: labels formatter 把第一个参数当 datum 对象
// formatter 的第一个参数是 text 已映射的值(如 85),不是 datum
// d.value 在数字 85 上为 undefined,结果为 "undefined%"
chart.options({
  labels: [{ text: 'value', formatter: (d) => d.value + '%' }],
});

// ✅ Correct: 用 text 函数直接访问 datum 并格式化(推荐)
chart.options({
  labels: [{ text: (d) => d.value + '%' }],
});

// ✅ Correct: 或用 formatter 的正确用法(val 是已映射的数值)
chart.options({
  labels: [{ text: 'value', formatter: (val) => val + '%' }],
});

// ❌ Wrong: hex 色值放在数据中,被 Ordinal scale 当作类别 key
// 渲染颜色是 G2 默认调色板,图例显示无意义的 '#1e3a5f' 等字符串
const barData = [
  { group: '法律界', value: 85, color: '#1e3a5f' },
  { group: '公司治理专家', value: 78, color: '#2d4a6f' },
];
chart.options({
  type: 'interval',
  data: barData,
  encode: { x: 'group', y: 'value', color: 'color' },
  scale: { color: { type: 'ordinal' } },
});

// ✅ Correct: hex 色值放入 scale.color.range,encode.color 指向业务字段
chart.options({
  type: 'interval',
  data: [
    { group: '法律界', value: 85 },
    { group: '公司治理专家', value: 78 },
  ],
  encode: { x: 'group', y: 'value', color: 'group' },
  scale: {
    color: {
      type: 'ordinal',
      domain: ['法律界', '公司治理专家'],
      range: ['#1e3a5f', '#2d4a6f'],
    },
  },
});

// ✅ Correct (Dynamic Colors): 若必须直接使用数据中的 hex 颜色,需显式指定 identity 比例尺
chart.options({
  type: 'interval',
  data: [
    { group: '法律界', value: 85, color: '#1e3a5f' },
    { group: '公司治理专家', value: 78, color: '#2d4a6f' },
  ],
  encode: { x: 'group', y: 'value', color: 'color' },
  scale: {
    color: { type: 'identity' },
  },
});

// ❌ Wrong: area 图上使用 stroke + lineWidth 会包裹整个填充区域
// 底部和两侧也会被描边,正确做法是 view + children 叠加 area + line
chart.options({
  type: 'area',
  data,
  encode: { x: 'date', y: 'value' },
  style: { fill: '#FF5924', fillOpacity: 0.4, stroke: '#FF5924', lineWidth: 2 },
});

// ✅ Correct: view + area(填充) + line(顶部边缘线)
chart.options({
  type: 'view',
  data,
  children: [
    { type: 'area', encode: { x: 'date', y: 'value' }, style: { fill: '#FF5924', fillOpacity: 0.4 } },
    { type: 'line', encode: { x: 'date', y: 'value' }, style: { stroke: '#FF5924', lineWidth: 2 } },
  ],
});

// ❌ Wrong: unnecessary scale type specification
chart.options({ scale: { x: { type: 'linear' }, y: { type: 'linear' } } });

// ✅ Correct: let G2 infer scale type automatically
chart.options({ scale: { y: { domain: [0, 100] } } });
<!-- CONSTRAINTS:END -->

3. Basic Structure / 基础结构

import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',           // Mark type
  data: [...],                // Data array
  encode: { x: 'field', y: 'field', color: 'field' },
  transform: [],              // Data transforms
  scale: {},                  // Scale config
  coordinate: {},             // Coordinate system
  style: {},                  // Style config
  labels: [],                 // Data labels
  tooltip: {},                // Tooltip config
  axis: {},                   // Axis config
  legend: {},                 // Legend config
});

chart.render();

4. Core / 核心概念

核心概念是 G2 的基础,理解这些概念是正确使用 G2 的前提。

4.1 Chart 初始化

Chart 是 G2 的核心类,所有图表都从 Chart 实例开始。

import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',  // 必填:DOM 容器 ID 或元素
  width: 640,              // 可选:宽度
  height: 480,             // 可选:高度
  autoFit: true,           // 可选:自适应容器大小
  padding: 'auto',         // 可选:内边距
  theme: 'light',          // 可选:主题
});

详细文档: Chart 初始化

4.2 encode 通道系统

encode 将数据字段映射到视觉通道,是 G2 的核心概念。

通道用途示例
xX 轴位置encode: { x: 'month' }
yY 轴位置encode: { y: 'value' }
color颜色encode: { color: 'category' }
size大小encode: { size: 'population' }
shape形状encode: { shape: 'type' }

详细文档: encode 通道系统

4.3 视图组合 (view + children)

使用 view 类型配合 children 数组组合多个 mark。

chart.options({
  type: 'view',
  data,
  children: [
    { type: 'line', encode: { x: 'date', y: 'value' } },
    { type: 'point', encode: { x: 'date', y: 'value' } },
  ],
});

详细文档: 视图组合


5. Concepts / 概念指南

概念指南帮助选择正确的图表类型和配置方案。

5.1 图表类型选择 / Chart Selection

根据数据特征和可视化目标选择合适的图表类型:

数据关系推荐图表Mark
比较柱状图、条形图interval
趋势折线图、面积图linearea
占比饼图、环形图interval + theta
分布直方图、箱线图rectboxplot
相关散点图、气泡图point
层级矩形树图、分区图、旭日图treemappartitionsunburst(需扩展包)

详细文档: 图表类型选择指南

5.2 视觉通道 / Visual Channels

视觉通道是数据到视觉属性的映射方式:

通道类型适合数据感知精度
位置连续/离散最高
长度连续
颜色(色相)离散
颜色(亮度)连续
大小连续中低
形状离散

详细文档: 视觉通道

5.3 配色理论 / Color Theory

选择合适的配色方案提升图表可读性:

场景推荐方案示例
分类数据离散色板category10category20
连续数据顺序色板BluesRdYlBu
正负对比发散色板RdYlGn

详细文档: 配色理论


6. Marks / 图表类型

Marks 是 G2 的核心可视化元素,决定了数据的视觉表现形式。每种 Mark 适合特定的数据类型和可视化场景。

6.1 柱状图系列 / Interval

柱状图用于比较分类数据的大小,是最常用的图表类型。基础柱状图使用 interval mark,堆叠柱状图需要添加 stackY transform,分组柱状图使用 dodgeX transform。

类型Mark关键配置
基础柱状图interval-
堆叠柱状图intervaltransform: [{ type: 'stackY' }]
分组柱状图intervaltransform: [{ type: 'dodgeX' }]
百分比柱状图intervaltransform: [{ type: 'normalizeY' }]
水平柱状图intervalcoordinate: { transform: [{ type: 'transpose' }] }

详细文档: 基础柱状图 | 堆叠柱状图 | 分组柱状图 | 百分比柱状图

6.2 折线图系列 / Line

折线图用于展示数据随时间或有序类别的变化趋势。支持单线、多线对比,以及不同插值方式。

类型Mark关键配置
基础折线图line-
多系列折线lineencode: { color: 'category' }
平滑曲线lineencode: { shape: 'smooth' }
阶梯线lineencode: { shape: 'step' }

详细文档: 基础折线图 | 多系列折线 | LineX/LineY

6.3 面积图系列 / Area

面积图在折线图基础上填充区域,强调数量随时间的变化程度。堆叠面积图用于展示各部分对整体的贡献。

类型Mark关键配置
基础面积图area-
堆叠面积图areatransform: [{ type: 'stackY' }]

详细文档: 基础面积图 | 堆叠面积图

6.4 饼图/环形图 / Arc (Pie/Donut)

饼图用于展示各部分占整体的比例关系。使用 theta 坐标系配合 interval mark 实现。

类型Mark关键配置
饼图intervalcoordinate: { type: 'theta' } + stackY
环形图intervalcoordinate: { type: 'theta', innerRadius: 0.6 }

详细文档: 饼图 | 环形图

6.5 散点图/气泡图 / Point

散点图用于展示两个数值变量的关系,气泡图通过点的大小展示第三个维度。

类型Mark关键配置
散点图pointencode: { x, y }
气泡图pointencode: { x, y, size }

详细文档: 散点图 | 气泡图

6.6 直方图 / Histogram

直方图用于展示连续数值数据的分布情况,使用 rect 标记配合 binX 转换实现。与柱状图不同,直方图的柱子之间无间隔,表示数据连续。

类型Mark关键配置
基础直方图recttransform: [{ type: 'binX', y: 'count' }]
多分布对比rectgroupBy 分组

详细文档: 直方图

6.7 玫瑰图/玉珏图 / Polar Charts

极坐标系下的图表,通过半径或弧长表示数值大小,视觉上更加美观。

类型Mark关键配置
玫瑰图intervalcoordinate: { type: 'polar' }
玉珏图intervalcoordinate: { type: 'radial' }

详细文档: 玫瑰图 | 玉珏图

6.8 统计分布图 / Distribution

展示数据分布特征的图表,适用于统计分析和探索性数据分析。

类型Mark用途
箱线图boxplot数据分布统计
箱型图(Box)box手动指定五数概括的箱线图
密度图density核密度估计曲线
小提琴图density + boxplot密度分布 + 统计信息
分布曲线图line + smooth频率密度分布曲线
多边形polygon自定义多边形区域
等高线图cell + sequential色阶二维连续数据分布(模拟等高线)

详细文档: 箱线图 | 箱型图(Box) | 密度图 | 小提琴图 | 分布曲线图 | 等高线图 | 多边形

6.9 关系图 / Relation

展示数据之间关系的图表,适用于网络分析和集合关系展示。

类型Mark用途
桑基图sankey流向/转移关系
和弦图chord矩阵流向关系
韦恩图path + venn数据变换集合交集关系(venn 是 data transform,不是 mark type)
弧长连接图line + point节点链接关系

详细文档: 桑基图 | 和弦图 | 韦恩图 | 弧长连接图

6.10 项目管理图 / Project

适用于项目管理和进度跟踪的图表。

类型Mark用途
甘特图interval任务时间安排
子弹图interval + pointKPI 指标展示

详细文档: 甘特图 | 子弹图

6.11 金融图表 / Finance

适用于金融数据分析的专业图表。

类型Mark用途
K线图link + interval股票四价数据

详细文档: K线图

6.12 多维数据图 / Multivariate

展示多维数据关系的图表。

类型Mark用途
平行坐标系line多维数据关系分析
雷达图line多维数据对比

详细文档: 平行坐标系 | 雷达图

6.13 对比图 / Comparison

适用于数据对比的图表。

类型Mark用途
双向柱状图interval正负数据对比
漏斗图interval + shape:'funnel'业务转化率展示
马赛克图interval/cell + transform二维分类分布

详细文档: 双向柱状图 | 漏斗图 | 马赛克图

6.14 基础标记 / Basic Marks

基础标记是 G2 的底层构建块,可独立使用或组合构建复杂图表。

类型Mark用途
矩形rect矩形区域,直方图/热力图基础
文本text文本标注和标签
图片image图片标记,数据点用图片表示
路径path自定义路径绘制
链接link两点之间的连线
连接器connector数据点之间的连接线
形状shape自定义形状绘制
向量vector向量/箭头标记,风场图等

详细文档: rect | text | image | path | link | connector | shape | vector

6.15 范围标记 / Range

范围标记用于展示数据的区间范围。

类型Mark用途
时间段/区间高亮(X 方向)rangeXX 轴区间,encode: { x: 'start', x1: 'end' }
数值范围高亮(Y 方向)rangeYY 轴区间,encode: { y: 'min', y1: 'max' }
二维矩形区域rangex/y 字段为 [start,end] 数组,encode: { x:'x', y:'y' },极少使用

详细文档: range/rangeY | rangeX

6.16 分布与打包图 / Distribution & Pack

类型Mark用途
蜂群图point + pack数据点紧密排列展示分布
打包图pack层级数据的圆形打包

详细文档: 蜂群图 | 打包图

6.17 层次结构图 / Hierarchy

展示层级数据的图表,通过面积或半径表示数值占比。

类型Mark用途
矩形树图treemap层级数据占比
旭日图sunburst⚠️多层级同心圆展示(需引入 @antv/g2-extension-plot)
分区图partition层级数据分区展示
树图tree树形层级结构

详细文档: 矩形树图 | 旭日图 | 分区图 | 树图

6.18 其他图表 / Others

类型Mark用途
热力图cell二维矩阵数据可视化
密度热力图heatmap连续密度热力图
仪表盘gauge指标进度展示
词云wordCloud文本频率可视化
水波图liquid百分比进度
螺旋图line/interval + helix坐标系大量时间序列的周期性规律

详细文档: 热力图 | 密度热力图 | 仪表盘 | 词云 | 水波图 | 螺旋图


7. Data / 数据变换

数据变换在数据加载阶段执行,配置在 data.transform 中,影响所有使用该数据的标记。

7.1 Data Transform 类型(配置在 data.transform

变换类型用途示例场景
foldfold宽表转长表多列数据转多系列
filterfilter条件过滤数据过滤无效数据
sortsort使用回调函数排序自定义排序逻辑
sortBysortBy按字段排序按字段值排序
mapmap数据映射转换添加计算字段
joinjoin合并数据表关联外部数据
pickpick选择指定字段精简字段
renamerename重命名字段字段重命名
sliceslice截取数据范围分页/截取
emaema指数移动平均时间序列平滑
kdekde核密度估计密度图/小提琴图
loglog打印数据到控制台调试
customcustom自定义数据处理复杂转换

7.2 数据格式与模式

类型用途
表格数据格式G2 接受的标准表格数据格式说明
数据变换模式Data Transform 和 Mark Transform 的组合使用模式

详细文档: filter | sort | sortBy | fold | slice | ema | kde | log | fetch | 数据变换模式

7.3 常见错误:Data Transform 放错位置

// ❌ 错误:fold 是数据变换,不能放在 mark transform
chart.options({
  type: 'interval',
  data: wideData,
  transform: [{ type: 'fold', fields: ['a', 'b'] }],  // ❌ 错误!
});

// ✅ 正确:fold 放在 data.transform
chart.options({
  type: 'interval',
  data: {
    type: 'inline',
    value: wideData,
    transform: [{ type: 'fold', fields: ['a', 'b'] }],  // ✅ 正确
  },
  transform: [{ type: 'stackY' }],  // mark transform
});

7.4 组合示例:宽表数据 + 堆叠图

// 宽表数据:每个月有多个类型的数据列
const wideData = [
  { year: '2000', '类型 A': 21, '类型 B': 16, '类型 C': 8 },
  { year: '2001', '类型 A': 25, '类型 B': 16, '类型 C': 8 },
  // ...
];

chart.options({
  type: 'interval',
  data: {
    type: 'inline',
    value: wideData,
    transform: [
      // ✅ Data Transform:宽表转长表
      { type: 'fold', fields: ['类型 A', '类型 B', '类型 C'], key: 'type', value: 'value' },
    ],
  },
  encode: { x: 'year', y: 'value', color: 'type' },
  transform: [
    // ✅ Mark Transform:堆叠
    { type: 'stackY' },
  ],
  coordinate: { type: 'polar' },  // 极坐标系
});

8. Transforms / 标记变换

标记变换在绑定视觉通道时执行,配置在 mark 的 transform 数组中,用于数据聚合、防重叠等。

配置位置transform 数组,与 dataencode 同级,不是data.transform 中。

chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'type' },
  transform: [  // ✅ Mark Transform:与 data/encode 同级
    { type: 'stackY' },
    { type: 'sortX', by: 'y' },
  ],
});

8.1 防重叠变换 / Anti-overlap

变换类型用途
堆叠stackY数据堆叠,用于堆叠图
分组dodgeX数据分组,用于分组图
抖动jitter散点抖动避免重叠
X轴抖动jitterXX 方向抖动
Y轴抖动jitterYY 方向抖动
打包pack紧密排列数据点

详细文档: stackY | dodgeX | jitter | jitterX | jitterY | pack

8.2 聚合变换 / Aggregation

变换类型用途
通用分组group通用分组聚合
分组聚合groupX / groupY按维度分组并聚合
分组颜色groupColor按颜色分组聚合
分箱bin二维分箱
X轴分箱binXX 轴方向分箱
采样sample数据采样

详细文档: group | groupX | groupY | groupColor | bin | binX | sample

8.3 排序变换 / Sorting

变换类型用途
X轴排序sortX按 X 通道排序
Y轴排序sortY按 Y 通道排序
颜色排序sortColor按颜色通道排序

详细文档: sortX | sortY | sortColor

8.4 选取变换 / Selection

变换类型用途
选取select全局选取数据
X轴选取selectX按 X 分组选取
Y轴选取selectY按 Y 分组选取

详细文档: select | selectX | selectY

8.5 其他变换 / Others

变换类型用途
归一化normalizeYY 轴归一化
差值diffY计算差值
对称symmetryYY 轴对称
弹性XflexXX 轴弹性布局
堆叠入场stackEnter入场动画堆叠

详细文档: normalizeY | diffY | symmetryY | flexX | stackEnter


9. Interactions / 交互

G2 提供丰富的内置交互,用于数据探索和图表操作。

9.1 选择类交互 / Selection

交互类型用途
元素选择elementSelect点击选择数据元素
按条件选择elementSelectBy按条件批量选择元素
框选brush / brushX / brushY矩形区域选择
二维框选brushXYXY 同时框选
轴框选brushAxis坐标轴范围选择
图例过滤legendFilter点击图例筛选数据

详细文档: elementSelect | elementSelectBy | brush | brushXY | brushAxis | legendFilter

9.2 高亮类交互 / Highlight

交互类型用途
元素高亮elementHighlight悬停高亮元素
按条件高亮elementHighlightBy按条件批量高亮元素
悬停缩放elementHoverScale悬停时元素放大
图例高亮legendHighlight悬停图例高亮对应元素
框选高亮brushXHighlight / brushYHighlight框选区域高亮

详细文档: elementHighlight | elementHighlightBy | elementHoverScale | legendHighlight | brushXHighlight | brushYHighlight | 单轴框选高亮

9.3 过滤类交互 / Filter

交互类型用途
滑动条过滤sliderFilter滑动条筛选数据范围
滚动条过滤scrollbarFilter滚动条筛选数据
框选过滤brushFilter框选区域过滤数据
X轴框选过滤brushXFilterX 轴方向框选过滤
Y轴框选过滤brushYFilterY 轴方向框选过滤
自适应过滤adaptiveFilter自适应数据过滤

详细文档: sliderFilter | scrollbarFilter | brushFilter | brushXFilter | brushYFilter | adaptiveFilter

9.4 其他交互 / Others

交互类型用途
提示信息tooltip悬停显示数据详情
气泡提示poptip简洁气泡提示
下钻drilldown层级数据下钻
矩形树图下钻treemapDrilldown矩形树图层级下钻
缩放fisheye鱼眼放大镜效果
滚轮滑动sliderWheel鼠标滚轮控制滑动条
拖拽移动elementPointMove拖拽数据点移动
图表索引chartIndex多图表联动索引线

详细文档: tooltip | poptip | drilldown | treemapDrilldown | fisheye | sliderWheel | elementPointMove | chartIndex


10. Components / 组件

组件是图表的辅助元素,如坐标轴、图例、提示信息等。

10.1 坐标轴 / Axis

坐标轴展示数据维度,支持丰富的样式配置。

详细文档: 坐标轴配置 | 雷达图坐标轴

10.2 图例 / Legend

图例展示数据分类或连续数值映射,支持分类图例和连续图例(色带)。

类型用途
分类图例离散分类数据的颜色映射说明
连续图例连续数值的颜色/大小映射说明(色带)

详细文档: 图例配置 | 分类图例 | 连续图例

10.3 提示信息 / Tooltip

Tooltip 在悬停时显示数据详情,支持自定义模板和格式化。

详细文档: Tooltip 配置

10.4 其他组件 / Others

组件用途
标题图表标题
标签数据标签
滚动条数据滚动浏览
滑动条数据范围选择
标注数据标注和辅助线

详细文档: 标题 | 标签 | 滚动条 | 滑动条 | 标注


11. Scales / 比例尺

比例尺将数据值映射到视觉通道,如位置、颜色、大小等。

11.1 ⚠️ 默认行为(不要过度指定 type)

G2 会根据数据类型自动推断 scale 类型,非特殊情况下不要手动指定 type:

数据类型自动推断的 scale示例
数值字段linear{ value: 100 } → linear
分类字段band{ category: 'A' } → band
Date 对象time{ date: new Date() } → time
// ❌ 错误:不必要的 type 指定,可能导致渲染异常
chart.options({
  scale: {
    x: { type: 'linear' },  // ❌ 数值字段默认就是 linear
    y: { type: 'linear' },  // ❌ 不需要指定
  },
});

// ✅ 正确:让 G2 自动推断,只在需要时配置 domain/range
chart.options({
  scale: {
    y: { domain: [0, 100] },  // ✅ 只配置需要的属性
    color: { range: ['#1890ff', '#52c41a'] },
  },
});

需要手动指定 type 的特殊情况:

场景type说明
对数刻度log跨数量级数据
幂函数刻度pow非线性数据映射
平方根刻度sqrt非负数据的压缩
字符串日期time日期字段是字符串而非 Date 对象时
自定义映射ordinal离散值到离散值
渐变色sequential连续数值到颜色渐变
分段映射threshold按阈值分段映射到颜色
等量分段quantize / quantile连续数据离散化

11.2 比例尺类型

比例尺类型用途
线性linear连续数值映射(默认)
分类band离散分类映射(默认)
point离散点位置映射
时间time时间数据映射
对数log对数刻度
幂/平方根pow / sqrt幂函数/平方根映射
序数ordinal离散值到离散值映射
顺序sequential连续值到颜色渐变
分位数/量化quantile / quantize连续数据离散化映射
阈值threshold按阈值分段映射

详细文档: linear | band | point | time | log | pow/sqrt | ordinal | sequential | quantile/quantize | threshold


12. Coordinates / 坐标系

坐标系定义数据到画布位置的映射方式,不同坐标系产生不同图表形态。

坐标系类型用途
笛卡尔cartesian直角坐标系(默认)
极坐标polar雷达图、玫瑰图
Thetatheta饼图、环形图
径向radial径向坐标系,玉珏图
转置transposeX/Y 轴交换
平行parallel平行坐标系
螺旋helix螺旋坐标系
鱼眼fisheye局部放大效果

详细文档: cartesian | polar | theta | radial | transpose | parallel | helix | fisheye


13. Compositions / 组合视图

组合视图用于创建多图表布局,如分面、多视图叠加等。

组合类型用途
基础视图view单视图容器,组合多个 mark
分面图facetRect按维度拆分矩形网格多图
圆形分面facetCircle按维度拆分环形多图
重复矩阵repeatMatrix多变量组合矩阵图
空间层叠spaceLayer多图层叠加
空间弹性spaceFlex弹性布局
时间关键帧timingKeyframe动画序列
地理视图geoView地理坐标系视图
地图geoPath地理路径绘制

详细文档: view | facetRect | facetCircle | repeatMatrix | spaceLayer | spaceFlex | timingKeyframe | geoView | 地图


14. Themes / 主题

主题定义图表的整体视觉风格,包括颜色、字体、间距等。

详细文档: 内置主题 | 自定义主题


15. Palettes / 调色板

调色板定义颜色序列,用于分类数据或连续数据的颜色映射。

详细文档: category10 | category20


16. Animations / 动画

动画增强图表的表现力,支持入场、更新、退场动画配置。

⚠️ 重要规则:G2 底层自带默认动画效果,用户没有明确要求动画时不要添加 animate 配置。只有用户明确描述了动画需求(如"渐入动画"、"波浪入场"等)时,才查阅参考文档添加对应的 animate 配置。

详细文档: 动画介绍 | 动画类型 | 关键帧动画


17. Label Transforms / 标签变换

标签变换用于处理标签重叠、溢出等问题,提升标签可读性。

变换类型用途
溢出隐藏overflowHide超出区域的标签隐藏
重叠隐藏overlapHide重叠标签自动隐藏
重叠偏移overlapDodgeY重叠标签 Y 方向偏移
对比反转contrastReverse标签颜色自动反转以保证对比度
溢出调整exceedAdjust超出画布边界的标签位置调整
溢出描边overflowStroke溢出区域添加描边标记

详细文档: overflowHide | overlapHide | overlapDodgeY | contrastReverse | exceedAdjust | overflowStroke


18. Patterns / 模式

模式是常见场景的最佳实践,包含迁移指南、性能优化、响应式适配等。

18.1 迁移指南 / Migration (v4 → v5)

v4 (Deprecated)v5 (Correct)
chart.source(data)chart.options({ data })
.position('x*y')encode: { x: 'x', y: 'y' }
.color('field')encode: { color: 'field' }
.adjust('stack')transform: [{ type: 'stackY' }]
.adjust('dodge')transform: [{ type: 'dodgeX' }]
label: {}labels: [{}]

详细文档: v4 → v5 迁移

18.2 性能优化 / Performance

数据预聚合、LTTB 降采样、Canvas 渲染器确认、高频实时数据节流更新。

场景数据量建议方案
折线图< 1,000 点直接渲染
折线图1,000 ~ 10,000 点降采样到 500 点以内
折线图> 10,000 点后端聚合 + 时间范围过滤
散点图< 5,000 点直接渲染
散点图5,000 ~ 50,000 点Canvas 渲染 + 降采样

详细文档: 性能优化

18.3 响应式适配 / Responsive

autoFit 自适应、ResizeObserver 动态调整、移动端字体/边距适配。

详细文档: 响应式适配


附带文件

references/animations/g2-animation-intro.md
---
id: "g2-animation-intro"
title: "G2 动画系统总览(animate 配置)"
description: |
  G2 v5 动画系统通过 animate 属性配置,支持入场(enter)、更新(update)、退场(exit)三种时机。
  内置动画类型包括 fadeIn/Out、scaleInX/Y、growInX/Y、waveIn、zoomIn/Out、morphing、pathIn。
  每种动画可配置 duration(时长)、delay(延迟)、easing(缓动函数)。

library: "g2"
version: "5.x"
category: "animations"
tags:
  - "animation"
  - "动画"
  - "animate"
  - "入场动画"
  - "fadeIn"
  - "scaleInX"
  - "waveIn"

related:
  - "g2-animation-keyframe"
  - "g2-core-chart-init"

use_cases:
  - "图表首次渲染时添加入场动画提升视觉体验"
  - "数据更新时添加过渡动画"
  - "退场时添加淡出效果"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/animate"
---

## 内置动画类型速查

| 动画名 | 效果 | 适合场景 |
|--------|------|---------|
| `fadeIn` | 从透明到不透明 | 通用入场 |
| `fadeOut` | 从不透明到透明 | 通用退场 |
| `scaleInX` | 从 X 轴起点缩放展开 | 柱状图入场 |
| `scaleInY` | 从 Y 轴底部缩放展开 | 柱状图入场(竖向) |
| `scaleOutX` | 向 X 轴收缩消失 | 柱状图退场 |
| `scaleOutY` | 向 Y 轴收缩消失 | 柱状图退场 |
| `growInX` | 从左向右生长 | 条形图、折线图入场 |
| `growInY` | 从下向上生长 | 柱状图入场 |
| `waveIn` | 波浪扫描入场 | 极坐标图(玫瑰图、饼图) |
| `zoomIn` | 从中心缩放放大 | 点图入场 |
| `zoomOut` | 向中心缩小消失 | 点图退场 |
| `pathIn` | 路径逐步绘制 | 折线图、路径图 |
| `morphing` | 形状变形过渡 | 图表类型切换 |

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data: [
    { genre: 'Sports', sold: 275 },
    { genre: 'Strategy', sold: 115 },
    { genre: 'Action', sold: 120 },
    { genre: 'RPG', sold: 98 },
  ],
  encode: { x: 'genre', y: 'sold', color: 'genre' },
  animate: {
    enter: {
      type: 'growInY',    // 入场动画:从下向上生长
      duration: 800,      // 持续时间(毫秒)
      delay: 0,           // 延迟
      easing: 'ease-out', // 缓动函数
    },
  },
});

chart.render();
```

## 配置动画的三个时机

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'x', y: 'y', color: 'type' },
  animate: {
    // 入场:图表首次渲染时
    enter: {
      type: 'scaleInY',
      duration: 1000,
      easing: 'ease-out-bounce',
    },
    // 更新:数据变化时
    update: {
      type: 'morphing',
      duration: 500,
    },
    // 退场:图元被移除时
    exit: {
      type: 'fadeOut',
      duration: 300,
    },
  },
});
```

## 禁用动画

```javascript
// 禁用所有动画
chart.options({
  animate: false,
});

// 仅禁用入场动画
chart.options({
  animate: {
    enter: false,
  },
});
```

## 常见动画组合推荐

```javascript
// 柱状图:growInY 入场
animate: { enter: { type: 'growInY', duration: 800 } }

// 折线图:pathIn 入场(路径绘制效果)
animate: { enter: { type: 'pathIn', duration: 1200 } }

// 饼图(极坐标):waveIn 入场
animate: { enter: { type: 'waveIn', duration: 1000 } }

// 散点图:zoomIn 入场
animate: { enter: { type: 'zoomIn', duration: 600 } }

// 通用淡入
animate: { enter: { type: 'fadeIn', duration: 500 } }
```

## 常见错误与修正

### 错误 1:animate.enter 写成字符串
```javascript
// ❌ 错误:enter 不是字符串,是对象
chart.options({
  animate: { enter: 'fadeIn' },  // ❌
});

// ✅ 正确
chart.options({
  animate: { enter: { type: 'fadeIn', duration: 600 } },  // ✅
});
```

### 错误 2:在极坐标图用非极坐标动画
```javascript
// ❌ scaleInX/Y 在极坐标中效果不对
chart.options({
  coordinate: { type: 'theta' },
  animate: { enter: { type: 'scaleInY' } },  // ❌ 饼图应该用 waveIn
});

// ✅ 极坐标图推荐 waveIn
chart.options({
  coordinate: { type: 'theta' },
  animate: { enter: { type: 'waveIn', duration: 1000 } },  // ✅
});
```
references/animations/g2-animation-keyframe.md
---
id: "g2-animation-keyframe"
title: "G2 关键帧动画(timingKeyframe)"
description: |
  timingKeyframe 是 G2 v5 的组合类型,将多个图表视图按时序播放,
  实现数据故事讲述(data storytelling)效果。
  每个子视图是一个"关键帧",系统自动在帧间插值过渡,支持形变动画(morphing)。

library: "g2"
version: "5.x"
category: "animations"
tags:
  - "timingKeyframe"
  - "关键帧"
  - "数据故事"
  - "keyframe"
  - "morphing"
  - "动画"
  - "composition"

related:
  - "g2-animation-intro"
  - "g2-core-view-composition"

use_cases:
  - "演示数据如何从一种图表类型变为另一种(柱状图 → 折线图)"
  - "展示数据随时间的演变过程"
  - "数据新闻和可视化故事讲述"

difficulty: "advanced"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/composition/timing-keyframe"
---

## 最小可运行示例(柱状图 → 折线图)

```javascript
import { Chart } from '@antv/g2';

const data = [
  { month: 'Jan', value: 83 },
  { month: 'Feb', value: 60 },
  { month: 'Mar', value: 95 },
  { month: 'Apr', value: 72 },
  { month: 'May', value: 110 },
];

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'timingKeyframe',   // 关键帧组合类型
  duration: 1000,           // 每帧过渡时长(毫秒)
  iterationCount: 2,        // 循环次数('infinite' 为无限循环)
  direction: 'alternate',   // 'normal' | 'reverse' | 'alternate' | 'reverse-alternate'
  easing: 'ease-in-out-sine',
  children: [
    // 关键帧 1:柱状图
    {
      type: 'interval',
      data,
      encode: { x: 'month', y: 'value', color: 'month' },
      axis: { y: { title: '月份销量' } },
    },
    // 关键帧 2:折线图(自动在两者之间插值动画)
    {
      type: 'line',
      data,
      encode: { x: 'month', y: 'value' },
      style: { lineWidth: 3 },
    },
  ],
});

chart.render();
```

## 多关键帧(数据更新动画)

```javascript
chart.options({
  type: 'timingKeyframe',
  duration: 800,
  iterationCount: 'infinite',
  direction: 'alternate',
  children: [
    // 关键帧 1:2022 年数据
    {
      type: 'interval',
       data2022,
      encode: { x: 'city', y: 'gdp', color: 'city' },
      title: '2022 年 GDP',
    },
    // 关键帧 2:2023 年数据(相同字段,自动形变过渡)
    {
      type: 'interval',
      data: data2023,
      encode: { x: 'city', y: 'gdp', color: 'city' },
      title: '2023 年 GDP',
    },
  ],
});
```

## 配置项

```javascript
chart.options({
  type: 'timingKeyframe',
  duration: 1000,                  // 关键帧间过渡时长(毫秒),默认 1000
  iterationCount: 1,               // 循环次数,默认 1;'infinite' 无限循环
  direction: 'normal',             // 播放方向:
                                   //   'normal' - 正向
                                   //   'reverse' - 反向
                                   //   'alternate' - 正反交替
                                   //   'reverse-alternate' - 反正交替
  easing: 'ease-in-out-sine',     // 缓动函数,默认 'ease-in-out-sine'
  children: [/* 各关键帧视图配置 */],
});
```

## 常见错误与修正

### 错误 1:children 帧的 encode 字段名不一致——无法形变
```javascript
// ❌ 字段名不一致,无法识别对应关系,形变效果丢失
children: [
  { type: 'interval', encode: { x: 'month', y: 'sales' } },   // sales
  { type: 'line',     encode: { x: 'month', y: 'revenue' } }, // revenue ❌ 名字不同
]

// ✅ 相同字段名才能实现平滑形变
children: [
  { type: 'interval', encode: { x: 'month', y: 'value' } },
  { type: 'line',     encode: { x: 'month', y: 'value' } },  // ✅ 同名字段
]
```

### 错误 2:iterationCount 写成数字字符串
```javascript
// ❌ 错误:应该是字符串 'infinite',不是数字
chart.options({ iterationCount: Infinity });  // ❌

// ✅ 正确
chart.options({ iterationCount: 'infinite' });  // ✅
chart.options({ iterationCount: 3 });           // ✅ 或具体数字
```
references/animations/g2-animation-types.md
---
id: "g2-animation-types"
title: "G2 内置动画类型详解(fadeIn/scaleIn/growIn/pathIn/waveIn/zoomIn/morphing)"
description: |
  G2 v5 内置多种动画类型,每种适用于不同的 Mark 和坐标系:
  fadeIn/Out(渐显渐隐)、scaleInX/Y(缩放展开)、growInX/Y(生长入场)、
  pathIn(路径绘制)、waveIn(极坐标波浪入场)、zoomIn/Out(缩放点入场)、morphing(形变过渡)。
  通过 animate.enter.type 等配置使用。

library: "g2"
version: "5.x"
category: "animations"
tags:
  - "fadeIn"
  - "scaleInX"
  - "scaleInY"
  - "growInX"
  - "growInY"
  - "pathIn"
  - "waveIn"
  - "zoomIn"
  - "zoomOut"
  - "morphing"
  - "动画类型"

related:
  - "g2-animation-intro"
  - "g2-animation-keyframe"

use_cases:
  - "按图表类型选择最合适的入场动画"
  - "折线图路径绘制动画"
  - "饼图/玫瑰图波浪入场"
  - "数据更新时的形变过渡"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/animate"
---

## 动画类型与适用场景

| 动画名 | 方向 | 最适合 Mark | 特点 |
|--------|------|------------|------|
| `fadeIn` | - | 所有 Mark | 渐显,通用,最安全 |
| `fadeOut` | - | 所有 Mark | 渐隐,退场通用 |
| `scaleInX` | X 轴 | interval(柱状图) | 从左上角向右扩展 |
| `scaleInY` | Y 轴 | interval(柱状图) | 从底部向上缩放 |
| `scaleOutX` | X 轴 | interval | scaleInX 的退场版本 |
| `scaleOutY` | Y 轴 | interval | scaleInY 的退场版本 |
| `growInX` | X 轴 | line, area, interval(直角坐标) | 裁剪从左向右生长 |
| `growInY` | Y 轴 | interval, area(直角坐标) | 裁剪从底部向上生长;**极坐标/helix 禁用** |
| `pathIn` | 路径 | line, path, link | 路径线条逐步绘制 |
| `waveIn` | 波浪 | interval(极坐标) | 极坐标专用扇形展开 |
| `zoomIn` | 中心 | point, text | 从中心缩放放大 |
| `zoomOut` | 中心 | point, text | 向中心缩小消失 |
| `morphing` | 形变 | 所有 Mark | 形状平滑变形过渡 |

## fadeIn / fadeOut(渐显渐隐)

```javascript
// 最通用的动画,适合任何 mark
chart.options({
  type: 'point',
  data,
  encode: { x: 'x', y: 'y' },
  animate: {
    enter: { type: 'fadeIn', duration: 600 },
    exit: { type: 'fadeOut', duration: 300 },
  },
});
```

## scaleInY / growInY(柱状图入场)

```javascript
// scaleInY:缩放展开(有缩放感)
// growInY:裁剪生长(有"从地面长出来"的感觉,更自然)
chart.options({
  type: 'interval',
  data,
  encode: { x: 'genre', y: 'sold' },
  animate: {
    // 方式一:缩放
    enter: { type: 'scaleInY', duration: 800, easing: 'ease-out' },
    // 方式二:生长(推荐)
    // enter: { type: 'growInY', duration: 800 },
  },
});
```

## pathIn(折线图路径绘制)

```javascript
// pathIn:折线/路径从左向右逐步绘制
chart.options({
  type: 'line',
  data: timeSeriesData,
  encode: { x: 'date', y: 'value', color: 'type' },
  animate: {
    enter: {
      type: 'pathIn',      // 路径逐步绘制
      duration: 1500,
      easing: 'linear',    // 匀速绘制效果更佳
    },
  },
});
```

## waveIn(极坐标/饼图专用)

```javascript
// waveIn:从外圈向内的波浪扫入,专为极坐标设计
chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: { type: 'theta', outerRadius: 0.8 },
  animate: {
    enter: {
      type: 'waveIn',       // 极坐标专用
      duration: 1000,
    },
  },
});
```

## zoomIn / zoomOut(点图缩放)

```javascript
// zoomIn:散点从中心缩放出现
chart.options({
  type: 'point',
  data: scatterData,
  encode: { x: 'x', y: 'y', size: 'value' },
  animate: {
    enter: { type: 'zoomIn', duration: 500 },
    exit: { type: 'zoomOut', duration: 300 },
  },
});
```

## morphing(形变更新动画)

```javascript
// morphing:数据更新时图形平滑变形
chart.options({
  type: 'interval',
  data,
  encode: { x: 'genre', y: 'sold' },
  animate: {
    update: {
      type: 'morphing',    // 数据更新时形变过渡
      duration: 600,
    },
  },
});

// 也可以在 timingKeyframe 中自动触发形变
chart.options({
  type: 'timingKeyframe',
  children: [
    { type: 'interval', data, encode: { x: 'x', y: 'y' } },
    { type: 'line',     data, encode: { x: 'x', y: 'y' } },
  ],
});
```

## 按图表类型推荐的动画

```javascript
// 柱状图(推荐 growInY)
{ type: 'interval', animate: { enter: { type: 'growInY', duration: 800 } } }

// 条形图(推荐 growInX)
{ type: 'interval', coordinate: { transform: [{ type: 'transpose' }] },
  animate: { enter: { type: 'growInX', duration: 800 } } }

// 折线图(推荐 pathIn)
{ type: 'line', animate: { enter: { type: 'pathIn', duration: 1200 } } }

// 散点图(推荐 zoomIn 或 fadeIn)
{ type: 'point', animate: { enter: { type: 'zoomIn', duration: 400 } } }

// 饼图/环形图(推荐 waveIn)
{ type: 'interval', coordinate: { type: 'theta' },
  animate: { enter: { type: 'waveIn', duration: 1000 } } }

// 面积图(推荐 fadeIn 或 growInX)
{ type: 'area', animate: { enter: { type: 'fadeIn', duration: 800 } } }

// 螺旋图 helix 坐标系(必须用 fadeIn,禁止用 growInX/Y)
{ type: 'interval', coordinate: { type: 'helix', ... },
  animate: { enter: { type: 'fadeIn', duration: 800 } } }
```

## 常见错误与修正

### 错误 1:在条形图(转置)上用 scaleInY
```javascript
// ❌ 条形图是水平方向,用 scaleInY(竖向缩放)效果不对
chart.options({
  type: 'interval',
  coordinate: { transform: [{ type: 'transpose' }] },
  animate: { enter: { type: 'scaleInY' } },  // ❌ 应该用 growInX 或 scaleInX
});

// ✅ 条形图用 X 方向动画
chart.options({
  animate: { enter: { type: 'growInX', duration: 800 } },  // ✅
});
```

### 错误 2:在 helix(螺旋)坐标系上用 growInX/growInY

`growInX` / `growInY` 的实现是沿直角坐标轴方向做 **clipPath 裁剪**。在 `helix` 坐标系中,坐标轴被重映射为螺旋路径,屏幕上不存在"底部"或"左侧"基线,裁剪矩形会横穿螺旋形,导致部分螺旋区域被切掉或渲染残缺,动画结束后图表也可能显示不完整。

**同样问题适用于所有非直角坐标系**(`polar`、`theta`、`helix`)——这些坐标系均应使用 `waveIn`(极坐标专用)或 `fadeIn`(通用),不能使用 `growInX/Y`。

```javascript
// ❌ 错误:helix 坐标系用 growInY → 裁剪矩形横穿螺旋,图表渲染残缺
chart.options({
  type: 'interval',
  coordinate: { type: 'helix', startAngle: 0, endAngle: Math.PI * 6 },
  animate: {
    enter: { type: 'growInY', duration: 2000 },  // ❌ 螺旋被裁剪,部分区域缺失
  },
});

// ✅ 正确:helix 坐标系用 fadeIn
chart.options({
  type: 'interval',
  coordinate: { type: 'helix', startAngle: 0, endAngle: Math.PI * 6 },
  animate: {
    enter: { type: 'fadeIn', duration: 1000 },   // ✅ 渐显,无裁剪副作用
  },
});

// ✅ 极坐标(theta/polar)用 waveIn
chart.options({
  type: 'interval',
  coordinate: { type: 'theta' },
  animate: {
    enter: { type: 'waveIn', duration: 1000 },   // ✅ 极坐标专用扇形展开
  },
});
```

**根本原因**:`growInX/Y` 假设存在固定的直角基线(X=0 或 Y=0)作为裁剪起点,这在笛卡尔坐标系中成立;但 `helix` / `polar` 将坐标重映射到极坐标或螺旋路径后,该基线不再对应可见边界,裁剪结果是任意截断螺旋形状。
references/components/g2-comp-annotation.md
---
id: "g2-comp-annotation"
title: "G2 标注(Annotation)"
description: |
  在 G2 v5 中,标注通过额外的 Mark(text、line、image 等)叠加在图表上实现,
  常见的有文字标注、参考线(reference line)、参考区间(reference area)。
  本文采用 Spec 模式的 view + children 方式组合标注。

library: "g2"
version: "5.x"
category: "components"
tags:
  - "annotation"
  - "标注"
  - "参考线"
  - "reference line"
  - "文字标注"
  - "lineX"
  - "lineY"
  - "spec"

related:
  - "g2-core-view-composition"
  - "g2-comp-axis-config"

use_cases:
  - "在图表中添加平均线、目标线"
  - "标注特殊数据点(最大值、最小值)"
  - "添加参考区间背景色"

difficulty: "intermediate"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/extra-topics/annotation"
---

## 水平参考线(lineY)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'view',
  data,
  children: [
    // 主图:折线图
    {
      type: 'line',
      encode: { x: 'month', y: 'value' },
    },
    // 标注:y=60 的水平参考线
    {
      type: 'lineY',
      data: [60],
      style: {
        stroke: '#f5222d',
        strokeDasharray: '4 4',
        lineWidth: 1.5,
      },
      labels: [
        {
          text: '目标值: 60',
          position: 'right',
          style: { fill: '#f5222d', fontSize: 11 },
        },
      ],
    },
  ],
});

chart.render();
```

## 垂直参考线(lineX)

```javascript
// 标记某个特殊时间点
{
  type: 'lineX',
  data: [new Date('2024-03-01')],
  style: { stroke: '#722ed1', strokeDasharray: '4 4', lineWidth: 1.5 },
  labels: [
    { text: '版本发布', position: 'top', style: { fill: '#722ed1' } },
  ],
}
```

## 标注最大值点

```javascript
chart.options({
  type: 'view',
  data,
  children: [
    { type: 'line', encode: { x: 'month', y: 'value' } },
    {
      // 用 point + text 标注最大值
      type: 'point',
      data,
      encode: { x: 'month', y: 'value' },
      transform: [{ type: 'select', channel: 'y', selector: 'max' }],  // 只选最大值点
      style: { fill: '#f5222d', r: 5 },
      labels: [
        {
          text: (d) => `最大值\n${d.value}`,
          position: 'top',
          style: { fill: '#f5222d', fontSize: 11 },
        },
      ],
    },
  ],
});
```

## 参考区间(rangeX)

```javascript
// 高亮某个 x 值范围(如正常区间)
{
  type: 'rangeX',
  data: [{ x: '6月', x1: '7月' }],
  encode: { x: 'x', x1: 'x1' },
  style: {
    fill: '#52c41a',
    fillOpacity: 0.08,
  },
  labels: [
    {
      text: '正常范围',
      position: 'top-right',
      style: { fill: '#52c41a', fontSize: 11 },
    },
  ],
}
```

## 参考区间(rangeY)

```javascript
// 高亮某个 y 值范围(如正常区间)
{
  type: 'rangeY',
  data: [{ y: 50, y1: 80 }],
  encode: { y: 'y', y1: 'y1' },
  style: {
    fill: '#52c41a',
    fillOpacity: 0.08,
  },
  labels: [
    {
      text: '正常范围',
      position: 'right',
      style: { fill: '#52c41a', fontSize: 11 },
    },
  ],
}
```

## 文字标注(text mark)

```javascript
// 在指定坐标处添加文字
{
  type: 'text',
  data: [{ x: 'Mar', y: 91, label: '最高点' }],
  encode: { x: 'x', y: 'y', text: 'label' },
  style: {
    textAlign: 'center',
    textBaseline: 'bottom',
    fill: '#1890ff',
    fontSize: 12,
    dy: -6,
  },
}
```

## 图片标注(image mark)

```javascript
// 在图表中心添加图片标注
{
  type: 'image',
  data: [{
    src: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
    x: '50%',
    y: '50%'
  }],
  encode: { 
    x: 'x', 
    y: 'y', 
    src: 'src' 
  },
  style: {
    width: 80,
    height: 80,
    textAlign: 'center',
    textBaseline: 'middle'
  }
}
```

## 常见错误与修正

### 错误:在非 view 容器中直接叠加标注
```javascript
// ❌ 错误:多个 chart.options() 会互相覆盖
chart.options({ type: 'line', ... });
chart.options({ type: 'lineY', ... });  // 覆盖了折线图!

// ✅ 正确:用 type: 'view' + children 数组叠加
chart.options({
  type: 'view',
  data,
  children: [
    { type: 'line', ... },
    { type: 'lineY', ... },
  ],
});
```

### 错误:image 标注未正确设置位置和编码
```javascript
// ❌ 错误:使用函数返回固定坐标,未绑定到数据通道
{
  type: 'image',
  data: [{ url: 'https://example.com/image.png' }],
  encode: {
    x: () => 0, // 固定在中心
    y: () => 0  // 固定在中心
  },
  style: {
    img: (d) => d.url,
    width: 80,
    height: 80
  }
}

// ✅ 正确:使用相对百分比坐标并正确映射 src 通道
{
  type: 'image',
  data: [{
    src: 'https://example.com/image.png',
    x: '50%',
    y: '50%'
  }],
  encode: { 
    x: 'x', 
    y: 'y', 
    src: 'src' 
  },
  style: {
    width: 80,
    height: 80
  }
}
```
references/components/g2-comp-axis-config.md
---
id: "g2-comp-axis-config"
title: "G2 坐标轴配置(axis)"
description: |
  详解 G2 v5 Spec 模式中 axis 字段的配置,涵盖轴标题、刻度、标签格式化、
  网格线、轴线样式等,支持对 x、y 轴独立配置。

library: "g2"
version: "5.x"
category: "components"
tags:
  - "axis"
  - "坐标轴"
  - "轴标题"
  - "刻度"
  - "标签格式化"
  - "网格线"
  - "spec"

related:
  - "g2-core-chart-init"
  - "g2-scale-linear"
  - "g2-scale-time"
  - "g2-scale-band"

use_cases:
  - "自定义坐标轴标题"
  - "格式化轴刻度标签(百分比、货币、日期等)"
  - "控制刻度数量和网格线"
  - "隐藏坐标轴"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/component/axis"
---

## 基本用法

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'revenue' },
  axis: {
    x: { title: '月份' },
    y: { title: '收入(万元)' },
  },
});

chart.render();
```

---

## 增量修改配置

如果已有图表,只想修改某个配置项(如标签颜色),可以使用以下方式:

```javascript
// 方式一:重新调用 options,只传需要修改的配置
chart.options({
  axis: {
    y: {
      labelFill: 'red',  // 只修改标签颜色
    },
  },
});
chart.render();  // 需要重新渲染

// 方式二:完整配置后修改
const options = {
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  axis: { x: { title: '日期' } },
};
chart.options(options);

// 后续修改
options.axis = { y: { labelFill: 'red' } };
chart.options(options);
chart.render();
```

---

## 完整配置项参考

### 通用配置

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `position` | 坐标轴位置 | `'left' \| 'right' \| 'top' \| 'bottom'` | x: `'bottom'`, y: `'left'` |
| `animate` | 是否开启动画 | `boolean` | - |

### 轴标题样式(title)

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `title` | 标题内容 | `string \| false` | - |
| `titleSpacing` | 标题到坐标轴的距离 | `number` | `10` |
| `titlePosition` | 标题相对坐标轴的位置 | `'top' \| 'bottom' \| 'left' \| 'right'` | `'lb'` |
| `titleFontSize` | **标题文字大小** | `number` | - |
| `titleFontWeight` | 标题文字字体粗细 | `number \| string` | - |
| `titleFontFamily` | 标题文字字体 | `string` | - |
| `titleLineHeight` | 标题文字行高 | `number` | `1` |
| `titleTextAlign` | 标题文字水平对齐方式 | `string` | `'start'` |
| `titleTextBaseline` | 标题文字垂直基线 | `string` | `'middle'` |
| `titleFill` | **标题文字填充色** | `string` | - |
| `titleFillOpacity` | 标题文字填充透明度 | `number` | `1` |
| `titleStroke` | 标题文字描边颜色 | `string` | `transparent` |
| `titleStrokeOpacity` | 标题文字描边透明度 | `number` | `1` |
| `titleLineWidth` | 标题文字描边宽度 | `number` | `0` |
| `titleLineDash` | 标题文字描边虚线配置 | `number[]` | `[]` |
| `titleOpacity` | 标题文字整体透明度 | `number` | `1` |
| `titleShadowColor` | 标题文字阴影颜色 | `string` | `transparent` |
| `titleShadowBlur` | 标题文字阴影模糊系数 | `number` | `0` |
| `titleShadowOffsetX` | 标题文字阴影水平偏移量 | `number` | `0` |
| `titleShadowOffsetY` | 标题文字阴影垂直偏移量 | `number` | `0` |
| `titleCursor` | 标题文字鼠标样式 | `string` | `default` |
| `titleDx` | 标题文字水平偏移量 | `number` | `0` |
| `titleDy` | 标题文字垂直偏移量 | `number` | `0` |

### 轴线样式(line)

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `line` | 是否显示轴线 | `boolean` | `false` |
| `arrow` | 是否显示箭头 | `boolean` | `true` |
| `lineExtension` | 轴线两侧的延长线 | `[number, number]` | - |
| `lineArrow` | 轴线箭头形状 | `DisplayObject` | - |
| `lineArrowOffset` | 箭头偏移长度 | `number` | `15` |
| `lineArrowSize` | 箭头尺寸 | `number` | - |
| `lineStroke` | **轴线描边颜色** | `string` | - |
| `lineStrokeOpacity` | 轴线描边透明度 | `number` | - |
| `lineLineWidth` | **轴线描边宽度** | `number` | - |
| `lineLineDash` | 轴线描边虚线配置 | `[number, number]` | - |
| `lineOpacity` | 轴线整体透明度 | `number` | `1` |
| `lineShadowColor` | 轴线阴影颜色 | `string` | - |
| `lineShadowBlur` | 轴线阴影模糊系数 | `number` | - |
| `lineShadowOffsetX` | 轴线阴影水平偏移量 | `number` | - |
| `lineShadowOffsetY` | 轴线阴影垂直偏移量 | `number` | - |
| `lineCursor` | 轴线鼠标样式 | `string` | `default` |

### 刻度线样式(tick)

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `tick` | 是否显示刻度 | `boolean` | `true` |
| `tickCount` | 推荐生成的刻度数量 | `number` | - |
| `tickMethod` | 自定义刻度生成方法 | `(start, end, count) => number[]` | - |
| `tickFilter` | 刻度线过滤 | `(datum, index, data) => boolean` | - |
| `tickFormatter` | 刻度线格式化 | `(datum, index, data, Vector) => DisplayObject` | - |
| `tickDirection` | 刻度朝向 | `'positive' \| 'negative'` | `'positive'` |
| `tickLength` | **刻度线长度** | `number` | `15` |
| `tickStroke` | **刻度线描边颜色** | `string` | - |
| `tickStrokeOpacity` | 刻度线描边透明度 | `number` | - |
| `tickLineWidth` | 刻度线描边宽度 | `number` | - |
| `tickLineDash` | 刻度线描边虚线配置 | `[number, number]` | - |
| `tickOpacity` | 刻度线整体透明度 | `number` | - |
| `tickShadowColor` | 刻度线阴影颜色 | `string` | - |
| `tickShadowBlur` | 刻度线阴影模糊系数 | `number` | - |
| `tickShadowOffsetX` | 刻度线阴影水平偏移量 | `number` | - |
| `tickShadowOffsetY` | 刻度线阴影垂直偏移量 | `number` | - |
| `tickCursor` | 刻度线鼠标样式 | `string` | `default` |

### 刻度标签样式(label)

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `labelFormatter` | **标签格式化** | `string \| (datum, index, data) => string` | - |
| `labelFilter` | 标签过滤 | `(datum, index, data) => boolean` | - |
| `labelAutoRotate` | 标签过长时自动旋转 | `boolean` | - |
| `labelAutoHide` | 标签过密时自动隐藏 | `boolean` | - |
| `labelSpacing` | 标签与刻度线的间距 | `number` | - |
| `labelFontSize` | **标签文字大小** | `number` | - |
| `labelFontWeight` | 标签文字字体粗细 | `number \| string` | - |
| `labelFontFamily` | 标签文字字体 | `string` | - |
| `labelLineHeight` | 标签文字行高 | `number` | - |
| `labelTextAlign` | 标签文字水平对齐方式 | `string` | - |
| `labelTextBaseline` | 标签文字垂直基线 | `string` | - |
| `labelFill` | **标签文字填充色** | `string` | - |
| `labelFillOpacity` | 标签文字填充透明度 | `number` | - |
| `labelStroke` | 标签文字描边颜色 | `string` | - |
| `labelStrokeOpacity` | 标签文字描边透明度 | `number` | - |
| `labelLineWidth` | 标签文字描边宽度 | `number` | - |
| `labelLineDash` | 标签文字描边虚线配置 | `number[]` | - |
| `labelOpacity` | 标签文字整体透明度 | `number` | - |
| `labelShadowColor` | 标签文字阴影颜色 | `string` | - |
| `labelShadowBlur` | 标签文字阴影模糊系数 | `number` | - |
| `labelShadowOffsetX` | 标签文字阴影水平偏移量 | `number` | - |
| `labelShadowOffsetY` | 标签文字阴影垂直偏移量 | `number` | - |
| `labelCursor` | 标签文字鼠标样式 | `string` | `default` |
| `labelDx` | 标签文字水平偏移量 | `number` | - |
| `labelDy` | 标签文字垂直偏移量 | `number` | - |

### 刻度标签样式(label,补充)

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `labelRender` | 自定义标签渲染,支持 HTML 字符串,用法同 `labelFormatter` | `string \| (datum, index, array) => string` | - |
| `labelAlign` | 刻度值对齐方式 | `'horizontal' \| 'parallel' \| 'perpendicular'` | `'parallel'` |
| `labelDirection` | 刻度值相对轴线的位置 | `'positive' \| 'negative'` | `'positive'` |
| `labelAutoEllipsis` | 自动缩略过长的刻度值 | `boolean` | - |
| `labelAutoWrap` | 自动换行刻度值 | `boolean` | - |

### 网格线样式(grid)

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `grid` | 是否显示网格线 | `boolean` | - |
| `gridAreaFill` | **网格线区域填充色**,支持交替颜色数组或函数 | `string \| string[] \| (datum, index, data) => string` | - |
| `gridFilter` | 网格线过滤,返回 false 隐藏该网格线 | `(datum, index, data) => boolean` | - |
| `gridLength` | 网格线长度 | `number` | `0` |
| `gridStroke` | **网格线描边颜色** | `string` | - |
| `gridStrokeOpacity` | 网格线描边透明度 | `number` | - |
| `gridLineWidth` | **网格线描边宽度** | `number` | - |
| `gridLineDash` | **网格线描边虚线配置** | `[number, number]` | - |
| `gridOpacity` | 网格线整体透明度 | `number` | - |
| `gridShadowColor` | 网格线阴影颜色 | `string` | - |
| `gridShadowBlur` | 网格线阴影模糊系数 | `number` | - |
| `gridShadowOffsetX` | 网格线阴影水平偏移量 | `number` | - |
| `gridShadowOffsetY` | 网格线阴影垂直偏移量 | `number` | - |
| `gridCursor` | 网格线鼠标样式 | `string` | `default` |

---

## 常用配置示例

### 完整配置示例

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  axis: {
    x: {
      title: '日期',
      titleFontSize: 14,
      titleFill: '#666',
      tickCount: 6,
      labelFormatter: 'YYYY-MM',
      labelFontSize: 11,
      labelFill: '#888',
      tick: true,
      tickLength: 5,
      line: true,
      grid: true,
      gridLineDash: [4, 4],
    },
    y: {
      title: '收入(万元)',
      labelFormatter: (v) => `¥${v}`,
    },
  },
});
```

### 刻度相关配置职责速查

刻度控制有三个配置项,职责不同,不能混用:

| 配置项 | 签名 | 职责 | 使用频率 |
|--------|------|------|---------|
| `labelFormatter` | `(value, index, array) => string` | 刻度**文字内容** | ⭐ 最常用 |
| `tickMethod` | `(start, end, tickCount) => number[]` | 刻度**数值位置** | 偶尔使用 |
| `tickFormatter` | `(datum, index, array, vector) => DisplayObject` | 刻度**线图形** | 极少使用 |

> ❌ 常见错误:把 `tickFormatter` 当 `labelFormatter` 用——`tickFormatter` 返回的是图形对象,不是字符串,用错会导致标签不显示。

### 常用格式化场景

```javascript
// 数值格式化
axis: { y: { labelFormatter: (v) => `${(v / 1000).toFixed(0)}K` } }

// 百分比格式化
axis: { y: { labelFormatter: (v) => `${(v * 100).toFixed(0)}%` } }

// 货币格式化
axis: { y: { labelFormatter: (v) => `¥${v.toLocaleString()}` } }

// 日期格式化(x 轴为 Date 类型)
axis: { x: { labelFormatter: 'MM/DD' } }

// 保留两位小数(纯 d3-format,不能追加文字单位)
axis: { y: { labelFormatter: '.2f' } }         // ✅ 纯 d3-format
// axis: { y: { labelFormatter: '.2f 元' } }   // ❌ 无效!d3-format 后不能加文字
```

### 隐藏坐标轴

```javascript
// 完全隐藏某轴
axis: { x: false }

// 只隐藏标题
axis: { y: { title: false } }

// 只隐藏网格线
axis: { y: { grid: false } }
```

### 修改坐标轴文本配色

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  axis: {
    x: {
      labelFill: '#8c8c8c',        // 标签文字颜色
      labelFontSize: 12,
      titleFill: '#595959',        // 标题文字颜色
      titleFontSize: 13,
      titleFontWeight: 'bold',
    },
    y: {
      labelFill: '#8c8c8c',
      titleFill: '#595959',
    },
  },
});
```

### 网格线区域交替填充(gridAreaFill)

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'month', y: 'value' },
  axis: {
    y: {
      grid: true,
      gridAreaFill: ['rgba(0,0,0,0.04)', 'transparent'],  // 交替填充,增强可读性
      gridLineWidth: 0,   // 隐藏网格线本身,只显示区域色
    },
  },
});

// 也可以用函数控制
axis: {
  y: {
    gridAreaFill: (datum, index) => index % 2 === 0 ? 'rgba(0,0,0,0.04)' : '',
  },
}
```

### 断轴(breaks)—— 跳过数据空洞

```javascript
// 当数据中某段范围远超其他值,用断轴压缩该区间
chart.options({
  type: 'interval',
  data: [
    { x: 'A', y: 100 },
    { x: 'B', y: 200 },
    { x: 'C', y: 95000 },  // 异常值,导致其他柱看不清
    { x: 'D', y: 150 },
  ],
  encode: { x: 'x', y: 'y' },
  axis: {
    y: {
      breaks: [
        {
          start: 500,     // 断轴起点
          end: 90000,     // 断轴终点(跳过这段区间)
          gap: '3%',      // 断轴占画布高度比例
        },
      ],
    },
  },
});
```

### 双 y 轴

```javascript
// 使用 view 容器 + 不同 y 比例尺实现双轴
chart.options({
  type: 'view',
  data,
  children: [
    {
      type: 'interval',
      encode: { x: 'month', y: 'revenue' },
      axis: { y: { title: '收入', position: 'left' } },
    },
    {
      type: 'line',
      encode: { x: 'month', y: 'growth' },
      scale: { y: { key: 'right' } },
      axis: { y: { title: '增速', position: 'right' } },
    },
  ],
});
```

---

## 常见错误与修正

### 错误 1:axis 写在 encode 或 scale 里

```javascript
// ❌ 错误:axis 是独立的顶级字段
chart.options({
  encode: { x: 'month', y: 'value' },
  scale: { x: { title: '月份' } },   // title 不在 scale 里
});

// ✅ 正确:axis 是与 encode/scale 平级的字段
chart.options({
  encode: { x: 'month', y: 'value' },
  axis: { x: { title: '月份' } },
});
```

### 错误 2:样式属性名错误

```javascript
// ❌ 错误的属性名
axis: { x: { fontSize: 12 } }  // 不存在

// ✅ 正确的属性名(带前缀)
axis: { x: { labelFontSize: 12 } }  // 标签字体大小
axis: { x: { titleFontSize: 14 } }  // 标题字体大小
```

### 错误 3:混淆轴标题与图表标题

```javascript
// ❌ 轴标题写在 title 里
title: { title: '月份' }  // 这是图表标题

// ✅ 轴标题在 axis 里
axis: { x: { title: '月份' } }  // 这是 X 轴标题
```

### 错误 4:用 tickFormatter 格式化标签文字

```javascript
// ❌ 错误:tickFormatter 返回的是 DisplayObject(图形对象),不是字符串
axis: {
  y: {
    tickFormatter: (v) => `${v / 1000}K`,  // ❌ 返回字符串给 tickFormatter 无效
  },
}

// ✅ 正确:标签文字格式化用 labelFormatter
axis: {
  y: {
    labelFormatter: (v) => `${v / 1000}K`,  // ✅ labelFormatter 返回 string
  },
}
```

### 错误 5:在 scale.tickMethod 里格式化标签或接收 scale 对象

```javascript
// ❌ 错误:tickMethod 参数不是 scale 对象,返回值不是对象数组
scale: {
  y: {
    tickMethod: (scale) => {              // ❌ 参数不是 scale 对象
      return scale.ticks().map(v => ({    // ❌ scale.ticks() 不存在
        value: v, text: `${v}K`          // ❌ 不能返回对象,只能返回 number[]
      }));
    },
  },
}

// ✅ 正确:tickMethod 签名是 (min, max, count) => number[]
// 格式化文字另用 labelFormatter
scale: {
  y: {
    tickMethod: (min, max, count) => [100, 500, 1000, 5000, 10000],  // ✅ number[]
  },
},
axis: {
  y: {
    labelFormatter: (v) => `${v / 1000}K`,  // ✅ 文字格式化在 axis
  },
}
```

### 错误 6:labelFormatter 用 d3-format 字符串拼接单位

`labelFormatter` 与 `tooltip.items[].valueFormatter` 一样,支持函数或 d3-format 字符串两种形式。**d3-format 字符串只格式化数字,不能在后面追加文字单位**——`'.2f 元'`、`'.0f 米'` 是无效写法。

```javascript
// ❌ 错误:d3-format 字符串后追加文字单位
axis: {
  y: { labelFormatter: '.2f 元' },   // ❌ d3-format 不支持拼接文字,标签异常
  x: { labelFormatter: '.0f 米' },   // ❌ 同上
}

// ✅ 正确:需要拼接单位时必须用函数形式
axis: {
  y: { labelFormatter: (v) => `${v.toFixed(2)} 元` },   // ✅ 函数,可拼接任意文字
  x: { labelFormatter: (v) => `${Math.round(v)} 米` },  // ✅ 函数
}

// ✅ 纯数字格式化(不加单位)可用 d3-format 字符串
axis: {
  y: { labelFormatter: '.2f' },    // ✅ 保留两位小数
  x: { labelFormatter: ',.0f' },  // ✅ 千分位整数
  z: { labelFormatter: '.1%' },   // ✅ 百分比
}
```

### 错误 7:labelFormatter 回调函数签名错误

`labelFormatter` 的回调函数签名应为 `(datum, index, array) => string`,其中:

- `datum`: 当前刻度值(通常是数值或字符串)
- `index`: 当前刻度索引
- `array`: 所有刻度值组成的数组

```javascript
// ❌ 错误:参数顺序错误或使用了不存在的参数
axis: {
  x: {
    labelFormatter: (task, item) => {  // ❌ item 参数不存在
      return `${item.data.stage}-${task}`;
    }
  }
}

// ✅ 正确:使用正确的参数签名
axis: {
  x: {
    labelFormatter: (datum, index, array) => {
      // 注意:此时 datum 是原始数据中的字段值,不是整个数据项
      return `${datum}`;  // 返回字符串即可
    }
  }
}

// ✅ 更推荐的做法:在 encode 中预处理复合标签
chart.options({
  encode: {
    x: (d) => `${d.stage} - ${d.task}`,  // 在 encode 中构造复合标签
    y: 'start',
    y1: 'end'
  },
  axis: {
    x: {
      labelTransform: 'rotate(30)'  // 如需旋转标签防止重叠
    }
  }
});
```

### 错误 8:legend.labelFormatter 与 axis.labelFormatter 混淆

虽然两者都用于格式化标签,但它们作用的对象不同。`legend.labelFormatter` 用于图例标签,而 `axis.labelFormatter` 用于坐标轴刻度标签。

```javascript
// ❌ 错误:在 legend 中使用 axis.labelFormatter
legend: {
  color: {
    labelFormatter: '.0%'  // ❌ legend 不支持 axis 的 labelFormatter
  }
}

// ✅ 正确:legend 使用自己的 labelFormatter
legend: {
  color: {
    labelFormatter: (value) => `${Math.round(value)}%`  // ✅ 函数形式
  }
}
```

### 错误 9:tooltip.valueFormatter 与 axis.labelFormatter 混淆

`tooltip.valueFormatter` 用于格式化提示框中的值,而 `axis.labelFormatter` 用于坐标轴标签。

```javascript
// ❌ 错误:在 tooltip.items 中使用 axis.labelFormatter
tooltip: {
  items: [
    { channel: 'y', labelFormatter: '.2f' }  // ❌ tooltip.items 不支持 labelFormatter
  ]
}

// ✅ 正确:tooltip.items 使用 valueFormatter
tooltip: {
  items: [
    { channel: 'y', valueFormatter: '.2f' }  // ✅ 使用 valueFormatter
  ]
}
```

### 错误 10:cell 图表中 style.inset 设置不当导致渲染空白

在 `cell` 类型图表中,`style.inset` 控制单元格的内边距。如果设置过大,可能导致单元格不可见。

```javascript
// ❌ 错误:inset 设置过大
chart.options({
  type: 'cell',
  data,
  encode: { x: 'x', y: 'y', color: 'value' },
  style: {
    inset: 10  // ❌ inset 太大,可能使矩形不可见
  }
});

// ✅ 正确:合理设置 inset
chart.options({
  type: 'cell',
  data,
  encode: { x: 'x', y: 'y', color: 'value' },
  style: {
    inset: 0.5  // ✅ 合理的 inset 值
  }
});
```

### 错误 11:legend.layout 配置错误导致布局异常

`legend.layout` 使用 Flexbox 布局模型,若配置不当会影响图例排版。

```javascript
// ❌ 错误:justifyContent 写错或不支持的值
legend: {
  color: {
    layout: { justifyContent: 'centered' }  // ❌ 不支持的值
  }
}

// ✅ 正确:使用合法的 Flexbox 值
legend: {
  color: {
    layout: { justifyContent: 'center' }  // ✅ 正确值
  }
}
```

---

> **深色背景适配**:深色背景下坐标轴标签/标题不可见时,使用 `theme: 'classicDark'` 一行解决,或手动设置 `labelFill`/`titleFill`。详见 [深色主题适配](../concepts/g2-concept-dark-theme-adaptation.md)
references/components/g2-comp-axis-radar.md
---
id: "g2-comp-axis-radar"
title: "G2 雷达图坐标轴(AxisRadar)"
description: |
  雷达图专用的坐标轴组件。在极坐标系中显示多个维度的轴线和刻度,
  是雷达图的核心组件之一。

library: "g2"
version: "5.x"
category: "components"
tags:
  - "坐标轴"
  - "雷达图"
  - "极坐标"
  - "axis"

related:
  - "g2-coord-polar"
  - "g2-mark-radar"
  - "g2-comp-axis-config"

use_cases:
  - "雷达图的多维度展示"
  - "极坐标系下的坐标轴"
  - "性能评估图表"

anti_patterns:
  - "直角坐标系图表不适用"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/component/axis"
---

## 核心概念

AxisRadar 是雷达图专用的坐标轴组件:
- 在极坐标系中显示放射状的轴线
- 支持多个维度的轴标签
- 自动计算轴的角度和位置

**特点:**
- 自动连接各轴形成网格
- 支持自定义轴样式
- 与极坐标系配合使用

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'line',
  coordinate: { type: 'polar' },
  data: [
    { item: 'Design', score: 70 },
    { item: 'Development', score: 60 },
    { item: 'Marketing', score: 50 },
    { item: 'Sales', score: 80 },
    { item: 'Support', score: 90 },
  ],
  encode: {
    x: 'item',
    y: 'score',
  },
  axis: {
    x: {
      // 雷达图 X 轴配置
      title: false,
      tickLine: null,
    },
    y: {
      // 雷达图 Y 轴(放射状轴)
      title: 'Score',
      grid: true,
      gridConnect: 'line',  // 网格连接方式
    },
  },
});

chart.render();
```

## 常用变体

### 自定义网格样式

```javascript
chart.options({
  type: 'line',
  coordinate: { type: 'polar' },
  data,
  encode: { x: 'item', y: 'score' },
  axis: {
    y: {
      grid: true,
      gridConnect: 'line',
      gridLineWidth: 1,
      gridStroke: '#e8e8e8',
      gridType: 'line',
    },
  },
});
```

### 隐藏轴线

```javascript
chart.options({
  type: 'line',
  coordinate: { type: 'polar' },
  data,
  encode: { x: 'item', y: 'score' },
  axis: {
    x: { line: null },
    y: { line: null },
  },
});
```

### 自定义标签

```javascript
chart.options({
  type: 'line',
  coordinate: { type: 'polar' },
  data,
  encode: { x: 'item', y: 'score' },
  axis: {
    x: {
      labelFormatter: (val) => val.toUpperCase(),
      labelSpacing: 10,
    },
    y: {
      labelFormatter: (val) => `${val}%`,
    },
  },
});
```

## 完整类型参考

```typescript
interface AxisRadarOptions {
  // 基础配置
  title?: string | { text: string; style?: object };
  tickLine?: null | { length?: number; style?: object };
  line?: null | { style?: object };

  // 标签配置
  labelFormatter?: string | ((val: any) => string);
  labelSpacing?: number;
  labelStyle?: object;

  // 网格配置
  grid?: boolean;
  gridConnect?: 'line' | 'curve';  // 网格连接方式
  gridLineWidth?: number;
  gridStroke?: string;
  gridType?: 'line' | 'circle';

  // 雷达图特有
  radar?: {
    count: number;   // 轴的数量
    index: number;   // 当前轴的索引
  };
}
```

## 与普通坐标轴的区别

| 特性 | 普通坐标轴 | 雷达图坐标轴 |
|------|-----------|-------------|
| 坐标系 | 直角坐标 | 极坐标 |
| 轴方向 | 水平/垂直 | 放射状 |
| 网格 | 矩形 | 多边形/圆形 |
| 标签位置 | 轴两端 | 轴末端外侧 |

## 常见错误与修正

### 错误 1:未使用极坐标系

```javascript
// ❌ 错误:雷达图轴需要极坐标系
chart.options({
  type: 'line',
  data,
  encode: { x: 'item', y: 'score' },
  axis: { y: { gridConnect: 'line' } },
});

// ✅ 正确:添加极坐标系
chart.options({
  type: 'line',
  coordinate: { type: 'polar' },
  data,
  encode: { x: 'item', y: 'score' },
  axis: { y: { gridConnect: 'line' } },
});
```

### 错误 2:gridConnect 参数错误

```javascript
// ❌ 错误:gridConnect 只支持 'line' 或 'curve'
axis: { y: { gridConnect: 'polygon' } }

// ✅ 正确
axis: { y: { gridConnect: 'line' } }
```

---

## 雷达图描边不显示问题

雷达图通过 `coordinate: { type: 'polar' }` + `area` + `line` Mark 组合实现。`line` Mark 在极坐标下的 stroke 依赖 color scale 推导——如果未显式设置 `lineWidth`,部分主题/场景下描边可能不可见。

```javascript
// ❌ 错误:line mark 未设置 lineWidth,在某些主题下描边不可见
chart.options({
  type: 'view',
  data,
  coordinate: { type: 'polar' },
  children: [
    { type: 'area', encode: { x: 'item', y: 'score', color: 'type' }, style: { fillOpacity: 0.2 } },
    { type: 'line', encode: { x: 'item', y: 'score', color: 'type' } },  // ❌ 缺少 lineWidth
  ],
});

// ✅ 正确:显式设置 lineWidth
chart.options({
  type: 'view',
  data,
  coordinate: { type: 'polar' },
  children: [
    { type: 'area', encode: { x: 'item', y: 'score', color: 'type' }, style: { fillOpacity: 0.2 } },
    { type: 'line', encode: { x: 'item', y: 'score', color: 'type' }, style: { lineWidth: 2 } },
  ],
});
```

## 雷达图主题默认值

G2 主题中雷达图坐标轴(`axisRadar`)的默认值:

| 属性 | 默认值 | 说明 |
|------|--------|------|
| `gridStrokeOpacity` | `0.3` | 网格线透明度 |
| `gridType` | `'surround'` | 环绕式网格 |
| `tick` | `false` | 不显示刻度线 |
| `titlePosition` | `'start'` | 标题在轴起始位置 |
| `gridClosed` | `true` | 网格闭合 |

> **深色背景适配**:雷达图在深色背景下轴标签不可见时,使用 `theme: 'classicDark'` 一行解决,或手动设置各轴 `labelFill`/`gridStroke`。详见 [深色主题适配](../concepts/g2-concept-dark-theme-adaptation.md)
references/components/g2-comp-label-config.md
---
id: "g2-comp-label-config"
title: "G2 数据标签配置(labels)"
description: |
  详解 G2 v5 Spec 模式中 labels 字段的配置,涵盖标签文本、位置、格式化、
  选择器(只显示部分标签)、标签转换(transform)、背景框、连接线及样式定制。
  注意:Spec 模式中使用 labels(复数)。

library: "g2"
version: "5.x"
category: "components"
tags:
  - "labels"
  - "label"
  - "数据标签"
  - "文字标签"
  - "position"
  - "formatter"
  - "transform"
  - "spec"

related:
  - "g2-mark-interval-basic"
  - "g2-mark-line-basic"
  - "g2-comp-annotation"

use_cases:
  - "在柱体上方显示数值"
  - "在折线末端显示系列名称"
  - "在饼图扇区内外显示百分比"
  - "解决标签重叠问题"
  - "标签颜色与图形颜色对比度优化"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-05-31"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/component/label"
---

## 基本用法

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data,
  encode: { x: 'genre', y: 'sold' },
  labels: [
    {
      text: 'sold',          // 显示哪个字段的值(字段名字符串或函数)
      position: 'outside',   // 标签位置
    },
  ],
});

chart.render();
```

## 常用位置说明

### 笛卡尔坐标系(interval / point / line / cell 等)

支持 9 种位置:`top`、`left`、`right`、`bottom`、`top-left`、`top-right`、`bottom-left`、`bottom-right`、`inside`。

| position 值      | 适用 Mark | 效果                     |
| ---------------- | --------- | ------------------------ |
| `'top'`          | interval  | 柱体顶部(紧贴顶端)     |
| `'right'`        | interval  | 柱体右侧                 |
| `'left'`         | interval  | 柱体左侧                 |
| `'bottom'`       | interval  | 柱体底部                 |
| `'inside'`       | interval  | 柱体内部中央             |
| `'top-left'`     | interval  | 柱体左上角               |
| `'top-right'`    | interval  | 柱体右上角               |
| `'bottom-left'`  | interval  | 柱体左下角               |
| `'bottom-right'` | interval  | 柱体右下角               |
| `'top'`          | point     | 点的上方                 |
| `'right'`        | line      | 折线末端右侧             |

### 非笛卡尔坐标系(arc / 饼图 / 环形图)

支持 `outside`、`inside` 两种基本位置,以及特殊位置:

| position     | 用途                                             |
| ------------ | ------------------------------------------------ |
| `'outside'`  | 扇区外侧引线                                     |
| `'inside'`   | 扇区内部                                         |
| `'spider'`   | 调整标签沿坐标轴边沿两端对齐,适用于 polar 坐标系(饼图/环形图) |
| `'surround'` | 调整标签环形环绕坐标系,适用于玫瑰图             |
| `'area'`     | 将面积图的标签显示在面积区域中心,并设置一定的旋转角度 |

## 格式化标签文本

```javascript
labels: [
  {
    // 推荐:text 函数方式,可访问完整数据行 datum
    text: (d) => `${d.sold.toLocaleString()} 万`,

    // 或字符串字段名(自动取该字段的值)
    // text: 'sold',
  },
],
```

## formatter 用法(仅格式化已取值的文本)

`formatter` 接收的第一个参数是 `text` 已映射的值(非完整 datum),适合对数值进行简单格式化:

```javascript
labels: [
  {
    text: 'yield_rate',              // 先映射字段 yield_rate 的值
    formatter: (val) => `${val}%`,   // val 是 yield_rate 的值,非 datum 对象
  },
],
```

完整签名:`formatter(text, datum, index, data) => string`

## 完整 label 配置项

```javascript
labels: [
  {
    text: (d) => d.value.toFixed(1),  // 标签文本(推荐用函数直接访问 datum)
    position: 'outside',               // 位置

    // ── 样式(可直接在 label 上配置,也可通过 style 嵌套) ──
    fill: '#333',
    fontSize: 12,
    fontWeight: 'bold',
    textAlign: 'center',
    lineHeight: 20,
    dy: -4,                          // y 方向偏移(px)
    dx: 0,                           // x 方向偏移

    // ── 选择器(只显示部分标签)──────────────
    selector: 'last',                  // 'first' | 'last' | (labels) => filteredLabels
    // 过滤(只对满足条件的数据显示标签)
    filter: (d) => d.value > 50,

    // ── 标签转换(优化标签展示)──────────────
    transform: [{ type: 'overlapDodgeY' }],

    // ── 连接线(饼图 spider/surround 位置时常用)──
    connectorDistance: 5,
    connectorStroke: '#aaa',
    connectorLineWidth: 1,

    // ── 背景框 ───────────────────────────────
    background: true,
    backgroundFill: '#fff',
    backgroundRadius: 4,
    backgroundPadding: [8, 12],
  },
],
```

## selector 选择器

`selector` 用于选择需要保留显示的标签,支持三种方式:

```javascript
labels: [
  {
    text: 'Symbol',
    selector: 'first',              // 方式一:预置 'first' / 'last'
    style: { fill: 'blue' },
  },
  {
    text: 'Symbol',
    selector: 'last',               // 最后一个
    style: { fill: 'red' },
  },
  {
    text: 'Symbol',
    selector: (labels) => {         // 方式二:自定义函数,参数为所有 label 数组
      // labels 内含 bounds 坐标等信息,可用于过滤
      return labels.filter(({ bounds }) => {
        const [x, y] = bounds[0];
        return x > 200 && x < 300 && y > 200 && y < 350;
      });
    },
    style: { fill: '#ac1ce6' },
  },
],
```
`selector` 支持的值:
- `'first'` — 保留第一个标签
- `'last'` — 保留最后一个标签
- `(labels) => filteredLabels` — 自定义选择器函数,接收全部 label 信息数组,可基于坐标等信息过滤

## 标签转换(transform)

当标签的展示不符合预期时(如重叠、颜色不明显、溢出等),可以使用**标签转换(Label Transform)** 来优化标签的展示。

### transform 配置方式

标签转换支持两种配置层级:

**方式一:在 labels 数组中的单个 label 上配置(推荐)**

```javascript
labels: [
  {
    text: 'value',
    transform: [{ type: 'overlapDodgeY' }],
  },
],
```

**方式二:在 view 层级通过 labelTransform 全局配置**

在 `view` 层级通过 `labelTransform` 声明标签转换,作用于该视图下所有标签:

```javascript
chart.options({
  type: 'view',
  labelTransform: [{ type: 'overlapHide' }, { type: 'contrastReverse' }],
});
```

### 标签转换类型一览

| type            | 描述                                                       |
| --------------- | ---------------------------------------------------------- |
| overlapDodgeY   | 对位置碰撞的标签在 y 方向上调整,防止标签重叠             |
| contrastReverse | 标签颜色在图形背景上对比度低时,从指定色板选择最优对比颜色 |
| overflowStroke  | 标签溢出图形时,从指定色板选择对比度最优的颜色进行描边    |
| overflowHide    | 对于标签在图形上放置不下时,隐藏标签                      |
| overlapHide     | 对位置碰撞的标签进行隐藏,默认保留前一个、隐藏后一个      |
| exceedAdjust    | 自动对标签做溢出检测和矫正,超出指定区域时做反方向位移    |

### overlapDodgeY — 防重叠(y 方向调整)

对位置碰撞的标签在 y 方向上做位置调整,适合折线图等标签密集场景。

```javascript
labels: [
  {
    text: 'price',
    transform: [{ type: 'overlapDodgeY' }],
  },
],
```

| 属性          | 描述                                     | 类型     | 默认值 |
| ------------- | ---------------------------------------- | -------- | ------ |
| maxIterations | 位置调整的最大迭代次数                   | _number_ | `10`   |
| padding       | 期望调整后标签之间的间距                 | _number_ | `1`    |
| maxError      | 最大误差(实际间距与期望 padding 的误差)| _number_ | `0.1`  |

### contrastReverse — 对比度反转

标签颜色在图形背景上对比度低时,从指定色板自动选择一个对比度最优的颜色。适用于多颜色的柱状图中颜色和标签接近的场景。

```javascript
labels: [
  {
    text: 'genre',
    transform: [{ type: 'contrastReverse' }],
  },
],
```

| 属性      | 描述                                                   | 类型     | 默认值             |
| --------- | ------------------------------------------------------ | -------- | ------------------ |
| threshold | 标签和背景的颜色对比度阈值,超过阈值才推荐提升对比度  | _number_ | `4.5`              |
| palette   | 对比度提升算法中备选的颜色色板                          | _string[]_ | `['#000', '#fff']` |

可以同时搭配 `overflowStroke` 使用:

```javascript
labels: [
  {
    text: 'frequency',
    transform: [
      { type: 'contrastReverse' },
      { type: 'overflowStroke' },
    ],
  },
],
```

### overflowStroke — 溢出描边

类似字幕黑底白字原理,从指定色板选择一个与标签颜色对比度最优的颜色进行描边,解决标签溢出图形后可读性变差的问题。

```javascript
labels: [
  {
    text: 'frequency',
    transform: [{ type: 'overflowStroke' }],
  },
],
```

| 属性      | 描述                         | 类型       | 默认值             |
| --------- | ---------------------------- | ---------- | ------------------ |
| threshold | 溢出阈值,越大越不容易触发描边 | _number_   | `2`                |
| palette   | 描边备选的颜色色板            | _string[]_ | `['#000', '#fff']` |

### overflowHide — 溢出隐藏

对于标签在图形上放不下的时候,隐藏标签。适用于每个小图形都映射有 `label` 标签导致重叠不清的场景(如旭日图、矩形树图等)。

> **与 overlapDodgeY 的区别**:`overflowHide` 针对 label 与 mark 图形之间的溢出问题;`overlapDodgeY` 针对多个 label 之间的重叠问题。

```javascript
labels: [
  {
    text: 'name',
    transform: [{ type: 'overflowHide' }],
  },
],
```

### overlapHide — 碰撞隐藏

对位置碰撞的标签进行隐藏,默认保留前一个,隐藏后一个。和 `overlapDodgeY` 的区别在于 `overlapHide` 直接隐藏而不是移动位置。

```javascript
labels: [
  {
    text: 'price',
    transform: [{ type: 'overlapHide' }],
  },
],
```

### exceedAdjust — 溢出矫正

自动对标签做溢出检测和矫正,当标签超出指定区域时自动做反方向位移。

```javascript
labels: [
  {
    text: 'tooltip',
    transform: [{ type: 'exceedAdjust' }],  // 默认检测 view 边界
  },
],
```

```javascript
// 带完整配置
labels: [
  {
    text: 'tooltip',
    transform: [{
      type: 'exceedAdjust',
      bounds: 'main',     // 检测主区域边界
      offsetX: 15,        // X 轴偏移附加值
      offsetY: 10,        // Y 轴偏移附加值
    }],
  },
],
```

| 属性    | 说明                                                              | 类型               | 默认值  |
| ------- | ----------------------------------------------------------------- | ------------------ | ------- |
| bounds  | 指定检测边界的区域类型(`5.3.4` 开始支持)。`'view'` 为整个视图区域;`'main'` 为主区域 | `'view' \| 'main'` | `'view'` |
| offsetX | 触发自动调整位置时 X 轴偏移附加值(左侧边界向右,右侧边界向左)  | _number_           | `0`     |
| offsetY | 触发自动调整位置时 Y 轴偏移附加值(上侧边界向下,下侧边界向上)  | _number_           | `0`     |

## 标签背景框(background)

标签可以配置背景框样式,格式为 `background${style}`,如 `backgroundFill` 代表背景框填充色。需要设置 `background: true` 开启。

```javascript
labels: [
  {
    text: 'value',
    background: true,
    backgroundFill: '#fff',
    backgroundRadius: 4,
    backgroundPadding: [10, 10, 10, 10],
    backgroundOpacity: 0.8,
    backgroundStroke: '#000',
    backgroundLineWidth: 1,
  },
],
```

### 背景框配置项

| 参数                    | 说明               | 类型              | 默认值 |
| ----------------------- | ------------------ | ----------------- | ------ |
| backgroundFill          | 背景框填充色       | _string_          | -      |
| backgroundFillOpacity   | 背景框填充透明度   | _number_          | -      |
| backgroundStroke        | 背景框描边         | _string_          | -      |
| backgroundStrokeOpacity | 背景框描边透明度   | _number_          | -      |
| backgroundLineWidth     | 背景框描边宽度     | _number_          | -      |
| backgroundLineDash      | 背景框描边虚线配置 | _[number,number]_ | -      |
| backgroundOpacity       | 背景框整体透明度   | _number_          | -      |
| backgroundShadowColor   | 背景框阴影颜色     | _string_          | -      |
| backgroundShadowBlur    | 背景框阴影模糊系数 | _number_          | -      |
| backgroundShadowOffsetX | 背景框阴影水平偏移 | _number_          | -      |
| backgroundShadowOffsetY | 背景框阴影垂直偏移 | _number_          | -      |
| backgroundCursor        | 鼠标样式           | _string_          | `default` |
| backgroundRadius        | 背景框圆角半径     | _number_          | -      |
| backgroundPadding       | 背景框内边距       | _number[]_        | -      |

## 推荐 Label Transform 组合

不同场景下的最佳 transform 组合,避免标签可见性问题:

### 柱状图 inside label(多色柱)

多色柱状图的 label 放在柱体内部时,必须使用 `contrastReverse` 自动适配背景色,配合 `overflowHide` 隐藏放不下的标签:

```javascript
labels: [
  {
    text: 'value',
    position: 'inside',
    transform: [{ type: 'contrastReverse' }, { type: 'overflowHide' }],
  },
]
```

### 折线图末端标签(密集系列)

多系列折线图末端标签容易重叠,用 `overlapDodgeY` 在 Y 方向自动避让,`exceedAdjust` 防止溢出边界:

```javascript
labels: [
  {
    text: 'type',
    selector: 'last',
    position: 'right',
    transform: [{ type: 'overlapDodgeY' }, { type: 'exceedAdjust' }],
  },
]
```

### 饼图外侧标签

饼图 `spider` 或 `outside` 位置的标签可能重叠,用 `overlapHide` 隐藏碰撞标签:

```javascript
labels: [
  {
    text: (d) => `${d.name}: ${d.value}%`,
    position: 'spider',
    transform: [{ type: 'overlapHide' }],
  },
]
```

### 堆叠图 / TreeMap / 旭日图

空间有限的 mark 使用 `overflowHide` 隐藏放不下的标签,配合 `contrastReverse` 确保可读性:

```javascript
labels: [
  {
    text: 'name',
    position: 'inside',
    transform: [{ type: 'contrastReverse' }, { type: 'overflowHide' }],
  },
]
```

### 通用安全组合(适用于大多数场景)

不确定该用哪种 transform 时,以下组合覆盖最常见的标签问题:

```javascript
labels: [
  {
    text: 'field',
    transform: [{ type: 'overlapHide' }, { type: 'exceedAdjust' }],
  },
]
```

## innerHTML / render 自定义 HTML 标签

除了 `text` 字段映射,还可以使用 `innerHTML` 或 `render` 渲染自定义 HTML 内容。

```javascript
labels: [
  {
    // innerHTML 自定义,返回 string 或 HTMLElement
    innerHTML: ({ genre, sold }) =>
      `<div style="padding:0 4px;border-radius:10px;background:#f5f5f5;border:2px solid #5ea9e6;font-size:11px;">${genre}:${sold}</div>`,
    dx: 10,
    dy: 50,
    style: { fill: 'rgba(0,0,0,0)', color: '#333' },
  },
],
```

> **注意**:`innerHTML` 和 `text` 同时配置时 `text` 会失效。`render` 与 `innerHTML` 数据类型一致,传参略有区别:
> ```ts
> type RenderFunc = (text: string, datum: object, index: number, { channel: Record<string, Channel> }) => String | HTMLElement;
> ```

## 连接线完整样式(connector)

在饼图和环形图等非笛卡尔坐标系下,使用 `position: 'spider'` 或 `'surround'` 时会展示连接线元素。连接线样式格式为 `connector${style}`。

```javascript
labels: [
  {
    text: 'id',
    position: 'spider',
    connectorDistance: 5,           // 文本和连接线的间距
    connectorStroke: '#0649f2',     // 连接线颜色
    connectorLineWidth: 1,          // 连接线宽度
    connectorLineDash: [3, 4],      // 虚线配置
    connectorOpacity: 0.8,          // 透明度
  },
],
```

| 参数                   | 说明                    | 类型              | 默认值    |
| ---------------------- | ----------------------- | ----------------- | --------- |
| connectorStroke        | 连接线颜色              | _string_          | -         |
| connectorStrokeOpacity | 连接线透明度            | _number_          | -         |
| connectorLineWidth     | 连接线描边宽度          | _number_          | -         |
| connectorLineDash      | 连接线虚线配置          | _[number,number]_ | -         |
| connectorOpacity       | 连接线整体透明度        | _number_          | -         |
| connectorShadowColor   | 连接线阴影颜色          | _string_          | -         |
| connectorShadowBlur    | 连接线阴影模糊系数      | _number_          | -         |
| connectorShadowOffsetX | 连接线阴影水平偏移      | _number_          | -         |
| connectorShadowOffsetY | 连接线阴影垂直偏移      | _number_          | -         |
| connectorCursor        | 鼠标样式                | _string_          | `default` |
| connectorDistance      | 连接线和文本距离        | _number_          | -         |

## 折线末端标签

```javascript
// 只在每条折线的最后一个点显示系列名称
chart.options({
  type: 'line',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  labels: [
    {
      text: 'type',         // 显示系列名
      selector: 'last',     // 只在最后一个数据点显示
      position: 'right',
      style: { fontSize: 11 },
    },
  ],
});
```

## 堆叠柱中心标签

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  labels: [
    {
      text: (d) => d.value >= 30 ? d.value : '',  // 数值太小时不显示
      position: 'inside',
      style: { fill: 'white', fontSize: 11 },
    },
  ],
});
```

## 常见错误与修正

### 错误:Spec 模式中写成 label(单数)
```javascript
// ❌ 错误:链式 API 是 .label(),但 Spec 模式是 labels(复数,且是数组)
chart.options({ label: { text: 'value' } });

// ✅ 正确:Spec 中用 labels 数组
chart.options({ labels: [{ text: 'value' }] });
```

### 错误:text 传入了数字常量
```javascript
// ❌ 错误:text 为数字 0,所有标签显示 '0'
chart.options({ labels: [{ text: 0 }] });

// ✅ 正确:text 应为字段名字符串或函数
chart.options({ labels: [{ text: 'value' }] });
chart.options({ labels: [{ text: (d) => d.value.toFixed(1) }] });
```

### 错误:formatter 中把第一个参数当成 datum
```javascript
// ❌ 错误:formatter 的第一个参数是已映射的文本值,不是 datum
labels: [{
  text: 'yield_rate',
  formatter: (d) => `${d.yield_rate}%`,  // d 是数值,d.yield_rate 为 undefined
}]

// ✅ 方案一:用 text 函数直接访问 datum(推荐)
labels: [{
  text: (d) => `${d.yield_rate}%`,
}]

// ✅ 方案二:formatter 正确用法(参数是已取值的文本)
labels: [{
  text: 'yield_rate',
  formatter: (val) => `${val}%`,  // val 是 yield_rate 的值
}]
```
references/components/g2-comp-legend-category.md
---
id: "g2-comp-legend-category"
title: "G2 分类图例(LegendCategory)"
description: |
  分类图例组件,用于展示离散类别的图例项。
  是最常用的图例类型,适用于分类数据的可视化。

library: "g2"
version: "5.x"
category: "components"
tags:
  - "图例"
  - "legend"
  - "分类"
  - "category"

related:
  - "g2-comp-legend-config"
  - "g2-scale-ordinal"
  - "g2-interaction-legend-filter"

use_cases:
  - "柱状图的分类图例"
  - "折线图的系列图例"
  - "散点图的分组图例"

anti_patterns:
  - "连续数据应使用连续图例(LegendContinuous)"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/component/legend"
---

## 核心概念

LegendCategory 是分类图例组件:
- 显示离散类别的图例项
- 每项包含图标和标签
- 支持交互(点击筛选、hover 高亮)

**特点:**
- 自动从 color/shape 通道推断
- 支持水平和垂直布局
- 支持自定义图标

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'interval',
  data: [
    { category: 'A', type: 'X', value: 100 },
    { category: 'A', type: 'Y', value: 150 },
    { category: 'B', type: 'X', value: 120 },
    { category: 'B', type: 'Y', value: 180 },
  ],
  encode: {
    x: 'category',
    y: 'value',
    color: 'type',
  },
  legend: {
    color: {
      position: 'top',
      layout: {
        justifyContent: 'center',
      },
    },
  },
});

chart.render();
```

## 常用变体

### 垂直布局

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'type' },
  legend: {
    color: {
      position: 'right',
      layout: {
        flexDirection: 'column',
      },
    },
  },
});
```

### 自定义标签格式

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'type' },
  legend: {
    color: {
      labelFormatter: (val) => `类型: ${val}`,
    },
  },
});
```

### 添加标题

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'type' },
  legend: {
    color: {
      title: '类型',
      position: 'top',
    },
  },
});
```

### 自定义图标

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'type' },
  legend: {
    color: {
      itemMarker: 'square',  // 'circle' | 'square' | 'line' | ...
      itemMarkerSize: 12,
    },
  },
});
```

### 网格布局

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'type' },
  legend: {
    color: {
      cols: 3,  // 每行显示 3 项
      layout: { justifyContent: 'center' },
    },
  },
});
```

### 禁用交互

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'type' },
  legend: {
    color: {
      itemMarker: 'circle',
    },
  },
  interaction: {
    legendFilter: false,  // 禁用点击筛选
  },
});
```

## 完整类型参考

```typescript
interface LegendCategoryOptions {
  // 位置和布局
  position?: 'top' | 'bottom' | 'left' | 'right' | 'center';
  layout?: {
    flexDirection?: 'row' | 'column';
    justifyContent?: 'flex-start' | 'center' | 'flex-end';
    flexWrap?: 'wrap' | 'nowrap';
  };
  cols?: number;  // 网格布局列数

  // 标题
  title?: string | string[];

  // 图标
  itemMarker?: string | ((id: any, index: number) => string);
  itemMarkerSize?: number;
  itemMarkerLineWidth?: number;
  itemSpacing?: number;

  // 标签
  labelFormatter?: string | ((val: any) => string);
  maxItemWidth?: number;

  // 样式
  style?: {
    fill?: string;
    fontSize?: number;
    // 更多样式...
  };

  // 其他
  dx?: number;
  dy?: number;
}
```

## 与连续图例的区别

| 特性 | 分类图例 | 连续图例 |
|------|---------|---------|
| 数据类型 | 离散类别 | 连续数值 |
| 显示方式 | 图标 + 标签列表 | 色带 + 刻度 |
| 交互 | 点击筛选 | 无筛选 |
| 适用场景 | 分类数据 | 数值映射 |

## 常见错误与修正

### 错误 1:position 参数错误

```javascript
// ❌ 错误:position 应该是预定义值
legend: { color: { position: 'top-left' } }

// ✅ 正确
legend: { color: { position: 'top' } }
```

### 错误 2:未映射 color 通道

```javascript
// ❌ 错误:没有 color 通道,图例不会显示
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value' },
  legend: { color: { position: 'top' } },
});

// ✅ 正确:添加 color 通道
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'type' },
  legend: { color: { position: 'top' } },
});
```

### 错误 3:itemMarker 类型错误

```javascript
// ❌ 错误:itemMarker 应该是预定义形状名或函数
legend: { color: { itemMarker: 'triangle-up' } }

// ✅ 正确:使用支持的形状
legend: { color: { itemMarker: 'triangle' } }
// 或
legend: { color: { itemMarker: (id, i) => i === 0 ? 'circle' : 'square' } }
```
references/components/g2-comp-legend-config.md
---
id: "g2-comp-legend-config"
title: "G2 图例配置(legend)"
description: |
  详解 G2 v5 Spec 模式中 legend 字段的配置,
  涵盖图例位置、布局、标题、颜色图例、过滤交互及隐藏图例等用法。

library: "g2"
version: "5.x"
category: "components"
tags:
  - "legend"
  - "图例"
  - "位置"
  - "过滤"
  - "颜色图例"
  - "spec"

related:
  - "g2-core-chart-init"
  - "g2-interaction-legend-filter"
  - "g2-comp-axis-config"
  - "g2-comp-legend-category"
  - "g2-comp-legend-continuous"

use_cases:
  - "调整图例位置和布局"
  - "自定义图例标题和样式"
  - "隐藏不需要的图例"
  - "配置连续颜色图例(色带)"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/component/legend"
---

## 基本用法

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  legend: {
    color: {               // 对应 encode.color 通道的图例
      position: 'bottom',  // 'top'(默认) | 'bottom' | 'left' | 'right'
    },
  },
});

chart.render();
```

---

## 增量修改配置

如果已有图表,只想修改某个配置项(如图例位置),可以使用以下方式:

```javascript
// 方式一:重新调用 options,只传需要修改的配置
chart.options({
  legend: {
    color: {
      position: 'right',  // 只修改位置
    },
  },
});
chart.render();  // 需要重新渲染

// 方式二:完整配置后修改
const options = {
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  legend: { color: { position: 'top' } },
};
chart.options(options);

// 后续修改
options.legend = { color: { position: 'bottom' } };
chart.options(options);
chart.render();
```

---

## 完整配置项参考

### 通用配置(分类图例 & 连续图例)

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `position` | 图例位置 | `'top' \| 'right' \| 'left' \| 'bottom'` | `'top'` |
| `orientation` | 图例朝向 | `'horizontal' \| 'vertical'` | `'horizontal'` |
| `layout` | Flex 布局配置 | `{ justifyContent, alignItems, flexDirection }` | - |
| `size` | 图例容器尺寸 | `number` | - |
| `length` | 图例容器长度 | `number` | - |
| `crossPadding` | 图例到图表区域的距离 | `number` | `12` |
| `order` | 布局排序 | `number` | `1` |
| `title` | 图例标题 | `string \| string[]` | - |

### 分类图例配置

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `cols` | 每行显示的图例项数量 | `number` | - |
| `colPadding` | 图例项横向间隔 | `number` | `12` |
| `rowPadding` | 图例项纵向间隔 | `number` | `8` |
| `maxRows` | 图例最大行数 | `number` | `3` |
| `maxCols` | 图例最大列数 | `number` | `3` |
| `itemWidth` | 图例项宽度 | `number` | - |
| `itemSpan` | 图例项图标、标签、值的空间划分 | `number \| number[]` | `[1, 1, 1]` |
| `itemSpacing` | 图例项内部间距 | `number \| number[]` | `[8, 8, 4]` |
| `focus` | 是否启用图例聚焦 | `boolean` | `false` |
| `focusMarkerSize` | 图例聚焦图标大小 | `number` | `12` |
| `defaultSelect` | 默认选中的图例项 | `string[]` | - |

### 图例项图标样式(itemMarker)

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `itemMarker` | 图例项图标 | `string \| (datum, index, data) => string` | - |
| `itemMarkerSize` | 图标大小 | `number` | `8` |
| `itemMarkerFill` | **图标填充色** | `string` | - |
| `itemMarkerFillOpacity` | 图标填充透明度 | `number` | - |
| `itemMarkerStroke` | 图标描边 | `string` | - |
| `itemMarkerStrokeOpacity` | 图标描边透明度 | `number` | - |
| `itemMarkerLineWidth` | 图标描边宽度 | `number` | - |
| `itemMarkerRadius` | 图标圆角 | `number` | - |

### 图例项标签样式(itemLabel)

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `itemLabelFill` | **标签文字填充色** | `string` | `#333` |
| `itemLabelFillOpacity` | 标签文字填充透明度 | `number` | - |
| `itemLabelFontSize` | 标签文字大小 | `number` | `12` |
| `itemLabelFontFamily` | 标签文字字体 | `string` | - |
| `itemLabelFontWeight` | 标签字体粗细 | `number \| string` | - |
| `itemLabelTextAlign` | 标签水平对齐方式 | `string` | - |
| `itemLabelTextBaseline` | 标签垂直基线 | `string` | - |
| `itemLabelStroke` | 标签文字描边 | `string` | - |
| `itemLabelLineWidth` | 标签文字描边宽度 | `number` | - |
| `itemLabelDx` | 标签水平偏移量 | `number` | - |
| `itemLabelDy` | 标签垂直偏移量 | `number` | - |

### 图例项值样式(itemValue)

图例项右侧可以额外显示一个"值"列(通过 `formatter` 或数据字段),适合显示数量、百分比等辅助信息。

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `itemValueFill` | 值文字填充色 | `string` | `#1D2129` |
| `itemValueFillOpacity` | 值文字填充透明度 | `number` | `0.65` |
| `itemValueFontSize` | 值文字大小 | `number` | `12` |
| `itemValueFontFamily` | 值文字字体 | `string` | - |
| `itemValueFontWeight` | 值文字字体粗细 | `number \| string` | - |
| `itemValueStroke` | 值文字描边 | `string` | - |
| `itemValueLineWidth` | 值文字描边宽度 | `number` | - |

### 图例项背景样式(itemBackground)

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `itemBackgroundFill` | 图例项背景填充色 | `string` | - |
| `itemBackgroundFillOpacity` | 图例项背景填充透明度 | `number` | - |
| `itemBackgroundStroke` | 图例项背景描边 | `string` | - |
| `itemBackgroundStrokeOpacity` | 图例项背景描边透明度 | `number` | - |
| `itemBackgroundLineWidth` | 图例项背景描边宽度 | `number` | - |
| `itemBackgroundRadius` | 图例项背景圆角 | `number` | - |

### 图例标题样式(title)

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `titleFill` | **标题填充色** | `string` | `#666` |
| `titleFillOpacity` | 标题填充透明度 | `number` | - |
| `titleFontSize` | 标题字体大小 | `number` | `12` |
| `titleFontFamily` | 标题字体 | `string` | - |
| `titleFontWeight` | 标题字体粗细 | `number \| string` | - |
| `titleStroke` | 标题描边 | `string` | - |
| `titleLineWidth` | 标题描边宽度 | `number` | - |
| `titleSpacing` | 标题与图例项的间距 | `number` | - |

### 连续图例配置

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `color` | 色带颜色 | `string[]` | - |
| `block` | 是否按区间显示 | `boolean` | `false` |
| `type` | 连续图例类型 | `'size' \| 'color'` | `'color'` |

---

## 常用配置示例

### 隐藏图例

```javascript
// 隐藏特定通道的图例
legend: { color: false }

// 全部隐藏(不常用)
legend: false
```

### 修改图例位置和布局

```javascript
chart.options({
  legend: {
    color: {
      position: 'bottom',
      layout: {
        justifyContent: 'center',  // 水平居中
        alignItems: 'center',      // 垂直居中
      },
    },
  },
});
```

### 修改图例项图标颜色

```javascript
chart.options({
  legend: {
    color: {
      itemMarkerFill: 'red',       // 图标填充色
      itemMarkerSize: 10,          // 图标大小
      itemMarkerStroke: 'darkred', // 图标描边
    },
  },
});
```

### 修改图例标签颜色

```javascript
chart.options({
  legend: {
    color: {
      itemLabelFill: '#333',
      itemLabelFontSize: 14,
      itemLabelFontWeight: 'bold',
    },
  },
});
```

### 修改图例标题样式

```javascript
chart.options({
  legend: {
    color: {
      title: '产品类型',
      titleFill: '#1D2129',
      titleFontSize: 14,
      titleFontWeight: 'bold',
      titleSpacing: 12,
    },
  },
});
```

### 饼图图例放底部居中

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: { type: 'theta', outerRadius: 0.8 },
  legend: {
    color: {
      position: 'bottom',
      layout: { justifyContent: 'center' },
    },
  },
});
```

### 连续颜色图例(色带)

当 `color` 通道映射连续数值时,图例自动变为色带形式。

```javascript
chart.options({
  type: 'cell',
  data,
  encode: { x: 'x', y: 'y', color: 'value' },  // value 是连续数值
  scale: { color: { palette: 'Blues' } },
  legend: {
    color: {
      position: 'right',
      length: 200,
      labelFormatter: (v) => Number(v).toFixed(0),  // 注意:v 可能是 string,需先转换为数字
    },
  },
});
```

> **更多连续图例配置**:[连续图例详细文档](g2-comp-legend-continuous.md) 涵盖阈值图例、size 通道图例、自定义色带等高级用法。

---

## 常见错误与修正

### 错误 1:legend 写成数组

```javascript
// ❌ 错误:legend 是对象,不是数组
chart.options({ legend: [{ color: { position: 'bottom' } }] });

// ✅ 正确
chart.options({ legend: { color: { position: 'bottom' } } });
```

### 错误 2:legend.color 与 encode.color 不对应

```javascript
// ❌ 错误:encode 没有 color 通道,配置 legend.color 无效
chart.options({
  encode: { x: 'month', y: 'value' },  // 没有 color
  legend: { color: { position: 'bottom' } },
});

// ✅ 正确:只有 encode.color 有映射时,legend.color 才有效
chart.options({
  encode: { x: 'month', y: 'value', color: 'type' },
  legend: { color: { position: 'bottom' } },
});
```

### 错误 3:样式属性名错误

```javascript
// ❌ 错误的属性名
legend: { color: { markerFill: 'red' } }  // 不存在

// ✅ 正确的属性名(带前缀)
legend: { color: { itemMarkerFill: 'red' } }  // 正确
```

### 错误 4:混淆图例标题与图表标题

```javascript
// ❌ 图例标题写在 axis 里
axis: { x: { title: '产品类型' } }  // 这是 X 轴标题

// ✅ 图例标题在 legend 里
legend: { color: { title: '产品类型' } }  // 这是图例标题
```

---

## 图例与 Label 的布局冲突

### 饼图外侧 label + 顶部 legend 重叠

饼图使用 `position: 'outside'` 或 `'spider'` 的 label 时,标签分布在饼图周围的上下左右。如果 legend 使用默认的 `position: 'top'`,顶部的 label 和 legend 会发生重叠。

```javascript
// ❌ 错误:spider label + 默认 top legend → 上方空间冲突
chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: { type: 'theta' },
  labels: [{ text: 'type', position: 'spider' }],
  // legend 默认 'top',与顶部 spider label 重叠
});

// ✅ 方案一:legend 移到 bottom(推荐)
chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: { type: 'theta' },
  labels: [{ text: 'type', position: 'spider' }],
  legend: {
    color: {
      position: 'bottom',
      layout: { justifyContent: 'center' },
    },
  },
});

// ✅ 方案二:增大 paddingTop 留出空间
chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: { type: 'theta' },
  labels: [{ text: 'type', position: 'spider' }],
  paddingTop: 60,
});
```

**适用范围**:所有使用 `position: 'outside'` / `'spider'` / `'surround'` 标签的非笛卡尔坐标图(饼图、环形图、玫瑰图)。
references/components/g2-comp-legend-continuous.md
---
id: "g2-comp-legend-continuous"
title: "G2 连续图例(legendContinuous)"
description: |
  连续图例用于展示连续数值到颜色的映射关系,常见于热力图、地理可视化等场景。
  支持色带(ribbon)和块状(block)两种形态,可配置标签格式化、范围等。

library: "g2"
version: "5.x"
category: "components"
tags:
  - "legend"
  - "连续图例"
  - "色带"
  - "color legend"
  - "热力图"

related:
  - "g2-comp-legend-config"
  - "g2-comp-legend-category"
  - "g2-scale-sequential"

use_cases:
  - "热力图的颜色映射说明"
  - "地理可视化的数值范围图例"
  - "连续数值的颜色编码"

anti_patterns:
  - "分类数据应使用分类图例(legendCategory)"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/component/legend"
---

## 核心概念

连续图例(Continuous Legend)展示连续数值到视觉通道(通常是颜色)的映射:
- 当 `encode.color` 映射到连续数值字段时,图例自动变为连续图例
- 支持线性比例尺(linear)、阈值比例尺(threshold)、分位数比例尺(quantile/quantize)
- 默认显示为色带(ribbon)形式

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const data = Array.from({ length: 100 }, (_, i) => ({
  x: i % 10,
  y: Math.floor(i / 10),
  value: Math.random() * 100,
}));

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'cell',
  data,
  encode: { x: 'x', y: 'y', color: 'value' },  // value 是连续数值
  scale: { color: { palette: 'Blues' } },
  legend: {
    color: {
      position: 'right',
      length: 200,
      labelFormatter: (v) => Number(v).toFixed(0),  // 注意:v 可能是 string,需先转换
    },
  },
});

chart.render();
```

## 完整配置项

```javascript
chart.options({
  type: 'cell',
  data,
  encode: { x: 'x', y: 'y', color: 'value' },
  legend: {
    color: {
      // ── 位置 ─────────────────────────────────
      position: 'right',       // 'top' | 'bottom' | 'left' | 'right'
      layout: {
        justifyContent: 'center',
      },

      // ── 尺寸 ─────────────────────────────────
      length: 200,             // 色带长度(px)
      size: 20,                // 色带宽度/高度(px)

      // ── 标题 ─────────────────────────────────
      title: '数值范围',
      titleFontSize: 12,

      // ── 标签 ─────────────────────────────────
      labelFormatter: (v) => Number(v).toFixed(1),  // 注意:v 可能是 string,需先转换
      labelAlign: 'value',     // 'value' | 'range'

      // ── 样式 ─────────────────────────────────
      style: {
        ribbonFill: 'black',   // 默认色带填充色(无颜色映射时)
      },
    },
  },
});
```

## 常用变体

### 阈值图例(分段色带)

```javascript
// 使用 threshold/quantize/quantile 比例尺时,图例自动变为分段
chart.options({
  type: 'cell',
  data,
  encode: { x: 'x', y: 'y', color: 'value' },
  scale: {
    color: {
      type: 'quantize',       // 分段比例尺
      domain: [0, 100],
      range: ['#f7fbff', '#6baed6', '#08519c'],  // 3 段颜色
    },
  },
  legend: {
    color: {
      position: 'right',
    },
  },
});
```

### 水平色带

```javascript
chart.options({
  type: 'cell',
  data,
  encode: { x: 'x', y: 'y', color: 'value' },
  legend: {
    color: {
      position: 'bottom',
      length: 400,
      size: 15,
      layout: { justifyContent: 'center' },
    },
  },
});
```

### 自定义色带颜色

```javascript
chart.options({
  type: 'cell',
  data,
  encode: { x: 'x', y: 'y', color: 'value' },
  scale: {
    color: {
      type: 'linear',
      domain: [0, 100],
      range: ['#e6f5ff', '#0066cc'],  // 渐变范围
    },
  },
  legend: {
    color: {
      position: 'right',
      labelFormatter: (v) => `${Number(v)}°C`,  // 注意:v 可能是 string,需先转换
    },
  },
});
```

### size 通道图例

```javascript
// size 通道也会生成连续图例
chart.options({
  type: 'point',
  data,
  encode: { x: 'x', y: 'y', size: 'value' },
  legend: {
    size: {
      position: 'right',
      title: '大小',
    },
  },
});
```

## 完整类型参考

```typescript
interface LegendContinuousOptions {
  position?: 'top' | 'bottom' | 'left' | 'right';
  layout?: FlexLayout;
  title?: string | string[];
  length?: number;           // 色带长度
  size?: number;             // 色带宽度
  labelFormatter?: string | ((value: number) => string);
  labelAlign?: 'value' | 'range';
  style?: {
    ribbonFill?: string;
    [key: string]: any;
  };
}
```

## 连续图例 vs 分类图例

| 特性 | 连续图例 | 分类图例 |
|------|----------|----------|
| 数据类型 | 连续数值 | 离散分类 |
| 视觉形式 | 色带/块状 | 图例项列表 |
| 比例尺 | linear, threshold, quantize | band, ordinal |
| 适用场景 | 热力图、地图、气泡图 | 柱状图、折线图 |

## 常见错误与修正

### 错误 1:分类数据使用连续图例

```javascript
// ❌ 问题:category 是分类字段,不应使用连续图例
encode: { color: 'category' }  // 分类数据
// 连续图例显示效果不佳

// ✅ 正确:分类数据自动使用分类图例
// G2 会根据数据类型自动选择图例类型
```

### 错误 2:labelFormatter 参数类型错误

```javascript
// ❌ 问题:labelFormatter 的参数 v 可能是 string 类型(不是 number)
// G2 连续图例传入的刻度值为字符串,直接调用 .toFixed() 会报错
labelFormatter: (v) => v.toFixed(1)   // ❌ TypeError: v.toFixed is not a function
labelFormatter: (v) => v * 100        // ❌ 返回数字而不是字符串

// ✅ 正确:先转换为数字,再格式化,最终返回字符串
labelFormatter: (v) => Number(v).toFixed(1)          // ✅ 保留 1 位小数
labelFormatter: (v) => `${(Number(v) * 100).toFixed(0)}%`  // ✅ 百分比格式
labelFormatter: (v) => `${parseFloat(v).toFixed(0)}m`      // ✅ 带单位
```

### 错误 3:length 设置过小

```javascript
// ❌ 问题:色带长度太小,标签重叠
legend: { color: { length: 50 } }  // 太短

// ✅ 正确:根据标签数量设置合适长度
legend: { color: { length: 200 } }  // 合适
```

## 与 legendCategory 的选择

- **使用连续图例**:当 color/size 通道映射到连续数值字段
- **使用分类图例**:当 color 通道映射到分类字段

G2 会根据比例尺类型自动选择正确的图例类型,无需手动指定。
references/components/g2-comp-scrollbar.md
---
id: "g2-comp-scrollbar"
title: "G2 滚动条(scrollbar)"
description: |
  scrollbar 组件让用户滚动查看超出画布范围的数据,适合数据条目过多时的浏览。
  滚动条可配置在 x 轴(scrollbarX)或 y 轴(scrollbarY)方向。
  与 slider 不同,scrollbar 是固定比例窗口的滚动,slider 支持调整窗口大小。

library: "g2"
version: "5.x"
category: "components"
tags:
  - "scrollbar"
  - "滚动条"
  - "数据浏览"
  - "scrollbarX"
  - "scrollbarY"
  - "component"

related:
  - "g2-comp-slider"
  - "g2-interaction-scrollbar-filter"

use_cases:
  - "分类项目太多时水平滚动查看(> 20 个类别)"
  - "时间序列数据太长时滚动浏览"
  - "固定可视窗口大小、滚动查看全部数据"

anti_patterns:
  - "数据量不多时不需要滚动条"
  - "需要调整窗口大小时应使用 slider"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/component/scrollbar"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

// 50 个分类项目
const data = Array.from({ length: 50 }, (_, i) => ({
  category: `类别${i + 1}`,
  value: Math.random() * 100,
}));

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
  scrollbar: {
    x: true,   // 启用 X 轴滚动条
  },
  legend: false,
});

chart.render();
```

---

## 增量修改配置

如果已有图表,只想修改某个配置项(如滑块颜色),可以使用以下方式:

```javascript
// 方式一:重新调用 options,只传需要修改的配置
chart.options({
  scrollbar: {
    x: {
      thumbFill: 'red',  // 只修改滑块填充色
    },
  },
});
chart.render();  // 需要重新渲染

// 方式二:完整配置后修改
const options = {
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value' },
  scrollbar: { x: true },
};
chart.options(options);

// 后续修改
options.scrollbar = { x: { thumbFill: 'red' } };
chart.options(options);
chart.render();
```

---

## 完整配置项参考

### 基础配置

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `ratio` | 滚动条的比例,单页显示数据在总数据量上的比例 | `number` | `0.5` |
| `value` | 滚动条的起始位置(0~1),水平默认 0,垂直默认 1 | `number` | - |
| `slidable` | 是否可以拖动 | `boolean` | `true` |
| `position` | 滚动条相对图表方位 | `string` | `'bottom'` |
| `isRound` | 滚动条样式是否为圆角 | `boolean` | `true` |

### 滑块样式(thumb)

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `thumbFill` | **滑块填充色** | `string` | `#000` |
| `thumbFillOpacity` | 滑块填充透明度 | `number` | `0.15` |
| `thumbStroke` | 滑块描边颜色 | `string` | - |
| `thumbLineWidth` | 滑块描边宽度 | `number` | - |
| `thumbStrokeOpacity` | 滑块描边透明度 | `number` | - |
| `thumbLineDash` | 滑块虚线配置 | `[number, number]` | - |
| `thumbOpacity` | 滑块整体透明度 | `number` | - |
| `thumbShadowColor` | 滑块阴影颜色 | `string` | - |
| `thumbShadowBlur` | 滑块阴影模糊系数 | `number` | - |
| `thumbShadowOffsetX` | 阴影水平偏移 | `number` | - |
| `thumbShadowOffsetY` | 阴影垂直偏移 | `number` | - |
| `thumbCursor` | 滑块鼠标样式 | `string` | `default` |

### 滑轨样式(track)

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `trackSize` | 滑轨宽度 | `number` | `10` |
| `trackLength` | 滑轨长度 | `number` | - |
| `trackFill` | 滑轨填充色 | `string` | - |
| `trackFillOpacity` | 滑轨填充透明度 | `number` | `0` |
| `trackStroke` | 滑轨描边颜色 | `string` | - |
| `trackLineWidth` | 滑轨描边宽度 | `number` | - |
| `trackStrokeOpacity` | 滑轨描边透明度 | `number` | - |
| `trackLineDash` | 滑轨虚线配置 | `[number, number]` | - |
| `trackOpacity` | 滑轨整体透明度 | `number` | - |
| `trackShadowColor` | 滑轨阴影颜色 | `string` | - |
| `trackShadowBlur` | 滑轨阴影模糊系数 | `number` | - |
| `trackShadowOffsetX` | 阴影水平偏移 | `number` | - |
| `trackShadowOffsetY` | 阴影垂直偏移 | `number` | - |
| `trackCursor` | 滑轨鼠标样式 | `string` | `default` |

---

## 常用配置示例

### 配置滚动条样式和初始位置

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'date', y: 'value' },
  scrollbar: {
    x: {
      ratio: 0.2,      // 可视窗口占全部数据的比例
      value: 0,        // 初始滚动位置(0=最左,1=最右)
      // 滑轨样式
      trackSize: 14,
      trackFill: '#f0f0f0',
      trackFillOpacity: 1,
      // 滑块样式
      thumbFill: '#5B8FF9',
      thumbFillOpacity: 0.5,
    },
  },
});
```

### 修改滑块颜色为红色

```javascript
chart.options({
  scrollbar: {
    x: {
      thumbFill: 'red',
      thumbFillOpacity: 0.3,
      thumbStroke: 'darkred',
      thumbLineWidth: 1,
    },
  },
});
```

### Y 轴滚动条

```javascript
chart.options({
  type: 'interval',
  data: manyRowsData,
  encode: { x: 'value', y: 'category' },
  coordinate: { transform: [{ type: 'transpose' }] },
  scrollbar: {
    y: {
      ratio: 0.3,   // 每次只显示 30% 的数据
      value: 0.5,   // 从中间开始
    },
  },
});
```

### 同时配置 X 和 Y 滚动条

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'letter', y: 'frequency' },
  scrollbar: {
    x: {
      ratio: 0.2,
      trackSize: 14,
      trackFill: '#000',
      trackFillOpacity: 1,
    },
    y: {
      ratio: 0.5,
      trackSize: 12,
      value: 0.1,
      trackFill: '#000',
      trackFillOpacity: 1,
    },
  },
});
```

---

## 常见错误与修正

### 错误 1:样式属性名错误

```javascript
// ❌ 错误的属性名
scrollbar: { x: { fill: 'red' } }  // 不存在

// ✅ 正确的属性名(带前缀)
scrollbar: { x: { thumbFill: 'red' } }  // 修改滑块颜色
scrollbar: { x: { trackFill: '#f0f0f0' } }  // 修改滑轨颜色
```

### 错误 2:与 slider 混淆

```javascript
// scrollbar:固定窗口大小,只能移动,不能缩放
scrollbar: { x: { ratio: 0.2 } }  // 总是显示 20% 的数据

// slider:可以拖拽两端调整显示范围
slider: { x: { values: [0, 0.2] } }  // 可以拖拽调整到任意范围
```

### 错误 3:数据量不多却使用滚动条

```javascript
// ❌ 只有 10 个分类,不需要滚动条
chart.options({ scrollbar: { x: true } });  // 多余

// ✅ 通常在 > 20 个类别或时序数据较长时才考虑
// 少量数据时建议直接调整 chart.width 或坐标轴旋转标签
```

### 错误 4:scrollbar 写在 style 里

```javascript
// ❌ 错误:样式属性直接写在配置项,不是 style 对象里
scrollbar: { x: { style: { thumbFill: 'red' } } }

// ✅ 正确:样式属性直接写在配置项
scrollbar: { x: { thumbFill: 'red' } }
```
references/components/g2-comp-slider.md
---
id: "g2-comp-slider"
title: "G2 缩略轴 / 滑条(slider)"
description: |
  slider(缩略轴)让用户通过拖拽两端控制手柄来调整图表的数据显示范围。
  常见于时间序列图表的时间范围筛选,可配置在 x 轴(sliderX)或 y 轴(sliderY)方向。
  支持设置初始值(values)、联动交互(sliderFilter interaction)。

library: "g2"
version: "5.x"
category: "components"
tags:
  - "slider"
  - "缩略轴"
  - "sliderX"
  - "sliderY"
  - "时间筛选"
  - "范围选择"
  - "component"

related:
  - "g2-comp-scrollbar"
  - "g2-interaction-slider-filter"
  - "g2-scale-time"

use_cases:
  - "时间序列图表的时间范围交互筛选"
  - "数值范围的动态调整查看"
  - "大数据集的局部数据探索"

anti_patterns:
  - "分类轴几乎不使用缩略轴"
  - "数据量少时不需要缩略轴"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/component/slider"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const data = Array.from({ length: 200 }, (_, i) => ({
  date: new Date(2020, 0, i + 1).toISOString().split('T')[0],
  value: Math.sin(i / 30) * 50 + 100 + Math.random() * 20,
}));

const chart = new Chart({ container: 'container', width: 800, height: 400 });

chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  slider: {
    x: true,   // 启用 X 轴缩略轴(默认显示全部范围)
  },
});

chart.render();
```

---

## 增量修改配置

如果已有图表,只想修改某个配置项(如手柄颜色),可以使用以下方式:

```javascript
// 方式一:重新调用 options,只传需要修改的配置
chart.options({
  slider: {
    x: {
      handleIconFill: 'red',  // 只修改手柄图标填充色
    },
  },
});
chart.render();  // 需要重新渲染

// 方式二:完整配置后,在 render 前修改
const options = {
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  slider: { x: true },
};
chart.options(options);

// 后续修改
options.slider = { x: { handleIconFill: 'red' } };
chart.options(options);
chart.render();
```

---

## 完整配置项参考

### 基础配置

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `values` | 初始选区范围,位于 0~1 区间 | `[number, number]` | `[0, 1]` |
| `slidable` | 是否允许拖动选取和手柄 | `boolean` | `true` |
| `brushable` | 是否启用刷选 | `boolean` | `true` |
| `labelFormatter` | 拖动手柄标签格式化 | `(value) => string` | - |
| `showHandle` | 是否显示拖动手柄 | `boolean` | `true` |
| `showLabel` | 是否显示拖动手柄文本 | `boolean` | `true` |
| `showLabelOnInteraction` | 在调整手柄或刷选时才显示手柄文本 | `boolean` | `false` |
| `autoFitLabel` | 是否自动调整拖动手柄文本位置 | `boolean` | `true` |
| `padding` | 缩略轴内边距 | `number \| number[]` | - |

### 选区样式(selection)

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `selectionFill` | 选区填充色 | `string` | `#1783FF` |
| `selectionFillOpacity` | 选区填充透明度 | `number` | `0.15` |
| `selectionStroke` | 选区描边 | `string` | - |
| `selectionStrokeOpacity` | 选区描边透明度 | `number` | - |
| `selectionLineWidth` | 选区描边宽度 | `number` | - |
| `selectionLineDash` | 选区描边虚线配置 | `[number, number]` | - |
| `selectionOpacity` | 选区整体透明度 | `number` | - |
| `selectionShadowColor` | 选区阴影颜色 | `string` | - |
| `selectionShadowBlur` | 选区阴影模糊系数 | `number` | - |
| `selectionShadowOffsetX` | 阴影水平偏移 | `number` | - |
| `selectionShadowOffsetY` | 阴影垂直偏移 | `number` | - |
| `selectionCursor` | 选区鼠标样式 | `string` | `default` |

### 滑轨样式(track)

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `trackLength` | 滑轨长度 | `number` | - |
| `trackSize` | 滑轨尺寸 | `number` | `16` |
| `trackFill` | 滑轨填充色 | `string` | `#416180` |
| `trackFillOpacity` | 滑轨填充透明度 | `number` | `1` |
| `trackStroke` | 滑轨描边 | `string` | - |
| `trackStrokeOpacity` | 滑轨描边透明度 | `number` | - |
| `trackLineWidth` | 滑轨描边宽度 | `number` | - |
| `trackLineDash` | 滑轨描边虚线配置 | `[number, number]` | - |
| `trackOpacity` | 滑轨整体透明度 | `number` | - |
| `trackShadowColor` | 滑轨阴影颜色 | `string` | - |
| `trackShadowBlur` | 滑轨阴影模糊系数 | `number` | - |
| `trackShadowOffsetX` | 阴影水平偏移 | `number` | - |
| `trackShadowOffsetY` | 阴影垂直偏移 | `number` | - |
| `trackCursor` | 滑轨鼠标样式 | `string` | `default` |

### 手柄图标样式(handleIcon)

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `handleIconSize` | 手柄图标尺寸 | `number` | `10` |
| `handleIconRadius` | 手柄图标圆角 | `number` | `2` |
| `handleIconShape` | 手柄图标形状 | `string \| (type) => DisplayObject` | - |
| `handleIconFill` | **手柄图标填充色** | `string` | `#f7f7f7` |
| `handleIconFillOpacity` | 手柄图标填充透明度 | `number` | `1` |
| `handleIconStroke` | 手柄图标描边 | `string` | `#1D2129` |
| `handleIconStrokeOpacity` | 手柄图标描边透明度 | `number` | `0.25` |
| `handleIconLineWidth` | 手柄图标描边宽度 | `number` | `1` |
| `handleIconLineDash` | 手柄图标描边虚线配置 | `[number, number]` | - |
| `handleIconOpacity` | 手柄图标整体透明度 | `number` | - |
| `handleIconShadowColor` | 手柄图标阴影颜色 | `string` | - |
| `handleIconShadowBlur` | 手柄图标阴影模糊系数 | `number` | - |
| `handleIconShadowOffsetX` | 阴影水平偏移 | `number` | - |
| `handleIconShadowOffsetY` | 阴影垂直偏移 | `number` | - |
| `handleIconCursor` | 手柄图标鼠标样式 | `string` | `default` |

### 手柄标签样式(handleLabel)

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `handleLabelFontSize` | 标签文字大小 | `number` | `12` |
| `handleLabelFontFamily` | 标签文字字体 | `string` | - |
| `handleLabelFontWeight` | 标签字体粗细 | `number` | `normal` |
| `handleLabelLineHeight` | 标签文字行高 | `number` | - |
| `handleLabelTextAlign` | 标签水平对齐方式 | `string` | `start` |
| `handleLabelTextBaseline` | 标签垂直基线 | `string` | `bottom` |
| `handleLabelFill` | 标签文字填充色 | `string` | `#1D2129` |
| `handleLabelFillOpacity` | 标签文字填充透明度 | `number` | `0.45` |
| `handleLabelStroke` | 标签文字描边 | `string` | - |
| `handleLabelStrokeOpacity` | 标签文字描边透明度 | `number` | - |
| `handleLabelLineWidth` | 标签文字描边宽度 | `number` | - |
| `handleLabelLineDash` | 标签文字描边虚线配置 | `[number, number]` | - |
| `handleLabelOpacity` | 标签整体透明度 | `number` | - |
| `handleLabelShadowColor` | 标签阴影颜色 | `string` | - |
| `handleLabelShadowBlur` | 标签阴影模糊系数 | `number` | - |
| `handleLabelShadowOffsetX` | 阴影水平偏移 | `number` | - |
| `handleLabelShadowOffsetY` | 阴影垂直偏移 | `number` | - |
| `handleLabelCursor` | 标签鼠标样式 | `string` | `default` |
| `handleLabelDx` | 标签水平偏移量 | `number` | `0` |
| `handleLabelDy` | 标签垂直偏移量 | `number` | `0` |

### 迷你图样式(sparkline)

| 属性 | 描述 | 类型 | 默认值 |
|------|------|------|--------|
| `sparklineType` | 迷你图类型 | `'line' \| 'column'` | `'line'` |
| `sparklineIsStack` | 是否堆叠 | `boolean` | `false` |
| `sparklineRange` | 值范围 | `[number, number]` | - |
| `sparklineColor` | 颜色 | `string \| string[]` | - |
| `sparklineSmooth` | 平滑曲线 | `boolean` | `false` |
| `sparklineLineStroke` | 折线颜色 | `string` | - |
| `sparklineLineStrokeOpacity` | 折线透明度 | `number` | - |
| `sparklineLineLineDash` | 折线虚线配置 | `[number, number]` | - |
| `sparklineAreaFill` | 填充区域颜色 | `string` | - |
| `sparklineAreaFillOpacity` | 填充区域透明度 | `number` | - |
| `sparklineColumnFill` | 直方图条形颜色 | `string` | - |
| `sparklineColumnFillOpacity` | 直方图条形透明度 | `number` | - |
| `sparklineIsGroup` | 是否分组显示 | `boolean` | `false` |
| `sparklineSpacing` | 分组直方间距 | `number` | `0` |

---

## 常用配置示例

### 设置初始显示范围

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  slider: {
    x: {
      values: [0.5, 1.0],  // 初始显示后 50% 的数据
    },
  },
});
```

### 修改手柄图标为红色

```javascript
chart.options({
  slider: {
    x: {
      handleIconFill: 'red',
      handleIconStroke: 'darkred',
      handleIconSize: 12,
    },
  },
});
```

### 自定义手柄图标形状

```javascript
import { Circle } from '@antv/g';

chart.options({
  slider: {
    x: {
      handleIconShape: (type) => {
        // type 为 'start' 或 'end',分别表示左右手柄
        return new Circle({
          style: {
            r: 8,
            fill: type === 'start' ? '#FF6B9D' : '#00D9FF',
            stroke: '#fff',
            lineWidth: 2,
          },
        });
      },
      handleIconSize: 16,
    },
  },
});
```

### 完整样式配置

```javascript
chart.options({
  slider: {
    x: {
      values: [0.3, 0.7],
      // 选区样式
      selectionFill: '#1890ff',
      selectionFillOpacity: 0.2,
      // 滑轨样式
      trackFill: '#f0f0f0',
      trackSize: 20,
      // 手柄图标样式
      handleIconFill: '#fff',
      handleIconStroke: '#1890ff',
      handleIconSize: 14,
      handleIconRadius: 4,
      // 手柄标签样式
      handleLabelFill: '#333',
      handleLabelFontSize: 12,
    },
  },
});
```

---

## 常见错误与修正

### 错误 1:values 超出 [0, 1] 范围

```javascript
// ❌ values 必须在 [0, 1] 区间
chart.options({ slider: { x: { values: [50, 100] } } });

// ✅ values 是数据比例(0~1)
chart.options({ slider: { x: { values: [0.5, 1.0] } } });
```

### 错误 2:样式属性名错误

```javascript
// ❌ 错误的属性名
slider: { x: { handleFill: 'red' } }  // 不存在

// ✅ 正确的属性名(带前缀)
slider: { x: { handleIconFill: 'red' } }  // 正确
```

### 错误 3:与 scrollbar 混淆

```javascript
// slider:两端手柄可分别拖动,窗口大小可变
slider: { x: { values: [0.3, 0.7] } }

// scrollbar:固定窗口大小,只能整体滑动
scrollbar: { x: { ratio: 0.4 } }
```
references/components/g2-comp-title.md
---
id: "g2-comp-title"
title: "G2 图表标题配置(title)"
description: |
  G2 v5 通过顶层 title 字段为图表添加标题和副标题。
  支持自定义标题文本、字体样式、对齐方式和与绘图区的间距。
  title 字段可在 Chart 构造函数或 options 中配置。

library: "g2"
version: "5.x"
category: "components"
tags:
  - "title"
  - "图表标题"
  - "subtitle"
  - "副标题"
  - "标题样式"

related:
  - "g2-core-chart-init"
  - "g2-comp-axis-config"
  - "g2-comp-legend-config"

use_cases:
  - "为图表添加主标题和副标题"
  - "自定义标题字体、颜色和大小"
  - "控制标题对齐方式(左对齐/居中/右对齐)"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/component/title"
---

## 基本用法

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value' },
  title: {
    title: '月度销售额',        // 主标题
    subtitle: '单位:万元',     // 副标题
  },
});

chart.render();
```

## 完整配置项

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value' },
  title: {
    // ── 文本内容 ────────────────────────────
    title: '月度销售趋势分析',        // 主标题文本
    subtitle: '数据来源:2024年度报告',  // 副标题文本(可选)

    // ── 对齐 ─────────────────────────────────
    align: 'left',    // 'left'(默认)| 'center' | 'right'

    // ── 间距 ─────────────────────────────────
    spacing: 4,       // 主标题与副标题之间的间距,默认 2

    // ── 主标题样式 ────────────────────────────
    titleFontSize: 16,
    titleFontWeight: 'bold',
    titleFill: '#1d1d1d',
    titleSpacing: 8,     // 标题与图表内容区域之间的间距

    // ── 副标题样式 ────────────────────────────
    subtitleFontSize: 12,
    subtitleFill: '#8c8c8c',
    subtitleFontWeight: 'normal',
  },
});
```

## 居中标题

```javascript
chart.options({
  title: {
    title: '季度对比报告',
    subtitle: 'Q1-Q4 各季度销售数据',
    align: 'center',          // 居中对齐
    titleFontSize: 18,
    titleFontWeight: 600,
    subtitleFontSize: 13,
    subtitleFill: '#999',
  },
});
```

## 在构造函数中配置

```javascript
// title 也可以在 Chart 构造函数的选项中配置
const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
  title: {
    title: '销售趋势',
    align: 'center',
  },
});
```

## 常见错误与修正

### 错误:title 写成字符串而不是对象
```javascript
// ❌ 错误:title 字段必须是配置对象,不能直接写字符串
chart.options({
  title: '月度销售额',   // ❌ 不支持字符串
});

// ✅ 正确:title 字段是对象,主标题文本在 title.title 中
chart.options({
  title: {
    title: '月度销售额',   // ✅ 正确写法
  },
});
```

### 错误:把图表标题与坐标轴标题混淆
```javascript
// ❌ 错误:在 axis 里写整体图表标题
chart.options({
  axis: { x: { title: '月度销售额' } },  // ❌ 这是 X 轴标题,不是图表标题
});

// ✅ 图表标题用顶层 title 字段
chart.options({
  title: { title: '月度销售额' },     // ✅ 图表标题
  axis: { x: { title: '月份' } },     // ✅ X 轴标题
});
```
references/components/g2-comp-tooltip-config.md
---
id: "g2-comp-tooltip-config"
title: "G2 Tooltip 配置与自定义"
description: |
  G2 v5 tooltip 通过 tooltip 顶层配置或 interaction: [{ type: 'tooltip' }] 启用,
  支持自定义内容(items 字段过滤、render 函数完全自定义 HTML),
  groupKey 控制合并规则,crosshairs 显示十字准线。

library: "g2"
version: "5.x"
category: "components"
tags:
  - "tooltip"
  - "提示框"
  - "自定义"
  - "交互"
  - "spec"

related:
  - "g2-interaction-tooltip"
  - "g2-mark-line-basic"
  - "g2-mark-interval-basic"

use_cases:
  - "自定义 tooltip 显示字段和格式"
  - "多系列共享 tooltip(分组 tooltip)"
  - "完全自定义 tooltip HTML 模板"
  - "显示十字准线辅助定位"

difficulty: "intermediate"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/component/tooltip"
---

## 最小可运行示例(启用默认 tooltip)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 600,
  height: 400,
});

const data = [
  { month: '1月', value: 120, type: '销售额' },
  { month: '2月', value: 180, type: '销售额' },
  { month: '3月', value: 150, type: '销售额' },
];

chart.options({
  type: 'line',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  // tooltip 默认开启,此配置可自定义
  tooltip: {
    title: (d) => `${d.month} 数据`,   // 自定义标题
    items: [
      { channel: 'y', name: '销售额', valueFormatter: (v) => `¥${v}` },
    ],
  },
});

chart.render();
```

## 多字段 tooltip(显示多个信息项)

```javascript
const data = [
  { date: '2024-01', revenue: 1200, cost: 800, profit: 400 },
  { date: '2024-02', revenue: 1800, cost: 950, profit: 850 },
  { date: '2024-03', revenue: 1500, cost: 1000, profit: 500 },
];

chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'revenue' },
  tooltip: {
    title: 'date',
    // items 中每项对应一行显示内容
    items: [
      { field: 'revenue', name: '收入', valueFormatter: (v) => `¥${v}` },
      { field: 'cost', name: '成本', valueFormatter: (v) => `¥${v}` },
      { field: 'profit', name: '利润', valueFormatter: (v) => `¥${v}` },
    ],
  },
});
```

## 完全自定义 HTML(render 函数)

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
  tooltip: {
    render: (event, { title, items }) => {
      // 返回 HTML 字符串,完全自定义 tooltip 内容
      return `
        <div style="padding: 8px 12px; background: #fff; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.15);">
          <div style="font-weight: bold; margin-bottom: 6px;">${title}</div>
          ${items.map(({ name, value, color }) => `
            <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px;">
              <span style="width: 8px; height: 8px; background: ${color}; border-radius: 50%; display: inline-block;"></span>
              <span>${name}:</span>
              <span style="font-weight: 500;">${value}</span>
            </div>
          `).join('')}
        </div>
      `;
    },
  },
});
```

## 多系列共享 tooltip(groupKey)

```javascript
// 多折线图,鼠标悬停时同时显示所有系列的值
chart.options({
  type: 'view',
  data,
  children: [
    {
      type: 'line',
      encode: { x: 'month', y: 'value', color: 'type' },
      tooltip: {
        // groupKey:按哪个字段合并多系列的 tooltip
        // 默认按 x 值合并,所有系列在同一 x 处的点显示在一个 tooltip 中
        title: 'month',
      },
    },
  ],
  interaction: [{ type: 'tooltip', shared: true }],   // shared: true 显示共享 tooltip
});
```

## 完整配置项

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'month', y: 'value' },

  tooltip: {
    // 标题
    title: 'month',               // 字段名 或 函数 (d) => string

    // 显示项
    items: [
      {
        field: 'value',           // 数据字段名
        channel: 'y',             // 或者用通道名('x' | 'y' | 'color' 等)
        name: '销售额',           // 显示名称(覆盖默认)
        color: '#1890ff',         // 色块颜色
        // valueFormatter 接受:
        //   函数 (value) => string   ← 需要拼接单位时必须用这种形式
        //   d3-format 字符串 '.2f'   ← 只格式化数字本身,不支持追加文字
        valueFormatter: (v) => `${v} 万元`,   // ✅ 函数形式,可拼接单位
        // valueFormatter: '.2f',             // ✅ d3-format,仅格式化数字
        // valueFormatter: '.0f 米',          // ❌ 错误!d3-format 后不能追加文字
      },
    ],

    // 渲染
    render: (event, { title, items }) => `<div>...</div>`,  // 完全自定义 HTML

    // 触发方式
    // 在 interaction 中配置
  },

  // tooltip 交互(可补充配置)
  interaction: [
    {
      type: 'tooltip',
      shared: true,       // 多 Mark 共享 tooltip
      crosshairs: true,   // 显示十字准线
    },
  ],
});
```

## 十字准线(crosshairs)

十字准线通过 `interaction` 中的 `tooltip` 交互项配置:

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'month', y: 'value' },
  interaction: [
    {
      type: 'tooltip',
      crosshairs: true,           // 显示十字准线(默认开启)
      crosshairsStroke: '#aaa',   // 准线颜色
      crosshairsLineWidth: 1,     // 准线宽度
      crosshairsLineDash: [4, 4], // 虚线样式
    },
  ],
});
```

## 通过 CSS 自定义 tooltip 样式

当 `render` 函数的定制化程度不够时,可以通过 CSS 直接覆盖默认样式:

```javascript
// 方式 1:在页面全局 CSS 中覆盖
// .g2-tooltip { background: #1a1a1a; color: #fff; border-radius: 8px; }
// .g2-tooltip-title { font-size: 14px; font-weight: bold; }
// .g2-tooltip-list-item-value { color: #fadb14; }

// 方式 2:通过 interaction 的 css 参数(局部覆盖)
chart.options({
  interaction: [
    {
      type: 'tooltip',
      css: {
        '.g2-tooltip': {
          background: '#1a1a1a',
          color: '#fff',
          borderRadius: '8px',
          padding: '8px 12px',
        },
        '.g2-tooltip-title': {
          fontSize: '14px',
          fontWeight: 'bold',
          marginBottom: '6px',
        },
        '.g2-tooltip-list-item-value': {
          color: '#fadb14',
        },
      },
    },
  ],
});
```

**内置 CSS 类名:**
- `.g2-tooltip` — tooltip 容器
- `.g2-tooltip-title` — 标题
- `.g2-tooltip-list-item` — 单条数据项
- `.g2-tooltip-list-item-name-label` — 数据项名称
- `.g2-tooltip-list-item-value` — 数据项值
- `.g2-tooltip-list-item-marker` — 数据项颜色标记点

---

## 常见错误与修正

### 错误 1:tooltip.items 字段名与数据不匹配

```javascript
// ❌ 错误:数据字段是 'revenue' 但 items 写的是 'value'
const data = [{ month: '1月', revenue: 1200 }];
chart.options({
  tooltip: {
    items: [{ field: 'value' }],   // ❌ 数据中没有 'value' 字段
  },
});

// ✅ 正确:field 与数据字段名对应
chart.options({
  tooltip: {
    items: [{ field: 'revenue', name: '收入' }],   // ✅
  },
});
```

### 错误 2:render 函数忘记返回字符串

```javascript
// ❌ 错误:render 函数没有 return 语句
chart.options({
  tooltip: {
    render: (event, { title, items }) => {
      const html = `<div>${title}</div>`;
      // 忘记 return!
    },
  },
});

// ✅ 正确:必须返回 HTML 字符串
chart.options({
  tooltip: {
    render: (event, { title, items }) => {
      return `<div>${title}</div>`;   // ✅
    },
  },
});
```

### 错误 3:valueFormatter 用 d3-format 字符串拼接单位

`valueFormatter` 支持两种形式:函数 `(v) => string` 或 d3-format 字符串(如 `'.2f'`)。**d3-format 字符串只格式化数字本身,不能在后面追加文字单位**——带空格的写法如 `'.0f 米'` 会被当作无效格式符,导致显示异常或直接报错。

```javascript
// ❌ 错误:d3-format 字符串后追加文字单位
chart.options({
  tooltip: {
    items: [
      { field: 'distance', name: '距离', valueFormatter: '.0f 米' },  // ❌ 无效格式,d3-format 不支持拼接文字
      { field: 'price',    name: '价格', valueFormatter: '.2f 元' },  // ❌ 同上
    ],
  },
});

// ✅ 正确:需要拼接单位时必须用函数形式
chart.options({
  tooltip: {
    items: [
      { field: 'distance', name: '距离', valueFormatter: (v) => `${Math.round(v)} 米` },  // ✅ 函数形式
      { field: 'price',    name: '价格', valueFormatter: (v) => `¥${v.toFixed(2)}` },     // ✅ 函数形式
    ],
  },
});

// ✅ 仅格式化数字(不需要单位)时可用 d3-format 字符串
chart.options({
  tooltip: {
    items: [
      { field: 'ratio', name: '占比', valueFormatter: '.1%' },  // ✅ 纯 d3-format,无文字
      { field: 'value', name: '数值', valueFormatter: ',.0f' }, // ✅ 千分位整数
    ],
  },
});
```

### 错误 4:多系列 tooltip 未配置 shared

```javascript
// ❌ 问题:多折线图 tooltip 只显示当前 hover 的那条线
chart.options({
  type: 'view',
  children: [
    { type: 'line', encode: { x: 'month', y: 'value', color: 'type' } },
  ],
  // 没有 shared: true,tooltip 只显示鼠标直接悬停的那条线
});

// ✅ 正确:设置 shared: true 显示所有系列
chart.options({
  type: 'view',
  children: [
    { type: 'line', encode: { x: 'month', y: 'value', color: 'type' } },
  ],
  interaction: [{ type: 'tooltip', shared: true }],   // ✅
});
```

---

> **深色背景适配**:深色背景下 tooltip 样式需要适配时,使用 `theme: 'classicDark'` 自动切换,或通过 `interaction.tooltip.css` 手动控制。详见 [深色主题适配](../concepts/g2-concept-dark-theme-adaptation.md)
references/compositions/g2-comp-facet-circle.md
---
id: "g2-comp-facet-circle"
title: "G2 圆形分面(facetCircle)"
description: |
  facetCircle 将数据按某个字段分成多个子集,每个子集的图表排列在圆形轨迹上。
  与 facetRect 的矩形网格不同,facetCircle 适合展示循环性数据(如 12 个月的环形排列)。
  每个子图共享相同的 y 轴范围,便于对比。

library: "g2"
version: "5.x"
category: "compositions"
tags:
  - "facetCircle"
  - "圆形分面"
  - "facet"
  - "分面"
  - "循环"
  - "小多图"

related:
  - "g2-comp-facet-rect"
  - "g2-comp-repeat-matrix"

use_cases:
  - "12 个月的环形小多图对比"
  - "周期性时间数据的圆形分面(7天 / 12月)"
  - "循环分类的图表排列"

difficulty: "advanced"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/composition/facet-circle"
---

## 最小可运行示例(12 个月圆形分面)

```javascript
import { Chart } from '@antv/g2';

// 每个月的每日数据
const data = [];
const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
months.forEach((month, mi) => {
  for (let day = 1; day <= 10; day++) {
    data.push({ month, day, value: Math.random() * 100 });
  }
});

const chart = new Chart({ container: 'container', width: 640, height: 640 });

chart.options({
  type: 'facetCircle',
  data,
  encode: { position: 'month' },  // 按月份分面(决定每个子图的位置)
  children: [
    {
      type: 'interval',
      encode: { x: 'day', y: 'value', color: 'value' },
      scale: { color: { type: 'sequential', palette: 'blues' } },
      style: { lineWidth: 0 },
      coordinate: { type: 'polar' },  // 每个子图用极坐标
    },
  ],
});

chart.render();
```

## 常见错误与修正

### 错误:children 中没有用 coordinate: polar——子图是矩形而非环形
```javascript
// ❌ facetCircle 虽然分面排列是圆形,但子图本身仍可以是直角坐标
// 通常需要在 children 中指定 polar 坐标系才有圆形效果
chart.options({
  type: 'facetCircle',
  encode: { position: 'month' },
  children: [
    {
      type: 'interval',
      encode: { x: 'day', y: 'value' },
      // ❌ 没有 coordinate: polar,子图是普通柱状图,排列在圆圈上但不是极坐标
    },
  ],
});

// ✅ 通常在子图中加上 polar 坐标
children: [
  {
    type: 'interval',
    encode: { x: 'day', y: 'value' },
    coordinate: { type: 'polar' },  // ✅
  },
]
```
references/compositions/g2-comp-facet-rect.md
---
id: "g2-comp-facet-rect"
title: "G2 矩形分面(facetRect)"
description: |
  facetRect 将数据按分类字段拆分,在网格布局中为每个分类绘制一个独立的子图表。
  适合对比不同分组的数据分布和趋势。通过 type: 'facetRect' + encode.x/y 指定分面维度。

library: "g2"
version: "5.x"
category: "compositions"
tags:
  - "facetRect"
  - "分面"
  - "facet"
  - "小多图"
  - "trellis"
  - "网格布局"
  - "spec"

related:
  - "g2-core-view-composition"
  - "g2-mark-interval-basic"
  - "g2-mark-point-scatter"

use_cases:
  - "对比不同类别的数据分布"
  - "多维度时间序列对比"
  - "按地区/产品/部门分面展示"

difficulty: "intermediate"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/composition/facet-rect"
---

## 基本概念

```
chart.options({
  type: 'facetRect',
  encode: {
    x: '分面列字段',      // 按此字段将数据分为多列
    y: '分面行字段',      // 按此字段将数据分为多行(可选)
  },
  children: [
    { type: '子图 Mark', ... },    // 每个子图的配置(共用,数据自动过滤)
  ],
});
```

**关键规则**:
- `encode.x` → 按该字段的唯一值拆分为多列(列分面)
- `encode.y` → 按该字段的唯一值拆分为多行(行分面)
- `children` 中的 Mark 会自动接收过滤后的数据

## 单维度列分面(按类别分列)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 800,
  height: 300,
});

const data = [
  { month: 'Jan', value: 33, region: '华东' },
  { month: 'Feb', value: 78, region: '华东' },
  { month: 'Mar', value: 56, region: '华东' },
  { month: 'Jan', value: 45, region: '华南' },
  { month: 'Feb', value: 62, region: '华南' },
  { month: 'Mar', value: 71, region: '华南' },
  { month: 'Jan', value: 28, region: '华北' },
  { month: 'Feb', value: 39, region: '华北' },
  { month: 'Mar', value: 53, region: '华北' },
];

chart.options({
  type: 'facetRect',
  data,
  encode: { x: 'region' },     // 按 region 列分面(3 列)
  children: [
    {
      type: 'interval',
      encode: { x: 'month', y: 'value' },
      style: { fill: '#1890ff' },
    },
  ],
});

chart.render();
```

## 二维分面(行 + 列)

```javascript
const data = [
  { quarter: 'Q1', value: 100, region: '华东', type: '线上' },
  { quarter: 'Q2', value: 130, region: '华东', type: '线上' },
  { quarter: 'Q1', value: 80,  region: '华南', type: '线上' },
  { quarter: 'Q2', value: 95,  region: '华南', type: '线上' },
  { quarter: 'Q1', value: 60,  region: '华东', type: '线下' },
  { quarter: 'Q2', value: 85,  region: '华东', type: '线下' },
  { quarter: 'Q1', value: 40,  region: '华南', type: '线下' },
  { quarter: 'Q2', value: 55,  region: '华南', type: '线下' },
];

chart.options({
  type: 'facetRect',
  data,
  encode: {
    x: 'region',   // 列:华东/华南
    y: 'type',     // 行:线上/线下
  },
  children: [
    {
      type: 'interval',
      encode: { x: 'quarter', y: 'value' },
    },
  ],
});
```

## 分面折线图(多系列趋势对比)

```javascript
chart.options({
  type: 'facetRect',
  data,
  encode: { x: 'product' },          // 按产品分面
  children: [
    {
      type: 'view',
      children: [
        {
          type: 'area',
          encode: { x: 'month', y: 'sales' },
          style: { fill: '#1890ff', fillOpacity: 0.15 },
        },
        {
          type: 'line',
          encode: { x: 'month', y: 'sales' },
          style: { stroke: '#1890ff', lineWidth: 2 },
        },
      ],
    },
  ],
});
```

## 配置分面标题样式

```javascript
chart.options({
  type: 'facetRect',
  data,
  encode: { x: 'region' },
  children: [
    {
      type: 'interval',
      encode: { x: 'month', y: 'value' },
    },
  ],
  // 分面标题配置(通过 frame 字段)
  frame: false,                        // 是否显示边框
  // 标题通过 facetRect 的 title 配置
  title: {
    position: 'top',                   // 标题在顶部
    style: { fontSize: 13, fill: '#333', fontWeight: 'bold' },
  },
});
```

## 共享坐标轴(shareData)

```javascript
chart.options({
  type: 'facetRect',
  data,
  encode: { x: 'category' },
  shareData: true,          // 共享数据范围(坐标轴刻度一致)
  children: [
    {
      type: 'point',
      encode: { x: 'x', y: 'y', color: 'category' },
    },
  ],
});
```

## 常见错误与修正

### 错误:facetRect 的 children 中写了 data
```javascript
// ❌ 错误:不应在子 Mark 中再指定 data,否则分面过滤不生效
chart.options({
  type: 'facetRect',
  data: allData,
  encode: { x: 'region' },
  children: [
    {
      type: 'interval',
       allData,            // ❌ 会导致每个分面显示全量数据
      encode: { x: 'month', y: 'value' },
    },
  ],
});

// ✅ 正确:子 Mark 不指定 data,自动接收分面过滤后的数据
chart.options({
  type: 'facetRect',
  data: allData,
  encode: { x: 'region' },
  children: [
    {
      type: 'interval',
      encode: { x: 'month', y: 'value' },   // 不写 data,继承并自动过滤
    },
  ],
});
```

### 错误:encode 字段与数据字段名不匹配
```javascript
// ❌ 错误:encode.x 指定的分面字段在数据中不存在
chart.options({
  type: 'facetRect',
  data: [{ month: 'Jan', value: 33, area: '华东' }],
  encode: { x: 'region' },   // ❌ 数据中是 'area',不是 'region'
});

// ✅ 正确:字段名与数据保持一致
chart.options({
  type: 'facetRect',
  data,
  encode: { x: 'area' },     // ✅ 与数据字段名一致
});
```
references/compositions/g2-comp-geo-map.md
---
id: "g2-comp-geo-map"
title: "G2 地理地图(geoView / geoPath / d3Projection)"
description: |
  G2 v5 通过 geoView、geoPath 组合类型以及 d3Projection 地图投影实现地理可视化。
  geoView 是地理视图容器,geoPath 是地理路径 Mark(绘制行政区域等 GeoJSON)。
  支持 geoMercator、geoNaturalEarth1 等多种地图投影。
  数据格式为 GeoJSON 的 FeatureCollection。

library: "g2"
version: "5.x"
category: "compositions"
tags:
  - "geoView"
  - "geoPath"
  - "地图"
  - "GeoJSON"
  - "地理可视化"
  - "d3Projection"
  - "choropleth"
  - "choropleth map"

related:
  - "g2-comp-geoview"
  - "g2-core-view-composition"

use_cases:
  - "省份/城市分布热力地图(choropleth map)"
  - "世界地图可视化"
  - "地理数据的空间分布展示"

difficulty: "advanced"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/geo/geo/#choropleth"
---

## 核心概念

G2 地理可视化的三个核心组件:

| 组件 | 类型 | 说明 |
|------|------|------|
| `geoView` | composition | 地理视图容器,配置投影和视口 |
| `geoPath` | mark(在 geoView 内) | 绘制 GeoJSON 地理路径 |
| `d3Projection` | 投影函数 | 从 d3-geo 导出的投影函数(geoMercator 等) |

## 最小可运行示例(世界地图)

```javascript
import { Chart } from '@antv/g2';

async function renderMap() {
  // 加载 GeoJSON 数据
  const world = await fetch(
    'https://assets.antv.antgroup.com/g2/world.json',
  ).then((res) => res.json());

  const chart = new Chart({ container: 'container', width: 900, height: 500 });

  chart.options({
    type: 'geoView',       // 地理视图容器
    data: {
      type: 'fetch',
      value: 'https://assets.antv.antgroup.com/g2/world.json',
    },
    children: [
      {
        type: 'geoPath',   // 地理路径 Mark
        style: {
          fill: '#ccc',
          stroke: '#fff',
          lineWidth: 0.5,
        },
      },
    ],
  });

  chart.render();
}
renderMap();
```

## Choropleth Map(着色地图)

```javascript
import { Chart } from '@antv/g2';

const populationData = [
  { province: '广东', value: 12601 },
  { province: '山东', value: 10169 },
  // ...
];

const chart = new Chart({ container: 'container', width: 900, height: 600 });

chart.options({
  type: 'geoView',
  children: [
    {
      type: 'geoPath',
      data: {
        type: 'fetch',
        value: 'https://assets.antv.antgroup.com/g2/china.json',
      },
      // 将 GeoJSON 属性与业务数据 join
      join: {
         populationData,
        on: ['properties.name', 'province'],   // GeoJSON 属性字段 → 业务数据字段
      },
      encode: {
        color: 'value',   // 按 value 字段着色
      },
      style: {
        stroke: '#fff',
        lineWidth: 0.5,
      },
      scale: {
        color: {
          type: 'sequential',
          range: ['#eaf4d3', '#006d2c'],   // 颜色渐变范围
        },
      },
    },
  ],
});

chart.render();
```

## 自定义地图投影

```javascript
import { Chart } from '@antv/g2';
import { geoMercator, geoNaturalEarth1 } from '@antv/g2';  // 从 g2 导出 d3-geo 投影

const chart = new Chart({ container: 'container', width: 900, height: 500 });

chart.options({
  type: 'geoView',
  projection: {
    type: 'mercator',       // 内置投影名
    // type: 'naturalEarth1', // 自然地球投影
    // type: 'orthographic',  // 正射投影
  },
  children: [
    {
      type: 'geoPath',
       { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/world.json' },
      style: { fill: '#ccc', stroke: '#fff' },
    },
  ],
});

chart.render();
```

## 内置投影类型

```javascript
// G2 内置的 d3-geo 投影(在 projection.type 中指定)
// 'mercator'         - 墨卡托投影(适合局部区域)
// 'naturalEarth1'    - 自然地球投影(适合世界地图)
// 'orthographic'     - 正射投影(球形效果)
// 'equalEarth'       - 等面积地球投影
// 'albersUsa'        - 美国阿尔伯斯投影
```

## 常见错误与修正

### 错误:geoPath 没有放在 geoView 里
```javascript
// ❌ 错误:geoPath 必须在 geoView 内使用
chart.options({
  type: 'geoPath',   // ❌ 不能直接作为顶层类型
   geojson,
});

// ✅ 正确:geoPath 在 geoView children 中
chart.options({
  type: 'geoView',
  children: [
    { type: 'geoPath', data: { type: 'fetch', value: '...' } },  // ✅
  ],
});
```

### 错误:数据不是 GeoJSON 格式
```javascript
// ❌ 错误:geoPath 需要 GeoJSON FeatureCollection 格式
chart.options({
  type: 'geoView',
  children: [{
    type: 'geoPath',
     [{ province: '广东', lng: 113, lat: 23 }],  // ❌ 普通经纬度数据不行
  }],
});

// ✅ 正确:使用标准 GeoJSON FeatureCollection
// GeoJSON 格式:{ type: 'FeatureCollection', features: [...] }
chart.options({
  type: 'geoView',
  children: [{
    type: 'geoPath',
     { type: 'fetch', value: 'china.geojson' },  // ✅ GeoJSON 文件
  }],
});
```
references/compositions/g2-comp-geoview.md
---
id: "g2-comp-geoview"
title: "G2 地理视图(geoView)"
description: |
  geoView 基于 D3 地理投影,在 G2 中绘制地图可视化。
  支持多种投影方式(mercator、equalEarth、orthographic 等),
  数据需为 GeoJSON 格式,通过 geoPath mark 渲染地理形状。

library: "g2"
version: "5.x"
category: "compositions"
tags:
  - "geoView"
  - "地图"
  - "地理"
  - "GeoJSON"
  - "choropleth"
  - "地理投影"
  - "composition"

related:
  - "g2-mark-cell-heatmap"
  - "g2-scale-threshold"

use_cases:
  - "世界地图着色(choropleth map)"
  - "国家/省份数据地图展示"
  - "地理空间数据可视化"

difficulty: "advanced"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/geo/geo/#choropleth"
---

## 最小可运行示例(世界地图)

```javascript
import { Chart } from '@antv/g2';

// 需要提前加载 world.geo.json 数据
fetch('https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json')
  .then(res => res.json())
  .then(world => {
    // 将 TopoJSON 转换为 GeoJSON(需要 topojson-client)
    const countries = topojson.feature(world, world.objects.countries);

    const chart = new Chart({ container: 'container', width: 900, height: 500 });

    chart.options({
      type: 'geoView',
      coordinate: {
        type: 'projection',
        projection: 'equalEarth',   // 投影方式
      },
      children: [
        {
          type: 'geoPath',
           countries,
          encode: { color: 'id' },   // 按国家 id 着色
          style: {
            stroke: '#fff',
            lineWidth: 0.5,
            fillOpacity: 0.85,
          },
        },
      ],
    });

    chart.render();
  });
```

## 数据关联着色(choropleth)

```javascript
// 将 GeoJSON 与数据表格按 name 关联,实现数据着色
const gdpData = {
  CN: 17.7, US: 25.5, JP: 4.2, DE: 4.1,
  // ...
};

chart.options({
  type: 'geoView',
  coordinate: { type: 'projection', projection: 'mercator' },
  children: [
    {
      type: 'geoPath',
       geoJsonFeatures,
      encode: {
        color: (d) => gdpData[d.properties.iso_a2] || 0,  // 关联 GDP 数据
      },
      scale: {
        color: {
          type: 'sequential',
          palette: 'blues',
          unknown: '#eee',   // 无数据国家颜色
        },
      },
      tooltip: {
        items: [
          { field: 'properties.name', name: '国家' },
          { callback: (d) => gdpData[d.properties.iso_a2], name: 'GDP(万亿美元)' },
        ],
      },
    },
  ],
});
```

## 支持的投影方式

```javascript
// 常用投影
coordinate: { type: 'projection', projection: 'mercator' }       // 墨卡托(Web地图标准)
coordinate: { type: 'projection', projection: 'equalEarth' }     // 等面积地球投影
coordinate: { type: 'projection', projection: 'orthographic' }   // 正交(球形)
coordinate: { type: 'projection', projection: 'naturalEarth1' }  // 自然地球
coordinate: { type: 'projection', projection: 'albersUsa' }      // 美国阿尔伯斯
```

## 常见错误与修正

### 错误:数据不是 GeoJSON 格式——直接传统计数据
```javascript
// ❌ geoPath mark 需要 GeoJSON Feature/FeatureCollection 数据
chart.options({
  children: [{
    type: 'geoPath',
     [{ country: 'China', gdp: 17.7 }],  // ❌ 不是 GeoJSON
  }],
});

// ✅ 需要 GeoJSON 格式
chart.options({
  children: [{
    type: 'geoPath',
     { type: 'FeatureCollection', features: [...] },  // ✅ GeoJSON
  }],
});
```
references/compositions/g2-comp-repeat-matrix.md
---
id: "g2-comp-repeat-matrix"
title: "G2 重复矩阵(repeatMatrix)"
description: |
  G2 v5 repeatMatrix 组合类型将同一图表按两个维度字段重复排列成矩阵,
  每个格子共享相同的 Mark 配置,x 轴和 y 轴分别对应一个分类字段,
  适合展示多变量之间的两两关系(散点图矩阵)。

library: "g2"
version: "5.x"
category: "compositions"
tags:
  - "重复矩阵"
  - "repeatMatrix"
  - "散点图矩阵"
  - "多变量"
  - "分面"
  - "spec"

related:
  - "g2-comp-facet-rect"
  - "g2-mark-point-scatter"
  - "g2-core-view-composition"

use_cases:
  - "多变量两两散点图矩阵"
  - "多维度数据的相关性探索"
  - "对角线展示分布直方图"

difficulty: "advanced"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/matrix"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 800,
  height: 800,
});

// 多维度数据(每行是一个样本,多个数值字段)
const data = [
  { sepalLength: 5.1, sepalWidth: 3.5, petalLength: 1.4, petalWidth: 0.2, species: 'setosa' },
  { sepalLength: 4.9, sepalWidth: 3.0, petalLength: 1.4, petalWidth: 0.2, species: 'setosa' },
  { sepalLength: 7.0, sepalWidth: 3.2, petalLength: 4.7, petalWidth: 1.4, species: 'versicolor' },
  { sepalLength: 6.4, sepalWidth: 3.2, petalLength: 4.5, petalWidth: 1.5, species: 'versicolor' },
  { sepalLength: 6.3, sepalWidth: 3.3, petalLength: 6.0, petalWidth: 2.5, species: 'virginica' },
  // ...更多数据
];

chart.options({
  type: 'repeatMatrix',
  data,
  encode: {
    x: ['sepalLength', 'sepalWidth', 'petalLength'],   // 列变量
    y: ['sepalLength', 'sepalWidth', 'petalLength'],   // 行变量
  },
  children: [
    {
      type: 'point',
      encode: { color: 'species' },
      style: { r: 3, fillOpacity: 0.7 },
    },
  ],
});

chart.render();
```

## 完整散点图矩阵(含对角线)

```javascript
chart.options({
  type: 'repeatMatrix',
  data,
  encode: {
    x: ['sepalLength', 'sepalWidth', 'petalLength', 'petalWidth'],
    y: ['sepalLength', 'sepalWidth', 'petalLength', 'petalWidth'],
  },
  // 格子间距
  padding: 10,
  children: [
    {
      type: 'point',
      encode: { color: 'species' },
      style: { r: 2.5, fillOpacity: 0.6 },
      legend: { color: { position: 'top' } },
    },
  ],
});
```

## 与 facetRect 的对比

```javascript
// repeatMatrix:x/y encode 均为变量数组,自动排列成 n×n 矩阵
chart.options({
  type: 'repeatMatrix',
  encode: {
    x: ['var1', 'var2', 'var3'],
    y: ['var1', 'var2', 'var3'],
  },
  children: [{ type: 'point', encode: { color: 'category' } }],
});

// facetRect:按单个分类字段的不同值分面(每个值一个格子,排成一行或一列)
chart.options({
  type: 'facetRect',
  encode: { x: 'region' },   // 按 region 的不同值分成多列
  children: [
    {
      type: 'interval',
      encode: { x: 'month', y: 'sales' },
    },
  ],
});
```

## 常见错误与修正

### 错误 1:encode.x/y 写成单个字段而非数组

```javascript
// ❌ 错误:repeatMatrix 的 encode.x/y 必须是字段名数组
chart.options({
  type: 'repeatMatrix',
  encode: {
    x: 'sepalLength',   // ❌ 单个字段名
    y: 'sepalWidth',
  },
});

// ✅ 正确:x/y 都必须是数组
chart.options({
  type: 'repeatMatrix',
  encode: {
    x: ['sepalLength', 'sepalWidth', 'petalLength'],   // ✅ 数组
    y: ['sepalLength', 'sepalWidth', 'petalLength'],
  },
});
```

### 错误 2:将散点图矩阵与普通分面混淆

```javascript
// ❌ 错误:想做散点图矩阵却用了 facetRect
chart.options({
  type: 'facetRect',
  encode: {
    x: ['sepalLength', 'sepalWidth'],   // ❌ facetRect 的 encode.x 只接受单个字段
  },
});

// ✅ 正确:多变量两两比较用 repeatMatrix
chart.options({
  type: 'repeatMatrix',
  encode: {
    x: ['sepalLength', 'sepalWidth'],   // ✅
    y: ['sepalLength', 'sepalWidth'],
  },
  children: [{ type: 'point', encode: { color: 'species' } }],
});
```
references/compositions/g2-comp-space-flex.md
---
id: "g2-comp-space-flex"
title: "G2 弹性布局(spaceFlex)"
description: |
  spaceFlex 将多个子图按弹性比例(ratio)排列在行(row)或列(col)方向。
  类似 CSS flexbox,每个子图的大小由 ratio 数组按比例分配画布空间。
  适合制作不等宽的多图并排布局,比 repeatMatrix 更灵活。

library: "g2"
version: "5.x"
category: "compositions"
tags:
  - "spaceFlex"
  - "弹性布局"
  - "多图"
  - "flex"
  - "并排"
  - "composition"

related:
  - "g2-comp-space-layer"
  - "g2-comp-facet-rect"
  - "g2-comp-repeat-matrix"

use_cases:
  - "左宽右窄的双图布局(如 2:1 比例)"
  - "多图等分排列(如 3 个等宽图表)"
  - "不等宽的多图并排展示"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/composition/space-flex"
---

## 最小可运行示例(左右 2:1 布局)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 900, height: 400 });

chart.options({
  type: 'spaceFlex',
  width: 900,
  height: 400,
  direction: 'row',      // 'row'(横向)或 'col'(纵向)
  ratio: [2, 1],         // 左图占 2/3,右图占 1/3
  padding: 20,           // 子图间距
  children: [
    // 左图:折线图(占 2/3 宽度)
    {
      type: 'line',
       salesData,
      encode: { x: 'month', y: 'value', color: 'city' },
      title: '月度销售趋势',
    },
    // 右图:饼图(占 1/3 宽度)
    {
      type: 'interval',
       categoryData,
      encode: { y: 'value', color: 'type' },
      transform: [{ type: 'stackY' }],
      coordinate: { type: 'theta', outerRadius: 0.85 },
      title: '类别占比',
    },
  ],
});

chart.render();
```

## 纵向布局(上大下小)

```javascript
chart.options({
  type: 'spaceFlex',
  width: 640,
  height: 700,
  direction: 'col',      // 纵向排列
  ratio: [3, 1],         // 上图占 3/4 高度,下图占 1/4
  children: [
    {
      type: 'line',
       timeData,
      encode: { x: 'date', y: 'value', color: 'type' },
    },
    // 缩略轴图(底部小图)
    {
      type: 'line',
       timeData,
      encode: { x: 'date', y: 'value', color: 'type' },
      style: { lineWidth: 1 },
      axis: { y: false },
    },
  ],
});
```

## 等分三图并排

```javascript
chart.options({
  type: 'spaceFlex',
  direction: 'row',
  ratio: [1, 1, 1],   // 三图等宽
  children: [chart1Config, chart2Config, chart3Config],
});
```

## 常见错误与修正

### 错误:ratio 数组长度与 children 不一致
```javascript
// ❌ 错误:3 个子图但 ratio 只有 2 个值
chart.options({
  type: 'spaceFlex',
  ratio: [2, 1],       // ❌ 只有 2 个比例值
  children: [c1, c2, c3],  // 3 个子图
});

// ✅ ratio 数组长度必须等于 children 数组长度
chart.options({
  ratio: [2, 1, 1],   // ✅ 3 个比例值对应 3 个子图
  children: [c1, c2, c3],
});
```

### 错误:子图没有设置宽高——spaceFlex 会自动计算,不需要子图单独设置
```javascript
// ⚠️  子图单独设置 width/height 会覆盖 spaceFlex 的自动布局
children: [
  { type: 'line', width: 400, height: 300, ... },  // ⚠️  不要单独设置
]

// ✅ 子图只设置内容,宽高由父级 spaceFlex 按 ratio 自动分配
children: [
  { type: 'line', data: ..., encode: { ... } },  // ✅ 不设置宽高
]
```
references/compositions/g2-comp-space-layer.md
---
id: "g2-comp-space-layer"
title: "G2 图层叠加(spaceLayer / view 多 mark)"
description: |
  spaceLayer 将多个视图堆叠在同一区域(共享坐标轴),
  实现折线图 + 柱状图叠加、折线图 + 散点图叠加等复合图表。
  更常见的用法是在单个 view 中使用 children 数组配置多个 mark。

library: "g2"
version: "5.x"
category: "compositions"
tags:
  - "spaceLayer"
  - "图层"
  - "叠加"
  - "复合图表"
  - "双轴图"
  - "view"
  - "children"

related:
  - "g2-core-view-composition"
  - "g2-comp-facet-rect"

use_cases:
  - "柱状图 + 折线图叠加(双指标对比)"
  - "散点图 + 趋势线叠加"
  - "折线图 + 置信区间面积叠加"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/composition/space-layer"
---

## 最小可运行示例(柱状图 + 折线图)

```javascript
import { Chart } from '@antv/g2';

const data = [
  { month: 'Jan', sales: 200, growth: 15 },
  { month: 'Feb', sales: 280, growth: 22 },
  { month: 'Mar', sales: 320, growth: 8 },
  { month: 'Apr', sales: 250, growth: -5 },
  { month: 'May', sales: 410, growth: 18 },
];

const chart = new Chart({ container: 'container', width: 640, height: 400 });

// 方式一:type: 'view' + children(推荐,最简洁)
chart.options({
  type: 'view',
  data,
  children: [
    // 柱状图:展示销售额
    {
      type: 'interval',
      encode: { x: 'month', y: 'sales', color: '#5B8FF9' },
      style: { fillOpacity: 0.8 },
      axis: { y: { title: '销售额' } },
    },
    // 折线图:展示增长率(共享 x 轴)
    {
      type: 'line',
      encode: { x: 'month', y: 'growth' },
      scale: { y: { independent: true } },  // 独立 y 轴(双 Y 轴)
      style: { lineWidth: 2.5, stroke: '#F4664A' },
      axis: { y: { position: 'right', title: '增长率 (%)' } },
    },
  ],
});

chart.render();
```

## 折线图 + 散点(mark 复合)

```javascript
chart.options({
  type: 'view',
  data,
  children: [
    {
      type: 'line',
      encode: { x: 'date', y: 'value', color: 'type' },
      style: { lineWidth: 2 },
    },
    {
      type: 'point',
      encode: { x: 'date', y: 'value', color: 'type' },
      style: { r: 4, lineWidth: 1, fill: '#fff' },
    },
  ],
});
```

## 面积图 + 折线(置信区间)

```javascript
chart.options({
  type: 'view',
  data: confidenceData,
  children: [
    // 置信区间(面积)
    {
      type: 'area',
      encode: { x: 'date', y: 'upper', y1: 'lower', color: '#5B8FF9' },
      style: { fillOpacity: 0.2 },
    },
    // 中线(折线)
    {
      type: 'line',
      encode: { x: 'date', y: 'mean' },
      style: { lineWidth: 2, stroke: '#5B8FF9' },
    },
  ],
});
```

## 常见错误与修正

### 错误:双 Y 轴不设置 independent: true——两组数据被映射到同一 y 轴范围
```javascript
// ❌ sales(0~400)和 growth(-10~25)共用一个 y 轴,growth 曲线近乎水平
chart.options({
  type: 'view',
  children: [
    { type: 'interval', encode: { x: 'month', y: 'sales' } },
    { type: 'line',     encode: { x: 'month', y: 'growth' } },  // ❌ 没有独立 y 轴
  ],
});

// ✅ 第二个 y 轴设置 independent: true
chart.options({
  type: 'view',
  children: [
    { type: 'interval', encode: { x: 'month', y: 'sales' } },
    {
      type: 'line',
      encode: { x: 'month', y: 'growth' },
      scale: { y: { independent: true } },  // ✅ 独立比例尺
      axis: { y: { position: 'right' } },    // ✅ 放在右侧
    },
  ],
});
```
references/compositions/g2-comp-timing-keyframe.md
---
id: "g2-comp-timing-keyframe"
title: "G2 timingKeyframe 关键帧动画组合"
description: |
  timingKeyframe 是 G2 v5 的组合类型,将多个图表视图按时序播放形成关键帧动画。
  各子视图依次渲染并在相邻帧之间自动插值过渡,实现数据故事讲述效果。
  参见 g2-animation-keyframe 获取详细配置和示例。

library: "g2"
version: "5.x"
category: "compositions"
tags:
  - "timingKeyframe"
  - "关键帧动画"
  - "数据故事"
  - "morphing"
  - "composition"
  - "动画组合"

related:
  - "g2-animation-keyframe"
  - "g2-animation-intro"
  - "g2-core-view-composition"

use_cases:
  - "图表类型间的形变动画(柱状图→折线图)"
  - "数据随时间演变的动画展示"
  - "可视化数据故事讲述"

difficulty: "advanced"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/composition/timing-keyframe"
---

## 核心概念

`timingKeyframe` 是 composition 类型,每个 child 是一个"关键帧"视图。
系统自动在相邻关键帧之间进行数据和图形的插值过渡动画。

详细配置和示例请参见 [g2-animation-keyframe](./g2-animation-keyframe.md)。

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const data = [
  { month: 'Jan', value: 83 },
  { month: 'Feb', value: 60 },
  { month: 'Mar', value: 95 },
];

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'timingKeyframe',
  duration: 1000,
  iterationCount: 'infinite',
  direction: 'alternate',
  children: [
    {
      type: 'interval',
      data,
      encode: { x: 'month', y: 'value', color: 'month' },
    },
    {
      type: 'line',
      data,
      encode: { x: 'month', y: 'value' },
    },
  ],
});

chart.render();
```

## 配置项速查

```javascript
chart.options({
  type: 'timingKeyframe',
  duration: 1000,                  // 关键帧间过渡时长(毫秒)
  iterationCount: 1,               // 循环次数('infinite' = 无限)
  direction: 'normal',             // 'normal' | 'reverse' | 'alternate' | 'reverse-alternate'
  easing: 'ease-in-out-sine',     // 缓动函数
  children: [/* 各关键帧视图 */],
});
```
references/compositions/g2-comp-view.md
---
id: "g2-comp-view"
title: "G2 View 组合"
description: |
  View 组合用于创建多视图图表。可以将多个 mark 组合在一起,
  共享数据、比例尺、坐标轴等配置。

library: "g2"
version: "5.x"
category: "compositions"
tags:
  - "组合"
  - "view"
  - "多视图"
  - "复合图表"

related:
  - "g2-comp-space-layer"
  - "g2-comp-space-flex"
  - "g2-core-chart-init"

use_cases:
  - "多系列图表"
  - "复合图表"
  - "共享配置的多 mark 图表"

anti_patterns:
  - "单一 mark 图表不需要 View 组合"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/composition"
---

## 核心概念

View 组合允许将多个 mark 组合在一起:
- 共享数据和配置
- 统一管理比例尺和坐标轴
- 支持嵌套组合

**特点:**
- 子 mark 继承父级配置
- 支持数据合并
- 可配置坐标轴、图例等

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'view',
  data: [
    { month: 'Jan', value: 100, type: 'A' },
    { month: 'Feb', value: 120, type: 'A' },
    { month: 'Jan', value: 80, type: 'B' },
    { month: 'Feb', value: 90, type: 'B' },
  ],
  children: [
    {
      type: 'line',
      encode: { x: 'month', y: 'value', color: 'type' },
    },
    {
      type: 'point',
      encode: { x: 'month', y: 'value', color: 'type' },
    },
  ],
});

chart.render();
```

## 常用变体

### 共享坐标轴配置

```javascript
chart.options({
  type: 'view',
  data,
  axis: {
    x: { title: 'Month' },
    y: { title: 'Value' },
  },
  children: [
    { type: 'line', encode: { x: 'month', y: 'value', color: 'type' } },
    { type: 'point', encode: { x: 'month', y: 'value', color: 'type' } },
  ],
});
```

### 共享比例尺

```javascript
chart.options({
  type: 'view',
  data,
  scale: {
    color: {
      range: ['#1890ff', '#52c41a'],
    },
  },
  children: [
    { type: 'line', encode: { x: 'month', y: 'value', color: 'type' } },
    { type: 'point', encode: { x: 'month', y: 'value', color: 'type' } },
  ],
});
```

### 子 mark 独立数据

```javascript
chart.options({
  type: 'view',
  children: [
    {
      type: 'interval',
      data: [{ category: 'A', value: 100 }],
      encode: { x: 'category', y: 'value' },
    },
    {
      type: 'line',
      data: [{ x: 0, y: 50 }, { x: 1, y: 150 }],
      encode: { x: 'x', y: 'y' },
      scale: { x: { type: 'identity' }, y: { domain: [0, 200] } },
    },
  ],
});
```

### 带图例配置

```javascript
chart.options({
  type: 'view',
  data,
  encode: { color: 'type' },
  legend: {
    color: { position: 'top' },
  },
  children: [
    { type: 'line', encode: { x: 'month', y: 'value', color: 'type' } },
    { type: 'point', encode: { x: 'month', y: 'value', color: 'type' } },
  ],
});
```

## 完整类型参考

```typescript
interface ViewComposition {
  type: 'view';
  data?: DataOption;
  encode?: EncodeOption;
  scale?: ScaleOption;
  axis?: AxisOption;
  legend?: LegendOption;
  transform?: TransformOption[];
  slider?: SliderOption;
  children: MarkSpec[];  // 子 mark 数组
}
```

## 与 SpaceLayer/SpaceFlex 的区别

| 组合类型 | 用途 | 特点 |
|---------|------|------|
| view | 多 mark 叠加 | 共享坐标系 |
| spaceLayer | 多图层叠加 | 独立坐标系 |
| spaceFlex | 多视图排列 | 并排/堆叠布局 |

## 常见错误与修正

### 错误 1:children 格式错误

```javascript
// ❌ 错误:children 应该是数组
chart.options({
  type: 'view',
  children: { type: 'line', ... },
});

// ✅ 正确
chart.options({
  type: 'view',
  children: [{ type: 'line', ... }],
});
```

### 错误 2:子 mark 未指定 type

```javascript
// ❌ 错误:子 mark 必须有 type
chart.options({
  type: 'view',
  children: [{ encode: { x: 'a', y: 'b' } }],
});

// ✅ 正确
chart.options({
  type: 'view',
  children: [{ type: 'line', encode: { x: 'a', y: 'b' } }],
});
```

### 错误 3:混淆 data 和 children 的数据

```javascript
// ⚠️ 注意:View 的 data 会与子 mark 的 data 合并
// 如果子 mark 有自己的 data,会覆盖父级的 data

// 方式 1:父级提供数据
chart.options({
  type: 'view',
  data,
  children: [
    { type: 'line', encode: { x: 'a', y: 'b' } },
  ],
});

// 方式 2:子 mark 独立数据
chart.options({
  type: 'view',
  children: [
    { type: 'line', data, encode: { x: 'a', y: 'b' } },
  ],
});
```

### 错误 4:density 和 boxplot 使用不当导致白屏

```javascript
// ❌ 错误:density 和 boxplot 的数据格式不正确
// density 需要经过 KDE 转换后的数据,包含 y 和 size 字段
// boxplot 需要原始数据进行内部统计计算
chart.options({
  type: 'view',
  data: rawData,
  children: [
    {
      type: 'density',
      encode: { x: 'category', y: 'value', size: 'size' },
    },
    {
      type: 'boxplot',
      encode: { x: 'category', y: 'value' },
    },
  ],
});

// ✅ 正确:使用 transform 进行 KDE 转换,确保数据格式正确
chart.options({
  type: 'view',
  data: {
    type: 'inline',
    value: rawData,
  },
  children: [
    {
      type: 'density',
      data: {
        transform: [
          {
            type: 'kde',
            field: 'value',
            groupBy: ['category'],
            size: 50, // 控制密度曲线的精细程度
          },
        ],
      },
      encode: {
        x: 'category',
        y: 'value',
        size: 'size',
        series: 'category',
      },
      style: {
        fillOpacity: 0.7,
      },
      tooltip: false,
    },
    {
      type: 'boxplot',
      encode: {
        x: 'category',
        y: 'value',
        series: 'category',
        shape: 'violin', // 可选,用于小提琴图
      },
      style: {
        opacity: 0.8,
        strokeOpacity: 0.6,
        point: false, // 可选,隐藏异常点
      },
    },
  ],
});
```
references/concepts/g2-concept-chart-selection.md
---
id: "g2-concept-chart-selection"
title: "G2 图表类型选择指南"
description: |
  根据数据特征和分析目的选择合适的图表类型。
  覆盖比较、趋势、占比、分布、关系六大场景,
  对应 G2 的具体实现方式,帮助避免"用错图表"的常见错误。

library: "g2"
version: "5.x"
category: "concepts"
tags:
  - "图表选择"
  - "chart selection"
  - "可视化设计"
  - "柱状图"
  - "折线图"
  - "饼图"
  - "散点图"
  - "决策"

related:
  - "g2-concept-visual-channels"
  - "g2-mark-interval-basic"
  - "g2-mark-line-basic"
  - "g2-mark-arc-pie"
  - "g2-mark-point-scatter"

use_cases:
  - "根据用户需求选择正确的图表类型"
  - "避免在不合适场景使用饼图或折线图"
  - "理解何时用 G2 图表 vs G6 图分析"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
---

## 核心决策树

```
你的数据和目的是什么?

├── 比较类别间的大小 → 柱状图 / 条形图
├── 展示随时间的变化趋势 → 折线图 / 面积图
├── 展示部分与整体的占比 → 饼图 / 环形图 / 堆叠柱状图
├── 展示两个变量的相关性 → 散点图 / 气泡图
├── 展示数据的分布规律 → 直方图 / 箱线图 / 小提琴图
└── 展示节点间的关系网络 → G6 图(force/dagre/tree 布局)
```

## 场景一:比较(Comparison)

**目的**:比较不同类别/时间点的数值大小

| 数据特征 | 推荐图表 | G2 实现 |
|---------|---------|---------|
| 类别 ≤ 10,竖向标签可读 | **柱状图** | `type: 'interval'` |
| 类别名称较长 / 类别多 | **条形图**(水平) | `type: 'interval'` + `coordinate: { transform: [{ type: 'transpose' }] }` |
| 多个系列并排比较 | **分组柱状图** | `transform: [{ type: 'dodgeX' }]` |
| 展示子类在总量中的贡献 | **堆叠柱状图** | `transform: [{ type: 'stackY' }]` |

```javascript
// 柱状图(最常见的比较图)
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
  transform: [{ type: 'sortX', by: 'y', reverse: true }],  // 按值降序
});
```

## 场景二:趋势(Trend)

**目的**:展示数值随时间或序列的变化

| 数据特征 | 推荐图表 | G2 实现 |
|---------|---------|---------|
| 单一指标时间趋势 | **折线图** | `type: 'line'` |
| 多指标对比趋势 | **多系列折线图** | `type: 'line'` + `encode.color: 'series'` |
| 强调面积/累积量 | **面积图** | `type: 'area'` |
| 展示数量随时间增减 | **面积图**(堆叠)| `type: 'area'` + `transform: [{ type: 'stackY' }]` |

```javascript
// 多系列折线图
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value', color: 'series' },
  labels: [{ text: 'series', selector: 'last', position: 'right' }],
});
```

## 场景三:占比(Part-to-Whole)

**目的**:展示部分占整体的比例

| 数据特征 | 推荐图表 | G2 实现 | 注意 |
|---------|---------|---------|------|
| 类别 ≤ 5,强调比例 | **饼图** | `interval` + `theta` 坐标 | 类别多时难区分 |
| 需要中心留白 | **环形图** | 饼图 + `innerRadius` | 可在中心放总数 |
| 类别多,强调排名 | **百分比堆叠柱状图** | `stackY` + `normalizeY` | |
| 多层级占比 | **旭日图** | 暂用 `sunburst` 插件 | |

```javascript
// 饼图(类别 ≤ 5 时)
chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'category' },
  transform: [{ type: 'stackY' }],
  coordinate: { type: 'theta', outerRadius: 0.85 },
  labels: [{
    text: (d) => `${d.category}\n${d.pct}%`,
    position: 'outside',
    connector: true,
  }],
});
```

## 场景四:相关性(Correlation)

**目的**:探索两个或多个变量之间的关系

| 数据特征 | 推荐图表 | G2 实现 |
|---------|---------|---------|
| 两个数值变量 | **散点图** | `type: 'point'` |
| 两个数值 + 第三数值维度 | **气泡图** | `point` + `encode.size` |
| 多变量热力矩阵 | **热力图** | `type: 'cell'` |
| 展示分布+相关 | **散点图 + 趋势线** | `view` + `point` + `line` |

```javascript
// 气泡图
// 颜色映射表:scale.color.range 和 fill 回调共用
const COLOR_MAP = { 'Asia': '#fb7678', 'Europe': '#81e7ee', 'Americas': '#5B8FF9' };

chart.options({
  type: 'point',
  data,
  encode: {
    x: 'income',
    y: 'happiness',
    size: 'population',  // 第三数值维度
    color: 'region',
    shape: 'point',
  },
  scale: {
    size: { type: 'sqrt', range: [4, 40] },    // sqrt 比例尺 + 合适的气泡范围
    color: { range: Object.values(COLOR_MAP) },
  },
  style: {
    fillOpacity: 0.85,
    lineWidth: 0,
    // 径向渐变:从白色中心到映射色边缘,模拟 3D 球体质感
    // 通过 COLOR_MAP[datum.region] 获取颜色,与 scale.color.range 保持一致
    fill: (datum) => {
      const color = COLOR_MAP[datum.region];
      return `radial-gradient(circle at 35% 35%, rgb(255,255,255) 0%, ${color} 100%)`;
    },
    shadowBlur: 10,
    shadowColor: 'rgba(0, 0, 0, 0.15)',
    shadowOffsetY: 5,
  },
  legend: { size: false },
});
```

## 场景五:分布(Distribution)

**目的**:了解数据的分布规律

| 数据特征 | 推荐图表 | G2 实现 |
|---------|---------|---------|
| 单变量分布 | **直方图** | `type: 'interval'` + `transform: [{ type: 'binX' }]` |
| 多组分布比较 | **箱线图** | `type: 'boxplot'` |
| 展示中位数/四分位 | **箱线图** | `type: 'boxplot'` |

```javascript
// 直方图
chart.options({
  type: 'interval',
  data,
  encode: { x: 'value', y: 'count' },
  transform: [{ type: 'binX', y: 'count' }],
});
```

## 场景六:关系网络(Relationship)

**目的**:展示实体间的连接关系、层次结构、流向

| 数据特征 | 推荐库 | G6 布局 |
|---------|--------|---------|
| 无层级的网络关系 | **G6** | `force`(力导向) |
| 有方向的流程/依赖 | **G6** | `dagre`(有向无环图) |
| 单根树形层级 | **G6** | `compactBox`(树形)|
| 对等环状关系 | **G6** | `circular`(环形) |

```javascript
// G6 知识图谱(力导向)
const graph = new Graph({
  layout: { type: 'force', preventOverlap: true },
  data: { nodes, edges },
});
await graph.render();
```

## 快速选择口诀

```
比较用柱状,趋势用折线,
占比用饼图,关系用散点,
分布用直方,层级用树形,
网络用 G6,复杂用组合。
```

## 常见错误

### 错误 1:用折线图表示无时间顺序的分类数据

```javascript
// ❌ 误用:城市名称没有顺序,不应该用折线(会误导"趋势"感知)
chart.options({
  type: 'line',
   [{ city: '北京', gdp: 3.6 }, { city: '上海', gdp: 4.3 }],
  encode: { x: 'city', y: 'gdp' },   // ❌ 城市是无序分类,不是时序
});

// ✅ 正确:分类比较用柱状图
chart.options({
  type: 'interval',
  encode: { x: 'city', y: 'gdp' },
});
```

### 错误 2:用饼图展示 8+ 个类别

```javascript
// ❌ 误用:10个类别的饼图,各扇区难以区分
chart.options({
  type: 'interval',
  coordinate: { type: 'theta' },
  // 如果有 10 个国家/地区...很难读取
});

// ✅ 正确:超过 5 类改用排序条形图
chart.options({
  type: 'interval',
  encode: { x: 'country', y: 'value' },
  coordinate: { transform: [{ type: 'transpose' }] },
  transform: [{ type: 'sortX', by: 'y', reverse: true }],
});
```
references/concepts/g2-concept-color-theory.md
---
id: "g2-concept-color-theory"
title: "G2 配色理论"
description: |
  数据可视化中颜色的三种使用模式:分类色板(区分类别)、
  顺序色阶(表示数值大小)、发散色阶(表示正负偏差)。
  覆盖 G2 scale.color 配置方法和常见配色误用。

library: "g2"
version: "5.x"
category: "concepts"
tags:
  - "配色"
  - "color"
  - "色板"
  - "顺序色阶"
  - "发散色阶"
  - "分类色板"
  - "palette"
  - "scale.color"

related:
  - "g2-concept-visual-channels"
  - "g2-core-encode-channel"
  - "g2-theme-builtin"

use_cases:
  - "为不同数据类型选择正确的颜色模式"
  - "配置 G2 scale.color 实现正确的颜色映射"
  - "避免颜色误导数据读者"

difficulty: "intermediate"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
---

## 三种颜色使用模式

### 1. 分类色板(Categorical Palette)

**用途**:区分不同类别(定性数据),颜色之间**没有大小关系**

```javascript
// 场景:多系列折线图,用颜色区分产品线
chart.options({
  type: 'line',
  data,
  encode: { x: 'month', y: 'sales', color: 'product' },
  // 默认就是分类色板,无需配置
  // 如需自定义:
  scale: {
    color: {
      type: 'ordinal',
      range: ['#1890ff', '#52c41a', '#fa8c16', '#f5222d', '#722ed1'],
    },
  },
});
```

**规则**:
- 最多 **8 个**颜色,超过则难以区分
- 颜色之间亮度相近(避免某类特别突出)
- 考虑色盲友好性(红绿色盲常见,避免仅用红/绿区分)

### 2. 顺序色阶(Sequential Scale)

**用途**:表示数值从小到大,颜色从浅到深(或一种色调变化)

```javascript
// 场景:热力图、地图、气泡图的颜色深度
chart.options({
  type: 'cell',
  data,
  encode: { x: 'weekday', y: 'hour', color: 'count' },
  scale: {
    color: {
      type: 'sequential',     // 顺序比例尺
      palette: 'blues',        // 单色系:whites → blues
      // 常用内置色阶:'blues' | 'greens' | 'oranges' | 'reds' | 'purples'
      // 多色系:'YlOrRd' | 'YlGnBu' | 'BuPu' | 'GnBu'
    },
  },
});
```

**规则**:
- 数值越大 → 颜色越深(感知自然)
- 使用**单色系**(同色调深浅变化)或**多色系渐变**
- 不要用分类色板表示数值(红/绿没有大小感)

### 3. 发散色阶(Diverging Scale)

**用途**:表示以零(或某基准值)为中心的正负偏差

```javascript
// 场景:盈亏热力图、同比增减、差异对比
chart.options({
  type: 'cell',
  data,
  encode: { x: 'product', y: 'region', color: 'growth' },
  scale: {
    color: {
      type: 'diverging',      // 发散比例尺
      palette: 'RdBu',        // 红(负)→ 白(零)→ 蓝(正)
      // 常用:'RdBu' | 'RdYlGn' | 'BrBG' | 'PuOr'
      domain: [-100, 0, 100], // 对称范围(中间是 0)
    },
  },
});
```

**规则**:
- 中性值(零/平均值)映射为**白色或浅灰**
- 两端颜色**感知强度相当**(避免一端视觉更突出)
- 定义对称的 domain(如 `[-50, 0, 50]`)

## 颜色通道与比例尺配置

```javascript
// G2 完整颜色配置
scale: {
  color: {
    // ── 比例尺类型 ────────────────────────
    type: 'ordinal',     // 分类:'ordinal'
    // type: 'sequential', // 连续顺序
    // type: 'diverging',  // 发散
    // type: 'threshold',  // 分段阈值

    // ── 颜色范围(分类色板)────────────────
    range: ['#1890ff', '#52c41a', '#fa8c16'],   // 自定义颜色列表

    // ── 内置色板名称 ──────────────────────
    palette: 'tableau10',   // 'tableau10' | 'category10' | 'blues' 等

    // ── 域(分类的显示顺序)───────────────
    domain: ['产品A', '产品B', '产品C'],

    // ── 未知值颜色 ────────────────────────
    unknown: '#f0f0f0',
  },
}
```

## 内置色板完整参考

**⚠️ 重要:只能使用下表中列出的名称**。G2 的 `palette` 通过 d3-scale-chromatic 查找,不在此列表中的名称(如 `'blueOrange'`、`'redGreen'`、`'heatmap'`、`'hot'`、`'jet'`)会在运行时报错 `Unknown palette`,图表无法渲染。名称大小写不敏感(`'blues'` 和 `'Blues'` 均可)。

### 分类色板(ordinal scale 用,区分类别)

| 色板名 | 颜色数 | 风格 |
|-------|--------|------|
| `'tableau10'` | 10 | Tableau 经典配色(柔和,默认) |
| `'category10'` | 10 | D3 经典分类色 |
| `'set2'` | 8 | 粉彩风格,温和 |
| `'paired'` | 12 | 成对颜色(浅+深) |
| `'dark2'` | 8 | 深色调,高对比 |
| `'set1'` | 9 | 高饱和度 |
| `'set3'` | 12 | 中等饱和度 |
| `'pastel1'` | 9 | 淡彩色 |
| `'pastel2'` | 8 | 淡彩色 |
| `'accent'` | 8 | 强调色 |

### 顺序色阶(sequential scale 用,正值数值映射)

| 色板名 | 效果 |
|-------|------|
| `'blues'` | 白→蓝 |
| `'greens'` | 白→绿 |
| `'reds'` | 白→红 |
| `'oranges'` | 白→橙 |
| `'purples'` | 白→紫 |
| `'greys'` | 白→灰 |
| `'ylOrRd'` | 黄→橙→红(热力图常用) |
| `'ylGnBu'` | 黄→绿→蓝(sequential 默认值) |
| `'ylOrBr'` | 黄→橙→棕 |
| `'buGn'` | 蓝→绿 |
| `'buPu'` | 蓝→紫 |
| `'gnBu'` | 绿→蓝 |
| `'orRd'` | 橙→红 |
| `'puBu'` | 紫→蓝 |
| `'puBuGn'` | 紫→蓝→绿 |
| `'puRd'` | 紫→红 |
| `'rdPu'` | 红→紫 |
| `'ylGn'` | 黄→绿 |
| `'viridis'` | 紫→蓝→绿→黄(感知均匀,**色盲友好**,推荐)|
| `'plasma'` | 蓝紫→橙黄 |
| `'magma'` | 黑→紫→橙→白 |
| `'inferno'` | 黑→紫→红→黄 |
| `'cividis'` | 蓝→黄(对所有色盲类型友好) |
| `'turbo'` | 蓝→绿→黄→红(彩虹改进版) |
| `'warm'` | 橙→红→紫(暖色) |
| `'cool'` | 青→蓝→紫(冷色) |
| `'rainbow'` | 彩虹(感知不均匀,不推荐) |
| `'sinebow'` | 平滑彩虹 |
| `'cubehelixDefault'` | 螺旋渐变 |

### 发散色阶(diverging scale 用,正负值对比)

| 色板名 | 效果 |
|-------|------|
| `'rdBu'` | 红→白→蓝(**最常用**,涨跌/正负) |
| `'rdYlBu'` | 红→黄→蓝 |
| `'rdYlGn'` | 红→黄→绿(同比增减) |
| `'rdGy'` | 红→白→灰 |
| `'pRGn'` | 紫→白→绿 |
| `'piYG'` | 粉红→白→黄绿 |
| `'puOr'` | 紫→白→橙 |
| `'brBG'` | 棕→白→蓝绿 |
| `'spectral'` | 红→橙→黄→绿→蓝(多色发散) |

## 色盲友好配色

约 8% 的男性有红绿色盲,应避免仅用红/绿区分数据:

```javascript
// ❌ 不友好:红/绿区分(色盲用户无法区分)
scale: { color: { range: ['#ff4d4f', '#52c41a'] } }

// ✅ 友好:使用蓝/橙(色盲可区分)
scale: { color: { range: ['#1890ff', '#fa8c16'] } }

// ✅ 也可以同时用颜色+形状双通道编码
chart.options({
  type: 'point',
  encode: {
    color: 'category',
    shape: 'category',   // 同时用形状区分(不依赖颜色)
  },
});
```

## 常见颜色错误

### 错误 1:用分类色板表示数值(热力图)

```javascript
// ❌ 用分类色(红/蓝/绿)表示数值大小,毫无规律感
chart.options({
  type: 'cell',
  encode: { color: 'temperature' },
  scale: { color: { type: 'ordinal' } },   // ❌ 数值用了分类色板
});

// ✅ 数值用顺序色阶
chart.options({
  type: 'cell',
  encode: { color: 'temperature' },
  scale: { color: { type: 'sequential', palette: 'YlOrRd' } },   // ✅
});
```

### 错误 2:发散色阶域不对称

```javascript
// ❌ 域不对称,零点不在颜色中点
scale: {
  color: {
    type: 'diverging',
    palette: 'RdBu',
    domain: [-20, 100],   // ❌ 负值范围小,零点偏左
  },
}

// ✅ 对称的域,零点在中心
scale: {
  color: {
    type: 'diverging',
    palette: 'RdBu',
    domain: [-100, 0, 100],   // ✅ 明确指定三个控制点
  },
}
```

### 错误 3:颜色过多导致混淆

```javascript
// ❌ 12 个颜色,读者无法区分
chart.options({
  encode: { color: 'province' },   // 31个省份
});

// ✅ 分组或合并,保持 ≤ 8 个颜色类别
// 方案:取 Top 7 + "其他"
const processedData = aggregateTopN(data, 'province', 7);
```

### 错误 4:把 hex 色值放在数据中作为颜色字段

```javascript
// ❌ 错误:数据中的 hex 字符串被 Ordinal scale 当作类别 key
// 最终渲染颜色是 G2 默认调色板,图例显示无意义的 '#1e3a5f' 等字符串
const barData = [
  { group: '法律界', value: 85, color: '#1e3a5f' },
  { group: '公司治理专家', value: 78, color: '#2d4a6f' },
];
chart.options({
  type: 'interval',
  data: barData,
  encode: { x: 'group', y: 'value', color: 'color' },
  scale: { color: { type: 'ordinal' } },
});

// ✅ 正确:将 hex 色值放入 scale.color.range,encode.color 指向业务字段
chart.options({
  type: 'interval',
  data: [
    { group: '法律界', value: 85 },
    { group: '公司治理专家', value: 78 },
  ],
  encode: { x: 'group', y: 'value', color: 'group' },
  scale: {
    color: { type: 'ordinal', domain: ['法律界', '公司治理专家'], range: ['#1e3a5f', '#2d4a6f'] },
  },
});
```
references/concepts/g2-concept-dark-theme-adaptation.md
---
id: "g2-concept-dark-theme-adaptation"
title: "G2 深色主题与背景色适配"
description: |
  详解 G2 v5 中深色/自定义背景下如何确保图表各组件(坐标轴、图例、tooltip、
  标签)的文本可见性,涵盖内置主题切换、手动样式覆盖、viewStyle 配置等方案。

library: "g2"
version: "5.x"
category: "concepts"
tags:
  - "深色主题"
  - "dark theme"
  - "背景色"
  - "文本对比度"
  - "可见性"
  - "classicDark"
  - "tooltip样式"
  - "axis颜色"
  - "legend颜色"

related:
  - "g2-comp-axis-config"
  - "g2-comp-legend-config"
  - "g2-comp-tooltip-config"
  - "g2-comp-label-config"
  - "g2-theme-builtin"
  - "g2-theme-custom"

use_cases:
  - "深色背景下图表文本不可见"
  - "tooltip 文本与背景颜色接近"
  - "坐标轴/图例标签在深色容器中看不清"
  - "自定义背景色时文本颜色适配"

difficulty: "intermediate"
completeness: "full"
created: "2025-06-04"
updated: "2025-06-04"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/theme/overview"
---

## 核心原理

G2 主题系统通过 **color token** 控制所有组件的文字颜色:

| Token | light 主题 | dark/classicDark 主题 |
|-------|-----------|----------------------|
| `colorBlack`(文本主色) | `#1D2129` | `#fff` |
| `colorWhite`(反色) | `#fff` | `#000` |
| `colorStroke`(描边/辅助) | `#416180` | `#416180` |

切换到暗黑主题后,**坐标轴标签、图例文字、标题、label** 都会自动使用 `colorBlack`(即 `#fff`),无需手动逐个设置。

## 内置主题列表

| 主题名称 | 描述 | 适用场景 |
|---------|------|---------|
| `'light'` | 默认亮色主题 | 浅色背景 |
| `'dark'` | 暗色主题 | 深色背景 |
| `'classic'` | 经典主题 | 基于 light 的配色变体 |
| `'classicDark'` | 经典暗色主题 | 基于 dark 的配色变体(推荐深色场景使用) |
| `'academy'` | 学术主题 | 论文/报告 |

## 方式一:使用暗黑主题(推荐)

最简单的深色适配方案——一行配置解决所有组件的文本颜色:

```javascript
// 字符串简写
chart.options({
  type: 'interval',
  theme: 'classicDark',
  data,
  encode: { x: 'genre', y: 'sold', color: 'genre' },
});

// 对象形式(可同时配置背景色)
chart.options({
  type: 'interval',
  theme: {
    type: 'classicDark',
    view: { viewFill: '#1a1a1a' },  // 自定义视图背景色
  },
  data,
  encode: { x: 'genre', y: 'sold', color: 'genre' },
});
```

暗黑主题自动处理:
- 坐标轴标签:`labelFill: '#fff'`,`labelOpacity: 0.45`
- 坐标轴标题:`titleFill: '#fff'`,`titleOpacity: 0.9`
- 图例文字:`itemLabelFill: '#fff'`,`itemLabelFillOpacity: 0.9`
- 网格线:`gridStroke: '#fff'`,`gridStrokeOpacity: 0.25`
- Tooltip:深色背景 `#1f1f1f` + 浅色文字 `#A6A6A6`

## 方式二:手动设置组件文本颜色

适用于不想切换整个主题、只需局部深色适配的场景:

### 坐标轴

```javascript
axis: {
  x: {
    labelFill: 'rgba(255,255,255,0.65)',
    titleFill: 'rgba(255,255,255,0.9)',
    gridStroke: '#404040',
    gridLineWidth: 0.5,
  },
  y: {
    labelFill: 'rgba(255,255,255,0.65)',
    titleFill: 'rgba(255,255,255,0.9)',
    gridStroke: '#404040',
    gridLineWidth: 0.5,
  },
}
```

### 图例

```javascript
legend: {
  color: {
    itemLabelFill: 'rgba(255,255,255,0.85)',
    titleFill: 'rgba(255,255,255,0.65)',
  },
}
```

### Tooltip

Tooltip 样式通过 interaction 的 css 属性配置:

```javascript
interaction: {
  tooltip: {
    css: {
      '.g2-tooltip': {
        background: '#1f1f1f',
        color: '#fff',
        opacity: '0.95',
      },
      '.g2-tooltip-title': {
        color: '#a6a6a6',
      },
      '.g2-tooltip-list-item-name-label': {
        color: '#a6a6a6',
      },
      '.g2-tooltip-list-item-value': {
        color: '#fff',
      },
    },
  },
}
```

**Tooltip 可用的 CSS 选择器完整列表**:

| 选择器 | 作用 |
|--------|------|
| `.g2-tooltip` | 主容器(背景色、圆角、阴影) |
| `.g2-tooltip-title` | 标题文字 |
| `.g2-tooltip-list` | 数据列表容器 |
| `.g2-tooltip-list-item` | 单条数据行 |
| `.g2-tooltip-list-item-name-label` | 数据项名称 |
| `.g2-tooltip-list-item-value` | 数据项值 |
| `.g2-tooltip-list-item-marker` | 色块标记 |

### Label

```javascript
labels: [
  {
    text: 'value',
    fill: 'rgba(255,255,255,0.85)',
  },
]
```

## viewStyle 配置项

控制图表各区域的背景色:

```javascript
chart.options({
  theme: { type: 'classicDark' },
  viewStyle: {
    viewFill: '#1f1f1f',    // 整个视图区域(含标题/图例区)
    plotFill: '#2a2a2a',    // 绘图区域(数据展示区)
    mainFill: 'transparent', // 主区域
    contentFill: 'transparent', // 内容区域
    plotStroke: '#404040',  // 绘图区边框
    plotLineWidth: 1,
  },
});
```

也可通过 `theme.view` 配置:

```javascript
theme: {
  type: 'classicDark',
  view: {
    viewFill: '#111827',
    plotFill: '#1a1a1a',
  },
}
```

## 完整深色主题示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', height: 400 });

chart.options({
  type: 'view',
  theme: {
    type: 'classicDark',
    view: { viewFill: '#0f0f0f', plotFill: '#1a1a1a' },
  },
  data: [
    { month: '1月', value: 120, type: '产品A' },
    { month: '2月', value: 180, type: '产品A' },
    { month: '1月', value: 90, type: '产品B' },
    { month: '2月', value: 150, type: '产品B' },
  ],
  children: [
    {
      type: 'interval',
      encode: { x: 'month', y: 'value', color: 'type' },
      transform: [{ type: 'dodgeX' }],
      labels: [
        {
          text: 'value',
          position: 'top',
          fill: 'rgba(255,255,255,0.85)',
          transform: [{ type: 'overlapHide' }],
        },
      ],
    },
  ],
  axis: {
    x: { grid: true, gridStroke: '#404040' },
    y: { grid: true, gridStroke: '#404040' },
  },
  legend: {
    color: {
      position: 'top',
      layout: { justifyContent: 'center' },
    },
  },
});

chart.render();
```

## 常见错误与修正

### 错误 1:深色容器 + 默认 light 主题

```javascript
// ❌ 错误:容器背景深色但使用默认 light 主题
// 坐标轴标签为深色 #1D2129,在深色背景上完全不可见
chart.options({
  type: 'interval',
  data,
  encode: { x: 'x', y: 'y' },
  viewStyle: { viewFill: '#1a1a1a' },
});

// ✅ 正确:使用暗黑主题
chart.options({
  type: 'interval',
  data,
  encode: { x: 'x', y: 'y' },
  theme: { type: 'classicDark', view: { viewFill: '#1a1a1a' } },
});
```

### 错误 2:浅色背景下将文本设为浅灰色

```javascript
// ❌ 错误:白色背景 + 浅灰文字 → 看不清
axis: { y: { labelFill: '#ccc' } }

// ✅ 正确:浅色背景保持深色文本
axis: { y: { labelFill: '#666' } }
```

### 错误 3:饼图配色包含与背景相同的颜色

```javascript
// ❌ 错误:白色背景 + range 中包含白色 → 某个扇区不可见
scale: { color: { range: ['#1890ff', '#ffffff', '#52c41a'] } }

// ✅ 正确:所有颜色与背景有明确区分
scale: { color: { range: ['#1890ff', '#fadb14', '#52c41a'] } }
```
references/concepts/g2-concept-rendering-troubleshoot.md
---
id: "g2-concept-rendering-troubleshoot"
title: "G2 图表渲染排查清单"
description: |
  G2 v5 图表不显示或部分不显示时的排查指南,涵盖 chart.render() 缺失、
  多次 chart.options() 覆盖、encode 字段名不匹配、mark type 不存在、
  自定义 HTML 样式不生效等常见问题及修复方案。

library: "g2"
version: "5.x"
category: "concepts"
tags:
  - "渲染失败"
  - "图表不显示"
  - "排查"
  - "troubleshoot"
  - "debug"
  - "render"
  - "encode"
  - "field"

related:
  - "g2-core-chart-init"
  - "g2-core-view-composition"
  - "g2-comp-label-config"

use_cases:
  - "图表没有成功渲染"
  - "部分图表不显示"
  - "指标卡片/自定义 HTML 样式不生效"
  - "图表空白无内容"
  - "mark 静默不渲染"

difficulty: "beginner"
completeness: "full"
created: "2025-06-04"
updated: "2025-06-04"
author: "antv-team"
---

## 快速排查清单

图表不显示时,按以下顺序检查:

| # | 检查项 | 常见表现 |
|---|--------|---------|
| 1 | 缺少 `chart.render()` | 图表完全空白 |
| 2 | 多次调用 `chart.options()` | 只有最后一次的 mark 显示 |
| 3 | `encode` 字段名与 data 不匹配 | mark 静默不渲染 |
| 4 | 使用了不存在的 mark type | 运行时报错或空白 |
| 5 | `data` 不是数组 | mark 静默跳过 |
| 6 | `children` 嵌套 `view` | 子视图不渲染 |
| 7 | 填充色与背景色一致 | 图形存在但不可见 |
| 8 | innerHTML/render 样式不生效 | 自定义 HTML 显示异常 |

---

## 1. 缺少 `chart.render()`

代码末尾没有调用 `chart.render()`,图表不会显示。

```javascript
// ❌ 错误:缺少 render 调用
const chart = new Chart({ container: 'container' });
chart.options({ type: 'interval', data, encode: { x: 'x', y: 'y' } });
// 图表空白

// ✅ 正确:必须调用 render
const chart = new Chart({ container: 'container' });
chart.options({ type: 'interval', data, encode: { x: 'x', y: 'y' } });
chart.render();
```

## 2. 多次调用 `chart.options()` 导致覆盖

`chart.options()` 是**全量替换**——多次调用只有最后一次生效。

```javascript
// ❌ 错误:第一次 options 被第二次完全覆盖,柱状图不渲染
chart.options({ type: 'interval', data, encode: { x: 'x', y: 'y' } });
chart.options({ type: 'line', data, encode: { x: 'x', y: 'y' } });
chart.render(); // 只有折线图

// ✅ 正确:用 view + children 叠加多个 mark
chart.options({
  type: 'view',
  data,
  children: [
    { type: 'interval', encode: { x: 'x', y: 'y' } },
    { type: 'line', encode: { x: 'x', y: 'y' } },
  ],
});
chart.render();
```

## 3. `encode` 字段名与 data 不匹配

G2 通过 `data.map(d => d[fieldName])` 提取数据列。如果字段名不匹配,得到的是全 `undefined` 数组——**不会报错**,但 mark 不渲染或渲染为不可见状态。

```javascript
// ❌ 错误:data 中是 'Name'(大写),encode 写了 'name'(小写)
const data = [
  { Name: '张三', Score: 80 },
  { Name: '李四', Score: 95 },
];
chart.options({
  type: 'interval',
  data,
  encode: { x: 'name', y: 'score' },  // ❌ 大小写不匹配
});

// ✅ 正确:字段名完全一致
chart.options({
  type: 'interval',
  data,
  encode: { x: 'Name', y: 'Score' },
});
```

**排查方法**:打印 `data[0]` 的 key,与 encode 中引用的字段名逐一比对。

## 4. 使用了不存在的 mark type

G2 v5 的合法 mark 类型是固定列表,使用不存在的类型会报错或静默失败。

```javascript
// ❌ 错误:'bar' 在 G2 中不存在
chart.options({ type: 'bar', data, encode: { x: 'x', y: 'y' } });

// ✅ 正确:柱状图用 'interval',横向柱状图用 coordinate transpose
chart.options({
  type: 'interval',
  data,
  encode: { x: 'x', y: 'y' },
  coordinate: { transform: [{ type: 'transpose' }] },
});

// ❌ 错误:'radar' 在 G2 中不存在
chart.options({ type: 'radar', data, encode: { x: 'item', y: 'score' } });

// ✅ 正确:雷达图用 polar 坐标 + area/line
chart.options({
  type: 'view',
  data,
  coordinate: { type: 'polar' },
  children: [
    { type: 'area', encode: { x: 'item', y: 'score' }, style: { fillOpacity: 0.2 } },
    { type: 'line', encode: { x: 'item', y: 'score' }, style: { lineWidth: 2 } },
  ],
});
```

## 5. `data` 不是数组

G2 内部会检查 `Array.isArray(data)`,如果不是数组直接返回 `null`,mark 静默跳过。

```javascript
// ❌ 错误:data 是对象不是数组
chart.options({ type: 'interval', data: { x: 'a', y: 1 }, encode: { x: 'x', y: 'y' } });

// ✅ 正确:data 必须是数组
chart.options({ type: 'interval', data: [{ x: 'a', y: 1 }], encode: { x: 'x', y: 'y' } });
```

## 6. `children` 嵌套 `view`

`children` 内不能再有 `type: 'view'` + `children`(禁止嵌套)。

```javascript
// ❌ 错误:children 内嵌套 view
chart.options({
  type: 'view',
  children: [
    { type: 'view', children: [{ type: 'interval', ... }] },  // ❌
  ],
});

// ✅ 正确:子项直接是 mark,复杂布局用 spaceLayer
chart.options({
  type: 'view',
  children: [
    { type: 'interval', encode: { x: 'x', y: 'y' } },
    { type: 'line', encode: { x: 'x', y: 'y' } },
  ],
});
```

## 7. 填充色与背景色一致

图形存在但肉眼不可见——常见于白色背景 + 白色填充,或深色背景 + 深色图形。

```javascript
// ❌ 错误:白色背景 + 白色填充 → 图形不可见
chart.options({
  type: 'interval',
  data,
  encode: { x: 'x', y: 'y' },
  style: { fill: '#fff' },
});

// ✅ 正确:使用有区分度的颜色,或依赖 G2 默认配色
chart.options({
  type: 'interval',
  data,
  encode: { x: 'x', y: 'y', color: 'category' },
});
```

## 8. innerHTML/render 自定义 HTML 样式不生效

### 问题:指标卡片/自定义 HTML 元素样式看起来没效果

**原因 1**:外部 CSS class 被容器隔离,样式不生效。

```javascript
// ❌ 错误:依赖外部 class
innerHTML: (d) => `<div class="card">${d.value}</div>`,

// ✅ 正确:使用 inline style
innerHTML: (d) => `<div style="padding: 12px 16px; background: #f0f5ff; border-radius: 8px; font-size: 14px; font-weight: bold; color: #333;">${d.value}</div>`,
```

**原因 2**:`fill` 和 `color` 混淆。在 innerHTML/render 模式下:
- `fill` 控制背景色(有时默认黑色)
- `color` 控制文本颜色

```javascript
// ❌ 错误:背景黑色遮盖内容
labels: [{
  innerHTML: (d) => `<div style="padding: 4px;">${d.value}</div>`,
  style: { color: '#333' },  // 缺少 fill 设置,背景可能为黑
}]

// ✅ 正确:显式设置 fill 为透明或白色
labels: [{
  innerHTML: (d) => `<div style="padding: 4px;">${d.value}</div>`,
  style: { fill: 'rgba(0,0,0,0)', color: '#333' },
}]
```

**原因 3**:卡片未设置明确尺寸,被父容器压缩。

```javascript
// ✅ 确保卡片有明确的宽高和 padding
innerHTML: (d) => `
  <div style="
    width: 120px;
    padding: 12px 16px;
    background: #f0f5ff;
    border: 1px solid #d6e4ff;
    border-radius: 8px;
    text-align: center;
  ">
    <div style="font-size: 24px; font-weight: bold; color: #1890ff;">${d.value}</div>
    <div style="font-size: 12px; color: #666; margin-top: 4px;">${d.label}</div>
  </div>
`,
```

## 9. children 中子 mark 不显示

当 `type: 'view'` 的 data 在 view 级别声明,但子 mark 引用了 view data 中不存在的字段时,该子 mark 静默不渲染。

```javascript
// ❌ 错误:text mark 引用了 view data 中不存在的字段 'annotation'
chart.options({
  type: 'view',
  data: [{ x: '1月', y: 100 }, { x: '2月', y: 200 }],
  children: [
    { type: 'interval', encode: { x: 'x', y: 'y' } },
    { type: 'text', encode: { x: 'x', y: 'y', text: 'annotation' } }, // ❌ 字段不存在
  ],
});

// ✅ 正确:子 mark 需要不同字段时,单独声明 data
chart.options({
  type: 'view',
  data: mainData,
  children: [
    { type: 'interval', encode: { x: 'x', y: 'y' } },
    {
      type: 'text',
      data: annotationData,  // 单独的数据源
      encode: { x: 'x', y: 'y', text: 'annotation' },
    },
  ],
});
```
references/concepts/g2-concept-visual-channels.md
---
id: "g2-concept-visual-channels"
title: "G2 视觉通道(Visual Channels)"
description: |
  视觉通道是数据属性到视觉属性的映射方式,包括位置、颜色、大小、形状、方向等。
  理解各通道的感知效率和适用数据类型,有助于设计更准确、更有效的数据可视化。
  这是 G2 encode 配置设计的理论基础。

library: "g2"
version: "5.x"
category: "concepts"
tags:
  - "视觉通道"
  - "visual channels"
  - "encode"
  - "感知效率"
  - "数据映射"
  - "可视化设计"
  - "颜色"
  - "大小"
  - "位置"

related:
  - "g2-core-encode-channel"
  - "g2-concept-color-theory"

use_cases:
  - "理解 G2 encode 各通道的设计原理"
  - "为不同数据类型选择合适的视觉通道"
  - "避免感知效率低的通道误用"

difficulty: "intermediate"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
---

## 核心概念

视觉通道(Visual Channel)是将**数据属性**映射到**视觉属性**的媒介。G2 中通过 `encode` 字段完成这种映射:

```javascript
chart.options({
  encode: {
    x: 'month',      // 位置通道(x轴)← 分类字段
    y: 'revenue',    // 位置通道(y轴)← 数值字段
    color: 'product',// 颜色通道 ← 分类字段
    size: 'amount',  // 大小通道 ← 数值字段
  },
});
```

## 主要视觉通道及其感知效率

### 定量数据(连续数值)

按感知精确度从高到低排序:

| 排名 | 通道 | G2 对应 | 说明 |
|------|------|---------|------|
| ★★★★★ | **位置(x/y轴)** | `encode.x`, `encode.y` | 最精确,人眼可精确比较 |
| ★★★★ | **长度/高度** | `encode.y`(柱状图) | 次精确,需共同基线 |
| ★★★ | **面积/大小** | `encode.size` | 中等,适合气泡图相对比较 |
| ★★ | **颜色深浅** | `encode.color`(连续色阶)| 较难精确比较,仅适合粗略趋势 |
| ★ | **角度** | 饼图扇区角度 | 人眼对角度判断不精确,慎用 |

### 分类数据(离散类别)

| 通道 | G2 对应 | 适用场景 |
|------|---------|---------|
| **位置分组** | `encode.x`(分类轴) | 柱状图、折线图的分类 |
| **颜色(色相)** | `encode.color` | 区分≤8个类别,超过易混淆 |
| **形状** | `encode.shape` | 散点图区分类别,≤6个 |
| **纹理/图案** | `encode.shape`(自定义)| 无色环境或辅助区分 |

## 通道适配规则

```
定量数据(数值)→ 优先:位置轴(x/y)> 大小(size)> 颜色深度(连续色)
分类数据(类别)→ 优先:位置轴(x/y)> 颜色色相(color)> 形状(shape)
有序数据(排名)→ 优先:位置轴(顺序)> 大小(递减)> 颜色(渐变色)
```

## 通道组合示例

### 气泡图:3个数值通道

```javascript
// x位置 + y位置 + 大小(size) = 三维数值编码
// 颜色映射表:scale.color.range 和 fill 回调共用
const COLOR_MAP = { 'Asia': '#fb7678', 'Europe': '#81e7ee', 'Americas': '#5B8FF9' };

chart.options({
  type: 'point',
  data,
  encode: {
    x: 'GDP',          // 定量 → 位置(最精确)
    y: 'LifeExpectancy',// 定量 → 位置
    size: 'Population', // 定量 → 大小(第三维度)
    color: 'Region',    // 分类 → 颜色色相(第四维度)
    shape: 'point',
  },
  scale: {
    size: { type: 'sqrt', range: [4, 40] },   // sqrt 比例尺,确保面积与数值成正比
    color: { range: Object.values(COLOR_MAP) },
  },
  style: {
    fillOpacity: 0.85,
    lineWidth: 0,
    // 径向渐变:从白色中心到映射色边缘,模拟 3D 球体质感
    // 通过 COLOR_MAP[datum.Region] 获取颜色,与 scale.color.range 保持一致
    fill: (datum) => {
      const color = COLOR_MAP[datum.Region];
      return `radial-gradient(circle at 35% 35%, rgb(255,255,255) 0%, ${color} 100%)`;
    },
    shadowBlur: 10,
    shadowColor: 'rgba(0, 0, 0, 0.15)',
    shadowOffsetY: 5,
  },  // 不要用 stroke:'#fff'
  legend: { size: false },
});
```

### 热力图:颜色深度编码数值

```javascript
// 颜色用于定量数据时,应使用顺序色阶(浅→深),不用分类色板
chart.options({
  type: 'cell',
  data,
  encode: {
    x: 'weekday',
    y: 'hour',
    color: 'value',     // 定量 → 颜色深浅(连续色阶)
  },
  scale: {
    color: {
      type: 'sequential',
      palette: 'blues',  // 顺序色阶(而非分类色板)
    },
  },
});
```

## 常见通道误用

### 误用 1:用颜色色相表示数值大小

```javascript
// ❌ 误用:颜色色相(红/绿/蓝)不能表达数值大小关系
chart.options({
  encode: { color: 'temperature' },   // temperature 是数值,用色相无法体现大小
  scale: { color: { type: 'ordinal' } },   // ❌ 分类色板用于数值
});

// ✅ 正确:数值用连续色阶
chart.options({
  encode: { color: 'temperature' },
  scale: {
    color: {
      type: 'sequential',   // 顺序比例尺
      palette: 'reds',      // 浅→深的顺序色
    },
  },
});
```

### 误用 2:颜色类别过多导致难以区分

```javascript
// ❌ 超过 8 个颜色类别,人眼难以区分
chart.options({
  encode: { color: 'province' },   // 如果有 31 个省份,颜色无法有效区分
});

// ✅ 超过 8 类时的替代方案:
// 1. 合并次要类别为"其他"
// 2. 改用位置通道(分组柱状图/分面)
// 3. 使用交互过滤(点击图例显示/隐藏)
```

### 误用 3:饼图扇区过多

```javascript
// ❌ 角度通道感知精度低,超过 5 个扇区难以比较
chart.options({
  type: 'interval',
  coordinate: { type: 'theta' },
  // 如果有 10+ 个分类,饼图效果很差
});

// ✅ 分类多时改用柱状图(位置通道感知更精确)
chart.options({
  type: 'interval',
  encode: { x: 'category', y: 'value' },
  transform: [{ type: 'sortX', by: 'y', reverse: true }],
});
```
references/coordinates/g2-coord-cartesian.md
---
id: "g2-coord-cartesian"
title: "G2 直角坐标系(cartesian)"
description: |
  cartesian(直角坐标系)是 G2 v5 的默认坐标系,x 和 y 通道分别映射为水平和垂直位置。
  大多数常见图表(柱状图、折线图、散点图)均使用直角坐标系。
  通过 coordinate.transform 可以为直角坐标系添加转置(transpose)等变换。

library: "g2"
version: "5.x"
category: "coordinates"
tags:
  - "cartesian"
  - "直角坐标系"
  - "默认坐标系"
  - "coordinate"
  - "笛卡尔坐标"

related:
  - "g2-coord-transpose"
  - "g2-coord-polar"
  - "g2-mark-interval-basic"
  - "g2-mark-line-basic"

use_cases:
  - "柱状图(默认直角坐标)"
  - "折线图(默认直角坐标)"
  - "条形图(直角坐标 + 转置)"
  - "散点图(直角坐标)"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/coordinate/cartesian"
---

## 核心概念

直角坐标系是 G2 的**默认坐标系**,无需显式配置 `coordinate` 字段。

- x 通道 → 水平位置(从左到右)
- y 通道 → 垂直位置(从下到上)
- 支持通过 `coordinate.transform` 添加转置等变换

## 默认使用(无需配置)

```javascript
import { Chart } from '@antv/g2';

// 直角坐标系是默认值,不需要写 coordinate 配置
const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data: [
    { genre: 'Sports',   sold: 275 },
    { genre: 'Strategy', sold: 115 },
    { genre: 'Action',   sold: 120 },
  ],
  encode: { x: 'genre', y: 'sold' },
  // 无需 coordinate 配置,默认即为直角坐标系
});

chart.render();
```

## 显式指定(与默认等效)

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'genre', y: 'sold' },
  coordinate: { type: 'cartesian' },  // 显式指定(与不写等效)
});
```

## 直角坐标系 + 转置(条形图)

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'genre', y: 'sold' },
  coordinate: {
    type: 'cartesian',
    transform: [{ type: 'transpose' }],  // 转置:x/y 互换,柱状图变条形图
  },
});
```

## 坐标系配置项

```javascript
chart.options({
  coordinate: {
    type: 'cartesian',
    transform: [
      { type: 'transpose' },         // 转置(x↔y 互换)
      { type: 'reflect', x: true },  // X 轴镜像翻转
      { type: 'reflect', y: true },  // Y 轴镜像翻转
      { type: 'scale', sx: 1, sy: -1 },  // 自定义缩放
    ],
  },
});
```

## 常见错误与修正

### 错误:配置了 cartesian 坐标系但期望得到环形图
```javascript
// ❌ 错误:饼图/环形图需要 theta 坐标系,不是 cartesian
chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: { type: 'cartesian' },  // ❌ 这样会画出普通柱状图
});

// ✅ 饼图/环形图使用 theta 坐标系
chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: { type: 'theta', outerRadius: 0.8, innerRadius: 0.5 },  // ✅
});
```
references/coordinates/g2-coord-fisheye.md
---
id: "g2-coord-fisheye"
title: "G2 鱼眼坐标系(fisheye)"
description: |
  鱼眼坐标系在焦点附近放大,远离焦点区域压缩,
  用于在大量密集数据中同时保留局部细节和全局概览。
  通常配合鱼眼交互(fisheye interaction)实现鼠标跟随的动态放大效果。

library: "g2"
version: "5.x"
category: "coordinates"
tags:
  - "fisheye"
  - "鱼眼"
  - "焦点上下文"
  - "focus+context"
  - "coordinate"
  - "密集数据"

related:
  - "g2-mark-point-scatter"
  - "g2-coord-transpose"

use_cases:
  - "密集散点图的局部细节查看"
  - "时间序列密集区域的细节探索"
  - "需要同时看全局和局部细节的场景"

difficulty: "advanced"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/coordinate/fisheye"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'point',
   Array.from({ length: 200 }, (_, i) => ({
    x: Math.random() * 100,
    y: Math.random() * 100,
    group: i % 5,
  })),
  encode: { x: 'x', y: 'y', color: 'group', shape: 'point' },
  scale: { color: { type: 'ordinal' } },
  coordinate: {
    transform: [
      {
        type: 'fisheye',
        focusX: 0.5,       // 焦点 X 位置(0~1 相对坐标)
        focusY: 0.5,       // 焦点 Y 位置
        distortionX: 2,    // X 方向放大系数(越大放大越强)
        distortionY: 2,    // Y 方向放大系数
      }
    ]
  },
  // 通常配合鱼眼交互,让焦点跟随鼠标
  interaction: { fisheye: true },
});

chart.render();
```

## 配置项

```javascript
coordinate: {
  transform: [
    {
      type: 'fisheye',
      focusX: 0,        // 焦点 X(相对坐标 0~1),默认 0
      focusY: 0,        // 焦点 Y(相对坐标 0~1),默认 0
      distortionX: 2,   // X 方向扭曲强度,默认 2
      distortionY: 2,   // Y 方向扭曲强度,默认 2
      visual: false,    // 是否启用视觉效果,默认 false
    }
  ]
}
```

## 仅 X 方向鱼眼(时间序列)

```javascript
chart.options({
  type: 'line',
  data: timeSeriesData,
  encode: { x: 'date', y: 'value', color: 'type' },
  coordinate: {
    transform: [
      {
        type: 'fisheye',
        distortionX: 3,   // 仅放大 X 方向
        distortionY: 0,   // Y 方向不变形
      }
    ]
  },
  interaction: { fisheye: true },
});
```

## 常见错误与修正

### 错误:单独使用鱼眼坐标系但不加交互——效果是静态的
```javascript
// ⚠️  可以用,但焦点固定,无法响应鼠标
chart.options({
  coordinate: {
    transform: [
      {
        type: 'fisheye',
        focusX: 0.3,
        focusY: 0.5,
      }
    ]
  },
  // 没有 interaction.fisheye
});

// ✅ 推荐:配合交互实现动态鱼眼
chart.options({
  coordinate: { transform: [ { type: 'fisheye' } ] },
  interaction: { fisheye: true },  // 焦点跟随鼠标
});
```
references/coordinates/g2-coord-helix.md
---
id: "g2-coord-helix"
title: "G2 螺旋坐标系(helix)"
description: |
  螺旋坐标系将时间/顺序数据沿螺旋线排布,适合展示具有周期性规律的长时间序列。
  数据按螺旋盘绕,相同周期位置的数据点上下对齐,便于发现周期模式。

library: "g2"
version: "5.x"
category: "coordinates"
tags:
  - "helix"
  - "螺旋"
  - "螺旋图"
  - "周期"
  - "时间序列"
  - "coordinate"

related:
  - "g2-mark-interval-basic"
  - "g2-scale-time"

use_cases:
  - "展示多年日均气温的周期规律"
  - "股票价格长时间序列的周期分析"
  - "周/月/年周期性规律可视化"

difficulty: "advanced"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/coordinate/helix"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

// 生成一年的日均温度数据
const data = Array.from({ length: 365 }, (_, i) => ({
  day: i,
  temp: 15 + 12 * Math.sin((i / 365) * Math.PI * 2) + (Math.random() - 0.5) * 5,
}));

const chart = new Chart({ container: 'container', width: 600, height: 600 });

chart.options({
  type: 'interval',
  data,
  encode: {
    x: 'day',    // 顺序(沿螺旋排布)
    y: 'temp',   // 数值(映射为半径变化)
    color: 'temp',
  },
  scale: {
    color: { type: 'sequential', palette: 'rdYlBu' },
  },
  coordinate: {
    type: 'helix',
    startAngle: 0,              // 起始角度,默认 0
    endAngle: Math.PI * 6,      // 结束角度,默认 6π(3圈)
    innerRadius: 0.1,
    outerRadius: 0.9,
  },
  style: { lineWidth: 0 },
  legend: false,
});

chart.render();
```

## 配置项

```javascript
coordinate: {
  type: 'helix',
  startAngle: 0,              // 起始角度(弧度),默认 0
  endAngle: Math.PI * 6,      // 结束角度,默认 6π(3圈)
  innerRadius: 0,             // 内孔半径,默认 0
  outerRadius: 1,             // 外径比例,默认 1
}
```

## 常见错误与修正

### 错误:数据量太少,螺旋圈数太多——空白区域很大
```javascript
// ❌ 数据只有 12 个月却设置 6π(3圈),每圈只有 4 个点
chart.options({
  data: monthlyData,  // 只有 12 条
  coordinate: { type: 'helix', endAngle: Math.PI * 6 },
});

// ✅ 根据数据量调整圈数:endAngle = 圈数 × 2π
chart.options({
  coordinate: {
    type: 'helix',
    endAngle: Math.PI * 2,  // 1 圈,适合月度数据
  },
});
```

### 错误:样式设置不当导致图形不可见或渲染异常
```javascript
// ❌ 使用了 lineWidth: 0 和 interval 类型但未设置足够宽度,可能导致视觉上“消失”
chart.options({
  type: 'interval',
  coordinate: { type: 'helix' },
  style: { lineWidth: 0 },
});

// ✅ 设置合适的 lineWidth 或调整图形类型如 point 更适合细粒度数据
chart.options({
  type: 'point', // 对于大量密集数据更合适
  style: { lineWidth: 2 },
});
```

### 错误:动画类型与图形元素不兼容导致无动画效果
```javascript
// ❌ growInY 动画可能不适用于所有 helix 场景下的 interval 元素
chart.options({
  animate: {
    enter: {
      type: 'growInY',
    }
  }
});

// ✅ 使用 fadeIn 等通用动画类型确保兼容性
chart.options({
  animate: {
    enter: {
      type: 'fadeIn',
      duration: 2000,
    }
  }
});
```
references/coordinates/g2-coord-parallel.md
---
id: "g2-coord-parallel"
title: "G2 平行坐标系(parallel)"
description: |
  平行坐标系将多个维度排列为平行的竖轴,每条折线代表一条数据记录,
  用于发现多维数据中的模式、聚类和异常值。
  需要配合 line mark 使用,encode 中用 position 通道绑定多个字段。

library: "g2"
version: "5.x"
category: "coordinates"
tags:
  - "parallel"
  - "平行坐标"
  - "parallel coordinates"
  - "多维"
  - "高维数据"
  - "coordinate"

related:
  - "g2-mark-line-basic"
  - "g2-coord-transpose"

use_cases:
  - "多维度数据对比分析(如汽车性能多指标)"
  - "发现高维数据中的聚类和关联"
  - "异常值检测"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/coordinate/parallel"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const data = [
  { name: '产品A', price: 120, sales: 300, rating: 4.5, stock: 80 },
  { name: '产品B', price: 85,  sales: 450, rating: 3.8, stock: 120 },
  { name: '产品C', price: 200, sales: 180, rating: 4.9, stock: 40 },
  { name: '产品D', price: 60,  sales: 600, rating: 3.2, stock: 200 },
];

const chart = new Chart({ container: 'container', width: 600, height: 400 });

chart.options({
  type: 'line',
  data,
  encode: {
    position: ['price', 'sales', 'rating', 'stock'],  // 多维字段列表
  },
  coordinate: { type: 'parallel' },  // 平行坐标系
  style: {
    lineWidth: 1.5,
    strokeOpacity: 0.7,
  },
  legend: { color: { position: 'right' } },
});

chart.render();
```

## 带交互高亮的平行坐标

```javascript
chart.options({
  type: 'line',
  data,
  encode: {
    position: ['cylinders', 'displacement', 'horsepower', 'weight', 'acceleration', 'miles_per_gallon'],
    color: 'origin',
  },
  coordinate: { type: 'parallel' },
  style: { lineWidth: 1, strokeOpacity: 0.5 },
  interaction: {
    elementHighlight: { background: true },  // 悬停高亮
  },
  axis: {
    // 为每个维度单独配置标题
    position0: { title: '气缸数' },
    position1: { title: '排量' },
    position2: { title: '马力' },
  },
});
```

## 常见错误与修正

### 错误 1:用 x/y encode 替代 position
```javascript
// ❌ 错误:平行坐标不使用 x/y,必须用 position 通道
chart.options({
  type: 'line',
  encode: {
    x: 'price',      // ❌
    y: 'sales',      // ❌ 只有两个维度,不是平行坐标
  },
  coordinate: { type: 'parallel' },
});

// ✅ 正确:position 通道传入字段数组
chart.options({
  type: 'line',
  encode: {
    position: ['price', 'sales', 'rating'],  // ✅ 数组形式
  },
  coordinate: { type: 'parallel' },
});
```

### 错误 2:在平行坐标中使用 interval 或 point mark
```javascript
// ❌ 错误:平行坐标系只适合 line mark
chart.options({
  type: 'interval',  // ❌ 在平行坐标中没有意义
  coordinate: { type: 'parallel' },
});

// ✅ 正确:配合 line mark
chart.options({
  type: 'line',      // ✅
  coordinate: { type: 'parallel' },
});
```
references/coordinates/g2-coord-polar.md
---
id: "g2-coord-polar"
title: "G2 极坐标系(polar)"
description: |
  极坐标系将直角坐标系映射为圆形区域,x 通道映射为角度,y 通道映射为半径。
  常用于玫瑰图(极坐标柱状图)、极坐标面积图、环形图等。
  通过 startAngle / endAngle 控制角度范围,innerRadius 控制内孔大小。

library: "g2"
version: "5.x"
category: "coordinates"
tags:
  - "polar"
  - "极坐标"
  - "玫瑰图"
  - "nightingale"
  - "coxcomb"
  - "radial"
  - "coordinate"

related:
  - "g2-coord-transpose"
  - "g2-mark-arc-pie"
  - "g2-mark-interval-stacked"

use_cases:
  - "玫瑰图 / 南丁格尔玫瑰图(各分类用角度+半径双重编码)"
  - "极坐标面积图(周期性数据的环形展示)"
  - "环形进度条"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/coordinate/polar"
---

## 最小可运行示例(玫瑰图)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 500, height: 500 });

chart.options({
  type: 'interval',
  data: [
    { month: 'Jan', value: 83 },
    { month: 'Feb', value: 60 },
    { month: 'Mar', value: 95 },
    { month: 'Apr', value: 72 },
    { month: 'May', value: 110 },
    { month: 'Jun', value: 88 },
  ],
  encode: {
    x: 'month',   // 映射为角度(方向)
    y: 'value',   // 映射为半径(长度)
    color: 'month',
  },
  coordinate: { type: 'polar' },  // 关键:极坐标
});

chart.render();
```

## 配置项

```javascript
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'month', y: 'value', color: 'month' },
  coordinate: {
    type: 'polar',
    startAngle: -Math.PI / 2,    // 起始角度,默认 -π/2(12点钟方向)
    endAngle: (Math.PI * 3) / 2, // 结束角度,默认 3π/2(顺时针一圈)
    innerRadius: 0,              // 内孔半径,0 = 无孔,0.5 = 半径50% 的孔
    outerRadius: 1,              // 外径比例,默认 1
  },
});
```

## 半圆玫瑰图

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'month' },
  coordinate: {
    type: 'polar',
    startAngle: -Math.PI / 2,   // 从顶部开始
    endAngle: Math.PI / 2,      // 只到底部,半圆
  },
});
```

## 极坐标堆叠面积图

```javascript
chart.options({
  type: 'area',
  data: timeSeriesData,
  encode: { x: 'date', y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: { type: 'polar', innerRadius: 0.2 },
  style: { fillOpacity: 0.65 },
});
```

## 常见错误与修正

### 错误 1:玫瑰图角度不均匀——x 通道数据类型不是分类
```javascript
// ❌ 错误:x 通道是数值,极坐标下角度不均匀
chart.options({
  encode: { x: 'timestamp', y: 'value' },  // ❌ 时间戳为数值
  coordinate: { type: 'polar' },
});

// ✅ 正确:x 通道应为分类字段(字符串)
chart.options({
  encode: { x: 'month', y: 'value' },  // ✅ 字符串类别
  coordinate: { type: 'polar' },
});
```

### 错误 2:与 theta 坐标系混淆
```javascript
// ❌ 饼图用 polar 无效——y 通道不会自动转为扇形角度
chart.options({
  type: 'interval',
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: { type: 'polar' },  // ❌ 饼图应该用 theta,不是 polar
});

// ✅ 饼图必须用 theta 坐标系
chart.options({
  coordinate: { type: 'theta' },  // ✅
});
```
references/coordinates/g2-coord-radial.md
---
id: "g2-coord-radial"
title: "G2 径向坐标系(radial)"
description: |
  radial(径向坐标系)是 G2 v5 中极坐标系的一种变体,
  将转置后的直角坐标映射为圆形布局:x 轴映射为半径,y 轴映射为角度。
  与 polar(极坐标)相反(polar 是 x→角度,y→半径),
  radial 适合绘制径向柱状图(向心柱状图)、径向折线图等。

library: "g2"
version: "5.x"
category: "coordinates"
tags:
  - "radial"
  - "径向坐标"
  - "向心柱状图"
  - "径向图"
  - "coordinate"
  - "圆形布局"

related:
  - "g2-coord-polar"
  - "g2-coord-theta"
  - "g2-mark-interval-basic"

use_cases:
  - "径向柱状图(向外辐射的柱状图)"
  - "环形条形图(各类别从圆心向外延伸)"
  - "时间序列的圆形布局展示"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/coordinate/radial"
---

## 核心概念

径向坐标系(radial)与极坐标系(polar)的映射关系相反:

| 坐标系 | x 通道 | y 通道 | 典型图表 |
|--------|--------|--------|----------|
| `polar` | → 角度(圆周方向) | → 半径(距中心距离) | 玫瑰图 |
| `radial` | → 半径(距中心距离) | → 角度(圆周方向) | 径向柱状图 |

## 最小可运行示例(径向柱状图)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 500, height: 500 });

chart.options({
  type: 'interval',
  data: [
    { month: 'Jan', value: 83 },
    { month: 'Feb', value: 60 },
    { month: 'Mar', value: 95 },
    { month: 'Apr', value: 72 },
    { month: 'May', value: 110 },
    { month: 'Jun', value: 85 },
  ],
  encode: {
    x: 'month',    // x 通道 → 角度(圆周位置)
    y: 'value',    // y 通道 → 半径(柱子长度)
    color: 'month',
  },
  coordinate: { type: 'radial', innerRadius: 0.1, outerRadius: 0.8 },
});

chart.render();
```

## 配置项

```javascript
chart.options({
  coordinate: {
    type: 'radial',
    innerRadius: 0.1,            // 内环半径(0=从中心开始),默认 0
    outerRadius: 1,              // 外环半径比例,默认 1
    startAngle: -Math.PI / 2,   // 起始角度,默认 -π/2(12点钟方向)
    endAngle: (Math.PI * 3) / 2, // 结束角度,默认 3π/2(顺时针一圈)
  },
});
```

## 带内孔的径向柱状图(环形)

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
  coordinate: {
    type: 'radial',
    innerRadius: 0.3,   // 留出中心空间
    outerRadius: 0.9,
  },
  style: { fillOpacity: 0.85 },
});
```

## 与 polar 坐标系的区别

```javascript
// polar:x→角度,y→半径(玫瑰图效果)
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value' },  // x 为分类(角度),y 为数值(半径)
  coordinate: { type: 'polar' },
});

// radial:x→半径,y→角度(径向柱状图效果)
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value' },  // x 为分类(角度),y 为数值(半径)
  coordinate: { type: 'radial' },
});
```

## 常见错误与修正

### 错误:encode x/y 与预期方向相反
```javascript
// ❌ 错误:radial 中 x 应该是角度方向(类别),y 是半径方向(数值)
chart.options({
  type: 'interval',
  encode: { x: 'value', y: 'month' },  // ❌ 数值作为角度,月份作为半径
  coordinate: { type: 'radial' },
});

// ✅ 正确:将分类字段作为 x(映射为角度),数值字段作为 y(映射为半径)
chart.options({
  type: 'interval',
  encode: { x: 'month', y: 'value' },  // ✅ 月份→角度,数值→半径
  coordinate: { type: 'radial' },
});
```

### 错误:中心图片未正确显示
```javascript
// ❌ 错误:使用固定坐标 (0,0) 显示图片无法保证其位于中心
chart.options({
  type: 'image',
  data: [{ url: 'https://example.com/logo.png' }],
  encode: {
    x: () => 0,
    y: () => 0
  },
  style: {
    img: (d) => d.url,
    width: 80,
    height: 80
  }
});

// ✅ 正确:使用 style.x 和 style.y 设置相对位置,确保图片居中
chart.options({
  type: 'image',
  data: [{ src: 'https://example.com/logo.png' }],
  style: {
    x: '50%',      // 相对于容器宽度的 50%
    y: '50%',      // 相对于容器高度的 50%
    width: 80,
    height: 80
  }
});
```

### 错误:多个视图叠加导致坐标系冲突
```javascript
// ❌ 错误:在 view 中重复定义 coordinate 导致渲染异常
chart.options({
  type: 'view',
  children: [
    {
      type: 'interval',
      coordinate: { type: 'radial' }  // 子视图中定义坐标系可能导致冲突
    },
    {
      type: 'image',
      coordinate: { type: 'radial' }  // 图片标记不需要坐标系
    }
  ]
});

// ✅ 正确:在顶层 view 定义 coordinate,子元素继承即可
chart.options({
  type: 'view',
  coordinate: { type: 'radial', innerRadius: 0.3 },
  children: [
    {
      type: 'interval',
      data,
      encode: { x: 'type', y: 'value' }
    },
    {
      type: 'image',
      data: [{ src: 'https://example.com/logo.png' }],
      style: {
        x: '50%',
        y: '50%',
        width: 80,
        height: 80
      }
    }
  ]
});
```
references/coordinates/g2-coord-theta.md
---
id: "g2-coord-theta"
title: "G2 Theta 坐标系(饼图 / 环形图)"
description: |
  Theta 坐标系是 G2 v5 中制作饼图和环形图的专用坐标系。
  本质上是 Transpose + Polar 的组合:将 y 通道(数值)映射为角度。
  必须配合 stackY transform 使用,否则所有扇形角度从 0 开始完全重叠。

library: "g2"
version: "5.x"
category: "coordinates"
tags:
  - "theta"
  - "饼图"
  - "环形图"
  - "pie"
  - "donut"
  - "coordinate"

related:
  - "g2-transform-stacky"
  - "g2-mark-arc-pie"
  - "g2-mark-arc-donut"
  - "g2-coord-polar"

use_cases:
  - "饼图(展示各部分占总体的比例)"
  - "环形图(中间留空展示汇总数值)"
  - "玫瑰饼图"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/coordinate/theta"
---

## 最小可运行示例(饼图)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 480, height: 480 });

chart.options({
  type: 'interval',
  data: [
    { type: '电子产品', value: 40 },
    { type: '服装',     value: 25 },
    { type: '食品',     value: 20 },
    { type: '其他',     value: 15 },
  ],
  encode: {
    y: 'value',      // 数值映射为扇形角度大小
    color: 'type',   // 颜色区分类别
  },
  transform: [{ type: 'stackY' }],       // 必须!将数值累积为角度区间
  coordinate: { type: 'theta' },         // 必须!theta 坐标系
});

chart.render();
```

## 环形图(设置 innerRadius)

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: {
    type: 'theta',
    innerRadius: 0.6,   // 内孔半径比例(0.5~0.7 是常见值)
    outerRadius: 0.9,
  },
  labels: [
    {
      position: 'outside',
      text: (d) => `${d.type}: ${d.value}`,
    },
  ],
});
```

## 配置项

```javascript
coordinate: {
  type: 'theta',
  startAngle: -Math.PI / 2,    // 起始角度,默认 -π/2(12点钟方向)
  endAngle: (Math.PI * 3) / 2, // 结束角度,默认顺时针一整圈
  innerRadius: 0,              // 内孔大小,0 = 实心饼图,> 0 = 环形图
  outerRadius: 1,              // 外径比例
}
```

## 带百分比标签的饼图

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: { type: 'theta', outerRadius: 0.8 },
  labels: [
    {
      position: 'outside',
      text: (d, i, arr) => {
        const total = arr.reduce((sum, item) => sum + item.value, 0);
        return `${((d.value / total) * 100).toFixed(1)}%`;
      },
    },
  ],
  legend: { color: { position: 'right' } },
});
```

## 常见错误与修正

### 错误 1:忘记 stackY —— 所有扇形从 0 开始完全重叠
```javascript
// ❌ 错误:没有 stackY,所有扇区角度都从 0 开始,图形全部重叠
chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'type' },
  coordinate: { type: 'theta' },   // ❌ 缺少 transform!
});

// ✅ 正确:必须加 stackY
chart.options({
  transform: [{ type: 'stackY' }],  // ✅ 先累积角度
  coordinate: { type: 'theta' },
});
```

### 错误 2:用 polar 代替 theta 做饼图
```javascript
// ❌ 错误:polar 坐标系 y 通道映射半径,不会生成扇形角度
chart.options({
  coordinate: { type: 'polar' },  // ❌ 得到玫瑰图,不是饼图
});

// ✅ 饼图必须用 theta
chart.options({
  coordinate: { type: 'theta' },  // ✅
});
```

### 错误 3:encode 中设置了 x 通道
```javascript
// ❌ 错误:theta 坐标系的饼图不需要 x 通道
chart.options({
  encode: {
    x: 'type',    // ❌ 多余,theta 坐标中 x 通道无意义
    y: 'value',
    color: 'type',
  },
});

// ✅ 正确:theta 饼图只需要 y 和 color
chart.options({
  encode: {
    y: 'value',    // ✅ 数值 → 角度
    color: 'type', // ✅ 类别 → 颜色
  },
});
```
references/coordinates/g2-coord-transpose.md
---
id: "g2-coord-transpose"
title: "G2 转置坐标系(将柱状图转为条形图)"
description: |
  通过 coordinate: { transform: [{ type: 'transpose' }] } 将直角坐标系的 x/y 轴对调,
  最常见的用途是将竖向柱状图转换为水平条形图,
  适合分类名称较长或类别较多的场景。

library: "g2"
version: "5.x"
category: "coordinates"
tags:
  - "transpose"
  - "转置"
  - "条形图"
  - "horizontal"
  - "水平"
  - "coordinate"
  - "spec"

related:
  - "g2-mark-interval-basic"
  - "g2-mark-interval-grouped"
  - "g2-mark-interval-stacked"

use_cases:
  - "分类名称较长时,水平条形图标签更清晰"
  - "类别数量较多(> 8 个)时水平排列更美观"
  - "排名图(横向从大到小排列)"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/coordinate/transpose"
---

## 最小可运行示例(柱状图转条形图)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'interval',
  data: [
    { city: '北京',   gdp: 3.6 },
    { city: '上海',   gdp: 4.3 },
    { city: '广州',   gdp: 2.8 },
    { city: '深圳',   gdp: 3.2 },
    { city: '杭州',   gdp: 1.8 },
  ],
  encode: {
    x: 'city',   // 转置后,city 在 y 轴(垂直方向)
    y: 'gdp',    // 转置后,gdp 在 x 轴(水平方向)
  },
  coordinate: { transform: [{ type: 'transpose' }] },   // 关键:转置坐标系
});

chart.render();
```

## 排名条形图(排序 + 转置)

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'city', y: 'gdp', color: 'city' },
  transform: [
    { type: 'sortX', by: 'y', reverse: true },   // 先按值降序排列
  ],
  coordinate: { transform: [{ type: 'transpose' }] },
  axis: {
    x: { title: 'GDP(万亿元)' },
    y: { title: null },
  },
  labels: [
    {
      text: (d) => d.gdp.toFixed(1),
      position: 'outside',
      style: { fontSize: 12 },
    },
  ],
});
```

## 水平堆叠条形图

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: { transform: [{ type: 'transpose' }] },
});
```

## 横向区间图(甘特图风格)

```javascript
chart.options({
  type: 'interval',
  autoFit: true,
  data: [
    { stage: 'Phase 1', task: '原型', start: 1, end: 3 },
    { stage: 'Phase 1', task: '验证', start: 3, end: 5 },
    { stage: 'Phase 2', task: '开发', start: 4, end: 10 },
    { stage: 'Phase 2', task: '单元测试', start: 8, end: 11 },
    { stage: 'Phase 3', task: '集成', start: 10, end: 13 },
    { stage: 'Phase 3', task: '压测', start: 12, end: 15 }
  ],
  encode: {
    x: (d) => `${d.stage} - ${d.task}`,  // 组合标签字段
    y: 'start',                          // 起始时间映射到 y 轴
    y1: 'end',                           // 结束时间映射到 y1 通道
    color: 'stage'                       // 阶段映射到颜色
  },
  coordinate: { transform: [{ type: 'transpose' }] },  // 转置坐标系
  axis: {
    x: {
      title: '阶段与任务',
      labelTransform: 'rotate(30)'       // 标签倾斜防止重叠
    },
    y: { title: '时间(周)' }            // 时间轴标题
  }
});

chart.render();
```

## 常见错误与修正

### 错误:转置后轴标题配置未调整
```javascript
// ❌ 注意:转置后,原 x 配置作用于竖轴,原 y 配置作用于横轴
// 如果需要水平轴显示数值单位,应配置 axis.x(而非 axis.y)
chart.options({
  coordinate: { transform: [{ type: 'transpose' }] },
  axis: {
    y: { title: 'GDP(万亿)' },   // ❌ 转置后 y 轴是分类轴,不是数值轴
  },
});

// ✅ 正确:转置后的"水平轴"对应配置中的 axis.x
chart.options({
  coordinate: { transform: [{ type: 'transpose' }] },
  axis: {
    x: { title: 'GDP(万亿)' },   // ✅ 数值轴
    y: { title: null },             // ✅ 分类轴(分类名已经在左侧,无需标题)
  },
});
```

### 错误:横向区间图标签处理不当
```javascript
// ❌ 错误示例:使用 labelFormatter 处理组合标签易出错
chart.options({
  encode: {
    x: 'task',
    y: 'start',
    y1: 'end'
  },
  axis: {
    x: {
      labelFormatter: (task, item) => {
        const datum = item.data;
        return `${datum.stage}\n${task}`;
      }
    }
  }
});

// ✅ 正确做法:在数据预处理阶段构造组合字段
chart.options({
  encode: {
    x: (d) => `${d.stage} - ${d.task}`,  // 使用函数构造组合标签
    y: 'start',
    y1: 'end'
  },
  axis: {
    x: {
      title: '阶段与任务',
      labelTransform: 'rotate(30)'        // 适当旋转标签避免重叠
    }
  }
});
```
references/core/g2-core-chart-init.md
---
id: "g2-core-chart-init"
title: "G2 Chart 初始化与基础配置"
description: |
  介绍 G2 v5 中 Chart 对象的创建方式、必填与可选参数、
  自适应尺寸、主题配置及生命周期管理。
  必须使用 Spec 声明式写法(chart.options({})),禁止使用链式 API。

library: "g2"
version: "5.x"
category: "core"
tags:
  - "Chart"
  - "初始化"
  - "容器"
  - "autoFit"
  - "主题"
  - "生命周期"
  - "init"
  - "spec"
  - "options"
  - "padding"
  - "paddingLeft"
  - "paddingTop"
  - "paddingRight"
  - "paddingBottom"
  - "margin"
  - "inset"
  - "布局"

related:
  - "g2-core-encode-channel"
  - "g2-core-data-binding"
  - "g2-core-lifecycle"
  - "g2-theme-builtin"

use_cases:
  - "开始创建任何 G2 图表"
  - "配置图表画布尺寸和容器"
  - "设置全局主题和内边距"

anti_patterns:
  - "不要在同一个容器上多次 new Chart(会产生多个画布)"
  - "禁止使用链式 API(chart.interval().encode()...)"
  - "禁止在同一图表中多次调用 chart.options({})(后者会完全覆盖前者)——合并配置时应合并为一次调用;叠加多个 mark 时应使用 type: 'view' + children"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-27"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/chart"
---

## 核心概念

`Chart` 是 G2 中最顶层的容器对象,负责管理画布、视图、坐标系和渲染。

**必须使用 Spec 模式**:通过 `chart.options({})` 一次性传入完整描述对象,结构清晰,易于序列化和动态生成。

**禁止使用链式 API**:`chart.interval().encode()` 等链式调用禁止使用。

## 最小可运行示例(Spec 模式)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'line',        // Mark 类型
  data: [
    { x: 1, y: 10 },
    { x: 2, y: 30 },
    { x: 3, y: 20 },
  ],
  encode: { x: 'x', y: 'y' },
});

chart.render();
```

## 完整 Chart 容器配置项

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  // ── 必填 ──────────────────────────────
  container: 'container',        // string | HTMLElement:DOM 容器

  // ── 尺寸 ──────────────────────────────
  width: 640,                    // 画布宽度(px),默认 640
  height: 480,                   // 画布高度(px),默认 480
  autoFit: true,                 // 自动适应容器尺寸(忽略 width/height)

  // ── 内边距 ────────────────────────────
  // padding 只接受 number 或 'auto',不支持数组形式
  // 默认 'auto':G2 根据坐标轴/图例等组件自动计算,无需手动设置
  padding: 'auto',               // 'auto' | number(四边统一值)
  // 需要分方向控制时,使用以下单独配置(优先级高于 padding)
  paddingTop: 40,
  paddingRight: 20,
  paddingBottom: 40,
  paddingLeft: 60,
  inset: 0,                      // 数据区域内缩(防止数据点紧贴边缘)

  // ── 主题 ──────────────────────────────
  theme: 'classic',              // 'classic' | 'classicDark' | 'academy'

  // ── 渲染器 ────────────────────────────
  renderer: undefined,           // 默认 Canvas,可传入 SVG 渲染器

  // ── 像素比 ────────────────────────────
  devicePixelRatio: window.devicePixelRatio,
});
```

## Spec 模式完整结构

```javascript
chart.options({
  // Mark 类型
  type: 'interval',

  // 数据,不同 Mark 直接存在结构上的差异,优先使用对应 mark 中的数据结构
  data: [...],

  // 视觉通道映射
  encode: {
    x: 'genre',
    y: 'sold',
    color: 'genre',
  },

  // 数据变换
  transform: [{ type: 'stackY' }],

  // 比例尺
  scale: {
    y: { domain: [0, 500] },
    color: { range: ['#1890ff', '#52c41a'] },
  },

  // 坐标系
  coordinate: { transform: [{ type: 'transpose' }] },

  // 样式
  style: { radius: 4 },

  // 数据标签
  labels: [{ text: 'sold', position: 'outside' }],

  // Tooltip
  tooltip: { title: 'genre', items: [{ field: 'sold', name: '销量' }] },

  // 坐标轴
  axis: {
    x: { title: '游戏类型' },
    y: { title: '销量' },
  },

  // 图例
  legend: {
    color: { position: 'top' },
  },
});
```

## Spec 模式标准写法

```javascript
// ✅ 正确:Spec 模式(唯一推荐写法)
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'genre', y: 'sold', color: 'genre' },
  style: { radius: 4 },
});

// ❌ 禁止:链式 API 模式
chart.interval()
  .data([...])
  .encode('x', 'genre')
  .encode('y', 'sold')
  .encode('color', 'genre')
  .style({ radius: 4 });
```


## 响应式自适应

```javascript
// autoFit:宽度跟随容器,高度可固定
const chart = new Chart({
  container: 'container',
  autoFit: true,
  height: 400,
});

chart.options({ type: 'line', data: [...], encode: { x: 'x', y: 'y' } });
chart.render();
```

## 生命周期

```javascript
// 初次渲染
chart.render();

// 更新 Spec 后重新渲染(changeData 只更新数据)
chart.options({ type: 'bar',  newData, encode: { x: 'x', y: 'y' } });
chart.render();

// 仅更新数据(性能更好)
chart.changeData(newData);

// 销毁
chart.destroy();

// 事件监听
chart.on('afterrender', () => console.log('渲染完成'));
```

## 布局模型:margin / padding / inset

G2 v5 的视图空间分为四层,从外到内:

```
View Area(width × height)
  └─ margin(外边距,默认 16,View Area 与 Plot Area 之间的固定留白)
      └─ Plot Area(绘制区域)
          └─ padding(内边距,默认 auto,自动为 axis/legend/title 等组件计算空间)
              └─ Main Area(主区域)
                  └─ inset(呼吸范围,默认 0,防止数据点紧贴边缘)
                      └─ Content Area(数据标记绘制区域)
```

- **`margin`**:外边距,`number`,默认 `16`,View Area 与 Plot Area 之间的固定留白,不直接关联组件渲染
- **`padding`**:内边距,`number | 'auto'`,默认 `'auto'`,由 G2 自动计算为 axis/legend/title 等组件预留空间;手动设置会关闭自适应
- **`inset`**:呼吸范围,`number`,默认 `0`,散点图等防止点紧贴边缘时使用

```javascript
const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
  margin: 16,        // 默认,通常不需要修改
  // padding: 'auto',  // 默认,通常不需要修改
  paddingLeft: 80,   // 仅需要调整某侧时,单独设置
  inset: 8,          // 散点图建议设置,防止数据点被裁剪
});
```

## 常见错误与修正

### 错误 0:多次调用 chart.options({})

`chart.options()` 是**全量替换**,不是合并。每个图表只能调用一次。多 mark 叠加必须用 `type: 'view'` + `children`。详见 SKILL.md 核心约束 #3。

```javascript
// ❌ 错误:多次调用,只有最后一次生效
chart.options({ type: 'interval', data, encode: { x: 'x', y: 'y' } });
chart.options({ type: 'text', data: labelData, encode: { x: 'x', y: 'y', text: 'text' } });

// ✅ 正确:一次 chart.options(),用 view + children 组合
chart.options({
  type: 'view',
  children: [
    { type: 'interval', data, encode: { x: 'x', y: 'y' } },
    { type: 'text', data: labelData, encode: { x: 'x', y: 'y', text: 'text' } },
  ],
});
```


### 错误 1:container 指向不存在的 ID
```javascript
// ❌ 错误:DOM 还未加载
const chart = new Chart({ container: 'chart' });

// ✅ 正确:确保 DOM 已存在
document.addEventListener('DOMContentLoaded', () => {
  const chart = new Chart({ container: 'chart', width: 640, height: 400 });
  chart.options({ type: 'line',  [...], encode: { x: 'x', y: 'y' } });
  chart.render();
});
```

### 错误 2:重复初始化同一容器
```javascript
// ❌ 错误:会创建两个画布叠加
const chart1 = new Chart({ container: 'container' });
const chart2 = new Chart({ container: 'container' });

// ✅ 正确:先销毁旧实例
chart1.destroy();
const chart2 = new Chart({ container: 'container' });
```

### 错误 3:autoFit 与固定宽度混用
```javascript
// ❌ 错误:autoFit 会覆盖 width
const chart = new Chart({ container: 'c', autoFit: true, width: 640 });

// ✅ 正确:autoFit 时只设 height
const chart = new Chart({ container: 'c', autoFit: true, height: 400 });
```

### 错误 4:padding 使用数组形式(CSS 简写)
```javascript
// ❌ 错误:padding 不支持数组,G2 v5 的类型是 number | 'auto'
const chart = new Chart({
  container: 'container',
  autoFit: true,
  padding: [40, 30, 40, 50],   // ❌ 无效,会被忽略或引发异常
});

// ✅ 正确:四边统一
const chart = new Chart({ container: 'container', padding: 40 });

// ✅ 正确:分方向控制,使用 paddingTop/Right/Bottom/Left
const chart = new Chart({
  container: 'container',
  autoFit: true,
  paddingTop: 40,
  paddingRight: 30,
  paddingBottom: 40,
  paddingLeft: 50,
});
```

### 错误 5:padding 设为 0 导致坐标轴被截断
```javascript
// ❌ 错误:padding=0 会关闭自动计算,坐标轴/图例可能显示不全
const chart = new Chart({ container: 'container', padding: 0 });

// ✅ 正确:默认 'auto' 即可;只需要调整某一方向时,单独设置对应方向
const chart = new Chart({ container: 'container', paddingLeft: 80 });
// 或保持默认自动计算
const chart = new Chart({ container: 'container' });
```
references/core/g2-core-encode-channel.md
---
id: "g2-core-encode-channel"
title: "G2 encode 通道系统详解"
description: |
  encode 是 G2 v5 的核心数据映射机制,将数据字段映射到视觉通道(位置、颜色、大小、形状等)。
  在 Spec 模式中,encode 是 options 对象中的一个字段;在链式 API 中通过 .encode() 方法调用。

library: "g2"
version: "5.x"
category: "core"
tags:
  - "encode"
  - "通道"
  - "channel"
  - "数据映射"
  - "x"
  - "y"
  - "color"
  - "size"
  - "shape"
  - "spec"

related:
  - "g2-core-chart-init"
  - "g2-scale-linear"
  - "g2-scale-ordinal"
  - "g2-core-data-binding"

use_cases:
  - "将数据字段映射到图表的视觉属性"
  - "理解 Spec 模式中 encode 对象的结构"
  - "配置多通道映射"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/encode"
---

## 核心概念

**通道(Channel)** 是图形属性的抽象。在 Spec 模式中,`encode` 是 `options` 对象的一个字段,
其中每个 key 是通道名,value 是数据字段名(字符串)或常量。

## 通用通道列表

| 通道 | 说明 | 常见 Mark |
|------|------|-----------|
| `x` | X 轴位置 | 所有 Mark |
| `y` | Y 轴位置 | 所有 Mark |
| `color` | 颜色(fill + stroke) | 所有 Mark |
| `size` | 大小/粗细 | Point、Link、Line |
| `shape` | 形状 | Point、Interval |
| `opacity` | 透明度 | 所有 Mark |
| `series` | 系列分组(不影响颜色) | Line、Area |
| `key` | 动画时元素匹配键 | 所有 Mark |

## 基本用法(Spec 模式)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data: [
    { city: '北京', gdp: 3.6 },
    { city: '上海', gdp: 4.0 },
    { city: '广州', gdp: 2.8 },
  ],
  encode: {
    x: 'city',      // 分类轴:自动使用 Band Scale
    y: 'gdp',       // 数值轴:自动使用 Linear Scale
    color: 'city',  // 颜色区分
  },
});

chart.render();
```

## 典型场景示例

### 时间 x 轴(折线图)

```javascript
chart.options({
  type: 'line',
  data: [
    { date: new Date('2024-01-01'), value: 100 },
    { date: new Date('2024-02-01'), value: 130 },
    { date: new Date('2024-03-01'), value: 110 },
  ],
  encode: {
    x: 'date',      // Date 对象自动使用 Time Scale
    y: 'value',
    color: 'series', // 多系列折线
  },
});
```

### 双数值轴 + 气泡图(多通道映射)

```javascript
// 颜色映射表:scale.color.range 和 fill 回调共用
const COLOR_MAP = { 'China': '#5B8FF9', 'USA': '#fb7678', 'Japan': '#81e7ee' };

chart.options({
  type: 'point',
  data: [
    { income: 30000, lifeExpect: 72, population: 1400, country: 'China' },
    { income: 60000, lifeExpect: 79, population: 330,  country: 'USA' },
    { income: 45000, lifeExpect: 84, population: 125,  country: 'Japan' },
  ],
  encode: {
    x: 'income',
    y: 'lifeExpect',
    size: 'population',    // 气泡大小
    color: 'country',
    shape: 'point',
  },
  scale: {
    size: { type: 'sqrt', range: [4, 40] },    // sqrt 比例尺 + 合适的气泡范围
    color: { range: Object.values(COLOR_MAP) },
  },
  style: {
    fillOpacity: 0.85,            // 不要用 stroke: '#fff',浅色主题下像错误图表
    lineWidth: 0,
    // 径向渐变:从白色中心到映射色边缘,模拟 3D 球体质感
    // 通过 COLOR_MAP[datum.country] 获取颜色,与 scale.color.range 保持一致
    fill: (datum) => {
      const color = COLOR_MAP[datum.country];
      return `radial-gradient(circle at 35% 35%, rgb(255,255,255) 0%, ${color} 100%)`;
    },
    shadowBlur: 10,
    shadowColor: 'rgba(0, 0, 0, 0.15)',
    shadowOffsetY: 5,
  },
  legend: { size: false },        // size 图例意义不大,建议隐藏
});
```

### 函数映射(高级)

```javascript
chart.options({
  type: 'point',
  data: [...],
  encode: {
    x: 'date',
    y: 'value',
    // value 是函数时:动态计算通道值
    color: (d) => d.value > 100 ? 'red' : 'blue',
    size: (d) => Math.sqrt(d.count),
  },
});
```

## encode 字段值类型说明

| 值类型 | 含义 | 示例 |
|--------|------|------|
| `string`(字段名)| 映射数据字段 | `'genre'` |
| `string`(颜色/形状常量)| 所有元素相同值 | `'#1890ff'`、`'circle'` |
| `number` | 所有元素相同数值 | `10`(size 常量) |
| `function` | 动态计算 | `(d) => d.val * 2` |

> **判断规则**:`encode.color` 传入 `'genre'` → 视为字段名;传入 `'#1890ff'` → 视为颜色常量(以 `#` 开头或合法 CSS 颜色名)。`encode.size` 传入 `10`(数字)→ 常量。

## G2 v4 → v5 Spec 迁移对照

| G2 v4 链式 | G2 v5 Spec encode 字段 |
|-----------|------------------------|
| `.position('x*y')` | `encode: { x: 'x', y: 'y' }` |
| `.color('type')` | `encode: { color: 'type' }` |
| `.size('count')` | `encode: { size: 'count' }` |
| `.shape('circle')` | `encode: { shape: 'circle' }` |
| `.opacity('rate')` | `encode: { opacity: 'rate' }` |

## Mark 特有通道

不同 mark 类型支持额外的通道:

| Mark | 特有通道 | 说明 |
|------|---------|------|
| `interval` | `y1` | 区间终点(甘特图、范围柱)|
| `line` | `shape` | 线型:`'line'`\|`'smooth'`\|`'hv'`\|`'vh'`\|`'hvh'`\|`'vhv'` |
| `point` | `shape` | 点形:`'circle'`\|`'square'`\|`'diamond'`\|`'triangle'`\|... |
| `area` | `shape` | 区域:`'area'`\|`'smooth'`\|`'hvh'` |
| `text` | `text` | 文本内容(字段名或函数)|
| `image` | `src` | 图片 URL 字段 |
| `vector` | `rotate`, `size` | 方向角度和长度 |
| `sankey`/`chord` | `source`, `target`, `value` | 关系图的起终点和权重 |

## encode 常量 vs 字段名判定

```javascript
encode: {
  color: 'type',       // 字段名 → 按 type 字段分色
  color: '#1890ff',    // # 开头 → 颜色常量,所有元素同色
  color: 'steelblue',  // CSS 颜色名 → 颜色常量
  color: () => 'red',  // 函数 → 常量(返回固定值时等同常量)
  size: 'population',  // 字段名 → 按 population 映射大小
  size: 10,            // 数字 → 大小常量
}
```

## 常见错误与修正

### 错误 1:encode 写在了 style 里
```javascript
// ❌ 错误:style 不做数据映射
chart.options({
  type: 'interval',
  data: [...],
  style: { color: 'genre' },  // 无效!genre 是字段名,不是颜色值
});

// ✅ 正确:数据映射用 encode,固定样式用 style
chart.options({
  type: 'interval',
  data: [...],
  encode: { color: 'genre' },   // 数据驱动颜色
  style: { fillOpacity: 0.8 },  // 固定透明度
});
```

### 错误 2:color 与 series 通道混淆
```javascript
// 说明:color 既分组又改颜色;series 只分组不改颜色
// 多系列折线图推荐用 color:
chart.options({
  type: 'line',
  encode: {
    x: 'month',
    y: 'value',
    color: 'type',    // ✅ 推荐:每条线不同颜色
    // series: 'type', // 只分组,颜色相同(少用)
  },
});
```
references/core/g2-core-view-composition.md
---
id: "g2-core-view-composition"
title: "G2 视图组合(view + children)"
description: |
  G2 v5 通过 type: 'view' 容器和 children 数组实现多 Mark 叠加、
  共享数据、分面(facet)等复合图表。
  这是 Spec 模式中组合多个图形层的标准方式。

library: "g2"
version: "5.x"
category: "core"
tags:
  - "view"
  - "children"
  - "视图组合"
  - "多Mark叠加"
  - "layer"
  - "复合图表"
  - "spec"

related:
  - "g2-core-chart-init"
  - "g2-comp-annotation"
  - "g2-comp-facet-rect"

use_cases:
  - "在同一坐标系中叠加多种图形(折线+散点、面积+折线)"
  - "为多个子 Mark 共享数据源"
  - "在图表中添加标注层"

anti_patterns:
  - "只有单个 Mark 时不需要 view 容器,直接用对应 type 即可"
  - "children 中嵌套 type: 'view'——当某个子 Mark 需要独立数据时,直接在该 Mark 上指定 data 字段,而非再套一层 view + children"

difficulty: "intermediate"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/composition/view"
---

## 核心概念

```
chart.options({
  type: 'view',      // 容器类型
   [...],       // 父级数据(子 Mark 可继承)
  encode: {...},     // 父级编码(子 Mark 可继承)
  children: [        // 子 Mark 列表(按顺序渲染,后面的在上层)
    { type: 'area', ... },
    { type: 'line', ... },
    { type: 'point', ... },
  ],
});
```

**数据继承规则**:
- 子 Mark 若未指定 `data`,继承父级 `data`
- 子 Mark 若未指定 `encode`,继承父级 `encode` 中对应通道

## 面积 + 折线 + 散点叠加

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 700, height: 400 });

const data = [
  { month: 'Jan', value: 33 },
  { month: 'Feb', value: 78 },
  { month: 'Mar', value: 56 },
  { month: 'Apr', value: 91 },
  { month: 'May', value: 67 },
];

chart.options({
  type: 'view',
  data,                                    // 父级数据,三个子 Mark 共享
  encode: { x: 'month', y: 'value' },     // 父级编码,子 Mark 继承
  children: [
    {
      type: 'area',
      style: { fill: '#1890ff', fillOpacity: 0.15 },
    },
    {
      type: 'line',
      style: { stroke: '#1890ff', lineWidth: 2 },
    },
    {
      type: 'point',
      encode: { shape: 'circle' },
      style: { fill: 'white', stroke: '#1890ff', r: 4, lineWidth: 2 },
    },
  ],
});

chart.render();
```

## 子 Mark 独立数据(不继承父级)

```javascript
chart.options({
  type: 'view',
  children: [
    {
      type: 'interval',
       salesData,       // 独立数据
      encode: { x: 'month', y: 'revenue' },
    },
    {
      type: 'line',
       trendData,       // 独立数据
      encode: { x: 'month', y: 'growth' },
      scale: { y: { key: 'right' } },   // 独立 y 轴
    },
  ],
});
```

## 折线 + 参考线组合

```javascript
chart.options({
  type: 'view',
  data,
  children: [
    {
      type: 'line',
      encode: { x: 'month', y: 'value' },
    },
    {
      type: 'lineY',                       // 水平参考线
       [{ threshold: 60 }],
      encode: { y: 'threshold' },
      style: { stroke: 'red', lineDash: [4, 4] },
      labels: [{ text: '目标线', position: 'right', style: { fill: 'red' } }],
    },
  ],
});
```

## 常见错误与修正

### 错误 1:多次调用 options() 覆盖配置
```javascript
// ❌ 错误:每次 options() 调用都会覆盖上一次
chart.options({ type: 'area', ... });
chart.options({ type: 'line', ... });   // 覆盖了面积图!

// ✅ 正确:用 view + children
chart.options({
  type: 'view',
  data,
  children: [
    { type: 'area', ... },
    { type: 'line', ... },
  ],
});
```

### 错误 2:children 中嵌套 view(为子 Mark 单独变换数据时的常见误区)

```javascript
// ❌ 错误:在 children 里再套一层 type:'view' + children
chart.options({
  type: 'view',
  data,
  children: [
    { type: 'line', encode: { x: 'time', y: 'value' } },
    {
      type: 'view',                        // ❌ 不必要的嵌套 view
      data: data.map(d => ({              // 只是想用派生数据
        time: d.time,
        min: d.value - 0.1,
        max: d.value + 0.1,
      })),
      children: [
        { type: 'rangeY', encode: { x: 'time', y: 'min', y1: 'max' } },
      ],
    },
  ],
});

// ✅ 正确:直接在子 Mark 上指定 data,无需嵌套 view
chart.options({
  type: 'view',
  data,
  children: [
    { type: 'line', encode: { x: 'time', y: 'value' } },
    {
      type: 'rangeY',
      data: data.map(d => ({             // ✅ 直接在 Mark 上声明独立 data
        time: d.time,
        min: d.value - 0.1,
        max: d.value + 0.1,
      })),
      encode: { x: 'time', y: 'min', y1: 'max' },
      style: { fillOpacity: 0.1 },
    },
  ],
});
```

**规则**:`children` 数组的每个元素必须是 Mark(`line`/`point`/`interval` 等),
当某个 Mark 需要独立或派生数据时,在该 Mark 节点上直接写 `data`,而不是再包一层 `view`。
G2 不支持在 `children` 内嵌套 `view`。

### 错误 3:子 Mark 的 encode 字段名与数据不匹配
```javascript
// ❌ 错误:父级和子级 encode 的字段名应保持一致
chart.options({
  type: 'view',
  data: [{ month: 'Jan', value: 33 }],
  encode: { x: 'month', y: 'value' },
  children: [
    {
      type: 'point',
      encode: { x: 'date', y: 'amount' },  // 字段名与数据不匹配!
    },
  ],
});
```
references/data/g2-data-ema.md
---
id: "g2-data-ema"
title: "G2 EMA 指数移动平均"
description: |
  EMA(Exponential Moving Average)数据变换对数据进行指数移动平均平滑处理。
  通过对最近的数据点赋予更高的权重,减少数据波动性,更清晰地观察趋势。
  配置在 data.transform 中。

library: "g2"
version: "5.x"
category: "data"
tags:
  - "ema"
  - "指数移动平均"
  - "平滑"
  - "趋势"
  - "数据变换"
  - "data transform"

related:
  - "g2-mark-line"

use_cases:
  - "时间序列数据平滑"
  - "金融数据技术分析"
  - "训练指标平滑展示"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-27"
updated: "2025-03-27"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/data/ema"
---

## 核心概念

**EMA 是数据变换(Data Transform),不是标记变换(Mark Transform)**

- 数据变换配置在 `data.transform` 中
- 指数移动平均是一种数据平滑算法

**公式**:EMA_t = α × P_t + (1 - α) × EMA_{t-1}

**注意事项**:
- G2 中 `alpha` 越接近 1,平滑效果越明显
- `alpha` 越接近 0,EMA 越接近原始数据
- `field` 字段必须为数值型

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 700, height: 400 });

const data = [
  { t: 0, y: 100 },
  { t: 1, y: 180 },
  { t: 2, y: 120 },
  { t: 3, y: 200 },
  { t: 4, y: 150 },
  { t: 5, y: 250 },
];

chart.options({
  type: 'view',
  children: [
    {
      type: 'line',
       {
        type: 'inline',
        value: data,
        transform: [
          {
            type: 'ema',
            field: 'y',      // 要平滑的字段
            alpha: 0.6,      // 平滑因子
            as: 'emaY',      // 输出字段名
          },
        ],
      },
      encode: { x: 't', y: 'emaY' },
      style: { stroke: '#f90' },
    },
    {
      type: 'line',
       { type: 'inline', value: data },
      encode: { x: 't', y: 'y' },
      style: { stroke: '#ccc', lineDash: [4, 2] },
    },
  ],
});

chart.render();
```

## 配置项

| 属性  | 描述                                 | 类型     | 默认值     | 必选 |
| ----- | ------------------------------------ | -------- | ---------- | ---- |
| field | 需要平滑的字段名                     | `string` | `'y'`      | ✓    |
| alpha | 平滑因子,控制平滑程度(越大越平滑) | `number` | `0.6`      |      |
| as    | 生成的新字段名,若不指定将覆盖原字段 | `string` | 同 `field` |      |

## 金融行情平滑

```javascript
chart.options({
  type: 'view',
  children: [
    {
      type: 'line',
       {
        type: 'fetch',
        value: 'https://example.com/stock.csv',
        transform: [
          {
            type: 'ema',
            field: 'close',
            alpha: 0.7,
            as: 'emaClose',
          },
        ],
      },
      encode: { x: 'date', y: 'emaClose' },
      style: { stroke: '#007aff', lineWidth: 2 },
    },
    {
      type: 'line',
       { type: 'fetch', value: 'https://example.com/stock.csv' },
      encode: { x: 'date', y: 'close' },
      style: { stroke: '#bbb', lineDash: [4, 2] },
    },
  ],
});
```

## 常见错误与修正

### 错误 1:ema 放在 mark transform 中

```javascript
// ❌ 错误:ema 是数据变换,不能放在 mark 的 transform 中
chart.options({
  type: 'line',
  data,
  transform: [{ type: 'ema', field: 'y' }],  // ❌ 错误位置
});

// ✅ 正确:ema 放在 data.transform 中
chart.options({
  type: 'line',
   {
    type: 'inline',
    value: data,
    transform: [{ type: 'ema', field: 'y', as: 'emaY' }],  // ✅ 正确
  },
});
```

### 错误 2:字段不是数值型

```javascript
// ❌ 错误:field 字段必须为数值型
 {
  transform: [{ type: 'ema', field: 'name' }],  // ❌ name 是字符串
}

// ✅ 正确:使用数值型字段
 {
  transform: [{ type: 'ema', field: 'value' }],
}
```

### 错误 3:忘记设置 as 字段

```javascript
// ⚠️ 注意:不设置 as 会覆盖原字段
data: {
  transform: [{ type: 'ema', field: 'y' }],  // y 字段会被覆盖
}
encode: { y: 'y' },  // 使用的是平滑后的数据

// ✅ 推荐:设置 as 保留原字段
 {
  transform: [{ type: 'ema', field: 'y', as: 'emaY' }],
}
// 可以同时展示原始数据和平滑数据
```
references/data/g2-data-fetch.md
---
id: "g2-data-fetch"
title: "G2 Fetch 远程数据获取"
description: |
  Fetch 数据连接器从远程接口获取数据,支持 JSON、CSV 等格式解析。
  通过设置 data.type 为 'fetch' 启用,让数据源具备动态性。

library: "g2"
version: "5.x"
category: "data"
tags:
  - "fetch"
  - "远程数据"
  - "JSON"
  - "CSV"
  - "数据连接器"
  - "connector"

related:
  - "g2-data-filter"
  - "g2-data-fold"

use_cases:
  - "从 API 获取动态数据"
  - "加载远程 CSV 文件"
  - "大屏监控数据展示"

difficulty: "beginner"
completeness: "full"
created: "2025-03-27"
updated: "2025-03-27"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/data/fetch"
---

## 核心概念

**Fetch 是数据连接器(Data Connector),不是数据变换**

- 通过设置 `data.type: 'fetch'` 启用
- 支持 JSON、CSV 格式自动解析
- 远程地址不能设置鉴权

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 700, height: 400 });

chart.options({
  type: 'point',
   {
    type: 'fetch',
    value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/scatter.json',
  },
  encode: {
    x: 'weight',
    y: 'height',
    color: 'gender',
  },
});

chart.render();
```

## 配置项

| 属性      | 描述                                              | 类型               | 默认值                         |
| --------- | ------------------------------------------------- | ------------------ | ------------------------------ |
| value     | fetch 请求的网络地址                              | `string`           | -                              |
| format    | 远程文件的数据格式类型,决定用什么方式解析        | `'json' \| 'csv'`  | 默认取 value 末尾 `.` 后的后缀 |
| delimiter | 如果是 csv 文件,解析的时候分割符                 | `string`           | `,`                            |
| autoType  | 如果是 csv 文件,解析的时候是否自动判断列数据类型 | `boolean`          | `true`                         |
| transform | 对加载后的数据进行变换操作                        | `DataTransform[]`  | `[]`                           |

## 加载 CSV 文件

```javascript
chart.options({
  type: 'line',
   {
    type: 'fetch',
    value: 'https://example.com/data.csv',
    format: 'csv',           // 指定格式
    delimiter: ',',          // 分隔符
    autoType: true,          // 自动推断类型
    transform: [
      { type: 'filter', callback: (d) => d.value > 0 },
    ],
  },
  encode: { x: 'date', y: 'value' },
});
```

## 结合 transform 使用

```javascript
chart.options({
  type: 'interval',
   {
    type: 'fetch',
    value: 'https://example.com/sales.json',
    transform: [
      { type: 'filter', callback: (d) => d.year === 2024 },
      { type: 'sortBy', fields: [['amount', false]] },
      { type: 'slice', end: 10 },
    ],
  },
  encode: { x: 'product', y: 'amount' },
});
```

## 常见错误与修正

### 错误 1:远程地址需要鉴权

```javascript
// ❌ 错误:G2 fetch 不支持鉴权
data: {
  type: 'fetch',
  value: 'https://api.example.com/private-data',  // 需要 token
}

// ✅ 正确:使用公开的 API 或在服务端代理
 {
  type: 'fetch',
  value: 'https://public-api.example.com/data',  // 无需鉴权
}
```

### 错误 2:format 与文件格式不匹配

```javascript
// ❌ 错误:format 与实际格式不匹配
data: {
  type: 'fetch',
  value: 'https://example.com/data.json',
  format: 'csv',  // ❌ 实际是 JSON
}

// ✅ 正确:让 G2 自动推断或指定正确格式
 {
  type: 'fetch',
  value: 'https://example.com/data.json',
  // format 默认根据后缀推断为 'json'
}

// 或显式指定
 {
  type: 'fetch',
  value: 'https://example.com/api/data',  // 无后缀
  format: 'json',  // 显式指定
}
```

### 错误 3:CORS 问题

```javascript
// ❌ 错误:跨域请求被阻止
// 浏览器控制台会显示 CORS 错误

// ✅ 解决方案:
// 1. 服务端配置 CORS 头
// 2. 使用同源请求
// 3. 使用代理服务器
```
references/data/g2-data-filter.md
---
id: "g2-data-filter"
title: "G2 Filter 数据过滤"
description: |
  filter 数据变换在数据加载阶段根据条件过滤数据,只保留满足条件的行。
  与 JavaScript 的 Array.filter 类似,接受一个断言函数(predicate)。
  配置在 data.transform 中,在渲染前预处理数据。

library: "g2"
version: "5.x"
category: "data"
tags:
  - "filter"
  - "过滤"
  - "数据筛选"
  - "条件过滤"
  - "data transform"

related:
  - "g2-data-fold"
  - "g2-data-sort"
  - "g2-interaction-brush"

use_cases:
  - "只展示满足条件的数据子集(如值大于阈值的数据)"
  - "排除异常值或空值"
  - "在数据加载阶段做分类筛选"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/data/filter"
---

## 核心概念

**filter 是数据变换(Data Transform),不是标记变换(Mark Transform)**

- 数据变换配置在 `data.transform` 中
- 在数据加载阶段执行,影响所有使用该数据的标记
- 与 mark transform 不同,数据变换是数据预处理,不涉及视觉通道

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'interval',
   {
    type: 'inline',
    value: [
      { genre: 'Sports', sold: 275 },
      { genre: 'Strategy', sold: 115 },
      { genre: 'Action', sold: 120 },
      { genre: 'RPG', sold: 98 },
      { genre: 'Shooter', sold: 35 },
    ],
    transform: [
      {
        type: 'filter',
        callback: (d) => d.sold >= 100,  // 只保留销量 ≥ 100 的数据
      },
    ],
  },
  encode: { x: 'genre', y: 'sold', color: 'genre' },
});

chart.render();
```

## 排除空值 / 异常值

```javascript
chart.options({
  type: 'line',
   {
    type: 'inline',
    value: rawData,
    transform: [
      {
        type: 'filter',
        // 过滤掉 null、undefined、NaN
        callback: (d) => d.value != null && !isNaN(d.value) && d.value > 0,
      },
    ],
  },
  encode: { x: 'date', y: 'value' },
});
```

## 多条件过滤

```javascript
chart.options({
  type: 'point',
   {
    type: 'inline',
    value: allData,
    transform: [
      {
        type: 'filter',
        callback: (d) => d.category === 'A' && d.y > 50,
      },
    ],
  },
  encode: { x: 'x', y: 'y', color: 'category' },
});
```

## 与 fetch 连用

```javascript
chart.options({
  type: 'point',
   {
    type: 'fetch',
    value: 'https://example.com/data.json',
    transform: [
      {
        type: 'filter',
        callback: (d) => d.value > 100,
      },
    ],
  },
  encode: { x: 'x', y: 'y' },
});
```

## 多个数据变换组合

```javascript
chart.options({
  type: 'interval',
   {
    type: 'inline',
    value: rawData,
    transform: [
      { type: 'filter', callback: (d) => d.value != null },
      { type: 'sort', callback: (a, b) => b.value - a.value },
      { type: 'slice', start: 0, end: 10 },  // 只取前 10 条
    ],
  },
  encode: { x: 'category', y: 'value' },
});
```

## 配置项

| 属性     | 描述                                 | 类型                                           | 默认值                                                     |
| -------- | ------------------------------------ | ---------------------------------------------- | ---------------------------------------------------------- |
| callback | 过滤函数,返回 true 保留该行数据     | `(d: any, idx: number, arr: any[]) => boolean` | `(d) => d !== undefined && d !== null && !Number.isNaN(d)` |

## 常见错误与修正

### 错误 1:filter 放在 mark transform 中

```javascript
// ❌ 错误:filter 是数据变换,不能放在 mark 的 transform 中
chart.options({
  type: 'interval',
   myData,
  transform: [{ type: 'filter', callback: (d) => d.value > 100 }],  // ❌ 错误位置
});

// ✅ 正确:filter 放在 data.transform 中
chart.options({
  type: 'interval',
   {
    type: 'inline',
    value: myData,
    transform: [{ type: 'filter', callback: (d) => d.value > 100 }],  // ✅ 正确
  },
});
```

### 错误 2:callback 不是函数

```javascript
// ❌ 错误:callback 必须是函数
data: {
  transform: [{ type: 'filter', callback: 'value > 100' }],  // ❌ 字符串
}

// ✅ 正确:使用箭头函数
 {
  transform: [{ type: 'filter', callback: (d) => d.value > 100 }],  // ✅
}
```

### 错误 3:简写 data 无法配置 transform

```javascript
// ❌ 错误:简写 data 无法配置 transform
chart.options({
  data: myData,  // 简写形式
  // 无法添加 transform
});

// ✅ 正确:使用完整 data 配置
chart.options({
  data: {
    type: 'inline',
    value: myData,
    transform: [{ type: 'filter', callback: (d) => d.value > 100 }],
  },
});
```
references/data/g2-data-fold.md
---
id: "g2-data-fold"
title: "G2 Fold 宽表转长表"
description: |
  Fold 数据变换将宽格式数据(多列)转换为长格式数据(单列+分类列),
  使多个字段可以映射到同一个 color/series 通道。
  配置在 data.transform 中,是在 G2 中实现多系列图表的常用数据预处理手段。

library: "g2"
version: "5.x"
category: "data"
tags:
  - "fold"
  - "宽表转长表"
  - "pivot"
  - "多系列"
  - "数据变换"
  - "data transform"

related:
  - "g2-data-filter"
  - "g2-data-sort"
  - "g2-mark-line-basic"
  - "g2-mark-area-stacked"

use_cases:
  - "将宽表多列数据转换为多系列折线图"
  - "将同类指标的多个字段合并为一个系列字段"
  - "减少手动 flatMap 数据预处理代码"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/data/fold"
---

## 核心概念

**Fold 是数据变换(Data Transform),不是标记变换(Mark Transform)**

- 数据变换配置在 `data.transform` 中
- 在数据加载阶段执行,影响所有使用该数据的标记

**宽表(Wide)**:每个指标占一列
```
month | revenue | cost | profit
Jan   | 320     | 200  | 120
Feb   | 450     | 230  | 220
```

**长表(Long/Tidy)**:所有指标值合并到一列,增加分类列
```
month | key     | value
Jan   | revenue | 320
Jan   | cost    | 200
Jan   | profit  | 120
Feb   | revenue | 450
...
```

G2 的 `fold` 数据变换自动完成这个转换,无需手动 `flatMap`。

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 700, height: 400 });

// 宽表数据(每个指标是独立的列)
const wideData = [
  { month: 'Jan', revenue: 320, cost: 200, profit: 120 },
  { month: 'Feb', revenue: 450, cost: 230, profit: 220 },
  { month: 'Mar', revenue: 380, cost: 210, profit: 170 },
  { month: 'Apr', revenue: 510, cost: 260, profit: 250 },
];

chart.options({
  type: 'line',
  data: {
    type: 'inline',
    value: wideData,
    transform: [
      {
        type: 'fold',
        fields: ['revenue', 'cost', 'profit'],  // 要折叠的列名
        key: 'key',      // 生成的键列名(默认 'key')
        value: 'value',  // 生成的值列名(默认 'value')
      },
    ],
  },
  encode: {
    x: 'month',
    y: 'value',     // fold 后的值列
    color: 'key',   // fold 后的键列
  },
});

chart.render();
```

## 堆叠面积图中使用 fold

```javascript
chart.options({
  type: 'area',
  data: {
    type: 'inline',
    value: wideData,
    transform: [
      { type: 'fold', fields: ['revenue', 'cost', 'profit'] },
    ],
  },
  encode: { x: 'month', y: 'value', color: 'key' },
  transform: [{ type: 'stackY' }],  // mark transform
});
```

## 等价的手动方式(作为对比)

```javascript
// 不用 fold,手动 flatMap(代码较冗长)
const longData = wideData.flatMap((d) => [
  { month: d.month, metric: 'revenue', value: d.revenue },
  { month: d.month, metric: 'cost',    value: d.cost    },
  { month: d.month, metric: 'profit',  value: d.profit  },
]);

chart.options({
  type: 'line',
   longData,
  encode: { x: 'month', y: 'value', color: 'metric' },
});
```

## 配置项

| 属性   | 描述                           | 类型       | 默认值  |
| ------ | ------------------------------ | ---------- | ------- |
| fields | 需要展开的字段列表             | `string[]` |         |
| key    | 展开之后,字段枚举值对应字段名 | `string`   | `key`   |
| value  | 展开之后,数据值对应字段名     | `string`   | `value` |

## 常见错误与修正

### 错误 1:fold 放在 mark transform 中

```javascript
// ❌ 错误:fold 是数据变换,不能放在 mark 的 transform 中
chart.options({
  type: 'line',
   wideData,
  transform: [{ type: 'fold', fields: ['a', 'b'] }],  // ❌ 错误位置
});

// ✅ 正确:fold 放在 data.transform 中
chart.options({
  type: 'line',
  data: {
    type: 'inline',
    value: wideData,
    transform: [{ type: 'fold', fields: ['a', 'b'] }],  // ✅ 正确
  },
});
```

### 错误 2:fields 里的字段名拼写错误

```javascript
// ❌ 错误:字段名与数据不匹配,fold 后得到 undefined 值
data: {
  transform: [{ type: 'fold', fields: ['Revenue', 'Cost'] }],  // 大写,但数据是小写
}

// ✅ 正确:字段名必须与数据对象的 key 完全一致(区分大小写)
data: {
  transform: [{ type: 'fold', fields: ['revenue', 'cost'] }],
}
```

### 错误 3:encode 中 y/color 字段名与 as 配置不匹配

```javascript
// ❌ 错误:fold 默认生成 'key'/'value' 列,但 encode 用了别的名字
chart.options({
  data: {
    transform: [{ type: 'fold', fields: ['a', 'b'] }],  // 默认生成 key/value
  },
  encode: { y: 'metric', color: 'series' },  // 错误:字段名不存在
});

// ✅ 正确:encode 名字与 fold 的 key/value 配置一致
chart.options({
  data: {
    transform: [{ type: 'fold', fields: ['a', 'b'], key: 'metric', value: 'amount' }],
  },
  encode: { y: 'amount', color: 'metric' },
});
```

### 错误 4:简写 data 无法配置 transform

```javascript
// ❌ 错误:简写 data 无法配置 transform
chart.options({
   wideData,  // 简写形式
  // 无法添加 fold transform
});

// ✅ 正确:使用完整 data 配置
chart.options({
   {
    type: 'inline',
    value: wideData,
    transform: [{ type: 'fold', fields: ['revenue', 'cost'] }],
  },
});
```

### 错误 5:`` 关键字丢失——SyntaxError

这是代码生成时极常见的错误:`data` 属性值是一个多行嵌套对象,容易忘记写 `data:` 键名,导致 JavaScript 语法错误(`Unexpected token '{'`),图表完全无法运行。

```javascript
// ❌ 错误: 键名丢失,{ type: 'inline', ... } 是孤立对象字面量 → SyntaxError
chart.options({
  type: 'interval',
  {                          // ❌ 语法错误!缺少 data: 前缀
    type: 'inline',
    value: populationData,
    transform: [{
      type: 'fold',
      fields: ['Under 5 Years', '5 to 13 Years'],
      key: 'AgeGroup',
      value: 'Population',
    }]
  },
  encode: { x: 'State', y: 'Population', color: 'AgeGroup' },
});

// ✅ 正确:必须写 data: 键名
chart.options({
  type: 'interval',
   {                    // ✅  不能省略
    type: 'inline',
    value: populationData,
    transform: [{
      type: 'fold',
      fields: ['Under 5 Years', '5 to 13 Years'],
      key: 'AgeGroup',
      value: 'Population',
    }]
  },
  encode: { x: 'State', y: 'Population', color: 'AgeGroup' },
});
```

**为什么容易漏写**:`data` 值是一个多行嵌套对象,在生成时容易把它当作独立的「块」而非 `chart.options({})` 的属性,导致漏写 `` 前缀。同样问题也出现在 `coordinate:`、`children:` 等多行对象属性上——凡是值为复杂对象的属性,都要确认键名写全。
references/data/g2-data-kde.md
---
id: "g2-data-kde"
title: "G2 KDE 核密度估计"
description: |
  KDE(Kernel Density Estimation)数据变换对数据进行核密度估计,
  生成概率密度函数数据,用于密度图、小提琴图等可视化。
  配置在 data.transform 中。

library: "g2"
version: "5.x"
category: "data"
tags:
  - "kde"
  - "核密度估计"
  - "密度图"
  - "小提琴图"
  - "数据变换"
  - "data transform"

related:
  - "g2-mark-density"
  - "g2-mark-boxplot"

use_cases:
  - "密度图(Density Plot)"
  - "小提琴图(Violin Plot)"
  - "多组数据分布比较"

difficulty: "advanced"
completeness: "full"
created: "2025-03-27"
updated: "2025-03-27"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/data/kde"
---

## 核心概念

**KDE 是数据变换(Data Transform),不是标记变换(Mark Transform)**

- 数据变换配置在 `data.transform` 中
- 核密度估计是一种非参数统计方法,估计随机变量的概率密度函数
- 底层使用 [pdfast](https://www.npmjs.com/package/pdfast) 库

**输出**:处理后数据增加两个字段(默认 `y` 和 `size`),均为数组类型。

**⚠️ 关键:`field` 是输入,`as` 是输出,encode 必须用输出字段名**

```
kde: field='value' → 输出 as=['y','size'] → encode: { y: 'y', size: 'size' }
                      ↑ 不管 field 叫什么,encode 始终用 as 的值
```

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 700, height: 400 });

chart.options({
  type: 'density',
  data: {
    type: 'fetch',
    value: 'https://assets.antv.antgroup.com/g2/species.json',
    transform: [
      {
        type: 'kde',
        field: 'y',              // 进行 KDE 的字段
        groupBy: ['x', 'species'], // 分组字段
        size: 20,                // 生成数据点数量
      },
    ],
  },
  encode: {
    x: 'x',
    y: 'y',
    color: 'species',
    size: 'size',
  },
  tooltip: false,
});

chart.render();
```

## 配置项

| 属性    | 描述                                           | 类型               | 默认值          | 必选 |
| ------- | ---------------------------------------------- | ------------------ | --------------- | ---- |
| field   | 进行核密度算法的数据字段                       | `string`           | -               | ✓    |
| groupBy | 对数据进行分组的分组字段,可以指定多个         | `string[]`         | -               | ✓    |
| as      | 进行 KDE 处理之后,存储的字段                  | `[string, string]` | `['y', 'size']` | 否   |
| min     | 指定处理范围的最小值                           | `number`           | 数据最小值      | 否   |
| max     | 指定处理范围的最大值                           | `number`           | 数据最大值      | 否   |
| size    | 算法最终生成数据的条数,值越大密度曲线越精细   | `number`           | `10`            | 否   |
| width   | 确定一个元素左右影响多少点,值越大曲线越平滑   | `number`           | `2`             | 否   |

## 小提琴图

```javascript
chart.options({
  type: 'view',
  data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/species.json' },
  children: [
    {
      type: 'density',
      data: {
        transform: [
          { type: 'kde', field: 'y', groupBy: ['x', 'species'] },
        ],
      },
      encode: { x: 'x', y: 'y', series: 'species', color: 'species', size: 'size' },
      tooltip: false,
    },
    {
      type: 'boxplot',
      encode: { x: 'x', y: 'y', series: 'species', color: 'species', shape: 'violin' },
      style: { opacity: 0.5, strokeOpacity: 0.5, point: false },
    },
  ],
});
```

## 自定义参数

```javascript
chart.options({
  type: 'density',
  data: {
    transform: [
      {
        type: 'kde',
        field: 'y',
        groupBy: ['x'],
        size: 30,             // 更精细的密度曲线
        width: 3,             // 更平滑
        min: 0,               // 最小值
        max: 8,               // 最大值
        as: ['density_x', 'density_y'],  // 自定义输出字段名
      },
    ],
  },
  encode: { x: 'x', y: 'density_x', size: 'density_y' },
});
```

## 常见错误与修正

### 错误 1:kde 放在 mark transform 中

```javascript
// ❌ 错误:kde 是数据变换,不能放在 mark 的 transform 中
chart.options({
  type: 'density',
  data,
  transform: [{ type: 'kde', field: 'y' }],  // ❌ 错误位置
});

// ✅ 正确:kde 放在 data.transform 中
chart.options({
  type: 'density',
  data: {
    type: 'fetch',
    value: dataUrl,
    transform: [{ type: 'kde', field: 'y', groupBy: ['x'] }],  // ✅ 正确
  },
});
```

### 错误 2:缺少 groupBy

```javascript
// ❌ 错误:groupBy 是必选参数
data: {
  transform: [{ type: 'kde', field: 'y' }],  // 缺少 groupBy
}

// ✅ 正确:指定 groupBy
data: {
  transform: [{ type: 'kde', field: 'y', groupBy: ['category'] }],
}
```

### 错误 3:数据零方差或单点导致 KDE 退化(静默空白)

KDE 底层(pdfast 库)要求 `min < max`。当某分组所有值相同(方差=0)或只有 1 个数据点时,KDE 产生 NaN,该组不渲染。

```javascript
// ❌ 问题:零方差数据
const data = [
  { group: 'A', value: 10 },           // 只有 1 个点
  { group: 'B', value: 20 },
  { group: 'B', value: 20 },           // 全部相同,min=max=20
  { group: 'B', value: 20 },
];
// KDE groupBy: ['group'] 对 B 组处理时,min=max=20,产生 NaN → 不渲染

// ✅ 解决方案1:手动设置 min/max 保证区间非零
transform: [{
  type: 'kde',
  field: 'value',
  groupBy: ['group'],
  min: 0,    // 手动指定,确保 min ≠ max
  max: 100,
}]

// ✅ 解决方案2:数据不足时换图表类型
// KDE 建议每组至少 5-10 个不同值才能产生有意义的密度曲线
// 单点或全同值 → 改用散点图 / 箱线图
```

### 错误 4:encode 字段与 as 不匹配

```javascript
// ❌ 错误:使用默认 as 但 encode 用了其他字段名
data: {
  transform: [{ type: 'kde', field: 'y', groupBy: ['x'] }],  // 默认 as: ['y', 'size']
}
encode: { y: 'density', size: 'density_size' },  // ❌ 字段名不匹配

// ✅ 正确:使用正确的字段名
encode: { y: 'y', size: 'size' },  // ✅ 使用默认输出字段

// 或自定义 as
 {
  transform: [{ type: 'kde', field: 'y', groupBy: ['x'], as: ['density', 'density_size'] }],
},
encode: { y: 'density', size: 'density_size' },  // ✅ 匹配自定义字段
```

### 错误 5:数据量不足导致 KDE 结果为空或无效

KDE 需要足够的数据点才能生成有效的密度估计。如果某个分组内的数据点太少(如小于等于 1 个),KDE 无法计算出有效的结果,可能导致该分组不显示。

```javascript
// ❌ 问题:某些分组数据量不足
const data = [
  { species: 'setosa', x: 'SepalLength', y: 5.1 },
  { species: 'versicolor', x: 'SepalLength', y: 6.0 },
  { species: 'virginica', x: 'SepalLength' }, // 缺失 y 值
];

// ✅ 解决方案:确保每个分组有足够的有效数据
const validData = [
  { species: 'setosa', x: 'SepalLength', y: 5.1 },
  { species: 'setosa', x: 'SepalLength', y: 5.0 },
  { species: 'versicolor', x: 'SepalLength', y: 6.0 },
  { species: 'versicolor', x: 'SepalLength', y: 6.2 },
  { species: 'virginica', x: 'SepalLength', y: 6.5 },
  { species: 'virginica', x: 'SepalLength', y: 6.3 },
];
```

### 错误 6:未正确设置图表类型或编码导致渲染失败

在组合视图中使用 KDE 时,必须确保每个子图表都正确设置了类型和编码,特别是 `series` 映射。

```javascript
// ❌ 错误:缺少必要的 encode 字段或类型设置不当
children: [
  {
    type: 'density',
    data: { transform: [{ type: 'kde', field: 'y', groupBy: ['x', 'species'] }] },
    encode: { x: 'x', y: 'y', color: 'species', size: 'size' },
  },
  {
    type: 'boxplot',
    encode: { x: 'x', y: 'y', color: 'species', shape: 'violin' }, // 缺少 series 映射
  }
]

// ✅ 正确:确保所有必要字段都被正确映射
children: [
  {
    type: 'density',
    data: { transform: [{ type: 'kde', field: 'y', groupBy: ['x', 'species'] }] },
    encode: { x: 'x', y: 'y', series: 'species', color: 'species', size: 'size' },
  },
  {
    type: 'boxplot',
    encode: { x: 'x', y: 'y', series: 'species', color: 'species', shape: 'violin' },
  }
]
```

### 错误 7:KDE 分组后数据点过少导致渲染空白

KDE 需要每个分组有足够的数据点才能生成有效的密度估计。如果分组后的数据点过少(如每组少于 2 个有效值),KDE 无法计算出有效的结果,可能导致整个图表渲染为空白。

```javascript
// ❌ 问题:分组后每组数据点太少
const insufficientData = [
  { species: 'setosa', x: 'SepalLength', y: 5.1 },
  { species: 'versicolor', x: 'SepalLength', y: 6.0 },
  { species: 'virginica', x: 'SepalLength', y: 6.5 },
];

// ✅ 解决方案:确保每个分组有足够多的有效数据点
const sufficientData = [
  { species: 'setosa', x: 'SepalLength', y: 5.1 },
  { species: 'setosa', x: 'SepalLength', y: 5.0 },
  { species: 'setosa', x: 'SepalLength', y: 5.2 },
  { species: 'versicolor', x: 'SepalLength', y: 6.0 },
  { species: 'versicolor', x: 'SepalLength', y: 6.2 },
  { species: 'versicolor', x: 'SepalLength', y: 5.9 },
  { species: 'virginica', x: 'SepalLength', y: 6.5 },
  { species: 'virginica', x: 'SepalLength', y: 6.3 },
  { species: 'virginica', x: 'SepalLength', y: 6.7 },
];
```

### 错误 8:KDE 与 density 图表类型配合使用时未正确设置 encode

使用 KDE 作为数据变换时,必须确保 `encode` 中正确引用了 KDE 生成的字段(默认为 `y` 和 `size`),否则图表可能无法正确渲染。

```javascript
// ❌ 错误:未正确引用 KDE 生成的字段
chart.options({
  type: 'density',
  data: {
    transform: [{ type: 'kde', field: 'y', groupBy: ['x'] }]
  },
  encode: { x: 'x', y: 'originalY', size: 'originalSize' } // 错误字段名
});

// ✅ 正确:引用 KDE 生成的字段
chart.options({
  type: 'density',
  data: {
    transform: [{ type: 'kde', field: 'y', groupBy: ['x'] }]
  },
  encode: { x: 'x', y: 'y', size: 'size' } // 正确字段名
});
```

### 错误 9:KDE 分组字段选择不当导致渲染异常

在使用 KDE 时,`groupBy` 字段的选择非常重要。如果选择了错误的字段或者没有充分考虑数据结构,可能导致生成的密度曲线不符合预期甚至完全不可见。

```javascript
// ❌ 错误:groupBy 字段选择不当
chart.options({
  type: 'density',
  data: {
    transform: [
      {
        type: 'kde',
        field: 'y',
        groupBy: ['species'], // 仅按 species 分组,忽略了 x 字段的不同类别
      },
    ],
  },
  encode: {
    x: 'x',
    y: 'y',
    color: 'species',
    size: 'size',
  },
});

// ✅ 正确:合理选择 groupBy 字段
chart.options({
  type: 'density',
  data: {
    transform: [
      {
        type: 'kde',
        field: 'y',
        groupBy: ['x', 'species'], // 同时按 x 和 species 分组
      },
    ],
  },
  encode: {
    x: 'x',
    y: 'y',
    color: 'species',
    size: 'size',
  },
});
```

### 错误 10:KDE 输出字段被后续操作覆盖

当 KDE 生成的字段(如 `y` 和 `size`)在后续的数据处理步骤中被修改或覆盖时,会导致图表渲染异常。

```javascript
// ❌ 错误:KDE 输出字段被后续 transform 覆盖
chart.options({
  type: 'density',
  data: {
    transform: [
      { type: 'kde', field: 'y', groupBy: ['x', 'species'] },
      { type: 'map', callback: (d) => ({ ...d, y: d.y.map(v => v * 2) }) } // 修改了 y 字段
    ],
  },
  encode: {
    x: 'x',
    y: 'y',
    color: 'species',
    size: 'size',
  },
});

// ✅ 正确:使用自定义字段名避免冲突
chart.options({
  type: 'density',
  data: {
    transform: [
      { 
        type: 'kde', 
        field: 'y', 
        groupBy: ['x', 'species'],
        as: ['kdeY', 'kdeSize'] // 使用自定义字段名
      },
      { type: 'map', callback: (d) => ({ ...d, y: d.y.map(v => v * 2) }) } // 不会影响 KDE 结果
    ],
  },
  encode: {
    x: 'x',
    y: 'kdeY', // 使用 KDE 生成的自定义字段
    color: 'species',
    size: 'kdeSize',
  },
});
```
references/data/g2-data-log.md
---
id: "g2-data-log"
title: "G2 Log 数据日志"
description: |
  Log 数据变换将当前数据变换流中的数据打印到控制台,用于调试。
  配置在 data.transform 中,不影响数据流。

library: "g2"
version: "5.x"
category: "data"
tags:
  - "log"
  - "调试"
  - "日志"
  - "数据变换"
  - "data transform"

related:
  - "g2-data-filter"

use_cases:
  - "调试数据处理流程"
  - "检查中间数据状态"

difficulty: "beginner"
completeness: "full"
created: "2025-03-27"
updated: "2025-03-27"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/data/log"
---

## 核心概念

**Log 是数据变换(Data Transform),不是标记变换(Mark Transform)**

- 数据变换配置在 `data.transform` 中
- 用于调试,将数据打印到控制台
- 不影响数据流,数据会原样传递给下一个 transform

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 700, height: 400 });

const data = [
  { a: 1, b: 2, c: 3 },
  { a: 4, b: 5, c: 6 },
  { a: 7, b: 8, c: 9 },
];

chart.options({
  type: 'interval',
   {
    type: 'inline',
    value: data,
    transform: [
      { type: 'slice', start: 1 },  // 先切片
      { type: 'log' },               // 打印中间结果(调试用)
      { type: 'filter', callback: (d) => d.a < 5 },  // 再过滤
    ],
  },
  encode: { x: 'a', y: 'b' },
});

chart.render();
// 控制台会输出 slice 后的数据
```

## 调试数据处理流程

```javascript
chart.options({
   {
    type: 'fetch',
    value: 'https://example.com/data.json',
    transform: [
      { type: 'filter', callback: (d) => d.value > 100 },
      { type: 'log' },  // 检查过滤后的数据
      { type: 'sort', callback: (a, b) => b.value - a.value },
      { type: 'log' },  // 检查排序后的数据
      { type: 'slice', end: 10 },
    ],
  },
});
```

## 配置项

Log 变换没有配置项,直接使用即可。

```javascript
{ type: 'log' }
```

## 常见错误与修正

### 错误 1:log 放在 mark transform 中

```javascript
// ❌ 错误:log 是数据变换,不能放在 mark 的 transform 中
chart.options({
  type: 'interval',
  data,
  transform: [{ type: 'log' }],  // ❌ 错误位置
});

// ✅ 正确:log 放在 data.transform 中
chart.options({
  type: 'interval',
   {
    type: 'inline',
    value: data,
    transform: [{ type: 'log' }],  // ✅ 正确
  },
});
```

### 注意事项

```javascript
// ⚠️ 注意:生产环境应移除 log 变换,避免不必要的控制台输出
// 开发环境
 {
  transform: [
    { type: 'filter', callback: (d) => d.value > 0 },
    { type: 'log' },  // 调试用
  ],
}

// 生产环境
 {
  transform: [
    { type: 'filter', callback: (d) => d.value > 0 },
    // 移除 log
  ],
}
```
references/data/g2-data-slice.md
---
id: "g2-data-slice"
title: "G2 Slice 数据切片"
description: |
  Slice 数据变换对数据进行分片,获得子集。
  类似于 Array.prototype.slice,配置在 data.transform 中。

library: "g2"
version: "5.x"
category: "data"
tags:
  - "slice"
  - "切片"
  - "分页"
  - "数据变换"
  - "data transform"

related:
  - "g2-data-filter"
  - "g2-data-sort"

use_cases:
  - "数据分页展示"
  - "只取前 N 条数据"
  - "截取特定范围的数据"

difficulty: "beginner"
completeness: "full"
created: "2025-03-27"
updated: "2025-03-27"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/data/slice"
---

## 核心概念

**Slice 是数据变换(Data Transform),不是标记变换(Mark Transform)**

- 数据变换配置在 `data.transform` 中
- 类似于 [Array.prototype.slice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice)

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 700, height: 400 });

const data = [
  { month: 'Jan', value: 100 },
  { month: 'Feb', value: 120 },
  { month: 'Mar', value: 150 },
  { month: 'Apr', value: 180 },
  { month: 'May', value: 200 },
];

chart.options({
  type: 'line',
   {
    type: 'inline',
    value: data,
    transform: [
      {
        type: 'slice',
        start: 0,
        end: 3,  // 只取前 3 条数据
      },
    ],
  },
  encode: { x: 'month', y: 'value' },
});

chart.render();
```

## 配置项

| 属性  | 描述           | 类型     | 默认值           |
| ----- | -------------- | -------- | ---------------- |
| start | 分片的起始索引 | `number` | `0`              |
| end   | 分片的结束索引 | `number` | `arr.length - 1` |

## 取前 N 条数据

```javascript
chart.options({
  type: 'interval',
   {
    type: 'inline',
    value: largeData,
    transform: [
      { type: 'sort', callback: (a, b) => b.value - a.value },  // 先排序
      { type: 'slice', end: 10 },  // 取前 10 条
    ],
  },
  encode: { x: 'category', y: 'value' },
});
```

## 分页效果

```javascript
// 第 2 页,每页 10 条
const page = 2;
const pageSize = 10;

chart.options({
  data: {
    transform: [
      { type: 'slice', start: (page - 1) * pageSize, end: page * pageSize },
    ],
  },
});
```

## 常见错误与修正

### 错误 1:slice 放在 mark transform 中

```javascript
// ❌ 错误:slice 是数据变换,不能放在 mark 的 transform 中
chart.options({
  type: 'interval',
  data,
  transform: [{ type: 'slice', end: 10 }],  // ❌ 错误位置
});

// ✅ 正确:slice 放在 data.transform 中
chart.options({
  type: 'interval',
   {
    type: 'inline',
    value: data,
    transform: [{ type: 'slice', end: 10 }],  // ✅ 正确
  },
});
```

### 错误 2:索引超出范围

```javascript
// ⚠️ 注意:如果索引超出范围,G2 会自动处理,不会报错
data: {
  transform: [{ type: 'slice', start: 100, end: 200 }],  // 数据只有 50 条
}
// 结果:返回空数组
```
references/data/g2-data-sort.md
---
id: "g2-data-sort"
title: "G2 Sort 数据排序"
description: |
  sort 数据变换对数据进行排序,类似于 Array.prototype.sort。
  配置在 data.transform 中,在渲染前预处理数据顺序。
  常用于饼图、排行榜条形图等需要按数据大小排列的场景。

library: "g2"
version: "5.x"
category: "data"
tags:
  - "sort"
  - "排序"
  - "数据顺序"
  - "data transform"

related:
  - "g2-data-filter"
  - "g2-data-fold"
  - "g2-transform-sortx"
  - "g2-transform-sorty"

use_cases:
  - "饼图按大小排列扇区"
  - "条形图按数值排序"
  - "排行榜数据展示"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/data/sort"
---

## 核心概念

**sort 是数据变换(Data Transform),不是标记变换(Mark Transform)**

- 数据变换配置在 `data.transform` 中
- 使用 callback 比较函数(类似 Array.sort)
- 在数据加载阶段执行,影响所有使用该数据的标记

**与 mark transform sortX/sortY/sortColor 的区别:**
- 数据 sort:直接对原始数据数组排序
- mark sortX/sortY/sortColor:按视觉通道值排序,可聚合后排序

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data: {
    type: 'inline',
    value: [
      { category: 'A', value: 30 },
      { category: 'B', value: 50 },
      { category: 'C', value: 20 },
      { category: 'D', value: 40 },
    ],
    transform: [
      {
        type: 'sort',
        callback: (a, b) => b.value - a.value,  // 降序排列
      },
    ],
  },
  encode: { x: 'category', y: 'value' },
});

chart.render();
```

## 升序排列

```javascript
chart.options({
  type: 'interval',
   {
    type: 'inline',
    value: data,
    transform: [
      {
        type: 'sort',
        callback: (a, b) => a.value - b.value,  // 升序
      },
    ],
  },
  encode: { x: 'category', y: 'value' },
});
```

## 饼图按大小排序

```javascript
chart.options({
  type: 'interval',
  data: {
    type: 'inline',
    value: [
      { item: 'A', count: 40 },
      { item: 'B', count: 20 },
      { item: 'C', count: 30 },
    ],
    transform: [
      {
        type: 'sort',
        callback: (a, b) => b.count - a.count,  // 从大到小
      },
    ],
  },
  encode: { y: 'count', color: 'item' },
  coordinate: { type: 'theta' },
  transform: [{ type: 'stackY' }],
});
```

## 与其他数据变换组合

```javascript
chart.options({
  type: 'interval',
   {
    type: 'inline',
    value: rawData,
    transform: [
      { type: 'filter', callback: (d) => d.value > 0 },  // 先过滤
      { type: 'sort', callback: (a, b) => b.value - a.value },  // 再排序
      { type: 'slice', start: 0, end: 10 },  // 取前 10 条
    ],
  },
  encode: { x: 'category', y: 'value' },
});
```

## 按字符串排序

```javascript
chart.options({
  type: 'interval',
  data: {
    type: 'inline',
    value: data,
    transform: [
      {
        type: 'sort',
        callback: (a, b) => a.name.localeCompare(b.name),  // 按名称字母排序
      },
    ],
  },
  encode: { x: 'name', y: 'value' },
});
```

## 配置项

| 属性     | 描述                                               | 类型                         | 默认值        |
| -------- | -------------------------------------------------- | ---------------------------- | ------------- |
| callback | Array.sort 的 comparator,返回 1,0,-1 代表 > = < | `(a: any, b: any) => number` | `(a, b) => 0` |

## 与 Mark Transform sortX/sortY 的对比

| 特性 | 数据 sort | mark sortX/sortY |
|------|----------|------------------|
| 配置位置 | `data.transform` | `transform` (mark 层级) |
| 排序依据 | 原始数据字段 | 视觉通道值 |
| 聚合支持 | 不支持 | 支持按聚合值排序 |
| 切片支持 | 需配合 slice | 内置 slice 参数 |

```javascript
// 数据 sort:直接对数据排序
data: {
  transform: [{ type: 'sort', callback: (a, b) => b.value - a.value }],
}

// mark sortX:按 Y 通道聚合值排序
transform: [{ type: 'sortX', by: 'y', reducer: 'sum' }]
```

## 常见错误与修正

### 错误 1:sort 放在 mark transform 中

```javascript
// ❌ 错误:数据 sort 不能放在 mark 的 transform 中
chart.options({
  data: myData,
  transform: [{ type: 'sort', callback: (a, b) => b.value - a.value }],  // ❌ 错误位置
});

// ✅ 正确:sort 放在 data.transform 中
chart.options({
   {
    type: 'inline',
    value: myData,
    transform: [{ type: 'sort', callback: (a, b) => b.value - a.value }],  // ✅ 正确
  },
});
```

### 错误 2:混淆数据 sort 和 mark sortX

```javascript
// ❌ 错误:数据 sort 不支持 channel/by/reducer 参数
data: {
  transform: [{ type: 'sort', channel: 'x', by: 'value' }],  // ❌ 这是 mark transform 语法
}

// ✅ 正确:数据 sort 使用 callback
 {
  transform: [{ type: 'sort', callback: (a, b) => b.value - a.value }],
}

// 如果需要按聚合值排序,应使用 mark transform
transform: [{ type: 'sortX', by: 'y', reducer: 'sum' }]
```

### 错误 3:callback 返回值错误

```javascript
// ❌ 错误:返回布尔值
callback: (a, b) => a.value > b.value  // ❌ 返回布尔值

// ✅ 正确:返回数字(正数、负数、零)
callback: (a, b) => a.value - b.value  // ✅ 升序
callback: (a, b) => b.value - a.value  // ✅ 降序
```

### 错误 4:简写 data 无法配置 transform

```javascript
// ❌ 错误:简写 data 无法配置 transform
chart.options({
  data: myData,  // 简写形式
  // 无法添加 sort transform
});

// ✅ 正确:使用完整 data 配置
chart.options({
   {
    type: 'inline',
    value: myData,
    transform: [{ type: 'sort', callback: (a, b) => b.value - a.value }],
  },
});
```
references/data/g2-data-sortby.md
---
id: "g2-data-sortby"
title: "G2 SortBy 字段排序"
description: |
  SortBy 数据变换按照指定字段对数据进行排序。
  与 sort 不同,sortBy 通过字段名指定排序,更简洁直观。
  配置在 data.transform 中。

library: "g2"
version: "5.x"
category: "data"
tags:
  - "sortBy"
  - "字段排序"
  - "排序"
  - "数据变换"
  - "data transform"

related:
  - "g2-data-sort"
  - "g2-transform-sortx"

use_cases:
  - "按字段值排序"
  - "多字段组合排序"
  - "升序/降序排列"

difficulty: "beginner"
completeness: "full"
created: "2025-03-27"
updated: "2025-03-27"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/data/sortBy"
---

## 核心概念

**SortBy 是数据变换(Data Transform),不是标记变换(Mark Transform)**

- 数据变换配置在 `data.transform` 中
- 按字段名指定排序,比 sort 更简洁

**与 sort 的区别:**
- `sort`: 使用 callback 比较函数
- `sortBy`: 通过字段名指定,更简洁

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 700, height: 400 });

const data = [
  { genre: 'Sports', sold: 275 },
  { genre: 'Strategy', sold: 115 },
  { genre: 'Action', sold: 120 },
  { genre: 'Shooter', sold: 350 },
  { genre: 'Other', sold: 150 },
];

chart.options({
  type: 'interval',
  data: {
    type: 'inline',
    value: data,
    transform: [
      {
        type: 'sortBy',
        fields: ['sold'],  // 按 sold 字段升序排序
      },
    ],
  },
  encode: { x: 'genre', y: 'sold' },
});

chart.render();
```

## 配置项

| 属性   | 描述       | 类型                              | 默认值 |
| ------ | ---------- | --------------------------------- | ------ |
| fields | 排序的字段 | `(string \| [string, boolean])[]` | `[]`   |

## 降序排列

```javascript
chart.options({
  data: {
    type: 'inline',
    value: data,
    transform: [
      {
        type: 'sortBy',
        fields: [['sold', false]],  // false 表示降序
      },
    ],
  },
});
```

## 多字段排序

```javascript
// 先按 name 升序,name 相同时按 age 降序
chart.options({
  data: {
    type: 'inline',
    value: data,
    transform: [
      {
        type: 'sortBy',
        fields: [
          ['name', true],   // name 升序
          ['age', false],   // age 降序
        ],
      },
    ],
  },
});
```

## 与 sort 的对比

```javascript
// 使用 sortBy(推荐,更简洁)
data: {
  transform: [{ type: 'sortBy', fields: ['value'] }],
}

// 使用 sort(更灵活)
data: {
  transform: [{ type: 'sort', callback: (a, b) => a.value - b.value }],
}

// sortBy 降序
data: {
  transform: [{ type: 'sortBy', fields: [['value', false]] }],
}

// sort 降序
data: {
  transform: [{ type: 'sort', callback: (a, b) => b.value - a.value }],
}
```

## 常见错误与修正

### 错误 1:sortBy 放在 mark transform 中

```javascript
// ❌ 错误:sortBy 是数据变换,不能放在 mark 的 transform 中
chart.options({
  type: 'interval',
  data,
  transform: [{ type: 'sortBy', fields: ['value'] }],  // ❌ 错误位置
});

// ✅ 正确:sortBy 放在 data.transform 中
chart.options({
  type: 'interval',
  data: {
    type: 'inline',
    value: data,
    transform: [{ type: 'sortBy', fields: ['value'] }],  // ✅ 正确
  },
});
```

### 错误 2:字段名不存在

```javascript
// ❌ 错误:字段名不存在,排序无效
data: {
  transform: [{ type: 'sortBy', fields: ['nonexistent'] }],
}

// ✅ 正确:确保字段名存在
data: {
  transform: [{ type: 'sortBy', fields: ['value'] }],
}
```
references/data/g2-data-transform-patterns.md
---
id: "g2-data-transform-patterns"
title: "G2 数据转换模式"
description: |
  G2 可视化前最常用的数据预处理模式:宽转长(fold)、分组聚合、
  百分比计算、排名/排序、数据过滤、时间粒度聚合。
  这些模式既可在 JavaScript 前端完成,也可使用 G2 的内置 data transform。

library: "g2"
version: "5.x"
category: "data"
tags:
  - "数据转换"
  - "数据处理"
  - "宽转长"
  - "fold"
  - "分组聚合"
  - "百分比"
  - "排序"
  - "时间聚合"

related:
  - "g2-data-fold"
  - "g2-data-filter"
  - "g2-data-sort"
  - "g2-data-slice"

use_cases:
  - "将 API 返回的宽表转为 G2 需要的长表"
  - "前端对数据进行聚合、排名、百分比计算"
  - "为不同图表类型准备对应格式的数据"

difficulty: "intermediate"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-27"
author: "antv-team"
---

## 核心概念

**数据转换有两种方式**:

1. **JavaScript 前端预处理**:在传入 G2 之前用 JS 函数处理
2. **G2 内置 data transform**:配置在 `data.transform` 中,声明式处理

**推荐**:优先使用 G2 内置 transform,代码更简洁、可序列化。

## 模式 1:宽表转长表(Wide to Long)

**场景**:后端返回每列一个指标的宽表,需转为每行一个观测值的长表

**JavaScript 方式**:
```javascript
// 输入:宽表
const wideData = [
  { month: 'Jan', 北京: 3.6, 上海: 4.3, 广州: 2.8 },
  { month: 'Feb', 北京: 3.8, 上海: 4.5, 广州: 3.0 },
];

function wideToLong(data, idCols, valueCols, keyName = 'key', valueName = 'value') {
  return data.flatMap(row =>
    valueCols.map(col => ({
      ...Object.fromEntries(idCols.map(id => [id, row[id]])),
      [keyName]: col,
      [valueName]: row[col],
    }))
  );
}

const longData = wideToLong(wideData, ['month'], ['北京', '上海', '广州'], 'city', 'gdp');
// 输出:
// [
//   { month: 'Jan', city: '北京', gdp: 3.6 },
//   { month: 'Jan', city: '上海', gdp: 4.3 },
//   ...
// ]
```

**G2 内置方案**(推荐):
```javascript
chart.options({
  type: 'interval',
  data: {
    type: 'inline',
    value: wideData,
    transform: [{
      type: 'fold',
      fields: ['北京', '上海', '广州'],
      key: 'city',
      value: 'gdp',
    }],
  },
  encode: { x: 'month', y: 'gdp', color: 'city' },
});
```

## 模式 2:分组聚合(Group + Aggregate)

**场景**:按某字段分组,对另一字段求和/均值/计数

**JavaScript 方式**:
```javascript
// 输入:明细数据
const orderData = [
  { month: 'Jan', region: '华东', amount: 1200 },
  { month: 'Jan', region: '华东', amount: 800 },
  { month: 'Jan', region: '华南', amount: 950 },
  { month: 'Feb', region: '华东', amount: 1500 },
];

function groupSum(data, groupKeys, sumKey) {
  const map = new Map();
  data.forEach(row => {
    const key = groupKeys.map(k => row[k]).join('|');
    if (!map.has(key)) {
      const group = Object.fromEntries(groupKeys.map(k => [k, row[k]]));
      group[sumKey] = 0;
      map.set(key, group);
    }
    map.get(key)[sumKey] += row[sumKey];
  });
  return Array.from(map.values());
}

const aggregated = groupSum(orderData, ['month', 'region'], 'amount');
// 输出:
// [
//   { month: 'Jan', region: '华东', amount: 2000 },
//   { month: 'Jan', region: '华南', amount: 950 },
//   { month: 'Feb', region: '华东', amount: 1500 },
// ]
```

**G2 内置方案**(使用 mark transform):
```javascript
chart.options({
  type: 'interval',
   orderData,
  encode: { x: 'month', y: 'amount', color: 'region' },
  transform: [{ type: 'groupX', y: 'sum' }],  // 按 x 分组求和
});
```

## 模式 3:百分比计算

**场景**:计算每个类别占总量的百分比(饼图标签、百分比柱状图)

**JavaScript 方式**:
```javascript
function addPercentage(data, valueKey, pctKey = 'pct') {
  const total = data.reduce((sum, d) => sum + (d[valueKey] || 0), 0);
  return data.map(d => ({
    ...d,
    [pctKey]: total > 0 ? ((d[valueKey] / total) * 100).toFixed(1) : '0.0',
  }));
}

const dataWithPct = addPercentage(
  [{ city: '北京', gdp: 3.6 }, { city: '上海', gdp: 4.3 }, { city: '广州', gdp: 2.8 }],
  'gdp'
);
// 输出:[{ city: '北京', gdp: 3.6, pct: '33.6' }, ...]
```

**G2 内置方案**(百分比柱状图):
```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'city', y: 'gdp', color: 'city' },
  transform: [{ type: 'normalizeY' }],  // Y 轴归一化为百分比
});
```

**在饼图标签中使用百分比**:
```javascript
const total = data.reduce((sum, d) => sum + d.value, 0);

chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: { type: 'theta' },
  labels: [{
    text: (d) => `${d.type}\n${((d.value / total) * 100).toFixed(1)}%`,
    position: 'outside',
  }],
});
```

## 模式 4:排名 / Top N

**场景**:取数值最大/最小的 N 条数据,用于排名图

**JavaScript 方式**:
```javascript
function topN(data, valueKey, n, ascending = false) {
  return [...data]
    .sort((a, b) => ascending ? a[valueKey] - b[valueKey] : b[valueKey] - a[valueKey])
    .slice(0, n);
}

const top5Cities = topN(cityData, 'gdp', 5);
```

**G2 内置方案**:
```javascript
chart.options({
  type: 'interval',
  data: {
    type: 'inline',
    value: cityData,
    transform: [
      { type: 'sortBy', fields: [['gdp', false]] },  // 按 gdp 降序
      { type: 'slice', end: 5 },                      // 只取前 5 条
    ],
  },
  encode: { x: 'city', y: 'gdp' },
});
```

## 模式 5:时间粒度聚合

**场景**:日粒度数据聚合为周/月/季度

**JavaScript 方式**:
```javascript
// 日粒度 → 月粒度聚合
function aggregateByMonth(data, dateKey, valueKey) {
  const map = new Map();
  data.forEach(d => {
    const date = new Date(d[dateKey]);
    const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
    if (!map.has(monthKey)) {
      map.set(monthKey, { month: monthKey, total: 0, count: 0 });
    }
    const entry = map.get(monthKey);
    entry.total += d[valueKey];
    entry.count += 1;
  });
  return Array.from(map.values())
    .map(({ month, total, count }) => ({
      month,
      value: total,
      avg: total / count,
    }))
    .sort((a, b) => a.month.localeCompare(b.month));
}

// 月粒度 → 季度粒度
function aggregateByQuarter(data, monthKey, valueKey) {
  return data.reduce((acc, d) => {
    const [year, month] = d[monthKey].split('-');
    const quarter = `${year}-Q${Math.ceil(Number(month) / 3)}`;
    const existing = acc.find(a => a.quarter === quarter);
    if (existing) {
      existing[valueKey] += d[valueKey];
    } else {
      acc.push({ quarter, [valueKey]: d[valueKey] });
    }
    return acc;
  }, []);
}
```

## 模式 6:数据过滤与条件筛选

**场景**:根据维度/时间范围筛选数据

**JavaScript 方式**:
```javascript
function filterData(data, conditions) {
  return data.filter(d =>
    conditions.every(({ key, op, value }) => {
      switch (op) {
        case 'eq':  return d[key] === value;
        case 'gt':  return d[key] > value;
        case 'lt':  return d[key] < value;
        case 'gte': return d[key] >= value;
        case 'lte': return d[key] <= value;
        case 'in':  return value.includes(d[key]);
        default:    return true;
      }
    })
  );
}

const filtered = filterData(data, [
  { key: 'region', op: 'in',  value: ['华东', '华南'] },
  { key: 'sales',  op: 'gt',  value: 1000 },
]);
```

**G2 内置方案**:
```javascript
chart.options({
  type: 'interval',
  data: {
    type: 'inline',
    value: data,
    transform: [
      { type: 'filter', callback: (d) => ['华东', '华南'].includes(d.region) && d.sales > 1000 },
    ],
  },
  encode: { x: 'region', y: 'sales' },
});
```

**联动过滤示例**:
```javascript
document.getElementById('region-select').addEventListener('change', (e) => {
  const region = e.target.value;
  const filtered = region === 'all'
    ? allData
    : allData.filter(d => d.region === region);
  chart.changeData(filtered);
});
```

## 模式 7:数据归一化(0-1 标准化)

**场景**:多指标对比时,将不同量级的数据归一化到相同范围

**JavaScript 方式**:
```javascript
// Min-Max 归一化
function normalize(data, valueKey, normalizedKey = 'normalized') {
  const values = data.map(d => d[valueKey]);
  const min = Math.min(...values);
  const max = Math.max(...values);
  const range = max - min || 1;
  return data.map(d => ({
    ...d,
    [normalizedKey]: (d[valueKey] - min) / range,
  }));
}

// 多指标分别归一化(雷达图/平行坐标准备)
function normalizeMultiple(data, keys) {
  return keys.reduce((acc, key) => normalize(acc, key, `${key}_norm`), data);
}
```

## 常见错误与修正

### 错误 1:fold 后字段名与 encode 不匹配

```javascript
// ❌ fold 配置了 key='city', value='gdp',但 encode 还用原字段名
chart.options({
  type: 'interval',
  data: {
    type: 'inline',
    value: wideData,
    transform: [{ type: 'fold', fields: ['北京', '上海'], key: 'city', value: 'gdp' }],
  },
  encode: { color: '北京' },  // ❌ '北京' 字段已被 fold 消除
});

// ✅ encode 要用 fold 生成的新字段名
chart.options({
  type: 'interval',
  data: {
    type: 'inline',
    value: wideData,
    transform: [{ type: 'fold', fields: ['北京', '上海'], key: 'city', value: 'gdp' }],
  },
  encode: { y: 'gdp', color: 'city' },  // ✅ 使用 key/value 配置的字段名
});
```

### 错误 2:data transform 与 mark transform 混淆

```javascript
// ❌ 错误:fold 是数据变换,不应放在 mark transform
chart.options({
  type: 'interval',
   wideData,
  transform: [{ type: 'fold', fields: ['北京', '上海'] }],  // ❌ 错误位置
});

// ✅ 正确:fold 放在 data.transform 中
chart.options({
  type: 'interval',
  data: {
    type: 'inline',
    value: wideData,
    transform: [{ type: 'fold', fields: ['北京', '上海'], key: 'city', value: 'gdp' }],
  },
  transform: [{ type: 'stackY' }],  // mark transform
});
```
references/interactions/g2-interaction-adaptive-filter.md
---
id: "g2-interaction-adaptive-filter"
title: "G2 AdaptiveFilter 自适应过滤交互"
description: |
  adaptiveFilter 是 G2 v5 的交互,当数据量过大导致图表渲染性能下降时,
  自动对数据进行采样或聚合,保持图表响应流畅。
  适用于大数据量折线图、散点图等场景,结合 sliderFilter 或 scrollbarFilter 使用效果更佳。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "adaptiveFilter"
  - "自适应过滤"
  - "大数据"
  - "性能优化"
  - "采样"
  - "interaction"

related:
  - "g2-interaction-slider-filter"
  - "g2-transform-sample"
  - "g2-mark-line-basic"

use_cases:
  - "大数据量折线图自动降采样保持流畅"
  - "滑动窗口过滤时动态调整数据密度"
  - "散点图数据量超过阈值时自动聚合"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction/adaptive-filter"
---

## 核心概念

`adaptiveFilter` 监听图表的视口变化和数据规模,当可见数据量超过像素容量时,
自动应用采样策略(LTTB 算法等)减少渲染点数,避免过度绘制导致的性能问题。

通常与 `sliderFilter` 或 `scrollbarFilter` 配合使用,实现"滑动时自动适配数据量"。

## 基本用法

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 800, height: 400 });

chart.options({
  type: 'line',
  data: largeDataArray,   // 数千条以上数据
  encode: { x: 'date', y: 'value' },
  interaction: {
    adaptiveFilter: true,   // 启用自适应过滤
  },
});

chart.render();
```

## 与 sliderFilter 配合使用

```javascript
chart.options({
  type: 'view',
  data: largeDataArray,
  children: [
    {
      type: 'line',
      encode: { x: 'date', y: 'value' },
    },
  ],
  interaction: {
    sliderFilter: {
      x: { labelFormatter: (v) => new Date(v).toLocaleDateString() },
    },
    adaptiveFilter: true,   // 滑动窗口过滤后自动采样
  },
  slider: {
    x: { values: [0, 0.3] },   // 初始显示前 30% 数据
  },
});
```

## 配置项

```javascript
chart.options({
  interaction: {
    adaptiveFilter: {
      // 触发自适应采样的数据量阈值(默认 2000)
      // 可见数据点数超过此值时开始采样
      maxPoints: 2000,
    },
  },
});
```

## 常见错误与修正

### 错误:小数据量也启用 adaptiveFilter 导致数据被意外过滤
```javascript
// ❌ 不必要:数据量小时无需启用,反而可能造成数据丢失误解
chart.options({
   smallData,   // 只有 50 条数据
  interaction: { adaptiveFilter: true },
});

// ✅ 仅在大数据量场景启用
// adaptiveFilter 适用于 > 1000 条数据的场景
chart.options({
  data: massiveData,
  interaction: { adaptiveFilter: true },
});
```
references/interactions/g2-interaction-brush-axis.md
---
id: "g2-interaction-brush-axis"
title: "G2 轴刷选高亮(brushAxisHighlight)"
description: |
  brushAxisHighlight 在平行坐标系中,对单个轴进行区间刷选,
  高亮满足所有轴选区条件的折线。是平行坐标图最常见的多维过滤交互,
  可在多个轴上同时设置区间,实现多维度联合过滤。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "brushAxisHighlight"
  - "轴刷选"
  - "平行坐标"
  - "多维过滤"
  - "interaction"

related:
  - "g2-coord-parallel"
  - "g2-interaction-brush-filter"

use_cases:
  - "平行坐标图中多维联合筛选数据"
  - "在多个轴上分别设置过滤区间"
  - "高维数据的交互式探索"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction/brush-axis-highlight"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const data = [
  { name: '产品A', price: 120, sales: 300, rating: 4.5, stock: 80 },
  { name: '产品B', price: 85,  sales: 450, rating: 3.8, stock: 120 },
  { name: '产品C', price: 200, sales: 180, rating: 4.9, stock: 40 },
  { name: '产品D', price: 60,  sales: 600, rating: 3.2, stock: 200 },
  { name: '产品E', price: 150, sales: 220, rating: 4.2, stock: 65 },
];

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'line',
  data,
  encode: {
    position: ['price', 'sales', 'rating', 'stock'],
    color: 'name',
  },
  coordinate: { type: 'parallel' },
  style: { lineWidth: 1.5, strokeOpacity: 0.7 },
  interaction: {
    brushAxisHighlight: true,   // 在每个轴上可拖拽设置过滤区间
  },
});

chart.render();
```

## 与 parallel 坐标系的标准组合

```javascript
chart.options({
  type: 'line',
  data: carData,
  encode: {
    position: ['mpg', 'cylinders', 'displacement', 'horsepower', 'weight', 'acceleration'],
    color: 'origin',
  },
  coordinate: { type: 'parallel' },
  style: { lineWidth: 1, strokeOpacity: 0.5 },
  interaction: {
    brushAxisHighlight: {
      // 未被选中的线条的样式
      unhighlightedOpacity: 0.1,
    },
  },
  legend: { color: { position: 'top' } },
});
```

## 常见错误与修正

### 错误:在非平行坐标系图表上使用 brushAxisHighlight
```javascript
// ❌ brushAxisHighlight 专门为平行坐标系设计
chart.options({
  type: 'line',
  encode: { x: 'date', y: 'value' },   // 普通折线图
  coordinate: { type: 'cartesian' },
  interaction: { brushAxisHighlight: true },  // ❌ 普通图表没有"轴"可以刷选
});

// ✅ 应使用普通的 brushHighlight 或 brushFilter
chart.options({
  interaction: { brushHighlight: true },  // ✅ 普通矩形刷选
});

// ✅ 平行坐标图才用 brushAxisHighlight
chart.options({
  coordinate: { type: 'parallel' },
  interaction: { brushAxisHighlight: true },  // ✅
});
```
references/interactions/g2-interaction-brush-filter.md
---
id: "g2-interaction-brush-filter"
title: "G2 刷选过滤交互(brushFilter)"
description: |
  brushFilter 允许用户在图表上拖拽绘制矩形区域来过滤数据。
  与 brushHighlight 不同,brushFilter 会直接过滤掉选区外的数据点,
  只保留选中区域内的数据。支持 x/y 方向单轴过滤和二维矩形过滤。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "brush"
  - "brushFilter"
  - "刷选"
  - "过滤"
  - "交互"
  - "interaction"

related:
  - "g2-interaction-brush"
  - "g2-interaction-element-select"

use_cases:
  - "散点图中框选感兴趣的数据点进行深入分析"
  - "时间序列中框选特定时间段放大查看"
  - "多维数据探索:矩形框选数据子集"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction/brush-filter"
---

## 最小可运行示例(散点图刷选过滤)

```javascript
import { Chart } from '@antv/g2';

const data = Array.from({ length: 300 }, () => ({
  x: Math.random() * 100,
  y: Math.random() * 100,
  group: Math.floor(Math.random() * 4),
}));

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'point',
  data,
  encode: { x: 'x', y: 'y', color: 'group', shape: 'point' },
  scale: { color: { type: 'ordinal' } },
  interaction: {
    brushFilter: true,   // 启用刷选过滤:拖拽矩形区域过滤数据
  },
});

chart.render();
```

## 仅 X 轴方向刷选(时间范围过滤)

```javascript
chart.options({
  type: 'line',
  data: timeData,
  encode: { x: 'date', y: 'value', color: 'type' },
  interaction: {
    brushXFilter: true,   // 仅 X 轴方向的刷选过滤(常用于时间筛选)
  },
});
```

## 自定义刷选样式

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'x', y: 'y' },
  interaction: {
    brushFilter: {
      maskFill: '#1890ff',
      maskFillOpacity: 0.15,
      maskStroke: '#1890ff',
      maskLineWidth: 1.5,
    },
  },
});
```

## 刷选高亮 vs 刷选过滤

```javascript
// brushHighlight:选区外的元素变暗(全部数据仍可见)
chart.options({ interaction: { brushHighlight: true } });

// brushFilter:选区外的元素被过滤掉(只剩选中数据)
chart.options({ interaction: { brushFilter: true } });
```

## 常见错误与修正

### 错误:brushFilter 和 brushHighlight 同时启用——行为冲突
```javascript
// ❌ 两者同时启用会产生冲突
chart.options({
  interaction: {
    brushFilter: true,
    brushHighlight: true,  // ❌ 与 brushFilter 冲突
  },
});

// ✅ 只启用其中一个
chart.options({
  interaction: {
    brushFilter: true,  // ✅ 过滤模式
  },
});
```
references/interactions/g2-interaction-brush-x-y-highlight.md
---
id: "g2-interaction-brush-x-highlight"
title: "G2 BrushXHighlight / BrushYHighlight 单轴框选高亮"
description: |
  brushXHighlight 和 brushYHighlight 是 G2 v5 的交互,
  限制框选范围在 X 轴方向(或 Y 轴方向),高亮选中区域内的图元,非选中区域半透明淡出。
  适用于时间序列对比、趋势局部聚焦等场景。
  若需要过滤数据而非高亮,请使用 brushXFilter / brushYFilter。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "brushXHighlight"
  - "brushYHighlight"
  - "框选高亮"
  - "X轴框选"
  - "Y轴框选"
  - "interaction"
  - "highlight"

related:
  - "g2-interaction-brush"
  - "g2-interaction-brush-filter"
  - "g2-interaction-brush-xy"

use_cases:
  - "时间轴上圈选某段时间段,高亮对应数据点"
  - "横向对比图表中选取某几个分类高亮"
  - "散点图中按 Y 轴范围高亮异常值区域"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction/brush-x-highlight"
---

## 核心概念

- `brushXHighlight`:仅在 X 轴方向框选,选中元素高亮,其余淡出
- `brushYHighlight`:仅在 Y 轴方向框选,选中元素高亮,其余淡出
- 高亮效果不过滤数据,所有数据仍然可见(与 `brushXFilter` 区别)

## BrushXHighlight 基本用法

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 800, height: 400 });

chart.options({
  type: 'line',
  data: timeSeriesData,
  encode: { x: 'date', y: 'value', color: 'type' },
  interaction: {
    brushXHighlight: true,   // 启用 X 轴框选高亮
  },
});

chart.render();
```

## BrushYHighlight 基本用法

```javascript
chart.options({
  type: 'point',
  data: scatterData,
  encode: { x: 'x', y: 'y', color: 'category' },
  interaction: {
    brushYHighlight: true,   // 启用 Y 轴框选高亮
  },
});
```

## 配置项

```javascript
chart.options({
  interaction: {
    brushXHighlight: {
      series: true,      // 高亮同系列所有点(折线图中选中一点则整条线高亮),默认 true
      state: {
        // 自定义高亮/非高亮状态样式
        selected: {
          lineWidth: 2,
          opacity: 1,
        },
        unselected: {
          opacity: 0.2,
        },
      },
    },
  },
});
```

## X/Y 同时框选(自由框选)

```javascript
// 如需自由框选(同时限制 X 和 Y),使用 brushHighlight
chart.options({
  interaction: {
    brushHighlight: true,    // 自由矩形框选高亮
  },
});
```

## 常见错误与修正

### 错误:把高亮和过滤混淆
```javascript
// ❌ 以为 brushXHighlight 会过滤掉非选中数据
// brushXHighlight 只改变透明度,数据仍然全部显示

// ✅ 如果需要过滤数据(非选中区域从图表中移除),使用:
chart.options({
  interaction: { brushXFilter: true },   // 过滤模式,非选中数据消失
});

// ✅ 如果只需要高亮不过滤,使用:
chart.options({
  interaction: { brushXHighlight: true },   // 高亮模式,非选中数据淡出
});
```
references/interactions/g2-interaction-brush-xy.md
---
id: "g2-interaction-brush-xy"
title: "G2 单轴框选(brushXHighlight / brushYHighlight / brushXFilter / brushYFilter)"
description: |
  单轴框选交互限制刷选只在一个方向上生效:
  - brushXHighlight/brushYHighlight:框选高亮,不过滤数据
  - brushXFilter/brushYFilter:框选并过滤数据(隐藏框选范围外的元素)
  X 方向框选适合时间序列的区间选择,Y 方向框选适合数值范围筛选。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "brushXHighlight"
  - "brushYHighlight"
  - "brushXFilter"
  - "brushYFilter"
  - "单轴框选"
  - "刷选"
  - "interaction"

related:
  - "g2-interaction-brush-filter"
  - "g2-interaction-brush-axis"
  - "g2-comp-slider"

use_cases:
  - "时间序列图表:X 方向框选时间区间高亮"
  - "散点图:Y 方向框选数值范围筛选"
  - "折线图区间对比标注"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction/brush-highlight"
---

## brushXHighlight(X 方向框选高亮)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 800, height: 400 });

chart.options({
  type: 'line',
  data: timeSeriesData,
  encode: { x: 'date', y: 'value', color: 'series' },
  interaction: {
    brushXHighlight: true,  // 横向框选,高亮选中区间的折线
  },
});

chart.render();
```

## brushXFilter(X 方向框选过滤)

```javascript
// 框选后只显示框选区间内的数据
chart.options({
  type: 'point',
  data: scatterData,
  encode: { x: 'date', y: 'value', color: 'category' },
  interaction: {
    brushXFilter: true,   // 框选 X 范围,过滤范围外的点
  },
});
```

## brushYFilter(Y 方向框选过滤)

```javascript
// 框选数值范围,只显示 Y 值在选中区间内的数据
chart.options({
  type: 'point',
  data,
  encode: { x: 'x', y: 'y', color: 'category', size: 'value' },
  interaction: {
    brushYFilter: true,   // 纵向框选,过滤 Y 范围外的点
  },
});
```

## 四种框选交互对比

```javascript
// brushHighlight   → 二维框选,高亮(不过滤)
// brushFilter      → 二维框选,过滤数据
// brushXHighlight  → X 方向框选,高亮
// brushXFilter     → X 方向框选,过滤
// brushYHighlight  → Y 方向框选,高亮
// brushYFilter     → Y 方向框选,过滤

// 在散点图上同时支持高亮(单 X 轴框选)
chart.options({
  interaction: {
    brushXHighlight: {
      series: true,        // 是否高亮同系列其他点
    },
  },
});
```

## 常见错误与修正

### 错误:brushXFilter 与 brushFilter 效果相同的误解
```javascript
// brushFilter 可以在 X/Y 两个方向同时框选一个矩形区域
chart.options({ interaction: { brushFilter: true } });  // 二维矩形框选

// brushXFilter 只能在 X 方向拖拽,形成一个竖直条带
chart.options({ interaction: { brushXFilter: true } }); // 仅 X 轴方向

// 使用场景不同:时间序列用 brushXFilter 更直观
```
references/interactions/g2-interaction-brush.md
---
id: "g2-interaction-brush"
title: "G2 框选交互(brush)"
description: |
  G2 v5 内置框选交互,通过 interaction: [{ type: 'brushHighlight' }] 或 brushFilter
  实现鼠标拖拽框选、高亮/过滤数据点。常用于散点图、折线图等需要局部聚焦的场景。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "brush"
  - "框选"
  - "interaction"
  - "brushHighlight"
  - "brushFilter"
  - "交互"
  - "spec"

related:
  - "g2-mark-point-scatter"
  - "g2-interaction-element-highlight"
  - "g2-core-view-composition"

use_cases:
  - "散点图中框选关注的数据点区域"
  - "时间序列图中框选某段时间范围并过滤"
  - "大数据集局部聚焦分析"

difficulty: "intermediate"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction/brush-highlight"
---

## 基本用法(brushHighlight:框选高亮)

拖拽鼠标框选数据点,选中区域高亮,未选中区域变暗:

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'point',
  data: [
    { x: 1, y: 4.8, category: 'A' },
    { x: 2, y: 3.2, category: 'B' },
    { x: 3, y: 6.1, category: 'A' },
    { x: 4, y: 2.5, category: 'C' },
    { x: 5, y: 7.3, category: 'B' },
    { x: 6, y: 5.0, category: 'A' },
    { x: 7, y: 1.8, category: 'C' },
  ],
  encode: { x: 'x', y: 'y', color: 'category', size: 8 },
  interaction: [
    { type: 'brushHighlight' },   // 框选高亮
  ],
});

chart.render();
```

## brushFilter:框选过滤

框选后只保留选中区域内的数据点(其余被移除):

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'x', y: 'y', color: 'category' },
  interaction: [
    { type: 'brushFilter' },   // 框选过滤(只显示选中区域的点)
  ],
});
```

## 散点图 + 框选 + 详情联动

```javascript
chart.options({
  type: 'point',
  data,
  encode: {
    x: 'income',
    y: 'happiness',
    color: 'region',
    size: 'population',
  },
  scale: {
    size: { range: [4, 20] },
  },
  interaction: [
    { type: 'brushHighlight' },
    { type: 'tooltip' },         // 同时保留 tooltip 交互
  ],
  legend: { color: { position: 'top' } },
});
```

## 单轴框选(只横向/纵向)

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'date', y: 'price' },
  interaction: [
    {
      type: 'brushXHighlight',   // 只允许横向框选(按时间范围)
    },
  ],
});

// 纵向框选:brushYHighlight
chart.options({
  interaction: [{ type: 'brushYHighlight' }],
});
```

## 框选 + 联动其他图表

通过监听事件,实现多图联动:

```javascript
const chart = new Chart({ container: 'container', width: 700, height: 400 });

chart.options({
  type: 'point',
  data,
  encode: { x: 'x', y: 'y', color: 'type' },
  interaction: [{ type: 'brushFilter' }],
});

chart.render();

// 监听框选事件
chart.on('brush:filter', (event) => {
  const filteredData = event.data.items;   // 框选后的剩余数据
  console.log('选中数据:', filteredData);
  // 可据此更新其他图表...
});
```

## 常见错误与修正

### 错误:interaction 写成对象而非数组
```javascript
// ❌ 错误:interaction 必须是数组
chart.options({
  interaction: { type: 'brushHighlight' },
});

// ✅ 正确:数组格式
chart.options({
  interaction: [{ type: 'brushHighlight' }],
});
```

### 错误:同时启用 brushHighlight 和 brushFilter
```javascript
// ❌ 不推荐:两者功能冲突,同时使用会产生意外行为
chart.options({
  interaction: [
    { type: 'brushHighlight' },
    { type: 'brushFilter' },
  ],
});

// ✅ 正确:根据需求选其一
chart.options({
  interaction: [{ type: 'brushHighlight' }],  // 高亮但保留所有点
  // 或
  // interaction: [{ type: 'brushFilter' }],  // 过滤只显示选中点
});
```
references/interactions/g2-interaction-brushx-filter.md
---
id: "g2-interaction-brushx-filter"
title: "G2 BrushXFilter Interaction"
description: |
  X 轴方向刷选过滤交互。用户可以通过拖拽选择 X 轴范围,
  过滤显示该范围内的数据。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "刷选"
  - "过滤"
  - "brush"
  - "X轴"
  - "数据筛选"

related:
  - "g2-interaction-brush-filter"
  - "g2-interaction-brushy-filter"
  - "g2-interaction-brushx-highlight"

use_cases:
  - "时间范围筛选"
  - "X 轴区间选择"
  - "数据缩放查看"

anti_patterns:
  - "需要 Y 轴方向筛选时改用 BrushYFilter"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction"
---

## 核心概念

BrushXFilter 交互允许用户在 X 轴方向拖拽选择一个区间,图表会自动过滤并只显示该区间内的数据。

**特点:**
- 只能在 X 轴方向选择
- 选择后自动过滤数据
- 支持重置选择

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'line',
  data: [
    { date: '2024-01', value: 100 },
    { date: '2024-02', value: 120 },
    { date: '2024-03', value: 150 },
    { date: '2024-04', value: 130 },
    { date: '2024-05', value: 160 },
  ],
  encode: {
    x: 'date',
    y: 'value',
  },
  interaction: {
    brushXFilter: true,
  },
});

chart.render();
```

## 常用变体

### 自定义样式

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  interaction: {
    brushXFilter: {
      brushStyle: {
        fill: '#1890ff',
        fillOpacity: 0.2,
        stroke: '#1890ff',
      },
    },
  },
});
```

### 设置初始选区

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  interaction: {
    brushXFilter: {
      selection: [0.2, 0.8],  // 初始选区比例 [start, end]
    },
  },
});
```

## 完整类型参考

```typescript
interface BrushXFilterInteraction {
  brushXFilter: boolean | {
    brushStyle?: {
      fill?: string;
      fillOpacity?: number;
      stroke?: string;
      lineWidth?: number;
    };
    selection?: [number, number];  // [startRatio, endRatio]
    // 其他配置继承自 BrushFilter
  };
}
```

## 与 BrushFilter/BrushYFilter 的对比

| Interaction | 选择方向 | 常用场景 |
|-------------|---------|---------|
| brushFilter | 任意方向 | 通用筛选 |
| brushXFilter | 仅 X 方向 | 时间范围筛选 |
| brushYFilter | 仅 Y 方向 | 数值范围筛选 |

## 常见错误与修正

### 错误 1:与其他 brush 交互冲突

```javascript
// ❌ 错误:同时启用多个 brush 交互可能冲突
interaction: {
  brushXFilter: true,
  brushYFilter: true,
}

// ✅ 正确:根据需求选择一个
interaction: {
  brushXFilter: true,
}
```

### 错误 2:selection 参数格式错误

```javascript
// ❌ 错误:selection 应该是比例值 [0-1]
interaction: {
  brushXFilter: { selection: ['2024-01', '2024-03'] }
}

// ✅ 正确:使用比例值
interaction: {
  brushXFilter: { selection: [0.2, 0.6] }
}
```
references/interactions/g2-interaction-brushx-highlight.md
---
id: "g2-interaction-brushx-highlight"
title: "G2 BrushXHighlight Interaction"
description: |
  X 轴方向刷选高亮交互。用户可以通过拖拽选择 X 轴范围,
  高亮显示该范围内的数据元素。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "刷选"
  - "高亮"
  - "brush"
  - "X轴"
  - "数据探索"

related:
  - "g2-interaction-brush"
  - "g2-interaction-brushy-highlight"
  - "g2-interaction-brushx-filter"

use_cases:
  - "时间范围高亮"
  - "X 轴区间选择高亮"
  - "数据对比分析"

anti_patterns:
  - "需要过滤数据时改用 BrushXFilter"
  - "需要 Y 轴方向选择时改用 BrushYHighlight"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction"
---

## 核心概念

BrushXHighlight 交互允许用户在 X 轴方向拖拽选择一个区间,选区内的数据元素会被高亮显示,其他元素变暗。

**特点:**
- 只能在 X 轴方向选择
- 高亮而非过滤数据
- 适合数据探索和对比分析

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'interval',
  data: [
    { category: 'A', value: 100 },
    { category: 'B', value: 150 },
    { category: 'C', value: 80 },
    { category: 'D', value: 200 },
    { category: 'E', value: 120 },
  ],
  encode: {
    x: 'category',
    y: 'value',
  },
  interaction: {
    brushXHighlight: true,
  },
});

chart.render();
```

## 常用变体

### 自定义刷选样式

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value' },
  interaction: {
    brushXHighlight: {
      brushStyle: {
        fill: '#1890ff',
        fillOpacity: 0.3,
      },
    },
  },
});
```

### 自定义高亮状态

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value' },
  interaction: {
    brushXHighlight: {
      selectedHandles: ['handle-e', 'handle-w'],  // 显示的拖拽手柄
    },
  },
  state: {
    active: {
      fill: '#1890ff',
      stroke: '#0050b3',
      lineWidth: 2,
    },
    inactive: {
      fillOpacity: 0.3,
    },
  },
});
```

## 完整类型参考

```typescript
interface BrushXHighlightInteraction {
  brushXHighlight: boolean | {
    brushStyle?: {
      fill?: string;
      fillOpacity?: number;
      stroke?: string;
    };
    selectedHandles?: string[];  // ['handle-e', 'handle-w']
    // 其他配置继承自 BrushHighlight
  };
}
```

## 与 BrushHighlight/BrushYHighlight 的对比

| Interaction | 选择方向 | 常用场景 |
|-------------|---------|---------|
| brushHighlight | 任意方向 | 通用高亮 |
| brushXHighlight | 仅 X 方向 | 分类/时间范围高亮 |
| brushYHighlight | 仅 Y 方向 | 数值范围高亮 |

## 与 BrushXFilter 的区别

| 特性 | BrushXHighlight | BrushXFilter |
|------|-----------------|--------------|
| 数据处理 | 高亮显示 | 过滤隐藏 |
| 非选区数据 | 变暗但可见 | 完全隐藏 |
| 适用场景 | 数据探索、对比 | 数据筛选、缩放 |

## 常见错误与修正

### 错误 1:与 Filter 交互混淆

```javascript
// ❌ 错误:想要过滤数据却用了 highlight
interaction: { brushXHighlight: true }

// ✅ 正确:根据需求选择
// 需要高亮:brushXHighlight
// 需要过滤:brushXFilter
```

### 错误 2:未配置 state 样式

```javascript
// ⚠️ 注意:默认高亮效果可能不明显
// 建议配置 state 以获得更好的视觉效果
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value' },
  interaction: { brushXHighlight: true },
  state: {
    active: { fill: '#1890ff' },
    inactive: { fillOpacity: 0.2 },
  },
});
```
references/interactions/g2-interaction-brushy-filter.md
---
id: "g2-interaction-brushy-filter"
title: "G2 BrushYFilter Interaction"
description: |
  Y 轴方向刷选过滤交互。用户可以通过拖拽选择 Y 轴范围,
  过滤显示该范围内的数据。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "刷选"
  - "过滤"
  - "brush"
  - "Y轴"
  - "数据筛选"

related:
  - "g2-interaction-brush-filter"
  - "g2-interaction-brushx-filter"
  - "g2-interaction-brushy-highlight"

use_cases:
  - "数值范围筛选"
  - "Y 轴区间选择"
  - "过滤异常值"

anti_patterns:
  - "需要 X 轴方向筛选时改用 BrushXFilter"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction"
---

## 核心概念

BrushYFilter 交互允许用户在 Y 轴方向拖拽选择一个区间,图表会自动过滤并只显示该区间内的数据。

**特点:**
- 只能在 Y 轴方向选择
- 选择后自动过滤数据
- 适合数值范围筛选

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'point',
  data: [
    { x: 10, y: 100 },
    { x: 20, y: 150 },
    { x: 30, y: 80 },
    { x: 40, y: 200 },
    { x: 50, y: 120 },
  ],
  encode: {
    x: 'x',
    y: 'y',
  },
  interaction: {
    brushYFilter: true,
  },
});

chart.render();
```

## 常用变体

### 自定义样式

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'x', y: 'y' },
  interaction: {
    brushYFilter: {
      brushStyle: {
        fill: '#52c41a',
        fillOpacity: 0.2,
        stroke: '#52c41a',
      },
    },
  },
});
```

### 设置初始选区

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'x', y: 'y' },
  interaction: {
    brushYFilter: {
      selection: [0.3, 0.7],  // 初始选区比例 [start, end]
    },
  },
});
```

## 完整类型参考

```typescript
interface BrushYFilterInteraction {
  brushYFilter: boolean | {
    brushStyle?: {
      fill?: string;
      fillOpacity?: number;
      stroke?: string;
      lineWidth?: number;
    };
    selection?: [number, number];  // [startRatio, endRatio]
    // 其他配置继承自 BrushFilter
  };
}
```

## 与 BrushFilter/BrushXFilter 的对比

| Interaction | 选择方向 | 常用场景 |
|-------------|---------|---------|
| brushFilter | 任意方向 | 通用筛选 |
| brushXFilter | 仅 X 方向 | 时间范围筛选 |
| brushYFilter | 仅 Y 方向 | 数值范围筛选 |

## 常见错误与修正

### 错误 1:与其他 brush 交互冲突

```javascript
// ❌ 错误:同时启用多个 brush 交互可能冲突
interaction: {
  brushXFilter: true,
  brushYFilter: true,
}

// ✅ 正确:根据需求选择一个
interaction: {
  brushYFilter: true,
}
```

### 错误 2:selection 参数格式错误

```javascript
// ❌ 错误:selection 应该是比例值 [0-1]
interaction: {
  brushYFilter: { selection: [100, 200] }
}

// ✅ 正确:使用比例值
interaction: {
  brushYFilter: { selection: [0.2, 0.6] }
}
```
references/interactions/g2-interaction-brushy-highlight.md
---
id: "g2-interaction-brushy-highlight"
title: "G2 BrushYHighlight Interaction"
description: |
  Y 轴方向刷选高亮交互。用户可以通过拖拽选择 Y 轴范围,
  高亮显示该范围内的数据元素。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "刷选"
  - "高亮"
  - "brush"
  - "Y轴"
  - "数据探索"

related:
  - "g2-interaction-brush"
  - "g2-interaction-brushx-highlight"
  - "g2-interaction-brushy-filter"

use_cases:
  - "数值范围高亮"
  - "Y 轴区间选择高亮"
  - "异常值识别"

anti_patterns:
  - "需要过滤数据时改用 BrushYFilter"
  - "需要 X 轴方向选择时改用 BrushXHighlight"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction"
---

## 核心概念

BrushYHighlight 交互允许用户在 Y 轴方向拖拽选择一个区间,选区内的数据元素会被高亮显示,其他元素变暗。

**特点:**
- 只能在 Y 轴方向选择
- 高亮而非过滤数据
- 适合数值范围的数据探索

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'point',
  data: [
    { x: 10, y: 100 },
    { x: 20, y: 150 },
    { x: 30, y: 80 },
    { x: 40, y: 200 },
    { x: 50, y: 120 },
  ],
  encode: {
    x: 'x',
    y: 'y',
  },
  interaction: {
    brushYHighlight: true,
  },
});

chart.render();
```

## 常用变体

### 自定义刷选样式

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'x', y: 'y' },
  interaction: {
    brushYHighlight: {
      brushStyle: {
        fill: '#52c41a',
        fillOpacity: 0.3,
      },
    },
  },
});
```

### 自定义高亮状态

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'x', y: 'y' },
  interaction: {
    brushYHighlight: {
      selectedHandles: ['handle-n', 'handle-s'],  // 显示的拖拽手柄
    },
  },
  state: {
    active: {
      fill: '#52c41a',
      r: 8,
    },
    inactive: {
      fillOpacity: 0.3,
    },
  },
});
```

## 完整类型参考

```typescript
interface BrushYHighlightInteraction {
  brushYHighlight: boolean | {
    brushStyle?: {
      fill?: string;
      fillOpacity?: number;
      stroke?: string;
    };
    selectedHandles?: string[];  // ['handle-n', 'handle-s']
    // 其他配置继承自 BrushHighlight
  };
}
```

## 与 BrushHighlight/BrushXHighlight 的对比

| Interaction | 选择方向 | 常用场景 |
|-------------|---------|---------|
| brushHighlight | 任意方向 | 通用高亮 |
| brushXHighlight | 仅 X 方向 | 分类/时间范围高亮 |
| brushYHighlight | 仅 Y 方向 | 数值范围高亮 |

## 与 BrushYFilter 的区别

| 特性 | BrushYHighlight | BrushYFilter |
|------|-----------------|--------------|
| 数据处理 | 高亮显示 | 过滤隐藏 |
| 非选区数据 | 变暗但可见 | 完全隐藏 |
| 适用场景 | 数据探索、对比 | 数据筛选、缩放 |

## 常见错误与修正

### 错误 1:与 Filter 交互混淆

```javascript
// ❌ 错误:想要过滤数据却用了 highlight
interaction: { brushYHighlight: true }

// ✅ 正确:根据需求选择
// 需要高亮:brushYHighlight
// 需要过滤:brushYFilter
```

### 错误 2:未配置 state 样式

```javascript
// ⚠️ 注意:默认高亮效果可能不明显
// 建议配置 state 以获得更好的视觉效果
chart.options({
  type: 'point',
  data,
  encode: { x: 'x', y: 'y' },
  interaction: { brushYHighlight: true },
  state: {
    active: { fill: '#52c41a', r: 8 },
    inactive: { fillOpacity: 0.2 },
  },
});
```
references/interactions/g2-interaction-chart-index.md
---
id: "g2-interaction-chart-index"
title: "G2 ChartIndex 联动游标线"
description: |
  chartIndex 在图表上渲染一条随鼠标移动的垂直游标线(参考线),
  并可联动多个图表的游标,用于多图表横向对比同一时间点的数据。
  适合时序数据多图联动、Dashboard 中多指标同步查看场景。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "chartIndex"
  - "游标线"
  - "联动"
  - "参考线"
  - "多图联动"
  - "interaction"
  - "crosshair"

related:
  - "g2-interaction-tooltip"
  - "g2-mark-linex-liney"
  - "g2-recipe-dashboard"

use_cases:
  - "多折线图联动查看同一时间点各指标值"
  - "时序数据 Dashboard 中的十字游标"
  - "对比两个时间序列的同期数据"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction/chart-index"
---

## 核心概念

`chartIndex` 在绘图区域渲染一条垂直参考线,随鼠标 X 轴位置移动。
与 `shared: true` 的 Tooltip 配合,可实现多系列同时刻数据的联动高亮。

## 单图游标线

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 800, height: 400 });

chart.options({
  type: 'line',
  data: timeSeriesData,
  encode: { x: 'date', y: 'value', color: 'type' },
  interaction: {
    chartIndex: true,          // 启用游标线
    tooltip: { shared: true }, // 配合共享 Tooltip
  },
});

chart.render();
```

## 配置项

```javascript
chart.options({
  interaction: {
    chartIndex: {
      // 游标线样式
      ruleStroke: '#aaa',          // 游标线颜色,默认 '#aaa'
      ruleLineWidth: 1,            // 游标线宽度,默认 1
      ruleLineDash: [4, 4],        // 游标线虚线样式
      // 标签配置
      labelDy: -8,                 // 标签垂直偏移
      labelBackground: true,       // 是否显示标签背景
      labelBackgroundFill: '#fff', // 标签背景色
      // 性能控制
      wait: 50,                    // 防抖时间(毫秒),默认 50
      leading: true,               // 防抖前缘触发
      trailing: false,             // 防抖后缘触发
    },
  },
});
```

## 多图联动(相同容器父元素)

```javascript
// 通过共享 emit 事件实现多图游标联动
// 两个图表使用同一 emitter(需要手动实现或使用 G2 的 on/emit API)
const chart1 = new Chart({ container: 'container1', width: 800, height: 200 });
const chart2 = new Chart({ container: 'container2', width: 800, height: 200 });

[chart1, chart2].forEach((chart) => {
  chart.options({
    type: 'line',
    data: timeSeriesData,
    encode: { x: 'date', y: 'value' },
    interaction: {
      chartIndex: true,
    },
  });
  chart.render();
});
```

## 常见错误与修正

### 错误:游标线出现但 Tooltip 不显示同时刻数据
```javascript
// ❌ 多系列图表,Tooltip 只显示当前鼠标最近的元素
chart.options({
  interaction: {
    chartIndex: true,
    // 缺少 tooltip shared 配置
  },
});

// ✅ 开启 shared Tooltip,所有系列同时刻数据一起显示
chart.options({
  interaction: {
    chartIndex: true,
    tooltip: { shared: true },   // 必须配合
  },
});
```
references/interactions/g2-interaction-drilldown.md
---
id: "g2-interaction-drilldown"
title: "G2 下钻交互(drillDown)"
description: |
  drillDown 交互用于层次数据(partition / 旭日图)的点击下钻,
  点击某个节点后只展示该节点的子树,同时在顶部显示面包屑导航。
  点击面包屑可以逐层向上回溯。仅适用于 partition mark。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "drillDown"
  - "下钻"
  - "层次数据"
  - "旭日图"
  - "partition"
  - "面包屑"
  - "interaction"

related:
  - "g2-mark-treemap"
  - "g2-interaction-element-select"

use_cases:
  - "旭日图/矩形分区图的层次数据下钻探索"
  - "组织架构图的逐层查看"
  - "文件目录树的交互式浏览"

difficulty: "advanced"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction/drill-down"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const data = {
  name: '公司',
  children: [
    {
      name: '研发部',
      children: [
        { name: '前端组', value: 12 },
        { name: '后端组', value: 18 },
        { name: '算法组', value: 8 },
      ],
    },
    {
      name: '市场部',
      children: [
        { name: '品牌组', value: 6 },
        { name: '运营组', value: 10 },
      ],
    },
    {
      name: '设计部',
      children: [
        { name: 'UX组', value: 7 },
        { name: '视觉组', value: 5 },
      ],
    },
  ],
};

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'sunburst',   // 旭日图(partition 的极坐标形式)
  data: { value: data },
  encode: { value: 'value', color: 'name' },
  interaction: {
    drillDown: true,   // 启用下钻交互
  },
});

chart.render();
```

## 自定义面包屑样式

```javascript
chart.options({
  type: 'sunburst',
  data: { value: data },
  encode: { value: 'value', color: 'name' },
  interaction: {
    drillDown: {
      breadCrumb: {
        rootText: '全公司',         // 根节点面包屑文字,默认 'root'
        style: {
          fill: 'rgba(0,0,0,0.65)',
          fontSize: 13,
        },
        active: {
          fill: '#1890ff',          // 悬停时面包屑文字颜色
        },
        y: 8,                       // 面包屑 Y 轴偏移
      },
    },
  },
});
```

## 常见错误与修正

### 错误 1:drillDown 用于 treemap 而非 partition/sunburst
```javascript
// ❌ 错误:drillDown 只适用于 partition 类型(包括旭日图)
// treemap 有专用的 treemapDrillDown 交互
chart.options({
  type: 'treemap',
  interaction: { drillDown: true },  // ❌ 应该用 treemapDrillDown
});

// ✅ treemap 用 treemapDrillDown
chart.options({
  type: 'treemap',
  interaction: { treemapDrillDown: true },  // ✅
});

// ✅ sunburst/partition 用 drillDown
chart.options({
  type: 'sunburst',
  interaction: { drillDown: true },  // ✅
});
```

### 错误 2:数据不是层次结构——下钻无法展示子节点
```javascript
// ❌ 扁平数据没有 children,下钻后没有内容
chart.options({
  data: [{ name: 'A', value: 10 }, { name: 'B', value: 20 }],  // ❌ 扁平
  interaction: { drillDown: true },
});

// ✅ 必须使用带 children 的层次数据
chart.options({
  data: {
    value: { name: 'root', children: [...] },  // ✅ 树形
  },
  interaction: { drillDown: true },
});
```
references/interactions/g2-interaction-element-highlight-by.md
---
id: "g2-interaction-element-highlight-by"
title: "G2 按颜色/X轴联动高亮(elementHighlightByColor / elementHighlightByX)"
description: |
  elementHighlightByColor:鼠标悬停时,高亮所有与该元素颜色通道值相同的元素。
  elementHighlightByX:鼠标悬停时,高亮所有与该元素 x 轴值相同的元素。
  两者都是 elementHighlight 的变体,区别在于高亮的分组依据不同,
  常用于多系列图表中同类别或同时间点的联动高亮。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "elementHighlightByColor"
  - "elementHighlightByX"
  - "联动高亮"
  - "分组高亮"
  - "interaction"

related:
  - "g2-interaction-element-highlight"
  - "g2-interaction-element-select"

use_cases:
  - "多系列图表中悬停某柱高亮同颜色(同类别)的所有柱"
  - "悬停某个时间点高亮同一时间点的所有系列"
  - "热力图按行/列联动高亮"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction/element-highlight"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const data = [
  { month: 'Jan', city: '北京', value: 83 },
  { month: 'Feb', city: '北京', value: 60 },
  { month: 'Jan', city: '上海', value: 71 },
  { month: 'Feb', city: '上海', value: 55 },
  { month: 'Jan', city: '广州', value: 95 },
  { month: 'Feb', city: '广州', value: 88 },
];

const chart = new Chart({ container: 'container', width: 640, height: 400 });

// ── 按颜色(城市)联动高亮 ──
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'city' },
  transform: [{ type: 'dodgeX' }],
  interaction: {
    elementHighlightByColor: true,   // 悬停某柱 → 高亮同城市所有柱
  },
});

chart.render();
```

## elementHighlightByX(同 X 轴值联动高亮)

```javascript
// 悬停某个月份的任意柱 → 高亮同月份所有城市的柱
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'city' },
  transform: [{ type: 'dodgeX' }],
  interaction: {
    elementHighlightByX: true,   // 高亮同一 x 值的所有元素
  },
});
```

## 三种高亮模式对比

```javascript
// 1. elementHighlight(默认):只高亮鼠标悬停的单个元素
interaction: { elementHighlight: true }

// 2. elementHighlightByColor:高亮同颜色分组的所有元素(同类别)
interaction: { elementHighlightByColor: true }

// 3. elementHighlightByX:高亮同 x 值的所有元素(同时间点/类别)
interaction: { elementHighlightByX: true }
```

## 自定义高亮样式

```javascript
chart.options({
  interaction: {
    elementHighlightByColor: {
      background: true,         // 高亮时显示背景(false 则只改透明度)
      link: false,              // 是否显示连线(仅对折线图等有效)
      offset: 0,                // 高亮时的偏移量
    },
  },
});
```

## 常见错误与修正

### 错误:在没有 color 通道的图表上用 elementHighlightByColor——所有元素都被高亮
```javascript
// ❌ 没有 color 通道时,所有元素视为同一颜色组,hover 时全部高亮
chart.options({
  type: 'interval',
  encode: { x: 'month', y: 'value' },  // ❌ 没有 color
  interaction: { elementHighlightByColor: true },
});

// ✅ 需要有 color 通道才能按颜色分组高亮
chart.options({
  encode: { x: 'month', y: 'value', color: 'city' },  // ✅ 有 color 分组
  interaction: { elementHighlightByColor: true },
});
```
references/interactions/g2-interaction-element-highlight.md
---
id: "g2-interaction-element-highlight"
title: "G2 元素高亮交互(elementHighlight)"
description: |
  elementHighlight 是 G2 v5 中最常用的交互之一,鼠标悬停时高亮当前元素、
  同时可选择高亮同系列元素或联动其他视图。支持柱状图、折线图、散点图等所有 Mark 类型。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "elementHighlight"
  - "高亮"
  - "interaction"
  - "hover"
  - "交互"
  - "spec"

related:
  - "g2-interaction-brush"
  - "g2-mark-interval-basic"
  - "g2-mark-line-basic"

use_cases:
  - "柱状图悬停高亮当前柱子"
  - "折线图悬停高亮当前系列"
  - "散点图悬停高亮同类数据点"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction/element-highlight"
---

## 基本用法(柱状图高亮)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'interval',
  data: [
    { genre: 'Sports',   sold: 275 },
    { genre: 'Strategy', sold: 115 },
    { genre: 'Action',   sold: 120 },
    { genre: 'Shooter',  sold: 350 },
    { genre: 'Other',    sold: 150 },
  ],
  encode: { x: 'genre', y: 'sold' },
  interaction: { elementHighlight: true },   // 悬停高亮当前柱子
});

chart.render();
```

## 高亮背景色配置

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'genre', y: 'sold', color: 'genre' },
  interaction: {
    elementHighlight: {
      background: true,              // 是否显示高亮背景
      backgroundFill: '#f0f0f0',    // 背景填充色
    },
  },
});
```

## 折线图:高亮当前系列

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'month', y: 'value', color: 'series' },
  interaction: {
    elementHighlight: true,        // 悬停高亮当前折线
  },
});
```

## elementHighlightByColor:高亮同色系列

```javascript
// 悬停时高亮所有相同颜色(系列)的元素
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'dodgeX' }],
  interaction: {
    elementHighlightByColor: true,   // 高亮同系列所有柱子
  },
});
```

## elementHighlightByX:高亮同 x 位置的元素

```javascript
// 悬停时高亮同一 x 值的所有元素(适合分组柱状图)
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  interaction: {
    elementHighlightByX: true,    // 高亮同组(同 x 位置)的所有元素
  },
});
```

## 同时启用 tooltip + 高亮

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'revenue', color: 'product' },
  transform: [{ type: 'dodgeX' }],
  interaction: {
    elementHighlight: true,    // 元素高亮
    tooltip: true,             // Tooltip 提示
  },
  tooltip: {
    title: 'month',
    items: [
      { field: 'revenue', valueFormatter: (v) => `$${v}万` },
    ],
  },
});
```

## 监听高亮事件

```javascript
chart.on('element:highlight', (event) => {
  const datum = event.data?.data;
  console.log('高亮元素数据:', datum);
});

chart.on('element:unhighlight', () => {
  console.log('取消高亮');
});
```

## 常见错误与修正

### 错误:interaction 写成对象
```javascript
// ❌ 错误:interaction 必须是数组(旧版写法)
chart.options({
  interaction: { type: 'elementHighlight' },
});

// ✅ 正确(新版支持对象形式)
chart.options({
  interaction: { elementHighlight: true },
});
```

### 错误:混淆 elementHighlight 与 elementHighlightByColor
```javascript
// ❌ 同时使用会导致重复响应
chart.options({
  interaction: {
    elementHighlight: true,
    elementHighlightByColor: true,
  },
});

// ✅ 根据需求选择一种
// - elementHighlight: 只高亮鼠标悬停的单个元素
// - elementHighlightByColor: 高亮同颜色(系列)的所有元素
// - elementHighlightByX: 高亮同 x 位置的所有元素
```

### 错误:在 view 的 children 中嵌套 view 导致白屏
```javascript
// ❌ 错误:在 children 中嵌套 view 会导致渲染失败
chart.options({
  type: 'view',
  children: [
    {
      type: 'view', // 不允许嵌套 view
      children: [...]
    }
  ]
});

// ✅ 正确:使用顶层容器或单一 view 结构
chart.options({
  type: 'view',
  children: [
    { type: 'interval', ... },
    { type: 'image', ... }
  ]
});
```

### 错误:未正确设置 image 标记导致无法显示
```javascript
// ❌ 错误:缺少必要的 encode 和 style 配置
{
  type: 'image',
  data: [{ url: '...' }],
  encode: { x: () => 0, y: () => 0 } // 不适用于居中显示
}

// ✅ 正确:使用 style 设置固定位置和尺寸
{
  type: 'image',
  style: {
    x: '50%', // 居中
    y: '50%',
    width: 80,
    height: 80
  },
  encode: {
    src: 'url'
  }
}
```

</skill>
references/interactions/g2-interaction-element-hover-scale.md
---
id: "g2-interaction-element-hover-scale"
title: "G2 悬停缩放交互(elementHoverScale)"
description: |
  elementHoverScale 在鼠标悬停时对元素进行缩放放大,提供立体感和视觉反馈。
  适合饼图、点图等独立元素的交互增强,比普通高亮更有视觉冲击力。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "elementHoverScale"
  - "悬停缩放"
  - "hover"
  - "缩放"
  - "interaction"

related:
  - "g2-interaction-element-highlight"
  - "g2-mark-arc-pie"

use_cases:
  - "饼图/环形图悬停时扇形外弹放大"
  - "散点图悬停时数据点放大"
  - "仪表盘卡片悬停放大效果"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction/element-hover-scale"
---

## 最小可运行示例(饼图悬停放大)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 480, height: 480 });

chart.options({
  type: 'interval',
  data: [
    { type: '电子', value: 40 },
    { type: '服装', value: 25 },
    { type: '食品', value: 20 },
    { type: '其他', value: 15 },
  ],
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: { type: 'theta', outerRadius: 0.85 },
  interaction: {
    elementHoverScale: true,   // 悬停时扇形外弹放大
  },
});

chart.render();
```

## 配置缩放比例

```javascript
chart.options({
  interaction: {
    elementHoverScale: {
      scale: 1.1,    // 缩放倍数,默认约 1.1(放大 10%)
    },
  },
});
```

## 与其他交互组合

```javascript
// 饼图:悬停放大 + tooltip
chart.options({
  type: 'interval',
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: { type: 'theta' },
  interaction: {
    elementHoverScale: true,   // 放大
    tooltip: true,             // 同时显示 tooltip
  },
});
```

## 常见错误与修正

### 错误:与 elementHighlight 同时使用——视觉效果冲突
```javascript
// ❌ 两者同时启用,被悬停元素既放大又改透明度,效果混乱
chart.options({
  interaction: {
    elementHoverScale: true,
    elementHighlight: true,   // ❌ 与 hoverScale 冲突
  },
});

// ✅ 只选一种悬停交互
chart.options({
  interaction: {
    elementHoverScale: true,  // ✅ 缩放效果
    // 或
    // elementHighlight: true,  // ✅ 暗淡效果
  },
});
```
references/interactions/g2-interaction-element-point-move.md
---
id: "g2-interaction-element-point-move"
title: "G2 ElementPointMove 数据点拖拽编辑"
description: |
  elementPointMove 是 G2 v5 的交互,允许用户通过鼠标拖拽图表中的数据点来修改数值。
  支持折线图、柱状图、饼图、面积图等,拖拽后触发 'element-point:moved' 事件回调新数据。
  适用于可视化数据编辑、交互式预算调整、预测值手动修正等场景。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "elementPointMove"
  - "数据编辑"
  - "拖拽"
  - "可交互"
  - "数据修改"
  - "interaction"

related:
  - "g2-mark-line-basic"
  - "g2-mark-interval-basic"
  - "g2-interaction-element-select"

use_cases:
  - "预算分配可视化编辑(拖拽柱体调整数值)"
  - "折线图手动调整预测趋势"
  - "饼图交互式调整各类别占比"

difficulty: "advanced"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction/element-point-move"
---

## 核心概念

`elementPointMove` 在图表元素上渲染可拖拽的控制点,鼠标按下拖拽时实时更新数据并重绘图表。
拖拽结束后触发 `element-point:moved` 事件,回调参数包含修改后的数据。

支持的 Mark 类型:
- `line`(折线图):每个数据点均可拖拽
- `area`(面积图):每个顶点可拖拽
- `interval`(柱状图/条形图/饼图):柱顶点可拖拽

## 折线图数据点拖拽

```javascript
import { Chart } from '@antv/g2';

const data = [
  { month: 'Jan', value: 83 },
  { month: 'Feb', value: 60 },
  { month: 'Mar', value: 95 },
  { month: 'Apr', value: 72 },
  { month: 'May', value: 110 },
];

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'line',
  data,
  encode: { x: 'month', y: 'value' },
  interaction: {
    elementPointMove: true,   // 启用数据点拖拽
  },
});

// 监听数据变更事件
chart.on('element-point:moved', (event) => {
  const { changeData, data } = event.data;
  console.log('修改后的单条数据:', changeData);
  console.log('完整新数据:', data);
});

chart.render();
```

## 柱状图数据点拖拽

```javascript
chart.options({
  type: 'interval',
  data: budgetData,
  encode: { x: 'department', y: 'budget', color: 'department' },
  interaction: {
    elementPointMove: {
      precision: 0,   // 拖拽提示精度(小数位数),默认 2
    },
  },
});
```

## 配置项

```javascript
chart.options({
  interaction: {
    elementPointMove: {
      precision: 2,             // 实时提示标签的小数位数,默认 2
      selection: [],            // 初始选中的数据点索引 [elementIndex, pointIndex]
      // 控制点样式
      pointR: 6,                // 控制点半径,默认 6
      pointStroke: '#888',      // 控制点描边颜色
      pointActiveStroke: '#f5f5f5',  // 激活时描边颜色
      // 辅助线样式
      pathStroke: '#888',
      pathLineDash: [3, 4],
      // 提示标签样式
      labelFontSize: 12,
      labelFill: '#888',
    },
  },
});
```

## 监听事件

```javascript
// 拖拽结束事件(数据已更新)
chart.on('element-point:moved', ({  { changeData, data } }) => {
  // changeData: 被修改的单条记录 { month: 'Feb', value: 75 }
  // data: 修改后的完整数据数组
  syncToServer(changeData);
});

// 选中控制点事件
chart.on('element-point:select', ({  { selection } }) => {
  // selection: [elementIndex, pointIndex]
  console.log('选中点索引:', selection);
});
```

## 常见错误与修正

### 错误:在 scatter 点图上使用(不支持)
```javascript
// ❌ point mark 不支持 elementPointMove
chart.options({
  type: 'point',
  interaction: { elementPointMove: true },  // 无效
});

// ✅ 支持的类型:line、area、interval
chart.options({
  type: 'line',
  interaction: { elementPointMove: true },  // ✅
});
```
references/interactions/g2-interaction-element-select-by.md
---
id: "g2-interaction-element-select-by"
title: "G2 分组选择(elementSelectByColor / elementSelectByX)"
description: |
  elementSelectByColor:点击某个元素时,选中所有相同颜色(color encode 值)的元素,
  常用于多系列折线图中点击一条线选中整条。
  elementSelectByX:点击某个元素时,选中所有相同 X 值的元素,
  常用于分组柱状图中选中同一 X 分类下所有柱。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "elementSelectByColor"
  - "elementSelectByX"
  - "分组选择"
  - "批量选中"
  - "interaction"

related:
  - "g2-interaction-element-highlight-by"
  - "g2-interaction-element-select"
  - "g2-interaction-legend-filter"

use_cases:
  - "多系列折线图:点击一个数据点选中整条折线"
  - "分组柱状图:点击某柱选中同 X 分类的所有柱"
  - "散点图按颜色分组批量选中"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction/element-select"
---

## elementSelectByColor(按颜色分组选中)

```javascript
import { Chart } from '@antv/g2';

const data = [
  { month: 'Jan', city: '北京', value: 5 },
  { month: 'Feb', city: '北京', value: 8 },
  { month: 'Jan', city: '上海', value: 12 },
  { month: 'Feb', city: '上海', value: 15 },
];

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'line',
  data,
  encode: { x: 'month', y: 'value', color: 'city' },
  interaction: {
    elementSelectByColor: true,  // 点击任意数据点,选中同色的所有点
  },
});

chart.render();
```

## elementSelectByX(按 X 分组选中)

```javascript
// 分组柱状图:点击某柱,选中该 X 值下所有分组柱
chart.options({
  type: 'interval',
  data: groupedData,
  encode: { x: 'month', y: 'value', color: 'city' },
  transform: [{ type: 'dodgeX' }],
  interaction: {
    elementSelectByX: true,  // 点击某柱,选中同月份所有分组柱
  },
});
```

## 多重交互组合

```javascript
// 结合高亮和选中:悬停高亮同色系列,点击选中
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value', color: 'series' },
  interaction: {
    elementHighlightByColor: true,  // 悬停:高亮同色系列
    elementSelectByColor: true,     // 点击:选中同色系列
  },
});
```

## 获取选中事件

```javascript
chart.on('element:select', (event) => {
  const { data } = event.detail;
  console.log('选中的数据:', data.datum);
});

chart.on('element:unselect', (event) => {
  console.log('取消选中');
});
```

## 常见错误与修正

### 错误:elementSelectByColor 在无 color encode 时无效
```javascript
// ❌ 没有 color encode,无法按颜色分组选中
chart.options({
  type: 'line',
  encode: { x: 'month', y: 'value' },   // ❌ 没有 color
  interaction: { elementSelectByColor: true },  // 无效
});

// ✅ 需要 color encode
chart.options({
  type: 'line',
  encode: { x: 'month', y: 'value', color: 'city' },  // ✅
  interaction: { elementSelectByColor: true },
});
```

### 错误:elementSelectByColor 与 elementSelect 混淆
```javascript
// elementSelect:只选中点击的单个元素
chart.options({ interaction: { elementSelect: true } });

// elementSelectByColor:选中所有相同 color 值的元素(批量选中)
chart.options({ interaction: { elementSelectByColor: true } });
```
references/interactions/g2-interaction-element-select.md
---
id: "g2-interaction-element-select"
title: "G2 元素选中交互(elementSelect)"
description: |
  G2 v5 元素选中交互通过 interaction: [{ type: 'elementSelect' }] 启用,
  点击图形元素切换 selected 状态,支持 selected/active 状态样式自定义,
  可配合 elementSelectByX/elementSelectByColor 实现批量选中。
library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "选中"
  - "elementSelect"
  - "交互"
  - "状态"
  - "click"
  - "spec"

related:
  - "g2-interaction-element-highlight"
  - "g2-mark-interval-basic"
  - "g2-interaction-tooltip"

use_cases:
  - "点击柱子高亮选中,其他柱变灰"
  - "点击图例项过滤图表"
  - "联动外部数据面板显示选中详情"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction"
---

## 基本用法(柱状图点击选中)

点击柱子切换 selected 状态,再次点击取消选中:

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'interval',
  data: [
    { genre: 'Sports',   sold: 275 },
    { genre: 'Strategy', sold: 115 },
    { genre: 'Action',   sold: 120 },
    { genre: 'Shooter',  sold: 350 },
    { genre: 'Other',    sold: 150 },
  ],
  encode: { x: 'genre', y: 'sold', color: 'genre' },
  interaction: [
    { type: 'elementSelect' },   // 点击元素切换 selected 状态
  ],
});

chart.render();
```

## elementSelectByX(按 x 值批量选中)

适合分组柱状图或堆叠图,点击任意一组元素时选中同一 x 位置的所有元素:

```javascript
chart.options({
  type: 'interval',
  data: [
    { month: 'Jan', type: 'A', value: 120 },
    { month: 'Jan', type: 'B', value: 80 },
    { month: 'Feb', type: 'A', value: 160 },
    { month: 'Feb', type: 'B', value: 95 },
    { month: 'Mar', type: 'A', value: 140 },
    { month: 'Mar', type: 'B', value: 110 },
  ],
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'dodgeX' }],
  interaction: [
    { type: 'elementSelectByX' },   // 点击任意柱子,选中同 x 位置的所有柱子
  ],
});
```

## 自定义选中状态样式

通过 `state.selected` 指定选中时的视觉样式,未选中的元素样式会相应降低:

```javascript
chart.options({
  type: 'interval',
  data: [
    { genre: 'Sports',   sold: 275 },
    { genre: 'Strategy', sold: 115 },
    { genre: 'Action',   sold: 120 },
    { genre: 'Shooter',  sold: 350 },
    { genre: 'Other',    sold: 150 },
  ],
  encode: { x: 'genre', y: 'sold', color: 'genre' },
  state: {
    selected: {
      fill: '#1890ff',          // 选中时填充色
      fillOpacity: 1,           // 选中时不透明度
      stroke: '#003a8c',        // 选中时描边色
      lineWidth: 2,             // 选中时描边宽度
    },
    unselected: {
      fillOpacity: 0.3,         // 未选中元素半透明
    },
  },
  interaction: [
    { type: 'elementSelect' },
  ],
});
```

## 组合使用 highlight + select

鼠标悬停触发高亮,点击触发选中,两者可同时启用:

```javascript
chart.options({
  type: 'interval',
  data: [
    { genre: 'Sports',   sold: 275 },
    { genre: 'Strategy', sold: 115 },
    { genre: 'Action',   sold: 120 },
    { genre: 'Shooter',  sold: 350 },
    { genre: 'Other',    sold: 150 },
  ],
  encode: { x: 'genre', y: 'sold', color: 'genre' },
  state: {
    active: {
      fill: '#69c0ff',        // 悬停高亮色(active 状态)
      fillOpacity: 0.9,
    },
    selected: {
      fill: '#1890ff',        // 点击选中色(selected 状态)
      fillOpacity: 1,
      stroke: '#003a8c',
      lineWidth: 2,
    },
    unselected: {
      fillOpacity: 0.3,
    },
  },
  interaction: [
    { type: 'elementHighlight' },   // 悬停高亮(active 状态)
    { type: 'elementSelect' },      // 点击选中(selected 状态)
    { type: 'tooltip' },
  ],
});
```

## 监听选中事件

```javascript
// 监听选中和取消选中事件
chart.on('element:select', (event) => {
  const datum = event.data?.data;
  console.log('选中元素数据:', datum);
  // 可在此处联动外部面板、更新状态等
});

chart.on('element:unselect', (event) => {
  console.log('取消选中');
});
```

## 常见错误与修正

### 错误:interaction 写成对象而非数组

```javascript
// ❌ 错误:interaction 必须是数组
chart.options({
  interaction: { type: 'elementSelect' },
});

// ✅ 正确
chart.options({
  interaction: [{ type: 'elementSelect' }],
});
```

### 错误:使用了不存在的交互名称

```javascript
// ❌ 错误:G2 中没有 'elementClick' 这个交互类型
chart.options({
  interaction: [{ type: 'elementClick' }],
});

// ✅ 正确的名称
chart.options({
  interaction: [{ type: 'elementSelect' }],         // 单个元素选中
  // 或
  // interaction: [{ type: 'elementSelectByX' }],   // 按 x 值批量选中
  // interaction: [{ type: 'elementSelectByColor' }], // 按颜色批量选中
});
```

### 错误:选中样式不生效(state 位置错误)

```javascript
// ❌ 错误:state 不能嵌套在 style 里
chart.options({
  style: {
    state: { selected: { fill: '#1890ff' } },
  },
});

// ✅ 正确:state 与 encode、style 并列,在 Mark 配置的顶层
chart.options({
  type: 'interval',
  data,
  encode: { x: 'genre', y: 'sold' },
  state: {
    selected: { fill: '#1890ff', fillOpacity: 1 },
  },
  interaction: [{ type: 'elementSelect' }],
});
```

### 错误:同时使用 elementSelect 和 elementSelectByX 导致冲突

```javascript
// ❌ 两者同时启用时行为不可预期,点击会触发双重选中逻辑
chart.options({
  interaction: [
    { type: 'elementSelect' },
    { type: 'elementSelectByX' },
  ],
});

// ✅ 根据需求选择一种
// - elementSelect: 只选中点击的单个元素
// - elementSelectByX: 选中同一 x 值的所有元素(适合分组/堆叠图)
// - elementSelectByColor: 选中同一颜色(系列)的所有元素
chart.options({
  interaction: [{ type: 'elementSelectByX' }],
});
```
references/interactions/g2-interaction-fisheye.md
---
id: "g2-interaction-fisheye"
title: "G2 鱼眼交互(fisheye interaction)"
description: |
  fisheye 交互让鱼眼效果的焦点跟随鼠标移动,实现动态的焦点+上下文放大。
  需要配合 fisheye 坐标系使用,或独立启用(会自动在 coordinate.transform 中添加 fisheye)。
  鼠标移出图表区域时自动恢复正常视图。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "fisheye"
  - "鱼眼"
  - "焦点上下文"
  - "focus context"
  - "interaction"

related:
  - "g2-coord-fisheye"
  - "g2-mark-point-scatter"

use_cases:
  - "密集散点图的动态局部放大"
  - "大量数据点的交互式细节查看"
  - "时间序列密集区域的探索"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction/fisheye"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const data = Array.from({ length: 300 }, (_, i) => ({
  x: Math.random() * 100,
  y: Math.random() * 100,
  group: i % 5,
}));

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'point',
  data,
  encode: { x: 'x', y: 'y', color: 'group', shape: 'point' },
  scale: { color: { type: 'ordinal' } },
  coordinate: { transform: [ { type: 'fisheye' } ] },  // 配合鱼眼坐标系
  interaction: {
    fisheye: true,   // 焦点跟随鼠标
  },
});

chart.render();
```

## 配置鱼眼强度

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'x', y: 'y', color: 'group' },
  coordinate: { transform: [ { type: 'fisheye' } ] },
  interaction: {
    fisheye: {
      wait: 30,       // 节流等待时间(毫秒),默认 30,值越小越灵敏
      leading: true,  // 节流 leading edge 执行,默认 undefined
      trailing: false, // 节流 trailing edge 执行,默认 false
    },
  },
});
```

## 仅 X 方向鱼眼(折线图密集区域探索)

```javascript
chart.options({
  type: 'line',
  data: denseTimeData,
  encode: { x: 'date', y: 'value', color: 'type' },
  coordinate: {
    transform: [
      {
        type: 'fisheye',
        distortionX: 4,   // X 方向放大强度
        distortionY: 0,   // Y 方向不变形
      }
    ]
  },
  interaction: { fisheye: true },
});
```

## 常见错误与修正

### 错误:只设置 interaction.fisheye 没有设置坐标系——鱼眼效果不生效
```javascript
// ⚠️  interaction.fisheye 会自动添加 fisheye coordinate.transform
// 但如果 coordinate 有其他设置,可能需要显式配置
chart.options({
  coordinate: { type: 'cartesian' },  // ⚠️  明确设置了笛卡尔坐标,fisheye 会追加变换
  interaction: { fisheye: true },     // 会自动在 coordinate.transform 中插入 fisheye
});

// ✅ 最简洁的写法:直接指定 fisheye 坐标系
chart.options({
  coordinate: { transform: [ { type: 'fisheye' } ] },
  interaction: { fisheye: true },
});
```
references/interactions/g2-interaction-legend-filter.md
---
id: "g2-interaction-legend-filter"
title: "G2 图例过滤交互(legendFilter)"
description: |
  legendFilter 让用户通过点击图例项来显示/隐藏对应的数据系列。
  在 G2 v5 中默认已启用,点击图例项即可切换对应系列的可见性。
  可通过配置关闭或自定义样式。legendHighlight 则是鼠标悬停时高亮对应系列。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "legendFilter"
  - "图例过滤"
  - "图例高亮"
  - "legendHighlight"
  - "交互"
  - "interaction"

related:
  - "g2-comp-legend-config"
  - "g2-interaction-element-highlight"

use_cases:
  - "多系列折线图中按需显示/隐藏特定系列"
  - "堆叠图中临时隐藏某个类别"
  - "大量系列时的选择性查看"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction/legend-filter"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const data = [
  { month: 'Jan', city: '北京', temp: -3 },
  { month: 'Feb', city: '北京', temp: 0 },
  { month: 'Jan', city: '上海', temp: 5 },
  { month: 'Feb', city: '上海', temp: 7 },
  { month: 'Jan', city: '广州', temp: 15 },
  { month: 'Feb', city: '广州', temp: 16 },
];

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'line',
  data,
  encode: { x: 'month', y: 'temp', color: 'city' },
  // legendFilter 默认已启用,无需显式配置
  // 点击图例中的城市名称即可切换可见性
});

chart.render();
```

## 显式启用 legendFilter

```javascript
// 如果被禁用,可以显式重新启用
chart.options({
  type: 'line',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  interaction: {
    legendFilter: true,   // 点击图例切换显示/隐藏
  },
});
```

## 同时启用 legendHighlight(悬停高亮)

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  interaction: {
    legendFilter: true,    // 点击:过滤数据
    legendHighlight: true, // 悬停:高亮系列
  },
});
```

## 禁用图例交互

```javascript
// 禁用图例过滤(图例仅用于展示,不可点击)
chart.options({
  interaction: {
    legendFilter: false,  // 禁用点击过滤
  },
});
```

## 常见错误与修正

### 错误:以为 legendFilter 需要手动配置——实际上 G2 v5 默认启用
```javascript
// ℹ️  G2 v5 默认已启用 legendFilter,无需额外配置
// 只有以下情况才需要显式配置:

// 1. 想要禁用时
chart.options({ interaction: { legendFilter: false } });

// 2. 想要自定义样式或行为时
chart.options({ interaction: { legendFilter: { /* 自定义选项 */ } } });
```

### 错误:legend: false 时仍想要 legendFilter——图例隐藏后无法交互
```javascript
// ❌ 隐藏了图例但还想要图例过滤——图例不可见就无法点击
chart.options({
  legend: false,
  interaction: { legendFilter: true },  // ❌ 没有图例,过滤无从触发
});

// ✅ legendFilter 需要可见图例配合
chart.options({
  legend: { color: { position: 'top' } },  // ✅ 保留图例
  interaction: { legendFilter: true },
});
```
references/interactions/g2-interaction-legend-highlight.md
---
id: "g2-interaction-legend-highlight"
title: "G2 图例高亮(legendHighlight)"
description: |
  legendHighlight 交互让用户悬停图例项时,图表中对应分组的元素高亮显示,
  其他分组元素变为半透明(inactive 状态)。
  与 legendFilter 的区别:legendHighlight 只改变视觉状态,不过滤数据;
  legendFilter 点击后真正隐藏数据项。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "legendHighlight"
  - "图例高亮"
  - "交互"
  - "highlight"
  - "interaction"

related:
  - "g2-interaction-legend-filter"
  - "g2-interaction-element-highlight-by"
  - "g2-comp-legend-config"

use_cases:
  - "多系列折线图悬停图例突出显示某一系列"
  - "分组柱状图图例悬停时高亮对应分组"
  - "散点图按颜色分类悬停高亮"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction/legend-highlight"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const data = [
  { month: 'Jan', city: '北京', value: 5 },
  { month: 'Jan', city: '上海', value: 12 },
  { month: 'Feb', city: '北京', value: 8 },
  { month: 'Feb', city: '上海', value: 15 },
  { month: 'Mar', city: '北京', value: 12 },
  { month: 'Mar', city: '上海', value: 18 },
];

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'line',
  data,
  encode: { x: 'month', y: 'value', color: 'city' },
  interaction: {
    legendHighlight: true,  // 悬停图例时高亮对应系列
  },
});

chart.render();
```

## legendHighlight vs legendFilter 对比

```javascript
// legendHighlight:悬停图例 → 高亮对应元素,其余变半透明(不隐藏数据)
chart.options({
  interaction: { legendHighlight: true },
});

// legendFilter:点击图例 → 切换显示/隐藏对应数据项(数据从图表中移除)
chart.options({
  interaction: { legendFilter: true },
});

// 同时开启两种交互:悬停高亮 + 点击过滤
chart.options({
  interaction: {
    legendHighlight: true,
    legendFilter: true,
  },
});
```

## 自定义高亮样式

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
  state: {
    active: {
      // 激活(高亮)状态样式
      lineWidth: 2,
      stroke: '#000',
    },
    inactive: {
      // 非激活(背景)状态样式
      fillOpacity: 0.2,
      strokeOpacity: 0.2,
    },
  },
  interaction: {
    legendHighlight: true,
  },
});
```

## 常见错误与修正

### 错误:图例没有 color encode 时高亮无效
```javascript
// ❌ 没有 color encode,图例不会与元素关联,高亮无效
chart.options({
  type: 'interval',
  encode: { x: 'month', y: 'value' },   // ❌ 缺少 color encode
  interaction: { legendHighlight: true },
});

// ✅ 需要 color encode 来建立图例与元素的关联
chart.options({
  type: 'interval',
  encode: { x: 'month', y: 'value', color: 'city' },  // ✅ 有 color encode
  interaction: { legendHighlight: true },
});
```
references/interactions/g2-interaction-poptip.md
---
id: "g2-interaction-poptip"
title: "G2 文本溢出提示(poptip)"
description: |
  poptip 交互在文本元素文字被截断(溢出容器)时,
  鼠标悬停自动弹出完整文本的气泡提示框。
  适合轴标签过长被截断、标注文字显示不全等场景,无需自定义 tooltip。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "poptip"
  - "文本提示"
  - "溢出"
  - "气泡"
  - "截断"
  - "interaction"

related:
  - "g2-comp-tooltip-config"
  - "g2-comp-axis-config"

use_cases:
  - "X 轴分类标签过长被截断时的完整文本提示"
  - "图表内文本标注过长时的悬停提示"
  - "自动处理文本溢出,无需手动配置 tooltip"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction/poptip"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

// 轴标签很长的数据
const data = [
  { category: '人工智能与机器学习算法研究', value: 85 },
  { category: '云计算基础设施服务', value: 72 },
  { category: '大数据分析与可视化平台', value: 68 },
  { category: '区块链与去中心化应用', value: 45 },
  { category: '物联网设备管理系统', value: 60 },
];

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
  axis: {
    x: {
      labelFormatter: (v) => v.length > 6 ? v.slice(0, 6) + '...' : v,  // 截断显示
    },
  },
  interaction: {
    poptip: true,   // 开启后,悬停截断标签时自动弹出完整文本
  },
});

chart.render();
```

## 自定义 poptip 样式

```javascript
chart.options({
  interaction: {
    poptip: {
      offsetX: 8,   // 气泡横向偏移(px),默认 8
      offsetY: 8,   // 气泡纵向偏移(px),默认 8
      // 气泡样式(CSS 属性)
      tip: {
        backgroundColor: 'rgba(0,0,0,0.75)',
        color: '#fff',
        fontSize: '12px',
        padding: '4px 8px',
        borderRadius: '4px',
      },
    },
  },
});
```

## 常见错误与修正

### 错误:文本没有溢出时 poptip 不弹出——这是正确行为
```javascript
// ℹ️  poptip 只在文本真正溢出(被截断)时触发,非溢出文本不会弹出
// 如果所有标签都完整显示,hover 时不会弹出任何提示
// 这是设计行为,不是 bug

// 如果需要对所有元素都显示提示,应该用 tooltip
chart.options({
  tooltip: {
    items: [{ channel: 'x' }],   // 显示 x 轴完整值
  },
});
```
references/interactions/g2-interaction-scrollbar-filter.md
---
id: "g2-interaction-scrollbar-filter"
title: "G2 ScrollbarFilter 滚动条过滤交互"
description: |
  scrollbarFilter 是 G2 v5 的交互,通过图表内嵌滚动条来过滤可见数据范围。
  与 sliderFilter 类似,但使用更紧凑的滚动条控件(而非滑块),
  适用于数据量大、需要翻页浏览的场景(如大量类别的柱状图)。
  需配合 scrollbar 组件(scrollbar: { x: true })一起使用。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "scrollbarFilter"
  - "滚动条"
  - "scrollbar"
  - "数据过滤"
  - "分页"
  - "interaction"

related:
  - "g2-interaction-slider-filter"
  - "g2-comp-scrollbar"
  - "g2-mark-interval-basic"

use_cases:
  - "类别过多的柱状图横向滚动查看"
  - "长时间序列数据翻页浏览"
  - "大量分类数据的局部展示"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/component/scrollbar"
---

## 核心概念

`scrollbarFilter` 交互需要与 `scrollbar` 组件配合:
- `scrollbar` 字段:控制滚动条的显示位置(x 轴 / y 轴)
- `scrollbarFilter` 交互:响应滚动条拖动事件,过滤数据范围

与 `sliderFilter` 的区别:
- `sliderFilter`:双端滑块,支持任意范围选取
- `scrollbarFilter`:固定窗口大小的滚动条,只能平移不能缩放范围

## 基本用法(X 轴滚动条)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 600, height: 400 });

chart.options({
  type: 'interval',
   manyCategories,   // 大量类别数据
  encode: { x: 'category', y: 'value' },
  scrollbar: {
    x: true,   // 启用 X 轴滚动条
  },
  interaction: {
    scrollbarFilter: true,   // 启用滚动条过滤
  },
});

chart.render();
```

## Y 轴滚动条

```javascript
chart.options({
  type: 'interval',
  data: manyCategories,
  encode: { x: 'value', y: 'category' },  // 条形图
  coordinate: { transform: [{ type: 'transpose' }] },
  scrollbar: {
    y: true,   // 启用 Y 轴滚动条(条形图竖向滚动)
  },
  interaction: {
    scrollbarFilter: true,
  },
});
```

## 配置项

```javascript
chart.options({
  scrollbar: {
    x: {
      ratio: 0.3,    // 滚动条初始窗口比例(显示全部数据的 30%),默认根据数据量计算
    },
  },
  interaction: {
    scrollbarFilter: {
      // 目前 scrollbarFilter 选项较少,主要通过 scrollbar 组件配置
    },
  },
});
```

## 常见错误与修正

### 错误:忘记配置 scrollbar 组件
```javascript
// ❌ 只加 interaction 但没有 scrollbar 组件,不会显示滚动条
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value' },
  interaction: { scrollbarFilter: true },  // ❌ 没有 scrollbar 组件
});

// ✅ 必须同时配置 scrollbar 组件
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value' },
  scrollbar: { x: true },              // ✅ 启用滚动条组件
  interaction: { scrollbarFilter: true },  // ✅ 启用过滤交互
});
```

### 错误:与 sliderFilter 混用
```javascript
// ❌ scrollbar 与 slider 同时启用会冲突
chart.options({
  scrollbar: { x: true },
  slider: { x: true },
  interaction: {
    scrollbarFilter: true,
    sliderFilter: true,   // ❌ 不要同时启用
  },
});

// ✅ 选择其中一种
chart.options({
  scrollbar: { x: true },
  interaction: { scrollbarFilter: true },
});
```
references/interactions/g2-interaction-slider-filter.md
---
id: "g2-interaction-slider-filter"
title: "G2 缩略轴过滤(slider filter)"
description: |
  G2 v5 缩略轴通过 slider: { x: true } 或 interaction: [{ type: 'sliderFilter' }] 启用,
  拖动滑块过滤 x/y 轴数据范围,常用于时序图的局部时间段筛选。
library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "缩略轴"
  - "slider"
  - "过滤"
  - "时序"
  - "范围筛选"
  - "spec"

related:
  - "g2-mark-line-basic"
  - "g2-interaction-tooltip"
  - "g2-scale-time"

use_cases:
  - "时序折线图拖动查看局部时间段"
  - "大数据量图表局部放大查看"
  - "联动多图表的时间范围"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/component/slider"
---

## 基本用法(时序折线图 + x 轴缩略轴)

在折线图底部添加缩略轴,拖动滑块筛选时间范围:

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 720,
  height: 480,
});

// 生成 30 天时序数据
const data = Array.from({ length: 30 }, (_, i) => ({
  date: new Date(2024, 0, i + 1).toISOString().slice(0, 10),
  value: Math.round(200 + Math.random() * 300),
}));

chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  slider: {
    x: true,   // 在 x 轴下方显示缩略轴
  },
});

chart.render();
```

## 设置初始显示范围

`values` 接受 `[0, 1]` 区间的比例值,控制缩略轴初始选中范围:

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  slider: {
    x: {
      values: [0.6, 1.0],   // 初始只显示后 40% 的数据
    },
  },
});
```

## 双轴缩略轴(x 轴 + y 轴同时过滤)

同时在 x 轴和 y 轴添加缩略轴,适合散点图等二维数据探索:

```javascript
chart.options({
  type: 'point',
  data: [
    { price: 12000, score: 85, brand: 'A' },
    { price: 8500,  score: 72, brand: 'B' },
    { price: 23000, score: 91, brand: 'C' },
    { price: 5000,  score: 60, brand: 'D' },
    { price: 18000, score: 88, brand: 'E' },
    { price: 31000, score: 95, brand: 'F' },
    { price: 9500,  score: 78, brand: 'G' },
  ],
  encode: { x: 'price', y: 'score', color: 'brand' },
  slider: {
    x: {
      values: [0, 0.7],   // x 轴初始显示 0-70%
    },
    y: {
      values: [0.2, 1.0], // y 轴初始显示 20%-100%
    },
  },
});
```

## 自定义 label 格式

通过 `labelFormatter` 格式化缩略轴两端的标签显示:

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  slider: {
    x: {
      values: [0.4, 1.0],
      labelFormatter: (value) => {
        // value 是实际数据值(经过比例换算后的原始数据)
        const date = new Date(value);
        return `${date.getMonth() + 1}月${date.getDate()}日`;
      },
    },
  },
});
```

## 使用 interaction 方式启用

也可以通过 `interaction` 数组启用 sliderFilter,两种写法效果相同:

```javascript
// 方式一:slider 属性(推荐,更简洁)
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  slider: { x: true },
});

// 方式二:interaction 数组
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  interaction: [
    { type: 'sliderFilter' },
  ],
});
```

## 常见错误与修正

### 错误:values 超出 [0, 1] 范围

```javascript
// ❌ values 必须在 [0, 1] 区间,代表数据比例
chart.options({
  slider: {
    x: { values: [10, 80] },   // 错误:不是像素或索引,是 0-1 比例
  },
});

// ✅ 正确:使用 0-1 之间的小数
chart.options({
  slider: {
    x: { values: [0.1, 0.8] },   // 显示 10% 到 80% 的数据
  },
});
```

### 错误:在离散分类轴上使用 sliderFilter

```javascript
// ❌ slider 主要适合连续轴(时间轴、数值轴),
// 在纯分类 x 轴上效果不佳,过滤逻辑可能不符合预期
chart.options({
  type: 'interval',
  data: [{ genre: 'Sports', sold: 275 }, { genre: 'Action', sold: 120 }],
  encode: { x: 'genre', y: 'sold' },   // genre 是离散分类
  slider: { x: true },
});

// ✅ sliderFilter 最适合时序数据或大量连续数值数据
chart.options({
  type: 'line',
  data: timeSeriesData,
  encode: { x: 'date', y: 'value' },   // date 是时间轴
  slider: { x: true },
});
```

### 错误:slider 写成数组

```javascript
// ❌ slider 是对象,不是数组
chart.options({
  slider: [{ x: true }],
});

// ✅ slider 是对象,x/y 是其属性
chart.options({
  slider: { x: true },
  // 或同时启用双轴
  // slider: { x: true, y: true },
});
```

### 错误:values 顺序写反(起始值大于结束值)

```javascript
// ❌ 起始值不能大于结束值
chart.options({
  slider: {
    x: { values: [0.8, 0.2] },
  },
});

// ✅ 第一个值为起始位置,第二个值为结束位置(均为 0-1 比例)
chart.options({
  slider: {
    x: { values: [0.2, 0.8] },
  },
});
```
references/interactions/g2-interaction-slider-wheel.md
---
id: "g2-interaction-slider-wheel"
title: "G2 SliderWheel 滚轮缩放交互"
description: |
  sliderWheel 是 G2 v5 的交互,通过鼠标滚轮(或触控板双指滚动)对图表的 slider 组件进行缩放操作。
  鼠标滚轮向上缩小时间窗口(放大),向下扩大时间窗口(缩小),
  缩放以鼠标位置为中心。需配合 slider 组件和 sliderFilter 交互使用。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "sliderWheel"
  - "滚轮缩放"
  - "wheel"
  - "zoom"
  - "缩放"
  - "interaction"
  - "slider"

related:
  - "g2-interaction-slider-filter"
  - "g2-comp-slider"
  - "g2-mark-line-basic"

use_cases:
  - "时序图表用滚轮快速缩放时间范围"
  - "替代手动拖拽 slider 的快速缩放操作"
  - "触控板双指捏合缩放图表时间轴"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/interaction/slider-wheel"
---

## 核心概念

`sliderWheel` 监听图表容器的 `wheel` 事件,将滚轮 delta 转换为 slider 值域的缩放变化。
- 滚轮向上(delta < 0):缩小窗口(放大数据)
- 滚轮向下(delta > 0):扩大窗口(缩小数据)
- 缩放以鼠标位置为中心,保持鼠标下的数据点不动

## 基本用法

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 800, height: 400 });

chart.options({
  type: 'line',
  data: timeSeriesData,
  encode: { x: 'date', y: 'value' },
  slider: {
    x: { values: [0, 0.3] },   // 初始显示前 30%
  },
  interaction: {
    sliderFilter: true,    // 必须先启用 sliderFilter
    sliderWheel: true,     // 再启用 sliderWheel
  },
});

chart.render();
```

## 配置项

```javascript
chart.options({
  interaction: {
    sliderWheel: {
      x: true,               // X 轴 slider 响应滚轮,默认 true
      y: true,               // Y 轴 slider 响应滚轮,默认 true
      // x: 'shift',         // 仅在按住 Shift 时响应
      // y: 'ctrl',          // 仅在按住 Ctrl 时响应
      wheelSensitivity: 0.05,  // 滚轮灵敏度,默认 0.05
      minRange: 0.01,          // 最小缩放范围(防止过度放大),默认 0.01
    },
  },
});
```

## 修饰键控制(避免与页面滚动冲突)

```javascript
// 按住 Ctrl 时才缩放图表(避免与页面滚动冲突)
chart.options({
  interaction: {
    sliderWheel: {
      x: 'ctrl',    // 仅 Ctrl+滚轮 触发 X 轴缩放
      y: false,     // Y 轴不响应滚轮
    },
  },
});
```

## 常见错误与修正

### 错误:忘记同时启用 sliderFilter
```javascript
// ❌ 只有 sliderWheel 没有 sliderFilter,滚轮滚动无效果
chart.options({
  slider: { x: true },
  interaction: {
    sliderWheel: true,   // ❌ 缺少 sliderFilter
  },
});

// ✅ 必须配合 sliderFilter
chart.options({
  slider: { x: true },
  interaction: {
    sliderFilter: true,   // ✅ 先启用过滤
    sliderWheel: true,    // ✅ 再启用滚轮缩放
  },
});
```

### 错误:没有 slider 组件但启用了 sliderWheel
```javascript
// ❌ 没有 slider 组件时 sliderWheel 不起作用
chart.options({
  // 没有 slider 配置
  interaction: { sliderWheel: true },  // 无效
});

// ✅ 必须有 slider 组件
chart.options({
  slider: { x: { values: [0, 0.5] } },
  interaction: {
    sliderFilter: true,
    sliderWheel: true,
  },
});
```
references/interactions/g2-interaction-tooltip.md
---
id: "g2-interaction-tooltip"
title: "G2 Tooltip 交互配置"
description: |
  配置 G2 图表的 Tooltip 提示框,包括内容定制、格式化和自定义渲染。
  在 Spec 模式中,Mark 级别的 tooltip 字段控制内容,
  图表级别的 interaction 字段控制 Tooltip 行为。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "Tooltip"
  - "提示框"
  - "tooltip"
  - "交互"
  - "悬停"
  - "hover"
  - "spec"

related:
  - "g2-core-chart-init"
  - "g2-interaction-crosshair"

use_cases:
  - "为图表添加数据悬停提示"
  - "自定义 Tooltip 展示的字段和格式"
  - "关闭不需要的 Tooltip"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/component/tooltip"
---

## 核心概念

G2 Spec 模式中 Tooltip 有两个配置位置:
- **Mark 级别 `tooltip` 字段**:控制该 Mark 的 Tooltip 显示内容
- **图表级别 `interaction` 字段**:控制 Tooltip 的触发行为和自定义渲染

G2 默认已启用 Tooltip,鼠标悬停时显示当前元素的数据。

## 基本用法(Spec 模式)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data,
  encode: { x: 'genre', y: 'sold' },
  tooltip: {
    title: 'genre',     // Tooltip 标题字段
    items: [
      { field: 'sold', name: '销量', valueFormatter: (v) => `${v} 万` },
    ],
  },
});

chart.render();
```

## tooltip 字段详细配置

```javascript
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'x', y: 'y' },
  tooltip: {
    // 标题:字段名字符串 | 固定字符串 | 函数
    title: 'name',

    // items:定义 Tooltip 中展示的数据行
    items: [
      // 写法 1:字段名字符串(快捷写法)
      'value',

      // 写法 2:对象配置
      {
        field: 'value',                              // 数据字段
        name: '数值',                                // 显示名称
        valueFormatter: (v) => `${v.toFixed(2)}%`,  // 值格式化
        color: '#1890ff',                            // 颜色标记
      },

      // 写法 3:函数(完全自定义)
      (data) => ({
        name: '计算值',
        value: data.a + data.b,
      }),
    ],
  },
});
```

## 关闭 Tooltip

```javascript
// 关闭整个图表的 Tooltip
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'x', y: 'y' },
  interaction: { tooltip: false },   // 图表级别关闭
});

// 或关闭特定 Mark 的 Tooltip 内容(传 false)
chart.options({
  type: 'interval',
  tooltip: false,   // 该 Mark 不提供 Tooltip 内容
});
```

## 自定义 Tooltip 渲染(HTML)

```javascript
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'genre', y: 'sold' },
  interaction: {
    tooltip: {
      render: (event, { title, items }) => `
        <div style="padding: 8px 12px; background: white; border: 1px solid #ddd; border-radius: 4px;">
          <strong>${title}</strong>
          ${items.map(item => `
            <div style="display: flex; justify-content: space-between; gap: 16px; margin-top: 4px;">
              <span style="color: ${item.color}">${item.name}</span>
              <span>${item.value}</span>
            </div>
          `).join('')}
        </div>
      `,
    },
  },
});
```

## 在 view 容器中配置 Tooltip

```javascript
// 多 Mark 叠加时,在外层 view 统一配置 Tooltip
chart.options({
  type: 'view',
  data: [...],
  interaction: { tooltip: { shared: true } },  // 共享 Tooltip(多 Mark 合并展示)
  children: [
    {
      type: 'line',
      encode: { x: 'month', y: 'value', color: 'type' },
      tooltip: { items: [{ field: 'value', name: '数值' }] },
    },
    {
      type: 'point',
      encode: { x: 'month', y: 'value', color: 'type' },
      tooltip: false,    // 点 Mark 不单独触发 Tooltip
    },
  ],
});
```

## 常见错误与修正

### 错误 1:tooltip 写在了 style 里
```javascript
// ❌ 错误
chart.options({ type: 'interval',  [...], style: { tooltip: { title: 'name' } } });

// ✅ 正确:tooltip 是与 encode/style 同级的字段
chart.options({ type: 'interval',  [...], tooltip: { title: 'name' } });
```

### 错误 2:interaction.tooltip 与 mark.tooltip 职责混淆
```javascript
// ❌ 错误:把内容配置写在 interaction 里
chart.options({
  interaction: { tooltip: { items: [{ field: 'value' }] } },  // 无效!
});

// ✅ 正确:内容配置在 mark 的 tooltip 字段;行为配置在 interaction.tooltip
chart.options({
  type: 'interval',
  tooltip: { items: [{ field: 'value', name: '数值' }] },  // 内容
  interaction: { tooltip: { shared: true } },              // 行为
});
```
references/interactions/g2-interaction-treemap-drilldown.md
---
id: "g2-interaction-treemap-drilldown"
title: "G2 矩形树图下钻(treemapDrillDown)"
description: |
  treemapDrillDown 为矩形树图(treemap)提供层级下钻交互,
  点击矩形块进入下一层级,顶部显示面包屑导航可返回上级。
  与 drillDown(用于 partition/sunburst)不同,专为 treemap 布局设计。

library: "g2"
version: "5.x"
category: "interactions"
tags:
  - "treemapDrillDown"
  - "矩形树图"
  - "下钻"
  - "层级"
  - "面包屑"
  - "interaction"

related:
  - "g2-mark-treemap"
  - "g2-interaction-drilldown"
  - "g2-mark-partition"

use_cases:
  - "多层级目录/文件大小可视化"
  - "产品分类层级销售分析"
  - "组织架构矩形树图下钻"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/hierarchy/treemap/#treemap-drill-down"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

// 多层级树形数据
const hierarchyData = {
  name: '总销售额',
  children: [
    {
      name: '电子产品',
      children: [
        { name: '手机', value: 400 },
        { name: '电脑', value: 350 },
        { name: '平板', value: 200 },
      ],
    },
    {
      name: '服装',
      children: [
        { name: '男装', value: 280 },
        { name: '女装', value: 320 },
      ],
    },
    {
      name: '食品',
      children: [
        { name: '零食', value: 180 },
        { name: '饮料', value: 150 },
      ],
    },
  ],
};

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'treemap',
  data: hierarchyData,
  encode: { value: 'value', color: 'name' },
  style: {
    labelText: (d) => d.data.name,
    labelFill: '#fff',
    stroke: '#fff',
    lineWidth: 1,
  },
  interaction: {
    treemapDrillDown: {
      // 面包屑导航样式
      breadCrumbFill: 'rgba(0,0,0,0.85)',
      breadCrumbFontSize: 12,
      activeFill: 'rgba(0,0,0,0.5)',
    },
  },
});

chart.render();
```

## treemapDrillDown vs drillDown 对比

```javascript
// treemapDrillDown:专为 treemap(矩形树图)设计
chart.options({
  type: 'treemap',
  interaction: { treemapDrillDown: true },
});

// drillDown:用于 partition(旭日图/冰柱图)
chart.options({
  type: 'partition',
  interaction: { drillDown: true },
  coordinate: { type: 'polar' },   // 旭日图用极坐标
});
```

## 常见错误与修正

### 错误:在非 treemap mark 上使用 treemapDrillDown
```javascript
// ❌ treemapDrillDown 只适用于 treemap mark
chart.options({
  type: 'partition',   // ❌ 应用 drillDown,不是 treemapDrillDown
  interaction: { treemapDrillDown: true },
});

// ✅ partition 使用 drillDown
chart.options({
  type: 'partition',
  interaction: { drillDown: true },   // ✅
});

// ✅ treemap 使用 treemapDrillDown
chart.options({
  type: 'treemap',
  interaction: { treemapDrillDown: true },  // ✅
});
```

### 错误:数据不是嵌套层级结构
```javascript
// ❌ 扁平数据无法下钻
chart.options({
  type: 'treemap',
  data: [{ name: 'a', value: 10 }, { name: 'b', value: 20 }],  // ❌ 无层级
  interaction: { treemapDrillDown: true },
});

// ✅ 需要嵌套 children 结构
chart.options({
  type: 'treemap',
   { name: 'root', children: [...] },  // ✅ 嵌套层级
  interaction: { treemapDrillDown: true },
});
```
references/label-transform/g2-label-transform-contrast-reverse.md
---
id: "g2-label-transform-contrast-reverse"
title: "G2 ContrastReverse 标签变换"
description: |
  标签对比度反转变换。根据背景颜色自动调整标签颜色,
  确保标签在深色背景上显示浅色,在浅色背景上显示深色。

library: "g2"
version: "5.x"
category: "label-transform"
tags:
  - "标签"
  - "label"
  - "对比度"
  - "颜色"
  - "contrast"

related:
  - "g2-label-transform-overflow-hide"
  - "g2-comp-label-config"

use_cases:
  - "柱状图内部标签"
  - "饼图标签"
  - "需要根据背景调整颜色的标签"

anti_patterns:
  - "固定颜色标签不需要此变换"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/label"
---

## 核心概念

ContrastReverse 标签变换会根据元素的颜色自动调整标签颜色:
- 深色背景 → 浅色标签
- 浅色背景 → 深色标签

**工作原理:**
1. 获取元素的填充颜色
2. 计算颜色的亮度
3. 根据亮度选择对比色

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'interval',
  data: [
    { category: 'A', value: 100 },
    { category: 'B', value: 150 },
    { category: 'C', value: 80 },
  ],
  encode: {
    x: 'category',
    y: 'value',
    color: 'category',
  },
  labels: [
    {
      text: 'value',
      position: 'inside',
      transform: [{ type: 'contrastReverse' }],
    },
  ],
});

chart.render();
```

## 常用变体

### 结合其他变换

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
  labels: [
    {
      text: 'value',
      position: 'inside',
      transform: [
        { type: 'contrastReverse' },
        { type: 'overflowHide' },
      ],
    },
  ],
});
```

### 自定义对比色

```javascript
// 注意:contrastReverse 通常使用默认的黑白对比
// 如需自定义,可以在 style 中设置
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
  labels: [
    {
      text: 'value',
      position: 'inside',
      style: {
        fill: '#fff',  // 固定白色
        stroke: '#000',  // 描边增加对比度
        lineWidth: 1,
      },
    },
  ],
});
```

## 完整类型参考

```typescript
interface ContrastReverseTransform {
  type: 'contrastReverse';
  // 无额外配置参数
}
```

## 与固定颜色的对比

| 方式 | 优点 | 缺点 |
|------|------|------|
| contrastReverse | 自动适应 | 可能不符合设计风格 |
| 固定颜色 | 风格统一 | 可能对比度不足 |
| 描边 | 增加对比度 | 可能影响清晰度 |

## 常见错误与修正

### 错误 1:transform 格式错误

```javascript
// ❌ 错误:transform 应该是数组
labels: [{ text: 'value', transform: { type: 'contrastReverse' } }]

// ✅ 正确
labels: [{ text: 'value', transform: [{ type: 'contrastReverse' }] }]
```

### 错误 2:position 设置不当

```javascript
// ⚠️ 注意:contrastReverse 主要用于 inside 位置
// outside 位置的标签不在元素上,无法获取背景色

// ✅ 正确:用于 inside 标签
labels: [{
  text: 'value',
  position: 'inside',
  transform: [{ type: 'contrastReverse' }]
}]
```
references/label-transform/g2-label-transform-exceed-adjust.md
---
id: "g2-label-transform-exceed-adjust"
title: "G2 ExceedAdjust 标签变换"
description: |
  标签超出调整变换。当标签超出指定范围时自动调整位置,
  确保标签在可视区域内显示。

library: "g2"
version: "5.x"
category: "label-transform"
tags:
  - "标签"
  - "label"
  - "超出"
  - "调整"
  - "exceed"

related:
  - "g2-label-transform-overflow-hide"
  - "g2-label-transform-overlap-dodge-y"
  - "g2-comp-label-config"

use_cases:
  - "图表边缘标签调整"
  - "小尺寸元素标签"
  - "需要保持标签完整的场景"

anti_patterns:
  - "标签可以隐藏的场景(改用 overflowHide)"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/label"
---

## 核心概念

ExceedAdjust 标签变换会检测标签是否超出可视区域:
- 如果超出,自动调整标签位置
- 确保标签完整显示

**工作原理:**
1. 计算标签的边界框
2. 检测是否超出图表边界
3. 如果超出,向内调整位置

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'point',
  data: [
    { x: 10, y: 100 },
    { x: 20, y: 150 },
    { x: 30, y: 200 },  // 可能在图表顶部边缘
  ],
  encode: {
    x: 'x',
    y: 'y',
  },
  labels: [
    {
      text: 'y',
      position: 'top',
      transform: [{ type: 'exceedAdjust' }],
    },
  ],
});

chart.render();
```

## 常用变体

### 结合其他变换

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'x', y: 'y' },
  labels: [
    {
      text: 'y',
      position: 'top',
      transform: [
        { type: 'exceedAdjust' },
        { type: 'overlapDodgeY' },
      ],
    },
  ],
});
```

## 完整类型参考

```typescript
interface ExceedAdjustTransform {
  type: 'exceedAdjust';
  // 无额外配置参数
}
```

## 与其他标签变换的对比

| Transform | 功能 | 处理方式 |
|-----------|------|---------|
| exceedAdjust | 超出调整 | 移动位置 |
| overflowHide | 溢出隐藏 | 隐藏标签 |
| overlapDodgeY | 重叠避让 | Y 方向分开 |

## 常见错误与修正

### 错误 1:transform 格式错误

```javascript
// ❌ 错误:transform 应该是数组
labels: [{ text: 'value', transform: { type: 'exceedAdjust' } }]

// ✅ 正确
labels: [{ text: 'value', transform: [{ type: 'exceedAdjust' }] }]
```

### 错误 2:与其他变换顺序错误

```javascript
// ⚠️ 注意:变换顺序影响结果
// 建议先处理超出,再处理重叠

// ✅ 正确顺序
transform: [
  { type: 'exceedAdjust' },
  { type: 'overlapDodgeY' },
]
```
references/label-transform/g2-label-transform-overflow-hide.md
---
id: "g2-label-transform-overflow-hide"
title: "G2 OverflowHide 标签变换"
description: |
  标签溢出隐藏变换。当标签超出其所属元素的边界时自动隐藏,
  避免标签溢出造成的视觉混乱。

library: "g2"
version: "5.x"
category: "label-transform"
tags:
  - "标签"
  - "label"
  - "溢出"
  - "隐藏"
  - "overflow"

related:
  - "g2-label-transform-overlap-hide"
  - "g2-label-transform-overlap-dodge-y"
  - "g2-comp-label-config"

use_cases:
  - "饼图标签溢出处理"
  - "柱状图数据标签"
  - "小尺寸元素的标签显示"

anti_patterns:
  - "标签必须全部显示的场景"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/label"
---

## 核心概念

OverflowHide 标签变换会检测标签是否超出其所属元素的边界:
- 如果标签在元素边界内,正常显示
- 如果标签超出边界,自动隐藏

**工作原理:**
1. 计算元素的边界框
2. 计算标签的边界框
3. 检测标签是否溢出元素边界
4. 溢出则隐藏标签

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'interval',
  data: [
    { category: 'A', value: 10 },
    { category: 'B', value: 50 },
    { category: 'C', value: 5 },  // 小柱子,标签可能溢出
  ],
  encode: {
    x: 'category',
    y: 'value',
  },
  labels: [
    {
      text: 'value',
      position: 'inside',
      transform: [{ type: 'overflowHide' }],
    },
  ],
});

chart.render();
```

## 常用变体

### 饼图标签溢出处理

```javascript
chart.options({
  type: 'interval',
  coordinate: { type: 'theta' },
  data,
  encode: { y: 'value', color: 'category' },
  labels: [
    {
      text: 'category',
      position: 'inside',
      transform: [{ type: 'overflowHide' }],
    },
  ],
});
```

### 结合其他标签变换

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value' },
  labels: [
    {
      text: 'value',
      position: 'inside',
      transform: [
        { type: 'overflowHide' },
        { type: 'overlapHide' },  // 先处理溢出,再处理重叠
      ],
    },
  ],
});
```

## 完整类型参考

```typescript
interface OverflowHideTransform {
  type: 'overflowHide';
  // 无额外配置参数
}
```

## 与其他标签变换的对比

| Transform | 功能 | 适用场景 |
|-----------|------|---------|
| overflowHide | 隐藏溢出标签 | 标签超出元素边界 |
| overlapHide | 隐藏重叠标签 | 标签之间重叠 |
| overlapDodgeY | Y 方向避让 | 标签垂直重叠 |

## 常见错误与修正

### 错误 1:transform 格式错误

```javascript
// ❌ 错误:transform 应该是数组
labels: [{ text: 'value', transform: { type: 'overflowHide' } }]

// ✅ 正确
labels: [{ text: 'value', transform: [{ type: 'overflowHide' }] }]
```

### 错误 2:position 设置不当

```javascript
// ⚠️ 注意:outside 位置的标签通常不会溢出
// overflowHide 主要用于 inside 位置

// 对于 inside 标签
labels: [{
  text: 'value',
  position: 'inside',
  transform: [{ type: 'overflowHide' }]
}]

// 对于 outside 标签,考虑使用 overlapHide
labels: [{
  text: 'value',
  position: 'outside',
  transform: [{ type: 'overlapHide' }]
}]
```

### 错误 3:与其他变换顺序错误

```javascript
// ⚠️ 注意:变换顺序影响结果

// 推荐:先处理溢出,再处理重叠
transform: [
  { type: 'overflowHide' },
  { type: 'overlapHide' },
]
```
references/label-transform/g2-label-transform-overflow-stroke.md
---
id: "g2-label-transform-overflow-stroke"
title: "G2 OverflowStroke 标签变换"
description: |
  标签溢出描边变换。当标签超出元素边界时自动添加描边,
  增强标签的可读性。

library: "g2"
version: "5.x"
category: "label-transform"
tags:
  - "标签"
  - "label"
  - "溢出"
  - "描边"
  - "stroke"

related:
  - "g2-label-transform-overflow-hide"
  - "g2-label-transform-contrast-reverse"
  - "g2-comp-label-config"

use_cases:
  - "饼图外部标签"
  - "需要增强可读性的标签"
  - "复杂背景下的标签显示"

anti_patterns:
  - "简单场景不需要描边"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/label"
---

## 核心概念

OverflowStroke 标签变换会检测标签是否超出元素边界:
- 如果超出,为标签添加描边
- 增强标签在复杂背景上的可读性

**工作原理:**
1. 计算元素和标签的边界框
2. 检测标签是否超出元素边界
3. 如果超出,添加描边样式

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'interval',
  data: [
    { category: 'A', value: 100 },
    { category: 'B', value: 150 },
    { category: 'C', value: 80 },
  ],
  encode: {
    x: 'category',
    y: 'value',
    color: 'category',
  },
  labels: [
    {
      text: 'value',
      position: 'inside',
      transform: [{ type: 'overflowStroke' }],
    },
  ],
});

chart.render();
```

## 常用变体

### 结合其他变换

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
  labels: [
    {
      text: 'value',
      position: 'inside',
      transform: [
        { type: 'overflowStroke' },
        { type: 'contrastReverse' },
      ],
    },
  ],
});
```

## 完整类型参考

```typescript
interface OverflowStrokeTransform {
  type: 'overflowStroke';
  // 无额外配置参数
}
```

## 与其他标签变换的对比

| Transform | 功能 | 处理方式 |
|-----------|------|---------|
| overflowStroke | 溢出描边 | 添加描边 |
| overflowHide | 溢出隐藏 | 隐藏标签 |
| contrastReverse | 对比度反转 | 改变颜色 |

## 常见错误与修正

### 错误 1:transform 格式错误

```javascript
// ❌ 错误:transform 应该是数组
labels: [{ text: 'value', transform: { type: 'overflowStroke' } }]

// ✅ 正确
labels: [{ text: 'value', transform: [{ type: 'overflowStroke' }] }]
```

### 错误 2:与其他变换顺序错误

```javascript
// ⚠️ 注意:描边应该在颜色调整之后
// 建议顺序:contrastReverse → overflowStroke

// ✅ 正确顺序
transform: [
  { type: 'contrastReverse' },
  { type: 'overflowStroke' },
]
```
references/label-transform/g2-label-transform-overlap-dodge-y.md
---
id: "g2-label-transform-overlap-dodge-y"
title: "G2 OverlapDodgeY 标签变换"
description: |
  标签 Y 方向避让变换。当标签在 Y 方向重叠时自动调整位置,
  通过迭代算法避免标签重叠。

library: "g2"
version: "5.x"
category: "label-transform"
tags:
  - "标签"
  - "label"
  - "重叠"
  - "避让"
  - "dodge"

related:
  - "g2-label-transform-overlap-hide"
  - "g2-label-transform-overflow-hide"
  - "g2-comp-label-config"

use_cases:
  - "密集数据点的标签显示"
  - "时间序列图表的标签避让"
  - "需要显示所有标签的场景"

anti_patterns:
  - "标签过多时可能导致布局混乱"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/label"
---

## 核心概念

OverlapDodgeY 标签变换通过迭代算法在 Y 方向调整标签位置:
- 检测相邻标签是否在 X 方向重叠
- 如果重叠,在 Y 方向分开
- 迭代直到无重叠或达到最大迭代次数

**算法特点:**
- 时间复杂度 O(n log n)
- 支持设置最大迭代次数
- 支持设置标签间距

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'line',
  data: [
    { date: '2024-01-01', value: 100, label: 'Event A' },
    { date: '2024-01-02', value: 120, label: 'Event B' },
    { date: '2024-01-02', value: 110, label: 'Event C' },  // 同一天,标签可能重叠
  ],
  encode: {
    x: 'date',
    y: 'value',
  },
  labels: [
    {
      text: 'label',
      position: 'top',
      transform: [{ type: 'overlapDodgeY' }],
    },
  ],
});

chart.render();
```

## 常用变体

### 自定义间距

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  labels: [
    {
      text: 'label',
      position: 'top',
      transform: [
        {
          type: 'overlapDodgeY',
          padding: 4,  // 标签之间的最小间距(像素)
        },
      ],
    },
  ],
});
```

### 控制迭代次数

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  labels: [
    {
      text: 'label',
      position: 'top',
      transform: [
        {
          type: 'overlapDodgeY',
          maxIterations: 20,  // 最大迭代次数,默认 10
          maxError: 0.1,      // 最大误差,默认 0.1
        },
      ],
    },
  ],
});
```

### 结合其他变换

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  labels: [
    {
      text: 'label',
      position: 'top',
      transform: [
        { type: 'overlapDodgeY' },
        { type: 'overflowHide' },  // 先避让,再处理溢出
      ],
    },
  ],
});
```

## 完整类型参考

```typescript
interface OverlapDodgeYTransform {
  type: 'overlapDodgeY';
  padding?: number;         // 标签间距,默认 1
  maxIterations?: number;   // 最大迭代次数,默认 10
  maxError?: number;        // 最大误差,默认 0.1
}
```

## 与其他标签变换的对比

| Transform | 功能 | 优点 | 缺点 |
|-----------|------|------|------|
| overlapDodgeY | Y 方向避让 | 保留所有标签 | 可能改变布局 |
| overlapHide | 隐藏重叠标签 | 布局稳定 | 丢失部分标签 |
| overflowHide | 隐藏溢出标签 | 避免溢出 | 可能丢失标签 |

## 工作原理图解

```
原始状态:
  Label A -------- Label B
      ↑ 重叠 ↑

处理后:
  Label B
      ↑
  Label A --------

  (Y 方向分开)
```

## 常见错误与修正

### 错误 1:transform 格式错误

```javascript
// ❌ 错误:transform 应该是数组
labels: [{ text: 'value', transform: { type: 'overlapDodgeY' } }]

// ✅ 正确
labels: [{ text: 'value', transform: [{ type: 'overlapDodgeY' }] }]
```

### 错误 2:迭代次数设置不当

```javascript
// ⚠️ 注意:迭代次数过多会影响性能
// 标签数量多时,建议减少迭代次数

// 标签较少时
transform: [{ type: 'overlapDodgeY', maxIterations: 20 }]

// 标签较多时
transform: [{ type: 'overlapDodgeY', maxIterations: 5 }]
```

### 错误 3:与其他变换顺序错误

```javascript
// ❌ 错误:先隐藏再避让,效果不佳
transform: [
  { type: 'overlapHide' },
  { type: 'overlapDodgeY' },
]

// ✅ 正确:先避让,再隐藏无法处理的
transform: [
  { type: 'overlapDodgeY' },
  { type: 'overlapHide' },
]
```
references/label-transform/g2-label-transform-overlap-hide.md
---
id: "g2-label-transform-overlap-hide"
title: "G2 OverlapHide 标签变换"
description: |
  标签重叠隐藏变换。当标签重叠时自动隐藏部分标签,
  避免视觉混乱。支持按优先级决定隐藏顺序。

library: "g2"
version: "5.x"
category: "label-transform"
tags:
  - "标签"
  - "label"
  - "重叠"
  - "隐藏"
  - "overlap"

related:
  - "g2-label-transform-overlap-dodge-y"
  - "g2-label-transform-overflow-hide"
  - "g2-comp-label-config"

use_cases:
  - "密集数据点的标签显示"
  - "时间序列图表的标签处理"
  - "需要简洁显示的场景"

anti_patterns:
  - "必须显示所有标签的场景(改用 overlapDodgeY)"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/label"
---

## 核心概念

OverlapHide 标签变换通过检测标签重叠来隐藏部分标签:
- 按顺序检测每个标签是否与已显示的标签重叠
- 如果重叠,隐藏当前标签
- 支持设置优先级决定隐藏顺序

**工作原理:**
1. 获取所有标签
2. 按优先级排序(可选)
3. 依次检测每个标签是否与已显示标签重叠
4. 重叠则隐藏,否则显示

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'line',
  data: [
    { date: '2024-01-01', value: 100 },
    { date: '2024-01-02', value: 120 },
    { date: '2024-01-03', value: 110 },
    { date: '2024-01-04', value: 130 },
  ],
  encode: {
    x: 'date',
    y: 'value',
  },
  labels: [
    {
      text: 'value',
      position: 'top',
      transform: [{ type: 'overlapHide' }],
    },
  ],
});

chart.render();
```

## 常用变体

### 设置优先级

```javascript
chart.options({
  type: 'interval',
  data: [
    { category: 'A', value: 100, priority: 2 },
    { category: 'B', value: 50, priority: 1 },
    { category: 'C', value: 80, priority: 3 },
  ],
  encode: { x: 'category', y: 'value' },
  labels: [
    {
      text: 'value',
      position: 'inside',
      transform: [
        {
          type: 'overlapHide',
          priority: (a, b) => a.priority - b.priority,  // 高优先级先显示
        },
      ],
    },
  ],
});
```

### 结合其他变换

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  labels: [
    {
      text: 'value',
      position: 'top',
      transform: [
        { type: 'overlapDodgeY' },  // 先尝试避让
        { type: 'overlapHide' },    // 无法避让的隐藏
      ],
    },
  ],
});
```

## 完整类型参考

```typescript
interface OverlapHideTransform {
  type: 'overlapHide';
  priority?: (a: any, b: any) => number;  // 优先级比较函数
}
```

## 与其他标签变换的对比

| Transform | 功能 | 优点 | 缺点 |
|-----------|------|------|------|
| overlapHide | 隐藏重叠标签 | 布局稳定 | 丢失部分标签 |
| overlapDodgeY | Y 方向避让 | 保留所有标签 | 可能改变布局 |
| overflowHide | 隐藏溢出标签 | 避免溢出 | 可能丢失标签 |

## 优先级排序示例

```javascript
// 按数值大小排序:大值优先显示
labels: [{
  text: 'value',
  transform: [{
    type: 'overlapHide',
    priority: (a, b) => b.value - a.value
  }]
}]

// 按特定顺序排序
labels: [{
  text: 'value',
  transform: [{
    type: 'overlapHide',
    priority: (a, b) => {
      const order = ['A', 'B', 'C', 'D'];
      return order.indexOf(a.category) - order.indexOf(b.category);
    }
  }]
}]
```

## 常见错误与修正

### 错误 1:transform 格式错误

```javascript
// ❌ 错误:transform 应该是数组
labels: [{ text: 'value', transform: { type: 'overlapHide' } }]

// ✅ 正确
labels: [{ text: 'value', transform: [{ type: 'overlapHide' }] }]
```

### 错误 2:优先级函数返回值错误

```javascript
// ❌ 错误:优先级函数应该返回数字
priority: (a, b) => a.value > b.value

// ✅ 正确:返回正数表示 a 优先,负数表示 b 优先
priority: (a, b) => b.value - a.value
```

### 错误 3:与其他变换顺序错误

```javascript
// ❌ 错误:先隐藏再处理其他问题
transform: [
  { type: 'overlapHide' },
  { type: 'overlapDodgeY' },  // 已经隐藏的标签无法避让
]

// ✅ 正确:先尝试其他解决方案,最后隐藏
transform: [
  { type: 'overlapDodgeY' },
  { type: 'overlapHide' },
]
```
references/marks/g2-mark-arc-diagram.md
---
id: "g2-mark-arc-diagram"
title: "G2 Arc Diagram Mark"
description: |
  弧长连接图 Mark。使用 line 和 point 组合展示节点之间的链接关系。
  适用于关系网络分析、社交网络、知识图谱等场景。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "弧长连接图"
  - "arc diagram"
  - "关系图"
  - "网络"

related:
  - "g2-mark-chord"
  - "g2-mark-sankey"

use_cases:
  - "关系网络分析"
  - "社交网络"
  - "知识图谱"

anti_patterns:
  - "层次结构应使用树图"
  - "节点过多不适合"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/mark/arc-diagram"
---

## 核心概念

弧长连接图展示节点之间的链接关系:
- 节点沿线性轴或环形排列
- 用弧线表示节点之间的连接
- 支持线性布局和环形布局

**关键特点:**
- 一维布局方式
- 清晰呈现环和桥结构
- 节点排序影响视觉效果

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  theme: 'classic',
});

// 数据预处理:计算弧线坐标
const processData = (nodes, links) => {
  const arcData = [];
  const nodePositions = {};

  nodes.forEach((node, i) => {
    nodePositions[node.id] = i * 15 + 50;
  });

  links.forEach((link) => {
    const sourceX = nodePositions[link.source];
    const targetX = nodePositions[link.target];
    const distance = Math.abs(targetX - sourceX);
    const arcHeight = Math.min(150, distance * 0.1);

    for (let i = 0; i <= 15; i++) {
      const t = i / 15;
      const x = sourceX + (targetX - sourceX) * t;
      const y = 600 - arcHeight * Math.sin(Math.PI * t);
      arcData.push({ x, y, linkId: `${link.source}-${link.target}` });
    }
  });

  return { arcData, nodePositions, nodes };
};

chart.options({
  type: 'view',
   data: { type: 'fetch', value: 'relationship.json' },
  // ... 数据处理和渲染
});

chart.render();
```

## 常用变体

### 环形布局

```javascript
chart.options({
  type: 'view',
  coordinate: { type: 'polar' },  // 极坐标系
  data,
  children: [
    {
      type: 'line',
      encode: { x: 'x', y: 'y', series: 'linkId' },
    },
    {
      type: 'point',
      encode: { x: 'angle', y: 'radius', color: 'group' },
    },
  ],
});
```

### 带节点标签

```javascript
chart.options({
  type: 'view',
  children: [
    { type: 'line', data: arcData, encode: { x: 'x', y: 'y', series: 'linkId' } },
    { type: 'point', data: nodeData, encode: { x: 'x', y: 'y', color: 'group' } },
    { type: 'text',  nodeData, encode: { x: 'x', y: 'y', text: 'name' } },
  ],
});
```

### 带交互高亮

```javascript
chart.options({
  type: 'view',
  children: [
    {
      type: 'line',
      data: arcData,
      encode: { x: 'x', y: 'y', series: 'linkId' },
      style: { strokeOpacity: 0.4 },
      state: {
        active: { strokeOpacity: 1, lineWidth: 2 },
      },
    },
  ],
  interactions: [{ type: 'elementHighlight' }],
});
```

## 完整类型参考

```typescript
interface ArcDiagramData {
  nodes: Array<{ id: string; label: string; group?: string }>;
  links: Array<{ source: string; target: string; value?: number }>;
}

// 弧长连接图由多个图层组成:
// 1. line - 弧线连接
// 2. point - 节点
// 3. text - 标签(可选)
```

## 弧长连接图 vs 和弦图

| 特性 | 弧长连接图 | 和弦图 |
|------|------------|--------|
| 节点布局 | 线性/环形 | 环形 |
| 连线方式 | 弧线重叠 | 平铺不重叠 |
| 适用场景 | 关系展示 | 流向展示 |

## 常见错误与修正

### 错误 1:节点未排序

```javascript
// ⚠️ 注意:节点排序影响视觉效果
// 建议按社区或度数排序
```

### 错误 2:连线过多

```javascript
// ⚠️ 注意:连线过多会导致视觉混乱
// 建议过滤或聚合部分连线
```

### 错误 3:缺少数据预处理

```javascript
// ❌ 问题:直接使用原始数据
 { nodes: [...], links: [...] }

// ✅ 正确:预处理计算坐标
data: { transform: [{ type: 'custom', callback: processData }] }
```
references/marks/g2-mark-arc-donut.md
---
id: "g2-mark-arc-donut"
title: "G2 环形图(Donut Chart)"
description: |
  在饼图基础上通过设置 coordinate.innerRadius 创建环形图(甜甜圈图),
  中间空白区域可放置汇总数字或说明文字,在保留占比展示的同时减少视觉重量。

library: "g2"
version: "5.x"
category: "marks"
subcategory: "arc"
tags:
  - "环形图"
  - "甜甜圈"
  - "donut"
  - "innerRadius"
  - "占比"
  - "饼图变体"
  - "spec"

related:
  - "g2-mark-arc-pie"
  - "g2-transform-stacky"

use_cases:
  - "展示各类别占比,中心区域显示汇总数据"
  - "比饼图更现代的占比展示方式"
  - "KPI 卡片中的占比环"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/donut"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 480,
  height: 480,
});

chart.options({
  type: 'interval',
  data: [
    { type: '分类一', value: 27 },
    { type: '分类二', value: 25 },
    { type: '分类三', value: 18 },
    { type: '分类四', value: 15 },
    { type: '其他',   value: 15 },
  ],
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: {
    type: 'theta',
    outerRadius: 0.8,
    innerRadius: 0.5,    // 关键:设置内径产生空心效果
  },
});

chart.render();
```

## 带中心文字的环形图

```javascript
import { Chart } from '@antv/g2';

const data = [
  { type: '已完成', value: 75 },
  { type: '未完成', value: 25 },
];
const total = data.reduce((s, d) => s + d.value, 0);

const chart = new Chart({ container: 'container', width: 400, height: 400 });

chart.options({
  type: 'view',
  children: [
    {
      type: 'interval',
       data,
      encode: { y: 'value', color: 'type' },
      transform: [{ type: 'stackY' }],
      coordinate: { type: 'theta', outerRadius: 0.85, innerRadius: 0.6 },
      scale: {
        color: { range: ['#1890ff', '#f0f0f0'] },
      },
      legend: false,
    },
    {
      // 中心文字用 text mark 在极坐标中心绘制
      type: 'text',
       [{ value: data[0].value }],
      encode: { text: (d) => `${d.value}%` },
      style: {
        x: '50%', y: '50%',
        textAlign: 'center',
        fontSize: 32,
        fontWeight: 'bold',
        fill: '#1890ff',
      },
    },
  ],
});

chart.render();
```

## 带外部标签的环形图

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: { type: 'theta', outerRadius: 0.8, innerRadius: 0.5 },
  labels: [
    {
      text: (d) => `${d.type}: ${d.value}`,
      position: 'outside',
      connector: true,
    },
  ],
});
```

## 常见错误与修正

### 错误:innerRadius 大于 outerRadius
```javascript
// ❌ 错误:内径大于外径,圆环消失
chart.options({
  coordinate: { type: 'theta', outerRadius: 0.5, innerRadius: 0.8 },
});

// ✅ 正确:innerRadius < outerRadius,推荐比例 0.5-0.7
chart.options({
  coordinate: { type: 'theta', outerRadius: 0.8, innerRadius: 0.5 },
});
```
references/marks/g2-mark-arc-pie.md
---
id: "g2-mark-arc-pie"
title: "G2 饼图(Interval + theta 坐标系)"
description: |
  使用 Interval Mark 配合 theta 坐标系和 stackY 变换创建饼图,
  展示各部分在整体中的占比关系。本文采用 Spec 模式(chart.options({}))。

library: "g2"
version: "5.x"
category: "marks"
subcategory: "arc"
tags:
  - "饼图"
  - "pie chart"
  - "占比"
  - "比例"
  - "theta坐标系"
  - "stackY"
  - "spec"

related:
  - "g2-mark-arc-donut"
  - "g2-core-chart-init"
  - "g2-transform-stacky"
  - "g2-interaction-tooltip"

use_cases:
  - "展示各类别占总量的比例"
  - "显示市场份额分布"
  - "可视化资源分配比例"

anti_patterns:
  - "类别超过 6-7 个时饼图难以阅读,改用柱状图"
  - "需要精确比较数值时不适用(人眼对角度判断不准确)"
  - "有零值或负值时饼图无意义"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/pie"
---

## 核心概念

G2 v5 饼图的 Spec 结构:
- `coordinate: { type: 'theta' }` — 将直角坐标转换为圆形角度坐标
- `transform: [{ type: 'stackY' }]` — 将各分类数值累积为角度区间(**必须**)
- `encode.y` — 映射数值字段(角度大小)
- `encode.color` — 映射分类字段(扇区颜色)

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'interval',
  data: [
    { type: '分类一', value: 27 },
    { type: '分类二', value: 25 },
    { type: '分类三', value: 18 },
    { type: '分类四', value: 15 },
    { type: '分类五', value: 10 },
    { type: '其他',   value: 5  },
  ],
  encode: {
    y: 'value',       // 映射数值字段(决定扇区角度大小)
    color: 'type',    // 映射分类字段(决定扇区颜色)
  },
  transform: [{ type: 'stackY' }],   // 必须:将 y 值转换为角度区间
  coordinate: { type: 'theta', outerRadius: 0.8 },
  legend: {
    color: { position: 'bottom', layout: { justifyContent: 'center' } },
  },
  labels: [
    {
      text: (d) => `${d.type}\n${d.value}`,
      position: 'outside',
      connector: true,
    },
  ],
});

chart.render();
```

## 带百分比标签的饼图

```javascript
import { Chart } from '@antv/g2';

const data = [
  { type: '分类一', value: 27 },
  { type: '分类二', value: 25 },
  { type: '分类三', value: 18 },
  { type: '分类四', value: 15 },
  { type: '其他',   value: 15 },
];
const total = data.reduce((sum, d) => sum + d.value, 0);

const chart = new Chart({ container: 'container', width: 600, height: 480 });

chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: { type: 'theta', outerRadius: 0.8 },
  labels: [
    {
      text: (d) => `${((d.value / total) * 100).toFixed(1)}%`,
      position: 'inside',
      style: { fill: 'white', fontSize: 12, fontWeight: 'bold' },
    },
  ],
});

chart.render();
```

## 环形图(Donut)

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: {
    type: 'theta',
    outerRadius: 0.8,
    innerRadius: 0.5,    // 设置内径即为环形图
  },
});
```

## 玫瑰图(极坐标柱状图)

```javascript
// 极坐标下每个扇区角度相同,半径由数值决定
chart.options({
  type: 'interval',
  data,
  encode: { x: 'type', y: 'value', color: 'type' },
  coordinate: { type: 'polar' },   // 注意:玫瑰图用 polar,不用 theta
});
```

## 常见错误与修正

### 错误 1:忘记 transform stackY
```javascript
// ❌ 错误:没有 stackY,所有扇形从 0 开始角度,完全重叠
chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'type' },
  coordinate: { type: 'theta' },
  // 缺少 transform!
});

// ✅ 正确:必须声明 stackY
chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],   // 必须!
  coordinate: { type: 'theta' },
});
```

### 错误 2:饼图误用 x 通道
```javascript
// ❌ 错误:theta 坐标系中 x 通道无效,不要在饼图中 encode.x
chart.options({
  type: 'interval',
  encode: { x: 'type', y: 'value' },    // x 在 theta 下没有意义
  coordinate: { type: 'theta' },
});

// ✅ 正确:饼图只需 encode.y(数值)和 encode.color(分类)
chart.options({
  type: 'interval',
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: { type: 'theta' },
});
```

### 错误 3:G2 v4 饼图写法
```javascript
// ❌ 错误(G2 v4 写法)
chart.coord('theta', { radius: 0.75 });
chart.interval().position('value').color('type');

// ✅ 正确(G2 v5 Spec 写法)
chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: { type: 'theta', outerRadius: 0.8 },
});
```
references/marks/g2-mark-area-basic.md
---
id: "g2-mark-area-basic"
title: "G2 基础面积图(Area Mark)"
description: |
  使用 Area Mark 创建面积图,在折线图的基础上填充线下方区域,
  强调数据的量级和趋势。本文采用 Spec 模式,涵盖单系列、渐变填充等用法。

library: "g2"
version: "5.x"
category: "marks"
subcategory: "area"
tags:
  - "面积图"
  - "Area"
  - "area chart"
  - "趋势"
  - "量级"
  - "填充"
  - "spec"

related:
  - "g2-mark-line-basic"
  - "g2-mark-area-stacked"
  - "g2-core-encode-channel"

use_cases:
  - "展示数值随时间的变化趋势,同时强调量级"
  - "叠加折线时作为背景填充"
  - "对比多个系列的总量分布"

anti_patterns:
  - "多系列面积图(无堆叠)时各系列互相遮挡,改用堆叠面积图或折线图"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/area/basic"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'area',
  data: [
    { month: 'Jan', value: 33 },
    { month: 'Feb', value: 78 },
    { month: 'Mar', value: 56 },
    { month: 'Apr', value: 91 },
    { month: 'May', value: 67 },
    { month: 'Jun', value: 45 },
  ],
  encode: { x: 'month', y: 'value' },
});

chart.render();
```

## 渐变填充面积图

```javascript
chart.options({
  type: 'area',
  data,
  encode: { x: 'month', y: 'value' },
  style: {
    fill: 'linear-gradient(180deg, #1890ff 0%, rgba(24,144,255,0.1) 100%)',
    fillOpacity: 0.8,
  },
});
```

## 面积图 + 折线(叠加)

```javascript
// 面积提供背景量感,折线提供精确走势
chart.options({
  type: 'view',
  data,
  children: [
    {
      type: 'area',
      encode: { x: 'month', y: 'value' },
      style: { fillOpacity: 0.2, fill: '#1890ff' },
    },
    {
      type: 'line',
      encode: { x: 'month', y: 'value' },
      style: { stroke: '#1890ff', lineWidth: 2 },
    },
    {
      type: 'point',
      encode: { x: 'month', y: 'value', shape: 'circle' },
      style: { fill: '#1890ff', r: 4 },
    },
  ],
});
```

## 平滑曲线面积图

```javascript
chart.options({
  type: 'area',
  data,
  encode: {
    x: 'month',
    y: 'value',
    shape: 'smooth',    // 平滑插值
  },
  style: { fillOpacity: 0.6 },
});
```

## 时间序列面积图

```javascript
chart.options({
  type: 'area',
  data: [
    { date: new Date('2024-01'), value: 100 },
    { date: new Date('2024-02'), value: 130 },
    { date: new Date('2024-03'), value: 90  },
    { date: new Date('2024-04'), value: 160 },
    { date: new Date('2024-05'), value: 145 },
  ],
  encode: { x: 'date', y: 'value' },
  axis: {
    x: { labelFormatter: 'YYYY-MM' },
  },
});
```

## 常见错误与修正

### 错误 1:在 area mark 上使用 stroke + lineWidth 描边

```javascript
// ❌ 错误:stroke + lineWidth 会包裹整个填充区域(底部、两侧都描边),
// 而不是仅顶部边缘线
chart.options({
  type: 'area',
  data,
  encode: { x: 'date', y: 'value' },
  style: {
    fill: '#FF5924',
    fillOpacity: 0.4,
    stroke: '#FF5924',      // ❌ 描边包裹整个区域
    lineWidth: 2,            // ❌
  },
});

// ✅ 正确:用 view + children 叠加 area(填充)+ line(顶部边缘线)
chart.options({
  type: 'view',
  data,
  children: [
    {
      type: 'area',
      encode: { x: 'date', y: 'value' },
      style: { fill: '#FF5924', fillOpacity: 0.4 },
    },
    {
      type: 'line',
      encode: { x: 'date', y: 'value' },
      style: { stroke: '#FF5924', lineWidth: 2 },
    },
  ],
});
```

### 错误 2:多系列面积图不加 stackY 导致互相遮挡
```javascript
// ❌ 问题:多系列面积相互覆盖,后面的系列遮挡前面的
chart.options({
  type: 'area',
  data: multiSeriesData,
  encode: { x: 'month', y: 'value', color: 'type' },
  // 没有 stackY,各系列从 y=0 开始叠加,互相遮盖
});

// ✅ 方案 1:堆叠面积图(见 g2-mark-area-stacked)
chart.options({
  type: 'area',
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
});

// ✅ 方案 2:改用折线图对比多系列
chart.options({
  type: 'line',
  encode: { x: 'month', y: 'value', color: 'type' },
});
```
references/marks/g2-mark-area-stacked.md
---
id: "g2-mark-area-stacked"
title: "G2 堆叠面积图"
description: |
  使用 Area Mark 配合 stackY Transform 创建堆叠面积图,
  同时展示各系列的变化趋势和总量的积累效果,各系列的面积从上一个系列顶端开始填充。

library: "g2"
version: "5.x"
category: "marks"
subcategory: "area"
tags:
  - "堆叠面积图"
  - "stacked area"
  - "stackY"
  - "多系列"
  - "趋势"
  - "总量"
  - "spec"

related:
  - "g2-mark-area-basic"
  - "g2-transform-stacky"
  - "g2-mark-interval-stacked"

use_cases:
  - "展示多个系列的总量随时间的变化"
  - "同时关注各系列趋势和总体规模"
  - "流量来源、收入构成等场景"

anti_patterns:
  - "系列超过 5 个时颜色难以区分"
  - "需要精确对比单个系列的变化时(基准线不统一),改用折线图"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/area/stacked"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'area',
  data: [
    { month: 'Jan', type: 'A', value: 100 },
    { month: 'Jan', type: 'B', value: 200 },
    { month: 'Jan', type: 'C', value: 150 },
    { month: 'Feb', type: 'A', value: 120 },
    { month: 'Feb', type: 'B', value: 180 },
    { month: 'Feb', type: 'C', value: 160 },
    { month: 'Mar', type: 'A', value: 90  },
    { month: 'Mar', type: 'B', value: 220 },
    { month: 'Mar', type: 'C', value: 130 },
  ],
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
});

chart.render();
```

## 平滑堆叠面积图

```javascript
chart.options({
  type: 'area',
  data,
  encode: {
    x: 'month',
    y: 'value',
    color: 'type',
    shape: 'smooth',
  },
  transform: [{ type: 'stackY' }],
  style: { fillOpacity: 0.85 },
});
```

## 堆叠面积 + 折线描边

```javascript
chart.options({
  type: 'view',
  data,
  children: [
    {
      type: 'area',
      encode: { x: 'month', y: 'value', color: 'type' },
      transform: [{ type: 'stackY' }],
      style: { fillOpacity: 0.7 },
    },
    {
      type: 'line',
      encode: { x: 'month', y: 'value', color: 'type', series: 'type' },
      transform: [{ type: 'stackY' }],
      style: { lineWidth: 1.5 },
    },
  ],
});
```

## 百分比堆叠面积图

```javascript
chart.options({
  type: 'area',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [
    { type: 'stackY' },
    { type: 'normalizeY' },
  ],
  axis: {
    y: { labelFormatter: (v) => `${(v * 100).toFixed(0)}%` },
  },
});
```

## 常见错误与修正

### 错误:忘记 stackY 导致系列互相遮挡
```javascript
// ❌ 错误:各系列面积都从 y=0 起,相互覆盖
chart.options({
  type: 'area',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  // 没有 transform!
});

// ✅ 正确
chart.options({
  type: 'area',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
});
```
references/marks/g2-mark-beeswarm.md
---
id: "g2-mark-beeswarm"
title: "G2 蜂群图(beeswarm)"
description: |
  beeswarm mark 将散点沿分类轴自动排布避免重叠,形如蜂巢,
  每个点紧密排列但互不遮挡。适合展示分类变量下单维数值分布。
  与 jitter transform 的随机偏移不同,beeswarm 使用力导向算法精确排布。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "beeswarm"
  - "蜂群图"
  - "点分布"
  - "无重叠散点"
  - "分布图"

related:
  - "g2-mark-point-scatter"
  - "g2-transform-jitter"
  - "g2-mark-box-boxplot"

use_cases:
  - "展示各类别下数据点的精确分布(无重叠)"
  - "与箱线图叠加使用,同时显示摘要和原始数据"
  - "小样本数据的精确分布展示"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/point/#beeswarm"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const data = [
  { dept: '研发', salary: 18000 }, { dept: '研发', salary: 22000 },
  { dept: '研发', salary: 15000 }, { dept: '研发', salary: 25000 },
  { dept: '研发', salary: 19000 }, { dept: '研发', salary: 21000 },
  { dept: '销售', salary: 12000 }, { dept: '销售', salary: 16000 },
  { dept: '销售', salary: 14000 }, { dept: '销售', salary: 11000 },
  { dept: '设计', salary: 17000 }, { dept: '设计', salary: 20000 },
  { dept: '设计', salary: 18500 }, { dept: '设计', salary: 23000 },
];

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'point',
  data,
  encode: {
    x: 'dept',
    y: 'salary',
    color: 'dept',
    shape: 'point',
  },
  // beeswarm 布局通过 layout 配置,而不是独立的 mark type
  // 实际上是 point mark + 蜂群布局变换
  style: { r: 5, fillOpacity: 0.8 },
  // 用 jitter transform 近似蜂群效果(或使用 beeswarm data 变换)
  transform: [{ type: 'jitter', padding: 0.1 }],
});

chart.render();
```

## 使用 beeswarm mark(独立类型)

```javascript
// G2 v5 也支持 type: 'beeswarm' 直接使用蜂群布局
const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'point',
  data,
  encode: {
    x: 'dept',
    y: 'salary',
    color: 'dept',
  },
  // beeswarm 通过力导向算法排布,点不重叠
  style: { r: 4, fillOpacity: 0.75 },
  layout: {
    type: 'beeswarm',   // 使用蜂群布局
    padding: 1,         // 点间距
  },
});
```

## 与箱线图叠加

```javascript
chart.options({
  type: 'view',
  data,
  children: [
    {
      type: 'boxplot',
      encode: { x: 'dept', y: 'salary' },
      style: { boxFill: 'transparent', boxStroke: '#999', lineWidth: 1.5 },
    },
    {
      type: 'point',
      encode: { x: 'dept', y: 'salary', color: 'dept' },
      transform: [{ type: 'jitter', padding: 0.1 }],
      style: { r: 3.5, fillOpacity: 0.65 },
    },
  ],
});
```

## 常见错误与修正

### 错误:数据量太大用 beeswarm——布局计算慢且视觉拥挤
```javascript
// ❌ 千条以上数据用蜂群图会很慢且视觉饱和
chart.options({
  data: tenThousandRows,   // ❌ 数据太多
  transform: [{ type: 'jitter' }],
});

// ✅ 大数据量改用密度图或带颜色的散点图
// beeswarm 适合 < 500 条数据
chart.options({
  data: smallSample,
  transform: [{ type: 'jitter', padding: 0.08 }],  // ✅ 小样本
});
```
references/marks/g2-mark-bi-directional-bar.md
---
id: "g2-mark-bi-directional-bar"
title: "G2 Bi-Directional Bar Mark"
description: |
  双向柱状图 Mark。使用 interval 标记展示正向和反向的数据对比。
  适用于正负数据对比、收入支出对比、完成/未完成对比等场景。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "双向柱状图"
  - "正负条形图"
  - "bi-directional"
  - "对比"
  - "人口金字塔"
  - "butterfly chart"
  - "对称条形图"

related:
  - "g2-mark-interval-basic"
  - "g2-mark-interval-stacked"

use_cases:
  - "正负分类数据对比"
  - "收入支出对比"
  - "完成/未完成对比"
  - "人口金字塔(男女对比)"
  - "butterfly chart(左右对称条形图)"

anti_patterns:
  - "不含相反含义的数据不适合"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/mark/bi-directional-bar"
---

## 核心概念

双向柱状图展示正向和反向的数据对比:
- 使用 `interval` 标记
- 通过负值表示反向数据
- 配合 `transpose` 坐标变换

**适用场景:**
- 完成/未完成对比
- 收入/支出对比
- 正负数据对比

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
});

const data = [
  { department: '部门1', people: 37, type: 'completed' },
  { department: '部门1', people: 9, type: 'uncompleted' },
  { department: '部门2', people: 27, type: 'completed' },
  { department: '部门2', people: 10, type: 'uncompleted' },
];

chart.options({
  type: 'interval',
  coordinate: { transform: [{ type: 'transpose' }] },
  data,
  encode: {
    x: 'department',
    y: (d) => (d.type === 'completed' ? d.people : -d.people),
    color: 'department',
  },
  style: {
    fill: ({ type }) => type === 'uncompleted' ? 'transparent' : undefined,
    stroke: ({ type }) => type === 'uncompleted' ? '#1890ff' : undefined,
    lineWidth: 2,
  },
});

chart.render();
```

## 常用变体

### 堆叠双向柱状图

```javascript
chart.options({
  type: 'interval',
  coordinate: { transform: [{ type: 'transpose' }] },
  data,
  transform: [{ type: 'stackY' }],
  encode: {
    x: 'question',
    y: (d) =>
      d.type === 'Disagree' || d.type === 'Strongly disagree'
        ? -d.percentage
        : d.percentage,
    color: 'type',
  },
});
```

### 自定义 Y 轴标签

```javascript
chart.options({
  type: 'interval',
  coordinate: { transform: [{ type: 'transpose' }] },
  data,
  encode: { x: 'category', y: (d) => d.type === 'A' ? d.value : -d.value },
  axis: {
    y: {
      labelFormatter: (d) => Math.abs(d),  // 显示绝对值
    },
  },
});
```

### 分组显示

```javascript
chart.options({
  type: 'interval',
  coordinate: { transform: [{ type: 'transpose' }] },
  data,
  encode: {
    x: 'group',
    y: (d) => d.direction === 'forward' ? d.value : -d.value,
    color: 'category',
  },
  style: {
    maxWidth: 20,
  },
});
```

## 完整类型参考

```typescript
interface BiDirectionalData {
  category: string;      // 分类字段
  value: number;         // 数值
  direction: 'forward' | 'backward';  // 方向
}

interface BiDirectionalOptions {
  type: 'interval';
  coordinate: {
    transform: [{ type: 'transpose' }];
  };
  encode: {
    x: string;           // 分类字段
    y: (d) => number;    // 根据方向返回正/负值
    color?: string;
  };
}
```

## 双向柱状图 vs 柱状图

| 特性 | 双向柱状图 | 柱状图 |
|------|------------|--------|
| 数据方向 | 正反两个方向 | 单一方向 |
| 用途 | 对比相反含义 | 数值对比 |
| 视觉效果 | 双向对称 | 单向 |

## 人口金字塔(butterfly chart)

人口金字塔是双向柱状图的典型场景——男女两侧数据方向相反,通过负值技巧实现,**无需 `createView`**。

```javascript
const data = [
  { age: '0-4',   male: 5.3, female: 5.1 },
  { age: '5-9',   male: 5.6, female: 5.4 },
  { age: '10-14', male: 5.8, female: 5.5 },
  // ...
];

// 宽表转长表:将 male/female 合并为一列
const longData = data.flatMap((d) => [
  { age: d.age, sex: 'Male',   population: d.male },
  { age: d.age, sex: 'Female', population: d.female },
]);

chart.options({
  type: 'interval',
  data: longData,
  coordinate: { transform: [{ type: 'transpose' }] },  // 横向条形图
  encode: {
    x: 'age',
    // 关键:男性用负值,女性用正值 → 形成左右对称
    y: (d) => d.sex === 'Male' ? -d.population : d.population,
    color: 'sex',
  },
  axis: {
    y: {
      labelFormatter: (d) => Math.abs(d),  // 显示绝对值(不显示负号)
      title: '人口占比 (%)',
    },
    x: { title: '年龄段' },
  },
  scale: {
    color: { range: ['#5B8FF9', '#FF7875'] },
  },
});
```

## 常见错误与修正

### 错误 1:缺少负值转换

```javascript
// ❌ 问题:所有值都是正值
encode: { y: 'value' }

// ✅ 正确:根据类型返回正/负值
encode: { y: (d) => d.type === 'A' ? d.value : -d.value }
```

### 错误 2:缺少 transpose

```javascript
// ❌ 问题:默认是垂直方向
coordinate: {}

// ✅ 正确:添加 transpose
coordinate: { transform: [{ type: 'transpose' }] }
```

### 错误 3:Y 轴标签显示负值

```javascript
// ❌ 问题:负值显示为负数
axis: {}

// ✅ 正确:格式化为绝对值
axis: {
  y: { labelFormatter: (d) => Math.abs(d) },
}
```

### 错误 4:用 `chart.createView()` 实现人口金字塔

这是最常见的错误——V4 时代用 `createView` 创建左右两个独立视图,V5 已移除此 API。正确做法是**负值技巧**(单一 `interval` + 负值编码)或 `spaceLayer`。

```javascript
// ❌ 禁止:V4 createView,V5 中不存在
const leftView = chart.createView();
leftView.options({
  type: 'interval',
  data: usData,
  encode: { x: 'age', y: 'male' },
});
const rightView = chart.createView();
rightView.options({ ... });

// ✅ 方案一(推荐):负值技巧——单一 interval,男性取负值
chart.options({
  type: 'interval',
  data: combinedData,   // male/female 合并到一个数组
  coordinate: { transform: [{ type: 'transpose' }] },
  encode: {
    x: 'age',
    y: (d) => d.sex === 'Male' ? -d.population : d.population,
    color: 'sex',
  },
  axis: { y: { labelFormatter: (d) => Math.abs(d) } },
});

// ✅ 方案二:spaceLayer(两侧需完全独立比例尺时使用)
chart.options({
  type: 'spaceLayer',
  children: [
    {
      type: 'interval',
      data: leftData,
      coordinate: { transform: [{ type: 'transpose' }, { type: 'reflectX' }] },
      encode: { x: 'age', y: 'male' },
      axis: { y: { position: 'right' } },
    },
    {
      type: 'interval',
      data: rightData,
      coordinate: { transform: [{ type: 'transpose' }] },
      encode: { x: 'age', y: 'female' },
      axis: { y: false },
    },
  ],
});
```
references/marks/g2-mark-box-boxplot.md
---
id: "g2-mark-box-boxplot"
title: "G2 箱线图(Box Mark)"
description: |
  使用 Box Mark 创建箱线图(又称盒须图),展示数据的分位数分布:
  最小值、Q1(25%分位)、中位数、Q3(75%分位)、最大值及异常值。
  本文采用 Spec 模式。

library: "g2"
version: "5.x"
category: "marks"
subcategory: "box"
tags:
  - "箱线图"
  - "盒须图"
  - "Box"
  - "boxplot"
  - "分布"
  - "分位数"
  - "异常值"
  - "spec"

related:
  - "g2-mark-point-scatter"
  - "g2-core-encode-channel"

use_cases:
  - "展示数值数据的分布形态和离散程度"
  - "对比多个分类的数据分布差异"
  - "识别异常值(outliers)"

anti_patterns:
  - "数据量极少(< 5 个点)时箱线图无统计意义"
  - "需要展示具体数据点分布时,改用小提琴图或散点图"

difficulty: "intermediate"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/statistics/boxplot"
---

## 核心概念

Box Mark 需要 5 个数值通道:
- `y`:中位数(Q2)
- `y1`:Q1(25% 分位数)
- `y2`:Q3(75% 分位数)
- `y3`:下须(最小非异常值)
- `y4`:上须(最大非异常值)

**数据格式**:数据需预先计算分位数后传入,或使用原始数据配合 `boxplot` transform 自动计算。

## 使用 boxplot transform 自动计算(推荐)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

// 原始数据,每个分类有多个观测值
const rawData = [
  { category: 'A', value: 10 },
  { category: 'A', value: 25 },
  { category: 'A', value: 30 },
  { category: 'A', value: 45 },
  { category: 'A', value: 50 },
  { category: 'A', value: 55 },
  { category: 'A', value: 80 },   // 异常值
  { category: 'B', value: 20 },
  { category: 'B', value: 35 },
  { category: 'B', value: 40 },
  { category: 'B', value: 48 },
  { category: 'B', value: 52 },
  { category: 'B', value: 65 },
];

chart.options({
  type: 'boxplot',          // boxplot 是 box mark + boxplot transform 的组合快捷方式
  data: rawData,
  encode: {
    x: 'category',
    y: 'value',
  },
  style: {
    fill: '#1890ff',
    fillOpacity: 0.3,
    stroke: '#1890ff',
  },
});

chart.render();
```

## 预计算分位数数据

```javascript
// 数据已包含分位数字段
chart.options({
  type: 'box',
  data: [
    { category: 'A', min: 10, q1: 25, median: 45, q3: 55, max: 75 },
    { category: 'B', min: 20, q1: 35, median: 48, q3: 58, max: 80 },
    { category: 'C', min: 5,  q1: 20, median: 35, q3: 50, max: 65 },
  ],
  encode: {
    x: 'category',
    y: 'median',     // 中位数
    y1: 'q1',        // 下四分位
    y2: 'q3',        // 上四分位
    y3: 'min',       // 下须
    y4: 'max',       // 上须
  },
  style: {
    fill: '#1890ff',
    fillOpacity: 0.3,
    stroke: '#1890ff',
    lineWidth: 1.5,
  },
});
```

## 箱线图 + 散点(显示原始数据点)

```javascript
chart.options({
  type: 'view',
  data: rawData,
  children: [
    {
      type: 'boxplot',
      encode: { x: 'category', y: 'value' },
      style: { fill: '#1890ff', fillOpacity: 0.2, stroke: '#1890ff' },
    },
    {
      // 叠加原始数据点
      type: 'point',
      encode: { x: 'category', y: 'value' },
      transform: [{ type: 'jitter' }],   // jitter 避免点重叠
      style: { fill: '#1890ff', fillOpacity: 0.5, r: 3 },
    },
  ],
});
```

## 常见错误与修正

### 错误:box mark 缺少 y1/y2/y3/y4 通道
```javascript
// ❌ 错误:box mark 需要 5 个 y 通道,缺少会渲染异常
chart.options({
  type: 'box',
  encode: { x: 'category', y: 'median' },  // 缺少 y1-y4!
});

// ✅ 正确:使用 boxplot(自动计算)或补全所有通道
chart.options({ type: 'boxplot', encode: { x: 'category', y: 'value' } });
```
references/marks/g2-mark-boxplot.md
---
id: "g2-mark-boxplot"
title: "G2 boxplot 自动统计箱线图"
description: |
  boxplot 是 G2 v5 的复合 Mark,自动从原始数据计算 Q1/Q2/Q3/须/离群值,
  直接输入明细数据即可生成标准箱线图,无需手动计算五数摘要。
  与 box mark(需要手动提供 Q1/Q3 等统计值)不同,boxplot 内置统计计算逻辑。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "boxplot"
  - "箱线图"
  - "自动统计"
  - "分布"
  - "Q1"
  - "Q3"
  - "中位数"
  - "离群值"

related:
  - "g2-mark-box-boxplot"
  - "g2-mark-point-scatter"
  - "g2-transform-bin"

use_cases:
  - "直接用明细数据绘制箱线图(无需预计算)"
  - "多组数据分布对比"
  - "展示数据的分布形状和离群值"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/statistics/box/#boxplot"
---

## 与 box mark 的区别

| | `boxplot` | `box` |
|--|-----------|-------|
| 输入数据 | 明细数据(自动计算统计量) | 需要手动提供 Q1/Q3 等字段 |
| 复合性 | 复合 Mark(包含箱体+须+离群值) | 单一 Mark(只绘制箱体) |
| 适用场景 | 大多数场景(推荐) | 数据已预聚合时 |

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'boxplot',
  data: [
    { group: 'A', value: 10 },
    { group: 'A', value: 14 },
    { group: 'A', value: 12 },
    { group: 'A', value: 25 },   // 离群值
    { group: 'A', value: 11 },
    { group: 'A', value: 13 },
    { group: 'B', value: 20 },
    { group: 'B', value: 22 },
    { group: 'B', value: 18 },
    { group: 'B', value: 5 },    // 离群值
    { group: 'B', value: 21 },
  ],
  encode: {
    x: 'group',   // 分组字段
    y: 'value',   // 数值字段(自动计算统计量)
  },
});

chart.render();
```

## 配置样式

```javascript
chart.options({
  type: 'boxplot',
  data,
  encode: {
    x: 'category',
    y: 'score',
    color: 'category',   // 按类别着色
  },
  style: {
    boxFill: '#1890ff',          // 箱体填充色
    boxFillOpacity: 0.3,         // 箱体透明度
    boxStroke: '#1890ff',        // 箱体边框色
    medianStroke: '#ff4d4f',     // 中位数线颜色
    medianLineWidth: 2,          // 中位数线宽
    whiskerStroke: '#666',       // 须线颜色
    outlierFill: '#ff4d4f',      // 离群点颜色
    outlierR: 4,                 // 离群点半径
  },
});
```

## 水平箱线图

```javascript
chart.options({
  type: 'boxplot',
  data,
  encode: {
    x: 'score',      // x 轴为数值
    y: 'category',   // y 轴为分类
  },
  coordinate: { transform: [{ type: 'transpose' }] },
});
```

## 极坐标箱线图

```javascript
chart.options({
  type: 'box',
  data: [
    { x: "Oceania", y: [1, 9, 16, 22, 24] },
    { x: "East Europe", y: [1, 5, 8, 12, 16] },
    { x: "Australia", y: [1, 8, 12, 19, 26] },
    { x: "South America", y: [2, 8, 12, 21, 28] },
    { x: "North Africa", y: [1, 8, 14, 18, 24] },
    { x: "North America", y: [3, 10, 17, 28, 30] },
    { x: "West Europe", y: [1, 7, 10, 17, 22] },
    { x: "West Africa", y: [1, 6, 8, 13, 16] }
  ],
  encode: {
    x: 'x',
    y: 'y', // y 字段本身就是 [min, Q1, median, Q3, max] 数组
    color: 'x' // 用 x (地区) 映射颜色
  },
  coordinate: {
    type: 'polar', // 极坐标
    innerRadius: 0.2 // 可选:设置内半径避免中心过于拥挤
  },
  scale: {
    x: {
      paddingInner: 0.6,
      paddingOuter: 0.3
    },
    y: {
      zero: true
    }
  },
  style: {
    stroke: "black"
  },
  axis: {
    y: {
      tickCount: 5
    }
  },
  tooltip: {
    items: [
      { channel: 'y', name: 'min' },
      { channel: 'y1', name: 'q1' },
      { channel: 'y2', name: 'q2' },
      { channel: 'y3', name: 'q3' },
      { channel: 'y4', name: 'max' }
    ]
  },
  legend: false // 隐藏图例(因颜色与x轴一致)
});
```

## 小提琴图(Violin Shape)

```javascript
chart.options({
  type: 'boxplot',
  data,
  encode: {
    x: 'category',
    y: 'value',
    color: 'category',
    shape: 'violin',  // 设置 shape 为 violin 实现小提琴图效果
  },
  style: {
    opacity: 0.5,
    strokeOpacity: 0.5,
    point: false,     // 隐藏离群点
  },
});
```

## 常见错误与修正

### 错误:用 box 替代 boxplot 但不提供统计字段
```javascript
// ❌ 错误:box mark 需要手动提供 Q1/median/Q3/min/max 字段
chart.options({
  type: 'box',
  data: rawDetailData,   // 原始明细数据
  encode: { x: 'group', y: 'value' },  // ❌ box 需要 y 为 [min, Q1, median, Q3, max]
});

// ✅ 使用原始明细数据时,应该用 boxplot(自动计算统计量)
chart.options({
  type: 'boxplot',
  data: rawDetailData,
  encode: { x: 'group', y: 'value' },  // ✅ boxplot 自动计算
});
```

### 错误:绘制小提琴图时未正确组合 density 和 boxplot
```javascript
// ❌ 错误:单独使用 boxplot 并设置 shape: 'violin' 无法实现真正的密度轮廓
chart.options({
  type: 'view',
  data,
  children: [
    {
      type: 'boxplot',
      encode: {
        x: 'x',
        y: 'y',
        color: 'species',
        shape: 'violin',
      },
      style: {
        opacity: 0.5,
        strokeOpacity: 0.5,
        point: false,
      },
    },
  ],
});

// ✅ 正确做法:使用 density + boxplot 组合实现小提琴图
chart.options({
  type: 'view',
  data,
  children: [
    // 密度估计曲线 (KDE)
    {
      type: 'density',
      data: {
        transform: [
          {
            type: 'kde',
            field: 'y',
            groupBy: ['x', 'species'],
          },
        ],
      },
      encode: {
        x: 'x',
        y: 'y',
        color: 'species',
        size: 'size',
        series: 'species',
      },
      style: {
        fillOpacity: 0.7,
      },
      tooltip: false,
    },
    // 小提琴形状的箱线图(仅显示统计信息)
    {
      type: 'boxplot',
      encode: {
        x: 'x',
        y: 'y',
        color: 'species',
        shape: 'violin',
      },
      style: {
        opacity: 0.8,
        strokeOpacity: 0.6,
        point: false,
      },
    },
  ],
});
```

### 错误:极坐标箱线图使用 boxplot 而不是 box
```javascript
// ❌ 错误:使用 boxplot 处理已聚合的五数概括数据
chart.options({
  type: 'boxplot',
  data: [
    { x: "Oceania", y: [1, 9, 16, 22, 24] },
    { x: "East Europe", y: [1, 5, 8, 12, 16] }
  ],
  encode: { x: 'x', y: 'y' }
});

// ✅ 正确:使用 box mark 处理已聚合的五数概括数据
chart.options({
  type: 'box',
  data: [
    { x: "Oceania", y: [1, 9, 16, 22, 24] },
    { x: "East Europe", y: [1, 5, 8, 12, 16] }
  ],
  encode: { x: 'x', y: 'y' }
});
```

### 错误:tooltip items 配置不正确
```javascript
// ❌ 错误:tooltip items 中使用不存在的 channel 名称
chart.options({
  type: 'box',
  data,
  encode: { x: 'x', y: 'y' },
  tooltip: {
    items: [
      { channel: 'y0', name: 'min' }, // 错误!y0 不是字段名而是通道名
      { channel: 'y1', name: 'Q1' },
      { channel: 'y2', name: 'median' },
      { channel: 'y3', name: 'Q3' },
      { channel: 'y4', name: 'max' }
    ]
  }
});

// ✅ 正确:使用正确的 channel 名称
chart.options({
  type: 'box',
  data,
  encode: { x: 'x', y: 'y' },
  tooltip: {
    items: [
      { channel: 'y', name: 'min' },
      { channel: 'y1', name: 'q1' },
      { channel: 'y2', name: 'q2' },
      { channel: 'y3', name: 'q3' },
      { channel: 'y4', name: 'max' }
    ]
  }
});
```
</skill>
```
references/marks/g2-mark-bullet.md
---
id: "g2-mark-bullet"
title: "G2 Bullet Chart Mark"
description: |
  子弹图 Mark。使用 view 组合 interval 和 point 实现,展示实际值与目标值的对比。
  适用于业绩监控、KPI 展示、进度跟踪等场景。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "子弹图"
  - "bullet"
  - "KPI"
  - "进度"

related:
  - "g2-mark-interval-basic"
  - "g2-mark-gauge"

use_cases:
  - "业绩指标监控"
  - "KPI 仪表盘"
  - "预算执行跟踪"

anti_patterns:
  - "时间趋势分析应使用折线图"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/mark/bullet"
---

## 核心概念

子弹图(Bullet Chart)是一种紧凑的指标展示图表,同时显示:
- **实际值条形**:当前实际达到的数值
- **目标值标记**:需要达成的目标
- **表现区间**:背景色带表示差/良/优等级

**适用场景:**
- 仪表盘 KPI 展示
- 业绩指标监控
- 资源利用率监控

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  theme: 'classic',
});

const data = [
  { title: '销售完成率', ranges: 100, measures: 80, target: 85 },
];

chart.options({
  type: 'view',
  coordinate: { transform: [{ type: 'transpose' }] },
  children: [
    {
      type: 'interval',
      data,
      encode: { x: 'title', y: 'ranges', color: '#f0efff' },
      style: { maxWidth: 30 },
    },
    {
      type: 'interval',
      data,
      encode: { x: 'title', y: 'measures', color: '#5B8FF9' },
      style: { maxWidth: 20 },
    },
    {
      type: 'point',
      data,
      encode: {
        x: 'title',
        y: 'target',
        shape: 'line',
        color: '#3D76DD',
        size: 8,
      },
    },
  ],
});

chart.render();
```

## 常用变体

### 多指标子弹图

```javascript
const multiData = [
  { metric: 'CPU使用率', ranges: 100, measures: 65, target: 80 },
  { metric: '内存使用率', ranges: 100, measures: 45, target: 70 },
  { metric: '磁盘使用率', ranges: 100, measures: 88, target: 85 },
];

chart.options({
  type: 'view',
  coordinate: { transform: [{ type: 'transpose' }] },
  children: [
    { type: 'interval', data: multiData, encode: { x: 'metric', y: 'ranges', color: '#f5f5f5' } },
    { type: 'interval', data: multiData, encode: { x: 'metric', y: 'measures', color: '#52c41a' } },
    { type: 'point', data: multiData, encode: { x: 'metric', y: 'target', shape: 'line', size: 6 } },
  ],
});
```

### 带表现区间

```javascript
const transformedData = [
  { title: '项目进度', value: 40, level: '差' },
  { title: '项目进度', value: 30, level: '良' },
  { title: '项目进度', value: 30, level: '优' },
];

chart.options({
  type: 'view',
  coordinate: { transform: [{ type: 'transpose' }] },
  children: [
    {
      type: 'interval',
      data: transformedData,
      encode: { x: 'title', y: 'value', color: 'level' },
      transform: [{ type: 'stackY' }],
      scale: {
        color: { domain: ['差', '良', '优'], range: ['#ffebee', '#fff3e0', '#e8f5e8'] },
      },
    },
    // ... 实际值和目标值
  ],
});
```

### 垂直子弹图

```javascript
chart.options({
  type: 'view',
  // 不使用 transpose
  children: [
    { type: 'interval', data, encode: { x: 'metric', y: 'ranges', color: '#f0f0f0' } },
    { type: 'interval', data, encode: { x: 'metric', y: 'measures', color: '#52c41a' } },
    { type: 'point', data, encode: { x: 'metric', y: 'target', shape: 'line', size: 6 } },
  ],
});
```

## 完整类型参考

```typescript
interface BulletData {
  title: string;      // 指标名称
  ranges: number;     // 背景范围(通常为 100)
  measures: number;   // 实际值
  target: number;     // 目标值
}

// 子弹图由三个图层组成:
// 1. interval - 背景区间
// 2. interval - 实际值条形
// 3. point (shape: 'line') - 目标值标记
```

## 子弹图 vs 仪表盘

| 特性 | 子弹图 | 仪表盘 |
|------|--------|--------|
| 空间占用 | 紧凑 | 较大 |
| 信息量 | 多指标 | 单指标 |
| 适用场景 | 仪表盘 | 大屏展示 |

## 常见错误与修正

### 错误 1:缺少 transpose

```javascript
// ❌ 问题:默认是垂直方向
coordinate: {}

// ✅ 正确:水平子弹图需要 transpose
coordinate: { transform: [{ type: 'transpose' }] }
```

### 错误 2:目标值标记不明显

```javascript
// ❌ 问题:目标值使用默认 point 形状
encode: { shape: 'point' }

// ✅ 正确:使用 line 形状
encode: { shape: 'line', size: 8 }
```
references/marks/g2-mark-cell-heatmap.md
---
id: "g2-mark-cell-heatmap"
title: "G2 热力图(Cell Mark)"
description: |
  使用 Cell Mark 创建矩阵热力图,通过颜色深浅表示两个分类维度交叉点的数值大小,
  常用于相关性分析、时间-类别分布等场景。本文采用 Spec 模式。

library: "g2"
version: "5.x"
category: "marks"
subcategory: "cell"
tags:
  - "热力图"
  - "Cell"
  - "heatmap"
  - "矩阵"
  - "相关性"
  - "颜色映射"
  - "spec"

related:
  - "g2-core-encode-channel"
  - "g2-scale-sequential"
  - "g2-comp-legend-config"

use_cases:
  - "展示两个分类维度的交叉数值(如相关矩阵)"
  - "时间热力图(如每周各天的活跃度)"
  - "用户行为矩阵分析"

anti_patterns:
  - "数据为连续型 x/y 时改用密度图或等值线图"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/heatmap/basic"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'cell',
  data: [
    { week: 'Mon', hour: '6AM',  value: 10 },
    { week: 'Mon', hour: '12PM', value: 80 },
    { week: 'Mon', hour: '6PM',  value: 60 },
    { week: 'Tue', hour: '6AM',  value: 5  },
    { week: 'Tue', hour: '12PM', value: 95 },
    { week: 'Tue', hour: '6PM',  value: 70 },
    { week: 'Wed', hour: '6AM',  value: 20 },
    { week: 'Wed', hour: '12PM', value: 75 },
    { week: 'Wed', hour: '6PM',  value: 55 },
  ],
  encode: {
    x: 'week',
    y: 'hour',
    color: 'value',    // 颜色深浅表示数值大小
  },
  scale: {
    color: { 
      type: 'sequential',   // 明确指定为顺序色阶
      palette: 'YlOrRd'     // 连续色阶:YlOrRd | Blues | Viridis 等
    },
  },
  style: {
    inset: 1,    // 格子间距(px)
  },
});

chart.render();
```

## 带数值标签的热力图

```javascript
chart.options({
  type: 'cell',
  data,
  encode: { x: 'week', y: 'hour', color: 'value' },
  scale: {
    color: { type: 'sequential', palette: 'Blues' },
  },
  labels: [
    {
      text: 'value',
      style: {
        fontSize: 11,
        fill: (d) => d.value > 60 ? 'white' : '#333',  // 深色背景用白字
      },
    },
  ],
  style: { inset: 2 },
});
```

## 相关系数矩阵

```javascript
// 相关性分析热力图(-1 到 1 的发散色阶)
chart.options({
  type: 'cell',
  data: correlationData,  // [{ x: '变量A', y: '变量B', corr: 0.75 }, ...]
  encode: {
    x: 'x',
    y: 'y',
    color: 'corr',
  },
  scale: {
    color: {
      type: 'sequential',  // 明确指定为顺序色阶
      palette: 'RdBu',     // 发散色阶:红-白-蓝
      domain: [-1, 1],     // 固定数值范围
    },
  },
  labels: [
    {
      text: (d) => d.corr.toFixed(2),
      style: { fontSize: 10 },
    },
  ],
});
```

## 日历热力图(GitHub 风格)

```javascript
// 每天活跃度的日历视图
chart.options({
  type: 'cell',
  data: dailyData,   // [{ date: '2024-01-01', weekday: 'Mon', week: 1, value: 5 }, ...]
  encode: {
    x: 'week',      // 第几周(1-53)
    y: 'weekday',   // 周几
    color: 'value',
  },
  scale: {
    color: { type: 'sequential', palette: 'Greens', domain: [0, 20] },
    y: {
      domain: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
    },
  },
  style: { inset: 2, radius: 2 },
  axis: {
    y: { title: null },
    x: { title: null, tickCount: 4 },
  },
});
```

## 地形高程热力图

```javascript
// 模拟地形高程数据
const terrainData = [];
for (let x = 0; x <= 50; x += 2) {
  for (let y = 0; y <= 50; y += 2) {
    // 模拟山峰地形:两个山峰的高程分布
    const elevation1 = 100 * Math.exp(-((x - 15) ** 2 + (y - 15) ** 2) / 200);
    const elevation2 = 80 * Math.exp(-((x - 35) ** 2 + (y - 35) ** 2) / 150);
    const elevation = elevation1 + elevation2 + 10; // 基础海拔
    terrainData.push({ x, y, elevation });
  }
}

const chart = new Chart({
  container: 'container',
  autoFit: true,
});

chart.options({
  type: 'cell',
  data: terrainData,
  encode: {
    x: 'x',
    y: 'y',
    color: 'elevation',
  },
  style: {
    stroke: '#333',
    lineWidth: 0.5,
    inset: 0.5,
  },
  scale: {
    color: {
      type: 'sequential',
      palette: 'viridis',
    },
  },
  legend: {
    color: {
      length: 300,
      layout: { justifyContent: 'center' },
      labelFormatter: (value) => `${Math.round(value)}m`,
    },
  },
  tooltip: {
    title: '海拔信息',
    items: [
      { field: 'x', name: '经度' },
      { field: 'y', name: '纬度' },
      {
        field: 'elevation',
        name: '海拔',
        valueFormatter: (value) => `${Math.round(value)}m`,
      },
    ],
  },
});

chart.render();
```

## 常见错误与修正

### 错误 1:color 通道缺少 scale 配置导致离散色
```javascript
// ❌ 问题:color 默认使用离散色阶,不适合连续数值
chart.options({ type: 'cell', encode: { x: 'a', y: 'b', color: 'value' } });
// value 是连续数值,却被映射到离散颜色

// ✅ 正确:指定连续色阶 palette 并明确类型为 sequential
chart.options({
  type: 'cell',
  encode: { x: 'a', y: 'b', color: 'value' },
  scale: { 
    color: { 
      type: 'sequential',   // 明确指定为顺序色阶
      palette: 'Blues'      // 或 'YlOrRd'、'Viridis' 等
    } 
  },
});
```

### 错误 2:格子大小不均匀
```javascript
// ❌ 问题:x/y 轴类别数量差异大时格子变形
// ✅ 解决:设置 Chart 的宽高比接近 x/y 分类数量之比
const chart = new Chart({
  container: 'container',
  width: xCategories.length * 40,    // 每格 40px
  height: yCategories.length * 40,
});
```

### 错误 3:未正确使用 transform.group 导致数据重复或缺失
```javascript
// ❌ 问题:当 x/y 通道存在重复组合时,未使用 group 聚合会导致多个格子重叠或数据丢失
chart.options({
  type: 'cell',
  data: [
    { day: 1, month: 0, temp: 10 },
    { day: 1, month: 0, temp: 15 }, // 同一天同一月有两个温度记录
  ],
  encode: {
    x: 'day',
    y: 'month',
    color: 'temp'
  }
});
// 上述代码可能只显示其中一个值,或出现多个重叠格子

// ✅ 正确:使用 transform.group 对重复数据进行聚合(如取最大值、平均值等)
chart.options({
  type: 'cell',
  data: [
    { day: 1, month: 0, temp: 10 },
    { day: 1, month: 0, temp: 15 },
  ],
  encode: {
    x: 'day',
    y: 'month',
    color: 'temp'
  },
  transform: [{
    type: 'group',
    color: 'max'  // 对相同 x/y 组合的数据,取 temp 的最大值
  }]
});
```

### 错误 4:未正确设置 scale.type 为 sequential 导致颜色映射异常
```javascript
// ❌ 问题:color 通道未显式设置 scale.type 为 'sequential',可能导致颜色映射不符合预期
chart.options({
  type: 'cell',
  encode: { x: 'a', y: 'b', color: 'value' },
  scale: { color: { palette: 'Blues' } } // 仅设置 palette,未设置 type
});

// ✅ 正确:明确指定 scale.type 为 'sequential'
chart.options({
  type: 'cell',
  encode: { x: 'a', y: 'b', color: 'value' },
  scale: { 
    color: { 
      type: 'sequential',  // 明确指定为顺序色阶
      palette: 'Blues' 
    } 
  }
});
```

### 错误 5:调色板名称大小写敏感导致找不到调色板
```javascript
// ❌ 问题:调色板名称大小写不匹配,如 'gnBu' 实际应为 'GnBu'
chart.options({
  type: 'cell',
  data,
  encode: { x: 'day', y: 'month', color: 'temp' },
  scale: {
    color: { type: 'sequential', palette: 'gnBu' } // 小写 g 不符合实际命名
  }
});

// ✅ 正确:使用正确的调色板名称(注意大小写)
chart.options({
  type: 'cell',
  data,
  encode: { x: 'day', y: 'month', color: 'temp' },
  scale: {
    color: { type: 'sequential', palette: 'GnBu' } // 正确的大写 G
  }
});
```

### 错误 6:数据未定义或引用错误
```javascript
// ❌ 问题:使用了未定义的变量 'data'
const processedData = data.map(...);

// ✅ 正确:确保使用的数据变量已正确定义
const rawData = [...];
const processedData = rawData.map(...);
```

### 错误 7:动画配置语法错误
```javascript
// ❌ 问题:animate.enter 应为对象而非字符串或其他类型
chart.options({
  type: 'cell',
  data,
  encode: { x: 'x', y: 'y', color: 'value' },
  animate: 'fadeIn' // 错误的配置方式
});

// ✅ 正确:使用标准的动画配置对象
chart.options({
  type: 'cell',
  data,
  encode: { x: 'x', y: 'y', color: 'value' },
  animate: {
    enter: {
      type: 'fadeIn',
      duration: 1000
    }
  }
});
```
references/marks/g2-mark-chord.md
---
id: "g2-mark-chord"
title: "G2 和弦图(Chord Mark)"
description: |
  使用 Chord Mark 创建和弦图。和弦图用于展示节点之间的流向关系,
  常见于贸易流向、迁移数据、资金流动等场景。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "和弦图"
  - "Chord"
  - "关系图"
  - "流向图"
  - "矩阵可视化"

related:
  - "g2-mark-sankey"
  - "g2-mark-link"
  - "g2-coord-polar"

use_cases:
  - "展示国家/地区间的贸易流向"
  - "可视化人口迁移数据"
  - "分析资金流动关系"
  - "展示部门间的协作关系"

anti_patterns:
  - "节点过多(>20个)时可视化效果差"
  - "不适合展示单向简单关系(改用 Sankey)"
  - "不适合展示层级结构数据"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/relationship/chord"
---

## 核心概念

Chord Mark 是 G2 v5 中用于绘制和弦图的复合标记:
- **节点(Node)**:圆弧上的多边形,表示实体
- **连线(Link)**:连接节点的带状区域,表示流向关系
- **布局**:自动计算节点位置和连线形状

**关键配置:**
- `encode.source`:边的起始节点字段
- `encode.target`:边的目标节点字段
- `encode.value`:边的权重字段
- `layout`:布局配置(节点宽度、间距等)

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

// 和弦图数据:节点 + 边
const data = {
  nodes: [
    { key: 'A', name: '产品A' },
    { key: 'B', name: '产品B' },
    { key: 'C', name: '产品C' },
  ],
  links: [
    { source: 'A', target: 'B', value: 100 },
    { source: 'B', target: 'C', value: 80 },
    { source: 'C', target: 'A', value: 60 },
  ],
};

chart.options({
  type: 'chord',
  data: {
    value: data
  },
  encode: {
    source: 'source',
    target: 'target',
    value: 'value',
  },
});

chart.render();
```

## 常用变体

### 带节点标签

```javascript
chart.options({
  type: 'chord',
  data: {
    value: data
  },
  encode: {
    source: 'source',
    target: 'target',
    value: 'value',
    nodeKey: 'key',  // 节点标识字段
  },
  nodeLabels: [
    { text: 'name', position: 'outside', fontSize: 12 },
  ],
});
```

### 自定义布局

```javascript
chart.options({
  type: 'chord',
  data: {
    value: data
  },
  encode: {
    source: 'source',
    target: 'target',
    value: 'value',
  },
  layout: {
    nodeWidthRatio: 0.05,    // 节点宽度比例 (0, 1)
    nodePaddingRatio: 0.1,   // 节点间距比例 [0, 1)
    sortBy: 'weight',        // 排序方式: 'id' | 'weight' | 'frequency' | null
  },
});
```

### 自定义样式

```javascript
chart.options({
  type: 'chord',
  data: {
    value: data
  },
  encode: {
    source: 'source',
    target: 'target',
    value: 'value',
    nodeColor: 'key',        // 节点颜色映射
    linkColor: 'source',     // 连线颜色映射
  },
  style: {
    node: {
      opacity: 1,
      lineWidth: 1,
    },
    link: {
      opacity: 0.5,
      lineWidth: 1,
    },
  },
});
```

### 带 Tooltip

```javascript
chart.options({
  type: 'chord',
  data: {
    value: data
  },
  encode: {
    source: 'source',
    target: 'target',
    value: 'value',
  },
  tooltip: {
    node: {
      title: '',
      items: [(d) => ({ name: d.key, value: d.value })],
    },
    link: {
      title: '',
      items: [(d) => ({ name: `${d.source} → ${d.target}`, value: d.value })],
    },
  },
});
```

## Spec 完整结构速查

```javascript
chart.options({
  type: 'chord',
  data: {
    // 数据(nodes + links 结构)
    value: {
      nodes: [...],
      links: [...],
    },
  },
  // 通道映射
  encode: {
    source: 'source',        // 边的起始节点
    target: 'target',        // 边的目标节点
    value: 'value',          // 边的权重
    nodeKey: 'key',          // 节点标识字段
    nodeColor: 'key',        // 节点颜色
    linkColor: 'source',     // 连线颜色
  },

  // 布局配置
  layout: {
    nodeWidthRatio: 0.05,
    nodePaddingRatio: 0.1,
    sortBy: null,            // 'id' | 'weight' | 'frequency' | function
  },

  // 样式
  style: {
    node: { opacity: 1, lineWidth: 1 },
    link: { opacity: 0.5, lineWidth: 1 },
    label: { fontSize: 10 },
  },

  // 标签
  nodeLabels: [{ text: 'name', position: 'outside' }],
  linkLabels: [],

  // Tooltip
  tooltip: { ... },

  // 动画
  animate: {
    node: { enter: { type: 'fadeIn' } },
    link: { enter: { type: 'fadeIn' } },
  },
});
```

## 完整类型参考

```typescript
interface ChordSpec {
  type: 'chord';
  data: {
    value: {
      nodes: Array<{ key: string; [key: string]: any }>;
      links: Array<{ source: string; target: string; value: number; [key: string]: any }>;
    };
  }
  encode?: {
    source?: string;
    target?: string;
    value?: string;
    nodeKey?: string;
    nodeColor?: string;
    linkColor?: string;
  };
  layout?: {
    nodeWidthRatio?: number;   // (0, 1), default: 0.05
    nodePaddingRatio?: number; // [0, 1), default: 0.1
    sortBy?: 'id' | 'weight' | 'frequency' | ((data: any) => any) | null;
  };
  style?: {
    node?: { opacity?: number; lineWidth?: number; fill?: string };
    link?: { opacity?: number; lineWidth?: number; fill?: string };
    label?: { fontSize?: number; fill?: string };
  };
  nodeLabels?: LabelOption[];
  linkLabels?: LabelOption[];
  tooltip?: TooltipOption;
  animate?: AnimateOption;
}
```

## 常见错误与修正

### 错误 1:数据格式不正确

```javascript
// ❌ 错误:使用扁平数组
chart.options({
  type: 'chord',
  data: [
    { source: 'A', target: 'B', value: 100 },
  ],
});

// ✅ 正确:使用 nodes + links 结构
chart.options({
  type: 'chord',
  data: {
    value: {
      nodes: [{ key: 'A' }, { key: 'B' }],
      links: [{ source: 'A', target: 'B', value: 100 }],
    }
  },
  encode: { source: 'source', target: 'target', value: 'value' },
});
```

### 错误 2:节点 key 不匹配

```javascript
// ❌ 错误:links 中的 source/target 与 nodes 的 key 不匹配
const data = {
  nodes: [{ key: 'ProductA' }],
  links: [{ source: 'A', target: 'B', value: 100 }],  // 'A' ≠ 'ProductA'
};

// ✅ 正确:确保 key 一致
const data = {
  nodes: [{ key: 'A' }, { key: 'B' }],
  links: [{ source: 'A', target: 'B', value: 100 }],
};
```

### 错误 3:缺少 value 编码

```javascript
// ❌ 错误:没有指定权重字段
chart.options({
  type: 'chord',
  data: {
    value: data
  },
  encode: { source: 'source', target: 'target' },
});

// ✅ 正确:指定 value 字段
chart.options({
  type: 'chord',
  data: {
    value: data
  },
  encode: { source: 'source', target: 'target', value: 'value' },
});
```
references/marks/g2-mark-connector.md
---
id: "g2-mark-connector"
title: "G2 连接器标注(connector)"
description: |
  connector mark 在两点之间绘制带折角的连接线,用于标注图表中两个数据点的关联或差异。
  常用于标注两个柱之间的差值、两个数据点之间的变化,配合 text 或 labels 显示差值标注。
  与 link mark 类似但更偏向标注用途,默认带直角折线。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "connector"
  - "连接器"
  - "标注"
  - "差值标注"
  - "annotation"
  - "折线连接"

related:
  - "g2-mark-link"
  - "g2-mark-linex-liney"
  - "g2-comp-annotation"

use_cases:
  - "标注两个柱状图数值之间的差异"
  - "连接两个数据点并显示差值"
  - "折线图中标注起止点的变化量"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/annotation/connector/"
---

## 最小可运行示例(差值标注)

```javascript
import { Chart } from '@antv/g2';

const data = [
  { month: 'Jan', value: 83 },
  { month: 'Feb', value: 60 },
  { month: 'Mar', value: 95 },
];

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'view',
  children: [
    // 主柱状图
    {
      type: 'interval',
       data,
      encode: { x: 'month', y: 'value', color: 'month' },
    },
    // connector:连接 Jan 和 Mar 两柱,标注差值
    {
      type: 'connector',
       [{ x: 'Jan', y: 83, x1: 'Mar', y1: 95 }],
      encode: {
        x: 'x',
        y: 'y',
        x1: 'x1',
        y1: 'y1',
      },
      labels: [
        {
          text: '+12',
          position: 'top',
          style: { fill: '#52c41a', fontWeight: 'bold' },
        },
      ],
      style: {
        stroke: '#52c41a',
        lineWidth: 1.5,
        offset: 16,   // 连接线相对于数据点的偏移量
      },
    },
  ],
});

chart.render();
```

## 配置项

```javascript
chart.options({
  type: 'connector',
  data: [{ x: 'A', y: 100, x1: 'B', y1: 150 }],
  encode: {
    x: 'x',    // 起点 x(与主图 x 轴对应)
    y: 'y',    // 起点 y
    x1: 'x1',  // 终点 x
    y1: 'y1',  // 终点 y
  },
  style: {
    stroke: '#999',
    lineWidth: 1,
    offset: 16,         // 连接线距离数据点的像素偏移,默认 16
    endMarker: true,    // 是否显示终点标记
    startMarker: false, // 是否显示起点标记
  },
});
```

## 常见错误与修正

### 错误:encode 中只写 x/y,没有 x1/y1——连接线无终点
```javascript
// ❌ 错误:connector 需要起点和终点
chart.options({
  type: 'connector',
  encode: { x: 'x', y: 'y' },   // ❌ 缺少 x1/y1
});

// ✅ 正确:必须同时指定起点和终点
chart.options({
  type: 'connector',
  encode: { x: 'x', y: 'y', x1: 'x1', y1: 'y1' },  // ✅
});
```
references/marks/g2-mark-contourline.md
---
id: "g2-mark-contourline"
title: "G2 等高线图(contour line)"
description: |
  等高线图通过 type: 'cell' 或 type: 'line' 实现,
  用颜色渐变网格或线条展示二维平面上的连续数据分布(如地形高程、温度分布)。
  G2 无内置等高线算法,通常用 cell + sequential 色阶模拟等高线效果。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "等高线图"
  - "contour"
  - "地形图"
  - "热力图"
  - "连续数据"
  - "二维分布"

related:
  - "g2-mark-cell-heatmap"
  - "g2-mark-point-scatter"

use_cases:
  - "地形海拔可视化"
  - "气象数据分布(温度、气压)"
  - "二维连续数据的空间分布"

anti_patterns:
  - "离散分类数据不适合等高线图"
  - "时间序列数据不适合"

difficulty: "intermediate"
completeness: "full"
created: "2025-04-01"
updated: "2025-04-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/contourline"
---

## 核心概念

G2 中等高线图有两种实现方式:

1. **网格色块模拟等高线**:`type: 'cell'` + `sequential` 色阶,颜色深浅代表数值高低
2. **等高线轮廓**:`type: 'line'` + 按数值级别分组,绘制闭合等值线

**网格密度越高,等高线效果越细腻**(需要数据覆盖均匀的网格点)

## 网格色块模拟等高线(最常用)

```javascript
import { Chart } from '@antv/g2';

// 生成地形数据
const terrainData = [];
for (let x = 0; x <= 50; x += 2) {
  for (let y = 0; y <= 50; y += 2) {
    const elevation1 = 100 * Math.exp(-((x - 15) ** 2 + (y - 15) ** 2) / 200);
    const elevation2 = 80 * Math.exp(-((x - 35) ** 2 + (y - 35) ** 2) / 150);
    const elevation = elevation1 + elevation2 + 10;
    terrainData.push({ x, y, elevation });
  }
}

const chart = new Chart({
  container: 'container',
  autoFit: true,
});

chart.options({
  type: 'cell',
  data: terrainData,
  encode: {
    x: 'x',
    y: 'y',
    color: 'elevation',
  },
  style: {
    stroke: '#333',
    lineWidth: 0.5,
    inset: 0.5,
  },
  scale: {
    color: {
      palette: 'viridis',
      type: 'sequential',
    },
  },
  legend: {
    color: {
      length: 300,
      layout: { justifyContent: 'center' },
      labelFormatter: (value) => `${Math.round(value)}m`,
    },
  },
  tooltip: {
    title: '海拔信息',
    items: [
      { field: 'x', name: '经度' },
      { field: 'y', name: '纬度' },
      {
        field: 'elevation',
        name: '海拔',
        valueFormatter: (value) => `${Math.round(value)}m`,
      },
    ],
  },
});

chart.render();
```

## 等高线轮廓(折线实现)

按数值级别预处理数据,每条线绘制一个等值级别:

```javascript
import { Chart } from '@antv/g2';

// 预先计算各等高线级别的点
const generateContourLines = () => {
  const lines = [];
  const levels = [20, 40, 60, 80, 100];

  levels.forEach((level, index) => {
    for (let angle = 0; angle <= 360; angle += 5) {
      const radian = (angle * Math.PI) / 180;
      const baseRadius = 5 + index * 4;
      const radius = baseRadius + Math.sin((angle * Math.PI) / 45) * 2;
      lines.push({
        x: 25 + radius * Math.cos(radian),
        y: 25 + radius * Math.sin(radian),
        level,
        lineId: `line_${level}`,
      });
    }
  });
  return lines;
};

const chart = new Chart({
  container: 'container',
  autoFit: true,
});

chart.options({
  type: 'line',
  data: generateContourLines(),
  encode: {
    x: 'x',
    y: 'y',
    color: 'level',
    series: 'lineId',   // 每条等高线独立成一个系列
  },
  style: {
    lineWidth: 2,
    strokeOpacity: 0.8,
  },
  scale: {
    color: {
      type: 'sequential',
      palette: 'oranges',
    },
  },
  axis: {
    x: { title: '距离 (km)' },
    y: { title: '距离 (km)' },
  },
  legend: {
    color: { title: '海拔高度 (m)' },
  },
});

chart.render();
```

## 常见错误与修正

### 错误 1:data 关键字缺失

```javascript
// ❌ 错误:data 关键字必须写明
chart.options({
  type: 'cell',
  terrainData,   // ❌ 孤立对象字面量,缺少 data: 键
  encode: { x: 'x', y: 'y', color: 'elevation' },
});

// ✅ 正确
chart.options({
  type: 'cell',
  data: terrainData,
  encode: { x: 'x', y: 'y', color: 'elevation' },
});
```

### 错误 2:等高线轮廓缺少 series 分组

```javascript
// ❌ 错误:没有 series,所有等高线点连成一条线
chart.options({
  type: 'line',
  data,
  encode: {
    x: 'x',
    y: 'y',
    color: 'level',
    // ❌ 缺少 series: 'lineId'
  },
});

// ✅ 正确:每条等高线用 series 独立分组
chart.options({
  type: 'line',
  data,
  encode: {
    x: 'x',
    y: 'y',
    color: 'level',
    series: 'lineId',  // ✅ 确保每条线独立绘制
  },
});
```

### 错误 3:色阶类型不匹配

```javascript
// ❌ 错误:连续数据用 ordinal 色阶,颜色过少
scale: { color: { type: 'ordinal' } }  // ❌ 适合离散类别

// ✅ 正确:连续数据用 sequential 色阶
scale: { color: { type: 'sequential', palette: 'viridis' } }  // ✅
```

## cell 等高线与 heatmap 的区别

| 特性 | 等高线 cell | 热力图 heatmap |
|------|------------|--------------|
| 坐标 | 二维均匀网格(x, y 均离散) | 二维均匀网格 |
| 颜色 | sequential 连续渐变 | 通常 sequential |
| 用途 | 地形、连续场分布 | 频率、密度可视化 |
| 数据 | 三维(x, y, z) | 通常频次聚合 |
references/marks/g2-mark-density.md
---
id: "g2-mark-density"
title: "G2 密度图(density)"
description: |
  density mark 通过核密度估计(KDE)将散点分布转换为连续的密度分布曲线或面积图,
  展示数据的概率密度。必须配合 KDE 数据变换(data.transform)预处理,
  适合大量重叠点的分布可视化。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "density"
  - "密度图"
  - "KDE"
  - "分布"
  - "核密度"
  - "violin"

related:
  - "g2-mark-boxplot"
  - "g2-mark-point-scatter"
  - "g2-data-kde"

use_cases:
  - "展示连续数值数据的分布形状"
  - "小提琴图(density + 极坐标 + 对称变换)"
  - "与箱线图对比展示数据分布"

difficulty: "advanced"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-27"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/mark/density"
---

## 核心概念

**density mark 必须配合 KDE 数据变换使用**:

- KDE 是**数据变换(Data Transform)**,配置在 `data.transform` 中
- density mark 需要的 encode 通道:`x`、`y`、`size`、`series`(均必选)

**关键配置结构**:
```javascript
chart.options({
  type: 'density',
  data: {
    type: 'fetch',  // 或 'inline'
    value: '...',
    transform: [{ type: 'kde', field: 'y', groupBy: ['x', 'species'] }],
  },
  encode: {
    x: 'x',
    y: 'y',       // ← KDE 输出字段(默认 'y'),不是原始 field 名!
    size: 'size', // ← KDE 输出字段(默认 'size')
    series: 'species', // 必选:系列分组
  },
});
```

**⚠️ `encode.y` 必须对应 KDE 的输出字段(默认 `'y'`),而非原始字段名**:无论 `field` 叫什么(`'value'`、`'score'` 等),KDE 输出都固定写入 `as` 指定的字段(默认 `['y', 'size']`)。

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  autoFit: true,
});

chart.options({
  type: 'density',
  data: {
    type: 'fetch',
    value: 'https://assets.antv.antgroup.com/g2/species.json',
    transform: [
      {
        type: 'kde',           // KDE 数据变换
        field: 'y',            // 做核密度估计的字段
        groupBy: ['x', 'species'],  // 分组字段
      },
    ],
  },
  encode: {
    x: 'x',
    y: 'y',
    color: 'species',
    size: 'size',      // 必选:映射密度大小
    series: 'species', // 必选:系列分组
  },
  tooltip: false,
});

chart.render();
```

## 分组密度图(多类别对比)

```javascript
chart.options({
  type: 'density',
  data: {
    type: 'fetch',
    value: 'https://assets.antv.antgroup.com/g2/species.json',
    transform: [
      {
        type: 'kde',
        field: 'y',
        groupBy: ['x'],  // 按 x 分组
        size: 20,        // 带宽参数
      },
    ],
  },
  encode: {
    x: 'x',
    y: 'y',
    color: 'x',
    size: 'size',
    series: 'x',
  },
  tooltip: false,
});
```

## 极坐标密度图

```javascript
chart.options({
  type: 'density',
  data: {
    type: 'fetch',
    value: 'https://assets.antv.antgroup.com/g2/species.json',
    transform: [
      { type: 'kde', field: 'y', groupBy: ['x', 'species'] },
    ],
  },
  encode: {
    x: 'x',
    y: 'y',
    color: 'species',
    size: 'size',
    series: 'species',
  },
  coordinate: { type: 'polar' },  // 极坐标系
  tooltip: false,
});
```

## 常见错误与修正

### 错误 1:kde 配置位置错误

```javascript
// ❌ 错误:kde 不是 data.type,而是 data.transform
chart.options({
  type: 'density',
  data: {
    type: 'kde',  // ❌ 错误!kde 不是数据连接器类型
    field: 'value',
  },
});

// ✅ 正确:kde 是数据变换,放在 data.transform 中
chart.options({
  type: 'density',
  data: {
    type: 'fetch',
    value: 'https://example.com/data.json',
    transform: [{ type: 'kde', field: 'y', groupBy: ['x'] }],  // ✅ 正确
  },
});
```

### 错误 2:缺少必选的 encode 通道

```javascript
// ❌ 错误:缺少 size 和 series 通道
chart.options({
  type: 'density',
  data: { /* ... */ },
  encode: { x: 'x', y: 'y' },  // ❌ 缺少 size 和 series
});

// ✅ 正确:包含所有必选通道
chart.options({
  type: 'density',
  data: { /* ... */ },
  encode: {
    x: 'x',
    y: 'y',
    size: 'size',      // 必选
    series: 'species', // 必选
  },
});
```

### 错误 3:encode.y 使用了原始字段名而非 KDE 输出字段名

最常见的命名混淆:原始字段叫 `value`,误以为 encode 也写 `y: 'value'`。

```javascript
// ❌ 错误:field: 'value' 是 KDE 的输入;但 encode.y 要用 KDE 的输出字段
chart.options({
  type: 'density',
  data: {
    type: 'inline',
    value: rawData,
    transform: [{ type: 'kde', field: 'value', groupBy: ['group'] }],
    //                                ↑ 原始字段叫 'value'
  },
  encode: {
    x: 'group',
    y: 'value',  // ❌ 'value' 是原始标量,不是 KDE 输出的密度数组
    size: 'size',
    series: 'group',
  },
});

// ✅ 正确:encode.y 对应 KDE 输出字段(默认 as[0] = 'y')
chart.options({
  type: 'density',
  data: {
    type: 'inline',
    value: rawData,
    transform: [{ type: 'kde', field: 'value', groupBy: ['group'] }],
  },
  encode: {
    x: 'group',
    y: 'y',      // ✅ KDE 默认输出字段名是 'y',不是 'value'
    size: 'size',
    series: 'group',
  },
});
```

**记忆规则**:`field` 是 KDE 的**输入**,`as`(默认 `['y', 'size']`)是 KDE 的**输出**,encode 必须用**输出字段名**。

### 错误 4:数据零方差或单点组导致 KDE 退化(图表空白)

当某分组数据只有 1 个点,或所有值完全相同(方差 = 0)时,KDE 内部 min=max,出现除以零,产生 NaN,该组密度图不渲染。

```javascript
// ❌ 问题数据:零方差 / 单点,KDE 静默失败
const data = [
  { group: '低负荷', value: 0 },           // 只有 1 个点
  { group: '中负荷', value: 20 },
  { group: '中负荷', value: 20 },          // 9 个完全相同的值
  // ...
];

// ✅ 解决方案1:指定 min/max 扩展 KDE 范围,避免零区间
transform: [{
  type: 'kde',
  field: 'value',
  groupBy: ['group'],
  min: -10,   // 手动指定范围,确保 min ≠ max
  max: 50,
}]

// ✅ 解决方案2:数据点太少时,改用箱线图或散点图代替密度图
// KDE 建议每组至少 5-10 个不同值才能产生有意义的密度曲线
```

### 错误 5:直接使用原始数据

```javascript
// ❌ 错误:原始数据没有经过 KDE 变换,没有 size 字段
chart.options({
  type: 'density',
  data: rawPoints,  // ❌ 需要先经过 kde 变换
  encode: { x: 'x', y: 'y', size: 'size' },
});

// ✅ 正确:使用 data.transform 进行 KDE 预处理
chart.options({
  type: 'density',
  data: {
    type: 'inline',
    value: rawPoints,
    transform: [{ type: 'kde', field: 'y', groupBy: ['x'] }],
  },
  encode: { x: 'x', y: 'y', size: 'size', series: 'x' },
});
```

### 错误 6:在组合视图中未正确传递数据

在组合视图 (`type: 'view'`) 中,如果 `children` 子图没有显式声明 `data`,会继承父级数据。但若子图需要特定的数据变换(如 KDE),必须显式声明自己的 `data` 配置。

```javascript
// ❌ 错误:子图未声明 data,无法应用 KDE 变换
chart.options({
  type: 'view',
  data: rawData,
  children: [{
    type: 'density',
    // 缺少 data 配置,transform 无效
    encode: { x: 'x', y: 'y', size: 'size', series: 'species' },
  }]
});

// ✅ 正确:子图显式声明 data 并应用 KDE 变换
chart.options({
  type: 'view',
  data: rawData,
  children: [{
    type: 'density',
    data: {
      // 显式声明 data,即使与父级相同
      type: 'inline',
      value: rawData,
      transform: [{ type: 'kde', field: 'y', groupBy: ['x', 'species'] }],
    },
    encode: { x: 'x', y: 'y', size: 'size', series: 'species' },
  }]
});
```

### 错误 7:KDE 分组字段配置不当导致数据不足

当 `groupBy` 字段划分过细,导致某些分组内的数据点过少(如小于等于1个),KDE 无法计算有效的密度分布,该分组不会被渲染。

```javascript
// ❌ 错误:groupBy 包含过多字段,导致某些分组只有一个数据点
chart.options({
  type: 'density',
  data: {
    type: 'inline',
    value: rawData,
    transform: [{ 
      type: 'kde', 
      field: 'y', 
      groupBy: ['x', 'species', 'extraCategory'] // 分组过细,可能造成某些组只有一个点
    }],
  },
  encode: { x: 'x', y: 'y', size: 'size', series: 'species' },
});

// ✅ 正确:合理选择 groupBy 字段,保证每组有足够的数据点
chart.options({
  type: 'density',
  data: {
    type: 'inline',
    value: rawData,
    transform: [{ 
      type: 'kde', 
      field: 'y', 
      groupBy: ['x', 'species'] // 合理分组,保证每组数据充足
    }],
  },
  encode: { x: 'x', y: 'y', size: 'size', series: 'species' },
});
```

### 错误 8:KDE 输出字段名与 encode 映射不一致导致图表空白

在 KDE 变换中使用 `as` 自定义输出字段名时,必须确保 `encode` 中的 `y` 和 `size` 通道引用的是正确的自定义字段名。

```javascript
// ❌ 错误:KDE 输出字段名为 density_x 和 density_y,但 encode 引用了默认字段名
chart.options({
  type: 'density',
  data: {
    type: 'inline',
    value: rawData,
    transform: [{
      type: 'kde',
      field: 'y',
      groupBy: ['x'],
      as: ['density_x', 'density_y']
    }]
  },
  encode: {
    x: 'x',
    y: 'y',       // ❌ 应为 'density_x'
    size: 'size', // ❌ 应为 'density_y'
    series: 'x'
  }
});

// ✅ 正确:encode 中引用 KDE 输出的自定义字段名
chart.options({
  type: 'density',
  data: {
    type: 'inline',
    value: rawData,
    transform: [{
      type: 'kde',
      field: 'y',
      groupBy: ['x'],
      as: ['density_x', 'density_y']
    }]
  },
  encode: {
    x: 'x',
    y: 'density_x',  // ✅ 正确引用自定义字段名
    size: 'density_y', // ✅ 正确引用自定义字段名
    series: 'x'
  }
});
```

### 错误 9:KDE 分组后每组样本数过少导致图表空白

KDE 算法要求每组数据具有足够的样本点(建议每组至少 5~10 个不同值)才能有效计算密度分布。若分组后每组样本数过少,可能导致图表渲染为空白。

```javascript
// ❌ 错误:分组后每组样本数过少
const insufficientData = [
  { group: 'A', value: 1 },
  { group: 'A', value: 1 },
  { group: 'B', value: 2 },
  { group: 'B', value: 2 }
];

chart.options({
  type: 'density',
  data: {
    type: 'inline',
    value: insufficientData,
    transform: [{ type: 'kde', field: 'value', groupBy: ['group'] }]
  },
  encode: { x: 'group', y: 'y', size: 'size', series: 'group' }
});

// ✅ 解决方案:合并分组或增加样本数,或改用其他图表类型
const sufficientData = [
  { group: 'A', value: 1 }, { group: 'A', value: 1.1 }, { group: 'A', value: 1.2 },
  { group: 'A', value: 1.3 }, { group: 'A', value: 1.4 }, { group: 'B', value: 2 },
  { group: 'B', value: 2.1 }, { group: 'B', value: 2.2 }, { group: 'B', value: 2.3 },
  { group: 'B', value: 2.4 }
];
```

## 配置项

### encode 通道

| 属性   | 描述                                     | 必选 |
|--------|------------------------------------------|------|
| x      | X 轴字段,时间或有序分类字段             | ✓    |
| y      | Y 轴字段,数值字段(KDE 输出字段)       | ✓    |
| size   | 密度大小字段(KDE 变换后生成)           | ✓    |
| series | 系列分组字段                             | ✓    |
| color  | 颜色映射字段                            |      |

### coordinate 坐标系

| 坐标系     | 类型         | 用途             |
|------------|--------------|------------------|
| 直角坐标系 | `'cartesian'` | 默认,和密度图等 |
| 极坐标系   | `'polar'`     | 极坐标小提琴图等 |
| 对称坐标系 | `'transpose'` | 对称小提琴图等   |
references/marks/g2-mark-distribution-curve.md
---
id: "g2-mark-distribution-curve"
title: "G2 分布曲线图(distribution curve)"
description: |
  分布曲线图使用 type: 'line' + encode.shape: 'smooth' + data.transform 中的自定义分箱统计,
  展示连续数值数据的频率密度分布。适合探索数据分布形态、多组数据分布比较。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "分布曲线图"
  - "distribution curve"
  - "频率密度"
  - "正态分布"
  - "smooth"
  - "KDE"

related:
  - "g2-mark-histogram"
  - "g2-mark-density"
  - "g2-mark-violin"

use_cases:
  - "展示连续数值的概率密度分布"
  - "多组数据分布形态对比"
  - "数据质量检查(正态性检验)"

anti_patterns:
  - "数据量少于 30 条时效果不稳定,改用散点图或箱线图"
  - "离散分类数据不适合分布曲线"

difficulty: "intermediate"
completeness: "full"
created: "2025-04-01"
updated: "2025-04-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/distributioncurve"
---

## 核心概念

**分布曲线图 = `type: 'line'` + `encode.shape: 'smooth'` + 手动分箱统计**

G2 本身没有内置分布曲线 mark,需要先把原始数据分箱并计算频率密度,再用 smooth 折线绘制:

```
原始数据 → 分箱(bins) → 计算每箱频率密度 → smooth 折线
```

如果原始数据已有 KDE 处理,也可以直接使用 `type: 'density'` + `data.transform kde`。

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  theme: 'classic',
});

chart.options({
  type: 'line',
  data: {
    value: [
      { value: 85 }, { value: 92 }, { value: 78 }, { value: 95 },
      { value: 88 }, { value: 72 }, { value: 91 }, { value: 83 },
      // ... 更多数据(建议 100+ 条)
    ],
    transform: [
      {
        type: 'custom',
        callback: (data) => {
          const values = data.map((d) => d.value);
          const min = Math.min(...values);
          const max = Math.max(...values);
          const binCount = 20;
          const binWidth = (max - min) / binCount;

          // 分箱统计
          const bins = Array.from({ length: binCount }, (_, i) => ({
            x0: min + i * binWidth,
            x1: min + (i + 1) * binWidth,
            count: 0,
          }));
          values.forEach((v) => {
            const idx = Math.min(Math.floor((v - min) / binWidth), binCount - 1);
            bins[idx].count++;
          });

          // 输出频率密度
          const total = values.length;
          return bins.map((bin) => ({
            x: (bin.x0 + bin.x1) / 2,
            y: bin.count / total,
          }));
        },
      },
    ],
  },
  encode: {
    x: 'x',
    y: 'y',
    shape: 'smooth',   // 平滑曲线
  },
  style: {
    lineWidth: 3,
    stroke: '#1890ff',
  },
  axis: {
    x: { title: '数值' },
    y: { title: '频率密度' },
  },
});

chart.render();
```

## 多组分布曲线对比

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  theme: 'classic',
});

chart.options({
  type: 'line',
  data: {
    type: 'fetch',
    value: 'https://assets.antv.antgroup.com/g2/species.json',
    transform: [
      {
        type: 'custom',
        callback: (data) => {
          // 按 species 分组,各自分箱
          const groups = {};
          data.forEach((d) => {
            if (!groups[d.species]) groups[d.species] = [];
            groups[d.species].push(d.y);
          });

          const binCount = 20;
          const results = [];

          Object.entries(groups).forEach(([species, values]) => {
            const filteredValues = values.filter((v) => !isNaN(v));
            const min = Math.min(...filteredValues);
            const max = Math.max(...filteredValues);
            const binWidth = (max - min) / binCount;

            const bins = Array.from({ length: binCount }, (_, i) => ({
              x0: min + i * binWidth,
              x1: min + (i + 1) * binWidth,
              count: 0,
            }));
            filteredValues.forEach((v) => {
              const idx = Math.min(Math.floor((v - min) / binWidth), binCount - 1);
              bins[idx].count++;
            });

            const total = filteredValues.length;
            bins.forEach((bin) => {
              results.push({
                x: (bin.x0 + bin.x1) / 2,
                y: bin.count / total,
                species,
              });
            });
          });

          return results;
        },
      },
    ],
  },
  encode: {
    x: 'x',
    y: 'y',
    color: 'species',
    shape: 'smooth',
  },
  style: {
    lineWidth: 2,
    strokeOpacity: 0.8,
  },
  axis: {
    x: { title: '花瓣长度' },
    y: { title: '频率密度' },
  },
  legend: {
    color: { title: '物种', position: 'right' },
  },
});

chart.render();
```

## 使用 density mark 替代(推荐)

当数据量足够大时,优先使用内置的 density mark + KDE 变换,比手动分箱更精准:

```javascript
chart.options({
  type: 'density',
  data: {
    type: 'inline',
    value: rawData,
    transform: [
      {
        type: 'kde',
        field: 'value',        // 做 KDE 的字段
        groupBy: ['category'], // 分组字段
        size: 30,              // 输出点数,越多越精细
      },
    ],
  },
  encode: {
    x: 'category',
    y: 'y',
    size: 'size',
    series: 'category',
    color: 'category',
  },
  tooltip: false,
});
```

## 常见错误与修正

### 错误 1:忘记 encode.shape: 'smooth'

```javascript
// ❌ 效果:折线图,有明显锯齿,不像分布曲线
chart.options({
  type: 'line',
  data: binnedData,
  encode: { x: 'x', y: 'y' },  // ❌ 缺少 shape: 'smooth'
});

// ✅ 正确:smooth 使曲线平滑
chart.options({
  type: 'line',
  data: binnedData,
  encode: { x: 'x', y: 'y', shape: 'smooth' },  // ✅
});
```

### 错误 2:原始数据未分箱直接绘制

```javascript
// ❌ 错误:原始数据点连成折线,不是密度曲线
chart.options({
  type: 'line',
  data: rawData,   // ❌ 未分箱,只是散点连线
  encode: { x: 'index', y: 'value', shape: 'smooth' },
});

// ✅ 正确:先在 data.transform 中分箱,再绘制
chart.options({
  type: 'line',
  data: {
    value: rawData,
    transform: [{ type: 'custom', callback: binningFn }],
  },
  encode: { x: 'x', y: 'y', shape: 'smooth' },
});
```

### 错误 3:data 关键字缺失

```javascript
// ❌ 错误:transform 必须放在 data 对象内
chart.options({
  type: 'line',
  data: { value: rawData, transform: [...] },  // ❌ 孤立的 { } 语法错误
  encode: { x: 'x', y: 'y' },
});

// ✅ 正确:必须有 data: 键
chart.options({
  type: 'line',
  data: { value: rawData, transform: [...] },  // ✅
  encode: { x: 'x', y: 'y' },
});
```

## 分布曲线 vs 相关图表选择

| 图表 | 适用场景 |
|------|---------|
| 分布曲线(line + smooth) | 展示连续分布形态,数据量 50+ |
| 直方图 | 需要精确频次统计,看区间分布 |
| density mark | 数据量大,自动 KDE 估计 |
| 小提琴图 | 多组对比 + 显示统计摘要 |
references/marks/g2-mark-funnel.md
---
id: "g2-mark-funnel"
title: "G2 漏斗图(funnel)"
description: |
  漏斗图使用 interval mark 配合 shape: 'funnel' 或 'pyramid',
  展示业务流程中数据在不同阶段的流转和转化率。
  必须配合 symmetryY transform 和 transpose coordinate 使用。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "漏斗图"
  - "funnel"
  - "pyramid"
  - "转化率"
  - "流程"
  - "symmetryY"

related:
  - "g2-mark-interval-basic"
  - "g2-transform-symmetryy"
  - "g2-coord-transpose"

use_cases:
  - "销售流程转化率分析"
  - "用户注册/购买漏斗"
  - "金字塔层级结构展示"
  - "双漏斗对比(两渠道对比)"

anti_patterns:
  - "无序数据不适合漏斗图"
  - "数值有增有减的流程不适合"

difficulty: "intermediate"
completeness: "full"
created: "2025-04-01"
updated: "2025-04-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/funnel"
---

## 核心概念

**漏斗图 = interval mark + shape: 'funnel' + symmetryY transform + transpose coordinate**

- `encode.shape: 'funnel'`:启用漏斗形状
- `transform: [{ type: 'symmetryY' }]`:使漏斗左右对称(**必须**)
- `coordinate: { transform: [{ type: 'transpose' }] }`:横向展示(推荐)
- `axis: false`:漏斗图通常隐藏坐标轴

**金字塔变体**:`shape: 'pyramid'` + `style: { reverse: true }`

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  theme: 'classic',
});

chart.options({
  type: 'interval',
  data: [
    { stage: '访问', value: 8043 },
    { stage: '咨询', value: 2136 },
    { stage: '报价', value: 908 },
    { stage: '议价', value: 691 },
    { stage: '成交', value: 527 },
  ],
  encode: {
    x: 'stage',
    y: 'value',
    color: 'stage',
    shape: 'funnel',
  },
  coordinate: { transform: [{ type: 'transpose' }] },
  transform: [{ type: 'symmetryY' }],
  scale: {
    color: { palette: 'spectral' },
  },
  animate: { enter: { type: 'fadeIn' } },
  axis: false,
  labels: [
    {
      text: (d) => `${d.stage}\n${d.value}`,
      position: 'inside',
      transform: [{ type: 'contrastReverse' }],
    },
  ],
  legend: false,
});

chart.render();
```

## 金字塔图

```javascript
chart.options({
  type: 'interval',
  data: [
    { text: '顶层', value: 5 },
    { text: '中上层', value: 10 },
    { text: '中等', value: 20 },
    { text: '中下层', value: 25 },
    { text: '底层', value: 40 },
  ],
  encode: {
    x: 'text',
    y: 'value',
    color: 'text',
    shape: 'pyramid',   // 金字塔形状
  },
  coordinate: { transform: [{ type: 'transpose' }] },
  transform: [{ type: 'symmetryY' }],
  style: {
    reverse: true,    // 反转,使小值在顶部(金字塔形)
  },
  scale: {
    x: { paddingOuter: 0, paddingInner: 0 },
    color: { type: 'ordinal' },
  },
  axis: false,
  labels: [
    { text: (d) => d.text, position: 'inside' },
    { text: (d) => `${d.value}%`, position: 'inside', style: { dy: 15 } },
  ],
});
```

## 对比漏斗图(双漏斗)

两个漏斗镜像对比,通过 y 轴负值实现下方漏斗反向展示:

```javascript
chart.options({
  type: 'view',
  autoFit: true,
  data: [
    { action: '访问', visitor: 500, site: '站点1' },
    { action: '浏览', visitor: 400, site: '站点1' },
    { action: '交互', visitor: 300, site: '站点1' },
    { action: '下单', visitor: 200, site: '站点1' },
    { action: '完成', visitor: 100, site: '站点1' },
    { action: '访问', visitor: 550, site: '站点2' },
    { action: '浏览', visitor: 420, site: '站点2' },
    { action: '交互', visitor: 280, site: '站点2' },
    { action: '下单', visitor: 150, site: '站点2' },
    { action: '完成', visitor: 80, site: '站点2' },
  ],
  scale: {
    x: { padding: 0 },
    color: { range: ['#0050B3', '#1890FF', '#40A9FF', '#69C0FF', '#BAE7FF'] },
  },
  coordinate: { transform: [{ type: 'transpose' }] },
  axis: false,
  children: [
    {
      type: 'interval',
      data: {
        transform: [{ type: 'filter', callback: (d) => d.site === '站点1' }],
      },
      encode: { x: 'action', y: 'visitor', color: 'action', shape: 'funnel' },
      style: { stroke: '#FFF' },
      animate: { enter: { type: 'fadeIn' } },
      labels: [
        {
          text: 'visitor',
          position: 'inside',
          transform: [{ type: 'contrastReverse' }],
        },
        { text: 'action', position: 'right' },
      ],
    },
    {
      type: 'interval',
      data: {
        transform: [{ type: 'filter', callback: (d) => d.site === '站点2' }],
      },
      encode: {
        x: 'action',
        y: (d) => -d.visitor,  // 负值实现镜像对称
        color: 'action',
        shape: 'funnel',
      },
      style: { stroke: '#FFF' },
      animate: { enter: { type: 'fadeIn' } },
      labels: [
        {
          text: 'visitor',
          position: 'inside',
          transform: [{ type: 'contrastReverse' }],
        },
      ],
    },
  ],
  legend: false,
});
```

## 百分比漏斗 + 转化率标注

`normalizeY` 使各阶段高度等比,`symmetryY` 使其对称——**顺序不能颠倒**:

```javascript
const data = [
  { stage: '访问', count: 10000 },
  { stage: '注册', count: 6200 },
  { stage: '激活', count: 3800 },
  { stage: '付费', count: 1500 },
];

const dataWithRate = data.map((d, i) => ({
  ...d,
  rate: i === 0 ? '100%' : `${((d.count / data[i - 1].count) * 100).toFixed(1)}%`,
}));

chart.options({
  type: 'interval',
  data: dataWithRate,
  encode: {
    x: 'stage',
    y: 'count',
    color: 'stage',
    shape: 'funnel',
  },
  transform: [
    { type: 'normalizeY' },   // ① 先归一化(统一高度比例)
    { type: 'symmetryY' },    // ② 再对称(形成漏斗形状)
  ],
  coordinate: { transform: [{ type: 'transpose' }] },
  axis: false,
  legend: false,
  labels: [
    {
      text: (d) => d.stage,
      position: 'inside',
      style: { fill: 'white', fontSize: 13, fontWeight: 'bold' },
    },
    {
      text: (d) => `转化率 ${d.rate}`,
      position: 'right',
      style: { fill: '#666', fontSize: 11 },
      dx: 8,
    },
  ],
});
```

## 常见错误与修正

### 错误 1:缺少 symmetryY transform

```javascript
// ❌ 错误:没有 symmetryY,漏斗形状不对称,会变成普通柱状图
chart.options({
  type: 'interval',
  data,
  encode: { x: 'stage', y: 'value', shape: 'funnel' },
  coordinate: { transform: [{ type: 'transpose' }] },
  // ❌ 缺少 transform: [{ type: 'symmetryY' }]
});

// ✅ 正确:必须加 symmetryY
chart.options({
  type: 'interval',
  data,
  encode: { x: 'stage', y: 'value', shape: 'funnel' },
  coordinate: { transform: [{ type: 'transpose' }] },
  transform: [{ type: 'symmetryY' }],  // ✅ 必须
});
```

### 错误 2:shape 值错误

```javascript
// ❌ 错误:shape 应在 encode 中,不是 style 中
chart.options({
  type: 'interval',
  encode: { x: 'stage', y: 'value' },
  style: { shape: 'funnel' },  // ❌ shape 不在 style 中
});

// ✅ 正确:shape 是 encode 通道
chart.options({
  type: 'interval',
  encode: { x: 'stage', y: 'value', shape: 'funnel' },  // ✅
});
```

### 错误 3:coordinate 语法错误

```javascript
// ❌ 错误:coordinate 不是数组
chart.options({
  coordinate: [{ type: 'transpose' }],  // ❌ 错误语法
});

// ✅ 正确:coordinate 是对象,transpose 放在 transform 数组中
chart.options({
  coordinate: { transform: [{ type: 'transpose' }] },  // ✅
});
```

### 错误 4:金字塔不反转

```javascript
// ❌ 错误:pyramid 默认宽端在顶部(不是金字塔形状)
chart.options({
  encode: { shape: 'pyramid' },
  // ❌ 缺少 style.reverse: true
});

// ✅ 正确:加 reverse: true 使小值在顶部
chart.options({
  encode: { shape: 'pyramid' },
  style: { reverse: true },  // ✅ 使最小值在顶(金字塔形)
});
```

## encode.shape 可选值

| shape 值    | 效果                     |
|------------|--------------------------|
| `'funnel'` | 标准漏斗形状(默认梯形)  |
| `'pyramid'`| 等腰三角形(金字塔)      |
references/marks/g2-mark-gantt.md
---
id: "g2-mark-gantt"
title: "G2 Gantt Chart Mark"
description: |
  甘特图 Mark。使用 interval 标记配合 transpose 坐标系,展示项目任务的时间安排。
  适用于项目管理、任务调度、进度跟踪等场景。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "甘特图"
  - "gantt"
  - "项目管理"
  - "进度"

related:
  - "g2-mark-interval-basic"
  - "g2-comp-slider"

use_cases:
  - "项目进度管理"
  - "任务调度"
  - "资源管理"

anti_patterns:
  - "非时间维度数据不适合"
  - "连续数值变化应使用折线图"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/mark/gantt"
---

## 核心概念

甘特图展示项目任务的时间安排:
- 使用 `interval` 标记
- 配合 `transpose` 坐标变换
- `y` 和 `y1` 表示开始和结束时间

**关键要素:**
- 任务名称:映射到横轴
- 开始时间:映射到 `y`
- 结束时间:映射到 `y1`

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  theme: 'classic',
});

chart.options({
  type: 'interval',
  autoFit: true,
  data: [
    { name: '活动策划', startTime: 1, endTime: 4 },
    { name: '场地规划', startTime: 3, endTime: 13 },
    { name: '选择供应商', startTime: 5, endTime: 8 },
  ],
  encode: {
    x: 'name',
    y: 'startTime',
    y1: 'endTime',
    color: 'name',
  },
  coordinate: {
    transform: [{ type: 'transpose' }],
  },
});

chart.render();
```

## 常用变体

### 带项目阶段

```javascript
chart.options({
  type: 'interval',
  data: [
    { name: '需求分析', startTime: 1, endTime: 5, phase: '规划' },
    { name: '系统设计', startTime: 4, endTime: 10, phase: '设计' },
    { name: '前端开发', startTime: 8, endTime: 20, phase: '开发' },
  ],
  encode: {
    x: 'name',
    y: 'startTime',
    y1: 'endTime',
    color: 'phase',  // 按阶段着色
  },
  coordinate: { transform: [{ type: 'transpose' }] },
});
```

### 带时序动画

```javascript
chart.options({
  type: 'interval',
  data,
  encode: {
    x: 'name',
    y: 'startTime',
    y1: 'endTime',
    color: 'name',
    enterDuration: (d) => (d.endTime - d.startTime) * 200,
    enterDelay: (d) => d.startTime * 100,
  },
  coordinate: { transform: [{ type: 'transpose' }] },
});
```

### 带里程碑

```javascript
chart.options({
  type: 'view',
  children: [
    {
      type: 'interval',
      data: tasks,
      encode: { x: 'name', y: 'startTime', y1: 'endTime' },
      coordinate: { transform: [{ type: 'transpose' }] },
    },
    {
      type: 'point',
      data: milestones,
      encode: {
        x: 'name',
        y: 'time',
        shape: 'diamond',
        size: 8,
      },
      coordinate: { transform: [{ type: 'transpose' }] },
    },
  ],
});
```

## 完整类型参考

```typescript
interface GanttData {
  name: string;        // 任务名称
  startTime: number;   // 开始时间
  endTime: number;     // 结束时间
  phase?: string;      // 项目阶段
}

interface GanttOptions {
  type: 'interval';
  encode: {
    x: string;         // 任务名称字段
    y: string;         // 开始时间字段
    y1: string;        // 结束时间字段
    color?: string;    // 颜色字段
  };
  coordinate: {
    transform: [{ type: 'transpose' }];
  };
}
```

## 甘特图 vs 柱状图

| 特性 | 甘特图 | 柱状图 |
|------|--------|--------|
| 用途 | 任务时间安排 | 数值对比 |
| 数据维度 | 时间区间 | 单一数值 |
| 视觉形式 | 水平条形 | 垂直柱形 |

## 常见错误与修正

### 错误 1:缺少 transpose

```javascript
// ❌ 问题:默认是垂直方向
coordinate: {}

// ✅ 正确:添加 transpose
coordinate: { transform: [{ type: 'transpose' }] }
```

### 错误 2:缺少 y1 编码

```javascript
// ❌ 问题:只有开始时间
encode: { x: 'name', y: 'startTime' }

// ✅ 正确:添加结束时间
encode: { x: 'name', y: 'startTime', y1: 'endTime' }
```

### 错误 3:任务过多

```javascript
// ⚠️ 注意:任务数量建议不超过 20 个
// 过多任务会导致图表拥挤
```
references/marks/g2-mark-gauge.md
---
id: "g2-mark-gauge"
title: "G2 仪表盘(gauge)"
description: |
  G2 v5 内置 gauge Mark,通过 type: 'gauge' 创建仪表盘,
  数据包含 target(当前值)和 total(最大值),
  支持分段配色(thresholds)、中心文字和自定义样式。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "仪表盘"
  - "gauge"
  - "表盘"
  - "KPI"
  - "进度"
  - "spec"

related:
  - "g2-core-chart-init"
  - "g2-mark-arc-pie"

use_cases:
  - "展示 KPI 完成率/达成度"
  - "实时监控指标(如 CPU 使用率)"
  - "进度展示(分数、评级)"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/gauge"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 400,
  height: 300,
});

chart.options({
  type: 'gauge',
  data: {
    value: {
      target: 120,   // 当前值
      total: 400,    // 满分/最大值
      name: '评分',  // 中心标签
    },
  },
  legend: false,
});

chart.render();
```

## 分段配色仪表盘(阈值配色)

```javascript
chart.options({
  type: 'gauge',
  data: {
    value: {
      target: 159,
      total: 280,
      name: '速度',
      // thresholds:按百分比分段(0-1),每段用不同颜色
      thresholds: [100, 200, 280],   // 对应数值分段
    },
  },
  scale: {
    color: {
      // 对应每段的颜色
      range: ['#F4664A', '#FAAD14', '#30BF78'],
    },
  },
  style: {
    // 中心文字
    textContent: (target, total) =>
      `进度\n${((target / total) * 100).toFixed(0)}%`,
  },
  legend: false,
});
```

## 完整配置说明

```javascript
chart.options({
  type: 'gauge',
  data: {
    value: {
      target: 75,       // 当前值(必填)
      total: 100,       // 最大值(必填)
      name: 'score',    // 标签名称(可选)
      thresholds: [40, 70, 100],  // 分段阈值(可选)
    },
  },

  // 颜色比例尺(配合 thresholds 使用)
  scale: {
    color: {
      range: ['#F4664A', '#FAAD14', '#30BF78'],
    },
  },

  // 仪表盘样式
  style: {
    // 弧线端点形状:'round'(圆弧端)| 'butt'(直角端)
    arcShape: 'round',
    arcLineWidth: 1,
    arcStroke: '#fff',

    // 中心文字:签名固定为 (target, total),无第三个 datum 参数
    textContent: (target, total) => `${target}/${total}`,
    textX: '50%',
    textY: '70%',
    textFontSize: 24,
    textFill: '#262626',

    // 指针(false 表示隐藏)
    pointerShape: false,
    pinShape: false,
  },

  legend: false,
});
```

## 自定义起止角度

```javascript
chart.options({
  type: 'gauge',
  data: { value: { target: 60, total: 100, name: '完成率' } },
  // gauge 内部使用 radial 坐标,可通过 coordinate 调整角度
  coordinate: {
    type: 'radial',
    innerRadius: 0.8,
    startAngle: (-10 / 12) * Math.PI,   // 约 -150°
    endAngle: (2 / 12) * Math.PI,        // 约 30°
  },
  legend: false,
});
```

## 多指标仪表盘组合

```javascript
// 用 facetRect 或 spaceFlex 并排多个仪表盘
chart.options({
  type: 'spaceFlex',
  children: [
    {
      type: 'gauge',
      data: { value: { target: 75, total: 100, name: 'CPU' } },
      legend: false,
    },
    {
      type: 'gauge',
      data: { value: { target: 60, total: 100, name: '内存' } },
      legend: false,
    },
    {
      type: 'gauge',
      data: { value: { target: 45, total: 100, name: '磁盘' } },
      legend: false,
    },
  ],
});
```

## 常见错误与修正

### 错误 0:textContent 函数签名错误——误传第三个 datum 参数

`textContent` 的签名是 `(target, total) => string`,G2 内部**只传两个数值**,不存在第三个参数。

```javascript
// ❌ 错误:datum 是 undefined,访问 datum.unit 会抛出 TypeError
style: {
  textContent: (target, total, datum) => `${target}${datum.unit}\n${datum.name}`,
  //                            ^^^^^ 始终是 undefined!
}

// ✅ 正确:用闭包捕获 data 中的额外字段
const gaugeData = {
  target: 48,
  total: 60,
  name: '响应时长',
  unit: 'min',
  thresholds: [15, 30, 45, 60],
};

chart.options({
  type: 'gauge',
  data: { value: gaugeData },
  style: {
    // 通过闭包引用外部变量
    textContent: (target, total) => `${target}${gaugeData.unit}\n${gaugeData.name}`,
  },
});
```

### 错误 1:数据格式不正确

```javascript
// ❌ 错误:gauge 数据需要嵌套在 value 对象内
chart.options({
  type: 'gauge',
  data: { target: 75, total: 100 },   // ❌ 顶层对象
});

// ✅ 正确:需要 { value: { target, total } } 结构
chart.options({
  type: 'gauge',
  data: {
    value: { target: 75, total: 100 },   // ✅
  },
});
```

### 错误 2:thresholds 与 color range 数量不匹配

```javascript
// ❌ 错误:3 个 thresholds 对应 3 段,但只给了 2 个颜色
chart.options({
  type: 'gauge',
  data: { value: { target: 60, total: 100, thresholds: [40, 70, 100] } },
  scale: {
    color: { range: ['#F4664A', '#30BF78'] },   // ❌ 应该有 3 个颜色
  },
});

// ✅ 正确:颜色数量 = 阈值段数(thresholds.length 段)
chart.options({
  scale: {
    color: { range: ['#F4664A', '#FAAD14', '#30BF78'] },   // ✅ 3 段 3 色
  },
});
```
references/marks/g2-mark-heatmap.md
---
id: "g2-mark-heatmap"
title: "G2 渐变热力图(heatmap mark)"
description: |
  heatmap mark(区别于 cell mark 的色块热力图)使用高斯核密度渐变绘制热力分布,
  每个点产生向外扩散的热晕效果,适合展示地理空间密度或二维密度分布。
  通过 color 通道指定强度,size 控制热晕半径。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "heatmap"
  - "热力图"
  - "密度热力"
  - "渐变热力"
  - "高斯核"
  - "空间密度"

related:
  - "g2-mark-cell-heatmap"
  - "g2-mark-density"
  - "g2-mark-point-scatter"

use_cases:
  - "地图上的用户点击/访问热力图"
  - "二维空间中的密度分布可视化"
  - "大量重叠点的密度展示(比散点图更清晰)"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/heatmap/"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

// 带密度权重的二维数据
const data = Array.from({ length: 500 }, () => ({
  x: Math.random() * 100 + (Math.random() > 0.5 ? 20 : 60),
  y: Math.random() * 100 + (Math.random() > 0.5 ? 20 : 70),
  weight: Math.random(),
}));

const chart = new Chart({ container: 'container', width: 600, height: 500 });

chart.options({
  type: 'heatmap',   // 渐变热力图(不是 cell 热力图)
  data,
  encode: {
    x: 'x',
    y: 'y',
    color: 'weight',  // 热力强度(0~1)
    size: 30,         // 热晕半径(px),固定值或字段名
  },
  style: {
    opacity: 0.8,
  },
  scale: {
    color: {
      type: 'sequential',
      palette: ['blue', 'cyan', 'lime', 'yellow', 'red'],  // 冷色→热色
    },
  },
  axis: false,
  legend: false,
});

chart.render();
```

## 配置项

```javascript
chart.options({
  type: 'heatmap',
  data,
  encode: {
    x: 'lng',
    y: 'lat',
    color: 'intensity',    // 强度字段(默认 0~1)
    size: 'radius',        // 热晕半径,可以是字段名或固定数字
                           // 默认 40(px)
  },
  style: {
    opacity: 1,            // 整体透明度
  },
});
```

## heatmap vs cell 热力图

```javascript
// heatmap mark:高斯渐变,连续热晕效果,适合点数据密度
chart.options({ type: 'heatmap', ... });

// cell mark:离散色块,适合矩阵数据(如时间×类别的二维表格)
chart.options({ type: 'cell', ... });
```

## 常见错误与修正

### 错误 1:color 通道值域不在 0~1——热力颜色映射异常
```javascript
// ❌ 如果 color 值是原始计数(如 500、1000),颜色映射可能不准确
chart.options({
  encode: { color: 'rawCount' },  // ⚠️  rawCount 值可能是 0~10000
});

// ✅ 归一化到 0~1,或设置 scale.color.domain
chart.options({
  encode: { color: 'intensity' },  // intensity 已归一化为 0~1
  // 或配置 domain
  scale: { color: { domain: [0, 1000] } },  // 显式指定范围
});
```

### 错误 2:与 cell mark 混淆——cell 是矩阵格子,heatmap 是连续渐变
```javascript
// ❌ 用 cell 展示空间密度——格子状,缺乏连续渐变感
chart.options({ type: 'cell', encode: { x: 'lng', y: 'lat', color: 'density' } });

// ✅ 空间密度用 heatmap(连续渐变热晕效果)
chart.options({ type: 'heatmap', encode: { x: 'lng', y: 'lat', color: 'density', size: 30 } });
```
references/marks/g2-mark-histogram.md
---
id: "g2-mark-histogram"
title: "G2 Histogram Mark"
description: |
  直方图 Mark。使用 rect 标记配合 binX 转换,展示连续数值数据的分布情况。
  适用于统计分析、数据分布探索等场景。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "直方图"
  - "histogram"
  - "分布"
  - "统计"

related:
  - "g2-mark-boxplot"
  - "g2-transform-binx"

use_cases:
  - "数据分布分析"
  - "统计分析"
  - "频数统计"

anti_patterns:
  - "分类数据比较应使用柱状图"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/mark/histogram"
---

## 核心概念

直方图用于展示连续数值数据的分布情况。与柱状图不同:
- 直方图使用 `rect` 标记,支持 `x` 和 `x1` 通道表示区间
- 必须配合 `binX` 转换,自动分箱统计
- 柱子之间无间隔,表示数据连续

**关键要素:**
- `rect` 标记:支持区间表示
- `binX` 转换:自动分箱统计
- `x1` 通道:表示区间结束位置

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  autoFit: true,
});

chart.options({
  type: 'rect',
  data: {
    type: 'fetch',
    value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/diamond.json',
  },
  encode: {
    x: 'carat',
    y: 'count',
  },
  transform: [
    { type: 'binX', y: 'count' },
  ],
  style: {
    fill: '#1890FF',
    fillOpacity: 0.9,
  },
});

chart.render();
```

## 常用变体

### 指定分箱数量

```javascript
chart.options({
  type: 'rect',
  data,
  encode: { x: 'value', y: 'count' },
  transform: [
    { type: 'binX', y: 'count', thresholds: 30 },  // 指定分箱数量
  ],
});
```

### 多分布对比

```javascript
chart.options({
  type: 'rect',
  data,
  encode: {
    x: 'price',
    y: 'count',
    color: 'group',
  },
  transform: [
    { type: 'binX', y: 'count', groupBy: ['group'] },
  ],
  style: { fillOpacity: 0.7 },
});
```

### 带坐标轴标题

```javascript
chart.options({
  type: 'rect',
  data,
  encode: { x: 'carat', y: 'count' },
  transform: [{ type: 'binX', y: 'count' }],
  axis: {
    x: { title: '钻石重量(克拉)' },
    y: { title: '频数' },
  },
});
```

## 完整类型参考

```typescript
interface HistogramOptions {
  type: 'rect';
  encode: {
    x: string;           // 连续数值字段
    y: 'count';          // 统计数量
    color?: string;      // 分组字段
  };
  transform: [
    {
      type: 'binX';
      y: 'count';
      thresholds?: number;  // 分箱数量
      groupBy?: string[];   // 分组字段
    }
  ];
}
```

## 直方图 vs 柱状图

| 特性 | 直方图 | 柱状图 |
|------|--------|--------|
| 数据类型 | 连续数值 | 分类数据 |
| Mark 类型 | `rect` | `interval` |
| 柱子间隔 | 无间隔 | 有间隔 |
| X 轴 | 连续区间 | 离散类别 |

## 常见错误与修正

### 错误 1:使用 interval 标记

```javascript
// ❌ 问题:interval 不支持区间表示
type: 'interval'

// ✅ 正确:使用 rect 标记
type: 'rect'
```

### 错误 2:缺少 binX 转换

```javascript
// ❌ 问题:没有分箱统计
encode: { x: 'value', y: 'count' }

// ✅ 正确:添加 binX 转换
transform: [{ type: 'binX', y: 'count' }]
```

### 错误 3:数据量过少

```javascript
// ⚠️ 注意:直方图需要足够的数据量
// 建议数据量 >= 50 条
```
references/marks/g2-mark-image.md
---
id: "g2-mark-image"
title: "G2 Image 图片标记"
description: |
  image mark 在图表的指定位置渲染图片,可用于图标型散点图(以图标代替点)、
  地图上的图标标注、带图片的标签等场景。
  通过 src 通道绑定图片 URL,x/y 通道确定位置,size 通道控制大小。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "image"
  - "图片"
  - "图标"
  - "icon"
  - "图片散点图"

related:
  - "g2-mark-point-scatter"
  - "g2-mark-text"

use_cases:
  - "以品牌 logo / 图标代替散点(图标散点图)"
  - "在特定坐标位置插入说明图片"
  - "结合地图的图标标注"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/point/#image"
---

## 最小可运行示例(图标散点图)

```javascript
import { Chart } from '@antv/g2';

const data = [
  { country: '中国',    gdp: 17.7, icon: 'https://example.com/flags/cn.png' },
  { country: '美国',    gdp: 25.5, icon: 'https://example.com/flags/us.png' },
  { country: '日本',    gdp: 4.2,  icon: 'https://example.com/flags/jp.png' },
  { country: '德国',    gdp: 4.1,  icon: 'https://example.com/flags/de.png' },
];

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'image',
  data,
  encode: {
    x: 'country',  // x 位置(分类轴)
    y: 'gdp',      // y 位置(数值轴)
    src: 'icon',   // 图片 URL 字段
    size: 40,      // 图片大小(px),固定值或字段名
  },
});

chart.render();
```

## image + point 叠加(图标 + 数据点)

```javascript
chart.options({
  type: 'view',
  data,
  children: [
    {
      type: 'image',
      encode: { x: 'x', y: 'y', src: 'icon', size: 32 },
    },
    {
      type: 'text',
      encode: { x: 'x', y: 'y', text: 'label' },
      style: { textAnchor: 'middle', dy: 20, fontSize: 12 },
    },
  ],
});
```

## 配置项

```javascript
chart.options({
  type: 'image',
  data,
  encode: {
    x: 'xField',       // x 坐标
    y: 'yField',       // y 坐标
    src: 'imageUrl',   // 图片 URL 字段(或固定 URL 字符串)
    size: 'sizeField', // 图片大小(px),可以是字段名或固定数值
  },
  style: {
    preserveAspectRatio: 'xMidYMid meet',  // 图片缩放策略(SVG 标准)
  },
});
```

## 常见错误与修正

### 错误 1:src 通道写的是图片数据本身,不是 URL
```javascript
// ❌ 错误:src 应该是 URL 字段名,不是 base64 或 blob
chart.options({
  encode: { src: btoa(imageData) },  // ❌ 不能传 base64 字符串(需要完整的 data: URL)
});

// ✅ 正确:传 URL 字段名,数据中是完整 URL
chart.options({
  encode: { src: 'iconUrl' },  // ✅ 数据中 iconUrl 字段是 'https://...' 格式
});
```

### 错误 2:没有设置 size——图片默认大小可能过大或过小
```javascript
// ❌ 未设置 size,图片可能太大覆盖其他元素
chart.options({
  type: 'image',
  encode: { x: 'x', y: 'y', src: 'icon' },  // ❌ 没有 size
});

// ✅ 明确设置合适的 size
chart.options({
  encode: { x: 'x', y: 'y', src: 'icon', size: 36 },  // ✅
});
```
references/marks/g2-mark-interval-basic.md
---
id: "g2-mark-interval-basic"
title: "G2 基础柱状图(Interval Mark)"
description: |
  使用 Interval Mark 创建基础柱状图。Interval Mark 是 G2 中
  用于绘制柱形、条形、直方图的核心标记类型。
  本文采用 Spec 模式(chart.options({})),通过 encode 映射 x/y/color 通道。

library: "g2"
version: "5.x"
category: "marks"
subcategory: "interval"
tags:
  - "柱状图"
  - "条形图"
  - "分类数据"
  - "比较"
  - "Interval"
  - "bar chart"
  - "bar"
  - "spec"
  - "options"

related:
  - "g2-mark-interval-grouped"
  - "g2-mark-interval-stacked"
  - "g2-mark-interval-normalized"
  - "g2-core-chart-init"
  - "g2-core-encode-channel"
  - "g2-scale-band"

use_cases:
  - "比较不同类别的数值大小"
  - "展示各项目的完成量、销量等指标"
  - "显示排名数据"
  - "对比多个维度的指标值"

anti_patterns:
  - "不适合展示连续数值的趋势变化(改用 Line 或 Area Mark)"
  - "类别超过 20 个时可读性差,考虑分页或过滤"
  - "不适合展示部分与整体的关系(改用堆叠柱状图或饼图)"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/bar/basic"
---

## 核心概念

Interval Mark 将数据映射为矩形区间:
- 在直角坐标系中:柱形(竖向)或条形(横向)
- 在极坐标系中:扇形(饼图)或玫瑰图
- 在径向坐标系中:玉珏图(Radial Bar Chart)

**关键 encode 通道:**
- `x`:分类轴,通常映射分类字段,自动使用 Band Scale
- `y`:数值轴,映射数值字段,使用 Linear Scale
- `y1`:区间终点,用于表示区间范围(如甘特图)
- `color`:颜色,用于视觉区分

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'interval',
  data: [
    { genre: 'Sports',   sold: 275 },
    { genre: 'Strategy', sold: 115 },
    { genre: 'Action',   sold: 120 },
    { genre: 'Shooter',  sold: 350 },
    { genre: 'Other',    sold: 150 },
  ],
  encode: {
    x: 'genre',
    y: 'sold',
    color: 'genre',
  },
});

chart.render();
```

## 常用变体

### 水平条形图(转置坐标系)

```javascript
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'genre', y: 'sold', color: 'genre' },
  coordinate: { transform: [{ type: 'transpose' }] },   // 关键:转置坐标系
});
```

### 带数据标签的柱状图

```javascript
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'genre', y: 'sold' },
  labels: [
    {
      text: 'sold',            // 显示哪个字段的值
      position: 'outside',     // 'inside' | 'outside' | 'top-left' | 'top-right'
      style: { fontSize: 12, fill: '#333' },
    },
  ],
});
```

### 圆角柱状图

```javascript
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'genre', y: 'sold' },
  style: {
    radius: 4,               // 统一圆角
    // 或单独设置:
    // radiusTopLeft: 4,
    // radiusTopRight: 4,
  },
});
```

### 自定义颜色

```javascript
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'genre', y: 'sold', color: 'genre' },
  scale: {
    color: {
      range: ['#1890ff', '#2fc25b', '#facc14', '#223273', '#8543e0'],
    },
  },
});
```

### 带 Tooltip 配置

```javascript
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'genre', y: 'sold' },
  tooltip: {
    title: 'genre',
    items: [{ field: 'sold', name: '销量' }],
  },
});
```

### Y 轴从指定值开始

```javascript
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'genre', y: 'sold' },
  scale: {
    y: { domain: [50, 400] },  // 手动设置 y 轴范围
  },
});
```

### 自定义坐标轴标题

```javascript
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'genre', y: 'sold' },
  axis: {
    x: { title: '游戏类型' },
    y: { title: '销量(万份)' },
  },
});
```

### 径向柱状图(玉珏图)

```javascript
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'genre', y: 'sold' },
  coordinate: { type: 'radial', innerRadius: 0.2 },  // 径向坐标系
});
```

### 带交互效果的柱状图

```javascript
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'genre', y: 'sold' },
  interaction: {
    elementHighlight: true,  // 元素高亮交互
  },
});
```

## Spec 完整结构速查

```javascript
chart.options({
  // Mark 类型
  type: 'interval',

  // 数据
  data: [...],

  // 通道映射
  encode: {
    x: 'genre',           // x 轴字段
    y: 'sold',            // y 轴字段
    y1: 'endValue',       // 区间终点字段(如甘特图)
    color: 'genre',       // 颜色字段
    shape: 'rect',        // 形状:'rect' | 'hollow'
  },

  // 比例尺
  scale: {
    y: { domain: [0, 500] },
    color: { range: ['#f00', '#00f'] },
  },

  // 坐标系变换
  coordinate: { 
    type: 'radial', 
    innerRadius: 0.2,
    transform: [{ type: 'transpose' }] 
  },

  // 样式
  style: {
    radius: 4,
    fillOpacity: 0.9,
  },

  // 数据标签(注意是 labels 复数)
  labels: [
    {
      text: 'sold',
      position: 'outside', // 'inside' | 'outside' | 'top-left' | 'top-right'
    }
  ],

  // Tooltip
  tooltip: { title: 'genre', items: [{ field: 'sold' }] },

  // 坐标轴
  axis: {
    x: { title: '游戏类型' },
    y: { title: '销量' },
  },

  // 图例
  legend: {
    color: { position: 'right' }
  },

  // 交互
  interaction: {
    elementHighlight: true
  }
});
```

## 完整类型参考

```typescript
// chart.options() 传入的 Spec 类型(interval 部分)
interface IntervalSpec {
  type: 'interval';
  data?: DataOption;
  encode?: {
    x?: string | ((d: any) => any);
    y?: string | ((d: any) => any);
    y1?: string | ((d: any) => any); // 区间终点通道
    color?: string | ((d: any) => any);
    shape?: 'rect' | 'hollow' | 'funnel' | 'pyramid' | string;
    size?: string | number | ((d: any) => any);
    series?: string;
  };
  transform?: Array<{ type: string; [key: string]: any }>;
  scale?: {
    x?: ScaleOption;
    y?: ScaleOption;
    color?: ScaleOption;
  };
  coordinate?: { 
    type?: 'polar' | 'cartesian' | 'radial';
    innerRadius?: number;
    outerRadius?: number;
    startAngle?: number;
    endAngle?: number;
    transform?: Array<{ type: string; [key: string]: any }>;
  };
  style?: {
    radius?: number;
    radiusTopLeft?: number;
    radiusTopRight?: number;
    radiusBottomLeft?: number;
    radiusBottomRight?: number;
    fill?: string;
    fillOpacity?: number;
    stroke?: string;
    lineWidth?: number;
  };
  labels?: LabelOption[];
  tooltip?: TooltipOption;
  axis?: { x?: AxisOption; y?: AxisOption };
  legend?: { color?: LegendOption };
  interaction?: { 
    elementHighlight?: boolean | { background?: boolean; region?: boolean }; 
  };
}
```

## 常见错误与修正

### 错误 1:使用 V4 链式 API(详见核心约束 Forbidden Patterns)
```javascript
// ❌ chart.interval().encode('x', 'genre');
// ✅ chart.options({ type: 'interval', data, encode: { x: 'genre', y: 'sold' } });
```

### 错误 2:缺少 container 参数
```javascript
// ❌ 错误
const chart = new Chart({ width: 640, height: 480 });

// ✅ 正确
const chart = new Chart({ container: 'container', width: 640, height: 480 });
```

### 错误 3:encode 和 style 混淆
```javascript
// ❌ 错误:style 不接受数据字段名
chart.options({ type: 'interval',  [...], style: { color: 'genre' } });

// ✅ 正确:数据映射用 encode,固定样式用 style
chart.options({
  type: 'interval',
  data: [...],
  encode: { color: 'genre' },   // 数据驱动
  style: { fill: '#1890ff' },   // 固定颜色时才用 style
});
```

### 错误 4:labels 写成 label(单数)
```javascript
// ❌ 错误:Spec 模式中标签字段是 labels(复数)
chart.options({ type: 'interval',  data: [...], label: { text: 'sold' } });

// ✅ 正确
chart.options({ type: 'interval', data: [...], labels: [{ text: 'sold' }] });
```

### 错误 5:y 轴负值处理
```javascript
// ❌ 潜在问题:负值柱体可能超出绘图区域
chart.options({ type: 'interval',  dataWithNegatives, encode: { y: 'value' } });

// ✅ 正确:通过 scale.y.domain 显式包含负值范围
chart.options({
  type: 'interval',
  data: dataWithNegatives,
  encode: { x: 'genre', y: 'value' },
  scale: { y: { domain: [-100, 300] } },
});
```

### 错误 6:径向坐标系使用不当
```javascript
// ❌ 错误:在径向坐标系中 x/y 映射顺序颠倒
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'value', y: 'genre' },  // 应该是 x: genre, y: value
  coordinate: { type: 'radial' }
});

// ✅ 正确:径向坐标系中 x 对应角度方向,y 对应半径方向
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'genre', y: 'value' },
  coordinate: { type: 'radial' }
});
```

### 错误 7:多 mark 需要 view+children(详见核心约束 #3)
```javascript
// ❌ 多次 chart.options() → 只有最后一次生效
// ✅ chart.options({ type: 'view', children: [{ type: 'interval', ... }, { type: 'image', ... }] });
```

### 错误 8:image mark 使用错误的编码字段
```javascript
// ❌ 错误:image mark 使用了 x/y 映射图像 URL
chart.options({
  type: 'image',
  data: [{ url: 'https://example.com/image.png' }],
  encode: { x: () => 0, y: () => 0, src: 'url' }  // 不应该用 x/y 来定位图像
});

// ✅ 正确:image mark 使用 src 字段映射图像地址,配合 style 设置尺寸和位置
chart.options({
  type: 'image',
  data: [{ url: 'https://example.com/image.png' }],
  encode: { src: 'url' },
  style: {
    x: '50%',   // 相对于容器的位置
    y: '50%',
    width: 80,
    height: 80
  }
});
```

### 错误 9:交互配置位置错误
```javascript
// ❌ 错误:将交互配置放在 mark 级别之外
chart.options({
  type: 'interval',
  data: [...],
  encode: {...},
  elementHighlight: true  // 放错位置
});

// ✅ 正确:交互配置应放在 interaction 对象中
chart.options({
  type: 'interval',
  data: [...],
  encode: {...},
  interaction: {
    elementHighlight: true
  }
});
```

### 错误 10:区间图未正确使用 y1 通道
```javascript
// ❌ 错误:将区间起点和终点都映射到 y 通道
chart.options({
  type: 'interval',
  data: [{ start: 1, end: 5 }],
  encode: { x: 'name', y: ['start', 'end'] }  // 错误方式
});

// ✅ 正确:使用 y 和 y1 通道分别映射起点和终点
chart.options({
  type: 'interval',
  data: [{ start: 1, end: 5 }],
  encode: { x: 'name', y: 'start', y1: 'end' }
});
```

### 错误 11:坐标轴标签格式化配置错误
```javascript
// ❌ 错误:使用不存在的 axis.labelFormatter 配置
chart.options({
  type: 'interval',
  data: [...],
  axis: {
    x: {
      labelFormatter: (task, item) => {
        const datum = item.data;
        return `${datum.stage}\n${task}`;
      }
    }
  }
});

// ✅ 正确:使用正确的 label 配置方式
chart.options({
  type: 'interval',
  data: [...],
  axis: {
    x: {
      labelTransform: 'rotate(30)',
      labelAutoWrap: true
    }
  }
});
```
references/marks/g2-mark-interval-grouped.md
---
id: "g2-mark-interval-grouped"
title: "G2 分组柱状图"
description: |
  使用 Interval Mark 配合 dodgeX Transform 创建分组柱状图。
  分组柱状图将同类别的多系列数据并排展示,便于横向对比各子类别的绝对数值。

library: "g2"
version: "5.x"
category: "marks"
subcategory: "interval"
tags:
  - "分组柱状图"
  - "grouped bar"
  - "dodgeX"
  - "多系列"
  - "并排"
  - "对比"
  - "spec"

related:
  - "g2-mark-interval-basic"
  - "g2-mark-interval-stacked"
  - "g2-transform-dodgex"

use_cases:
  - "对比同一类别下多个子指标的绝对值"
  - "不同时间段各产品线的销量对比"
  - "多维度数据并排展示"

anti_patterns:
  - "系列数超过 4-5 个时每组柱子过细,可读性差"
  - "关注占比关系时改用堆叠柱状图"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/bar/grouped"
---

## 核心概念

分组柱状图 = `type: 'interval'` + `transform: [{ type: 'dodgeX' }]`。
`dodgeX` 将同一 x 位置的多系列柱体在水平方向上错开排列,避免重叠。

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'interval',
  data: [
    { month: 'Jan', type: '产品A', value: 100 },
    { month: 'Jan', type: '产品B', value: 130 },
    { month: 'Jan', type: '产品C', value: 90  },
    { month: 'Feb', type: '产品A', value: 120 },
    { month: 'Feb', type: '产品B', value: 100 },
    { month: 'Feb', type: '产品C', value: 150 },
    { month: 'Mar', type: '产品A', value: 80  },
    { month: 'Mar', type: '产品B', value: 140 },
    { month: 'Mar', type: '产品C', value: 110 },
  ],
  encode: {
    x: 'month',
    y: 'value',
    color: 'type',
  },
  transform: [{ type: 'dodgeX' }],   // 关键:分组变换
});

chart.render();
```

## 分组条形图(水平方向)

```javascript
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'dodgeX' }],
  coordinate: { transform: [{ type: 'transpose' }] },
});
```

## 分组柱状图 + 数据标签

```javascript
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'dodgeX' }],
  labels: [
    {
      text: 'value',
      position: 'outside',
      style: { fontSize: 11 },
    },
  ],
});
```

## 调整分组间距

```javascript
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [
    {
      type: 'dodgeX',
      padding: 0.1,       // 组内柱子间距(0-1),默认 0
      paddingOuter: 0.1,  // 组间间距
    },
  ],
});
```

## 常见错误与修正

### 错误 1:忘记 dodgeX,柱体重叠
```javascript
// ❌ 错误:多系列数据没有 dodgeX,柱体在同位置叠加
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  // 缺少 transform!
});

// ✅ 正确
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'dodgeX' }],
});
```

### 错误 2:同时用了 stackY 和 dodgeX
```javascript
// ❌ 错误:两个变换冲突,行为不可预期
chart.options({
  transform: [{ type: 'stackY' }, { type: 'dodgeX' }],
});

// ✅ 正确:堆叠和分组是互斥的,选其一
chart.options({ transform: [{ type: 'stackY' }] });   // 堆叠
chart.options({ transform: [{ type: 'dodgeX' }] });   // 分组
```
references/marks/g2-mark-interval-normalized.md
---
id: "g2-mark-interval-normalized"
title: "G2 百分比堆叠柱状图"
description: |
  使用 Interval Mark 配合 stackY + normalizeY Transform 创建百分比堆叠柱状图。
  每组柱体总高度归一化为 100%,聚焦展示各子类别的占比变化,
  消除总量差异的干扰,便于跨组比较结构分布。

library: "g2"
version: "5.x"
category: "marks"
subcategory: "interval"
tags:
  - "百分比堆叠"
  - "normalized"
  - "normalizeY"
  - "占比"
  - "结构分析"
  - "100% stacked bar"
  - "spec"

related:
  - "g2-mark-interval-stacked"
  - "g2-mark-interval-grouped"
  - "g2-transform-normalizey"
  - "g2-transform-stacky"

use_cases:
  - "比较不同组别中各子类别的比例分布"
  - "关注结构变化而非绝对数值时"
  - "消除总量差异,突出占比"

anti_patterns:
  - "需要看绝对数值变化时,改用普通堆叠柱状图"
  - "子类别只有两个时,简单折线图或面积图更直观"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/bar/normalized"
---

## 核心概念

百分比堆叠 = `stackY` + `normalizeY` 两个变换顺序执行:
1. `stackY`:先将各子类别数值堆叠为 y0/y1 区间
2. `normalizeY`:再将每组的 y 值归一化到 [0, 1]

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'interval',
  data: [
    { month: 'Jan', type: 'A', value: 100 },
    { month: 'Jan', type: 'B', value: 200 },
    { month: 'Jan', type: 'C', value: 150 },
    { month: 'Feb', type: 'A', value: 80  },
    { month: 'Feb', type: 'B', value: 220 },
    { month: 'Feb', type: 'C', value: 100 },
    { month: 'Mar', type: 'A', value: 130 },
    { month: 'Mar', type: 'B', value: 180 },
    { month: 'Mar', type: 'C', value: 90  },
  ],
  encode: {
    x: 'month',
    y: 'value',
    color: 'type',
  },
  transform: [
    { type: 'stackY' },      // 1. 先堆叠
    { type: 'normalizeY' },  // 2. 再归一化为百分比
  ],
  axis: {
    y: { labelFormatter: (v) => `${(v * 100).toFixed(0)}%` },
  },
});

chart.render();
```

## 带百分比数据标签

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }, { type: 'normalizeY' }],
  labels: [
    {
      text: (d) => `${(d.value * 100).toFixed(1)}%`,  // 注意:归一化后 value 已是 0-1
      position: 'inside',
      style: {
        fill: 'white',
        fontSize: 11,
        fontWeight: 'bold',
      },
      // 过滤掉占比过小的标签(避免拥挤)
      filter: (d) => d.value > 0.05,
    },
  ],
  axis: {
    y: { labelFormatter: (v) => `${(v * 100).toFixed(0)}%` },
  },
});
```

## 百分比堆叠条形图(水平)

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }, { type: 'normalizeY' }],
  coordinate: { transform: [{ type: 'transpose' }] },
  axis: {
    x: { labelFormatter: (v) => `${(v * 100).toFixed(0)}%` },
  },
});
```

## 常见错误与修正

### 错误:transform 顺序颠倒
```javascript
// ❌ 错误:先 normalizeY 再 stackY,结果不正确
chart.options({
  transform: [{ type: 'normalizeY' }, { type: 'stackY' }],
});

// ✅ 正确:必须先 stackY 后 normalizeY
chart.options({
  transform: [{ type: 'stackY' }, { type: 'normalizeY' }],
});
```
references/marks/g2-mark-interval-stacked.md
---
id: "g2-mark-interval-stacked"
title: "G2 堆叠柱状图"
description: |
  使用 Interval Mark 配合 stackY Transform 创建堆叠柱状图。
  在 Spec 模式中,通过 transform 数组添加 stackY 变换。
  堆叠柱状图用于展示部分与整体的关系及各子类别在总量中的占比变化。

library: "g2"
version: "5.x"
category: "marks"
subcategory: "interval"
tags:
  - "堆叠柱状图"
  - "stacked bar"
  - "StackY"
  - "堆叠"
  - "部分整体"
  - "多系列"
  - "spec"

related:
  - "g2-mark-interval-basic"
  - "g2-mark-interval-grouped"
  - "g2-mark-interval-normalized"
  - "g2-transform-stacky"

use_cases:
  - "展示多个子类别在各时间点的构成"
  - "比较不同类别中各子项的占比"
  - "可视化总量与子项的关系"

anti_patterns:
  - "子类别超过 5-7 个时堆叠图难以阅读,考虑用分组柱状图"
  - "不适合比较单个子类别的趋势(难以对齐基准线)"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/bar/stacked"
---

## 核心概念

堆叠柱状图 = `type: 'interval'` + `transform: [{ type: 'stackY' }]`。
`stackY` 将同一 x 位置的多个数值叠加计算 y0/y1 区间,
使各子类别的柱体在垂直方向上依次堆叠。

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'interval',
  data: [
    { month: 'Jan', type: 'A', value: 100 },
    { month: 'Jan', type: 'B', value: 200 },
    { month: 'Jan', type: 'C', value: 150 },
    { month: 'Feb', type: 'A', value: 120 },
    { month: 'Feb', type: 'B', value: 180 },
    { month: 'Feb', type: 'C', value: 160 },
    { month: 'Mar', type: 'A', value: 90 },
    { month: 'Mar', type: 'B', value: 220 },
    { month: 'Mar', type: 'C', value: 130 },
  ],
  encode: {
    x: 'month',
    y: 'value',
    color: 'type',
  },
  transform: [{ type: 'stackY' }],   // 关键:堆叠变换
});

chart.render();
```

## 带数据标签的堆叠柱状图

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  labels: [
    {
      text: 'value',
      position: 'inside',     // 标签在柱体内部
      style: { fontSize: 11, fill: 'white' },
    },
  ],
});
```

## 堆叠条形图(水平方向)

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
  coordinate: { transform: [{ type: 'transpose' }] },   // 转置为水平条形图
});
```

## 控制堆叠顺序

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'stackY', orderBy: 'value' }],  // 按值大小排序堆叠
});
```

## 百分比堆叠柱状图

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [
    { type: 'stackY' },
    { type: 'normalizeY' },  // 归一化到 [0, 1],即百分比堆叠
  ],
  axis: {
    y: { labelFormatter: (v) => `${(v * 100).toFixed(0)}%` },
  },
});
```

## 常见错误与修正

### 错误 1:忘记 transform stackY
```javascript
// ❌ 错误:多系列数据不会自动堆叠,柱体会在同一位置重叠
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  // 缺少 transform!
});

// ✅ 正确:必须显式声明 stackY
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],   // 必须!
});
```

### 错误 2:同一 (x, color) 组合有重复数据行
```javascript
// ❌ 错误:同月份同类型出现两条数据,stackY 会重复叠加
const badData = [
  { month: 'Jan', type: 'A', value: 100 },
  { month: 'Jan', type: 'A', value: 50 },  // 重复!
];

// ✅ 正确:每个 (x, color) 组合只有一条数据;如需合并请在数据层聚合
```

### 错误 3:transform 写成对象而非数组
```javascript
// ❌ 错误:transform 必须是数组
chart.options({ transform: { type: 'stackY' } });

// ✅ 正确:transform 是数组,支持多个变换链式执行
chart.options({ transform: [{ type: 'stackY' }] });
```
references/marks/g2-mark-k-chart.md
---
id: "g2-mark-k-chart"
title: "G2 K-Chart (Candlestick) Mark"
description: |
  K线图 Mark。使用 link 和 interval 组合,展示股票等金融数据的价格走势。
  适用于股票分析、期货交易、数字货币分析等场景。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "K线图"
  - "蜡烛图"
  - "candlestick"
  - "股票"

related:
  - "g2-mark-line-basic"
  - "g2-mark-boxplot"

use_cases:
  - "股票价格分析"
  - "期货交易"
  - "数字货币分析"

anti_patterns:
  - "非时间序列数据不适合"
  - "单一数值展示应使用折线图"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/mark/candlestick"
---

## 核心概念

K线图展示金融数据的价格走势:
- 使用 `link` 标记表示影线(最高/最低价)
- 使用 `interval` 标记表示实体(开盘/收盘价)
- 颜色区分涨跌

**四价数据:**
- 开盘价(start)
- 收盘价(end)
- 最高价(max)
- 最低价(min)

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  autoFit: true,
});

const data = [
  { time: '2015-11-19', start: 8.18, max: 8.33, min: 7.98, end: 8.32 },
  { time: '2015-11-18', start: 8.37, max: 8.6, min: 8.03, end: 8.09 },
  { time: '2015-11-17', start: 8.7, max: 8.78, min: 8.32, end: 8.37 },
  { time: '2015-11-16', start: 8.48, max: 8.85, min: 8.43, end: 8.7 },
];

chart.options({
  type: 'view',
  data,
  encode: {
    x: 'time',
    color: (d) => (d.start > d.end ? '下跌' : '上涨'),
  },
  scale: {
    color: { domain: ['下跌', '上涨'], range: ['#4daf4a', '#e41a1c'] },
  },
  children: [
    // 影线(最高/最低价)
    {
      type: 'link',
      encode: { y: ['min', 'max'] },
    },
    // 实体(开盘/收盘价)
    {
      type: 'interval',
      encode: { y: ['start', 'end'] },
      style: { fillOpacity: 1 },
    },
  ],
});

chart.render();
```

## 常用变体

### 带成交量

```javascript
// K线图
const kChart = new Chart({ container: 'kChart' });
kChart.options({
  type: 'view',
  data,
  encode: { x: 'time', color: (d) => d.start > d.end ? '下跌' : '上涨' },
  children: [
    { type: 'link', encode: { y: ['min', 'max'] } },
    { type: 'interval', encode: { y: ['start', 'end'] } },
  ],
});

// 成交量图
const volumeChart = new Chart({ container: 'volumeChart' });
volumeChart.options({
  type: 'interval',
  data,
  encode: {
    x: 'time',
    y: 'volume',
    color: (d) => d.start > d.end ? '下跌' : '上涨',
  },
});
```

### Spec 模式

```javascript
chart.options({
  type: 'view',
  data,
  encode: {
    x: 'time',
    color: (d) => d.start > d.end ? '下跌' : '上涨',
  },
  scale: {
    color: { domain: ['下跌', '上涨'], range: ['#4daf4a', '#e41a1c'] },
  },
  children: [
    {
      type: 'link',
      encode: { y: ['min', 'max'] },
    },
    {
      type: 'interval',
      encode: { y: ['start', 'end'] },
      style: { fillOpacity: 1 },
    },
  ],
});
```

### 带坐标轴标题

```javascript
chart.options({
  type: 'view',
  data,
  children: [
    { type: 'link', encode: { y: ['min', 'max'] } },
    {
      type: 'interval',
      encode: { y: ['start', 'end'] },
      axis: {
        y: { title: '价格' },
      },
    },
  ],
});
```

## 完整类型参考

```typescript
interface KChartData {
  time: string;      // 时间
  start: number;     // 开盘价
  end: number;       // 收盘价
  max: number;       // 最高价
  min: number;       // 最低价
  volume?: number;   // 成交量(可选)
}

// K线图由两个图层组成:
// 1. link - 影线(最高/最低价)
// 2. interval - 实体(开盘/收盘价)
```

## K线图 vs 折线图

| 特性 | K线图 | 折线图 |
|------|-------|--------|
| 信息量 | 四价数据 | 单一价格 |
| 用途 | 技术分析 | 趋势展示 |
| 复杂度 | 较高 | 简单 |

## 常见错误与修正

### 错误 1:缺少 link 标记

```javascript
// ❌ 问题:只有实体,没有影线
chart.options({
  type: 'interval',
  encode: { y: ['start', 'end'] },
});

// ✅ 正确:使用 view 组合 link 和 interval
chart.options({
  type: 'view',
  children: [
    { type: 'link', encode: { y: ['min', 'max'] } },
    { type: 'interval', encode: { y: ['start', 'end'] } },
  ],
});
```

### 错误 2:颜色编码错误

```javascript
// ❌ 问题:颜色字段不正确
encode: { color: 'time' }

// ✅ 正确:根据涨跌设置颜色
encode: { color: (d) => d.start > d.end ? '下跌' : '上涨' }
```

### 错误 3:数据顺序错误

```javascript
// ⚠️ 注意:时间数据需要正确排序
scale: {
  x: {
    compare: (a, b) => new Date(a).getTime() - new Date(b).getTime(),
  },
}
```
references/marks/g2-mark-line-basic.md
---
id: "g2-mark-line-basic"
title: "G2 基础折线图(Line Mark)"
description: |
  使用 Line Mark 创建折线图,用于展示数据随时间或有序类别的变化趋势。
  本文采用 Spec 模式(chart.options({})),支持单系列、多系列、平滑曲线等常见变体。

library: "g2"
version: "5.x"
category: "marks"
subcategory: "line"
tags:
  - "折线图"
  - "趋势"
  - "时间序列"
  - "Line"
  - "line chart"
  - "曲线"
  - "多系列"
  - "spec"

related:
  - "g2-mark-area-basic"
  - "g2-core-encode-channel"
  - "g2-scale-time"
  - "g2-interaction-tooltip"

use_cases:
  - "展示数据随时间的变化趋势"
  - "对比多个指标或维度的走势"
  - "展示连续数值的变化"

anti_patterns:
  - "数据点较少(< 5 个)时折线图不直观,改用柱状图"
  - "非有序数据(无序分类)不适合折线图"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/line/basic"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'line',
  data: [
    { month: 'Jan', value: 33 },
    { month: 'Feb', value: 78 },
    { month: 'Mar', value: 56 },
    { month: 'Apr', value: 91 },
    { month: 'May', value: 67 },
    { month: 'Jun', value: 45 },
  ],
  encode: { x: 'month', y: 'value' },
});

chart.render();
```

## 时间序列折线图

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 800, height: 400 });

chart.options({
  type: 'line',
  data: [
    { date: new Date('2024-01-01'), value: 100 },
    { date: new Date('2024-02-01'), value: 130 },
    { date: new Date('2024-03-01'), value: 110 },
    { date: new Date('2024-04-01'), value: 160 },
    { date: new Date('2024-05-01'), value: 145 },
  ],
  encode: {
    x: 'date',     // Date 类型自动使用 Time Scale
    y: 'value',
  },
  axis: {
    x: {
      tickCount: 5,
      labelFormatter: 'YYYY-MM',  // 日期格式化
    },
  },
});

chart.render();
```

## 多系列折线图

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 700, height: 400 });

chart.options({
  type: 'line',
  data: [
    { month: 'Jan', type: '产品A', value: 33 },
    { month: 'Jan', type: '产品B', value: 55 },
    { month: 'Feb', type: '产品A', value: 78 },
    { month: 'Feb', type: '产品B', value: 62 },
    { month: 'Mar', type: '产品A', value: 56 },
    { month: 'Mar', type: '产品B', value: 89 },
    { month: 'Apr', type: '产品A', value: 91 },
    { month: 'Apr', type: '产品B', value: 74 },
  ],
  encode: {
    x: 'month',
    y: 'value',
    color: 'type',   // color 通道自动按 type 拆分多条线
  },
});

chart.render();
```

## 平滑曲线

```javascript
chart.options({
  type: 'line',
  data: [...],
  encode: {
    x: 'month',
    y: 'value',
    shape: 'smooth',    // 'line' | 'smooth' | 'hv' | 'vh' | 'hvh' | 'vhv'
  },
});
```

## 折线 + 数据点(Layer 组合)

```javascript
// Spec 中用 children 数组实现多 Mark 叠加
chart.options({
  type: 'view',               // view 容器
   [...],
  children: [
    {
      type: 'line',
      encode: { x: 'month', y: 'value', color: 'type' },
    },
    {
      type: 'point',
      encode: {
        x: 'month',
        y: 'value',
        color: 'type',
        shape: 'circle',
      },
      style: { r: 4 },
    },
  ],
});
```

## 折线 + 面积填充(Layer 组合)

```javascript
chart.options({
  type: 'view',
  data: [...],
  children: [
    {
      type: 'area',
      encode: { x: 'month', y: 'value' },
      style: { fillOpacity: 0.2 },
    },
    {
      type: 'line',
      encode: { x: 'month', y: 'value' },
      style: { stroke: '#1890ff', lineWidth: 2 },
    },
  ],
});
```

## 带 Tooltip 和末端标签

```javascript
chart.options({
  type: 'line',
  data: [...],
  encode: { x: 'month', y: 'value' },
  tooltip: {
    items: [{ field: 'value', name: '值' }],
  },
  labels: [
    {
      text: 'value',
      selector: 'last',    // 只在最后一个点显示标签
      style: { fontSize: 12, fill: '#1890ff' },
    },
  ],
});
```

## 宽表数据 + fold 转长表

宽表每行含多个指标列,用 `fold` data transform 转为长表再绘制多系列。

⚠️ `fold` 是 **data transform**,必须放在 `data.transform` 中,不能放在 mark 级 `transform`。

```javascript
const wideData = [
  { date: '2024-01', DAU: 12000, MAU: 45000 },
  { date: '2024-02', DAU: 13500, MAU: 47000 },
  { date: '2024-03', DAU: 11800, MAU: 44500 },
];

chart.options({
  type: 'line',
  data: {
    type: 'inline',
    value: wideData,
    transform: [
      {
        type: 'fold',
        fields: ['DAU', 'MAU'],   // 要转换的列
        key: 'metric',             // 新增列名(存原字段名)
        value: 'count',            // 新增列名(存原字段值)
      },
    ],
  },
  encode: {
    x: 'date',
    y: 'count',      // fold 后使用 value 字段名
    color: 'metric', // fold 后使用 key 字段名
  },
  labels: [
    { text: 'metric', selector: 'last', position: 'right' },
  ],
});
```

## 双 Y 轴(不同量级系列)

```javascript
chart.options({
  type: 'view',
  children: [
    {
      type: 'line',
      data: revenueData,
      encode: { x: 'date', y: 'revenue', color: () => '收入(万元)' },
      scale: { y: { key: 'revenue' } },   // key 唯一 → 独立 y 轴
    },
    {
      type: 'line',
      data: userCountData,
      encode: { x: 'date', y: 'count', color: () => '用户数' },
      scale: { y: { key: 'count' } },
      axis: { y: { position: 'right' } },  // 右侧 y 轴
    },
  ],
});
```

## 多系列 Tooltip 配置

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value', color: 'series' },
  tooltip: {
    title: (d) => {
      const date = new Date(d.date);
      return `${date.getFullYear()}年${date.getMonth() + 1}月`;
    },
    items: [
      { field: 'series', name: '系列' },
      { field: 'value', name: '数值', valueFormatter: (v) => v.toLocaleString() },
    ],
  },
  interaction: [{ type: 'tooltip' }],
});
```

## Spec 字段速查

| 字段 | 示例值 | 说明 |
|------|--------|------|
| `encode.x` | `'month'` | X 轴字段 |
| `encode.y` | `'value'` | Y 轴字段 |
| `encode.color` | `'type'` | 颜色/系列区分 |
| `encode.shape` | `'smooth'` | 线型 |
| `style.lineWidth` | `2` | 线宽 |
| `style.stroke` | `'#f00'` | 线条颜色(固定) |
| `labels` | `[{ text: 'value', selector: 'last' }]` | 数据标签 |
| `tooltip` | `{ items: [{ field: 'value' }] }` | Tooltip |

## 常见错误与修正

### 错误 1:多系列数据缺少 color 通道
```javascript
// ❌ 错误:多类型数据没有 color,所有点被错误地连成一条乱线
chart.options({
  type: 'line',
  data: multiSeriesData,
  encode: { x: 'month', y: 'value' },  // 缺少 color!
});

// ✅ 正确
chart.options({
  type: 'line',
  data: multiSeriesData,
  encode: { x: 'month', y: 'value', color: 'type' },
});
```

### 错误 2:时间字段是字符串
```javascript
// ❌ 不推荐:字符串时间轴排序可能不正确
const data = [{ date: '2024-03-01', value: 100 }];

// ✅ 正确:转为 Date 对象,或显式配置 scale type
const data = [{ date: new Date('2024-03-01'), value: 100 }];
// 或:
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
});
```

### 错误 3:多 Mark 叠加需要 view+children(详见核心约束 #3)
```javascript
// ❌ chart.options({ type: 'line', ... }); chart.options({ type: 'point', ... }); → 只有 point 生效
// ✅ chart.options({ type: 'view', data, children: [{ type: 'line', ... }, { type: 'point', ... }] });
```
references/marks/g2-mark-line-multi.md
---
id: "g2-mark-line-multi"
title: "G2 多系列折线图"
description: |
  通过 color 通道编码分类字段实现多系列折线图,每条线代表一个类别。
  G2 会自动为每个 color 值生成独立的折线。
  常用于趋势对比、多指标随时间变化的展示。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "折线图"
  - "多系列"
  - "line"
  - "时间序列"
  - "趋势对比"
  - "多折线"

related:
  - "g2-mark-line-basic"
  - "g2-mark-area-stacked"
  - "g2-transform-select"

use_cases:
  - "多个产品的销售趋势对比"
  - "多地区气温随时间变化"
  - "多指标 KPI 折线对比"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/line/"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const data = [
  { month: 'Jan', city: '北京', temp: -3 },
  { month: 'Feb', city: '北京', temp: 0 },
  { month: 'Mar', city: '北京', temp: 9 },
  { month: 'Apr', city: '北京', temp: 18 },
  { month: 'Jan', city: '上海', temp: 5 },
  { month: 'Feb', city: '上海', temp: 7 },
  { month: 'Mar', city: '上海', temp: 13 },
  { month: 'Apr', city: '上海', temp: 20 },
  { month: 'Jan', city: '广州', temp: 15 },
  { month: 'Feb', city: '广州', temp: 16 },
  { month: 'Mar', city: '广州', temp: 21 },
  { month: 'Apr', city: '广州', temp: 26 },
];

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'line',
  data,
  encode: {
    x: 'month',
    y: 'temp',
    color: 'city',   // 关键:按城市分色,自动生成多条折线
  },
  style: { lineWidth: 2 },
  legend: { color: { position: 'top' } },
});

chart.render();
```

## 折线 + 散点组合(突出数据点)

```javascript
chart.options({
  type: 'view',
  data,
  children: [
    {
      type: 'line',
      encode: { x: 'month', y: 'value', color: 'city' },
      style: { lineWidth: 2 },
    },
    {
      type: 'point',
      encode: { x: 'month', y: 'value', color: 'city', shape: 'circle' },
      style: { r: 4, lineWidth: 1.5, fill: '#fff' },
    },
  ],
});
```

## 折线 + 末端标签

```javascript
chart.options({
  type: 'view',
  children: [
    {
      type: 'line',
       data,
      encode: { x: 'month', y: 'value', color: 'city' },
    },
    {
      type: 'text',
       data,
      encode: {
        x: 'month',
        y: 'value',
        color: 'city',
        text: 'city',
      },
      transform: [{ type: 'selectX', selector: 'last' }],  // 只取每条线末端
      style: { textAnchor: 'start', dx: 6, fontSize: 12 },
    },
  ],
});
```

## 平滑曲线

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  style: {
    lineWidth: 2,
    shape: 'smooth',   // 平滑曲线(代替折线)
  },
});
```

## 常见错误与修正

### 错误:多系列数据使用宽表格式——应使用长表格式
```javascript
// ❌ 错误:宽表格式,G2 无法自动按系列分色
const wrongData = [
  { month: 'Jan', 北京: -3, 上海: 5, 广州: 15 },
  { month: 'Feb', 北京: 0,  上海: 7, 广州: 16 },
];

// ✅ 正确:长表格式(每条记录一个系列的一个数据点)
const correctData = [
  { month: 'Jan', city: '北京', value: -3 },
  { month: 'Jan', city: '上海', value: 5 },
  // ...
];
chart.options({
  encode: { x: 'month', y: 'value', color: 'city' },  // ✅ color 绑定分类字段
});
```
references/marks/g2-mark-linex-liney.md
---
id: "g2-mark-linex-liney"
title: "G2 LineX / LineY 参考线"
description: |
  lineX 绘制垂直参考线(指定 x 值),lineY 绘制水平参考线(指定 y 值)。
  常用于标注均值线、目标线、阈值线等,
  通常与主图放在同一 view 的 children 中。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "lineX"
  - "lineY"
  - "参考线"
  - "均值线"
  - "目标线"
  - "annotation"
  - "标注"

related:
  - "g2-comp-annotation"
  - "g2-mark-rangex"
  - "g2-mark-line-basic"

use_cases:
  - "在散点图中绘制 X/Y 均值线"
  - "添加目标值水平参考线"
  - "标注阈值警戒线"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/annotation/line/"
---

## 最小可运行示例(均值参考线)

```javascript
import { Chart } from '@antv/g2';

const data = [
  { month: 'Jan', value: 83 },
  { month: 'Feb', value: 60 },
  { month: 'Mar', value: 95 },
  { month: 'Apr', value: 72 },
  { month: 'May', value: 110 },
];

const avg = data.reduce((sum, d) => sum + d.value, 0) / data.length;

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'view',
  data,
  children: [
    // 主柱状图
    {
      type: 'interval',
      encode: { x: 'month', y: 'value', color: 'month' },
    },
    // 水平均值参考线
    {
      type: 'lineY',
       [{ y: avg }],    // 参考线的 y 值
      encode: { y: 'y' },
      style: {
        stroke: '#F4664A',
        lineWidth: 1.5,
        lineDash: [6, 3],   // 虚线样式
      },
      labels: [
        {
          text: `均值: ${avg.toFixed(1)}`,
          position: 'right',
          style: { fill: '#F4664A', fontSize: 12 },
        },
      ],
    },
  ],
});

chart.render();
```

## lineX(垂直参考线)

```javascript
// 在散点图中添加 X 轴均值线
const meanX = data.reduce((sum, d) => sum + d.x, 0) / data.length;

{
  type: 'lineX',
  data: [{ x: meanX }],
  encode: { x: 'x' },
  style: { stroke: '#5B8FF9', lineWidth: 1.5, lineDash: [4, 4] },
  labels: [{ text: `x̄=${meanX.toFixed(1)}`, position: 'top' }],
}
```

## 目标线(固定数值)

```javascript
{
  type: 'lineY',
  data: [{ y: 100 }],    // 固定目标值 100
  encode: { y: 'y' },
  style: {
    stroke: '#52c41a',
    lineWidth: 2,
  },
  labels: [
    { text: '目标线 100', position: 'right', style: { fill: '#52c41a' } },
  ],
}
```

## 常见错误与修正

### ❌ 错误:使用不存在的 ruleX / ruleY 类型
```javascript
// ❌ 错误:ruleX、ruleY 是 Vega-Lite 的概念,G2 中不存在
chart.options({ type: 'ruleX', ... });
chart.options({ type: 'ruleY', ... });

// ✅ 正确:G2 中用 lineX / lineY
chart.options({ type: 'lineX', data: [{ x: 5 }], encode: { x: 'x' } });
chart.options({ type: 'lineY', data: [{ y: 100 }], encode: { y: 'y' } });
```

### 错误:data 中 y 字段名与 encode.y 不一致
```javascript
// ❌ 错误:数据中字段是 'value',但 encode.y 写的是 'y'
chart.options({
  type: 'lineY',
  data: [{ value: 100 }],
  encode: { y: 'y' },     // ❌ 字段名不对,找不到 'y' 字段
});

// ✅ 正确:字段名与数据一致
chart.options({
  type: 'lineY',
  data: [{ value: 100 }],
  encode: { y: 'value' }, // ✅
});
// 或者直接用 'y' 字段名:
chart.options({
  type: 'lineY',
  data: [{ y: 100 }],
  encode: { y: 'y' },     // ✅ 字段名统一
});
```

### 错误:lineY 放在 view 外部——与主图比例尺不共享,位置偏移
```javascript
// ❌ 参考线与主图不在同一 view,y 轴范围不共享
chart.options({
  type: 'view',
  children: [
    { type: 'interval', encode: { x: 'month', y: 'sales' } },
  ],
});
// 单独添加 lineY(不在 children 中)——这样无法工作

// ✅ 参考线必须放在同一 view 的 children 数组中
chart.options({
  type: 'view',
  children: [
    { type: 'interval', encode: { x: 'month', y: 'sales' } },
    { type: 'lineY', data: [{ y: 100 }], encode: { y: 'y' } },  // ✅
  ],
});
```
references/marks/g2-mark-link.md
---
id: "g2-mark-link"
title: "G2 Link 连线图(两点间连线)"
description: |
  link mark 在两个数据点之间绘制连线,每条记录有独立的起点(x/y)和终点(x1/y1)。
  与 line mark 不同,link 的每条记录就是一条独立的线段,不需要通过 color/series 分组。
  适合展示迁移、比较、排名变化等场景。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "link"
  - "连线"
  - "坡度图"
  - "slope chart"
  - "迁移图"
  - "两点连线"

related:
  - "g2-mark-line-basic"
  - "g2-mark-point-scatter"

use_cases:
  - "坡度图(展示两个时期的排名/数值变化)"
  - "两个分类之间的连线(迁移、关联)"
  - "起止点数据的路径展示"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/link/"
---

## 最小可运行示例(坡度图 — 排名变化)

```javascript
import { Chart } from '@antv/g2';

// 每条记录代表一个城市从 2022 到 2023 年的排名变化
const data = [
  { city: '北京', rank2022: 1, rank2023: 2 },
  { city: '上海', rank2022: 2, rank2023: 1 },
  { city: '广州', rank2022: 3, rank2023: 5 },
  { city: '深圳', rank2022: 4, rank2023: 3 },
  { city: '成都', rank2022: 5, rank2023: 4 },
];

const chart = new Chart({ container: 'container', width: 400, height: 480 });

chart.options({
  type: 'link',
  data,
  encode: {
    x: ['2022', '2023'],           // x 轴的两个位置(起点/终点)
    y: ['rank2022', 'rank2023'],   // 两个端点的 y 值(起点/终点)
    color: 'city',
  },
  scale: {
    y: { reverse: true },  // 排名从上到下(1 在顶部)
  },
  style: {
    lineWidth: 2,
    strokeOpacity: 0.8,
  },
  // 显示两端散点
  labels: [
    { text: (d) => `${d.city} ${d.rank2022}`, position: 'left' },
    { text: (d) => `${d.rank2023}`, position: 'right' },
  ],
});

chart.render();
```

## 箭头连线

```javascript
chart.options({
  type: 'link',
  data,
  encode: {
    x: ['source_x', 'target_x'],
    y: ['source_y', 'target_y'],
    color: 'type',
  },
  style: {
    lineWidth: 1.5,
    // 末端箭头
    endArrow: true,
    endArrowSize: 8,
  },
});
```

## 常见错误与修正

### 错误:encode.x/y 写成单个字段名——link 需要 [起点字段, 终点字段] 数组
```javascript
// ❌ 错误:x 和 y 是单个字段,只绑定了一端
chart.options({
  type: 'link',
  encode: {
    x: 'x',    // ❌ 只有一个位置
    y: 'y',    // ❌
  },
});

// ✅ 正确:x 和 y 必须是包含两个字段名的数组
chart.options({
  type: 'link',
  encode: {
    x: ['x0', 'x1'],  // ✅ [起点字段, 终点字段]
    y: ['y0', 'y1'],  // ✅
  },
});
```

### 错误:用 line mark 代替 link——多数据系列时行为不同
```javascript
// ❌ 如果每条记录是独立的一段连线,用 line 需要 series 分组,容易出错
chart.options({
  type: 'line',
  encode: { x: 'x', y: 'y', color: 'id' },  // 需要 color 才能分线
});

// ✅ 每条记录一条线段的场景,直接用 link
chart.options({
  type: 'link',
  encode: { x: ['x0', 'x1'], y: ['y0', 'y1'] },  // ✅ 更直观
});
```
references/marks/g2-mark-liquid.md
---
id: "g2-mark-liquid"
title: "G2 水波图(liquid)"
description: |
  liquid mark 用波浪填充的圆形展示单一百分比数值,
  常用于展示完成率、健康指标、KPI 达成率等。
  数据为 0~1 的小数(百分比),内置波浪动画效果。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "liquid"
  - "水波图"
  - "进度"
  - "百分比"
  - "KPI"
  - "完成率"

related:
  - "g2-mark-gauge"
  - "g2-core-chart-init"

use_cases:
  - "展示目标完成率 / KPI 达成率"
  - "展示占比指标(如内存使用率)"
  - "仪表盘中的核心指标可视化"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/other/#liquid"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 300, height: 300 });

chart.options({
  type: 'liquid',
  data: 0.72,    // 0~1 之间的百分比数值
  style: {
    outlineBorder: 4,       // 外框边框宽度
    outlineDistance: 8,     // 外框与内圆的间距
    waveLength: 128,        // 波浪长度
  },
});

chart.render();
```

## 自定义样式

```javascript
chart.options({
  type: 'liquid',
  data: 0.6,
  style: {
    outlineBorder: 4,
    outlineDistance: 8,
    waveLength: 128,
    // 波浪颜色
    fill: '#5B8FF9',
    fillOpacity: 0.85,
    // 背景圆颜色
    background: {
      fill: '#E8F0FE',
    },
    // 中心文字样式
    text: {
      style: {
        fontSize: 28,
        fontWeight: 'bold',
        fill: '#fff',
        // 自定义文字内容(默认显示百分比)
        formatter: (v) => `${(v * 100).toFixed(1)}%`,
      },
    },
  },
  // 不显示坐标轴和图例
  axis: false,
  legend: false,
  tooltip: false,
});
```

## 常见错误与修正

### 错误 1:数值超出 0~1 范围——波浪位置异常
```javascript
// ❌ 错误:液位值是 72(应该是 0.72)
chart.options({
  type: 'liquid',
  data: 72,   // ❌ 应该是 0.72
});

// ✅ 正确:0~1 的小数
chart.options({
  data: 0.72,  // ✅
});
```

### 错误 2:设置了坐标轴——坐标轴在水波图中无意义
```javascript
// ❌ 液位图默认会显示坐标轴,通常需要关闭
chart.options({
  type: 'liquid',
  data: 0.7,
  // ❌ 没有关闭 axis/legend/tooltip
});

// ✅ 推荐关闭多余组件
chart.options({
  type: 'liquid',
  data: 0.7,
  axis: false,     // ✅ 关闭坐标轴
  legend: false,   // ✅ 关闭图例
  tooltip: false,  // ✅ 关闭 tooltip
});
```
references/marks/g2-mark-mosaic.md
---
id: "g2-mark-mosaic"
title: "G2 马赛克图(mosaic)"
description: |
  马赛克图(Mosaic Plot / Marimekko Chart)有两种形式:
  1. 均匀马赛克图:type: 'cell',用颜色和大小展示二维分类数据分布;
  2. 非均匀马赛克图:type: 'interval' + flexX/stackY/normalizeY transform,
     矩形宽度代表类别规模,高度代表内部分布比例;
  3. 密度马赛克图:type: 'rect' + bin transform,展示二维连续数据的分布密度。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "马赛克图"
  - "mosaic"
  - "marimekko"
  - "cell"
  - "flexX"
  - "bin"
  - "热力图"

related:
  - "g2-mark-cell-heatmap"
  - "g2-mark-interval-stacked"

use_cases:
  - "二维分类数据分布(均匀马赛克图)"
  - "市场细分分析(非均匀马赛克图)"
  - "二维连续数据密度分析(密度马赛克图)"

difficulty: "intermediate"
completeness: "full"
created: "2025-04-01"
updated: "2025-04-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/mosaic"
---

## 核心概念

马赛克图有三种实现方式:

| 类型 | mark | 特点 |
|------|------|------|
| 均匀马赛克图 | `cell` | 坐标轴均匀分布,用颜色/大小编码第三维 |
| 非均匀马赛克图 | `interval` + flexX | X 轴宽度按数据比例分配 |
| 密度马赛克图 | `rect` + bin | 连续数据分箱后展示密度 |

## 均匀马赛克图(cell)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  autoFit: true,
  height: 400,
});

chart.options({
  type: 'cell',
   [
    { product: '手机', region: '华北', sales: 120, category: '高端' },
    { product: '手机', region: '华东', sales: 180, category: '高端' },
    { product: '手机', region: '华南', sales: 150, category: '高端' },
    { product: '电脑', region: '华北', sales: 80, category: '中端' },
    { product: '电脑', region: '华东', sales: 110, category: '中端' },
    { product: '电脑', region: '华南', sales: 95, category: '中端' },
    { product: '平板', region: '华北', sales: 60, category: '中端' },
    { product: '平板', region: '华东', sales: 85, category: '中端' },
    { product: '平板', region: '华南', sales: 70, category: '低端' },
    { product: '耳机', region: '华北', sales: 40, category: '低端' },
    { product: '耳机', region: '华东', sales: 55, category: '低端' },
    { product: '耳机', region: '华南', sales: 45, category: '低端' },
  ],
  encode: {
    x: 'product',
    y: 'region',
    color: 'category',
    size: 'sales',      // 用格子大小编码数值
  },
  scale: {
    color: { palette: 'category10', type: 'ordinal' },
    size: { type: 'linear', range: [0.3, 1] },
  },
  style: {
    stroke: '#fff',
    lineWidth: 2,
    inset: 2,
  },
});

chart.render();
```

## 非均匀马赛克图(Marimekko Chart)

矩形宽度按 X 轴字段的总量比例分配,展示市场份额类数据:

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 900,
  height: 600,
  paddingLeft: 0,
  paddingRight: 0,
});

chart.options({
  type: 'interval',
  data: {
    type: 'fetch',
    value: 'https://gw.alipayobjects.com/os/bmw-prod/3041da62-1bf4-4849-aac3-01a387544bf4.csv',
  },
  transform: [
    { type: 'flexX', reducer: 'sum' },  // X轴宽度按 sum 比例分配
    { type: 'stackY' },                  // Y轴堆叠
    { type: 'normalizeY' },              // Y轴归一化到 0-1
  ],
  encode: {
    x: 'market',
    y: 'value',
    color: 'segment',
  },
  axis: {
    y: false,
  },
  scale: {
    x: { paddingOuter: 0, paddingInner: 0.01 },
  },
  tooltip: 'value',
  labels: [
    {
      text: 'segment',
      x: 5,
      y: 5,
      textAlign: 'start',
      textBaseline: 'top',
      fontSize: 10,
      fill: '#fff',
    },
  ],
});

chart.render();
```

## 密度马赛克图(bin transform)

适合展示两个连续字段的分布密度关系:

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  autoFit: true,
});

chart.options({
  type: 'rect',
  data: {
    type: 'fetch',
    value: 'https://assets.antv.antgroup.com/g2/movies.json',
  },
  encode: {
    x: 'IMDB Rating',
    y: 'Rotten Tomatoes Rating',
  },
  transform: [
    { type: 'bin', color: 'count', thresholdsX: 30, thresholdsY: 20 },
  ],
  scale: {
    color: { palette: 'ylGnBu' },
  },
});

chart.render();
```

## 常见错误与修正

### 错误 1:非均匀马赛克图缺少 flexX transform

```javascript
// ❌ 错误:没有 flexX,X 轴宽度均等,不是真正的马赛克图
chart.options({
  type: 'interval',
  data,
  transform: [
    { type: 'stackY' },
    { type: 'normalizeY' },
    // ❌ 缺少 flexX
  ],
  encode: { x: 'market', y: 'value', color: 'segment' },
});

// ✅ 正确:三个 transform 必须同时使用
chart.options({
  type: 'interval',
  data,
  transform: [
    { type: 'flexX', reducer: 'sum' },  // ✅ X轴宽度按比例
    { type: 'stackY' },
    { type: 'normalizeY' },
  ],
  encode: { x: 'market', y: 'value', color: 'segment' },
});
```

### 错误 2:均匀马赛克图使用 interval 而非 cell

```javascript
// ❌ 问题:interval 在均匀网格场景下不如 cell 直观
chart.options({
  type: 'interval',   // ❌ 均匀格子应用 cell
  data,
  encode: { x: 'product', y: 'region', color: 'category' },
});

// ✅ 正确:均匀二维分类用 cell
chart.options({
  type: 'cell',  // ✅
  data,
  encode: { x: 'product', y: 'region', color: 'category' },
});
```

### 错误 3:密度马赛克图用 cell/interval 而非 rect

```javascript
// ❌ 错误:连续数据分箱应用 rect + bin transform
chart.options({
  type: 'cell',   // ❌ cell 适合离散数据
  data,
  encode: { x: 'IMDB Rating', y: 'Rotten Tomatoes Rating' },
});

// ✅ 正确:连续数据分箱用 rect
chart.options({
  type: 'rect',  // ✅
  data,
  encode: { x: 'IMDB Rating', y: 'Rotten Tomatoes Rating' },
  transform: [{ type: 'bin', color: 'count' }],
});
```

## 三种马赛克图对比

| 类型 | 数据类型 | mark | 核心 transform |
|------|---------|------|---------------|
| 均匀马赛克图 | 二维离散 | `cell` | 无 |
| 非均匀马赛克图 | 多维分类+数值 | `interval` | `flexX + stackY + normalizeY` |
| 密度马赛克图 | 二维连续 | `rect` | `bin` |
references/marks/g2-mark-pack.md
---
id: "g2-mark-pack"
title: "G2 圆形打包图(pack)"
description: |
  pack mark 使用圆形打包布局(circle packing)展示层次数据,
  父子关系通过圆的包含关系表达,圆的大小映射数值。
  数据需要是树形结构(包含 children 字段的嵌套数组)或扁平带 parent 的结构。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "pack"
  - "圆形打包"
  - "circle packing"
  - "层次数据"
  - "树形"
  - "嵌套"

related:
  - "g2-mark-treemap"
  - "g2-core-chart-init"

use_cases:
  - "展示层次结构的规模关系(如文件目录大小)"
  - "展示分类的嵌套关系和比例"
  - "组织架构中各部门规模"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/other/#pack"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

// 层次数据(树形结构)
const data = {
  name: '公司',
  children: [
    {
      name: '研发部',
      children: [
        { name: '前端组', value: 12 },
        { name: '后端组', value: 18 },
        { name: '算法组', value: 8 },
      ],
    },
    {
      name: '市场部',
      children: [
        { name: '品牌组', value: 6 },
        { name: '运营组', value: 10 },
      ],
    },
    {
      name: '设计部',
      children: [
        { name: 'UX组', value: 7 },
        { name: '视觉组', value: 5 },
      ],
    },
  ],
};

const chart = new Chart({ container: 'container', width: 600, height: 600 });

chart.options({
  type: 'pack',
  data: {
    value: data,
  },
  encode: {
    value: 'value',   // 叶子节点的数值(决定圆大小)
  },
  style: {
    labelFontSize: 11,
    fillOpacity: 0.8,
  },
  legend: false,
});

chart.render();
```

## 数据配置形式说明

**为什么 pack 使用 ` { value: data }` 而不是 `data`?**

G2 v5 中数据配置有两种形式:

### 简写形式(仅限数组数据)

当数据满足**三个条件**时可使用简写:
1. 内联数据
2. **是数组**
3. 没有数据转换

```javascript
// ✅ 普通图表:数据是数组,可以用简写
const arrayData = [
  { genre: 'Sports', sold: 275 },
  { genre: 'Strategy', sold: 115 },
];

chart.options({
  type: 'interval',
  data: arrayData,  // 简写形式
});
```

### 完整形式(层次数据必须使用)

层次数据是**对象**(包含 name/children),不是数组,必须使用完整形式:

```javascript
// 层次数据是对象,不是数组
const hierarchyData = {
  name: 'root',
  children: [
    { name: 'A', value: 30 },
    { name: 'B', value: 50 },
  ],
};

// ❌ 错误:层次数据不是数组,不能用简写
chart.options({
  type: 'pack',
  data: hierarchyData,  // ❌ 不工作
});

// ✅ 正确:层次数据必须用完整形式
chart.options({
  type: 'pack',
  data: { value: hierarchyData },  // ✅
});
```

### 数据配置对照表

| 数据类型 | 形式 | 示例 |
|---------|------|------|
| 数组数据(无 transform) | 简写 | `data: arrayData` 或 ` [...]` |
| 数组数据(有 transform) | 完整 | ` { value: [...], transform: [...] }` |
| 层次数据(对象) | 完整 | ` { value: { name, children } }` |
| 远程数据 | 完整 | `data: { type: 'fetch', value: 'url' }` |

---

## 常见错误与修正

### 错误 1:data 直接传树形对象

```javascript
// ❌ 错误:层次数据不是数组,不能用简写形式
chart.options({
  type: 'pack',
  data: hierarchyData,   // ❌ 直接传树形对象不工作
});

// ✅ 正确:层次数据必须用 { value: treeData } 形式
chart.options({
  type: 'pack',
  data: { value: hierarchyData },  // ✅
});
```

### 错误 2:叶子节点没有 value 字段——所有圆大小相同

```javascript
// ❌ 叶子节点没有数值字段,所有节点大小相同(不能展示差异)
const data = {
  value: {
    name: 'root',
    children: [
      { name: 'A' },  // ❌ 没有 value
      { name: 'B' },
    ],
  }
};

// ✅ 叶子节点加 value 字段
const data = {
  value: {
    name: 'root',
    children: [
      { name: 'A', value: 30 },  // ✅
      { name: 'B', value: 50 },
    ],
  }
}
```

### 错误 3:encode.color 使用字符串字段名导致所有圆颜色相同

```javascript
// ❌ 错误:color: 'name' 等价于 d['name'],层次节点上没有 name 属性 → undefined
chart.options({
  type: 'pack',
  data: { value: data },
  encode: {
    value: 'value',
    color: 'name',   // ❌ d['name'] = undefined → 所有圆显示相同颜色
  },
});

// ✅ 正确:color 必须用回调函数,通过 d.data 访问原始字段
chart.options({
  type: 'pack',
  data: { value: data },
  encode: {
    value: 'value',
    color: (d) => d.data?.name,           // ✅ 按节点自身名称着色
    // 或按父节点着色(同门类同色,更直观):
    // color: (d) => d.path?.[1] || d.data?.name,
  },
});
```

**为什么 `value: 'value'` 字符串可以用,`color: 'name'` 字符串不行?**
G2 对层次 mark 的 `value` 通道有**专项处理**,直接读取 d3-hierarchy 计算好的节点 `.value` 属性;而 `color`、`shape` 等其他通道走通用路径,用字符串直接做 `datum[field]`,拿到的是层次节点而非原始数据,`datum['name']` 自然是 `undefined`。

### 错误 4:labels 中直接使用 d.name 导致 undefined

```javascript
// ❌ 错误:pack 节点的原始字段在 d.data 中,d.name 是 undefined
labels: [
  {
    text: (d) => `${d.name}\n${d.value?.toLocaleString()}`,  // ❌ d.name 是 undefined
  },
]

// ✅ 正确:通过 d.data 访问原始数据字段
labels: [
  {
    text: (d) => {
      if (d.height > 0) return '';  // 父节点不显示文字
      return `${d.data?.name}\n${d.value?.toLocaleString()}`;  // ✅
    },
    position: 'inside',
    fontSize: 10,
    fill: '#000',
  },
]
```

**根本原因**:层次图中 G2 将原始数据封装为层次节点,`d` 本身是节点对象(含 `depth`、`height`、`value` 等内置字段),原始数据对象整体存放在 `d.data` 下。

### 错误 5:混淆 data.value 和节点的 value 字段

```javascript
// ⚠️ 注意区分两个不同的 value:
// 1. data.value - 数据配置的值(可以是任意数据)
// 2. 节点的 value 字段 - 叶子节点的数值(决定圆大小)

// ✅ 正确理解
chart.options({
  type: 'pack',
  data: {
    value: {           // 这是数据配置的 value
      name: 'root',
      children: [
        { name: 'A', value: 30 },  // 这是节点的 value 字段
      ],
    },
  },
  encode: {
    value: 'value',    // 映射节点的 value 字段到圆大小
  },
});
```

---

## 节点数据访问规则(重要!)

层次结构图中,回调函数(encode、labels 的 text 等)接收到的参数 `d` **不是原始数据对象**,而是 G2 用 d3-hierarchy 包装后的层次节点,**原始数据在 `d.data` 中**。

### 为什么 `encode.color: 'name'` 不起作用?

**根本原因**:当 encode 是字符串时,G2 内部做的是 `datum[fieldName]`,即直接访问节点对象的属性。对于层次 mark,`datum` 是层次节点(hierarchy node),不是原始数据对象:

```
d['name']        → undefined  ❌(层次节点没有 name 属性)
d.data['name']   → '前端组'  ✅(原始数据在 d.data 上)
```

**特例**:`encode.value: 'value'` 看起来用字符串也能工作,是因为 G2 对层次 mark 的 `value` 通道做了**专项处理**,直接读取节点的 `value` 属性(d3-hierarchy 计算后的值)。其他通道(`color`、`shape` 等)没有这个特殊处理,字符串会直接 `datum[field]` 导致 `undefined`。

```javascript
// ❌ encode.color: 'name' 的内部执行等价于:
const color = datum['name']  // datum 是层次节点,'name' 属性不在节点上 → undefined
// 结果:所有圆使用相同颜色(undefined 被映射为默认颜色)

// ✅ 使用回调才能正确访问:
const color = datum.data?.['name']  // datum.data 才是原始数据对象
```

### 回调参数 d 的结构

```javascript
// d 是 d3-hierarchy 节点,结构如下:
{
  value: 100,              // 节点数值(d3 计算的叶子值之和)
  depth: 2,                // 层级深度(0 = 根节点)
  height: 0,               // 子树高度(叶子节点为 0)
  data: {                  // ← 原始数据在这里!
    name: '前端组',
    value: 12,
    category: 'tech',
    // ... 其它自定义字段
  },
  path: ['root', '技术', '前端'],  // 从根到当前节点的路径
}
```

### encode 中访问字段

```javascript
// ❌ 错误:字符串字段名对 color/shape 等通道不起作用,返回 undefined
encode: {
  value: 'value',   // ✅ value 通道有专项处理,字符串可用
  color: 'name',    // ❌ 等价于 d['name'] = undefined,所有圆颜色相同
}

// ✅ 正确:除 value 外的所有通道必须用回调函数
encode: {
  value: 'value',
  color: (d) => d.data?.name,  // ✅ 通过 d.data 访问原始字段
}
```

### labels 中访问字段

```javascript
// ❌ 错误:d.name 是 undefined,因为原始字段在 d.data 中
labels: [
  {
    text: (d) => `${d.name}\n${d.value}`,  // ❌ d.name 是 undefined
  },
]

// ✅ 正确:通过 d.data 访问原始字段
labels: [
  {
    text: (d) => `${d.data?.name}\n${d.value?.toLocaleString()}`,  // ✅
    position: 'inside',
    fontSize: 10,
    fill: '#000',
  },
]
```

### 常用访问模式

```javascript
// 原始字段(name、category 等自定义字段)— 必须通过 d.data 访问
d.data?.name
d.data?.category
d.data?.type

// 层次节点内置字段(不需要 .data)— 可直接访问
d.value    // 节点数值(d3 计算的子树总和)
d.depth    // 层级深度(0 = 根节点)
d.height   // 子树高度(叶子节点为 0)

// 常用着色策略
color: (d) => d.path?.[1] || d.data?.name   // 按第二层父节点着色(推荐,同门类同色)
color: (d) => d.depth                        // 按层级深度着色
color: (d) => d.data?.name                   // 按当前节点名称着色
color: (d) => d.data?.category               // 按自定义字段着色
color: (d) => d.value                        // 按数值大小着色
```

### 完整带 labels 的示例

```javascript
chart.options({
  type: 'pack',
  data: { value: data },
  encode: {
    value: 'value',
    color: (d) => d.path?.[1] || d.data?.name,
  },
  style: {
    stroke: '#fff',
    lineWidth: 1,
    fillOpacity: 0.8,
  },
  labels: [
    {
      text: (d) => {
        // 只在叶子节点(height === 0)显示文本,避免父节点文字遮挡
        if (d.height > 0) return '';
        return `${d.data?.name}\n${d.value?.toLocaleString()}`;
      },
      position: 'inside',
      fontSize: 10,
      fill: '#000',
    },
  ],
  legend: false,
});
```

### 配合 scale 自定义颜色

```javascript
encode: {
  value: 'value',
  color: (d) => d.data?.category,
},
scale: {
  color: {
    type: 'sequential',
    palette: 'blues',
  },
}
```
references/marks/g2-mark-parallel.md
---
id: "g2-mark-parallel"
title: "G2 Parallel Coordinates Mark"
description: |
  平行坐标系 Mark。使用 line 标记配合 parallel 坐标系,展示多维数据之间的关系。
  适用于多维数据关系分析、数据聚类识别等场景。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "平行坐标系"
  - "parallel"
  - "多维数据"
  - "关系分析"

related:
  - "g2-mark-radar"
  - "g2-mark-sankey"

use_cases:
  - "多维数据关系分析"
  - "数据聚类识别"
  - "特征工程"

anti_patterns:
  - "维度 <3 应使用散点图"
  - "数据量过大(>1000)不适合"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/mark/parallel"
---

## 核心概念

平行坐标系展示多维数据关系:
- 使用 `line` 标记
- 配合 `parallel` 坐标系
- 每条线代表一个数据记录的多个维度值

**关键特点:**
- 每个轴代表不同维度
- 轴之间没有因果关系
- 轴顺序可以调整

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  theme: 'classic',
});

chart.options({
  type: 'line',
  autoFit: true,
  data: {
    type: 'fetch',
    value: 'https://assets.antv.antgroup.com/g2/cars3.json',
  },
  coordinate: { type: 'parallel' },
  encode: {
    position: [
      'economy (mpg)',
      'cylinders',
      'displacement (cc)',
      'power (hp)',
    ],
    color: 'weight (lb)',
  },
  style: {
    lineWidth: 1.5,
    strokeOpacity: 0.4,
  },
});

chart.render();
```

## 常用变体

### 水平布局

```javascript
chart.options({
  type: 'line',
  coordinate: {
    type: 'parallel',
    transform: [{ type: 'transpose' }],
  },
  encode: {
    position: ['dim1', 'dim2', 'dim3'],
    color: 'category',
  },
});
```

### 带交互刷选

```javascript
chart.options({
  type: 'line',
  coordinate: { type: 'parallel' },
  data,
  encode: { position: ['A', 'B', 'C', 'D'], color: 'group' },
  interaction: {
    brushAxisHighlight: {
      maskFill: '#d8d0c0',
      maskOpacity: 0.3,
    },
  },
  state: {
    active: { lineWidth: 3, strokeOpacity: 1 },
    inactive: { stroke: '#ccc', opacity: 0.3 },
  },
});
```

### 平滑曲线

```javascript
chart.options({
  type: 'line',
  coordinate: { type: 'parallel' },
  data,
  encode: {
    position: ['A', 'B', 'C'],
    color: 'category',
    shape: 'smooth',  // 平滑曲线
  },
});
```

## 完整类型参考

```typescript
interface ParallelOptions {
  type: 'line';
  coordinate: {
    type: 'parallel';
    transform?: [{ type: 'transpose' }];
  };
  encode: {
    position: string[];  // 多个维度字段
    color?: string;      // 分类字段
  };
  style: {
    lineWidth?: number;
    strokeOpacity?: number;
  };
}
```

## 平行坐标 vs 折线图

| 特性 | 平行坐标系 | 折线图 |
|------|------------|--------|
| 用途 | 多维关系 | 时间趋势 |
| 轴含义 | 不同维度 | 时间序列 |
| 线含义 | 一条记录 | 一个指标 |

## 常见错误与修正

### 错误 1:使用错误坐标系

```javascript
// ❌ 问题:使用默认坐标系
coordinate: {}

// ✅ 正确:使用 parallel 坐标系
coordinate: { type: 'parallel' }
```

### 错误 2:position 编码错误

```javascript
// ❌ 问题:使用 x/y 编码
encode: { x: 'dim1', y: 'dim2' }

// ✅ 正确:使用 position 数组
encode: { position: ['dim1', 'dim2', 'dim3'] }
```

### 错误 3:维度过少

```javascript
// ⚠️ 注意:维度数量建议 >= 4
// 2-3 个维度应使用散点图
```
references/marks/g2-mark-partition.md
---
id: "g2-mark-partition"
title: "G2 矩形分区图(partition)"
description: |
  partition mark 用矩形(冰柱/icicle)布局展示层次数据,每层从父节点位置开始向下延伸,
  子节点宽度按值比例填满父节点宽度。使用笛卡尔坐标,横轴表示值域,纵轴表示层级深度。
  属于 @antv/g2 核心,无需额外扩展库。
  注意:partition 与 sunburst 是两个独立的 mark,不可混用:
  partition 为矩形布局(直角坐标),sunburst 为圆环布局(极坐标,来自 @antv/g2-extension-plot)。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "partition"
  - "矩形分区"
  - "icicle"
  - "冰柱图"
  - "层次数据"
  - "hierarchy"
  - "下钻"
  - "drillDown"

related:
  - "g2-mark-treemap"
  - "g2-mark-sunburst"
  - "g2-interaction-drilldown"
  - "g2-mark-pack"

use_cases:
  - "层次数据的矩形分区展示(如调用栈火焰图、文件目录结构)"
  - "多层类别数据的占比可视化"
  - "支持下钻交互的层次结构探索"

anti_patterns:
  - "不要用 partition 画圆环旭日图,应使用 sunburst(需要 @antv/g2-extension-plot)"
  - "不要把 data 写成 { value: treeRoot },partition 的 data 是数组形式"
  - "不要用 d.data?.name 访问字段,partition 回调拿到的是已平铺的 record,直接用 d.name"
  - "所有节点(包括根节点和中间节点)都必须显式设置 value 字段——partition 不会自动累加子节点;根节点缺少 value 时所有矩形宽度为 0,全部叠在 x=0 处"
  - "不要用 d['ancestor-node'] 做分支着色——该字段实际等于节点自身 name;分支着色应使用 d.path[1] || d.path[0]"
  - "encode.color 函数的返回值是颜色通道的域键(domain key),scale.color.domain 必须与函数实际返回值完全一致——若函数返回 hex 色值而 domain 填数据名,Ordinal scale 会把 hex 追加进 domain,图例出现 #E63946 这类乱码条目"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-04-27"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/graph/hierarchy/#partition"
---

## partition vs sunburst 对比

| 特性 | partition(矩形分区)| sunburst(旭日图)|
|------|----------------------|-------------------|
| 来源 | `@antv/g2` 核心,无需扩展 | `@antv/g2-extension-plot`,需要 `extend` |
| 坐标系 | 笛卡尔坐标(直角)| 极坐标(同心圆)|
| 视觉形态 | 矩形冰柱/icicle | 同心圆环 |
| data 格式 | 数组 `[treeRoot]` | `{ value: treeRoot }` |
| 回调中字段访问 | 直接 `d.name`、`d.depth`、`d.value` | 直接 `d.name`、`d.depth`、`d.path`(字符串)|

## 最小可运行示例

数据结构:单根数组,`name`/`value`/`children` 三个字段。**所有节点(含根节点和中间节点)都必须显式设置 `value`**——partition 不会自动累加子节点,根节点缺少 `value` 将导致所有矩形宽度为 0 并叠在一起。

以下 mock 数据模拟年度预算分配(4 大类别,3 层深度):

```javascript
import { Chart } from '@antv/g2';

const data = [
  {
    name: '年度预算',
    value: 1550,
    children: [
      {
        name: '产品研发',
        value: 600,
        children: [
          {
            name: '前端',
            value: 220,
            children: [
              { name: 'React', value: 90 },
              { name: 'Vue', value: 80 },
              { name: 'CSS', value: 50 },
            ],
          },
          {
            name: '后端',
            value: 230,
            children: [
              { name: 'Java', value: 100 },
              { name: 'Python', value: 80 },
              { name: 'Go', value: 50 },
            ],
          },
          {
            name: '移动端',
            value: 150,
            children: [
              { name: 'iOS', value: 80 },
              { name: 'Android', value: 70 },
            ],
          },
        ],
      },
      {
        name: '市场营销',
        value: 400,
        children: [
          {
            name: '数字营销',
            value: 180,
            children: [
              { name: 'SEO', value: 70 },
              { name: 'SEM', value: 60 },
              { name: '社媒', value: 50 },
            ],
          },
          {
            name: '品牌推广',
            value: 130,
            children: [
              { name: '设计', value: 70 },
              { name: '内容', value: 60 },
            ],
          },
          {
            name: '活动运营',
            value: 90,
            children: [
              { name: '线上', value: 50 },
              { name: '线下', value: 40 },
            ],
          },
        ],
      },
      {
        name: '运营支持',
        value: 300,
        children: [
          {
            name: '客户服务',
            value: 130,
            children: [
              { name: '售前', value: 60 },
              { name: '售后', value: 70 },
            ],
          },
          {
            name: '数据分析',
            value: 100,
            children: [
              { name: 'BI', value: 60 },
              { name: '算法', value: 40 },
            ],
          },
          {
            name: '技术支持',
            value: 70,
            children: [
              { name: '运维', value: 40 },
              { name: '安全', value: 30 },
            ],
          },
        ],
      },
      {
        name: '基础设施',
        value: 250,
        children: [
          {
            name: '云计算',
            value: 120,
            children: [
              { name: 'AWS', value: 60 },
              { name: '自建IDC', value: 60 },
            ],
          },
          {
            name: '工具链',
            value: 130,
            children: [
              { name: 'CI/CD', value: 50 },
              { name: '监控', value: 40 },
              { name: '日志', value: 40 },
            ],
          },
        ],
      },
    ],
  },
];

const chart = new Chart({ container: 'container', autoFit: true, height: 400 });

chart.options({
  type: 'partition',
  data,
  encode: {
    value: 'value',
    color: (d) => d.path[1] || d.path[0],
  },
  scale: {
    color: {
      range: [
        'rgb(91, 143, 249)',
        'rgb(90, 216, 166)',
        'rgb(246, 189, 22)',
        'rgb(232, 104, 74)',
        'rgb(154, 100, 220)',
      ],
    },
  },
  labels: [
    {
      text: 'name',
      position: 'inside',
      transform: [{ type: 'overflowHide' }],
    },
  ],
  style: { inset: 0.5 },
  axis: { x: { title: '预算(万元)' } },
});

chart.render();
```

## 数据格式说明

`partition` 的 `data` 是**数组**,每项是一个树根节点(支持多根)。

**关键:所有节点必须显式设置 `value`**,partition 布局不会自动累加子节点的值。非叶节点的 `value` 应等于其所有叶子节点 `value` 之和。

```javascript
// ✅ 正确:根节点和中间节点都显式设置 value
chart.options({
  type: 'partition',
  data: [
    {
      name: 'root',
      value: 300,          // ← 根节点必须有 value(= 所有叶子之和)
      children: [
        {
          name: 'A',
          value: 200,      // ← 中间节点也必须有 value
          children: [
            { name: 'A1', value: 120 },
            { name: 'A2', value: 80 },
          ],
        },
        { name: 'B', value: 100 },
      ],
    },
  ],
});

// ❌ 错误:根节点缺少 value → 所有矩形宽度为 0,全部叠在 x=0 处
chart.options({
  type: 'partition',
  data: [{ name: 'root', children: [...] }],   // ❌ 缺少 value,图表崩溃
});

// ❌ 错误:不能用 { value: treeRoot } 对象包装(sunburst 的写法)
chart.options({
  type: 'partition',
  data: { value: { name: 'root', children: [...] } },   // ❌ 不工作
});
```

## 回调函数中的数据结构

`partition` 在渲染前会将树形数据展平,回调中拿到的 `d` 是**已展平的 record**,直接访问字段:

```javascript
// d 的结构(展平后)
{
  name: 'diffProps',                                    // 节点名称
  value: 120,                                           // 节点数值
  depth: 3,                                             // 层级深度(根节点为 0)
  path: ['main', 'render', 'reconcile', 'diffProps'],  // 从根到当前节点的路径数组
  'ancestor-node': 'diffProps',  // 注意:等于节点自身 name,不是一级祖先名
  childNodeCount: 0,             // 子节点数量(叶子节点为 0)
  x: [x0, x1],                  // 水平位置区间
  y: [y0, y1],                   // 垂直位置区间(即层级)
}
```

**按分支(一级类别)着色**:使用 `d.path[1]`(路径第 2 元素 = 一级子节点名),而不是 `d['ancestor-node']`(它等于节点自身名,无分组效果):

```javascript
// ✅ 按分支着色(同一一级子树同色)
encode: { color: (d) => d.path[1] || d.path[0] }
// path[1] 对根节点为 undefined,回退到 path[0](根节点自身名)

// ✅ 按节点名着色(每个节点独立颜色)
encode: { color: 'name' }

// ✅ 按层级深度着色
encode: { color: (d) => d.depth }

// ❌ 错误:ancestor-node 等于节点自身 name,不是"一级祖先"
encode: { color: (d) => d['ancestor-node'] }  // 等效于 d.name,无分组效果

// ❌ 错误:partition 不使用 d3-hierarchy 包装,d.data 不存在
encode: { color: (d) => d.data?.name }
```

## labels 位置选择

- **浅树(≤ 6 层)**:用 `position: 'inside'`,文字显示在矩形内部,太窄时由 `overflowHide` 自动隐藏
- **深树(> 6 层)**:用 `position: 'left'` + `dx: 8`,文字从矩形左边缘开始,适合行高较小的场景

```javascript
// 浅树(推荐)
labels: [{ text: 'name', position: 'inside', transform: [{ type: 'overflowHide' }] }]

// 深树(官方 example 用法,适合 10+ 层)
labels: [{ text: 'name', position: 'left', dx: 8, transform: [{ type: 'overflowHide' }] }]
```

## layout 配置

```javascript
chart.options({
  type: 'partition',
  data: [...],
  encode: { value: 'value', color: 'name' },
  layout: {
    sort: (a, b) => b.value - a.value,  // 按值降序排列子节点
    fillParent: true,                   // 子节点填满父节点宽度(默认 true)
    // valueField: 'value',             // 数值字段名(默认 'value')
    // nameField: 'name',               // 名称字段名(默认 'name')
  },
});
```

## 带下钻交互

`partition` 内置 `drillDown` 交互,点击节点可下钻:

```javascript
chart.options({
  type: 'partition',
  data: [...],
  encode: { value: 'value', color: 'name' },
  interaction: {
    drillDown: true,  // 默认已开启
  },
});
```

## 常见错误与修正

### 错误 1:根节点缺少 value → 所有矩形叠在 x=0

partition 布局用 `node.value` 计算根节点宽度(`x1 = x0 + value`)。根节点 `value` 为 0 时,宽度为 0,所有子节点也从 `x=0` 开始且宽度由各自 value 决定,导致兄弟节点严重重叠。

```javascript
// ❌ 错误:根节点无 value,子节点起点均为 x=0,发生重叠
chart.options({
  type: 'partition',
  data: [
    {
      name: 'root',
      // value 缺失!
      children: [
        { name: 'A', value: 150 },  // x=[0, 150]
        { name: 'B', value: 200 },  // x=[0, 200] ← 与 A 重叠!
      ],
    },
  ],
});

// ✅ 正确:所有节点显式设置 value
chart.options({
  type: 'partition',
  data: [
    {
      name: 'root',
      value: 350,   // ← 必须有,等于所有叶子 value 之和
      children: [
        { name: 'A', value: 150 },  // x=[0, 150]
        { name: 'B', value: 200 },  // x=[150, 350] ← 正确
      ],
    },
  ],
});
```

### 错误 2:把 partition 和 sunburst 混用
```javascript
// ❌ 错误:用 partition 配极坐标想得到旭日图
chart.options({
  type: 'partition',
  coordinate: { type: 'polar' },  // ❌ partition 不支持极坐标旭日图
});

// ✅ 正确:旭日图应使用 sunburst(需要 @antv/g2-extension-plot)
import { plotlib } from '@antv/g2-extension-plot';
import { Runtime, corelib, extend } from '@antv/g2';
const Chart = extend(Runtime, { ...corelib(), ...plotlib() });

chart.options({
  type: 'sunburst',
  data: { value: treeRoot },  // sunburst 使用 { value: root } 对象
  encode: { value: 'sum' },
});
```

### 错误 3:data 使用 sunburst 的对象格式
```javascript
// ❌ 错误:partition 不接受 { value: root } 对象
chart.options({
  type: 'partition',
  data: { value: { name: 'root', children: [...] } },
});

// ✅ 正确:partition 使用数组,且根节点需显式设置 value
chart.options({
  type: 'partition',
  data: [{ name: 'root', value: 1000, children: [...] }],
});
```

### 错误 4:labels 中用 d.data?.name 访问字段
```javascript
// ❌ 错误:d3-hierarchy 写法,partition 已展平,d.data 不存在
labels: [{ text: (d) => d.data?.name }]

// ✅ 正确:直接访问展平后字段
labels: [{ text: 'name' }]
labels: [{ text: (d) => d.name }]
```

### 错误 5:误用 ancestor-node 做分支着色
```javascript
// ❌ 错误:ancestor-node 等于节点自身 name,所有节点颜色各不相同,无分组效果
encode: { color: (d) => d['ancestor-node'] }

// ✅ 正确:用 path[1] 取一级子节点名实现分支着色
encode: { color: (d) => d.path[1] || d.path[0] }
```

### 错误 6:encode.color 函数返回值与 scale.color.domain 不匹配

`encode.color` 的函数返回值是颜色通道的**域键(domain key)**,`scale.color.domain` 必须与函数实际返回的值完全一致。

若函数返回 hex 色值而 `domain` 填数据名,`@antv/scale` 的 Ordinal scale 会把未知的 hex 字符串**动态追加**进 domain,导致图例出现 `#E63946` 这类乱码条目。

```javascript
// ❌ 错误:函数返回 hex 色值,但 domain 是数据字段名
// Ordinal scale 把 '#E63946'/'#BDBDBD' 追加到 domain,图例出现 hex 字符串条目
encode: {
  color: (d) => ['产品研发', '基础设施'].includes(d.path[1]) ? '#E63946' : '#BDBDBD',
},
scale: {
  color: {
    domain: ['产品研发', '市场营销', '运营支持', '基础设施'],  // ❌ 与函数返回值不匹配
    range: ['#E63946', '#BDBDBD', '#BDBDBD', '#E63946'],
  },
},
// 结果:domain 被污染为 ['产品研发', '市场营销', '运营支持', '基础设施', '#E63946', '#BDBDBD']
// 图例显示 6 个条目,其中包含 '#E63946'、'#BDBDBD' 字符串

// ✅ 正确:函数返回语义标签,scale.domain 精确匹配返回值
encode: {
  color: (d) => ['产品研发', '基础设施'].includes(d.path[1]) ? 'highlight' : 'muted',
},
scale: {
  color: {
    domain: ['highlight', 'muted'],  // ✅ 与函数返回值完全一致
    range: ['#E63946', '#BDBDBD'],
  },
},
// 图例只显示 2 个语义条目,颜色映射正确
```

若不需要图例,只想指定颜色映射,可直接用 `range` 不设 `domain`(利用 Ordinal 的自动 domain 收集):

```javascript
// ✅ 不需要控制图例标签时:直接用 range,让 Ordinal 自动收集 domain
encode: { color: (d) => d.path[1] || d.path[0] },
scale: {
  color: {
    range: ['rgb(91,143,249)', 'rgb(90,216,166)', 'rgb(246,189,22)', 'rgb(232,104,74)'],
  },
},
```
references/marks/g2-mark-path.md
---
id: "g2-mark-path"
title: "G2 路径标注(path)"
description: |
  path mark 通过 SVG 路径字符串(d 属性)绘制任意形状,
  适合自定义图形、地图轮廓、流程图箭头等无法用标准 mark 表达的形状。
  与 line mark 区别:line 连接数据点坐标,path 直接使用 SVG d 路径字符串。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "path"
  - "路径"
  - "SVG"
  - "自定义形状"
  - "annotation"

related:
  - "g2-mark-polygon"
  - "g2-mark-link"
  - "g2-mark-connector"

use_cases:
  - "绘制自定义 SVG 路径形状"
  - "地图轮廓(非 GeoJSON)标注"
  - "自定义箭头和流程图元素"

difficulty: "advanced"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/mark/path"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

// path mark 通过 d 字段提供 SVG 路径字符串
const pathData = [
  {
    d: 'M 100 200 C 100 100 400 100 400 200 S 700 300 700 200',
    color: '#5B8FF9',
    label: '曲线路径',
  },
  {
    d: 'M 100 350 L 250 300 L 400 350 L 550 300 L 700 350',
    color: '#FF6B6B',
    label: '折线路径',
  },
];

chart.options({
  type: 'view',
  width: 640,
  height: 480,
  children: [
    {
      type: 'path',
      data: pathData,
      encode: {
        d: 'd',          // SVG 路径字符串字段
        color: 'color',
      },
      style: {
        lineWidth: 2,
        fillOpacity: 0,  // 路径通常只显示描边
      },
    },
  ],
});

chart.render();
```

## 带填充的封闭路径

```javascript
// 封闭路径(Z 命令)可以填充颜色
const shapes = [
  {
    d: 'M 200 100 L 300 300 L 100 300 Z',  // 三角形
    category: 'triangle',
  },
  {
    d: 'M 450 100 L 550 150 L 550 250 L 450 300 L 350 250 L 350 150 Z',  // 六边形
    category: 'hexagon',
  },
];

chart.options({
  type: 'path',
  data: shapes,
  encode: {
    d: 'd',
    color: 'category',
  },
  style: {
    fillOpacity: 0.3,
    lineWidth: 2,
  },
});
```

## 常见错误与修正

### 错误:path mark 使用 x/y encode——path 不支持坐标编码
```javascript
// ❌ path mark 不使用 x/y,而是使用 d(SVG 路径字符串)
chart.options({
  type: 'path',
  encode: { x: 'x', y: 'y' },  // ❌ path mark 不支持坐标编码
});

// ✅ path mark 使用 d 字段提供完整的 SVG 路径
chart.options({
  type: 'path',
  encode: { d: 'd' },           // ✅ d 字段为 SVG 路径字符串
  style: { lineWidth: 2 },
});
```

### 错误:path 与 line 混淆——连接数据点应用 line
```javascript
// 连接多个数据坐标点 → 用 line mark
chart.options({ type: 'line', encode: { x: 'date', y: 'value' } });

// 自定义 SVG 路径形状 → 用 path mark
chart.options({ type: 'path', encode: { d: 'pathString' } });
```
references/marks/g2-mark-point-bubble.md
---
id: "g2-mark-point-bubble"
title: "G2 气泡图(bubble chart)"
description: |
  气泡图是散点图的扩展,用第三个通道 size(气泡大小)编码额外的数值维度。
  通过 encode.size 绑定数值字段,G2 自动将数值映射为圆的面积(而非半径)。
  适合同时展示三个数值维度的关系。
  
  样式要点:
  1. 不要使用白色描边(stroke: '#fff'),浅色主题下会显得像错误图表;
  2. 推荐径向渐变填充(radial-gradient)+ 阴影(shadow),模拟 3D 球体质感;
  3. 定义颜色映射表 COLOR_MAP,scale.color.range 和 fill 回调共用,保持一致;
  4. size 比例尺推荐使用 sqrt 类型,确保气泡面积与数值成正比;
  5. 合理设置 size.range(建议 [4, 40] 区间),避免极端大小差异;
  6. 坐标轴使用虚线网格(gridLineDash),背景更清爽。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "气泡图"
  - "bubble"
  - "散点图"
  - "point"
  - "三维度"
  - "size"

related:
  - "g2-mark-point-scatter"
  - "g2-scale-linear"
  - "g2-scale-pow-sqrt"

use_cases:
  - "三维度数据关系(如 GDP、人口、预期寿命)"
  - "用气泡大小表达第三个指标"
  - "对比矩阵中的强度展示"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/point/#bubble"
---

## 最小可运行示例

经典气泡图设计:径向渐变填充 + 阴影 + sqrt 比例尺 + 虚线网格 + 双系列对比。

```javascript
import { Chart } from '@antv/g2';

// 数据:GDP、预期寿命、人口、国家、年份
const data1990 = [
  { income: 28604, life: 77,    population: 17096869, country: 'Australia' },
  { income: 31163, life: 77.4,  population: 27662440, country: 'Canada' },
  { income: 1516,  life: 68,    population: 1154605773, country: 'China' },
  { income: 13670, life: 74.7,  population: 10582082, country: 'Cuba' },
  { income: 28599, life: 75,    population: 4986705, country: 'Finland' },
  { income: 29476, life: 77.1,  population: 56943299, country: 'France' },
  { income: 31476, life: 75.4,  population: 78958237, country: 'Germany' },
  { income: 28666, life: 78.1,  population: 254830, country: 'Iceland' },
  { income: 1777,  life: 57.7,  population: 870601776, country: 'India' },
  { income: 29550, life: 79.1,  population: 122249285, country: 'Japan' },
  { income: 2076,  life: 67.9,  population: 20194354, country: 'North Korea' },
  { income: 12087, life: 72,    population: 42972254, country: 'South Korea' },
  { income: 24021, life: 75.4,  population: 3397534, country: 'New Zealand' },
  { income: 43296, life: 76.8,  population: 4240375, country: 'Norway' },
  { income: 10088, life: 70.8,  population: 38195258, country: 'Poland' },
  { income: 19349, life: 69.6,  population: 147568552, country: 'Russia' },
  { income: 10670, life: 67.3,  population: 53994605, country: 'Turkey' },
  { income: 26424, life: 75.7,  population: 57110117, country: 'United Kingdom' },
  { income: 37062, life: 75.4,  population: 252847810, country: 'United States' },
];

const data2015 = [
  { income: 44056, life: 81.8,  population: 23968973, country: 'Australia' },
  { income: 43294, life: 81.7,  population: 35939927, country: 'Canada' },
  { income: 13334, life: 76.9,  population: 1376048943, country: 'China' },
  { income: 21291, life: 78.5,  population: 11389562, country: 'Cuba' },
  { income: 38923, life: 80.8,  population: 5503457, country: 'Finland' },
  { income: 37599, life: 81.9,  population: 64395345, country: 'France' },
  { income: 44053, life: 81.1,  population: 80688545, country: 'Germany' },
  { income: 42182, life: 82.8,  population: 329425, country: 'Iceland' },
  { income: 5903,  life: 66.8,  population: 1311050527, country: 'India' },
  { income: 36162, life: 83.5,  population: 126573481, country: 'Japan' },
  { income: 1390,  life: 71.4,  population: 25155317, country: 'North Korea' },
  { income: 34644, life: 80.7,  population: 50293439, country: 'South Korea' },
  { income: 34186, life: 80.6,  population: 4528526, country: 'New Zealand' },
  { income: 64304, life: 81.6,  population: 5210967, country: 'Norway' },
  { income: 24787, life: 77.3,  population: 38611794, country: 'Poland' },
  { income: 23038, life: 73.13, population: 143456918, country: 'Russia' },
  { income: 19360, life: 76.5,  population: 78665830, country: 'Turkey' },
  { income: 38225, life: 81.4,  population: 64715810, country: 'United Kingdom' },
  { income: 53354, life: 79.1,  population: 321773631, country: 'United States' },
];

const allData = [
  ...data1990.map(d => ({ ...d, year: '1990' })),
  ...data2015.map(d => ({ ...d, year: '2015' })),
];

// 颜色映射表:scale.color.range 和 fill 回调共用,保持一致
const COLOR_MAP = { '1990': '#fb7678', '2015': '#81e7ee' };

const chart = new Chart({ container: 'container', width: 800, height: 500 });

chart.options({
  type: 'view',
  data: allData,
  children: [
    {
      type: 'point',
      encode: {
        x: 'income',
        y: 'life',
        size: 'population',
        color: 'year',
        shape: 'point',
      },
      scale: {
        size: { type: 'sqrt', range: [4, 40] },   // ✅ sqrt 比例尺:确保面积与数值成正比
        color: { domain: ['1990', '2015'], range: Object.values(COLOR_MAP) },
        y: { nice: true },
      },
      style: {
        fillOpacity: 0.85,
        lineWidth: 0,
        // ✅ 径向渐变:从白色中心到映射色边缘,模拟 3D 球体感
        // 通过 COLOR_MAP[datum.year] 获取颜色,与 scale.color.range 保持一致
        // 注意:channel.color[index] 存的是映射前的原始值(如 '1990'),不是映射后的颜色
        fill: (datum) => {
          const color = COLOR_MAP[datum.year];
          return `radial-gradient(circle at 35% 35%, rgb(255,255,255) 0%, ${color} 100%)`;
        },
        // ✅ 阴影:让气泡有浮起感
        shadowBlur: 10,
        shadowColor: 'rgba(0, 0, 0, 0.15)',
        shadowOffsetY: 5,
      },
      legend: { size: false },
      labels: [
        { text: 'country', position: 'outside', fontSize: 11, fill: '#333',
          transform: [{ type: 'overlapDodgeY' }] },
      ],
      tooltip: {
        title: (d) => `${d.country} (${d.year})`,
        items: [
          { channel: 'x', name: '人均GDP', valueFormatter: (v) => `$${v}` },
          { channel: 'y', name: '预期寿命', valueFormatter: (v) => `${v}岁` },
          { channel: 'size', name: '人口', valueFormatter: (v) => `${(v / 1e6).toFixed(1)}M` },
        ],
      },
    },
  ],
  axis: {
    x: { title: '人均GDP ($)', grid: true, gridLineDash: [4, 4], gridStrokeOpacity: 0.3 },
    y: { title: '预期寿命 (岁)', grid: true, gridLineDash: [4, 4], gridStrokeOpacity: 0.3 },
  },
});

chart.render();
```

> **设计要点**:
> - **径向渐变**(`radial-gradient`)— 从白色中心到映射色边缘,模拟 3D 球体质感
> - **阴影**(`shadowBlur` + `shadowColor` + `shadowOffsetY`)— 让气泡有浮起感
> - **sqrt 比例尺** — 确保气泡面积与数值成正比,而非半径
> - **虚线网格**(`gridLineDash: [4, 4]`)— 背景更清爽不喧宾夺主
> - **双系列对比**(1990 vs 2015)— 用颜色区分时间维度
> - **隐藏 size 图例**(`legend: { size: false }`)— size 图例对用户意义不大

## 配置 size 比例尺

```javascript
scale: {
  size: {
    type: 'sqrt',    // ✅ 推荐:sqrt 比例尺,确保气泡面积与数值成正比
    range: [4, 40],  // [最小半径, 最大半径] (px)
    // 注意:G2 用面积而非半径映射,视觉上更准确
    // 建议 range 区间适中(4~40),避免极端大小差异导致遮挡或不可见
  },
}
```

> **为什么用 sqrt?** 气泡图的 size 通道映射的是圆的面积。如果使用 linear 比例尺,面积与数值的映射不是线性的(面积 = πr²)。
> 使用 sqrt 比例尺后,半径 = √数值,面积 = π(√数值)² = π×数值,实现**面积与数值的线性映射**。
> 详细说明见 [sqrt 比例尺文档](g2-scale-pow-sqrt.md)。

## 气泡样式最佳实践

### ✅ 推荐:径向渐变 + 阴影 + sqrt 比例尺 + 虚线网格
```javascript
chart.options({
  type: 'point',
  encode: { x: 'income', y: 'life', size: 'population', color: 'year' },
  scale: {
    size: { type: 'sqrt', range: [4, 40] },    // sqrt 比例尺
    color: { domain: ['1990', '2015'], range: Object.values(COLOR_MAP) },
  },
  style: {
    fillOpacity: 0.85,
    lineWidth: 0,
    // 径向渐变:从白色中心到映射色边缘,模拟 3D 球体感
    // 通过 COLOR_MAP[datum.year] 获取颜色,与 scale.color.range 保持一致
    fill: (datum) => {
      const color = COLOR_MAP[datum.year];
      return `radial-gradient(circle at 35% 35%, rgb(255,255,255) 0%, ${color} 100%)`;
    },
    // 阴影:让气泡有浮起感
    shadowBlur: 10,
    shadowColor: 'rgba(0, 0, 0, 0.15)',
    shadowOffsetY: 5,
  },
  legend: { size: false },
  axis: {
    x: { grid: true, gridLineDash: [4, 4], gridStrokeOpacity: 0.3 },
    y: { grid: true, gridLineDash: [4, 4], gridStrokeOpacity: 0.3 },
  },
});
```

### ❌ 避免:白色描边 + 低透明度填充(浅色主题下像错误图表)
```javascript
// ❌ 浅色主题下,白色描边 + 半透明填充会让气泡看起来像空心/残缺
chart.options({
  style: {
    fillOpacity: 0.7,   // 太低,气泡显得空洞
    stroke: '#fff',      // 白色描边在浅色背景上几乎不可见,像没有描边
    lineWidth: 1,
  },
});
```

## 常见错误与修正

### 错误 1:size 通道绑定字符串类别而不是数值
```javascript
// ❌ 错误:size 通道应绑定数值字段,而不是类别
chart.options({
  encode: {
    size: 'country',  // ❌ 字符串,无法映射为大小
  },
});

// ✅ 正确:size 绑定数值字段
chart.options({
  encode: {
    size: 'population',  // ✅ 数值,可映射为气泡大小
  },
});
```

### 错误 2:没有设置 scale.size.range——气泡太小或太大
```javascript
// ❌ 默认 range 可能导致气泡尺寸不合适(遮挡其他数据或几乎不可见)
chart.options({
  encode: { size: 'value' },
  // ❌ 没有 scale.size.range
});

// ✅ 明确设置合适的气泡大小范围,建议 [4, 40] 区间
chart.options({
  encode: { size: 'value' },
  scale: {
    size: { type: 'sqrt', range: [4, 40] },  // ✅ sqrt 比例尺 + 合适的视觉范围
  },
});
```

### 错误 3:使用白色描边(stroke: '#fff')——浅色主题下像错误图表
```javascript
// ❌ 白色描边在浅色背景上几乎不可见,加上低 fillOpacity 让气泡显得空心/残缺
chart.options({
  style: { fillOpacity: 0.7, stroke: '#fff', lineWidth: 1 },
});

// ✅ 去掉描边,改用径向渐变 + 阴影 + 较高 fillOpacity
chart.options({
  style: {
    fillOpacity: 0.85,
    lineWidth: 0,
    fill: (datum) => {
      // 径向渐变:从白色中心到映射色边缘,模拟 3D 球体感
      const color = COLOR_MAP[datum.year];
      return `radial-gradient(circle at 35% 35%, rgb(255,255,255) 0%, ${color} 100%)`;
    },
    shadowBlur: 10,
    shadowColor: 'rgba(0, 0, 0, 0.15)',
    shadowOffsetY: 5,
  },
});
```

### 错误 4:气泡重叠导致信息丢失
```javascript
// ❌ 数据点密集时气泡互相遮挡
chart.options({ type: 'point', encode: { x: 'gdp', y: 'life', size: 'population' } });

// ✅ 添加 overlapDodgeY 标签防重叠
chart.options({
  type: 'point',
  encode: { x: 'gdp', y: 'life', size: 'population' },
  labels: [{ text: 'country', transform: [{ type: 'overlapDodgeY' }] }],
});
```
references/marks/g2-mark-point-scatter.md
---
id: "g2-mark-point-scatter"
title: "G2 散点图(Point Mark)"
description: |
  使用 Point Mark 创建散点图,通过 x/y 位置展示两个数值变量的相关性。
  本文采用 Spec 模式(chart.options({})),支持气泡图(size 通道)、分类着色、自定义形状等变体。

library: "g2"
version: "5.x"
category: "marks"
subcategory: "point"
tags:
  - "散点图"
  - "气泡图"
  - "Point"
  - "scatter"
  - "bubble"
  - "相关性"
  - "分布"
  - "spec"

related:
  - "g2-core-encode-channel"
  - "g2-scale-linear"
  - "g2-interaction-tooltip"

use_cases:
  - "展示两个连续变量的相关性"
  - "发现数据分布和异常值"
  - "用气泡图展示三维数据(x/y/size)"

anti_patterns:
  - "数据点超过 10000 个时性能较差,考虑使用密度图"
  - "两轴都是分类变量时,散点图意义不大"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/point/scatter"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'point',
  data: [
    { x: 10, y: 30, category: 'A' },
    { x: 20, y: 50, category: 'B' },
    { x: 30, y: 20, category: 'A' },
    { x: 40, y: 80, category: 'B' },
    { x: 50, y: 40, category: 'A' },
    { x: 60, y: 65, category: 'B' },
  ],
  encode: {
    x: 'x',
    y: 'y',
    color: 'category',
  },
});

chart.render();
```

## 气泡图(三维数据)

气泡图样式要点:不要使用白色描边(浅色主题下像错误图表),推荐径向渐变 + 阴影 + 较高 fillOpacity;
size 比例尺推荐使用 sqrt 类型,合理设置 size.range(建议 [4, 40]),隐藏 size 图例,
坐标轴使用虚线网格,标签用 overlapDodgeY 防重叠。

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 700, height: 500 });

const data1990 = [
  { income: 28604, life: 77,    population: 17096869, country: 'Australia' },
  { income: 31163, life: 77.4,  population: 27662440, country: 'Canada' },
  { income: 1516,  life: 68,    population: 1154605773, country: 'China' },
  { income: 29476, life: 77.1,  population: 56943299, country: 'France' },
  { income: 29550, life: 79.1,  population: 122249285, country: 'Japan' },
  { income: 37062, life: 75.4,  population: 252847810, country: 'United States' },
];

const data2015 = [
  { income: 44056, life: 81.8,  population: 23968973, country: 'Australia' },
  { income: 43294, life: 81.7,  population: 35939927, country: 'Canada' },
  { income: 13334, life: 76.9,  population: 1376048943, country: 'China' },
  { income: 37599, life: 81.9,  population: 64395345, country: 'France' },
  { income: 36162, life: 83.5,  population: 126573481, country: 'Japan' },
  { income: 53354, life: 79.1,  population: 321773631, country: 'United States' },
];

const allData = [
  ...data1990.map(d => ({ ...d, year: '1990' })),
  ...data2015.map(d => ({ ...d, year: '2015' })),
];

// 颜色映射表:scale.color.range 和 fill 回调共用
const COLOR_MAP = { '1990': '#fb7678', '2015': '#81e7ee' };

chart.options({
  type: 'point',
  data: allData,
  encode: {
    x: 'income',
    y: 'life',
    size: 'population',    // 气泡大小 = 第三个维度
    color: 'year',
    shape: 'point',
  },
  scale: {
    size: { type: 'sqrt', range: [4, 40] },    // sqrt 比例尺 + 合适的气泡范围
    color: { domain: ['1990', '2015'], range: Object.values(COLOR_MAP) },
  },
  style: {
    fillOpacity: 0.85,
    lineWidth: 0,
    // 径向渐变 + 阴影:模拟 3D 球体质感
    // 通过 COLOR_MAP[datum.year] 获取颜色,与 scale.color.range 保持一致
    fill: (datum) => {
      const color = COLOR_MAP[datum.year];
      return `radial-gradient(circle at 35% 35%, rgb(255,255,255) 0%, ${color} 100%)`;
    },
    shadowBlur: 10,
    shadowColor: 'rgba(0, 0, 0, 0.15)',
    shadowOffsetY: 5,
  },
  legend: { size: false },    // size 图例意义不大,建议隐藏
  labels: [
    { text: 'country', fontSize: 12, fontWeight: 700, fill: '#2D3748', dy: 10,
      transform: [{ type: 'overlapDodgeY' }] },
  ],
  axis: {
    x: { grid: true, gridLineDash: [4, 4], gridStrokeOpacity: 0.3 },
    y: { grid: true, gridLineDash: [4, 4], gridStrokeOpacity: 0.3 },
  },
  tooltip: {
    title: (d) => `${d.country} (${d.year})`,
    items: [
      { field: 'income',     name: '人均收入' },
      { field: 'life',       name: '预期寿命' },
      { field: 'population', name: '人口', valueFormatter: (v) => `${(v / 1e6).toFixed(1)}M` },
    ],
  },
});

chart.render();
```

> 气泡图完整样式指南(含径向渐变、阴影等)见 [气泡图文档](g2-mark-point-bubble.md)。

## 自定义点形状

```javascript
chart.options({
  type: 'point',
  data: [...],
  encode: {
    x: 'x',
    y: 'y',
    color: 'type',
    shape: 'type',    // 将 type 字段映射到形状通道
  },
  scale: {
    shape: {
      range: ['circle', 'square', 'triangle', 'diamond'],
    },
  },
});
```

## 散点图 + 趋势线

```javascript
// 用 type: 'view' + children 叠加散点和回归趋势线
chart.options({
  type: 'view',
  data: [...],
  children: [
    {
      type: 'point',
      encode: { x: 'x', y: 'y' },
    },
    {
      type: 'line',
      encode: { x: 'x', y: 'y' },
      transform: [{ type: 'regression' }],
      style: { stroke: '#f00', lineWidth: 1.5 },
    },
  ],
});
```

## 常见错误与修正

### 错误 1:大数据量性能问题
```javascript
// ❌ 注意:十万个点会导致渲染缓慢
chart.options({ type: 'point', data: hugeDataWith100000Points, encode: { x: 'x', y: 'y' } });

// ✅ 优化方案 1:先在数据层面采样
chart.options({ type: 'point',  sampledData, encode: { x: 'x', y: 'y' } });

// ✅ 优化方案 2:改用密度图展示分布
chart.options({ type: 'density',  [...], encode: { x: 'x', y: 'y' } });
```

### 错误 2:size 通道使用字符串常量
```javascript
// ❌ 误解:size 传字符串会被当作字段名
chart.options({ type: 'point', encode: { size: '10' } });  // 寻找名为 '10' 的字段

// ✅ 正确:固定大小用数字,数据映射用字段名字符串
chart.options({ type: 'point', encode: { size: 10 } });           // 固定大小 10
chart.options({ type: 'point', encode: { size: 'population' } }); // 映射字段
```
references/marks/g2-mark-polygon.md
---
id: "g2-mark-polygon"
title: "G2 多边形图(polygon)"
description: |
  polygon mark 根据多个 x/y 通道坐标绘制任意多边形,
  每条记录对应一个多边形,坐标点通过 x、x1、x2...和 y、y1、y2...通道传入。
  常用于地图区域着色、Voronoi 图等自定义形状场景。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "polygon"
  - "多边形"
  - "Voronoi"
  - "地图"
  - "自定义形状"

related:
  - "g2-mark-image"
  - "g2-mark-path"

use_cases:
  - "Voronoi 图(自然邻域分区)"
  - "自定义形状的区域着色"
  - "地理区域多边形着色(非标准地图)"

difficulty: "advanced"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/other/#polygon"
---

## 最小可运行示例(Voronoi 图)

```javascript
import { Chart } from '@antv/g2';
import { Delaunay } from 'd3-delaunay';

// 随机生成点,计算 Voronoi 分区
const points = Array.from({ length: 30 }, () => [
  Math.random() * 600,
  Math.random() * 400,
]);

const delaunay = Delaunay.from(points);
const voronoi = delaunay.voronoi([0, 0, 600, 400]);

// 将 Voronoi 多边形转换为 G2 数据格式
const polygonData = Array.from({ length: points.length }, (_, i) => {
  const cell = voronoi.cellPolygon(i);
  if (!cell) return null;
  return {
    x: cell.map(([px]) => px),
    y: cell.map(([, py]) => py),
    index: i,
  };
}).filter(Boolean);

const chart = new Chart({ container: 'container', width: 600, height: 400 });

chart.options({
  type: 'polygon',
  data: polygonData,
  encode: {
    x: 'x',    // 多边形各顶点的 x 坐标数组
    y: 'y',    // 多边形各顶点的 y 坐标数组
    color: 'index',
  },
  scale: {
    x: { domain: [0, 600] },   // 指定坐标范围(type 默认为 linear)
    y: { domain: [0, 400] },
    color: { type: 'ordinal' },
  },
  style: {
    fillOpacity: 0.6,
    stroke: '#fff',
    lineWidth: 1,
  },
  axis: false,
  legend: false,
});

chart.render();
```

## 数据格式说明

```javascript
// polygon mark 的数据格式:每条记录的 x/y 字段是数组(多边形顶点序列)
const data = [
  {
    x: [10, 50, 90, 10],   // 多边形顶点 x 坐标(按顺序,首尾自动闭合)
    y: [10, 80, 10, 10],   // 多边形顶点 y 坐标
    category: 'A',
  },
  {
    x: [100, 150, 200],    // 三角形
    y: [20, 100, 20],
    category: 'B',
  },
];

chart.options({
  type: 'polygon',
  data,
  encode: { x: 'x', y: 'y', color: 'category' },
});
```

## 常见错误与修正

### 错误:x/y 传入单个数值而不是数组
```javascript
// ❌ 错误:polygon 的 x/y 必须是坐标数组,不是单个值
chart.options({
  type: 'polygon',
  data: [{ x: 100, y: 200, ... }],  // ❌ x/y 是单值,只是一个点
  encode: { x: 'x', y: 'y' },
});

// ✅ 正确:x/y 是坐标数组
chart.options({
  type: 'polygon',
  data: [{ x: [10, 50, 90], y: [10, 80, 10], ... }],  // ✅ 数组
  encode: { x: 'x', y: 'y' },
});
```
references/marks/g2-mark-radar.md
---
id: "g2-mark-radar"
title: "G2 雷达图(polar 坐标 + area/line)"
description: |
  G2 v5 雷达图通过 coordinate: { type: 'polar' } + area + line Mark 组合实现,
  数据采用长表格式,encode.x 映射维度字段,encode.y 映射数值字段,
  encode.color 区分多个系列。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "雷达图"
  - "radar"
  - "polar"
  - "极坐标"
  - "蜘蛛网图"
  - "多维度"
  - "spec"

related:
  - "g2-core-view-composition"
  - "g2-mark-area-basic"
  - "g2-mark-line-basic"

use_cases:
  - "多维度能力/指标对比(如 KPI 雷达图)"
  - "多个对象的综合评分对比"
  - "运动员/产品多维评测"

anti_patterns:
  - "维度超过 8 个时,标签会重叠,改用平行坐标图"
  - "各维度量纲差异过大时,需先归一化"

difficulty: "intermediate"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/radar"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 480,
  height: 480,
});

const data = [
  { item: '设计', type: '产品A', score: 70 },
  { item: '开发', type: '产品A', score: 60 },
  { item: '营销', type: '产品A', score: 50 },
  { item: '运营', type: '产品A', score: 80 },
  { item: '服务', type: '产品A', score: 90 },
  { item: '设计', type: '产品B', score: 40 },
  { item: '开发', type: '产品B', score: 75 },
  { item: '营销', type: '产品B', score: 85 },
  { item: '运营', type: '产品B', score: 55 },
  { item: '服务', type: '产品B', score: 65 },
];

chart.options({
  type: 'view',
  data,
  coordinate: { type: 'polar' },   // 关键:极坐标
  scale: {
    x: { padding: 0.5, align: 0 },
    y: { tickCount: 5, domainMin: 0, domainMax: 100 },
  },
  axis: {
    x: { grid: true },
    y: { zIndex: 1, title: false },
  },
  children: [
    {
      type: 'area',
      encode: { x: 'item', y: 'score', color: 'type' },
      style: { fillOpacity: 0.2 },
    },
    {
      type: 'line',
      encode: { x: 'item', y: 'score', color: 'type' },
      style: { lineWidth: 2 },
    },
  ],
});

chart.render();
```

## 带数据点的雷达图

```javascript
chart.options({
  type: 'view',
  data,
  coordinate: { type: 'polar' },
  scale: {
    x: { padding: 0.5, align: 0 },
    y: { tickCount: 5, domainMin: 0 },
  },
  axis: {
    x: { grid: true, labelFontSize: 13 },
    y: { zIndex: 1, title: false, label: false },  // 隐藏 y 轴标签(只显示网格)
  },
  children: [
    {
      type: 'area',
      encode: { x: 'item', y: 'score', color: 'type' },
      style: { fillOpacity: 0.15 },
    },
    {
      type: 'line',
      encode: { x: 'item', y: 'score', color: 'type' },
      style: { lineWidth: 2 },
    },
    {
      type: 'point',
      encode: { x: 'item', y: 'score', color: 'type' },
      style: { r: 4, fill: 'white', lineWidth: 2 },
    },
  ],
  legend: { color: { position: 'top' } },
  interaction: [{ type: 'tooltip' }],
});
```

## 单系列雷达图(纯色填充)

```javascript
const singleData = [
  { item: '攻击', score: 85 },
  { item: '防御', score: 72 },
  { item: '速度', score: 90 },
  { item: '魔法', score: 60 },
  { item: '体力', score: 78 },
  { item: '运气', score: 66 },
];

chart.options({
  type: 'view',
  data: singleData,
  coordinate: { type: 'polar' },
  scale: {
    x: { padding: 0.5, align: 0 },
    y: { tickCount: 4, domainMin: 0, domainMax: 100 },
  },
  axis: {
    x: { grid: true, labelFontSize: 14 },
    y: { zIndex: 1, title: false },
  },
  children: [
    {
      type: 'area',
      encode: { x: 'item', y: 'score' },
      style: { fill: '#1890ff', fillOpacity: 0.25 },
    },
    {
      type: 'line',
      encode: { x: 'item', y: 'score' },
      style: { stroke: '#1890ff', lineWidth: 2 },
    },
    {
      type: 'point',
      encode: { x: 'item', y: 'score' },
      style: { r: 5, fill: '#1890ff' },
      labels: [{ text: (d) => d.score, position: 'top', style: { fontSize: 11 } }],
    },
  ],
});
```

## 常见错误与修正

### 错误 1:忘记设置 polar 坐标,变成普通面积折线图
```javascript
// ❌ 缺少 coordinate,渲染出的是普通折线图而非雷达图
chart.options({
  type: 'view',
  data,
  // 忘记了 coordinate: { type: 'polar' }
  children: [{ type: 'area', ... }],
});

// ✅ 正确:必须声明 polar 坐标
chart.options({
  type: 'view',
  data,
  coordinate: { type: 'polar' },   // ✅ 关键
  children: [{ type: 'area', ... }],
});
```

### 错误 2:数据格式使用宽表

```javascript
// ❌ 宽表格式无法直接用 color 区分系列
const wrongData = [
  { item: '设计', A: 70, B: 40 },
  { item: '开发', A: 60, B: 75 },
];

// ✅ 正确:使用长表格式(每行一个数据点 + 系列字段)
const correctData = [
  { item: '设计', type: 'A', score: 70 },
  { item: '设计', type: 'B', score: 40 },
  { item: '开发', type: 'A', score: 60 },
  { item: '开发', type: 'B', score: 75 },
];
```

### 错误 3:各维度量纲不统一导致视觉失真

```javascript
// ❌ 不同维度量级差异大(0-100 vs 0-10000),图形严重失真
const data = [
  { item: '销售额', score: 8500 },   // 万元
  { item: '评分',   score: 85 },     // 百分制
];

// ✅ 先归一化到 0-100 再绘制
const normalized = data.map(d => ({
  ...d,
  score: (d.score / maxScores[d.item]) * 100,
}));
```
references/marks/g2-mark-radial-bar.md
---
id: "g2-mark-radial-bar"
title: "G2 Radial Bar Chart Mark"
description: |
  玉珏图/径向柱状图 Mark。使用 interval 标记配合 radial 坐标系,以环形方式展示分类数据对比。
  适用于审美需求较高的数据展示场景。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "玉珏图"
  - "径向柱状图"
  - "radial bar"
  - "环形柱状图"

related:
  - "g2-mark-interval-basic"
  - "g2-mark-rose"

use_cases:
  - "分类数据对比"
  - "美观展示"
  - "大屏可视化"

anti_patterns:
  - "精确数值对比应使用柱状图"
  - "数据必须排序"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/mark/radial-bar"
---

## 核心概念

玉珏图(径向柱状图)是柱状图在极坐标系下的变换:
- 使用 `interval` 标记
- 配合 `radial` 坐标系
- 以弧长表示数值大小

**注意事项:**
- 存在半径反馈效应,外圈看起来更大
- 数据必须排序
- 更适合审美展示而非精确对比

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  theme: 'classic',
});

chart.options({
  type: 'interval',
  data: [
    { question: '台海关系', percent: 0.21 },
    { question: '军事力量', percent: 0.47 },
    { question: '环境影响', percent: 0.49 },
  ],
  coordinate: { type: 'radial', innerRadius: 0.2 },
  encode: {
    x: 'question',
    y: 'percent',
    color: 'question',
  },
  style: {
    radiusTopLeft: 4,
    radiusTopRight: 4,
  },
});

chart.render();
```

## 常用变体

### 指定角度范围

```javascript
chart.options({
  type: 'interval',
  coordinate: {
    type: 'radial',
    innerRadius: 0.3,
    startAngle: -Math.PI,
    endAngle: -0.25 * Math.PI,
  },
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
});
```

### 带标签

```javascript
chart.options({
  type: 'interval',
  coordinate: { type: 'radial', innerRadius: 0.2 },
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
  labels: [
    {
      text: 'value',
      position: 'inside',
      style: { fontWeight: 'bold', fill: 'white' },
    },
  ],
});
```

### 配合交互

```javascript
chart.options({
  type: 'interval',
  coordinate: { type: 'radial', innerRadius: 0.2 },
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
  interaction: [
    { type: 'elementHighlight', background: true },
  ],
});
```

## 完整类型参考

```typescript
interface RadialBarOptions {
  type: 'interval';
  coordinate: {
    type: 'radial';
    innerRadius?: number;    // 内半径
    startAngle?: number;     // 起始角度
    endAngle?: number;       // 结束角度
  };
  encode: {
    x: string;    // 分类字段(映射到角度)
    y: string;    // 数值字段(映射到半径)
    color?: string;
  };
}
```

## 玉珏图 vs 柱状图

| 特性 | 玉珏图 | 柱状图 |
|------|--------|--------|
| 坐标系 | 极坐标 | 直角坐标 |
| 视觉效果 | 更美观 | 更精确 |
| 数据对比 | 有半径效应 | 准确对比 |

## 常见错误与修正

### 错误 1:数据未排序

```javascript
// ❌ 问题:未排序会导致视觉误导
data: [{ category: 'A', value: 100 }, { category: 'B', value: 50 }]

// ✅ 正确:数据按值排序
data: [{ category: 'B', value: 50 }, { category: 'A', value: 100 }]
```

### 错误 2:使用 polar 坐标系

```javascript
// ❌ 问题:polar 是玫瑰图坐标系
coordinate: { type: 'polar' }

// ✅ 正确:使用 radial 坐标系
coordinate: { type: 'radial' }
```

### 错误 3:分类过多

```javascript
// ⚠️ 注意:分类数量建议不超过 15 个
// 过多分类会导致环形过窄
```

### 错误 4:encode 通道映射错误

```javascript
// ❌ 问题:x 映射数值字段,y 映射分类字段,这在 radial 坐标系中是错误的
encode: {
  x: 'value',       // x 应该映射分类字段
  y: 'category',    // y 应该映射数值字段
}

// ✅ 正确:x 映射分类字段,y 映射数值字段
encode: {
  x: 'category',    // x 映射分类字段(对应角度)
  y: 'value',       // y 映射数值字段(对应半径)
}
```

### 错误 5:transform 排序方式错误

```javascript
// ❌ 问题:使用了 transform 排序但方向错误
transform: [
  {
    type: 'sortX',
    by: 'value',
    reverse: false,   // 应该为 true 才能实现由内向外递增
  },
],

// ✅ 正确:使用正确的排序方向
transform: [
  {
    type: 'sortX',
    by: 'value',
    reverse: true,   // 由内向外递增
  },
],
// 或者更推荐的方式是在数据层面预先排序
data: rawData.sort((a, b) => b.value - a.value)
```
references/marks/g2-mark-range-rangey.md
---
id: "g2-mark-range-rangey"
title: "G2 range / rangeX / rangeY 区域标注"
description: |
  range、rangeX、rangeY 是 G2 v5 中用于绘制矩形区域标注的 Mark。
  rangeX 在 X 轴方向标注区间(纵向矩形带),常用于高亮时间段;
  rangeY 在 Y 轴方向标注区间(横向矩形带),常用于高亮数值范围;
  range 同时在 X 和 Y 两个方向标注矩形区域。
  常与其他 Mark 叠加使用,作为背景区域标注。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "range"
  - "rangeX"
  - "rangeY"
  - "区域标注"
  - "高亮区间"
  - "背景带"
  - "annotation"

related:
  - "g2-mark-linex-liney"
  - "g2-mark-connector"
  - "g2-comp-annotation"

use_cases:
  - "折线图上高亮特定时间段(如促销期)"
  - "标注正常范围的上下限区间"
  - "对比图中高亮某个参考区域"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/extra-topics/annotation#rangex"
---

## 三种 range Mark 对比

| Mark | 数据格式 | encode | 用途 |
|------|---------|--------|------|
| `rangeX` | `[{ start: v1, end: v2 }]` | `x: 'start', x1: 'end'` | X 轴区间(纵向带)**常用** |
| `rangeY` | `[{ min: v1, max: v2 }]` | `y: 'min', y1: 'max'` | Y 轴区间(横向带)**常用** |
| `range` | `[{ x: [v1,v2], y: [v1,v2] }]` | `x: 'x', y: 'y'` | 二维矩形,x/y 字段为数组 **极少用** |

> **选择原则**:只需高亮 X 方向时间段 → `rangeX`;只需高亮 Y 方向数值区间 → `rangeY`;需要同时限定 X 和 Y 的矩形区域 → `range`

## RangeX 高亮时间段

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 800, height: 400 });

chart.options({
  type: 'view',
  data: timeSeriesData,
  encode: { x: 'date', y: 'value' },
  children: [
    // 主折线图
    { type: 'line' },
    // X 轴区间标注(高亮促销期)
    {
      type: 'rangeX',
      data: [
        { start: '2024-11-01', end: '2024-11-30', label: '双十一' },
      ],
      encode: {
        x: 'start',    // 区间开始
        x1: 'end',     // 区间结束
      },
      style: {
        fill: '#ff4d4f',
        fillOpacity: 0.1,
      },
    },
  ],
});

chart.render();
```

## RangeY 标注数值区间

```javascript
chart.options({
  type: 'view',
  data,
  encode: { x: 'date', y: 'value' },
  children: [
    { type: 'line' },
    // Y 轴区间标注(正常范围)
    {
      type: 'rangeY',
      data: [{ min: 60, max: 100, label: '正常范围' }],
      encode: {
        y: 'min',    // 区间下限
        y1: 'max',   // 区间上限
      },
      style: {
        fill: '#52c41a',
        fillOpacity: 0.08,
        stroke: '#52c41a',
        strokeOpacity: 0.3,
        lineWidth: 1,
        lineDash: [4, 4],
      },
    },
  ],
});
```

## Range 二维矩形区域

> ⚠️ **`range` 的数据格式与 `rangeX`/`rangeY` 完全不同**:`x` 和 `y` 字段本身是 `[start, end]` 数组,encode 只需 `x` 和 `y`,**不需要** `x1`/`y1`。

```javascript
// 散点图四象限背景色(同时限定 X 和 Y 方向)
chart.options({
  type: 'view',
  children: [
    {
      type: 'point',
      data: scatterData,
      encode: { x: 'changeX', y: 'changeY' },
    },
    {
      type: 'range',
      // ✅ x 和 y 字段的值是 [start, end] 数组
      data: [
        { x: [-25, 0], y: [-30, 0], region: 'Q3' },
        { x: [-25, 0], y: [0, 20],  region: 'Q2' },
        { x: [0, 5],   y: [-30, 0], region: 'Q4' },
        { x: [0, 5],   y: [0, 20],  region: 'Q1' },
      ],
      encode: { x: 'x', y: 'y', color: 'region' },  // ✅ encode 只需 x 和 y
      style: { fillOpacity: 0.15 },
    },
  ],
});
```

## 与 lineX/lineY 搭配标注阈值

```javascript
// rangeY 标注背景区域 + lineY 标注具体阈值线
chart.options({
  type: 'view',
  data,
  children: [
    { type: 'line', encode: { x: 'date', y: 'value' } },
    // 危险区域背景
    {
      type: 'rangeY',
      data: [{ min: 80, max: 100 }],
      encode: { y: 'min', y1: 'max' },
      style: { fill: '#ff4d4f', fillOpacity: 0.08 },
    },
    // 阈值线
    {
      type: 'lineY',
      data: [{ y: 80 }],
      encode: { y: 'y' },
      style: { stroke: '#ff4d4f', lineWidth: 1, lineDash: [4, 4] },
    },
  ],
});
```

## 常见错误与修正

### ❌ 错误:使用不存在的 regionX / regionY 类型
```javascript
// ❌ 错误:regionX、regionY 是其他库的概念,G2 中不存在
chart.options({ type: 'regionX', ... });
chart.options({ type: 'regionY', ... });

// ✅ 正确:G2 中用 rangeX / rangeY
chart.options({ type: 'rangeX', encode: { x: 'start', x1: 'end' } });
chart.options({ type: 'rangeY', encode: { y: 'start', y1: 'end' } });
```

### ❌ 错误:range 使用 x1/y1 字段(混淆了 rangeX/rangeY 的写法)

```javascript
// ❌ 错误:range 不用 x1/y1,x 和 y 字段本身就是 [start, end] 数组
chart.options({
  type: 'range',
  data: [{ x0: 20, x1: 40, y0: 50, y1: 80 }],
  encode: { x: 'x0', x1: 'x1', y: 'y0', y1: 'y1' },  // ❌
});

// ✅ 正确:x/y 字段值是数组
chart.options({
  type: 'range',
  data: [{ x: [20, 40], y: [50, 80] }],
  encode: { x: 'x', y: 'y' },  // ✅
});

// 💡 大多数情况下,应使用 rangeX 或 rangeY,而非 range:
// - 只需高亮 X 方向 → rangeX(encode: { x: 'start', x1: 'end' })
// - 只需高亮 Y 方向 → rangeY(encode: { y: 'min', y1: 'max' })
```

### ❌ 错误:省略 encode(最常见,导致区域不渲染)

`rangeY` / `rangeX` 必须显式写 `encode`,G2 无法从字段名自动推断区间起止。

```javascript
// ❌ 错误:缺少 encode,区域不会渲染
{
  type: 'rangeY',
  data: [{ y: 54, y1: 72 }],
  style: { fill: '#FF0000', fillOpacity: 0.1 },
  // ❌ 没有 encode!
}

// ✅ 正确:必须写 encode 显式映射字段名
{
  type: 'rangeY',
  data: [{ y: 54, y1: 72 }],
  encode: { y: 'y', y1: 'y1' },         // ✅ 告诉 G2 哪个字段是起止
  style: { fill: '#FF0000', fillOpacity: 0.1 },
}

// ✅ 字段名可以随意,关键是 encode 里要映射
{
  type: 'rangeY',
  data: [{ lower: 54, upper: 72 }],
  encode: { y: 'lower', y1: 'upper' },   // ✅ 字段名与 data 对应即可
  style: { fill: '#FF0000', fillOpacity: 0.1 },
}
```

### ❌ 错误:rangeX 只写了 x 没有 x1
```javascript
// ❌ 错误:rangeX 需要 x(开始)和 x1(结束)两个 encode 字段
chart.options({
  type: 'rangeX',
  data: [{ start: 10, end: 20 }],
  encode: { x: 'start' },   // ❌ 缺少 x1
});

// ✅ 正确
chart.options({
  type: 'rangeX',
  data: [{ start: 10, end: 20 }],
  encode: { x: 'start', x1: 'end' },  // ✅ x 和 x1 都要
});
```
references/marks/g2-mark-rangex.md
---
id: "g2-mark-rangex"
title: "G2 RangeX / RangeY 区间标注"
description: |
  rangeX 在 X 轴方向绘制竖向区域带(区间高亮),常用于标注特殊时间段或阈值区间。
  rangeY 在 Y 轴方向绘制横向区域带,用于标注某个数值范围(如目标区间、警戒线)。
  常与折线图组合使用,在背景添加时间区间标注。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "rangeX"
  - "rangeY"
  - "区间"
  - "标注"
  - "高亮区域"
  - "参考区域"
  - "时间区间"

related:
  - "g2-comp-annotation"
  - "g2-mark-line-basic"
  - "g2-comp-view"

use_cases:
  - "时间序列图中高亮特殊时间段(如历史事件、假期)"
  - "标注正常值区间(如绿色安全区间)"
  - "突出显示某个数据范围"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/annotation/range/"
---

## 最小可运行示例(时间段高亮)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 800, height: 400 });

chart.options({
  type: 'view',
  children: [
    // 主折线图
    {
      type: 'line',
       [
        { date: '2024-01', value: 100 },
        { date: '2024-02', value: 120 },
        { date: '2024-03', value: 150 },
        { date: '2024-04', value: 130 },
        { date: '2024-05', value: 160 },
      ],
      encode: { x: 'date', y: 'value' },
      style: { lineWidth: 2 },
    },
    // 高亮特殊区域
    {
      type: 'rangeX',
       [
        { x: '2024-02', x1: '2024-03', label: '活动期' },
      ],
      encode: { x: 'x', x1: 'x1' },
      style: { fill: '#FF6B35', fillOpacity: 0.15 },
      labels: [{ text: 'label', position: 'top', style: { fontSize: 11 } }],
    },
  ],
});

chart.render();
```

## 时间区间标注(折线图 + 历史事件背景)

使用 Date 对象和数组格式标注历史时间段:

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  autoFit: true,
});

// 人口数据
const populationData = [
  { year: '1875', population: 1309 },
  { year: '1890', population: 1558 },
  { year: '1910', population: 4512 },
  { year: '1925', population: 8180 },
  { year: '1933', population: 15915 },
  { year: '1939', population: 24824 },
  { year: '1946', population: 28275 },
  { year: '1950', population: 29189 },
  { year: '1964', population: 29881 },
  { year: '1971', population: 26007 },
];

chart.options({
  type: 'view',
  autoFit: true,
  children: [
    // 背景区间标注(历史时期)
    {
      type: 'rangeX',
       [
        { year: [new Date('1933'), new Date('1945')], event: '纳粹统治时期' },
        { year: [new Date('1948'), new Date('1989')], event: '东德时期' },
      ],
      encode: { x: 'year', color: 'event' },
      scale: {
        color: {
          independent: true,  // 独立颜色比例尺,每个区间不同颜色
          range: ['#FAAD14', '#30BF78'],
        },
      },
      style: { fillOpacity: 0.75 },
      tooltip: false,
    },
    // 折线图
    {
      type: 'line',
      data: populationData,
      encode: {
        x: (d) => new Date(d.year),
        y: 'population',
      },
      style: { stroke: '#333', lineWidth: 2 },
    },
    // 数据点
    {
      type: 'point',
      data: populationData,
      encode: {
        x: (d) => new Date(d.year),
        y: 'population',
      },
      style: { fill: '#333', r: 3 },
    },
  ],
});

chart.render();
```

## 数据格式说明

rangeX 支持两种数据格式:

### 格式一:独立字段(x, x1)

```javascript
 [
  { x: '2024-01', x1: '2024-03', label: 'Q1' },
  { x: '2024-04', x1: '2024-06', label: 'Q2' },
],
encode: { x: 'x', x1: 'x1' },
```

### 格式二:数组字段

```javascript
data: [
  { year: [new Date('1933'), new Date('1945')], event: '事件A' },
  { year: [new Date('1948'), new Date('1989')], event: '事件B' },
],
encode: { x: 'year' },  // 数组自动解析为 [start, end]
```

## RangeY(水平参考区间)

```javascript
chart.options({
  type: 'view',
  children: [
    {
      type: 'line',
       data,
      encode: { x: 'date', y: 'temperature' },
    },
    // 标注正常温度区间(18~26°C)
    {
      type: 'rangeY',
       [{ y: 18, y1: 26, label: '舒适区间' }],
      encode: { y: 'y', y1: 'y1' },
      style: { fill: '#52c41a', fillOpacity: 0.1 },
      labels: [{ text: 'label', position: 'right', style: { fill: '#52c41a' } }],
    },
  ],
});
```

## 配置项

| 属性 | 说明 | 类型 |
|------|------|------|
| `encode.x` | 区间起点字段 | `string \| (d) => value` |
| `encode.x1` | 区间终点字段 | `string \| (d) => value` |
| `encode.color` | 颜色字段(区分不同区间) | `string` |
| `style.fill` | 填充颜色 | `string` |
| `style.fillOpacity` | 填充透明度 | `number` (0-1) |
| `scale.color.independent` | 独立颜色比例尺 | `boolean` |

## 常见错误与修正

### 错误 1:encode 中 x/x1 写成数组而不是独立字段名

```javascript
// ❌ 错误:rangeX 中 x 和 x1 是独立字段,不是数组
chart.options({
  type: 'rangeX',
  encode: { x: ['start', 'end'] },  // ❌ 不是这个用法
});

// ✅ 正确:x 和 x1 分别绑定起点和终点字段
chart.options({
  type: 'rangeX',
   [{ start: '2024-01', end: '2024-03' }],
  encode: {
    x: 'start',    // ✅ 起点字段
    x1: 'end',     // ✅ 终点字段
  },
});
```

### 错误 2:时间格式不一致导致区间不与主图对齐

```javascript
// ❌ 错误:折线图用 Date 对象,rangeX 用字符串,比例尺不一致
children: [
  { type: 'line', encode: { x: (d) => new Date(d.year) } },
  { type: 'rangeX', encode: { x: 'year' } },  // ❌ 格式不一致
]

// ✅ 正确:统一使用 Date 对象
children: [
  { type: 'line', encode: { x: (d) => new Date(d.year) } },
  { type: 'rangeX', encode: { x: 'year' }, data: [
    { year: [new Date('1933'), new Date('1945')] }  // ✅ 也用 Date
  ]},
]
```

### 错误 3:多个区间颜色相同

```javascript
// ❌ 问题:多个区间默认使用连续色板,颜色可能相近
{
  type: 'rangeX',
  data: [
    { year: [start1, end1], event: '事件A' },
    { year: [start2, end2], event: '事件B' },
  ],
  encode: { x: 'year', color: 'event' },
}

// ✅ 正确:使用 independent: true 让每个区间独立着色
{
  type: 'rangeX',
  data: [
    { year: [start1, end1], event: '事件A' },
    { year: [start2, end2], event: '事件B' },
  ],
  encode: { x: 'year', color: 'event' },
  scale: {
    color: {
      independent: true,  // ✅ 独立颜色
      range: ['#FAAD14', '#30BF78'],
    },
  },
}
```

### 错误 4:rangeX 放在折线图后面导致遮挡

```javascript
// ❌ 错误:rangeX 在后,会遮挡折线
children: [
  { type: 'line', ... },
  { type: 'rangeX', ... },  // ❌ 遮挡折线
]

// ✅ 正确:rangeX 放前面,作为背景层
children: [
  { type: 'rangeX', ... },  // ✅ 先渲染,作为背景
  { type: 'line', ... },
]
```
references/marks/g2-mark-rect.md
---
id: "g2-mark-rect"
title: "G2 矩形标注(rect)"
description: |
  rect mark 在图表中绘制任意位置和大小的矩形,
  通过 x/x1 指定左右边界,y/y1 指定上下边界(与坐标轴单位一致)。
  常用于高亮某个数据区间、背景分区、标注区域等。
  与 rangeX 类似但更通用(支持同时指定 x 和 y 方向边界)。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "rect"
  - "矩形"
  - "区域标注"
  - "背景分区"
  - "annotation"

related:
  - "g2-mark-rangex"
  - "g2-comp-annotation"
  - "g2-mark-linex-liney"

use_cases:
  - "高亮图表中特定 x/y 区间范围"
  - "在散点图中标注某个数值区间矩形"
  - "分区背景着色"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/annotation/range/"
---

## 最小可运行示例(二维区间标注)

```javascript
import { Chart } from '@antv/g2';

const scatterData = Array.from({ length: 100 }, () => ({
  x: Math.random() * 100,
  y: Math.random() * 100,
}));

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'view',
  children: [
    // 主散点图
    {
      type: 'point',
      data: scatterData,
      encode: { x: 'x', y: 'y' },
      style: { r: 3, fillOpacity: 0.6 },
    },
    // 矩形标注:高亮 x: 30~70, y: 30~70 的区间
    {
      type: 'rect',
       [{ x: 30, x1: 70, y: 30, y1: 70, label: '目标区间' }],
      encode: { x: 'x', x1: 'x1', y: 'y', y1: 'y1' },
      style: {
        fill: '#52c41a',
        fillOpacity: 0.1,
        stroke: '#52c41a',
        lineWidth: 1.5,
        lineDash: [4, 4],
      },
      labels: [
        { text: 'label', position: 'top-left', style: { fill: '#52c41a', fontSize: 11 } },
      ],
    },
  ],
});

chart.render();
```

## 配置项

```javascript
chart.options({
  type: 'rect',
  data: [{ x: 20, x1: 60, y: 0, y1: 100, label: '区间 A' }],
  encode: {
    x: 'x',     // 矩形左边界(与 x 轴单位相同)
    x1: 'x1',   // 矩形右边界
    y: 'y',     // 矩形下边界(与 y 轴单位相同)
    y1: 'y1',   // 矩形上边界
  },
  style: {
    fill: '#5B8FF9',
    fillOpacity: 0.1,
    stroke: '#5B8FF9',
    lineWidth: 1,
  },
});
```

## 常见错误与修正

### 错误:rect 和 rangeX/rangeY 混淆——rect 需要同时指定 x/y 两个方向
```javascript
// rangeX:只指定 x 方向边界,y 方向填满整个图表高度
chart.options({ type: 'rangeX', encode: { x: 'start', x1: 'end' } });

// rangeY:只指定 y 方向边界,x 方向填满整个图表宽度
chart.options({ type: 'rangeY', encode: { y: 'min', y1: 'max' } });

// rect:同时指定 x 和 y 两个方向(完整的矩形区间)
chart.options({ type: 'rect', encode: { x: 'x', x1: 'x1', y: 'y', y1: 'y1' } });
```
references/marks/g2-mark-regression-curve.md
---
id: "g2-mark-regression-curve"
title: "G2 回归曲线图(regression curve)"
description: |
  回归曲线图在散点图基础上叠加回归趋势线。使用 type: 'view' 组合
  type: 'point'(原始数据)和 type: 'line'(回归曲线),
  回归计算通过 data.transform 中的 custom callback 接入 d3-regression 等库。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "回归曲线图"
  - "regression"
  - "线性回归"
  - "趋势线"
  - "d3-regression"
  - "散点图"

related:
  - "g2-mark-point-scatter"
  - "g2-mark-line-basic"

use_cases:
  - "展示两变量间的线性/非线性关系"
  - "趋势预测"
  - "相关性分析"

anti_patterns:
  - "数据点少于 10 条时回归不可靠"
  - "变量间无相关关系时不适合加回归线"

difficulty: "intermediate"
completeness: "full"
created: "2025-04-01"
updated: "2025-04-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/regressioncurve"
---

## 核心概念

**回归曲线图 = `type: 'view'` 组合 `point`(散点)+ `line`(回归线)**

- 散点 (`type: 'point'`):展示原始数据
- 回归线 (`type: 'line'`):通过 `data.transform` 中的 `custom` callback 调用回归函数
- 常用回归库:`d3-regression`(`regressionLinear`, `regressionQuad`, `regressionExp`, `regressionLog`, `regressionPoly`)

**回归函数输出格式**:返回一个点数组 `[[x0, y0], [x1, y1], ...]`,encode 时用 `(d) => d[0]` 和 `(d) => d[1]`

## 线性回归(最小可运行示例)

```javascript
import { Chart } from '@antv/g2';
import { regressionLinear } from 'd3-regression';

const chart = new Chart({
  container: 'container',
  theme: 'classic',
});

chart.options({
  type: 'view',
  autoFit: true,
   {
    type: 'fetch',
    value: 'https://assets.antv.antgroup.com/g2/linear-regression.json',
  },
  children: [
    // 散点:原始数据
    {
      type: 'point',
      encode: { x: (d) => d[0], y: (d) => d[1] },
      scale: { x: { domain: [0, 1] }, y: { domain: [0, 5] } },
      style: { fillOpacity: 0.75, fill: '#1890ff' },
    },
    // 折线:回归曲线
    {
      type: 'line',
       {
        transform: [
          {
            type: 'custom',
            callback: regressionLinear(),  // d3-regression 回归函数
          },
        ],
      },
      encode: { x: (d) => d[0], y: (d) => d[1] },
      style: { stroke: '#30BF78', lineWidth: 2 },
      labels: [
        {
          text: 'y = 1.7x + 3.01',
          selector: 'last',
          position: 'right',
          textAlign: 'end',
          dy: -8,
        },
      ],
      tooltip: false,
    },
  ],
  axis: {
    x: { title: '自变量 X' },
    y: { title: '因变量 Y' },
  },
});

chart.render();
```

## 多项式回归(二次回归)

```javascript
import { regressionQuad } from 'd3-regression';

chart.options({
  type: 'view',
  autoFit: true,
   [
    { x: -4, y: 5.2 }, { x: -3, y: 2.8 }, { x: -2, y: 1.5 },
    { x: -1, y: 0.8 }, { x: 0, y: 0.5 }, { x: 1, y: 0.8 },
    { x: 2, y: 1.5 }, { x: 3, y: 2.8 }, { x: 4, y: 5.2 },
  ],
  children: [
    {
      type: 'point',
      encode: { x: 'x', y: 'y' },
      style: { fillOpacity: 0.75, fill: '#1890ff' },
    },
    {
      type: 'line',
       {
        transform: [
          {
            type: 'custom',
            callback: regressionQuad()
              .x((d) => d.x)
              .y((d) => d.y)
              .domain([-4, 4]),
          },
        ],
      },
      encode: { x: (d) => d[0], y: (d) => d[1] },
      style: { stroke: '#30BF78', lineWidth: 2 },
      labels: [
        {
          text: 'y = 0.3x² + 0.5',
          selector: 'last',
          textAlign: 'end',
          dy: -8,
        },
      ],
      tooltip: false,
    },
  ],
});
```

## 指数回归

```javascript
import { regressionExp } from 'd3-regression';

// 在 line 子 mark 中
{
  type: 'line',
  data: {
    transform: [
      {
        type: 'custom',
        callback: regressionExp(),
      },
    ],
  },
  encode: { x: (d) => d[0], y: (d) => d[1], shape: 'smooth' },
  style: { stroke: '#30BF78', lineWidth: 2 },
  tooltip: false,
}
```

## d3-regression 常用函数

| 函数 | 回归类型 | 适用场景 |
|------|---------|---------|
| `regressionLinear()` | 线性 y = ax + b | 线性相关 |
| `regressionQuad()` | 二次 y = ax² + bx + c | 抛物线关系 |
| `regressionPoly()` | 多项式 | 复杂弯曲 |
| `regressionExp()` | 指数 y = ae^(bx) | 指数增长/衰减 |
| `regressionLog()` | 对数 y = a·ln(x) + b | 增长率递减 |
| `regressionPow()` | 幂律 y = ax^b | 幂律关系 |

## 常见错误与修正

### 错误 1:回归函数放在错误位置

```javascript
// ❌ 错误:custom 回归应放在 line 子 mark 的 data.transform 中
chart.options({
  type: 'view',
   {
    transform: [{ type: 'custom', callback: regressionLinear() }],  // ❌ 放在父 view 数据上
  },
  children: [{ type: 'point', encode: { x: 'x', y: 'y' } }],
});

// ✅ 正确:每个子 mark 有独立数据来源
chart.options({
  type: 'view',
  data,  // 散点数据
  children: [
    { type: 'point', encode: { x: 'x', y: 'y' } },  // 散点用父数据
    {
      type: 'line',
      data: {
        transform: [{ type: 'custom', callback: regressionLinear().x(d=>d.x).y(d=>d.y) }],
      },                                               // ✅ 回归线有独立数据
      encode: { x: (d) => d[0], y: (d) => d[1] },
    },
  ],
});
```

### 错误 2:encode 字段访问方式错误

```javascript
// ❌ 错误:d3-regression 输出 [[x,y],...] 数组,不是对象
{
  type: 'line',
  encode: { x: 'x', y: 'y' },  // ❌ d[0] 不是 d.x
}

// ✅ 正确:用函数访问数组索引
{
  type: 'line',
  encode: { x: (d) => d[0], y: (d) => d[1] },  // ✅
}
```

### 错误 3:不指定 .x()/.y() 时数据格式不匹配

```javascript
// ❌ 问题:默认情况下 d3-regression 假设数据是 [x, y] 数组格式
const data = [{ x: 1, y: 2 }, { x: 3, y: 4 }];  // 对象格式
// regressionLinear() 默认读 d[0], d[1],不匹配对象格式

// ✅ 正确:显式指定字段读取方式
callback: regressionLinear()
  .x((d) => d.x)   // ✅ 指定 x 字段
  .y((d) => d.y),  // ✅ 指定 y 字段
```

### 错误 4:data 关键字缺失

```javascript
// ❌ 错误
children: [
  {
    type: 'line',
    {
      transform: [{ type: 'custom', callback: regressionLinear() }],
    },                             // ❌ 孤立 {} 语法错误,缺少 data: 键
    encode: { x: (d) => d[0], y: (d) => d[1] },
  },
]

// ✅ 正确
children: [
  {
    type: 'line',
     {                        // ✅ 必须有 data: 键
      transform: [{ type: 'custom', callback: regressionLinear() }],
    },
    encode: { x: (d) => d[0], y: (d) => d[1] },
  },
]
```
references/marks/g2-mark-rose.md
---
id: "g2-mark-rose"
title: "G2 Rose Chart Mark"
description: |
  南丁格尔玫瑰图 Mark。使用 interval 标记配合 polar 坐标系,通过扇形半径表示数值大小。
  适用于分类数据对比、周期性数据展示等场景。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "玫瑰图"
  - "rose"
  - "南丁格尔图"
  - "极坐标"

related:
  - "g2-mark-arc-pie"
  - "g2-coord-polar"

use_cases:
  - "分类数据对比"
  - "周期性数据展示"
  - "多维度比较"

anti_patterns:
  - "分类过少应使用饼图"
  - "数值差异悬殊不适合"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/mark/rose"
---

## 核心概念

南丁格尔玫瑰图在极坐标系下绘制柱状图:
- 使用 `interval` 标记
- 配合 `polar` 坐标系
- 扇形半径表示数值大小

**与饼图的区别:**
- 饼图:弧度表示数值
- 玫瑰图:半径表示数值

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  theme: 'classic',
});

chart.options({
  type: 'interval',
  autoFit: true,
  coordinate: { type: 'polar' },
  data: [
    { country: '中国', cost: 96 },
    { country: '德国', cost: 121 },
    { country: '美国', cost: 100 },
    { country: '日本', cost: 111 },
  ],
  encode: {
    x: 'country',
    y: 'cost',
    color: 'country',
  },
  style: {
    stroke: 'white',
    lineWidth: 1,
  },
});

chart.render();
```

## 常用变体

### 堆叠玫瑰图

```javascript
chart.options({
  type: 'interval',
  coordinate: { type: 'polar', innerRadius: 0.1 },
  data,
  encode: { x: 'year', y: 'count', color: 'type' },
  transform: [{ type: 'stackY' }],
});
```

### 扇形玫瑰图

```javascript
chart.options({
  type: 'interval',
  coordinate: {
    type: 'polar',
    startAngle: Math.PI,
    endAngle: Math.PI * (3 / 2),
  },
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
});
```

### 带标签

```javascript
chart.options({
  type: 'interval',
  coordinate: { type: 'polar' },
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
  labels: [
    {
      text: 'value',
      style: { textAlign: 'center', fontSize: 10 },
    },
  ],
});
```

## 完整类型参考

```typescript
interface RoseOptions {
  type: 'interval';
  coordinate: {
    type: 'polar';
    innerRadius?: number;    // 内半径
    startAngle?: number;     // 起始角度
    endAngle?: number;       // 结束角度
  };
  encode: {
    x: string;    // 分类字段
    y: string;    // 数值字段
    color?: string;
  };
  transform?: [{ type: 'stackY' }];  // 堆叠
}
```

## 玫瑰图 vs 饼图

| 特性 | 玫瑰图 | 饼图 |
|------|--------|------|
| 数值映射 | 半径 | 弧度 |
| 分类数量 | 较多 | 较少 |
| 对比方式 | 半径对比 | 面积对比 |

## 常见错误与修正

### 错误 1:使用 theta 坐标系

```javascript
// ❌ 问题:theta 是饼图坐标系
coordinate: { type: 'theta' }

// ✅ 正确:使用 polar 坐标系
coordinate: { type: 'polar' }
```

### 错误 2:数据未排序

```javascript
// ⚠️ 注意:玫瑰图建议数据排序后使用
// 可以使用 sortX transform
transform: [{ type: 'sortX', by: 'y' }]
```

### 错误 3:分类过多

```javascript
// ⚠️ 注意:分类数量建议不超过 30 个
// 过多分类会导致扇形过窄难以阅读
```
references/marks/g2-mark-sankey.md
---
id: "g2-mark-sankey"
title: "G2 桑基图(sankey)"
description: |
  G2 v5 内置 sankey Mark,用于展示多阶段流量/能量分配流向,
  数据格式为包含 source、target、value 的链接数组,
  节点宽度自动由传入/传出流量决定。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "桑基图"
  - "sankey"
  - "流向图"
  - "能量流"
  - "转化漏斗"
  - "spec"

related:
  - "g2-mark-funnel"
  - "g2-recipe-funnel"
  - "g2-core-chart-init"

use_cases:
  - "展示能源/物质流动分配"
  - "用户转化路径分析(多步骤)"
  - "预算/资金流向可视化"
  - "供应链流向图"

difficulty: "intermediate"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/graph/network/#sankey"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 800,
  height: 500,
});

// 链接数组:每条记录是一条流向
const links = [
  { source: '访问',   target: '注册',   value: 8000 },
  { source: '访问',   target: '直接离开', value: 2000 },
  { source: '注册',   target: '激活',   value: 5000 },
  { source: '注册',   target: '流失',   value: 3000 },
  { source: '激活',   target: '付费',   value: 2000 },
  { source: '激活',   target: '免费使用', value: 3000 },
];

chart.options({
  type: 'sankey',
  data: {
    value: {
      links,    // 链接数组(必须)
      // nodes 可选,不填则自动从 links 中提取节点
    },
  },
  layout: {
    nodeAlign: 'justify',   // 节点对齐:'left'|'right'|'center'|'justify'
    nodePadding: 0.03,      // 节点上下间距(0-1)
  },
  style: {
    labelSpacing: 3,
    nodeLineWidth: 1,
    linkFillOpacity: 0.4,
  },
  legend: false,
});

chart.render();
```

## 带颜色区分的桑基图

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 900,
  height: 600,
});

const links = [
  { source: '煤炭', target: '电力', value: 150 },
  { source: '石油', target: '交通', value: 120 },
  { source: '天然气', target: '供热', value: 80 },
  { source: '电力', target: '工业', value: 90 },
  { source: '电力', target: '居民', value: 60 },
  { source: '交通', target: '公路', value: 80 },
  { source: '交通', target: '航空', value: 40 },
];

chart.options({
  type: 'sankey',
  data: {
    value: { links },
  },
  layout: {
    nodeAlign: 'center',
    nodePadding: 0.03,
    nodeWidth: 0.02,       // 节点宽度(相对画布)
  },
  scale: {
    color: {
      type: 'ordinal',
      // 颜色跟随 source 节点
    },
  },
  style: {
    labelSpacing: 4,
    labelFontWeight: 'bold',
    labelFontSize: 12,
    nodeLineWidth: 1.2,
    linkFillOpacity: 0.35,
  },
  legend: false,
});

chart.render();
```

## 完整配置项

```javascript
chart.options({
  type: 'sankey',
  data: {
    value: {
      links: [
        { source: 'A', target: 'B', value: 10 },  // source/target 是节点名称
      ],
      nodes: [     // 可选,自动推断
        { key: 'A' },
        { key: 'B' },
      ],
    },
  },

  layout: {
    nodeId: (d) => d.key,      // 节点 ID 提取(默认 d.key)
    nodeAlign: 'justify',       // 'left'|'right'|'center'|'justify'
    nodeWidth: 0.02,            // 节点宽度(相对画布宽度,0-1)
    nodePadding: 0.02,          // 节点上下间距
    nodeSort: null,             // 节点排序函数
    linkSort: null,             // 链接排序函数
    iterations: 6,              // 布局迭代次数
  },

  style: {
    labelSpacing: 3,            // 标签与节点的间距
    labelFontSize: 12,
    labelFontWeight: 'normal',
    nodeLineWidth: 1,           // 节点边框宽度
    nodeStroke: '#fff',         // 节点边框颜色
    linkFillOpacity: 0.4,       // 链接透明度
  },
});
```

## 常见错误与修正

### 错误 1:数据格式错误——直接传 links 数组

```javascript
// ❌ 错误:sankey 的 data 需要包装为 { value: { links } }
chart.options({
  type: 'sankey',
  data: links,   // ❌ 直接传数组
});

// ✅ 正确
chart.options({
  type: 'sankey',
  data: {
    value: { links },   // ✅ 需要 { value: { links } } 结构
  },
});
```

### 错误 2:source/target 节点名称不一致导致断链

```javascript
// ❌ 错误:'电力' 和 '电力公司' 被当作两个不同节点
const links = [
  { source: '煤炭',   target: '电力',   value: 100 },
  { source: '电力公司', target: '工业', value: 80 },   // ❌ 名称不一致!
];

// ✅ 正确:source 和 target 中对同一节点使用完全相同的名称
const links = [
  { source: '煤炭', target: '电力', value: 100 },
  { source: '电力', target: '工业', value: 80 },   // ✅ 完全一致
];
```

### 错误 3:图中存在环(循环引用)

```javascript
// ❌ 桑基图不支持环形流向
const links = [
  { source: 'A', target: 'B', value: 10 },
  { source: 'B', target: 'A', value: 5 },   // ❌ 形成环!布局异常
];

// ✅ 桑基图只适合有向无环的流向数据
```
references/marks/g2-mark-shape.md
---
id: "g2-mark-shape"
title: "G2 shape 自定义图形 Mark"
description: |
  shape mark 是 G2 v5 中用于绘制完全自定义图形的 Mark,
  通过注册自定义 Shape 函数来渲染任意 SVG/Canvas 图形。
  与 image mark(使用图片)不同,shape mark 使用代码绘制矢量图形,
  可响应状态变化(高亮、选中等)。
  适用于需要特殊图形符号、自定义标记的可视化场景。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "shape"
  - "自定义图形"
  - "register"
  - "custom shape"
  - "矢量图形"

related:
  - "g2-mark-image"
  - "g2-mark-point-scatter"
  - "g2-core-chart-init"

use_cases:
  - "散点图中使用自定义图标代替默认圆形"
  - "地图上绘制自定义地标符号"
  - "特殊业务场景的定制化图形标记"

difficulty: "advanced"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/extra-topics/custom-mark"
---

## 核心概念

`shape` mark 需要先用 `register('shape.xxx', renderFn)` 注册自定义图形,
再在 mark 的 `style.shape` 中指定图形名称。

自定义 Shape 渲染函数接收 `(style, context)` 参数:
- `style`:包含 x/y 坐标、颜色、大小等样式属性
- `context`:G 渲染上下文,包含 document 等

## 最小可运行示例

```javascript
import { Chart, register } from '@antv/g2';
import { Circle } from '@antv/g';

// 1. 注册自定义图形(绘制带十字的圆)
register('shape.crossCircle', (style, context) => {
  const { x, y, r = 10, fill, stroke } = style;
  const group = new context.document.createElement('g', {});
  const circle = new Circle({ style: { cx: x, cy: y, r, fill, stroke } });
  group.appendChild(circle);
  return group;
});

// 2. 使用自定义图形
const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'point',
  data,
  encode: { x: 'x', y: 'y', color: 'category', size: 'value' },
  style: {
    shape: 'crossCircle',   // 使用注册的自定义图形名
  },
});

chart.render();
```

## 完整自定义形状(使用 @antv/g 图形)

```javascript
import { Chart, register } from '@antv/g2';
import { Path, Group } from '@antv/g';

// 注册星形图标
register('shape.star', (style, context) => {
  const { x, y, r = 10, fill = '#1890ff', opacity = 1 } = style;

  // 计算五角星的路径
  const path = [];
  for (let i = 0; i < 5; i++) {
    const angle = (i * 4 * Math.PI) / 5 - Math.PI / 2;
    const px = x + r * Math.cos(angle);
    const py = y + r * Math.sin(angle);
    path.push(i === 0 ? `M ${px} ${py}` : `L ${px} ${py}`);
  }
  path.push('Z');

  const shape = new Path({
    style: {
      d: path.join(' '),
      fill,
      opacity,
    },
  });
  return shape;
});

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'point',
  data,
  encode: { x: 'x', y: 'y', color: 'type' },
  style: {
    shape: 'star',
    r: 12,
  },
});

chart.render();
```

## 与 image mark 的选择

```javascript
// 使用图片文件作为标记点
chart.options({
  type: 'image',
  data,
  encode: { x: 'x', y: 'y', src: 'iconUrl' },  // src 为图片 URL
  style: { width: 24, height: 24 },
});

// 使用代码绘制矢量图形
register('shape.myIcon', (style) => { /* ... */ });
chart.options({
  type: 'point',
  data,
  encode: { x: 'x', y: 'y' },
  style: { shape: 'myIcon' },
});
```

## 常见错误与修正

### 错误:使用自定义图形前忘记注册
```javascript
// ❌ 错误:未注册就使用,图形不会渲染
chart.options({
  type: 'point',
  style: { shape: 'myCustomShape' },  // ❌ myCustomShape 未注册
});

// ✅ 先注册再使用
register('shape.myCustomShape', (style) => { /* 返回 G 图形 */ });
chart.options({
  type: 'point',
  style: { shape: 'myCustomShape' },  // ✅
});
```
references/marks/g2-mark-spiral.md
---
id: "g2-mark-spiral"
title: "G2 螺旋图(spiral)"
description: |
  螺旋图使用 helix 坐标系(coordinate.type: 'helix')将时间序列数据绘制成螺旋形状,
  从中心向外延伸。适合展示大量时间序列数据的周期性规律和变化趋势(通常 100+ 数据点)。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "螺旋图"
  - "spiral"
  - "helix"
  - "时间序列"
  - "周期性"
  - "大数据量"

related:
  - "g2-mark-line-basic"
  - "g2-mark-interval-basic"

use_cases:
  - "大量时间序列数据的趋势展示(100+ 数据点)"
  - "周期性数据规律识别(如年度季节性)"
  - "基因表达时间序列"

anti_patterns:
  - "少量数据(< 30 条)不适合,改用折线图"
  - "需要精确数值对比不适合,螺旋非线性坐标难以精确读值"
  - "animate.enter 不能使用 growInX/growInY,会导致螺旋渲染残缺,必须用 fadeIn"

difficulty: "intermediate"
completeness: "full"
created: "2025-04-01"
updated: "2025-04-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/spiral"
---

## 核心概念

**螺旋图 = interval/line mark + `coordinate: { type: 'helix', startAngle, endAngle }`**

- `coordinate.type: 'helix'`:阿基米德螺旋坐标系
- `startAngle`:螺旋起始角(弧度),`Math.PI / 2` ≈ 从顶部开始
- `endAngle`:螺旋结束角,值越大螺旋圈数越多
- **数据量要求**:通常需要 100 条以上才能形成完整螺旋

**角度与圈数关系**:`圈数 = (endAngle - startAngle) / (2 * Math.PI)`

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  autoFit: true,
  height: 500,
});

chart.options({
  type: 'interval',
  data: {
    value: [
      { time: '2025.07.11', value: 35 },
      { time: '2025.07.12', value: 30 },
      { time: '2025.07.13', value: 55 },
      // ... 更多数据(100+ 条)
    ],
  },
  encode: { x: 'time', y: 'value', color: 'value' },
  scale: {
    color: { type: 'linear', range: ['#ffffff', '#1890FF'] },
  },
  coordinate: {
    type: 'helix',
    startAngle: Math.PI / 2,              // 从顶部开始
    endAngle: Math.PI / 2 + 6 * Math.PI, // 转3圈(每圈 2π)
  },
  animate: { enter: { type: 'fadeIn' } },
  tooltip: { title: 'time' },
});

chart.render();
```

## 常用角度配置

```javascript
// 标准螺旋(约6圈,适合一年每周数据)
coordinate: {
  type: 'helix',
  startAngle: 1.5707963267948966,   // Math.PI / 2
  endAngle: 39.269908169872416,     // Math.PI / 2 + 12 * Math.PI(6圈)
}

// 少圈数(约3圈,适合季度数据)
coordinate: {
  type: 'helix',
  startAngle: Math.PI / 2,
  endAngle: Math.PI / 2 + 6 * Math.PI,
}

// 带内半径(圆环螺旋)
coordinate: {
  type: 'helix',
  startAngle: 0.2 * Math.PI,
  endAngle: 6.5 * Math.PI,
  innerRadius: 0.1,
}
```

## 按类别分组螺旋图

```javascript
chart.options({
  type: 'interval',
  data: {
    type: 'fetch',
    value: 'url-to-data.json',
  },
  encode: {
    x: 'time',
    y: 'group',     // 用 Y 轴区分类别
    color: 'value', // 用颜色映射数值
  },
  scale: {
    color: {
      type: 'linear',
      range: ['#fff', '#ec4839'],
    },
  },
  coordinate: {
    type: 'helix',
    startAngle: 0.2 * Math.PI,
    endAngle: 6.5 * Math.PI,
    innerRadius: 0.1,
  },
  tooltip: {
    title: 'time',
    items: [
      { field: 'group', name: '组别' },
      { field: 'value', name: '数值' },
    ],
  },
});
```

## 常见错误与修正

### 错误 1:数据量太少

```javascript
// ❌ 问题:5 条数据无法形成螺旋,效果极差
chart.options({
  type: 'interval',
  data: {
    value: [
      { time: '2025-01', value: 35 },
      { time: '2025-02', value: 50 },
      { time: '2025-03', value: 45 },
      { time: '2025-04', value: 60 },
      { time: '2025-05', value: 40 },
    ],
  },
  coordinate: { type: 'helix', startAngle: Math.PI / 2, endAngle: 40 },
});

// ✅ 改用折线图处理少量数据
chart.options({
  type: 'line',
  data,
  encode: { x: 'time', y: 'value' },
});
```

### 错误 2:coordinate 类型名错误

```javascript
// ❌ 错误:没有 'spiral' 类型,应该是 'helix'
coordinate: { type: 'spiral' }   // ❌ 不存在

// ✅ 正确:使用 helix
coordinate: { type: 'helix', startAngle: Math.PI / 2, endAngle: 40 }  // ✅
```

### 错误 3:角度单位混淆

```javascript
// ❌ 错误:使用角度(度数)而非弧度
coordinate: {
  type: 'helix',
  startAngle: 90,   // ❌ 90° 不是弧度,应该是 Math.PI / 2
  endAngle: 2250,   // ❌ 应该是弧度值
}

// ✅ 正确:使用弧度
coordinate: {
  type: 'helix',
  startAngle: Math.PI / 2,           // ✅ 90° = π/2 弧度
  endAngle: Math.PI / 2 + 12 * Math.PI,  // ✅ 6圈
}
```

### 错误 4:data 格式错误(行内数据需要 value 包裹)

```javascript
// ❌ 错误:行内数组数据需要放在 data.value 中
chart.options({
  data: [{ time: '2025.01', value: 35 }, ...],  // ❌ 直接数组
  coordinate: { type: 'helix', ... },
});

// ✅ 正确:行内数据用 { value: [...] } 包裹
chart.options({
   {
    value: [{ time: '2025.01', value: 35 }, ...],  // ✅
  },
  coordinate: { type: 'helix', ... },
});
```

### 错误 5:animate.enter 使用 growInY/growInX 导致螺旋渲染残缺

`growInX/Y` 通过沿直角坐标轴方向裁剪(clipPath)实现动画。helix 坐标系已将坐标重映射到螺旋路径,不存在"底部基线",裁剪矩形会横穿螺旋,导致部分螺旋区域被切掉,动画结束后图表仍显示不完整。

```javascript
// ❌ 错误:growInY 在 helix 坐标系下裁剪矩形横穿螺旋 → 图表渲染残缺
chart.options({
  type: 'interval',
  coordinate: { type: 'helix', startAngle: 0, endAngle: Math.PI * 6 },
  animate: {
    enter: { type: 'growInY', duration: 2000 },  // ❌ 螺旋被截断
  },
});

// ✅ 正确:helix 坐标系必须用 fadeIn(或不设置动画)
chart.options({
  type: 'interval',
  coordinate: { type: 'helix', startAngle: 0, endAngle: Math.PI * 6 },
  animate: {
    enter: { type: 'fadeIn', duration: 1000 },  // ✅
  },
});
```

## 与折线图的选择

| 场景 | 推荐图表 |
|------|---------|
| 数据量 < 50 条 | 折线图 |
| 数据量 100+ 条,观察趋势 | 螺旋图或折线图 |
| 需要发现周期性规律 | **螺旋图**(当每圈周期对齐时效果最佳)|
| 需要精确读取数值 | 折线图 |
| 大屏展示视觉效果 | **螺旋图** |
references/marks/g2-mark-sunburst.md
---
id: "g2-mark-sunburst"
title: "G2 旭日图(sunburst)"
description: |
  sunburst mark 用同心圆环(极坐标)展示多层级层次数据,来自 @antv/g2-extension-plot 扩展库。
  圆环的径向深度表示层级,弧长角度表示数值大小。
  注意:sunburst 与 partition 是两个独立的 mark:
  sunburst 为圆环布局(极坐标,需要扩展),partition 为矩形冰柱布局(直角坐标,@antv/g2 核心)。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "旭日图"
  - "sunburst"
  - "层次结构"
  - "多层级"
  - "hierarchy"
  - "polar"
  - "g2-extension-plot"

related:
  - "g2-mark-partition"
  - "g2-mark-treemap"
  - "g2-mark-arc-pie"

use_cases:
  - "组织架构展示"
  - "文件系统分析"
  - "预算分配的层次占比"

anti_patterns:
  - "层级过深(>4层)应使用矩形树图或 partition"
  - "不要用 type: 'partition' 加极坐标替代 sunburst,应直接使用 sunburst"
  - "不要把 data 写成数组,sunburst 的 data 是 { value: treeRoot } 对象"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-26"
updated: "2025-04-27"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/mark/sunburst"
---

## partition vs sunburst 对比

| 特性 | sunburst(旭日图)| partition(矩形分区)|
|------|-------------------|----------------------|
| 来源 | `@antv/g2-extension-plot`,需要 `extend` | `@antv/g2` 核心,无需扩展 |
| 坐标系 | 极坐标(同心圆)| 笛卡尔坐标(直角)|
| 视觉形态 | 同心圆环 | 矩形冰柱/icicle |
| data 格式 | `{ value: treeRoot }` 或 fetch | 数组 `[treeRoot]` 或 fetch |
| 回调中 path | `d.path` 是**字符串** `'A / B / C'` | `d.path` 是**数组** `['A', 'B', 'C']` |

## 引入扩展(必须)

```javascript
import { plotlib } from '@antv/g2-extension-plot';
import { Runtime, corelib, extend } from '@antv/g2';

const Chart = extend(Runtime, { ...corelib(), ...plotlib() });
```

## 最小可运行示例

```javascript
import { plotlib } from '@antv/g2-extension-plot';
import { Runtime, corelib, extend } from '@antv/g2';

const Chart = extend(Runtime, { ...corelib(), ...plotlib() });

const chart = new Chart({ container: 'container', autoFit: true });

chart.options({
  type: 'sunburst',
  data: {
    type: 'fetch',
    value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/sunburst.json',
  },
  encode: { value: 'sum' },
  labels: [
    {
      text: 'name',
      transform: [{ type: 'overflowHide' }],
    },
  ],
});

chart.render();
```

## 数据格式说明

`sunburst` 的 `data` 是 `{ value: treeRoot }` 对象(单棵树),不是数组:

```javascript
// ✅ 正确:内联数据,单棵树根对象
chart.options({
  type: 'sunburst',
  data: {
    value: {
      name: 'root',
      children: [
        { name: '分组1', children: [{ name: '分组1-1', sum: 100 }] },
        { name: '分组2', sum: 200 },
      ],
    },
  },
  encode: { value: 'sum' },
});

// ✅ 正确:远程 fetch
chart.options({
  type: 'sunburst',
  data: { type: 'fetch', value: 'https://example.com/tree.json' },
  encode: { value: 'sum' },
});

// ❌ 错误:不能直接传数组(partition 的写法)
chart.options({
  type: 'sunburst',
  data: [{ name: 'root', children: [...] }],  // ❌ 不工作
});
```

## 回调函数中的数据结构

sunburst 展平后,回调中 `d` 的结构:

```javascript
{
  name: '分组1-1',             // 节点名称
  value: 100,                  // 节点数值(子树汇总)
  depth: 2,                    // 层级深度(根节点为 1)
  path: '分组1 / 分组1-1',     // ← 路径是字符串(/ 分隔)
  'ancestor-node': '分组1',   // 第一层祖先节点名
  x: [x0, x1],
  y: [y0, y1],
}
```

**注意**:`path` 是**字符串**,用 ` / ` 分隔,与 partition 的数组不同。

## encode 着色策略

sunburst 展平后内置字段(`name`、`depth`、`path`、`ancestor-node`)可用字符串指定;
原始数据中的自定义字段不在展平记录中,需用回调通过 `path` 派生:

```javascript
// ✅ 默认着色(按 ancestor-node,同门类同色)
encode: { value: 'sum' }  // color 默认为 'ancestor-node'

// ✅ 按 name 字段着色(内置字段,字符串可用)
encode: { value: 'sum', color: 'name' }

// ✅ 按路径前两级着色(回调)
encode: {
  value: 'sum',
  color: (d) => {
    const parts = d.path.split(' / ');
    return [parts[0], parts[1]].join('/');
  },
}

// ✅ 按层级深度着色
encode: { value: 'sum', color: (d) => d.depth }
```

## 极坐标自定义

```javascript
// 调整内外半径
chart.options({
  type: 'sunburst',
  data: { value: treeData },
  encode: { value: 'sum' },
  coordinate: {
    type: 'polar',
    innerRadius: 0.3,   // 默认 0.2
    outerRadius: 0.9,
  },
});

// 还原为直角坐标(得到类似 partition 的矩形布局,但用 partition 更合适)
coordinate: { type: 'cartesian' }
```

## 下钻交互

```javascript
chart.options({
  type: 'sunburst',
  data: { value: treeData },
  encode: { value: 'sum' },
  interaction: {
    drillDown: {
      breadCrumb: {
        rootText: '总名称',
        style: { fontSize: '14px', fill: '#333' },
        active: { fill: 'red' },
      },
      isFixedColor: true,   // 下钻后维持原来颜色
    },
  },
});
```

## 常见错误与修正

### 错误 1:未引入扩展库
```javascript
// ❌ 错误:直接用 Chart from '@antv/g2',sunburst 未注册
import { Chart } from '@antv/g2';
chart.options({ type: 'sunburst', ... });  // ❌ Unknown mark type: sunburst

// ✅ 正确:通过 extend 注册 plotlib
import { plotlib } from '@antv/g2-extension-plot';
import { Runtime, corelib, extend } from '@antv/g2';
const Chart = extend(Runtime, { ...corelib(), ...plotlib() });
```

### 错误 2:data 使用 partition 的数组格式
```javascript
// ❌ 错误:sunburst 不接受数组
chart.options({
  type: 'sunburst',
  data: [{ name: 'root', children: [...] }],
});

// ✅ 正确:sunburst 使用 { value: root } 对象
chart.options({
  type: 'sunburst',
  data: { value: { name: 'root', children: [...] } },
});
```

### 错误 3:把 path 当数组处理
```javascript
// ❌ 错误:sunburst 的 path 是字符串
color: (d) => d.path[1]          // 拿到的是第 2 个字符,不是第 2 层路径

// ✅ 正确:先 split
color: (d) => d.path.split(' / ')[1]
```
references/marks/g2-mark-text.md
---
id: "g2-mark-text"
title: "G2 文字标注(text Mark)"
description: |
  G2 v5 内置 text Mark,用于在图表中绘制自定义文字标注,
  encode.x/y 确定位置,encode.text 或 style.text 提供内容,
  常与其他 Mark 叠加使用,实现数据标签、阈值标注和图表标题等效果。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "文字标注"
  - "text"
  - "标签"
  - "注释"
  - "annotation"
  - "spec"

related:
  - "g2-mark-line-basic"
  - "g2-mark-interval-basic"
  - "g2-core-view-composition"
  - "g2-comp-annotation"

use_cases:
  - "在柱状图顶部显示数值标签"
  - "标注图表中的特殊事件或阈值"
  - "添加自定义图表标题或说明文字"
  - "高亮某个数据点的文字描述"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/annotation/annotation/#text"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 600,
  height: 400,
});

const data = [
  { month: '1月', value: 120 },
  { month: '2月', value: 180 },
  { month: '3月', value: 150 },
  { month: '4月', value: 210 },
  { month: '5月', value: 170 },
];

chart.options({
  type: 'view',
  data,
  children: [
    {
      type: 'interval',
      encode: { x: 'month', y: 'value' },
    },
    {
      type: 'text',
      encode: {
        x: 'month',
        y: 'value',
        text: 'value',   // 数据字段名 → 显示该字段的值
      },
      style: {
        textAlign: 'center',
        dy: -8,           // 向上偏移
        fontSize: 12,
        fill: '#333',
      },
    },
  ],
});

chart.render();
```

## 固定位置文字标注(不绑定数据)

```javascript
// 在图表固定位置添加一行文字(如阈值说明)
chart.options({
  type: 'view',
  data,
  children: [
    {
      type: 'line',
      encode: { x: 'month', y: 'value' },
    },
    {
      // 固定位置文字,使用单条数据
      type: 'text',
       [{ x: '3月', y: 200, label: '达标线' }],
      encode: { x: 'x', y: 'y', text: 'label' },
      style: {
        fill: '#ff4d4f',
        fontSize: 12,
        fontWeight: 'bold',
        dx: 5,
      },
    },
  ],
});
```

## 结合 lineY 标注水平阈值线

```javascript
// lineY + text 组合:标注阈值线
chart.options({
  type: 'view',
  data,
  children: [
    {
      type: 'interval',
      encode: { x: 'month', y: 'value' },
      style: { fill: '#1890ff' },
    },
    {
      // 水平阈值线
      type: 'lineY',
      data: [{ y: 160 }],
      encode: { y: 'y' },
      style: { stroke: '#ff4d4f', lineDash: [4, 4], lineWidth: 1.5 },
    },
    {
      // 阈值标注文字
      type: 'text',
       [{ month: '5月', value: 160, label: '目标 160' }],
      encode: { x: 'month', y: 'value', text: 'label' },
      style: {
        fill: '#ff4d4f',
        fontSize: 12,
        textAlign: 'right',
        dy: -6,
      },
    },
  ],
});
```

## 完整配置项

```javascript
chart.options({
  type: 'text',
  data,
  encode: {
    x: 'xField',      // x 位置(对应坐标轴字段)
    y: 'yField',      // y 位置(对应坐标轴字段)
    text: 'textField', // 显示的文字内容字段
    color: 'series',  // 按系列着色(可选)
  },
  style: {
    // 文字样式
    fontSize: 12,
    fontWeight: 'normal',
    fill: '#333',
    textAlign: 'center',    // 'left' | 'center' | 'right'
    textBaseline: 'bottom', // 'top' | 'middle' | 'bottom'

    // 位置偏移
    dx: 0,    // 水平偏移(px)
    dy: -8,   // 垂直偏移(px,负值向上)

    // 背景框(可选)
    background: true,
    backgroundFill: 'rgba(255,255,255,0.8)',
    backgroundPadding: [2, 4],
    backgroundRadius: 3,

    // 旋转
    rotate: 0,   // 旋转角度(度)
  },
});
```

## 散点图数据点标签

```javascript
const scatterData = [
  { x: 10, y: 80, name: '产品A' },
  { x: 20, y: 60, name: '产品B' },
  { x: 35, y: 90, name: '产品C' },
  { x: 50, y: 40, name: '产品D' },
  { x: 65, y: 75, name: '产品E' },
];

chart.options({
  type: 'view',
  data: scatterData,
  children: [
    {
      type: 'point',
      encode: { x: 'x', y: 'y' },
      style: { r: 6, fill: '#1890ff' },
    },
    {
      type: 'text',
      encode: { x: 'x', y: 'y', text: 'name' },
      style: {
        dy: -12,
        textAlign: 'center',
        fontSize: 11,
        fill: '#666',
      },
    },
  ],
});
```

## 常见错误与修正

### 错误 1:text Mark 独立使用时没有数据

```javascript
// ❌ 错误:text Mark 需要 data 或者从父 view 继承数据
chart.options({
  type: 'text',
  // 缺少 data,且无父 view 提供数据
  encode: { x: 'month', y: 'value', text: 'label' },
});

// ✅ 正确方式一:在父 view 中提供数据
chart.options({
  type: 'view',
  data,               // 父 view 提供数据,子 mark 自动继承
  children: [
    { type: 'interval', encode: { x: 'month', y: 'value' } },
    { type: 'text', encode: { x: 'month', y: 'value', text: 'value' } },
  ],
});

// ✅ 正确方式二:text Mark 自带数据(用于固定标注)
chart.options({
  type: 'text',
  data: [{ x: '3月', y: 200, label: '特殊标注' }],
  encode: { x: 'x', y: 'y', text: 'label' },
});
```

### 错误 2:encode.text 写成了字面量字符串而非字段名

```javascript
// ❌ 错误:encode.text 应该是数据中的字段名,不能写固定文字
chart.options({
  type: 'text',
  encode: {
    x: 'month',
    y: 'value',
    text: '固定文字',   // ❌ 这里写的是字面量,但数据中没有叫 '固定文字' 的字段
  },
});

// ✅ 正确:固定文字应用 style.text 或 transform 函数
chart.options({
  type: 'text',
  encode: { x: 'month', y: 'value' },
  style: {
    text: (d) => `${d.value}`,   // ✅ 通过函数返回文字内容
    textAlign: 'center',
    dy: -8,
  },
});

// 或者在 encode 中用函数
chart.options({
  type: 'text',
  encode: {
    x: 'month',
    y: 'value',
    text: (d) => d.value,   // ✅ encode.text 可以是函数
  },
});
```

### 错误 3:text 与 interval 叠加时忘记共用 view

```javascript
// ❌ 错误:两个独立 chart 无法叠加
const chart1 = new Chart({ container: 'c1' });
chart1.options({ type: 'interval', data, encode: { x: 'month', y: 'value' } });

const chart2 = new Chart({ container: 'c2' });
chart2.options({ type: 'text', data, encode: { x: 'month', y: 'value', text: 'value' } });

// ✅ 正确:用 view 的 children 叠加
chart.options({
  type: 'view',
  data,
  children: [
    { type: 'interval', encode: { x: 'month', y: 'value' } },
    {
      type: 'text',
      encode: { x: 'month', y: 'value', text: 'value' },
      style: { textAlign: 'center', dy: -8 },
    },
  ],
});
```
references/marks/g2-mark-tree.md
---
id: "g2-mark-tree"
title: "G2 树形图(tree)"
description: |
  tree mark 将层级数据(树状 JSON)渲染为树形结构,
  自动布局节点(point mark)和连线(link mark),
  支持横向/纵向/径向布局,适合组织架构图、决策树、层级分类展示。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "tree"
  - "树形图"
  - "层级"
  - "组织架构"
  - "树状"
  - "hierarchy"

related:
  - "g2-mark-treemap"
  - "g2-mark-partition"
  - "g2-mark-sankey"

use_cases:
  - "组织架构图展示"
  - "决策树可视化"
  - "文件目录树形展示"
  - "分类层级结构可视化"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/hierarchy/tree/"
---

## 最小可运行示例(横向树形图)

```javascript
import { Chart } from '@antv/g2';

// 树形数据(嵌套 JSON 格式)
const treeData = {
  name: '总公司',
  children: [
    {
      name: '研发部',
      children: [
        { name: '前端组', value: 10 },
        { name: '后端组', value: 15 },
        { name: '算法组', value: 8 },
      ],
    },
    {
      name: '市场部',
      children: [
        { name: '品牌组', value: 6 },
        { name: '运营组', value: 9 },
      ],
    },
    {
      name: '产品部',
      children: [
        { name: 'B端产品', value: 7 },
        { name: 'C端产品', value: 5 },
      ],
    },
  ],
};

const chart = new Chart({ container: 'container', width: 800, height: 500 });

chart.options({
  type: 'tree',
  data: treeData,
  layout: {
    // 布局方向:false=纵向(上→下),true=横向(左→右)
    // G2 tree 使用 d3-hierarchy tidy tree 布局
  },
  encode: {
    value: 'value',  // 节点大小编码字段(可选)
  },
  // 节点样式
  nodeLabels: [
    { text: 'name', style: { fontSize: 12, dx: 6 } },
  ],
  // 连线样式
  style: {
    nodeSize: 5,
    nodeFill: '#5B8FF9',
    linkStroke: '#aaa',
    linkLineWidth: 1.5,
  },
});

chart.render();
```

## 纵向树形图(自上而下)

```javascript
chart.options({
  type: 'tree',
  data: treeData,
  coordinate: { transform: [{ type: 'transpose' }] },  // 转置为纵向
  nodeLabels: [
    {
      text: 'name',
      style: { fontSize: 11, textBaseline: 'bottom', dy: -6 },
    },
  ],
  style: {
    nodeFill: '#52c41a',
    nodeSize: 6,
    linkShape: 'smooth',  // 连线使用平滑曲线
  },
});
```

## 径向树形图(放射状)

```javascript
chart.options({
  type: 'tree',
  data: treeData,
  coordinate: { type: 'polar', innerRadius: 0.1 },  // 极坐标 = 径向布局
  style: {
    nodeFill: '#ff7875',
    nodeSize: 4,
  },
  nodeLabels: [
    {
      text: 'name',
      style: {
        fontSize: 10,
        textAlign: (d) => (d.x > Math.PI ? 'right' : 'left'),
      },
    },
  ],
});
```

## 常见错误与修正

### 错误:传入扁平数据而非嵌套 JSON
```javascript
// ❌ tree mark 需要嵌套 JSON(children 字段),不接受扁平数组
chart.options({
  type: 'tree',
  data: [
    { id: 1, parent: null, name: '根' },
    { id: 2, parent: 1, name: '子' },
  ],  // ❌ 扁平数据不能直接使用
});

// ✅ 需要嵌套格式
chart.options({
  type: 'tree',
   { name: '根', children: [{ name: '子' }] },  // ✅ 嵌套 JSON
});
```

### 错误:tree 与 treemap 混淆
```javascript
// tree:展示层级结构关系(节点+连线,强调层次和连接)
chart.options({ type: 'tree',  data: { value: hierarchyData } });

// treemap:按面积展示层级数据占比(矩形嵌套,强调大小和比例)
chart.options({ type: 'treemap',  data: { value: hierarchyData } });
```

---

## 节点数据访问规则(重要!)

层次结构图中,回调函数接收到的参数 `d` **不是原始数据对象**,而是 G2 用 d3-hierarchy 包装后的层次节点,**原始数据在 `d.data` 中**。

### 回调参数 d 的结构

```javascript
// d 是 d3-hierarchy 节点,结构如下:
{
  value: 10,               // 节点数值(d3 计算的子树总和)
  depth: 2,                // 层级深度(0 = 根节点)
  height: 0,               // 子树高度(叶子节点为 0)
  data: {                  // ← 原始数据在这里!
    name: '前端组',
    value: 10,
    // ... 其它自定义字段
  },
  path: ['根', '研发部', '前端组'],
}
```

### nodeLabels 中访问字段

tree mark 的 `nodeLabels` 使用字符串 `'name'` 时,G2 内部会查找 `d.data['name']`(有专项处理),所以字符串形式可以工作。但如需访问计算属性(`depth`、`height`)或条件渲染,必须使用回调:

```javascript
// ✅ 字符串形式(tree 的 nodeLabels 对 data 字段有专项处理)
nodeLabels: [
  { text: 'name', style: { fontSize: 12 } },
]

// ✅ 回调形式(需要条件判断或访问节点属性时)
nodeLabels: [
  {
    text: (d) => {
      if (d.height > 0) return d.data?.name;  // 父节点显示名称
      return `${d.data?.name}\n(${d.value})`;  // 叶节点显示名称+数值
    },
    style: { fontSize: 12 },
  },
]
```

### encode.color 必须用回调

与其他层次 mark 一样,`encode.color` 字符串对 tree 也**不起作用**:

```javascript
// ❌ 错误:color: 'type' 等价于 d['type'] = undefined
encode: {
  value: 'value',
  color: 'type',  // ❌ → undefined → 所有节点相同颜色
}

// ✅ 正确:必须用回调
encode: {
  value: 'value',
  color: (d) => d.data?.type,  // ✅ 通过 d.data 访问原始字段
}
```
references/marks/g2-mark-treemap.md
---
id: "g2-mark-treemap"
title: "G2 矩形树图(treemap)"
description: |
  G2 v5 内置 treemap Mark,用矩形面积表示层级数据中各节点的占比,
  数据采用嵌套的 children 树形结构,通过 encode.value 映射叶节点数值,
  支持多种 tile 布局算法和层级钻取交互。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "矩形树图"
  - "treemap"
  - "层级数据"
  - "占比"
  - "hierarchy"
  - "树形"
  - "spec"

related:
  - "g2-mark-arc-pie"
  - "g2-mark-sankey"
  - "g2-core-chart-init"

use_cases:
  - "展示文件目录/磁盘占用大小"
  - "产品类别的销售额占比(多层级)"
  - "证券市场板块涨跌热力图"

difficulty: "intermediate"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/graph/hierarchy/#treemap"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 800,
  height: 500,
});

// 树形嵌套数据
const data = {
  name: 'root',
  children: [
    {
      name: '技术',
      children: [
        { name: '前端', value: 120 },
        { name: '后端', value: 180 },
        { name: '算法', value: 80 },
      ],
    },
    {
      name: '产品',
      children: [
        { name: '移动端', value: 95 },
        { name: 'Web', value: 60 },
      ],
    },
    {
      name: '设计',
      children: [
        { name: 'UI', value: 70 },
        { name: 'UX', value: 45 },
      ],
    },
  ],
};

chart.options({
  type: 'treemap',
  data: {
    value: data
  },
  encode: {
    value: 'value',   // 叶节点数值字段
  },
  layout: {
    tile: 'treemapSquarify',    // 布局算法(默认)
    paddingInner: 2,            // 矩形间距
  },
  style: {
    labelText: (d) => d.data?.name || '',
    labelFill: '#fff',
    labelFontSize: 13,
    fillOpacity: 0.85,
  },
  legend: false,
});

chart.render();
```

## 数据配置形式说明

**为什么 treemap 使用 ` { value: data }` 而不是 `data`?**

层次数据是**对象**(包含 name/children),不是数组,必须使用完整形式:

```javascript
// ❌ 错误:层次数据不是数组,不能用简写
chart.options({
  type: 'treemap',
  data: hierarchyData,  // ❌ 不工作
});

// ✅ 正确:层次数据必须用完整形式
chart.options({
  type: 'treemap',
  data: { value: hierarchyData },  // ✅
});
```

**简写形式仅适用于数组数据**(满足三个条件:内联、是数组、无 transform)。

---

## 完整配置项

```javascript
chart.options({
  type: 'treemap',
  data: {
    value: hierarchicalData
  },
  encode: {
    value: 'value',   // 叶节点数值字段(决定矩形面积)
  },
  layout: {
    // tile 算法选择:
    // 'treemapSquarify'(默认,接近正方形)
    // 'treemapBinary'(二叉分割)
    // 'treemapDice'(横向分割)
    // 'treemapSlice'(纵向分割)
    // 'treemapSliceDice'(交替分割)
    tile: 'treemapSquarify',
    paddingInner: 2,      // 同层矩形间距(px)
    paddingOuter: 4,      // 外边距
    paddingTop: 20,       // 顶部留白(用于父节点标签)
    ratio: 1.618,         // 黄金比例(treemapSquarify 专用)
    ignoreParentValue: true,  // 忽略父节点自身 value
  },
  style: {
    // 矩形标签
    labelText: (d) => d.data?.name,
    labelFill: '#fff',
    labelFontSize: 12,
    labelPosition: 'top-left',  // 标签位置
    fillOpacity: 0.8,
    stroke: '#fff',
    lineWidth: 1,
  },
});
```

## 多层级标签(父节点 + 叶节点)

```javascript
chart.options({
  type: 'treemap',
  data: {
    value: data
  },
  encode: { value: 'value' },
  layout: {
    tile: 'treemapSquarify',
    paddingInner: 3,
    paddingTop: 24,       // 为父节点标题留空间
  },
  style: {
    // 叶节点显示名称
    labelText: (d) => {
      // path 是从根到当前节点的路径数组
      return d.depth > 1 ? d.data?.name : '';
    },
    // 父节点(depth=1)用大标签
    labelFontSize: (d) => d.depth === 1 ? 14 : 11,
    labelFontWeight: (d) => d.depth === 1 ? 'bold' : 'normal',
    labelFill: '#fff',
    fillOpacity: (d) => d.depth === 1 ? 0.6 : 0.85,
  },
});
```

## 股票板块热力图(市场涨跌)

```javascript
const marketData = {
  name: 'A股',
  children: [
    {
      name: '科技',
      children: [
        { name: '华为', value: 1200, change: 3.5 },
        { name: '腾讯', value: 980,  change: -1.2 },
        { name: '阿里', value: 850,  change: 0.8 },
      ],
    },
    {
      name: '金融',
      children: [
        { name: '工行', value: 2100, change: 1.1 },
        { name: '建行', value: 1800, change: -0.5 },
      ],
    },
  ],
};

chart.options({
  type: 'treemap',
  data: {
    value: marketData
  },
  encode: {
    value: 'value',
    // 颜色按涨跌幅映射
    color: (d) => d.data?.change ?? 0,
  },
  scale: {
    color: {
      type: 'diverging',
      palette: 'RdYlGn',      // 红(跌)→ 黄(平)→ 绿(涨)
      domain: [-5, 0, 5],
    },
  },
  style: {
    labelText: (d) =>
      d.data?.name && d.data?.change != null
        ? `${d.data.name}\n${d.data.change > 0 ? '+' : ''}${d.data.change}%`
        : d.data?.name || '',
    labelFill: '#fff',
    labelFontSize: 12,
  },
  legend: { color: { position: 'top' } },
});
```

## 常见错误与修正

### 错误 1:数据格式非树形结构

```javascript
// ❌ 错误:treemap 需要树形嵌套数据,不能用平坦数组
chart.options({
  type: 'treemap',
  data: [
    { name: '前端', value: 120, parent: '技术' },   // ❌ 平坦格式
  ],
});

// ✅ 正确:需要 children 嵌套结构
chart.options({
  type: 'treemap',
  data: {
    value: {
      name: 'root',
      children: [
        {
          name: '技术',
          children: [
            { name: '前端', value: 120 },   // ✅ 叶节点有 value
          ],
        },
      ],
    },
  },
  encode: { value: 'value' },
});
```

### 错误 2:encode.value 字段名与数据不匹配

```javascript
// ❌ 错误:数据中叶节点字段是 size,但 encode.value 写的是 value
const data = {
  value: { name: 'root', children: [{ name: 'A', size: 100 }] }
 };
chart.options({
  encode: { value: 'value' },   // ❌ 字段名不匹配
});

// ✅ 正确
chart.options({
  encode: { value: 'size' },    // ✅ 与数据字段一致
});
```

---

## 节点数据访问规则(重要!)

层次结构图中,回调函数接收到的参数 `d` **不是原始数据对象**,而是 G2 用 d3-hierarchy 包装后的层次节点,**原始数据在 `d.data` 中**。

### 为什么 `encode.color: 'growth'` 不起作用?

**根本原因**:当 encode 是字符串时,G2 内部做的是 `datum[fieldName]`,直接访问节点对象的属性。对于层次 mark,`datum` 是层次节点(hierarchy node),不是原始数据对象:

```
d['growth']        → undefined  ❌(层次节点没有 growth 属性)
d.data['growth']   → 3.5        ✅(原始数据在 d.data 上)
```

**特例**:`encode.value: 'value'` 看起来用字符串也能工作,是因为 G2 对层次 mark 的 `value` 通道做了**专项处理**,直接读取节点的 `value` 属性(d3-hierarchy 计算后的值)。其他通道(`color`、`shape` 等)没有这个特殊处理,字符串会直接 `datum[field]` 导致 `undefined`。

```javascript
// ❌ encode.color: 'growth' 的内部执行等价于:
const color = datum['growth']  // datum 是层次节点,'growth' 不在节点上 → undefined
// 结果:所有矩形使用相同颜色

// ✅ 使用回调才能正确访问:
const color = datum.data?.['growth']  // datum.data 才是原始数据对象
```

### 回调参数 d 的结构

```javascript
// d 是 d3-hierarchy 节点,结构如下:
{
  value: 100,              // 节点数值(d3 计算的叶子值之和)
  depth: 2,                // 层级深度(0 = 根节点)
  height: 0,               // 子树高度(叶子节点为 0)
   {                  // ← 原始数据在这里!
    name: '前端',
    value: 120,
    growth: 3.5,
    // ... 其它自定义字段
  },
  path: ['root', '技术', '前端'],  // 从根到当前节点的路径
}
```

### encode 中访问字段

```javascript
// ❌ 错误:字符串字段名对 color/shape 等通道不起作用,返回 undefined
encode: {
  value: 'value',   // ✅ value 通道有专项处理,字符串可用
  color: 'growth',  // ❌ 等价于 d['growth'] = undefined,所有矩形颜色相同
}

// ✅ 正确:除 value 外的所有通道必须用回调函数
encode: {
  value: 'value',
  color: (d) => d.data?.growth,  // ✅ 通过 d.data 访问原始字段
}
```

### 常用着色策略

```javascript
// 按父节点着色(推荐,同门类同色,视觉分组清晰)
color: (d) => d.path?.[1] || d.data?.name

// 按层级深度着色
color: (d) => d.depth

// 按自定义字段着色
color: (d) => d.data?.growth
color: (d) => d.data?.category

// 按数值着色(连续色板)
color: (d) => d.value
```

### 配合 scale 自定义颜色

```javascript
encode: {
  value: 'value',
  color: (d) => d.data?.growth,
},
scale: {
  color: {
    type: 'diverging',
    palette: 'RdYlGn',
    domain: [-5, 0, 5],
  },
}
```

### 错误 3:encode.color 使用字符串字段名导致所有矩形颜色相同

```javascript
// ❌ 错误:color: 'growth' 等价于 d['growth'],层次节点上没有 growth 属性 → undefined
chart.options({
  type: 'treemap',
  data: { value: data },
  encode: {
    value: 'value',
    color: 'growth',  // ❌ d['growth'] = undefined → 所有矩形显示相同颜色
  },
});

// ✅ 正确:color 必须用回调函数,通过 d.data 访问原始字段
chart.options({
  type: 'treemap',
  data: { value: data },
  encode: {
    value: 'value',
    color: (d) => d.data?.growth,  // ✅ 按涨跌幅着色
  },
});
```

### 错误 4:labels/style 中使用 d.name 导致 undefined

```javascript
// ❌ 错误:treemap 节点的原始字段在 d.data 中,d.name 是 undefined
style: {
  labelText: (d) => d.name,  // ❌ d.name 是 undefined
}

// ✅ 正确:通过 d.data 访问原始数据字段
style: {
  labelText: (d) => d.data?.name || '',  // ✅
}
```
references/marks/g2-mark-vector.md
---
id: "g2-mark-vector"
title: "G2 向量图(vector)"
description: |
  vector mark 在每个数据点绘制一个有方向和大小的箭头,
  用于展示风场、水流方向等具有方向和强度的场数据。
  encode 中用 rotate 通道控制方向(角度),size 通道控制长度。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "vector"
  - "向量"
  - "方向场"
  - "风场"
  - "箭头"
  - "流场"

related:
  - "g2-mark-point-scatter"
  - "g2-core-encode-channel"

use_cases:
  - "风场可视化(风向和风速)"
  - "流体力学模拟结果展示"
  - "梯度场、力场可视化"

difficulty: "advanced"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/point/#vector"
---

## 最小可运行示例(风场)

```javascript
import { Chart } from '@antv/g2';

// 模拟风场数据:每个格点有位置、风向(角度)和风速(大小)
const data = [];
for (let x = 0; x < 10; x++) {
  for (let y = 0; y < 10; y++) {
    const angle = (x * 30 + y * 15) % 360;  // 风向(度)
    const speed = 2 + Math.random() * 8;      // 风速
    data.push({ x, y, angle, speed });
  }
}

const chart = new Chart({ container: 'container', width: 600, height: 600 });

chart.options({
  type: 'vector',
  data,
  encode: {
    x: 'x',
    y: 'y',
    rotate: 'angle',   // 箭头旋转角度(度,0=向右,顺时针)
    size: 'speed',     // 箭头长度(映射到速度)
    color: 'speed',    // 颜色映射风速
  },
  scale: {
    color: { type: 'sequential', palette: 'viridis' },
    size: { range: [6, 24] },
  },
  style: {
    arrow: true,   // 显示箭头
  },
  legend: { color: { title: '风速 (m/s)' } },
});

chart.render();
```

## 配置项

```javascript
chart.options({
  type: 'vector',
  data,
  encode: {
    x: 'x',
    y: 'y',
    rotate: 'direction',  // 方向角度字段(0°=向右,顺时针增加)
    size: 'magnitude',    // 向量长度字段
    color: 'intensity',   // 颜色编码字段(可选)
  },
  style: {
    arrow: true,          // 是否显示箭头,默认 true
    arrowSize: 6,         // 箭头头部大小(px)
  },
});
```

## 常见错误与修正

### 错误:rotate 是弧度而不是角度
```javascript
// ❌ 如果原始数据是弧度,直接用 rotate 通道会导致方向错误
const data = [{ ..., direction: Math.PI / 4 }];  // 弧度
chart.options({ encode: { rotate: 'direction' } });  // ❌ G2 期望角度

// ✅ 将弧度转换为角度
const data = data.map(d => ({ ...d, dirDeg: (d.direction * 180) / Math.PI }));
chart.options({ encode: { rotate: 'dirDeg' } });  // ✅ 角度值
```
references/marks/g2-mark-venn.md
---
id: "g2-mark-venn"
title: "G2 Venn Diagram Mark"
description: |
  韦恩图 Mark。使用 path 标记配合 venn 转换,展示集合之间的交集、并集关系。
  适用于用户群体分析、产品功能对比、技能重叠分析等场景。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "韦恩图"
  - "venn"
  - "集合关系"
  - "交集"

related:
  - "g2-mark-chord"
  - "g2-mark-sankey"

use_cases:
  - "用户群体重叠分析"
  - "产品功能对比"
  - "技能重叠分析"

anti_patterns:
  - "集合数量 >4 应使用其他图表"
  - "数值差异过大不适合"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/mark/venn"
---

## 核心概念

韦恩图展示集合之间的交集关系:
- 使用 `path` 标记
- 配合 `venn` 数据转换
- 重叠区域表示交集

**数据格式:**
- `sets`:集合名称数组
- `size`:集合大小
- `label`:显示标签

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  autoFit: true,
});

chart.options({
  type: 'path',
  data: {
    type: 'inline',
    value: [
      { sets: ['微信'], size: 1200, label: '微信' },
      { sets: ['微博'], size: 800, label: '微博' },
      { sets: ['微信', '微博'], size: 300, label: '重叠' },
    ],
    transform: [{ type: 'venn' }],
  },
  encode: {
    d: 'path',
    color: 'key',
  },
  labels: [
    { position: 'inside', text: (d) => d.label || '' },
  ],
  style: {
    opacity: (d) => (d.sets.length > 1 ? 0.3 : 0.7),
  },
});

chart.render();
```

## 常用变体

### 三集合韦恩图

```javascript
chart.options({
  type: 'path',
  data: {
    type: 'inline',
    value: [
      { sets: ['前端'], size: 12, label: '前端' },
      { sets: ['后端'], size: 15, label: '后端' },
      { sets: ['设计'], size: 8, label: '设计' },
      { sets: ['前端', '后端'], size: 5, label: '全栈' },
      { sets: ['前端', '设计'], size: 3 },
      { sets: ['后端', '设计'], size: 2 },
      { sets: ['前端', '后端', '设计'], size: 1 },
    ],
    transform: [{ type: 'venn' }],
  },
  encode: { d: 'path', color: 'key' },
});
```

### 空心韦恩图

```javascript
chart.options({
  type: 'path',
  data: {
    type: 'inline',
    value: [...],
    transform: [{ type: 'venn' }],
  },
  encode: {
    d: 'path',
    color: 'key',
    shape: 'hollow',  // 空心样式
  },
  style: {
    lineWidth: 3,
  },
});
```

### 带交互

```javascript
chart.options({
  type: 'path',
  data: { type: 'inline', value: [...], transform: [{ type: 'venn' }] },
  encode: { d: 'path', color: 'key' },
  state: {
    inactive: { opacity: 0.2 },
    active: { opacity: 0.9 },
  },
  interactions: [{ type: 'elementHighlight' }],
});
```

## 完整类型参考

```typescript
interface VennData {
  sets: string[];    // 集合名称数组
  size: number;      // 集合大小
  label?: string;    // 显示标签
}

interface VennOptions {
  type: 'path';
  data: {
    type: 'inline';
    value: VennData[];
    transform: [{ type: 'venn' }];
  };
  encode: {
    d: 'path';
    color: 'key';
  };
}
```

## 韦恩图 vs 其他图表

| 场景 | 推荐图表 |
|------|----------|
| 集合交集关系 | 韦恩图 |
| 层次结构 | 旭日图 |
| 流向关系 | 桑基图 |

## 常见错误与修正

### 错误 1:缺少 venn 转换

```javascript
// ❌ 问题:没有 venn 转换
data: { type: 'inline', value: [...] }

// ✅ 正确:添加 venn 转换
data: { type: 'inline', value: [...], transform: [{ type: 'venn' }] }
```

### 错误 2:集合数量过多

```javascript
// ⚠️ 注意:集合数量建议不超过 4 个
// 5 个以上集合会导致视觉混乱
```

### 错误 3:encode 配置错误

```javascript
// ❌ 问题:使用 x/y 编码
encode: { x: 'sets', y: 'size' }

// ✅ 正确:使用 d 编码 path
encode: { d: 'path', color: 'key' }
```
references/marks/g2-mark-violin.md
---
id: "g2-mark-violin"
title: "G2 Violin Plot Mark"
description: |
  小提琴图 Mark。使用 density 和 boxplot 组合,结合核密度估计展示数据分布形状。
  适用于多组数据分布比较、探索数据分布模式等场景。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "小提琴图"
  - "violin"
  - "密度分布"
  - "统计分析"

related:
  - "g2-mark-boxplot"
  - "g2-mark-density"

use_cases:
  - "多组数据分布比较"
  - "数据分布模式探索"
  - "异常值检测"

anti_patterns:
  - "数据量少(<20)应使用箱形图"
  - "离散数据不适合"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/mark/violin"
---

## 核心概念

小提琴图结合了箱形图和核密度估计:
- 展示完整的数据分布形状
- 叠加箱形图的统计信息
- 通过 KDE(核密度估计)生成密度轮廓

**主要组成部分:**
- 密度轮廓:展示数据分布密度
- 箱形图:显示中位数、四分位数
- 中位线:标示中位数位置

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  theme: 'classic',
});

chart.options({
  type: 'view',
  data: {
    type: 'fetch',
    value: 'https://assets.antv.antgroup.com/g2/species.json',
  },
  children: [
    {
      type: 'density',
      data: {
        transform: [
          { type: 'kde', field: 'y', groupBy: ['x', 'species'] },
        ],
      },
      encode: {
        x: 'x',
        y: 'y',
        series: 'species',
        color: 'species',
        size: 'size',
      },
      tooltip: false,
    },
    {
      type: 'boxplot',
      encode: {
        x: 'x',
        y: 'y',
        series: 'species',
        color: 'species',
        shape: 'violin',
      },
      style: {
        opacity: 0.5,
        strokeOpacity: 0.5,
        point: false,
      },
    },
  ],
});

chart.render();
```

## 常用变体

### 极坐标小提琴图

```javascript
chart.options({
  type: 'view',
  coordinate: { type: 'polar' },
  data: {
    type: 'fetch',
    value: 'https://assets.antv.antgroup.com/g2/species.json',
  },
  children: [
    {
      type: 'density',
      data: { transform: [{ type: 'kde', field: 'y', groupBy: ['x', 'species'] }] },
      encode: { x: 'x', y: 'y', series: 'species', color: 'species', size: 'size' },
      tooltip: false,
    },
    {
      type: 'boxplot',
      encode: { x: 'x', y: 'y', series: 'species', color: 'species', shape: 'violin' },
      style: { opacity: 0.5, strokeOpacity: 0.5, point: false },
    },
  ],
});
```

### 纯密度图

```javascript
chart.options({
  type: 'density',
  data: {
    type: 'fetch',
    value: 'https://assets.antv.antgroup.com/g2/species.json',
    transform: [
      { type: 'kde', field: 'y', groupBy: ['x'], size: 20 },
    ],
  },
  encode: {
    x: 'x',
    y: 'y',
    color: 'x',
    size: 'size',
  },
  tooltip: false,
});
```

### 带异常值标记

```javascript
chart.options({
  type: 'view',
  data: {
    type: 'fetch',
    value: 'https://assets.antv.antgroup.com/g2/morley.json',
  },
  children: [
    {
      type: 'density',
      data: { transform: [{ type: 'kde', field: 'Speed', groupBy: ['Expt'] }] },
      encode: { x: 'Expt', y: 'Speed', size: 'size', color: 'Expt' },
      style: { fillOpacity: 0.4 },
      tooltip: false,
    },
    {
      type: 'boxplot',
      encode: { x: 'Expt', y: 'Speed', color: 'Expt', shape: 'violin' },
      style: {
        opacity: 0.8,
        point: { fill: 'red', size: 3 },  // 异常值标记
      },
    },
  ],
});
```

## 完整类型参考

```typescript
interface ViolinOptions {
  type: 'view';
  data: any; // 原始数据源
  children: [
    {
      type: 'density';
      data: {
        transform: [
          {
            type: 'kde';
            field: string;      // 数值字段
            groupBy: string[];  // 分组字段(必须包含x轴字段和分组字段)
            size?: number;      // 采样点数,默认为10,建议设置为20~50以获得更平滑的曲线
          }
        ]
      };
      encode: {
        x: string;
        y: string;
        size: 'size';
        color?: string;
        series: string;
      };
      tooltip?: boolean; // 推荐关闭,避免与boxplot重复
    },
    {
      type: 'boxplot';
      encode: {
        x: string;
        y: string;
        shape: 'violin';
        color?: string;
        series: string;
      };
      style?: {
        opacity?: number;
        strokeOpacity?: number;
        point?: boolean | object; // 是否显示异常点
      };
    }
  ];
}
```

## 小提琴图 vs 箱形图

| 特性 | 小提琴图 | 箱形图 |
|------|----------|--------|
| 分布信息 | 完整密度 | 统计摘要 |
| 多峰检测 | 支持 | 不支持 |
| 简洁程度 | 较复杂 | 简洁 |

## 常见错误与修正

### 错误 1:缺少 KDE 转换

```javascript
// ❌ 问题:没有核密度估计
data: { type: 'fetch', value: 'data.json' }

// ✅ 正确:添加 kde 转换
data: {
  type: 'fetch',
  value: 'data.json',
  transform: [{ type: 'kde', field: 'y', groupBy: ['x', 'species'] }],
}
```

### 错误 2:数据量过少

```javascript
// ⚠️ 注意:每个分组建议至少 20-30 个数据点
// 数据量少时建议使用箱形图
```

### 错误 3:缺少 boxplot 叠加

```javascript
// ❌ 问题:只有密度图,缺少统计信息
children: [{ type: 'density', ... }]

// ✅ 正确:叠加 boxplot
children: [
  { type: 'density', ... },
  { 
    type: 'boxplot', 
    encode: { 
      shape: 'violin',
      x: 'x',
      y: 'y',
      series: 'species',
      color: 'species'
    } 
  },
]
```

### 错误 4:KDE transform 配置错误

```javascript
// ❌ 问题:groupBy 字段不完整或缺失
transform: [{ type: 'kde', field: 'y', groupBy: ['x'] }]

// ✅ 正确:确保 groupBy 包含所有分组字段
transform: [{ type: 'kde', field: 'y', groupBy: ['x', 'species'] }]
```

### 错误 5:encode 映射不完整

```javascript
// ❌ 问题:缺少必要的 encode 映射
encode: { x: 'x', y: 'y' }

// ✅ 正确:确保映射了所有必需字段
encode: {
  x: 'x',
  y: 'y',
  series: 'species',
  color: 'species',
  size: 'size'
}
```
references/marks/g2-mark-wordcloud.md
---
id: "g2-mark-wordcloud"
title: "G2 词云图(wordCloud)"
description: |
  wordCloud mark 将词语按频率/权重排布成云状图,高频词字体更大。
  数据需包含文本字段(text)和权重字段(value),
  G2 内置词云布局算法,自动处理词语重叠。

library: "g2"
version: "5.x"
category: "marks"
tags:
  - "词云"
  - "wordCloud"
  - "word cloud"
  - "文本可视化"
  - "词频"

related:
  - "g2-mark-text"
  - "g2-core-chart-init"

use_cases:
  - "展示文本数据的词频分布"
  - "用户评论关键词可视化"
  - "话题热度展示"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/examples/general/other/#word-cloud"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const data = [
  { word: '数据可视化', count: 120 },
  { word: '图表', count: 85 },
  { word: '交互', count: 70 },
  { word: 'JavaScript', count: 95 },
  { word: '前端', count: 110 },
  { word: 'AntV', count: 65 },
  { word: 'G2', count: 100 },
  { word: '分析', count: 78 },
  { word: '用户', count: 55 },
  { word: '体验', count: 60 },
];

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'wordCloud',
  data,
  encode: {
    text: 'word',    // 显示的词语字段
    color: 'word',   // 颜色编码(每个词不同颜色)
    fontSize: {      // 字体大小映射(可用字段名或固定范围)
      field: 'count',
      range: [12, 60],  // 最小/最大字号
    },
  },
  layout: {
    spiral: 'archimedean',  // 布局螺旋形状:'archimedean' | 'rectangular'
    padding: 2,             // 词语间距
  },
  style: {
    fontFamily: 'Impact, sans-serif',
    fontWeight: 'bold',
  },
});

chart.render();
```

## 带旋转的词云

```javascript
chart.options({
  type: 'wordCloud',
  data,
  encode: {
    text: 'word',
    color: 'count',
    rotate: {
      // 随机旋转角度:水平或垂直
      callback: () => (Math.random() > 0.7 ? 90 : 0),
    },
    fontSize: { field: 'count', range: [14, 56] },
  },
  scale: {
    color: { type: 'sequential', palette: 'blues' },
  },
  layout: { padding: 4 },
});
```

## 固定词语颜色分组

```javascript
chart.options({
  type: 'wordCloud',
  data: wordsWithCategory,
  encode: {
    text: 'word',
    color: 'category',  // 按类别着色(分类色板)
    fontSize: { field: 'count', range: [16, 50] },
  },
  scale: {
    color: { type: 'ordinal', palette: 'set2' },
  },
});
```

## 常见错误与修正

### 错误 1:数据没有权重字段——所有词大小相同
```javascript
// ❌ 没有 fontSize 编码,所有词大小一样
chart.options({
  type: 'wordCloud',
  data: [{ word: 'A' }, { word: 'B' }],  // ❌ 没有数值字段
  encode: { text: 'word' },
});

// ✅ 必须提供权重字段并配置 fontSize
chart.options({
  encode: {
    text: 'word',
    fontSize: { field: 'count', range: [14, 60] },  // ✅
  },
});
```

### 错误 2:容器太小导致词语显示不全
```javascript
// ❌ 小容器下词云布局算法无法放置所有词
const chart = new Chart({ container: 'container', width: 300, height: 200 });  // ❌ 太小

// ✅ 词云推荐最小 400×300,多词时 600×400 以上
const chart = new Chart({ container: 'container', width: 640, height: 480 });  // ✅
```

### 错误 3:词语太多字号设置太大——大量词语无法布局
```javascript
// ❌ 100+ 个词,最大字号 80px,大量词语被丢弃
encode: { fontSize: { field: 'count', range: [20, 80] } }  // ❌ range 太大

// ✅ 词多时缩小字号范围
encode: { fontSize: { field: 'count', range: [10, 40] } }  // ✅
```
references/palette/g2-palette-category10.md
---
id: "g2-palette-category10"
title: "G2 Category10 调色板"
description: |
  AntV 经典 10 色调色板,用于分类数据的颜色映射。
  包含 10 种精心设计的颜色,适合大多数分类可视化场景。

library: "g2"
version: "5.x"
category: "palette"
tags:
  - "调色板"
  - "palette"
  - "颜色"
  - "分类"
  - "10色"

related:
  - "g2-palette-category20"
  - "g2-scale-ordinal"
  - "g2-theme-builtin"

use_cases:
  - "分类数据的默认颜色"
  - "柱状图、折线图的颜色映射"
  - "需要 10 种以内颜色的场景"

anti_patterns:
  - "超过 10 个类别时应考虑 Category20 或自定义调色板"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/palette"
---

## 核心概念

Category10 是 AntV 的默认分类调色板:
- 包含 10 种颜色
- 颜色经过精心设计,易于区分
- 适合大多数分类可视化场景

**颜色列表:**
```
#5B8FF9  - 蓝色
#5AD8A6  - 绿色
#5D7092  - 灰蓝色
#F6BD16  - 黄色
#6F5EF9  - 紫色
#6DC8EC  - 青色
#945FB9  - 深紫色
#FF9845  - 橙色
#1E9493  - 深青色
#FF99C3  - 粉色
```

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'interval',
  data: [
    { category: 'A', value: 100 },
    { category: 'B', value: 150 },
    { category: 'C', value: 80 },
  ],
  encode: {
    x: 'category',
    y: 'value',
    color: 'category',
  },
  // Category10 是默认调色板,无需显式指定
});

chart.render();
```

## 常用变体

### 显式指定调色板

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
  scale: {
    color: {
      type: 'ordinal',
      range: 'category10',  // 显式指定
    },
  },
});
```

### 自定义颜色范围

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
  scale: {
    color: {
      type: 'ordinal',
      range: [
        '#5B8FF9',
        '#5AD8A6',
        '#5D7092',
        '#F6BD16',
        '#6F5EF9',
      ],
    },
  },
});
```

### 使用 Theme 配置

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
  theme: {
    defaultCategory10: 'category10',
  },
});
```

## 完整颜色参考

| 索引 | 颜色值 | 颜色名 |
|------|--------|--------|
| 0 | #5B8FF9 | 蓝色 |
| 1 | #5AD8A6 | 绿色 |
| 2 | #5D7092 | 灰蓝色 |
| 3 | #F6BD16 | 黄色 |
| 4 | #6F5EF9 | 紫色 |
| 5 | #6DC8EC | 青色 |
| 6 | #945FB9 | 深紫色 |
| 7 | #FF9845 | 橙色 |
| 8 | #1E9493 | 深青色 |
| 9 | #FF99C3 | 粉色 |

## 与 Category20 的对比

| 特性 | Category10 | Category20 |
|------|------------|------------|
| 颜色数量 | 10 | 20 |
| 颜色风格 | 饱和度一致 | 饱和度交替 |
| 适用场景 | ≤10 类别 | 10-20 类别 |
| 默认使用 | 是 | 否 |

## 常见错误与修正

### 错误 1:类别超过 10 个

```javascript
// ⚠️ 注意:超过 10 个类别会循环使用颜色
// 类别 11 会使用与类别 1 相同的颜色

// ✅ 解决方案 1:使用 Category20
scale: {
  color: { type: 'ordinal', range: 'category20' }
}

// ✅ 解决方案 2:自定义更多颜色
scale: {
  color: {
    type: 'ordinal',
    range: [...customColors]
  }
}
```

### 错误 2:颜色值格式错误

```javascript
// ❌ 错误:颜色值格式不正确
range: ['rgb(91, 143, 249)', ...]

// ✅ 正确:使用标准 HEX 格式
range: ['#5B8FF9', ...]
```
references/palette/g2-palette-category20.md
---
id: "g2-palette-category20"
title: "G2 Category20 调色板"
description: |
  AntV 经典 20 色调色板,用于分类数据的颜色映射。
  包含 20 种颜色,采用饱和度交替的设计,适合更多类别的可视化场景。

library: "g2"
version: "5.x"
category: "palette"
tags:
  - "调色板"
  - "palette"
  - "颜色"
  - "分类"
  - "20色"

related:
  - "g2-palette-category10"
  - "g2-scale-ordinal"
  - "g2-theme-builtin"

use_cases:
  - "超过 10 个类别的颜色映射"
  - "需要更多颜色区分的场景"
  - "复杂分类数据可视化"

anti_patterns:
  - "类别较少时建议使用 Category10"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/palette"
---

## 核心概念

Category20 是 AntV 的扩展分类调色板:
- 包含 20 种颜色
- 采用饱和度交替的设计模式
- 适合 10-20 个类别的场景

**设计特点:**
- 前半部分为饱和色(与 Category10 一致)
- 后半部分为低饱和度色
- 交替使用可增加区分度

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'interval',
  data: [
    { category: 'A', value: 100 },
    { category: 'B', value: 150 },
    // ... 更多类别
  ],
  encode: {
    x: 'category',
    y: 'value',
    color: 'category',
  },
  scale: {
    color: {
      type: 'ordinal',
      range: 'category20',
    },
  },
});

chart.render();
```

## 常用变体

### 显式指定颜色范围

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
  scale: {
    color: {
      type: 'ordinal',
      range: [
        '#5B8FF9', '#CDDDFD',
        '#5AD8A6', '#CDF3E4',
        '#5D7092', '#CED4DE',
        '#F6BD16', '#FCEBB9',
        '#6F5EF9', '#D3CEFD',
        '#6DC8EC', '#D3EEF9',
        '#945FB9', '#DECFEA',
        '#FF9845', '#FFE0C7',
        '#1E9493', '#BBDEDE',
        '#FF99C3', '#FFE0ED',
      ],
    },
  },
});
```

### 结合自定义颜色

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
  scale: {
    color: {
      type: 'ordinal',
      range: [
        ...customColors.slice(0, 10),
        '#5B8FF9', '#CDDDFD', // 补充 Category20 的颜色
        // ...
      ],
    },
  },
});
```

## 完整颜色参考

| 索引 | 颜色值 | 饱和度 |
|------|--------|--------|
| 0 | #5B8FF9 | 高 |
| 1 | #CDDDFD | 低 |
| 2 | #5AD8A6 | 高 |
| 3 | #CDF3E4 | 低 |
| 4 | #5D7092 | 高 |
| 5 | #CED4DE | 低 |
| 6 | #F6BD16 | 高 |
| 7 | #FCEBB9 | 低 |
| 8 | #6F5EF9 | 高 |
| 9 | #D3CEFD | 低 |
| 10 | #6DC8EC | 高 |
| 11 | #D3EEF9 | 低 |
| 12 | #945FB9 | 高 |
| 13 | #DECFEA | 低 |
| 14 | #FF9845 | 高 |
| 15 | #FFE0C7 | 低 |
| 16 | #1E9493 | 高 |
| 17 | #BBDEDE | 低 |
| 18 | #FF99C3 | 高 |
| 19 | #FFE0ED | 低 |

## 与 Category10 的对比

| 特性 | Category10 | Category20 |
|------|------------|------------|
| 颜色数量 | 10 | 20 |
| 颜色风格 | 饱和度一致 | 饱和度交替 |
| 适用场景 | ≤10 类别 | 10-20 类别 |
| 默认使用 | 是 | 否 |
| 区分难度 | 较易 | 需注意相邻色 |

## 设计建议

### 类别数量建议

| 类别数量 | 推荐调色板 |
|---------|-----------|
| 1-5 | Category10 |
| 6-10 | Category10 |
| 11-15 | Category20 |
| 16-20 | Category20 |
| >20 | 自定义或分组 |

### 使用技巧

```javascript
// 技巧:利用饱和度交替增加区分度
// 将重要类别放在高饱和度位置(偶数索引)

// 例如:高亮重要类别
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
  scale: {
    color: {
      type: 'ordinal',
      range: [
        '#5B8FF9',  // 高饱和 - 类别 A
        '#CDDDFD',  // 低饱和 - 类别 B
        '#5AD8A6',  // 高饱和 - 类别 C(重要)
        // ...
      ],
    },
  },
});
```

## 常见错误与修正

### 错误 1:类别超过 20 个

```javascript
// ⚠️ 注意:超过 20 个类别会循环使用颜色

// ✅ 解决方案 1:自定义更多颜色
scale: {
  color: {
    type: 'ordinal',
    range: [...30colors]
  }
}

// ✅ 解决方案 2:合并小类别
// 将小类别合并为"其他"类别
```

### 错误 2:相邻颜色区分度不够

```javascript
// ⚠️ 注意:相邻的低饱和度颜色可能难以区分

// ✅ 解决方案:调整 domain 顺序
scale: {
  color: {
    type: 'ordinal',
    domain: ['A', 'C', 'E', 'B', 'D'],  // 交替高/低饱和度
    range: 'category20'
  }
}
```
references/patterns/g2-pattern-performance.md
---
id: "g2-pattern-performance"
title: "G2 大数据量性能优化"
description: |
  G2 处理大量数据时的性能优化策略:数据预聚合、LTTB 降采样、
  Canvas 渲染器确认、高频实时数据节流更新等。
  提供各场景的数据量阈值参考和具体优化方案。

library: "g2"
version: "5.x"
category: "patterns"
tags:
  - "性能优化"
  - "performance"
  - "大数据"
  - "Canvas"
  - "降采样"
  - "聚合"

related:
  - "g2-core-chart-init"
  - "g2-data-transform-patterns"

use_cases:
  - "图表数据量超过万条时的优化"
  - "实时数据流的高频更新场景"

difficulty: "advanced"
completeness: "full"
---

## 数据量阈值参考

| 场景 | 数据量 | 建议方案 |
|------|--------|---------|
| 折线图 | < 1,000 点 | 直接渲染 |
| 折线图 | 1,000 ~ 10,000 点 | 降采样到 500 点以内 |
| 折线图 | > 10,000 点 | 后端聚合 + 时间范围过滤 |
| 散点图 | < 5,000 点 | 直接渲染 |
| 散点图 | 5,000 ~ 50,000 点 | 开启 Canvas 渲染 + 降采样 |

## 数据预聚合(最重要的优化)

```javascript
// 10 万条日粒度数据 → 聚合为 365 条月粒度
function aggregateTimeSeries(data, dateKey, valueKey, granularity = 'month') {
  const getGroupKey = (dateStr) => {
    const d = new Date(dateStr);
    if (granularity === 'month') {
      return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
    }
    if (granularity === 'quarter') {
      return `${d.getFullYear()}-Q${Math.ceil((d.getMonth() + 1) / 3)}`;
    }
    return d.getFullYear().toString();
  };

  const groups = {};
  data.forEach(d => {
    const key = getGroupKey(d[dateKey]);
    if (!groups[key]) groups[key] = { date: key, sum: 0, count: 0, min: Infinity, max: -Infinity };
    groups[key].sum += d[valueKey];
    groups[key].count += 1;
    groups[key].min = Math.min(groups[key].min, d[valueKey]);
    groups[key].max = Math.max(groups[key].max, d[valueKey]);
  });

  return Object.values(groups)
    .map(g => ({ date: g.date, value: g.sum / g.count, min: g.min, max: g.max }))
    .sort((a, b) => a.date.localeCompare(b.date));
}

chart.options({
  data: aggregateTimeSeries(rawData, 'timestamp', 'value'),
  type: 'line',
  encode: { x: 'date', y: 'value' },
});
```

## 折线图降采样(LTTB 算法)

```javascript
// Largest Triangle Three Buckets (LTTB) 降采样
// 保留视觉上最重要的 N 个点,同时保持折线形状
function lttb(data, threshold) {
  const dataLength = data.length;
  if (threshold >= dataLength || threshold === 0) return data;

  const sampled = [];
  let sampledIndex = 0;
  const bucketSize = (dataLength - 2) / (threshold - 2);
  let a = 0;
  sampled[sampledIndex++] = data[a];

  for (let i = 0; i < threshold - 2; i++) {
    const rangeStart = Math.floor((i + 1) * bucketSize) + 1;
    const rangeEnd = Math.min(Math.floor((i + 2) * bucketSize) + 1, dataLength);

    let avgX = 0, avgY = 0;
    const avgRangeStart = Math.floor((i + 1) * bucketSize) + 1;
    const avgRangeEnd = Math.min(Math.floor((i + 2) * bucketSize) + 1, dataLength);
    for (let j = avgRangeStart; j < avgRangeEnd; j++) {
      avgX += data[j].x;
      avgY += data[j].y;
    }
    avgX /= (avgRangeEnd - avgRangeStart);
    avgY /= (avgRangeEnd - avgRangeStart);

    let maxArea = -1;
    let nextA = rangeStart;
    const pointAX = data[a].x;
    const pointAY = data[a].y;
    for (let j = rangeStart; j < rangeEnd; j++) {
      const area = Math.abs(
        (pointAX - avgX) * (data[j].y - pointAY) -
        (pointAX - data[j].x) * (avgY - pointAY)
      );
      if (area > maxArea) { maxArea = area; nextA = j; }
    }
    sampled[sampledIndex++] = data[nextA];
    a = nextA;
  }
  sampled[sampledIndex++] = data[dataLength - 1];
  return sampled;
}

// 10000 个点降采样到 500 个
const sampledData = lttb(rawData, 500);
chart.options({  sampledData, type: 'line', encode: { x: 'x', y: 'y' } });
```

## 确认使用 Canvas 渲染器

```javascript
// G2 默认使用 Canvas 渲染,比 SVG 快得多
// 大数据量时确认没有被切换为 SVG
const chart = new Chart({
  container: 'container',
  renderer: 'canvas',   // 默认,大数据量下比 SVG 快 5-10x
  width: 800,
  height: 400,
});
```

## 高频实时数据更新优化

```javascript
// 使用 requestAnimationFrame 节流(最多每帧更新一次)
let pendingData = null;
let rafId = null;

function updateChart(newData) {
  pendingData = newData;

  if (!rafId) {
    rafId = requestAnimationFrame(() => {
      if (pendingData) {
        chart.changeData(pendingData);
        pendingData = null;
      }
      rafId = null;
    });
  }
}

// 模拟实时数据流(每 100ms 有新数据)
setInterval(() => {
  const newPoint = { time: Date.now(), value: Math.random() * 100 };
  updateChart([...currentData.slice(-500), newPoint]);  // 只保留最近 500 个点
}, 100);
```

## 常见错误与修正

```javascript
// ❌ 10 万行数据直接传给 G2,页面卡死
chart.options({ data: tenThousandRows });

// ✅ 先聚合/降采样到合理数量(< 1000 点)
chart.options({ data: aggregateTimeSeries(tenThousandRows, 'date', 'value') });
```
references/patterns/g2-pattern-responsive.md
---
id: "g2-pattern-responsive"
title: "G2 响应式图表适配"
description: |
  在不同屏幕尺寸和容器大小下自适应图表宽高、字体、边距等配置。
  涵盖 autoFit 配置、ResizeObserver 动态调整以及移动端适配常见问题。

library: "g2"
version: "5.x"
category: "patterns"
tags:
  - "响应式"
  - "responsive"
  - "自适应"
  - "autoFit"
  - "resize"
  - "移动端"
  - "容器尺寸"

related:
  - "g2-core-chart-init"

use_cases:
  - "图表随浏览器窗口/容器大小变化自动调整"
  - "移动端和桌面端共用同一图表组件"
  - "嵌入弹框/侧边栏等动态尺寸容器"

difficulty: "intermediate"
completeness: "full"
---

## G2 自适应宽度(autoFit)

```javascript
import { Chart } from '@antv/g2';

// 方案 1:autoFit: true(宽度自动适配容器,高度固定)
const chart = new Chart({
  container: 'container',
  autoFit: true,     // 宽度 = 容器宽度,高度使用默认值
  height: 400,       // 高度固定
});

chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value' },
});

chart.render();
```

## ResizeObserver 动态响应容器变化

```javascript
// 方案 2:监听容器尺寸变化,手动调整图表
const container = document.getElementById('container');
const chart = new Chart({
  container: 'container',
  width: container.clientWidth,
  height: container.clientHeight,
});

chart.options({ type: 'interval', data, encode: { x: 'month', y: 'value' } });
chart.render();

// 监听容器大小变化
const resizeObserver = new ResizeObserver((entries) => {
  for (const entry of entries) {
    const { width, height } = entry.contentRect;
    chart.changeSize(width, height);
  }
});
resizeObserver.observe(container);

// 页面卸载时清理
window.addEventListener('unload', () => {
  resizeObserver.disconnect();
  chart.destroy();
});
```

## 窗口 resize 事件(简单方案)

```javascript
// 方案 3:监听 window resize(防抖处理)
function debounce(fn, delay) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

const handleResize = debounce(() => {
  const container = document.getElementById('container');
  chart.changeSize(container.clientWidth, container.clientHeight);
}, 300);

window.addEventListener('resize', handleResize);
```

## 响应式图表的字体/边距适配

```javascript
// 根据容器宽度动态调整字体大小和边距
function getResponsiveConfig(containerWidth) {
  const isMobile = containerWidth < 480;
  const isTablet = containerWidth < 768;

  return {
    fontSize: isMobile ? 10 : isTablet ? 11 : 12,
    tickCount: isMobile ? 4 : isTablet ? 6 : 10,
    labelRotate: isMobile ? Math.PI / 4 : 0,   // 移动端倾斜标签
    marginBottom: isMobile ? 40 : 20,
  };
}

const containerWidth = document.getElementById('container').clientWidth;
const config = getResponsiveConfig(containerWidth);

chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value' },
  axis: {
    x: {
      labelFontSize: config.fontSize,
      labelTransform: config.labelRotate ? `rotate(${config.labelRotate}rad)` : undefined,
      tickCount: config.tickCount,
    },
    y: {
      labelFontSize: config.fontSize,
    },
  },
});
```

## React/Vue 组件中的响应式处理

```javascript
// React 示例(使用 useEffect 和 ref)
import { useEffect, useRef } from 'react';
import { Chart } from '@antv/g2';

function ResponsiveChart({ data }) {
  const containerRef = useRef(null);
  const chartRef = useRef(null);

  useEffect(() => {
    const container = containerRef.current;
    if (!container) return;

    const chart = new Chart({
      container,
      autoFit: true,
      height: 400,
    });

    chart.options({ type: 'line', data, encode: { x: 'date', y: 'value' } });
    chart.render();
    chartRef.current = chart;

    const ro = new ResizeObserver(() => {
      chartRef.current?.forceFit();
    });
    ro.observe(container);

    return () => {
      ro.disconnect();
      chartRef.current?.destroy();
    };
  }, []);

  useEffect(() => {
    chartRef.current?.changeData(data);
  }, [data]);

  return <div ref={containerRef} style={{ width: '100%', height: 400 }} />;
}
```

## 移动端常见适配

```javascript
const isMobile = window.matchMedia('(max-width: 768px)').matches;

chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value' },
  // 移动端:转为水平条形图(类别标签更易读)
  coordinate: isMobile ? [{ type: 'transpose' }] : undefined,
  // 移动端:减少刻度数量
  axis: {
    x: { tickCount: isMobile ? 4 : 8 },
    y: {
      labelFontSize: isMobile ? 10 : 12,
      title: isMobile ? null : '数值',   // 移动端隐藏轴标题节省空间
    },
  },
  // 移动端:隐藏图例(空间有限)
  legend: isMobile ? false : { position: 'top' },
});
```

## 常见错误与修正

### 错误 1:容器 display:none 时初始化图表

```javascript
// ❌ 问题:容器隐藏时 clientWidth = 0,图表尺寸为 0
const chart = new Chart({ container: 'hidden-tab', autoFit: true });

// ✅ 解决:等容器可见时再初始化,或显示后调用 changeSize
container.style.display = 'block';
chart.changeSize(container.clientWidth, container.clientHeight);
```

### 错误 2:多次触发 resize 不防抖导致性能问题

```javascript
// ❌ 每次 resize 都立即重绘(可能每秒触发 60 次)
window.addEventListener('resize', () => {
  chart.changeSize(window.innerWidth * 0.8, 400);
});

// ✅ 防抖处理
window.addEventListener('resize', debounce(() => {
  chart.changeSize(window.innerWidth * 0.8, 400);
}, 300));
```
references/patterns/g2-pattern-v4-to-v5.md
---
id: "g2-pattern-v4-to-v5"
title: "G2 v4 → v5 迁移指南"
description: |
  G2 v5 相对 v4 有重大 API 变更。
  本文梳理最常见的 v4 写法及对应的 v5 正确写法,
  帮助避免 LLM 生成旧版废弃 API 的最常见错误。

library: "g2"
version: "5.x"
category: "patterns"
tags:
  - "v4"
  - "v5"
  - "迁移"
  - "migration"
  - "废弃API"
  - "升级"

related:
  - "g2-core-chart-init"
  - "g2-core-encode-channel"

use_cases:
  - "将旧 G2 v4 代码迁移到 v5"
  - "识别和修正 LLM 生成的 v4 废弃 API"

difficulty: "intermediate"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-27"
author: "antv-team"
---

## 变更 1:导入方式

```javascript
// ❌ G2 v4 写法
import G2 from '@antv/g2';
const chart = new G2.Chart({ container: 'container' });

// ✅ G2 v5 写法
import { Chart } from '@antv/g2';
const chart = new Chart({ container: 'container' });
```

## 变更 2:数据绑定

```javascript
// ❌ G2 v4:单独调用 .data()
chart.data(data);

// ✅ G2 v5(方式一):在 mark 上传入
chart.options({ type: 'interval',  data, encode: {...} });

// ✅ G2 v5(方式二):options 的 data 字段
chart.options({
  type: 'interval',
   data,    // data 作为 options 的一个字段
  encode: { x: 'month', y: 'value' },
});
```

## 变更 3:字段编码 API

```javascript
// ❌ G2 v4:链式 .encode() 方法
chart.interval()
  .encode('x', 'month')
  .encode('y', 'value')
  .encode('color', 'product');

// ✅ G2 v5:encode 作为对象字段
chart.options({
  type: 'interval',
  encode: {
    x: 'month',
    y: 'value',
    color: 'product',
  },
});
```

## 变更 4:数据变换(transform)

```javascript
// ❌ G2 v4:方法名和参数不同
chart.interval().adjust('stack');     // 堆叠
chart.interval().adjust('dodge');     // 分组

// ✅ G2 v5:使用 transform 数组
chart.options({
  type: 'interval',
  transform: [{ type: 'stackY' }],   // 堆叠
  // transform: [{ type: 'dodgeX' }],  // 分组
});
```

## 变更 5:坐标系配置

```javascript
// ❌ G2 v4
chart.coordinate('polar');
chart.coordinate('theta');
chart.coordinate().transpose();

// ✅ G2 v5:通过 coordinate 字段对象配置
chart.options({
  coordinate: { type: 'polar' },
  // coordinate: { type: 'theta' },
  // coordinate: { transform: [{ type: 'transpose' }] },
});
```

## 变更 6:辅助标注(guide → annotation)

```javascript
// ❌ G2 v4:guide API
chart.guide().line({ start: ['min', 50], end: ['max', 50] });
chart.guide().text({ position: ['median', 'median'], content: '中位线' });

// ✅ G2 v5:使用 lineY/lineX mark(在 view + children 中)
chart.options({
  type: 'view',
  data,
  children: [
    { type: 'line', encode: { x: 'month', y: 'value' } },
    {
      type: 'lineY',
       [{ threshold: 50 }],
      encode: { y: 'threshold' },
      style: { stroke: 'red', lineDash: [4, 4] },
      labels: [{ text: '目标线', position: 'right' }],
    },
  ],
});
```

## 变更 7:标签配置

```javascript
// ❌ G2 v4:.label() 方法
chart.interval().label('value');
chart.interval().label({ fields: ['value'], formatter: (v) => `${v}万` });

// ✅ G2 v5:labels 数组(注意是复数)
chart.options({
  type: 'interval',
  labels: [
    {
      // 推荐:直接用 text 函数访问完整 datum
      text: (d) => `${d.value}万`,
      // 或:text: 'value'  配合 formatter 格式化
      // formatter: (val) => `${val}万`,  // val 是 text 映射的值,不是 datum
    },
  ],
});
```

## 变更 8:Tooltip 配置

```javascript
// ❌ G2 v4
chart.tooltip({
  showTitle: false,
  itemTpl: '<li>{name}: {value}</li>',
});

// ✅ G2 v5
chart.options({
  tooltip: {
    title: (d) => d.month,
    items: [
      { field: 'value', name: '数值', valueFormatter: (v) => `${v}万` },
    ],
  },
  interaction: [{ type: 'tooltip' }],
});
```

## 变更 9:多 Mark 叠加

```javascript
// ❌ G2 v4:多次调用 chart 方法直接叠加
chart.line().encode('x', 'month').encode('y', 'value');
chart.point().encode('x', 'month').encode('y', 'value');

// ✅ G2 v5:使用 type: 'view' + children
chart.options({
  type: 'view',
  data,
  encode: { x: 'month', y: 'value' },
  children: [
    { type: 'line' },
    { type: 'point' },
  ],
});
```

## 变更 10:动画配置

```javascript
// ❌ G2 v4
chart.animate(true);

// ✅ G2 v5
chart.options({
  animate: {
    enter: { type: 'fadeIn', duration: 300 },
    update: { type: 'morphing', duration: 500 },
  },
});
```

## v4 错误特征快速检查

在审查 LLM 生成的代码时,检查以下常见 v4 错误特征:

- [ ] `import G2 from '@antv/g2'`(应为命名导入)
- [ ] `chart.source(data)` 或 `chart.data(data)`(应在 options 中)
- [ ] `.position('x*y')`(v4 写法)
- [ ] `.adjust('stack')` / `.adjust('dodge')`(应为 transform 数组)
- [ ] `chart.guide().line()`(应为 lineY/lineX mark)
- [ ] `.label()` 方法(应为 `labels` 数组)
- [ ] 多次 `chart.options()` 调用(应用 view + children)
references/scales/g2-scale-band.md
---
id: "g2-scale-band"
title: "G2 Band 分类比例尺"
description: |
  Band Scale 是 G2 中用于分类 x 轴(柱状图等)的比例尺,
  将离散分类值映射到等宽区间(band),支持配置内外间距。
  当 encode.x 映射字符串/分类字段时自动使用。

library: "g2"
version: "5.x"
category: "scales"
tags:
  - "band"
  - "分类比例尺"
  - "柱状图"
  - "padding"
  - "scale"
  - "ordinal"
  - "spec"

related:
  - "g2-mark-interval-basic"
  - "g2-mark-interval-grouped"
  - "g2-comp-axis-config"

use_cases:
  - "配置柱状图的柱体宽度和间距"
  - "指定分类轴的显示顺序"
  - "控制分类数据的对齐方式"

difficulty: "intermediate"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/scale/band"
---

## 自动识别

当 encode.x 映射字符串类型字段时,G2 自动使用 Band Scale,通常无需显式配置:

```javascript
chart.options({
  type: 'interval',
  data: [
    { genre: 'Sports',   sold: 275 },
    { genre: 'Strategy', sold: 115 },
  ],
  encode: { x: 'genre', y: 'sold' },   // 'genre' 是字符串,自动使用 Band Scale
});
```

## 配置柱体宽度(padding)

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'genre', y: 'sold' },
  scale: {
    x: {
      type: 'band',
      padding: 0.3,         // 柱体内间距(0-1),默认 0.1
      // paddingInner: 0.3, // 同 padding
      // paddingOuter: 0.2, // 两端外间距
    },
  },
});
```

## 自定义分类顺序

```javascript
// 指定分类显示顺序(不按数据顺序)
chart.options({
  type: 'interval',
  data,
  encode: { x: 'genre', y: 'sold' },
  scale: {
    x: {
      type: 'band',
      domain: ['Action', 'Shooter', 'Sports', 'Strategy', 'Other'],  // 显式指定顺序
    },
  },
});
```

## 热力图(cell mark)

`cell` mark 同样依赖 bandwidth,离散的 x/y 轴应使用 `band`(或省略让 G2 自动推断)。**不要用 `point` 比例尺**——point 的 bandwidth=0,格子会不可见。

```javascript
chart.options({
  type: 'cell',
  data: heatmapData,
  encode: { x: 'date', y: 'month', color: 'value' },
  // ✅ 省略 x/y scale,G2 自动为 cell 使用 band
  scale: {
    color: { type: 'sequential', palette: 'blues' },
  },
});

// ✅ 也可以显式写 band
scale: {
  x: { type: 'band' },
  y: { type: 'band' },
  color: { type: 'sequential', palette: 'blues' },
}

// ❌ 不要用 point:bandwidth=0,格子消失
scale: {
  x: { type: 'point' },  // ❌
  y: { type: 'point' },  // ❌
}
```

## 常见错误与修正

### 错误:padding 超出 [0, 1] 范围
```javascript
// ❌ 错误:padding > 1,柱体宽度变为负值
chart.options({ scale: { x: { padding: 1.5 } } });

// ✅ 正确:padding 在 0-1 之间,0 = 无间距,0.5 = 柱宽与间距各占一半
chart.options({ scale: { x: { padding: 0.3 } } });
```
references/scales/g2-scale-linear.md
---
id: "g2-scale-linear"
title: "G2 线性比例尺(linear scale)"
description: |
  G2 v5 线性比例尺用于连续数值字段的映射,通过 scale.y 或 scale.color 配置,
  支持自定义 domain(数据范围)和 range(视觉范围),
  nice/clamp/tickCount 控制轴刻度显示。
library: "g2"
version: "5.x"
category: "scales"
tags:
  - "线性比例尺"
  - "linear"
  - "连续"
  - "数值"
  - "domain"
  - "range"
  - "spec"

related:
  - "g2-core-chart-init"
  - "g2-mark-line-basic"
  - "g2-comp-annotation"

use_cases:
  - "控制 Y 轴的显示范围(不从 0 开始)"
  - "设置颜色映射为连续色板"
  - "clamp 截断超出范围的数据"

difficulty: "intermediate"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/scale"
---

## 基本用法(自定义 Y 轴 domain)

折线图默认 y 轴从 0 开始。使用 `scale.y.domain` 指定精确范围,让折线细节更清晰:

> **注意**:`linear` 是数值字段的默认 scale 类型,**不需要手动指定 `type: 'linear'`**。

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'line',
  data: [
    { month: 'Jan', value: 4200 },
    { month: 'Feb', value: 4500 },
    { month: 'Mar', value: 4100 },
    { month: 'Apr', value: 4800 },
    { month: 'May', value: 5200 },
    { month: 'Jun', value: 4900 },
  ],
  encode: { x: 'month', y: 'value' },
  scale: {
    y: {
      domain: [3800, 5500],   // 显式指定 y 轴范围,不从 0 开始
      nice: true,             // 自动扩展到"好看"的整数刻度
    },
  },
});

chart.render();
```

## 对数比例尺(log scale)

当数据跨越多个数量级时,使用 `type: 'log'` 将 y 轴压缩为对数尺度:

```javascript
chart.options({
  type: 'line',
  data: [
    { year: '2018', revenue: 1200 },
    { year: '2019', revenue: 8500 },
    { year: '2020', revenue: 32000 },
    { year: '2021', revenue: 210000 },
    { year: '2022', revenue: 1500000 },
  ],
  encode: { x: 'year', y: 'revenue' },
  scale: {
    y: {
      type: 'log',      // 对数比例尺,适合跨量级数据
      base: 10,         // 对数底数,默认 10
      nice: true,
    },
  },
});
```

> 注意:log scale 不能包含 0 或负数,否则会导致渲染异常。

## 颜色映射:连续色板(sequential color scale)

将数值字段映射为连续颜色,适合热力图或气泡图着色:

```javascript
chart.options({
  type: 'point',
  data: [
    { x: 10, y: 20, density: 0.1 },
    { x: 30, y: 50, density: 0.5 },
    { x: 60, y: 80, density: 0.9 },
    { x: 45, y: 35, density: 0.3 },
    { x: 75, y: 60, density: 0.7 },
  ],
  encode: { x: 'x', y: 'y', color: 'density', size: 12 },
  scale: {
    color: {
      type: 'linear',
      domain: [0, 1],                           // 数据范围
      range: ['#d0e8ff', '#0050b3'],            // 从浅蓝到深蓝
    },
  },
});
```

## 配置参考

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `type` | `'linear'` \| `'log'` \| `'pow'` \| `'sqrt'` | `'linear'` | 比例尺类型 |
| `domain` | `[number, number]` | 数据的 min/max | 数据映射范围(输入域) |
| `range` | `[number, number]` \| `string[]` | 取决于通道 | 视觉映射范围(输出域) |
| `nice` | `boolean` | `false` | 自动将 domain 扩展到整数刻度 |
| `clamp` | `boolean` | `false` | 超出 domain 的值截断到边界 |
| `tickCount` | `number` | 自动 | 期望的刻度数量(近似值) |
| `tickInterval` | `number` | 自动 | 相邻刻度的固定间隔 |
| `tickMethod` | `function` | 内置方法 | 自定义刻度生成方法 |
| `base` | `number` | `10` | 仅 `type: 'log'` 时有效,对数底数 |
| `exponent` | `number` | `2` | 仅 `type: 'pow'` 时有效,指数 |
| `zero` | `boolean` | `true` | 是否强制 domain 包含 0 |

```javascript
// 完整配置示例
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value' },
  scale: {
    y: {
      // type: 'linear',  // 可省略,数值字段默认就是 linear
      domain: [0, 1000],
      nice: true,
      clamp: true,
      tickCount: 5,
      // tickInterval: 200,  // 与 tickCount 二选一
      zero: false,           // 不强制从 0 开始
    },
  },
});
```

## tickMethod 自定义刻度

`tickMethod` 用于自定义刻度生成,签名是 `(min, max, count) => number[]`:

```javascript
scale: {
  y: {
    tickCount: 5,
    tickMethod: (min, max, count) => {
      // 参数说明:
      // min - 数据最小值
      // max - 数据最大值
      // count - 推荐的刻度数量

      // 自定义刻度生成逻辑
      const step = (max - min) / (count - 1);
      const ticks = [];
      for (let i = 0; i < count; i++) {
        ticks.push(min + i * step);
      }
      return ticks;  // 返回数值数组
    },
  },
}
```

**注意**:如果只需要格式化刻度标签文本,使用 `axis.labelFormatter`:

```javascript
axis: {
  y: {
    labelFormatter: (v) => `${v}万`,  // 格式化标签
  },
}
```

## 常见错误与修正

### 错误:忘记设置 `nice: true` 导致刻度不整齐

```javascript
// ❌ 刻度可能出现 3827、4183 等非整数
chart.options({
  scale: { y: { domain: [3827, 5243] } },
});

// ✅ nice: true 自动扩展为 3800、5400 等整数刻度
chart.options({
  scale: { y: { domain: [3827, 5243], nice: true } },
});
```

### 错误:domain 最小值大于最大值(反转轴)

```javascript
// ❌ domain 反转会导致轴方向翻转(通常不是预期效果)
chart.options({
  scale: { y: { domain: [1000, 0] } },
});

// ✅ 正确:最小值在前,最大值在后
chart.options({
  scale: { y: { domain: [0, 1000] } },
});
```

### 错误:对 0 或负值使用 log scale

```javascript
// ❌ log(0) = -Infinity,图表会出现渲染异常或空白
chart.options({
  data: [{ x: 'A', y: 0 }, { x: 'B', y: 100 }],
  scale: { y: { type: 'log' } },
});

// ✅ 确保所有 y 值 > 0,或对数据做预处理过滤
chart.options({
  data: data.filter(d => d.y > 0),
  scale: { y: { type: 'log', domain: [1, 1000000] } },
});
```

### 错误:tickCount 和 tickInterval 同时设置

```javascript
// ❌ 两者同时设置时 tickInterval 优先,tickCount 被忽略
chart.options({
  scale: { y: { tickCount: 5, tickInterval: 200 } },
});

// ✅ 根据需求二选一
chart.options({
  scale: { y: { tickCount: 5 } },      // 约 5 个刻度
  // 或
  // scale: { y: { tickInterval: 200 } },  // 每隔 200 一个刻度
});
```
references/scales/g2-scale-log.md
---
id: "g2-scale-log"
title: "G2 对数比例尺(log)"
description: |
  对数比例尺将数值映射为对数刻度,适合数据跨越多个数量级(如 1 到 1,000,000)的场景。
  使用 base 参数设置对数底数(默认 10),可以有效展示指数增长或数量级差异悬殊的数据。

library: "g2"
version: "5.x"
category: "scales"
tags:
  - "log"
  - "对数"
  - "比例尺"
  - "数量级"
  - "scale"
  - "指数增长"

related:
  - "g2-scale-linear"
  - "g2-scale-pow"

use_cases:
  - "展示数量级差异悬殊的数据(如 GDP 对比:100 万到 1 万亿)"
  - "病毒传播等指数增长数据"
  - "频率分布的幂律特征"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/scale/log"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

// 数量级差异悬殊的数据
const data = [
  { country: '卢森堡',     gdp: 135000 },
  { country: '美国',       gdp: 65000 },
  { country: '中国',       gdp: 12000 },
  { country: '巴西',       gdp: 7500 },
  { country: '印度',       gdp: 2100 },
  { country: '埃塞俄比亚', gdp: 900 },
];

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'interval',
  data,
  encode: { x: 'country', y: 'gdp', color: 'country' },
  scale: {
    y: {
      type: 'log',    // 对数比例尺
      base: 10,       // 底数,默认 10
      domain: [100, 200000],
    },
  },
  axis: {
    y: {
      title: '人均 GDP(美元,对数轴)',
      tickCount: 5,
    },
  },
});

chart.render();
```

## 配置项

```javascript
scale: {
  y: {
    type: 'log',
    base: 10,          // 对数底数,常用 2 或 10,默认 10
    domain: [1, 1e6],  // 数值范围(注意:不能包含 0 或负数!)
    nice: true,        // 将刻度扩展到整数幂次
    tickCount: 5,      // 推荐的刻度数量
    tickMethod: (min, max, count, base) => {
      // 自定义刻度生成方法
      // 返回刻度值数组
      return [1, 10, 100, 1000, 10000];
    },
  },
}
```

## 刻度控制:tickMethod vs labelFormatter vs tickFormatter

三者职责完全不同,不能混用:

| 配置项 | 位置 | 签名 | 职责 |
|--------|------|------|------|
| `tickMethod` | `scale.y` 或 `axis.y` | `(min, max, count, base?) => number[]` | 决定**哪些数值**显示刻度 |
| `labelFormatter` | `axis.y` | `(value, index, array) => string` | 决定刻度的**显示文字** ⭐ 最常用 |
| `tickFormatter` | `axis.y` | `(datum, index, array, vector) => DisplayObject` | 自定义刻度线的**图形对象**(极少用) |

### 只格式化刻度标签文字(最常见)

```javascript
// ✅ 只需改显示文字 → 用 axis.labelFormatter,不需要 tickMethod
chart.options({
  scale: { y: { type: 'log', base: 10 } },
  axis: {
    y: {
      labelFormatter: (v) => v >= 1e6 ? `${v/1e6}M` : v >= 1e3 ? `${v/1e3}K` : String(v),
    },
  },
});
```

### 同时自定义刻度位置 + 标签文字

```javascript
// ✅ tickMethod 控制"打哪些刻度",labelFormatter 控制"显示什么文字"
chart.options({
  scale: {
    y: {
      type: 'log',
      base: 10,
      domain: [0.1, 1000],
      // 签名:(min, max, count, base) => number[],必须返回数值数组
      tickMethod: (min, max, count, base) => [0.1, 1, 10, 100, 1000],
    },
  },
  axis: {
    y: {
      labelFormatter: (v) => `10^${Math.log10(v)}`,
    },
  },
});
```

## 折线图的对数轴(指数增长数据)

```javascript
chart.options({
  type: 'line',
  data: covidData,
  encode: { x: 'date', y: 'cases', color: 'country' },
  scale: {
    y: { type: 'log', base: 10, nice: true },
  },
  axis: {
    y: {
      title: '累计病例数(对数轴)',
      labelFormatter: (v) => v >= 1e6 ? `${v / 1e6}M` : v >= 1e3 ? `${v / 1e3}K` : String(v),
    },
  },
});
```

## 常见错误与修正

### 错误 1:tickMethod 签名错误,且混淆了刻度位置与标签格式化

`tickMethod` 有两处可配置,**签名不同,职责不同**:

| 位置 | 签名 | 职责 |
|------|------|------|
| `scale.y.tickMethod` | `(min, max, n?, base?) => number[]` | 控制刻度的**数值位置** |
| `axis.y.tickMethod` | `(start, end, tickCount) => number[]` | 同上,也是返回数值数组 |
| `axis.y.labelFormatter` | `(value) => string` | 控制刻度的**显示文字** |

```javascript
// ❌ 三重错误:
// 1. 参数写成了 scale 对象(实际是 min/max/count/base 四个数值)
// 2. 调用了不存在的 scale.ticks() 方法
// 3. 返回了 {value, text} 对象数组(应返回 number[])
scale: {
  y: {
    type: 'log',
    tickMethod: (scale) => {
      const ticks = scale.ticks();
      return ticks.map(tick => ({ value: tick, text: `log10(${tick}) + 1` }));
    },
  },
}

// ✅ 正确拆分:tickMethod 控制位置,labelFormatter 控制文字
scale: {
  y: {
    type: 'log',
    base: 10,
    domain: [0.1, 1000],
    tickMethod: (min, max, count, base) => [0.1, 1, 10, 100, 1000],  // ✅ 返回 number[]
  },
},
axis: {
  y: {
    labelFormatter: (v) => `${Math.log10(v) + 1}`,  // ✅ 格式化文字
  },
}
```

### 错误 2:数据包含 0 或负数——对数比例尺无法处理
```javascript
// ❌ 对数 log(0) = -∞,数据中有 0 会导致渲染异常
const data = [{ x: 'A', y: 0 }, { x: 'B', y: 100 }];
chart.options({
  scale: { y: { type: 'log' } },  // ❌ y=0 无法在对数轴上显示
});

// ✅ 对数轴要求所有值 > 0,可以过滤掉 0 或加微小偏移
const data = [{ x: 'B', y: 100 }];  // ✅ 过滤掉 0
// 或使用 domain 强制起点 > 0
chart.options({
  scale: { y: { type: 'log', domain: [0.1, 1000] } },
});
```

### 错误 3:对线性数据使用对数轴——视觉失真
```javascript
// ❌ 数据范围是 50~200,没有数量级差异,对数轴没有意义且会误导读者
const data = [/* 50~200 之间的均匀分布 */];
chart.options({ scale: { y: { type: 'log' } } });  // ❌ 不必要

// ✅ 线性数据用默认线性比例尺
chart.options({ scale: { y: { type: 'linear' } } });  // ✅ 或直接省略(默认)
```
references/scales/g2-scale-ordinal.md
---
id: "g2-scale-ordinal"
title: "G2 序数比例尺(ordinal)"
description: |
  序数比例尺将离散的分类值映射到离散的输出值(如颜色)。
  主要用于 color 通道,将字符串类别映射到颜色数组。
  通过 range 指定自定义颜色列表,或通过 palette 使用内置调色板。

library: "g2"
version: "5.x"
category: "scales"
tags:
  - "ordinal"
  - "序数"
  - "比例尺"
  - "颜色"
  - "分类色"
  - "scale"
  - "palette"

related:
  - "g2-scale-linear"
  - "g2-theme-builtin"

use_cases:
  - "自定义分类颜色映射"
  - "指定特定类别对应特定颜色"
  - "使用内置或自定义调色板"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/scale/ordinal"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'interval',
  data: [
    { genre: '运动', sold: 275 },
    { genre: '策略', sold: 115 },
    { genre: '动作', sold: 120 },
    { genre: 'RPG',  sold: 98 },
  ],
  encode: { x: 'genre', y: 'sold', color: 'genre' },
  scale: {
    color: {
      type: 'ordinal',
      // 自定义颜色列表(顺序对应 domain 中的分类)
      range: ['#F4664A', '#FAAD14', '#5B8FF9', '#30BF78'],
    },
  },
});

chart.render();
```

## 指定类别到颜色的映射(domain + range)

```javascript
chart.options({
  scale: {
    color: {
      type: 'ordinal',
      domain: ['通过', '失败', '跳过'],   // 指定分类顺序
      range: ['#52c41a', '#ff4d4f', '#faad14'],  // 对应颜色
    },
  },
});
```

## 使用内置调色板

```javascript
// G2 内置调色板名称:'tableau10', 'category10', 'set2', 'pastel', 'blues', etc.
chart.options({
  scale: {
    color: {
      type: 'ordinal',
      palette: 'tableau10',   // 使用 Tableau 10 色调色板
    },
  },
});
```

## 常见错误与修正

### 错误 1:range 颜色数量少于分类数量——后面的类别颜色循环重用
```javascript
// ⚠️  5 个分类只有 3 个颜色,第 4/5 个类别颜色与前两个相同
chart.options({
  scale: {
    color: {
      type: 'ordinal',
      domain: ['A', 'B', 'C', 'D', 'E'],
      range: ['red', 'blue', 'green'],  // ⚠️  只有 3 个颜色,D/E 会循环
    },
  },
});

// ✅ range 颜色数量应 ≥ 分类数量
chart.options({
  scale: {
    color: {
      type: 'ordinal',
      range: ['#F4664A', '#FAAD14', '#5B8FF9', '#30BF78', '#9254DE'],  // ✅ 5 个
    },
  },
});
```

### 错误 2:连续数值通道误用 ordinal——应用 linear 或 sequential
```javascript
// ❌ 对数值 y 轴使用 ordinal(Y 轴会变成离散)
chart.options({
  scale: {
    y: { type: 'ordinal' },  // ❌ y 是数值,应用 linear
  },
});

// ✅ 数值比例尺用 linear
chart.options({
  scale: {
    y: { type: 'linear' },  // ✅
  },
});
```
references/scales/g2-scale-point.md
---
id: "g2-scale-point"
title: "G2 Point Scale"
description: |
  点比例尺,将离散的类别映射到均匀分布的点上。
  与 Band Scale 类似,但带宽固定为 0,常用于散点图的位置映射。

library: "g2"
version: "5.x"
category: "scales"
tags:
  - "比例尺"
  - "scale"
  - "point"
  - "离散"
  - "位置"

related:
  - "g2-scale-band"
  - "g2-scale-ordinal"
  - "g2-mark-point-scatter"

use_cases:
  - "散点图的 X/Y 轴位置映射"
  - "分类数据的位置映射"
  - "需要均匀分布的离散数据"

anti_patterns:
  - "连续数值数据应使用 Linear Scale"
  - "需要带宽的场景应使用 Band Scale"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/scale"
---

## 核心概念

Point Scale 是一种离散比例尺:
- 将类别映射到均匀分布的点位置
- 带宽(bandwidth)固定为 0
- 每个类别对应一个精确的位置点

**与 Band Scale 的区别:**
- Band Scale:每个类别占据一段区间(有带宽)
- Point Scale:每个类别对应一个精确点(无带宽)

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'point',
  data: [
    { category: 'A', value: 10 },
    { category: 'B', value: 20 },
    { category: 'C', value: 15 },
  ],
  encode: {
    x: 'category',
    y: 'value',
  },
  scale: {
    x: { type: 'point' },
  },
});

chart.render();
```

## 常用变体

### 设置内边距

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'category', y: 'value' },
  scale: {
    x: {
      type: 'point',
      padding: 0.5,  // 两端的内边距,范围 [0, 1]
    },
  },
});
```

### 设置对齐方式

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'category', y: 'value' },
  scale: {
    x: {
      type: 'point',
      align: 0.5,  // 0: 左对齐, 0.5: 居中, 1: 右对齐
    },
  },
});
```

### 指定 domain

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'category', y: 'value' },
  scale: {
    x: {
      type: 'point',
      domain: ['A', 'B', 'C', 'D'],  // 显式指定类别顺序
    },
  },
});
```

### 指定 range

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'category', y: 'value' },
  scale: {
    x: {
      type: 'point',
      range: [0.1, 0.9],  // 映射范围,默认 [0, 1]
    },
  },
});
```

## 完整类型参考

```typescript
interface PointScaleOption {
  type: 'point';
  domain?: string[] | number[];  // 类别域
  range?: [number, number];      // 输出范围,默认 [0, 1]
  padding?: number;              // 内边距,默认 0
  align?: number;                // 对齐方式,默认 0.5
  round?: boolean;               // 是否四舍五入,默认 false
}
```

## 与 Band Scale 的对比

| 特性 | Point Scale | Band Scale |
|------|-------------|------------|
| 带宽 | 0 | 有带宽 |
| 输出 | 精确点位置 | 区间起点 |
| 适用 | 散点图、点图 | 柱状图、条形图 |
| padding | 单一值 | paddingInner + paddingOuter |

## 自动推断

G2 会根据 mark 类型自动推断比例尺:
- `interval` mark 的分类轴 → Band Scale
- `point` mark 的分类轴 → Point Scale
- `line` mark 的分类轴 → Band Scale

```javascript
// 自动推断为 Point Scale
chart.options({
  type: 'point',
  data,
  encode: { x: 'category', y: 'value' },
  // scale: { x: { type: 'point' } }  // 可省略
});
```

## 常见错误与修正

### 错误 1:用于需要带宽的 mark(柱状图、热力图)

`point` 比例尺 bandwidth = 0,`interval`(柱状图)和 `cell`(热力图)mark 依赖 bandwidth 来渲染有面积的图形。对这类 mark 使用 `point` 比例尺会导致柱体/格子宽度为 0,图形不可见。

```javascript
// ❌ 错误:柱状图用 point,柱体宽度为 0
chart.options({
  type: 'interval',
  encode: { x: 'category', y: 'value' },
  scale: { x: { type: 'point' } },  // ❌ bandwidth=0,柱子消失
});

// ❌ 错误:热力图用 point,格子宽度为 0(常见误用:"确保均匀分布")
chart.options({
  type: 'cell',
  encode: { x: 'date', y: 'month', color: 'value' },
  scale: {
    x: { type: 'point' },  // ❌ cell 需要 bandwidth
    y: { type: 'point' },  // ❌
  },
});

// ✅ 正确:interval 和 cell 用 band(或省略,G2 自动推断)
chart.options({
  type: 'cell',
  encode: { x: 'date', y: 'month', color: 'value' },
  scale: {
    x: { type: 'band' },   // ✅ 有带宽,格子可见
    y: { type: 'band' },   // ✅
  },
  // 或直接省略 scale,cell mark 默认就是 band
});
```

**适用 `point` 的 mark**:`point`(散点图)、`line`(折线图,用于分类 x 轴)。

### 错误 2:padding 值过大

```javascript
// ❌ 错误:padding 超过范围
scale: { x: { type: 'point', padding: 1.5 } }

// ✅ 正确:padding 在 [0, 1] 范围内
scale: { x: { type: 'point', padding: 0.5 } }
```

### 错误 3:align 值错误

```javascript
// ❌ 错误:align 超过范围
scale: { x: { type: 'point', align: 2 } }

// ✅ 正确:align 在 [0, 1] 范围内
scale: { x: { type: 'point', align: 0.5 } }
```
references/scales/g2-scale-pow-sqrt.md
---
id: "g2-scale-pow-sqrt"
title: "G2 幂次比例尺(pow)和平方根比例尺(sqrt)"
description: |
  pow 比例尺将数值按幂函数(y = x^exponent)映射,exponent=0.5 时等同于 sqrt 比例尺。
  sqrt 是 pow 的特例(exponent=0.5),将数值映射为平方根,
  常用于面积编码(如气泡大小)确保视觉面积与数值成线性比例。

library: "g2"
version: "5.x"
category: "scales"
tags:
  - "pow"
  - "sqrt"
  - "幂次"
  - "平方根"
  - "比例尺"
  - "scale"
  - "气泡图"

related:
  - "g2-scale-log"
  - "g2-scale-linear"
  - "g2-mark-point-bubble"

use_cases:
  - "气泡图 size 通道用 sqrt 比例尺(确保面积线性)"
  - "数据轻微偏斜时用 pow 拉伸/压缩数值范围"
  - "视觉编码中面积与数值的线性映射"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/scale/pow"
---

## 最小可运行示例(气泡图 sqrt 比例尺)

```javascript
import { Chart } from '@antv/g2';

const data = [
  { country: '中国', gdp: 17.7, population: 141 },
  { country: '美国', gdp: 25.5, population: 33 },
  { country: '印度', gdp: 3.4,  population: 142 },
  { country: '日本', gdp: 4.2,  population: 13 },
  { country: '巴西', gdp: 1.8,  population: 22 },
];

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'point',
  data,
  encode: {
    x: 'gdp',
    y: 'country',
    size: 'population',
    color: 'country',
  },
  scale: {
    size: {
      type: 'sqrt',        // 平方根比例尺:面积与 population 成线性比例
      range: [8, 60],      // 半径范围
    },
  },
  style: { fillOpacity: 0.7 },
});

chart.render();
```

## pow 比例尺(自定义指数)

```javascript
// exponent = 2:数值越大差异越被放大(适合展示小差异)
scale: {
  y: {
    type: 'pow',
    exponent: 2,    // y = x^2,放大大值之间的差异
  },
}

// exponent = 0.5:等同于 sqrt(压缩大值)
scale: {
  y: {
    type: 'pow',
    exponent: 0.5,  // 等同于 type: 'sqrt'
  },
}
```

## 为什么气泡大小要用 sqrt

```javascript
// ❌ 错误:用 linear 比例尺映射半径
// 半径 r 与数值成线性,则面积 = πr²,面积与数值呈平方关系
// 人口 100 和 400,视觉面积比是 1:16,误导读者
scale: { size: { type: 'linear', range: [8, 60] } }  // ❌

// ✅ 正确:用 sqrt 比例尺映射半径
// 半径 r = sqrt(数值),面积 = πr² = π×数值,面积与数值成线性
// 人口 100 和 400,视觉面积比是 1:4,符合实际比例
scale: { size: { type: 'sqrt', range: [8, 60] } }  // ✅
```

## 常见错误与修正

### 错误:数据包含 0 或负数且 exponent < 1——sqrt(0) = 0 正常,但负数会得到 NaN
```javascript
// ❌ sqrt(-1) = NaN,数据中有负数时会报错
chart.options({
  scale: { y: { type: 'sqrt' } },
   [{ y: -10 }],  // ❌ 负数
});

// ✅ sqrt 比例尺只适用于非负数
// 如果有负数,先用 Math.abs 处理,或改用 linear
chart.options({
  scale: { y: { type: 'sqrt', domain: [0, 200] } },  // ✅ 确保 domain 非负
});
```
references/scales/g2-scale-quantile-quantize.md
---
id: "g2-scale-quantile-quantize"
title: "G2 分位数比例尺(quantile)与分段比例尺(quantize)"
description: |
  quantile:按数据实际分布的分位数分组,每组数量相等(等频分组)。
  quantize:按数值范围等分,每段区间宽度相等(等距分组)。
  两者都将连续数值映射到离散输出(如颜色),常用于地图分级着色。
  与 threshold 的区别是:threshold 手动指定断点,quantile/quantize 自动计算。

library: "g2"
version: "5.x"
category: "scales"
tags:
  - "quantile"
  - "quantize"
  - "分位数"
  - "等频"
  - "等距"
  - "比例尺"
  - "scale"
  - "分级着色"

related:
  - "g2-scale-threshold"
  - "g2-scale-ordinal"
  - "g2-mark-cell-heatmap"

use_cases:
  - "地图分级着色:按数据分布自动分组(quantile)"
  - "等距分级着色(quantize)"
  - "热力图的颜色分级"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/scale/quantile"
---

## quantile vs quantize vs threshold 对比

| 比例尺 | 分组方式 | 适合场景 |
|--------|---------|---------|
| `threshold` | 手动指定断点 | 有业务含义的固定分级(如 60分=及格) |
| `quantize` | 数值范围等距分段 | 均匀分布数据的等距分级 |
| `quantile` | 数据实际分位数分组 | 偏斜分布数据的等频分级(每组数量相等) |

## quantile 比例尺

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 300 });

chart.options({
  type: 'cell',
  data,
  encode: { x: 'week', y: 'day', color: 'count' },
  scale: {
    color: {
      type: 'quantile',
      // 自动按数据分位数分组,每组记录数量相等
      range: ['#ebedf0', '#c6e48b', '#7bc96f', '#196127'],
      // domain 不需要指定,自动从数据中计算
    },
  },
  style: { lineWidth: 2, stroke: '#fff' },
});
```

## quantize 比例尺

```javascript
chart.options({
  type: 'cell',
  data,
  encode: { x: 'hour', y: 'day', color: 'value' },
  scale: {
    color: {
      type: 'quantize',
      domain: [0, 100],  // 明确指定数值范围(会被等距分为 N 段)
      range: ['#fee0d2', '#fc9272', '#de2d26'],  // 3 种颜色 = 3 段
    },
  },
});
```

## 常见错误与修正

### 错误:quantile 数据极度偏斜时视觉效果差——用 threshold 手动设置
```javascript
// ⚠️  数据高度偏斜(如 95% 数据集中在低值),quantile 分组合理但视觉上
// 大部分区域颜色相近,少数高值区域颜色鲜艳,不直观
chart.options({ scale: { color: { type: 'quantile' } } });  // ⚠️  偏斜数据效果差

// ✅ 偏斜数据改用 log 比例尺配合 sequential,或用 threshold 手动设置关键节点
chart.options({
  scale: {
    color: {
      type: 'threshold',
      domain: [10, 100, 1000],  // 按对数级设置断点
      range: ['#eee', '#fee', '#f66', '#c00'],
    },
  },
});
```

### 错误:quantize 不指定 domain——自动从数据中推断,可能有边界问题
```javascript
// ⚠️  不指定 domain 时,quantize 从数据 min/max 推断,
// 新数据超出范围时会超出色阶
chart.options({ scale: { color: { type: 'quantize' } } });  // ⚠️  依赖数据范围

// ✅ 明确指定业务含义的 domain
chart.options({
  scale: { color: { type: 'quantize', domain: [0, 100] } },  // ✅ 明确 0~100
});
```
references/scales/g2-scale-sequential.md
---
id: "g2-scale-sequential"
title: "G2 顺序比例尺(sequential)"
description: |
  sequential 比例尺将连续数值映射到颜色渐变,
  专为颜色通道设计,常配合 palette(内置色板)或自定义颜色插值函数使用。
  适合热力图、地图着色、连续数值颜色编码场景。
  与 linear 的区别:sequential 专为颜色输出优化,linear 支持任意数值输出。
  ⚠️ 约束:仅当 encode.color 映射的字段为连续类型(数值型)时使用。
  分类字段(字符串/枚举)和间断字段(ordinal/band)禁止使用 sequential,
  否则会产生错误的颜色渐变,应改用 ordinal 比例尺。

library: "g2"
version: "5.x"
category: "scales"
tags:
  - "sequential"
  - "顺序比例尺"
  - "颜色渐变"
  - "连续颜色"
  - "palette"
  - "scale"

related:
  - "g2-scale-linear"
  - "g2-scale-quantile-quantize"
  - "g2-scale-threshold"
  - "g2-mark-cell-heatmap"

use_cases:
  - "热力图颜色渐变(低值→高值)"
  - "地图按数值着色(choropleth)"
  - "散点图气泡颜色按数值渐变"

difficulty: "beginner"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/scale/sequential"
---

## ⚠️ 使用约束

**sequential 仅适用于 `encode.color` 字段为连续类型(数值型)的场景。**

| 字段类型 | 示例 | 是否可用 sequential |
|--------|------|------------------|
| 连续数值(quantitative) | `temp_max`、`sales`、`score` | ✅ 允许 |
| 分类(categorical / ordinal) | `city`、`category`、`name` | ❌ 禁止,用 `ordinal` |
| 间断(band / point) | 离散的坐标轴字段 | ❌ 禁止,用 `ordinal` |

分类或间断字段使用 sequential 会导致所有数据映射到渐变色的两端,颜色区分度极差。

## 最小可运行示例(热力图)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'cell',
  data: {
    type: 'fetch',
    value: 'https://assets.antv.antgroup.com/g2/seattle-weather.json',
  },
  encode: {
    x: (d) => new Date(d.date).getUTCDate(),
    y: (d) => new Date(d.date).getUTCMonth(),
    color: 'temp_max',
  },
  transform: [{ type: 'group', color: 'max' }],
  scale: {
    color: {
      type: 'sequential',
      palette: 'gnBu',   // 内置色板:从浅蓝到深蓝
    },
  },
  style: { inset: 0.5 },
});

chart.render();
```

## 合法 palette 完整列表

G2 的 `palette` 值通过 d3-scale-chromatic 查找,**只有以下名称合法**(大小写不敏感),不在此列表中的名称(如 `'blueOrange'`、`'redGreen'`、`'heatmap'`)会导致运行时报错 `Unknown palette`。

### 单色系顺序渐变(适合 sequential — 正值数据)

| palette 名称 | 效果 |
|------------|------|
| `'blues'` | 白→蓝 |
| `'greens'` | 白→绿 |
| `'reds'` | 白→红 |
| `'oranges'` | 白→橙 |
| `'purples'` | 白→紫 |
| `'greys'` | 白→灰 |
| `'orRd'` | 橙→红 |
| `'buGn'` | 蓝→绿 |
| `'buPu'` | 蓝→紫 |
| `'gnBu'` | 绿→蓝 |
| `'puBu'` | 紫→蓝 |
| `'puBuGn'` | 紫→蓝→绿 |
| `'puRd'` | 紫→红 |
| `'rdPu'` | 红→紫 |
| `'ylGn'` | 黄→绿 |
| `'ylGnBu'` | 黄→绿→蓝(sequential 默认值) |
| `'ylOrBr'` | 黄→橙→棕 |
| `'ylOrRd'` | 黄→橙→红 |

### 多色感知均匀渐变(适合 sequential — 推荐色盲友好)

| palette 名称 | 效果 |
|------------|------|
| `'viridis'` | 紫→蓝→绿→黄(感知均匀,色盲友好) |
| `'plasma'` | 蓝紫→橙黄 |
| `'magma'` | 黑→紫→橙→白 |
| `'inferno'` | 黑→紫→红→黄 |
| `'cividis'` | 蓝→黄(对所有色盲类型友好) |
| `'turbo'` | 蓝→绿→黄→红(彩虹改进版) |
| `'rainbow'` | 彩虹(不建议,感知不均匀) |
| `'sinebow'` | 平滑彩虹 |
| `'warm'` | 暖色(橙→红→紫) |
| `'cool'` | 冷色(青→蓝→紫) |
| `'cubehelixDefault'` | 螺旋渐变(黑→白) |

### 发散色阶(适合 diverging — 正负值对比)

| palette 名称 | 效果 |
|------------|------|
| `'rdBu'` | 红→白→蓝(最常用) |
| `'rdYlBu'` | 红→黄→蓝 |
| `'rdYlGn'` | 红→黄→绿(涨跌热力图) |
| `'rdGy'` | 红→白→灰 |
| `'pRGn'` | 紫→白→绿 |
| `'piYG'` | 粉红→白→黄绿 |
| `'puOr'` | 紫→白→橙 |
| `'brBG'` | 棕→白→蓝绿 |
| `'spectral'` | 红→橙→黄→绿→蓝(多色发散) |

```javascript
// ✅ 合法示例
scale: { color: { type: 'sequential', palette: 'blues' } }
scale: { color: { type: 'sequential', palette: 'viridis' } }
scale: { color: { type: 'sequential', palette: 'ylOrRd' } }
scale: { color: { type: 'diverging',  palette: 'rdBu' } }
scale: { color: { type: 'diverging',  palette: 'rdYlGn' } }

// ❌ 非法示例(不存在,会报 Unknown palette 错误)
scale: { color: { type: 'sequential', palette: 'blueOrange' } }  // ❌ 不存在
scale: { color: { type: 'sequential', palette: 'redGreen' } }    // ❌ 不存在
scale: { color: { type: 'sequential', palette: 'heatmap' } }     // ❌ 不存在
scale: { color: { type: 'sequential', palette: 'rainbow2' } }    // ❌ 不存在
scale: { color: { type: 'sequential', palette: 'blue-orange' } } // ❌ 不存在
```

## 自定义颜色范围

```javascript
// 使用 range 指定首尾颜色(两端插值)
chart.options({
  scale: {
    color: {
      type: 'sequential',
      range: ['#ebedf0', '#196127'],  // 从浅灰到深绿(GitHub 贡献图风格)
    },
  },
});

// 使用 domain 控制映射范围
chart.options({
  scale: {
    color: {
      type: 'sequential',
      palette: 'blues',
      domain: [0, 100],   // 明确指定数值范围
    },
  },
});
```

## sequential vs 其他颜色比例尺

```javascript
// sequential:连续颜色渐变(连续数值 → 连续颜色)
scale: { color: { type: 'sequential', palette: 'blues' } }

// quantile:自动分位数分组(连续数值 → 离散颜色,等频分组)
scale: { color: { type: 'quantile', range: ['#eee', '#aaa', '#666', '#000'] } }

// quantize:等距分段(连续数值 → 离散颜色,等距分组)
scale: { color: { type: 'quantize', domain: [0, 100], range: ['#fee', '#f99', '#f00'] } }

// threshold:手动断点分级(连续数值 → 离散颜色,自定义断点)
scale: { color: { type: 'threshold', domain: [25, 75], range: ['#0f0', '#ff0', '#f00'] } }
```

## 常见错误与修正

### 错误:使用不存在的 palette 名称

G2 的 palette 值来自 d3-scale-chromatic,不存在的名称会在运行时抛出 `Error: Unknown palette: XxxXxx`,图表无法渲染。

```javascript
// ❌ 这些名称看起来合理,但 G2 中不存在
scale: { color: { type: 'sequential', palette: 'blueOrange' } }   // ❌ → Error: Unknown palette
scale: { color: { type: 'sequential', palette: 'blueGreen' } }    // ❌ → 应用 'buGn' 或 'gnBu'
scale: { color: { type: 'sequential', palette: 'redBlue' } }      // ❌ → 应用 'rdBu'(发散)
scale: { color: { type: 'diverging',  palette: 'greenRed' } }     // ❌ → 应用 'rdYlGn'(注意顺序)
scale: { color: { type: 'sequential', palette: 'hot' } }          // ❌ → 不存在,用 'ylOrRd' 代替
scale: { color: { type: 'sequential', palette: 'jet' } }          // ❌ → 不存在,用 'turbo' 代替
scale: { color: { type: 'sequential', palette: 'coolwarm' } }     // ❌ → 应用 'rdBu'(发散)

// ✅ 不确定时,从下列可信名称选择
// 单色顺序:'blues' | 'greens' | 'reds' | 'oranges' | 'purples' | 'ylOrRd' | 'ylGnBu'
// 感知均匀:'viridis' | 'plasma' | 'magma' | 'inferno' | 'cividis' | 'turbo'
// 发散:    'rdBu' | 'rdYlGn' | 'rdYlBu' | 'pRGn' | 'brBG' | 'spectral'
```

### 错误:sequential 用于分类数据
```javascript
// ❌ sequential 只适合连续数值,分类数据应用 ordinal
chart.options({
  encode: { color: 'city' },   // city 是分类字段
  scale: { color: { type: 'sequential' } },  // ❌ 会产生奇怪的渐变
});

// ✅ 分类数据用 ordinal
chart.options({
  encode: { color: 'city' },
  scale: { color: { type: 'ordinal', range: ['#5B8FF9', '#61DDAA', '#FFD666'] } },  // ✅
});
```

### 错误:未使用 transform 导致数据聚合异常

在使用 `cell` 类型图表时,若原始数据包含多个相同 `(x, y)` 坐标的记录,必须使用 `transform` 对其进行聚合,否则可能导致颜色映射不准确甚至图表渲染失败。

```javascript
// ❌ 未聚合相同坐标的 temp_max 值
chart.options({
  type: 'cell',
  data: weatherData,
  encode: {
    x: (d) => new Date(d.date).getUTCDate(),
    y: (d) => new Date(d.date).getUTCMonth(),
    color: 'temp_max',
  },
  scale: { color: { type: 'sequential', palette: 'gnBu' } },
});

// ✅ 使用 group transform 聚合相同坐标的数据
chart.options({
  type: 'cell',
  data: weatherData,
  encode: {
    x: (d) => new Date(d.date).getUTCDate(),
    y: (d) => new Date(d.date).getUTCMonth(),
    color: 'temp_max',
  },
  transform: [{ type: 'group', color: 'max' }],  // 对每个格子取 temp_max 的最大值
  scale: { color: { type: 'sequential', palette: 'gnBu' } },
});
```
```
references/scales/g2-scale-threshold.md
---
id: "g2-scale-threshold"
title: "G2 阈值比例尺(threshold)"
description: |
  阈值比例尺将连续数值按指定的阈值切分为若干区间,每个区间映射到一个离散输出(如颜色)。
  常用于热力图、地图分级着色等场景,用几个关键阈值划分数据等级。
  与 quantize(等分)不同,threshold 支持自定义不均匀的分割点。

library: "g2"
version: "5.x"
category: "scales"
tags:
  - "threshold"
  - "阈值"
  - "比例尺"
  - "分级"
  - "choropleth"
  - "热力"
  - "scale"

related:
  - "g2-scale-linear"
  - "g2-scale-ordinal"
  - "g2-mark-cell-heatmap"

use_cases:
  - "地图分级着色(choropleth map)"
  - "热力图数据分级(低/中/高/极高)"
  - "自定义区间的颜色映射"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/scale/threshold"
---

## 最小可运行示例(热力图分级着色)

```javascript
import { Chart } from '@antv/g2';

const data = [
  { week: 'Mon', hour: '08:00', count: 5 },
  { week: 'Mon', hour: '09:00', count: 45 },
  { week: 'Mon', hour: '12:00', count: 120 },
  { week: 'Tue', hour: '09:00', count: 85 },
  { week: 'Wed', hour: '12:00', count: 200 },
  // ...
];

const chart = new Chart({ container: 'container', width: 640, height: 300 });

chart.options({
  type: 'cell',
  data,
  encode: {
    x: 'hour',
    y: 'week',
    color: 'count',
  },
  scale: {
    color: {
      type: 'threshold',
      domain: [30, 80, 150],          // 3 个阈值,划分为 4 个区间
      range: ['#ebedf0', '#c6e48b', '#7bc96f', '#196127'],  // 对应 4 个颜色
    },
  },
  style: { lineWidth: 2, stroke: '#fff' },
});

chart.render();
```

## 配置项

```javascript
scale: {
  color: {
    type: 'threshold',
    domain: [30, 80, 150],    // N 个阈值,产生 N+1 个区间
    range: ['#low', '#mid-low', '#mid-high', '#high'],  // N+1 个输出值
  },
}
```

## 风险等级着色示例

```javascript
// 将连续风险分数映射到 4 个风险等级颜色
chart.options({
  scale: {
    color: {
      type: 'threshold',
      domain: [25, 50, 75],     // 低/中/高/极高 分界线
      range: [
        '#52c41a',  // 0~25:低风险(绿)
        '#faad14',  // 25~50:中风险(黄)
        '#ff7a45',  // 50~75:高风险(橙)
        '#ff4d4f',  // 75+:极高风险(红)
      ],
    },
  },
});
```

## 常见错误与修正

### 错误:domain 和 range 数量不匹配
```javascript
// ❌ 错误:2 个 domain 阈值产生 3 个区间,但只有 2 个 range 颜色
chart.options({
  scale: {
    color: {
      type: 'threshold',
      domain: [50, 100],     // 2 个阈值 → 3 个区间
      range: ['#green', '#red'],  // ❌ 只有 2 个颜色,应该是 3 个
    },
  },
});

// ✅ 正确:domain N 个阈值 → range 需要 N+1 个颜色
chart.options({
  scale: {
    color: {
      type: 'threshold',
      domain: [50, 100],
      range: ['#52c41a', '#faad14', '#ff4d4f'],  // ✅ 3 个颜色
    },
  },
});
```
references/scales/g2-scale-time.md
---
id: "g2-scale-time"
title: "G2 Time 时间比例尺"
description: |
  Time 比例尺将时间数据(Date 对象或时间戳)映射到连续坐标轴,
  自动处理时间刻度间隔、格式化和排序。当 encode.x 映射 Date 类型数据时自动启用。

library: "g2"
version: "5.x"
category: "scales"
tags:
  - "time"
  - "时间比例尺"
  - "时间轴"
  - "Date"
  - "时间序列"
  - "scale"
  - "spec"

related:
  - "g2-mark-line-basic"
  - "g2-comp-axis-config"
  - "g2-scale-linear"

use_cases:
  - "绘制时间序列折线图、面积图"
  - "控制时间轴的刻度粒度和标签格式"
  - "设置时间轴的显示范围"

difficulty: "intermediate"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/scale/time"
---

## 自动识别(推荐)

当数据字段为 `Date` 对象时,G2 自动使用 Time Scale,无需显式配置:

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 700, height: 400 });

chart.options({
  type: 'line',
  data: [
    { date: new Date('2024-01-01'), value: 100 },
    { date: new Date('2024-02-01'), value: 130 },
    { date: new Date('2024-03-01'), value: 110 },
    { date: new Date('2024-04-01'), value: 160 },
    { date: new Date('2024-05-01'), value: 145 },
  ],
  encode: { x: 'date', y: 'value' },   // Date 对象自动用 Time Scale
});

chart.render();
```

## 显式配置 Time Scale

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  scale: {
    x: {
      type: 'time',               // 显式指定(字符串日期时需要)
      domain: [                   // 限制显示范围
        new Date('2024-01-01'),
        new Date('2024-12-31'),
      ],
      nice: true,                  // 将域扩展到整洁的时间边界
    },
  },
});
```

## 格式化时间轴标签

```javascript
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  axis: {
    x: {
      // 使用 dayjs 格式字符串
      labelFormatter: 'YYYY-MM',           // 年-月:2024-01
      // labelFormatter: 'MM/DD',          // 月/日:01/15
      // labelFormatter: 'YYYY年MM月',     // 中文格式
      // labelFormatter: (d) => `Q${Math.ceil((d.getMonth()+1)/3)}`,  // 自定义
      tickCount: 6,
    },
  },
});
```

## 字符串日期(推荐转为 Date 对象)

G2 v5 对 `YYYY-MM-DD` 格式的字符串有一定自动识别能力,但行为依赖内部推断,**不稳定**。
推荐在数据预处理阶段统一转为 `Date` 对象,避免歧义:

```javascript
// ✅ 推荐:预处理时转为 Date 对象
const rawData = [
  { date: '2024-01-01', value: 100 },
  { date: '2024-02-01', value: 130 },
];
const data = rawData.map(d => ({ ...d, date: new Date(d.date) }));

chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
  // 无需 scale.x.type,G2 自动识别 Date 对象为 Time Scale
});
```

**不要**在字符串日期上显式写 `scale: { x: { type: 'time' } }`,这是多余的配置,
且在某些场景(如 fold 后数据类型变化)会引发渲染异常。

## 常见错误与修正

### 错误 1:显式声明 type: 'time'(不必要且有风险)
```javascript
// ❌ 不推荐:在字符串日期上显式写 type: 'time'
chart.options({
  type: 'line',
  data: [{ date: '2024-01-01', value: 100 }],
  encode: { x: 'date', y: 'value' },
  scale: { x: { type: 'time' } },   // ❌ 多余,可能引发异常
});

// ✅ 正确:转为 Date 对象,让 G2 自动处理
const data = rawData.map(d => ({ ...d, date: new Date(d.date) }));
chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value' },
});
```

### 错误 2:数据乱序导致折线错乱
```javascript
// ❌ 错误:数据顺序混乱,折线会连错
const data = [
  { date: new Date('2024-03-01'), value: 110 },
  { date: new Date('2024-01-01'), value: 100 },  // 时间倒序
];

// ✅ 正确:按时间排序后再传入
const data = rawData
  .map(d => ({ ...d, date: new Date(d.date) }))
  .sort((a, b) => a.date - b.date);
```
references/themes/g2-theme-builtin.md
---
id: "g2-theme-builtin"
title: "G2 内置主题配置"
description: |
  G2 v5 内置 classic(经典)、classicDark(深色)、academy(学术)三种主题。
  通过 theme 字段或 Chart 构造函数中的 theme 参数全局切换,也可局部覆盖样式变量。

library: "g2"
version: "5.x"
category: "themes"
tags:
  - "theme"
  - "主题"
  - "dark"
  - "深色主题"
  - "classicDark"
  - "spec"

related:
  - "g2-core-chart-init"
  - "g2-mark-interval-basic"

use_cases:
  - "切换图表整体配色风格"
  - "适配深色模式(Dark Mode)"
  - "统一多图表的视觉风格"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/theme"
---

## 内置主题列表

| 主题名 | 说明 |
|--------|------|
| `'classic'` | 默认主题(蓝色系,白色背景) |
| `'classicDark'` | 深色主题(深色背景,明亮配色) |
| `'academy'` | 学术风主题(灰色调,适合论文/报告) |

## 基本用法(切换主题)

```javascript
import { Chart } from '@antv/g2';

// 方式 1:构造函数中指定
const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
  theme: 'classicDark',    // 深色主题
});

chart.options({
  type: 'interval',
  data: [
    { genre: 'Sports',   sold: 275 },
    { genre: 'Strategy', sold: 115 },
    { genre: 'Action',   sold: 120 },
  ],
  encode: { x: 'genre', y: 'sold', color: 'genre' },
});

chart.render();
```

```javascript
// 方式 2:options 中指定
const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data,
  encode: { x: 'genre', y: 'sold', color: 'genre' },
  theme: 'academy',        // 学术主题
});

chart.render();
```

## 深色主题示例

```javascript
const chart = new Chart({
  container: 'container',
  width: 700,
  height: 400,
  theme: 'classicDark',
});

chart.options({
  type: 'view',
  data,
  encode: { x: 'month', y: 'value' },
  children: [
    {
      type: 'area',
      style: { fillOpacity: 0.3 },
    },
    {
      type: 'line',
      style: { lineWidth: 2 },
    },
  ],
});

chart.render();
```

## 运行时切换主题

```javascript
const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({ type: 'interval', data, encode: { x: 'x', y: 'y' } });
chart.render();

// 切换到深色主题(需重新渲染)
chart.theme('classicDark');
chart.render();
```

## 局部覆盖主题变量

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'genre', y: 'sold' },
  theme: {
    defaultColor: '#ff6b35',         // 默认颜色(第一个系列的颜色)
    defaultStrokeColor: '#333',      // 默认边框色
    // 覆盖色板
    colors10: [
      '#ff6b35', '#f7c59f', '#efefd0', '#004e89', '#1a936f',
      '#88d498', '#c6dabf', '#eaf4d3', '#7b2d8b', '#ff3a5c',
    ],
  },
});
```

## 自定义主题注册

```javascript
import { Chart, register } from '@antv/g2';

// 注册自定义主题
register('theme.myTheme', {
  defaultColor: '#e63946',
  background: '#f8f9fa',
  colors10: ['#e63946', '#457b9d', '#1d3557', '#a8dadc', '#f1faee'],
  // ... 其他变量
});

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data,
  encode: { x: 'genre', y: 'sold', color: 'genre' },
  theme: 'myTheme',   // 使用自定义主题
});

chart.render();
```

## 常见错误与修正

### 错误:theme 传入了非字符串/对象类型
```javascript
// ❌ 错误:theme 值不存在
chart.options({ theme: 'dark' });   // 'dark' 不是内置主题名

// ✅ 正确:使用内置主题名
chart.options({ theme: 'classicDark' });   // 深色主题
chart.options({ theme: 'classic' });       // 默认主题
chart.options({ theme: 'academy' });       // 学术主题
```

### 错误:切换主题后忘记重新渲染
```javascript
// ❌ 错误:切换主题后没有重新渲染
chart.theme('classicDark');
// 图表没有变化!

// ✅ 正确:切换后需调用 render()
chart.theme('classicDark');
chart.render();
```
references/themes/g2-theme-custom.md
---
id: "g2-theme-custom"
title: "G2 自定义主题创建(register + create)"
description: |
  G2 v5 支持通过 register('theme.xxx', themeConfig) 注册自定义主题。
  自定义主题可以覆盖配色、字体、各 Mark 的默认样式等。
  也可以用 theme 字段传入对象,局部覆盖当前主题的特定属性。
  内置主题包括 classic、classicDark、academy(详见 g2-theme-builtin)。

library: "g2"
version: "5.x"
category: "themes"
tags:
  - "theme"
  - "自定义主题"
  - "register"
  - "主题注册"
  - "colors10"
  - "colors20"
  - "配色方案"

related:
  - "g2-theme-builtin"
  - "g2-core-chart-init"

use_cases:
  - "企业品牌定制化图表主题"
  - "统一多图表的配色风格"
  - "局部覆盖某几项默认样式"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/theme"
---

## 方式一:局部覆盖主题(theme 对象)

最简单的方式是在 options 的 theme 字段中直接传对象,覆盖部分属性:

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data,
  encode: { x: 'genre', y: 'sold', color: 'genre' },
  theme: {
    // 覆盖分类色板
    colors10: [
      '#3B82F6', '#EF4444', '#10B981', '#F59E0B',
      '#8B5CF6', '#F97316', '#06B6D4', '#84CC16',
      '#EC4899', '#6B7280',
    ],
    // 覆盖默认颜色
    defaultColor: '#3B82F6',
  },
});

chart.render();
```

## 方式二:注册全局自定义主题

```javascript
import { Chart, register } from '@antv/g2';

// 注册自定义主题(基于 classic 主题扩展)
register('theme.brand', {
  // 基础颜色
  defaultColor: '#e63946',
  defaultStrokeColor: '#1d1d1d',

  // 分类色板(10色 / 20色)
  colors10: [
    '#e63946', '#457b9d', '#1d3557', '#a8dadc',
    '#f1faee', '#e9c46a', '#f4a261', '#e76f51',
    '#264653', '#2a9d8f',
  ],
  colors20: [
    '#e63946', '#457b9d', '#1d3557', '#a8dadc',
    '#f1faee', '#e9c46a', '#f4a261', '#e76f51',
    '#264653', '#2a9d8f',
    // 后 10 种(渐变或变体)
    '#ff6b6b', '#74b9ff', '#55efc4', '#ffeaa7',
    '#dfe6e9', '#fab1a0', '#fd79a8', '#6c5ce7',
    '#00b894', '#00cec9',
  ],
});

// 使用自定义主题
const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data,
  encode: { x: 'genre', y: 'sold', color: 'genre' },
  theme: 'brand',   // 使用注册的自定义主题名
});

chart.render();
```

## 主题配置项速查

```javascript
// 以下是可覆盖的主要配置项
const themeConfig = {
  // ── 基础颜色 ─────────────────────────────
  defaultColor: '#1890ff',        // 默认颜色(单系列时)
  defaultStrokeColor: '#ffffff',  // 默认描边颜色

  // ── 色板 ──────────────────────────────────
  colors10: [...],   // 10 色分类色板
  colors20: [...],   // 20 色分类色板

  // ── 背景 ──────────────────────────────────
  background: '#ffffff',    // 图表背景色

  // ── 字体 ──────────────────────────────────
  fontFamily: 'sans-serif',  // 全局字体

  // ── 动画默认时长 ───────────────────────────
  enter: { duration: 300 },
  update: { duration: 300 },
  exit: { duration: 300 },
};
```

## 深色主题(基于 classicDark 局部覆盖)

```javascript
// 在 classicDark 基础上修改
const chart = new Chart({
  container: 'container',
  theme: 'classicDark',
});

chart.options({
  type: 'line',
  data,
  encode: { x: 'date', y: 'value', color: 'type' },
  // 局部覆盖:修改配色但保留深色背景
  theme: {
    colors10: ['#60a5fa', '#34d399', '#f87171', '#a78bfa',
               '#fbbf24', '#22d3ee', '#f472b6', '#4ade80',
               '#fb923c', '#e879f9'],
  },
});
```

## 常见错误与修正

### 错误:register 主题名忘加 'theme.' 前缀
```javascript
// ❌ 错误:注册时必须用 'theme.xxx' 格式
register('brandTheme', { colors10: [...] });    // ❌
chart.options({ theme: 'brandTheme' });          // 不生效

// ✅ 正确:必须加 'theme.' 前缀
register('theme.brandTheme', { colors10: [...] });  // ✅
chart.options({ theme: 'brandTheme' });              // ✅ 使用时不带前缀
```

### 错误:theme 和 style 混用
```javascript
// ❌ 错误:主题配色和单个 mark 的样式混淆
chart.options({
  type: 'interval',
  style: { colors10: [...] },  // ❌ colors10 不在 style 里
});

// ✅ 颜色主题在 theme 字段
chart.options({
  type: 'interval',
  theme: { colors10: [...] },  // ✅
  style: { fillOpacity: 0.8 }, // ✅ 单 mark 样式在 style 里
});
```
references/transforms/g2-transform-bin.md
---
id: "g2-transform-bin"
title: "G2 Bin / BinX 数值分桶变换(直方图)"
description: |
  binX 将连续的数值 x 通道分成若干区间(桶),统计每个区间内的数据量,
  是直方图的核心变换。bin 同时对 x 和 y 两个方向分桶,生成二维频率矩阵。
  通过 thresholds 控制桶的数量,y 通道指定聚合方式(count/sum 等)。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "bin"
  - "binX"
  - "分桶"
  - "直方图"
  - "histogram"
  - "频率分布"
  - "transform"

related:
  - "g2-transform-groupx"
  - "g2-mark-interval-basic"
  - "g2-mark-cell-heatmap"

use_cases:
  - "绘制直方图(数值分布频率)"
  - "二维频率热力图(bin 同时对 x/y 分桶)"
  - "将连续数值转化为离散分组统计"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform/bin"
---

## 最小可运行示例(直方图)

```javascript
import { Chart } from '@antv/g2';

// 连续数值数据,不需要预先统计频率
const rawData = Array.from({ length: 1000 }, () => ({
  age: Math.floor(Math.random() * 50 + 20),  // 20~70 岁之间的随机年龄
}));

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'interval',
  data: rawData,
  encode: {
    x: 'age',   // 连续数值 → 自动分桶
    y: '★',     // 占位符,binX 会计算 count
  },
  transform: [
    {
      type: 'binX',
      y: 'count',        // 聚合方式:统计每桶数量
      thresholds: 15,    // 桶的数量(近似值),默认自动计算
    },
  ],
  style: { inset: 1 },  // 柱子之间留 1px 间隙
  axis: { y: { title: '人数' } },
});

chart.render();
```

## BinX 配置项

```javascript
transform: [
  {
    type: 'binX',
    thresholds: 20,  // 桶数量(整数)或阈值数组(如 [0, 25, 50, 75, 100])
    y: 'count',      // 聚合:'count' | 'sum' | 'mean' | 'min' | 'max' | function
    // groupBy: 'color',  // 按颜色分组分桶(用于分组直方图)
  },
]
```

## 二维频率热力图(bin)

```javascript
// bin 同时对 x 和 y 方向分桶,生成二维频率矩阵
chart.options({
  type: 'cell',
  data: scatterData,
  encode: {
    x: 'x',
    y: 'y',
    color: '★',
  },
  transform: [
    {
      type: 'bin',
      color: 'count',    // 统计每个格子的点数(映射到颜色)
      thresholds: [20, 20],  // [x 方向桶数, y 方向桶数]
    },
  ],
  scale: { color: { type: 'sequential', palette: 'ylOrRd' } },
  style: { lineWidth: 0 },
});
```

## 分组直方图(按颜色分桶)

```javascript
chart.options({
  type: 'interval',
  data: employeeData,
  encode: { x: 'salary', y: '★', color: 'dept' },
  transform: [
    { type: 'binX', y: 'count', thresholds: 12 },
    { type: 'stackY' },   // 堆叠
  ],
});
```

## 常见错误与修正

### 错误 1:对分类字段 x 用 binX——分类应用 groupX
```javascript
// ❌ 错误:x 是字符串类别,binX 无法对字符串分桶
chart.options({
  encode: { x: 'department', y: '★' },   // department 是字符串
  transform: [{ type: 'binX', y: 'count' }],  // ❌
});

// ✅ 字符串类别用 groupX
chart.options({
  encode: { x: 'department', y: '★' },
  transform: [{ type: 'groupX', y: 'count' }],  // ✅
});

// ✅ binX 用于连续数值
chart.options({
  encode: { x: 'age', y: '★' },   // age 是数字
  transform: [{ type: 'binX', y: 'count' }],  // ✅
});
```

### 错误 2:thresholds 设置太大——出现极多细小的桶
```javascript
// ❌ 1000 条数据设置 500 个桶,每桶 2 条,直方图没有统计意义
transform: [{ type: 'binX', y: 'count', thresholds: 500 }]  // ❌

// ✅ 直方图桶数通常在 10~50 之间
transform: [{ type: 'binX', y: 'count', thresholds: 20 }]  // ✅
```
references/transforms/g2-transform-binx.md
---
id: "g2-transform-binx"
title: "G2 BinX 分箱变换(直方图)"
description: |
  BinX 将连续 x 值按指定区间(bin)分组,自动统计每个区间内的记录数(或聚合值),
  是绘制频率直方图的核心 Transform。与 Interval Mark 组合直接使用原始数据即可生成直方图。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "BinX"
  - "直方图"
  - "histogram"
  - "分箱"
  - "分布"
  - "频率"
  - "transform"
  - "spec"

related:
  - "g2-mark-interval-basic"
  - "g2-transform-stacky"

use_cases:
  - "展示连续数值数据的频率分布"
  - "探索数据的分布形态(正态、偏态等)"

anti_patterns:
  - "数据是离散分类时不需要 binX,直接用 interval 即可"

difficulty: "intermediate"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform/bin-x"
---

## 最小可运行示例(直方图)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 400,
});

// 原始连续数值数据
const rawData = Array.from({ length: 200 }, () => ({
  value: Math.random() * 100,
}));

chart.options({
  type: 'interval',
  data: rawData,
  encode: { x: 'value' },
  transform: [
    {
      type: 'binX',
      y: 'count',      // 统计每个 bin 内的记录数,结果存入 y 通道
      thresholds: 20,  // 分成约 20 个 bin
    },
  ],
  style: { inset: 0.5 },    // 柱体间留细缝
});

chart.render();
```

## 配置项

```javascript
transform: [
  {
    type: 'binX',
    // 统计目标
    y: 'count',       // 'count'(默认,计数)| 'sum' | 'mean' | 'max' | 'min'
    // 如果是 sum/mean 等,还需指定聚合的字段:
    // y: 'sum', field: 'amount',

    // 分箱数量控制(三选一)
    thresholds: 20,           // 目标分箱数(近似值,库会自动调整)
    // domain: [0, 100],      // 指定值域范围
    // step: 5,               // 每个 bin 的宽度(与 thresholds 互斥)
  },
],
```

## 分组直方图(按类别着色)

```javascript
chart.options({
  type: 'interval',
   data,  // [{ value: 42, group: 'A' }, ...]
  encode: { x: 'value', color: 'group' },
  transform: [
    { type: 'binX', y: 'count', thresholds: 15 },
    { type: 'stackY' },   // 堆叠同一 bin 内不同分组的计数
  ],
});
```

## 常见错误与修正

### 错误:对离散分类数据使用 binX
```javascript
// ❌ 错误:genre 是分类字段,不需要 binX
chart.options({
  type: 'interval',
  data,
  encode: { x: 'genre' },
  transform: [{ type: 'binX', y: 'count' }],  // 不必要!
});

// ✅ 正确:分类数据直接用 interval,不需要 binX
chart.options({
  type: 'interval',
  data,
  encode: { x: 'genre', y: 'count' },
});
```

### 错误:忘记指定 y 统计量
```javascript
// ❌ 错误:没有 y 参数,不知道统计什么
chart.options({ transform: [{ type: 'binX', thresholds: 20 }] });

// ✅ 正确:必须指定 y
chart.options({ transform: [{ type: 'binX', y: 'count', thresholds: 20 }] });
```
references/transforms/g2-transform-diffy.md
---
id: "g2-transform-diffy"
title: "G2 DiffY 差值区域变换"
description: |
  diffY 计算两条折线之间的差值区间(y0 到 y1),用于绘制差值面积图。
  保持上方折线的 y 值不变,计算下方折线相对于上方的差值作为 y1,
  视觉上展示两系列之间的正/负差异区域。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "diffY"
  - "差值"
  - "差异面积"
  - "对比"
  - "transform"
  - "区间面积"

related:
  - "g2-mark-area-stacked"
  - "g2-transform-stacky"
  - "g2-mark-line-basic"

use_cases:
  - "展示两条折线之间的正/负差异区域"
  - "实际值 vs 预测值的偏差可视化"
  - "价格上下区间的差值展示"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform/diff-y"
---

## 最小可运行示例(实际 vs 预测差异)

```javascript
import { Chart } from '@antv/g2';

const data = [
  { month: 'Jan', actual: 83,  forecast: 75 },
  { month: 'Feb', actual: 60,  forecast: 70 },
  { month: 'Mar', actual: 95,  forecast: 85 },
  { month: 'Apr', actual: 72,  forecast: 80 },
  { month: 'May', actual: 110, forecast: 100 },
  { month: 'Jun', actual: 88,  forecast: 95 },
];

// 转换为长表格式
const longData = data.flatMap(d => [
  { month: d.month, value: d.actual,   type: '实际' },
  { month: d.month, value: d.forecast, type: '预测' },
]);

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'view',
  children: [
    // 差值面积(正差异:实际>预测 显示绿色,负差异:实际<预测 显示红色)
    {
      type: 'area',
       longData,
      encode: { x: 'month', y: 'value', color: 'type', series: 'type' },
      transform: [{ type: 'diffY' }],   // 计算两系列间的差值区间
      style: {
        fillOpacity: 0.3,
      },
    },
    // 主折线
    {
      type: 'line',
       longData,
      encode: { x: 'month', y: 'value', color: 'type' },
      style: { lineWidth: 2 },
    },
  ],
});

chart.render();
```

## 配置项

```javascript
transform: [
  {
    type: 'diffY',
    groupBy: 'x',   // 按 x 通道分组对齐,默认 'x'
  },
]
```

## 常见错误与修正

### 错误:数据没有两个系列——diffY 需要至少两个 series 才能计算差值
```javascript
// ❌ 只有一个系列,diffY 没有对比基准,差值为 0
chart.options({
  type: 'area',
  data: singleSeriesData,
  encode: { x: 'date', y: 'value' },  // ❌ 没有 series/color 区分两组
  transform: [{ type: 'diffY' }],
});

// ✅ 必须有两个系列(通过 color/series 区分)
chart.options({
  type: 'area',
  data: twoSeriesData,
  encode: {
    x: 'date',
    y: 'value',
    color: 'type',   // ✅ 区分两个系列
    series: 'type',
  },
  transform: [{ type: 'diffY' }],
});
```

### 错误:diffY 与 stackY 混淆——stackY 是累积,diffY 是差值
```javascript
// stackY:将多个系列的 y 值累积(适合堆叠图)
transform: [{ type: 'stackY' }]

// diffY:计算两个系列之间的差值区间(适合差值面积图)
transform: [{ type: 'diffY' }]
```
references/transforms/g2-transform-dodgex.md
---
id: "g2-transform-dodgex"
title: "G2 DodgeX 分组变换"
description: |
  DodgeX 是 G2 v5 中用于分组展示的 Transform,
  将同一 x 位置的多系列元素在水平方向上错开排列,
  是分组柱状图的核心依赖。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "dodgeX"
  - "分组"
  - "并排"
  - "transform"
  - "分组柱状图"
  - "spec"

related:
  - "g2-mark-interval-grouped"
  - "g2-transform-stacky"

use_cases:
  - "创建分组柱状图(并排展示多系列)"
  - "分组散点图"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform/dodge-x"
---

## 基本用法

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'dodgeX' }],
});

chart.render();
```

## 配置项

```javascript
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [
    {
      type: 'dodgeX',
      padding: 0,          // 组内各柱之间的间距(相对于每组宽度,0-1),默认 0
      paddingOuter: 0.1,   // 整组与相邻组的外边距
      reverse: false,      // 是否反转分组顺序
    },
  ],
});
```

## 与 stackY 的区别

```javascript
// dodgeX:各系列并排展示,便于直接对比绝对值
chart.options({ transform: [{ type: 'dodgeX' }] });

// stackY:各系列堆叠展示,便于对比总量和占比
chart.options({ transform: [{ type: 'stackY' }] });
```

## 分组 + 堆叠组合

同时分组和堆叠:先 dodgeX 再 stackY,实现「组内堆叠、组间并排」。

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'quarter', y: 'value', color: 'type', series: 'group' },
  transform: [
    { type: 'dodgeX', groupBy: 'x' },   // 按 series 分组,指定 groupBy: 'x' 避免 color 参与分组
    { type: 'stackY' },                 // 组内按 color 堆叠
  ],
});
```

## 水平分组条形图

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'type' },
  transform: [{ type: 'dodgeX' }],
  coordinate: { transform: [{ type: 'transpose' }] },
});
```

## 常见错误与修正

### 错误 1:transform 写成对象
```javascript
// ❌ chart.options({ transform: { type: 'dodgeX' } });
// ✅ chart.options({ transform: [{ type: 'dodgeX' }] });
```

### 错误 2:多系列 interval 没有分组/堆叠变换
```javascript
// ❌ 错误:多系列数据无 transform,柱体重叠在同一位置
chart.options({
  type: 'interval',
  data: multiSeriesData,
  encode: { x: 'month', y: 'value', color: 'type' },
});

// ✅ 正确:添加 dodgeX 分组展示
chart.options({
  type: 'interval',
  data: multiSeriesData,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'dodgeX' }],
});
```

### 错误 3:dodgeX 放在 data.transform 中
```javascript
// ❌ 错误:dodgeX 是 Mark Transform,不是 Data Transform
chart.options({
  data: { type: 'inline', value: data, transform: [{ type: 'dodgeX' }] },
});

// ✅ 正确:与 data/encode 同级
chart.options({
  data,
  encode: { x: 'x', y: 'y', color: 'type' },
  transform: [{ type: 'dodgeX' }],
});
```
references/transforms/g2-transform-flexx.md
---
id: "g2-transform-flexx"
title: "G2 FlexX 弹性宽度变换(马赛克图 / 不等宽柱)"
description: |
  flexX 根据数据值动态调整 x 轴比例尺的 flex 属性,使每个柱的宽度与数值成比例。
  常用于马赛克图(Marimekko chart):柱宽表示一个维度,柱高表示另一个维度。
  通过 field 指定控制宽度的字段,reducer 指定聚合方式。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "flexX"
  - "不等宽柱"
  - "马赛克图"
  - "Marimekko"
  - "弹性宽度"
  - "transform"

related:
  - "g2-transform-stacky"
  - "g2-mark-interval-stacked"

use_cases:
  - "马赛克图(双维度占比:柱宽 × 柱高)"
  - "不等宽柱状图(柱宽代表样本量/权重)"
  - "市场份额图(宽度=市场规模,高度=占比)"

difficulty: "advanced"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform/flex-x"
---

## 最小可运行示例(马赛克图)

```javascript
import { Chart } from '@antv/g2';

// 马赛克图:x 类别,y 子类别比例,value 控制柱宽(市场规模)
const data = [
  { region: '华北', type: '线上', revenue: 300, share: 0.6 },
  { region: '华北', type: '线下', revenue: 300, share: 0.4 },
  { region: '华东', type: '线上', revenue: 500, share: 0.7 },
  { region: '华东', type: '线下', revenue: 500, share: 0.3 },
  { region: '华南', type: '线上', revenue: 200, share: 0.5 },
  { region: '华南', type: '线下', revenue: 200, share: 0.5 },
];

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'interval',
  data,
  encode: {
    x: 'region',
    y: 'share',
    color: 'type',
  },
  transform: [
    {
      type: 'flexX',
      field: 'revenue',    // 控制柱宽的字段
      reducer: 'sum',      // 聚合方式(同一 x 类别可能有多行,需要 sum)
    },
    { type: 'stackY' },   // 再堆叠 y 方向(百分比)
  ],
  axis: {
    y: { labelFormatter: (v) => `${(v * 100).toFixed(0)}%` },
  },
});

chart.render();
```

## 配置项

```javascript
transform: [
  {
    type: 'flexX',
    field: 'sampleSize',  // 控制宽度的字段名(每个 x 类别的权重)
    channel: 'y',         // 依据哪个通道的值计算弹性(默认 'y')
    reducer: 'sum',       // 同一 x 类别下 field 值的聚合方式('sum' 是最常用的)
  },
]
```

## 常见错误与修正

### 错误:flexX 和 stackY 顺序错误——先 stackY 后 flexX
```javascript
// ❌ 错误:应该先 flexX 再 stackY
transform: [
  { type: 'stackY' },   // ❌ stackY 先执行,flexX 后调整宽度,比例关系出错
  { type: 'flexX', field: 'revenue' },
]

// ✅ 正确顺序:先 flexX(设置宽度弹性),再 stackY(堆叠高度)
transform: [
  { type: 'flexX', field: 'revenue', reducer: 'sum' },  // ✅ 先设置弹性宽度
  { type: 'stackY' },                                     // ✅ 再堆叠
]
```

### 错误:没有设置 reducer——同一 x 有多行时宽度计算不一致
```javascript
// ❌ 未设置 reducer,同一 region 有多行(线上/线下),flexX 不知如何聚合宽度
transform: [{ type: 'flexX', field: 'revenue' }]  // ❌ 缺少 reducer

// ✅ 设置 reducer: 'sum' 对同一 x 的 field 求和
transform: [{ type: 'flexX', field: 'revenue', reducer: 'sum' }]  // ✅
```
references/transforms/g2-transform-group.md
---
id: "g2-transform-group"
title: "G2 Group / GroupX / GroupY 分组聚合变换"
description: |
  Group、GroupX、GroupY 是 G2 v5 中用于分组聚合的 Transform。
  Group 按 x 和 y 通道双维度分组;GroupX 按 x 通道分组;GroupY 按 y 通道分组。
  支持 mean、sum、count、min、max、median、first、last 等聚合函数。
  常用于直方图、统计柱状图、聚合折线图等场景。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "group"
  - "groupX"
  - "groupY"
  - "分组聚合"
  - "transform"
  - "统计"
  - "mean"
  - "sum"

related:
  - "g2-transform-bin"
  - "g2-transform-stacky"
  - "g2-mark-interval-basic"

use_cases:
  - "按类别计算平均值(均值柱状图)"
  - "按 X 分组后求和展示总量"
  - "将明细数据聚合为统计摘要"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform/group-x"
---

## 核心概念

| Transform | 分组维度 | 典型场景 |
|-----------|----------|----------|
| `groupX` | x 通道(+ color/series) | 同类别取均值/求和 |
| `groupY` | y 通道 | 按 Y 分组聚合 |
| `group` | x + y 双通道 | 二维分组聚合 |

聚合函数通过 `y: 'mean'` 等形式指定,支持:
`mean`(均值)、`sum`(求和)、`count`(计数)、`min`、`max`、`median`、`first`、`last`

## GroupX 基本用法(按类别求均值)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data: [
    { category: 'A', value: 10 },
    { category: 'A', value: 20 },
    { category: 'A', value: 30 },
    { category: 'B', value: 40 },
    { category: 'B', value: 50 },
  ],
  encode: { x: 'category', y: 'value' },
  transform: [
    {
      type: 'groupX',
      y: 'mean',   // 按 x 分组,y 取均值
    },
  ],
});

chart.render();
// 结果:A 显示均值 20,B 显示均值 45
```

## GroupX 聚合函数一览

```javascript
chart.options({
  type: 'interval',
  data: rawData,
  encode: { x: 'category', y: 'value' },
  transform: [
    {
      type: 'groupX',
      y: 'mean',      // 均值
      // y: 'sum',    // 求和
      // y: 'count',  // 计数(忽略 y 通道值,统计条数)
      // y: 'max',    // 最大值
      // y: 'min',    // 最小值
      // y: 'median', // 中位数
    },
  ],
});
```

## 统计计数(频率分布)

```javascript
// 统计各类别出现次数
chart.options({
  type: 'interval',
  data: rawData,
  encode: { x: 'category' },    // 无需 y 通道
  transform: [
    { type: 'groupX', y: 'count' },  // y 会自动生成为计数值
  ],
});
```

## GroupY 用法(按 Y 分组)

```javascript
// 水平方向按 y 分组求均值(常用于横向柱状图)
chart.options({
  type: 'interval',
  data: rawData,
  encode: { y: 'category', x: 'value' },
  transform: [
    { type: 'groupY', x: 'mean' },  // 按 y 分组,x 取均值
  ],
  coordinate: { transform: [{ type: 'transpose' }] },
});
```

## 多字段聚合

```javascript
// 同时对多个字段聚合
chart.options({
  type: 'point',
  data: rawData,
  encode: { x: 'date', y: 'value', size: 'amount' },
  transform: [
    {
      type: 'groupX',
      y: 'mean',       // y 取均值
      size: 'sum',     // size 通道取求和
    },
  ],
});
```

## Cell 图表中的 Group 使用

对于 `cell` 类型的图表,通常需要先对数据进行分组聚合再渲染。例如,按日期的 UTC 日和 UTC 月分组,并取最高温度的最大值:

```javascript
const chart = new Chart({
  container: 'container',
});

chart.options({
  type: 'cell',
  height: 300,
  data: {
    type: 'inline',
    value: [
      { date: '2012-01-01', temp_max: 12.8 },
      { date: '2012-01-02', temp_max: 10.6 },
      // 更多数据...
    ]
  },
  encode: {
    x: (d) => new Date(d.date).getUTCDate(),
    y: (d) => new Date(d.date).getUTCMonth(),
    color: 'temp_max',
  },
  transform: [{ type: 'group', color: 'max' }],
  scale: { color: { type: 'sequential', palette: 'gnBu' } },
  style: { inset: 0.5 },
});

chart.render();
```

## 常见错误与修正

### 错误 1:transform 写成对象而非数组
```javascript
// ❌ 错误
chart.options({ transform: { type: 'groupX', y: 'mean' } });

// ✅ 正确
chart.options({ transform: [{ type: 'groupX', y: 'mean' }] });
```

### 错误 2:count 聚合时仍传 y encode
```javascript
// ❌ count 聚合时不需要 y 通道
chart.options({
  encode: { x: 'category', y: 'someField' },
  transform: [{ type: 'groupX', y: 'count' }],  // y: 'count' 会忽略 encode.y
});

// ✅ count 聚合只需 x 通道
chart.options({
  encode: { x: 'category' },    // 不需要 y
  transform: [{ type: 'groupX', y: 'count' }],
});
```

### 错误 3:Cell 图表未正确使用 Group 聚合
```javascript
// ❌ 错误:没有对重复的 x/y 组合做聚合,导致渲染异常
chart.options({
  type: 'cell',
  data: weatherData,
  encode: {
    x: (d) => new Date(d.date).getUTCDate(),
    y: (d) => new Date(d.date).getUTCMonth(),
    color: 'temp_max'
  },
  transform: []  // 缺少必要的 group 聚合
});

// ✅ 正确:使用 group 并指定 color 聚合方式
chart.options({
  type: 'cell',
  data: weatherData,
  encode: {
    x: (d) => new Date(d.date).getUTCDate(),
    y: (d) => new Date(d.date).getUTCMonth(),
    color: 'temp_max'
  },
  transform: [{ type: 'group', color: 'max' }]  // 必须聚合 color 通道
});
```
references/transforms/g2-transform-groupcolor.md
---
id: "g2-transform-groupcolor"
title: "G2 GroupColor Transform"
description: |
  按 color 通道对数据进行分组聚合。常用于分类聚合场景,
  如按类别统计总和、平均值等。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "分组"
  - "聚合"
  - "color"
  - "分类统计"

related:
  - "g2-transform-groupx"
  - "g2-transform-groupy"
  - "g2-transform-group"

use_cases:
  - "按类别统计总和"
  - "按颜色维度聚合数据"
  - "计算各类别的平均值、最大值"

anti_patterns:
  - "需要保留原始数据时不应使用"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform"
---

## 核心概念

GroupColor Transform 按 `color` 通道的值对数据进行分组,然后对每组进行聚合计算。

**聚合函数支持:**
- `sum`:求和
- `mean`:平均值
- `median`:中位数
- `max`:最大值
- `min`:最小值
- `count`:计数
- `first`:取第一个值
- `last`:取最后一个值

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'interval',
  data: [
    { category: 'A', type: 'X', value: 10 },
    { category: 'A', type: 'Y', value: 20 },
    { category: 'B', type: 'X', value: 15 },
    { category: 'B', type: 'Y', value: 25 },
  ],
  encode: {
    x: 'category',
    y: 'value',
    color: 'type',  // 按 type 分组
  },
  transform: [
    {
      type: 'groupColor',
      y: 'sum',  // 对每组求和
    },
  ],
});

chart.render();
```

## 常用变体

### 计算平均值

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'type' },
  transform: [
    { type: 'groupColor', y: 'mean' },
  ],
});
```

### 多通道聚合

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'type', size: 'count' },
  transform: [
    {
      type: 'groupColor',
      y: 'sum',
      size: 'count',  // 同时聚合 size 通道
    },
  ],
});
```

### 自定义聚合函数

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'type' },
  transform: [
    {
      type: 'groupColor',
      y: (I, V) => {
        // I: 组内索引数组
        // V: 该通道的值数组
        return I.reduce((sum, i) => sum + V[i], 0) / I.length;
      },
    },
  ],
});
```

## 完整类型参考

```typescript
interface GroupColorTransform {
  type: 'groupColor';
  y?: 'sum' | 'mean' | 'median' | 'max' | 'min' | 'count' | 'first' | 'last' | ((I: number[], V: any[]) => any);
  // 其他通道也可以聚合
  [channel: string]: Reducer;
}

type Reducer = 'sum' | 'mean' | 'median' | 'max' | 'min' | 'count' | 'first' | 'last' | ((I: number[], V: any[]) => any);
```

## 常见错误与修正

### 错误 1:未指定 color 通道

```javascript
// ❌ 错误:没有 color 通道,groupColor 无效
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value' },
  transform: [{ type: 'groupColor', y: 'sum' }],
});

// ✅ 正确:添加 color 通道
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value', color: 'type' },
  transform: [{ type: 'groupColor', y: 'sum' }],
});
```

### 错误 2:聚合函数名拼写错误

```javascript
// ❌ 错误
transform: [{ type: 'groupColor', y: 'average' }]

// ✅ 正确
transform: [{ type: 'groupColor', y: 'mean' }]
```
references/transforms/g2-transform-groupx.md
---
id: "g2-transform-groupx"
title: "G2 GroupX 分组聚合变换"
description: |
  groupX 按 x 通道的值对数据分组,并对 y 通道进行聚合计算(count、sum、mean、min、max 等)。
  常用于从原始行级数据直接计算统计量,无需预先聚合数据。
  groupY、groupColor、groupN 是其变体,分别按 y、color 通道或固定数量分组。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "groupX"
  - "分组"
  - "聚合"
  - "count"
  - "sum"
  - "mean"
  - "transform"
  - "统计"

related:
  - "g2-transform-stacky"
  - "g2-transform-binx"
  - "g2-mark-interval-basic"

use_cases:
  - "从行级原始数据统计各类别的数量(频率柱状图)"
  - "从明细数据聚合出各组的均值/求和"
  - "词频统计可视化"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform/group"
---

## 最小可运行示例(计数频率柱状图)

```javascript
import { Chart } from '@antv/g2';

// 原始行级数据,不需要预先统计频次
const rawData = [
  { dept: '研发' }, { dept: '研发' }, { dept: '研发' },
  { dept: '销售' }, { dept: '销售' },
  { dept: '设计' }, { dept: '设计' }, { dept: '设计' }, { dept: '设计' },
  { dept: 'HR' },
];

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'interval',
  data: rawData,
  encode: {
    x: 'dept',  // 分组字段
    y: '★',     // 占位符,实际 y 值由 groupX 计算
    color: 'dept',
  },
  transform: [
    {
      type: 'groupX',
      y: 'count',    // 对 y 通道的聚合方式:统计每组数量
    },
  ],
});

chart.render();
```

## 聚合方式速查

```javascript
// 计数(每组有多少条记录)
transform: [{ type: 'groupX', y: 'count' }]

// 求和(每组 y 字段的总和)
transform: [{ type: 'groupX', y: 'sum' }]

// 均值(每组 y 字段的平均值)
transform: [{ type: 'groupX', y: 'mean' }]

// 最大值 / 最小值
transform: [{ type: 'groupX', y: 'max' }]
transform: [{ type: 'groupX', y: 'min' }]

// 中位数
transform: [{ type: 'groupX', y: 'median' }]

// 自定义聚合函数
transform: [{ type: 'groupX', y: (values) => values.reduce((a, b) => a + b, 0) / values.length }]
```

## 按颜色分组(groupColor)

```javascript
// 统计各部门男女人数
chart.options({
  type: 'interval',
  data: rawEmployeeData,
  encode: { x: 'dept', y: '★', color: 'gender' },
  transform: [
    { type: 'groupX', y: 'count' },   // 先按 x 和 color 组合分组统计
    { type: 'dodgeX' },               // 再分组并排
  ],
});
```

## 均值折线图(从明细数据直接绘制)

```javascript
chart.options({
  type: 'line',
  data: dailySalesData,
  encode: { x: 'month', y: 'dailySales' },
  transform: [
    { type: 'groupX', y: 'mean' },  // 计算每月平均日销售额
  ],
  style: { lineWidth: 2 },
});
```

## 密度图中的 KDE 分组说明

在使用 `density` 图表类型配合 `kde` 变换时,需要注意 `kde` 变换本身不依赖 `groupX`,而是通过 `groupBy` 参数指定分组字段。例如:

```javascript
chart.options({
  type: 'density',
  data: {
    type: 'inline',
    value: irisData,
    transform: [{
      type: 'kde',
      field: 'y',
      groupBy: ['x', 'species'],  // 按 x 和 species 字段分组进行 KDE 计算
    }],
  },
  encode: {
    x: 'x',
    y: 'y',
    color: 'species',
    size: 'size',
    series: 'species',
  },
});
```

在这种情况下,`kde` 变换会自动完成分组和密度计算,无需额外添加 `groupX`。

## 常见错误与修正

### 错误 1:encode.y 写成实际字段名——groupX 应用后 y 通道被覆盖
```javascript
// ❌ 如果想让 groupX 计算 count,encode.y 不需要写实际字段
chart.options({
  encode: { x: 'dept', y: 'salary' },
  transform: [{ type: 'groupX', y: 'count' }],  // ⚠️  count 会覆盖 salary
});
// 结果:y 轴显示的是 count,不是 salary 的总和

// ✅ 如果想要 count,y 字段名无所谓(通常写 '★' 或任意占位符)
chart.options({
  encode: { x: 'dept', y: '★' },
  transform: [{ type: 'groupX', y: 'count' }],  // ✅ 统计每组数量
});

// ✅ 如果想要 sum(salary),encode.y 必须写 'salary'
chart.options({
  encode: { x: 'dept', y: 'salary' },
  transform: [{ type: 'groupX', y: 'sum' }],  // ✅ 统计每组 salary 总和
});
```

### 错误 2:与 binX 混淆——groupX 用于已有分类,binX 用于数值分桶
```javascript
// ❌ 对数值 x 用 groupX,每个唯一值都是一组,组太多
chart.options({
  encode: { x: 'age', y: '★' },
  transform: [{ type: 'groupX', y: 'count' }],  // ❌ age 有几十个唯一值
});

// ✅ 数值 x 应该用 binX(先分桶再统计)
chart.options({
  encode: { x: 'age', y: '★' },
  transform: [{ type: 'binX', y: 'count', thresholds: 10 }],  // ✅ 分 10 个桶
});
```

### 错误 3:在 density 图中错误使用 groupX 与 kde 结合
```javascript
// ❌ 错误示例:在 density 图中混用 groupX 和 kde
chart.options({
  type: 'density',
  data: {
    type: 'inline',
    value: irisData,
    transform: [
      { type: 'kde', field: 'y', groupBy: ['x'] },
      { type: 'groupX', y: 'mean' }  // ❌ kde 已经完成了分组和聚合,不需要再用 groupX
    ]
  },
  encode: { x: 'x', y: 'y', color: 'x' }
});

// ✅ 正确做法:只使用 kde 并通过 groupBy 指定分组字段
chart.options({
  type: 'density',
  data: {
    type: 'inline',
    value: irisData,
    transform: [
      { type: 'kde', field: 'y', groupBy: ['x'] }  // ✅ 仅使用 kde 变换
    ]
  },
  encode: { x: 'x', y: 'y', color: 'x', size: 'size' }
});
```

### 错误 4:在 density 图中错误配置 encode 映射字段
```javascript
// ❌ 错误示例:未正确使用 kde 输出的字段
chart.options({
  type: 'density',
  data: {
    type: 'inline',
    value: rawData,
    transform: [{
      type: 'kde',
      field: 'y',
      groupBy: ['x']
    }]
  },
  encode: {
    x: 'x',
    y: 'y',        // ❌ 应该使用 kde 输出的 y 字段(默认为 'y')
    color: 'x',
    size: 'size'   // ❌ 应该使用 kde 输出的 size 字段(默认为 'size')
  }
});

// ✅ 正确做法:确保 encode 中使用的字段与 kde 输出字段一致
chart.options({
  type: 'density',
  data: {
    type: 'inline',
    value: rawData,
    transform: [{
      type: 'kde',
      field: 'y',
      groupBy: ['x'],
      as: ['kde_y', 'kde_size']  // ✅ 自定义输出字段名
    }]
  },
  encode: {
    x: 'x',
    y: 'kde_y',      // ✅ 使用自定义的 y 字段名
    color: 'x',
    size: 'kde_size' // ✅ 使用自定义的 size 字段名
  }
});
```
references/transforms/g2-transform-groupy.md
---
id: "g2-transform-groupy"
title: "G2 GroupY Transform"
description: |
  按 Y 通道对数据进行分组聚合。与 GroupX 对称,
  用于按 Y 维度聚合数据的场景。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "分组"
  - "聚合"
  - "Y轴"
  - "统计"

related:
  - "g2-transform-groupx"
  - "g2-transform-groupcolor"
  - "g2-transform-group"

use_cases:
  - "按 Y 维度统计"
  - "水平条形图聚合"
  - "转置坐标系下的分组聚合"

anti_patterns:
  - "Y 通道为连续数值时分组效果不佳"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform"
---

## 核心概念

GroupY Transform 按 `y` 通道的值对数据进行分组,同时考虑 `color` 和 `series` 通道,然后对每组进行聚合计算。

**分组维度:**
- 主维度:`y` 通道
- 附加维度:`color`、`series`

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'interval',
  coordinate: { transform: [{ type: 'transpose' }] },  // 转置为水平条形图
  data: [
    { category: 'A', group: 'X', value: 10 },
    { category: 'A', group: 'Y', value: 20 },
    { category: 'B', group: 'X', value: 15 },
    { category: 'B', group: 'Y', value: 25 },
  ],
  encode: {
    x: 'value',
    y: 'category',
    color: 'group',
  },
  transform: [
    {
      type: 'groupY',
      x: 'sum',  // 对每组求和
    },
  ],
});

chart.render();
```

## 常用变体

### 计算平均值

```javascript
chart.options({
  type: 'interval',
  coordinate: { transform: [{ type: 'transpose' }] },
  data,
  encode: { x: 'value', y: 'category', color: 'group' },
  transform: [
    { type: 'groupY', x: 'mean' },
  ],
});
```

### 计数统计

```javascript
chart.options({
  type: 'interval',
  coordinate: { transform: [{ type: 'transpose' }] },
  data,
  encode: { x: 'value', y: 'category' },
  transform: [
    { type: 'groupY', x: 'count' },
  ],
});
```

### 多通道聚合

```javascript
chart.options({
  type: 'interval',
  coordinate: { transform: [{ type: 'transpose' }] },
  data,
  encode: { x: 'value', y: 'category', size: 'count' },
  transform: [
    {
      type: 'groupY',
      x: 'sum',
      size: 'count',
    },
  ],
});
```

## 完整类型参考

```typescript
interface GroupYTransform {
  type: 'groupY';
  x?: Reducer;
  // 其他通道也可以聚合
  [channel: string]: Reducer;
}

type Reducer = 'sum' | 'mean' | 'median' | 'max' | 'min' | 'count' | 'first' | 'last' | ((I: number[], V: any[]) => any);
```

## 与 GroupX 的对比

| 特性 | GroupX | GroupY |
|------|--------|--------|
| 分组维度 | x, color, series | y, color, series |
| 常用场景 | 竖向柱状图 | 水平条形图 |
| 聚合通道 | 通常是 y | 通常是 x |

## 常见错误与修正

### 错误 1:在非转置坐标系中使用

```javascript
// ⚠️ 注意:在普通坐标系中,GroupY 通常用于 Y 为分类轴的情况
// 如果 Y 是数值轴,分组可能没有意义

// ✅ 正确:转置坐标系 + GroupY
chart.options({
  type: 'interval',
  coordinate: { transform: [{ type: 'transpose' }] },
  data,
  encode: { x: 'value', y: 'category' },
  transform: [{ type: 'groupY', x: 'sum' }],
});
```
references/transforms/g2-transform-jitter.md
---
id: "g2-transform-jitter"
title: "G2 Jitter 抖动变换(散开重叠点)"
description: |
  jitter 变换为分类轴上的数据点添加随机偏移,避免相同类别下数据点完全重叠。
  jitter 同时抖动 X 和 Y,jitterX 只抖动 X(常用于条形图上的点),
  jitterY 只抖动 Y。常与 point mark 配合展示分类数据分布。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "jitter"
  - "抖动"
  - "点图"
  - "分布"
  - "重叠"
  - "beeswarm"
  - "transform"

related:
  - "g2-mark-point-scatter"
  - "g2-transform-dodgex"
  - "g2-transform-stacky"

use_cases:
  - "展示各类别下数据点的分布密度(比箱线图更详细)"
  - "类别轴上多个数据点防止重叠"
  - "与箱线图叠加,同时显示统计摘要和原始数据"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform/jitter"
---

## 最小可运行示例(jitter 防止分类散点图重叠)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'point',
  data: [
    { dept: '研发', salary: 18000 }, { dept: '研发', salary: 22000 },
    { dept: '研发', salary: 15000 }, { dept: '研发', salary: 19000 },
    { dept: '销售', salary: 12000 }, { dept: '销售', salary: 16000 },
    { dept: '销售', salary: 14000 }, { dept: '销售', salary: 11000 },
    { dept: '设计', salary: 17000 }, { dept: '设计', salary: 20000 },
  ],
  encode: {
    x: 'dept',     // 分类轴(会在此方向抖动)
    y: 'salary',
    color: 'dept',
  },
  transform: [{ type: 'jitter' }],   // 在 x 和 y 方向添加随机抖动
  style: { fillOpacity: 0.7, r: 4 },
});

chart.render();
```

## jitterX(仅水平抖动)

```javascript
// 适合竖向分类轴 — 仅在 x 方向展开,y 方向保持精确数值
chart.options({
  type: 'point',
  data,
  encode: { x: 'category', y: 'value', color: 'category' },
  transform: [
    {
      type: 'jitterX',
      padding: 0.1,    // 类别宽度比例(0~0.5),默认 0
      random: Math.random,  // 自定义随机函数(可用固定种子)
    },
  ],
});
```

## 与箱线图叠加

```javascript
chart.options({
  type: 'view',
  children: [
    // 箱线图(显示统计摘要)
    {
      type: 'boxplot',
      data,
      encode: { x: 'dept', y: 'salary' },
      style: { boxFill: 'transparent', lineWidth: 1.5 },
    },
    // 散点(显示原始数据分布)
    {
      type: 'point',
      data,
      encode: { x: 'dept', y: 'salary', color: 'dept' },
      transform: [{ type: 'jitter', padding: 0.1 }],
      style: { r: 3, fillOpacity: 0.6 },
    },
  ],
});
```

## 配置项

```javascript
transform: [
  {
    type: 'jitter',    // 或 'jitterX' / 'jitterY'
    padding: 0,        // 类别边界留白(0~0.5),默认 0
    paddingX: 0,       // 单独设置 X 留白(覆盖 padding)
    paddingY: 0,       // 单独设置 Y 留白(覆盖 padding)
    random: Math.random, // 随机函数,可替换为固定种子伪随机
  },
]
```

## 常见错误与修正

### 错误 1:在数值轴上使用 jitter——两个方向都是数值时效果混乱
```javascript
// ❌ 错误:x 和 y 都是数值时,jitter 会破坏精确的数值关系
chart.options({
  type: 'point',
  encode: { x: 'price', y: 'sales' },  // 都是数值轴
  transform: [{ type: 'jitter' }],      // ❌ 散点图本就不重叠,不需要
});

// ✅ jitter 应用于有分类轴的场景
chart.options({
  encode: { x: 'category', y: 'value' },  // x 是分类
  transform: [{ type: 'jitter' }],         // ✅
});
```

### 错误 2:point mark 数据量大时抖动效果不明显——padding 太小
```javascript
// ❌ 默认 padding: 0 时,所有点只在类别宽度极小范围内抖动
transform: [{ type: 'jitter' }]  // padding 默认 0,抖动范围很小

// ✅ 适当增大 padding
transform: [{ type: 'jitter', padding: 0.15 }]  // 类别宽度 15% 作为抖动范围
```
references/transforms/g2-transform-jitterx.md
---
id: "g2-transform-jitterx"
title: "G2 JitterX Transform"
description: |
  在 X 方向对数据进行抖动处理,避免点重叠。
  常用于散点图中分类数据点的分散显示。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "抖动"
  - "jitter"
  - "散点图"
  - "防重叠"
  - "X轴"

related:
  - "g2-transform-jitter"
  - "g2-transform-jittery"
  - "g2-mark-point-scatter"

use_cases:
  - "分类散点图中避免点重叠"
  - "展示分类数据的分布密度"
  - "一维数据分布可视化"

anti_patterns:
  - "连续数值数据不需要抖动"
  - "点数量过少时抖动效果不明显"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform"
---

## 核心概念

JitterX Transform 在 X 方向对数据点进行随机偏移,使重叠的点分散显示。这对于分类数据的散点图特别有用。

**工作原理:**
1. 根据 X 轴比例尺确定每个类别的范围
2. 在范围内随机偏移每个点的 X 位置
3. 通过 `padding` 控制偏移范围

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'point',
  data: [
    { category: 'A', value: 10 },
    { category: 'A', value: 12 },
    { category: 'A', value: 11 },
    { category: 'B', value: 20 },
    { category: 'B', value: 22 },
  ],
  encode: {
    x: 'category',
    y: 'value',
  },
  transform: [
    { type: 'jitterX' },
  ],
});

chart.render();
```

## 常用变体

### 控制抖动范围

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'category', y: 'value' },
  transform: [
    {
      type: 'jitterX',
      padding: 0.2,  // 抖动范围比例,默认 0
    },
  ],
});
```

### 自定义随机函数

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'category', y: 'value' },
  transform: [
    {
      type: 'jitterX',
      random: () => Math.random(),  // 默认 Math.random
    },
  ],
});
```

### 结合 JitterY 使用

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'category', y: 'category2' },
  transform: [
    { type: 'jitterX' },
    { type: 'jitterY' },
  ],
});
```

## 完整类型参考

```typescript
interface JitterXTransform {
  type: 'jitterX';
  padding?: number;      // 抖动范围的内边距,默认 0
  random?: () => number; // 随机数生成函数,默认 Math.random
}
```

## 与 Jitter/JitterY 的对比

| Transform | 抖动方向 | 常用场景 |
|-----------|---------|---------|
| jitter | X 和 Y | 二维分类数据 |
| jitterX | 仅 X | X 轴分类数据 |
| jitterY | 仅 Y | Y 轴分类数据 |

## 常见错误与修正

### 错误 1:对连续数据使用抖动

```javascript
// ❌ 不推荐:X 轴是连续数值时抖动可能造成误解
chart.options({
  type: 'point',
  data,
  encode: { x: 'continuousValue', y: 'value' },
  transform: [{ type: 'jitterX' }],
});

// ✅ 正确:X 轴是分类数据时使用
chart.options({
  type: 'point',
  data,
  encode: { x: 'category', y: 'value' },
  transform: [{ type: 'jitterX' }],
});
```

### 错误 2:padding 值过大

```javascript
// ❌ 错误:padding 过大会导致点溢出到相邻类别
transform: [{ type: 'jitterX', padding: 0.8 }]

// ✅ 正确:合理的 padding 值
transform: [{ type: 'jitterX', padding: 0.2 }]
```
references/transforms/g2-transform-jittery.md
---
id: "g2-transform-jittery"
title: "G2 JitterY Transform"
description: |
  在 Y 方向对数据进行抖动处理,避免点重叠。
  常用于散点图中分类数据点的分散显示。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "抖动"
  - "jitter"
  - "散点图"
  - "防重叠"
  - "Y轴"

related:
  - "g2-transform-jitter"
  - "g2-transform-jitterx"
  - "g2-mark-point-scatter"

use_cases:
  - "分类散点图中避免点重叠"
  - "水平方向分类数据的分布展示"
  - "转置坐标系下的抖动"

anti_patterns:
  - "连续数值数据不需要抖动"
  - "点数量过少时抖动效果不明显"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform"
---

## 核心概念

JitterY Transform 在 Y 方向对数据点进行随机偏移,使重叠的点分散显示。与 JitterX 对称,适用于 Y 轴为分类数据的情况。

**工作原理:**
1. 根据 Y 轴比例尺确定每个类别的范围
2. 在范围内随机偏移每个点的 Y 位置
3. 通过 `padding` 控制偏移范围

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'point',
  data: [
    { value: 10, category: 'A' },
    { value: 12, category: 'A' },
    { value: 11, category: 'A' },
    { value: 20, category: 'B' },
    { value: 22, category: 'B' },
  ],
  encode: {
    x: 'value',
    y: 'category',
  },
  transform: [
    { type: 'jitterY' },
  ],
});

chart.render();
```

## 常用变体

### 控制抖动范围

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'value', y: 'category' },
  transform: [
    {
      type: 'jitterY',
      padding: 0.2,  // 抖动范围比例
    },
  ],
});
```

### 结合 JitterX 使用

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'categoryX', y: 'categoryY' },
  transform: [
    { type: 'jitterX' },
    { type: 'jitterY' },
  ],
});
```

### 自定义随机函数

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'value', y: 'category' },
  transform: [
    {
      type: 'jitterY',
      random: () => Math.random(),
    },
  ],
});
```

## 完整类型参考

```typescript
interface JitterYTransform {
  type: 'jitterY';
  padding?: number;      // 抖动范围的内边距,默认 0
  random?: () => number; // 随机数生成函数,默认 Math.random
}
```

## 与 Jitter/JitterX 的对比

| Transform | 抖动方向 | 常用场景 |
|-----------|---------|---------|
| jitter | X 和 Y | 二维分类数据 |
| jitterX | 仅 X | X 轴分类数据 |
| jitterY | 仅 Y | Y 轴分类数据 |

## 常见错误与修正

### 错误 1:对连续数据使用抖动

```javascript
// ❌ 不推荐:Y 轴是连续数值时抖动可能造成误解
chart.options({
  type: 'point',
  data,
  encode: { x: 'value', y: 'continuousValue' },
  transform: [{ type: 'jitterY' }],
});

// ✅ 正确:Y 轴是分类数据时使用
chart.options({
  type: 'point',
  data,
  encode: { x: 'value', y: 'category' },
  transform: [{ type: 'jitterY' }],
});
```

### 错误 2:padding 值过大

```javascript
// ❌ 错误:padding 过大会导致点溢出到相邻类别
transform: [{ type: 'jitterY', padding: 0.8 }]

// ✅ 正确:合理的 padding 值
transform: [{ type: 'jitterY', padding: 0.2 }]
```
references/transforms/g2-transform-normalizey.md
---
id: "g2-transform-normalizey"
title: "G2 NormalizeY 归一化变换"
description: |
  NormalizeY 将每个 x 分组内的 y 值归一化到 [0, 1],
  通常跟在 stackY 之后使用,用于创建百分比堆叠图表,
  消除总量差异,聚焦占比分布。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "normalizeY"
  - "归一化"
  - "百分比"
  - "transform"
  - "百分比堆叠"
  - "占比"
  - "spec"

related:
  - "g2-mark-interval-normalized"
  - "g2-transform-stacky"

use_cases:
  - "创建百分比堆叠柱状图"
  - "创建百分比堆叠面积图"
  - "消除总量差异,聚焦占比"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-01"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform/normalize-y"
---

## 基本用法(必须配合 stackY)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [
    { type: 'stackY' },       // 第一步:堆叠
    { type: 'normalizeY' },   // 第二步:归一化(顺序不能颠倒!)
  ],
  axis: {
    y: { labelFormatter: (v) => `${(v * 100).toFixed(0)}%` },
  },
});

chart.render();
```

## 配置项

```javascript
transform: [
  { type: 'stackY' },
  {
    type: 'normalizeY',
    basis: 'max',    // 归一化基准:'max'(默认,每组最大值)| 'min' | 'first' | 'last' | 'mean' | 'median'
    series: 'y',     // 指定归一化的通道,默认 'y'
  },
],
```

## 百分比堆叠面积图

```javascript
chart.options({
  type: 'area',
  data,
  encode: { x: 'date', y: 'value', color: 'type' },
  transform: [
    { type: 'stackY' },
    { type: 'normalizeY' },
  ],
  axis: {
    y: { labelFormatter: (v) => `${(v * 100).toFixed(0)}%` },
  },
});
```

## Y 轴百分比格式化

normalizeY 后 y 值范围为 [0, 1],需手动格式化为百分比显示:

```javascript
axis: {
  y: { labelFormatter: (v) => `${(v * 100).toFixed(0)}%` },
}
```

## 常见错误与修正

### 错误 1:normalizeY 在 stackY 之前执行
```javascript
// ❌ 错误:先归一化再堆叠,得不到百分比堆叠效果
transform: [{ type: 'normalizeY' }, { type: 'stackY' }],

// ✅ 正确:先堆叠,再归一化
transform: [{ type: 'stackY' }, { type: 'normalizeY' }],
```

### 错误 2:缺少 stackY 直接使用 normalizeY
```javascript
// ❌ 错误:仅 normalizeY 不会产生百分比堆叠效果
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'normalizeY' }],
});

// ✅ 正确:stackY + normalizeY 配合
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }, { type: 'normalizeY' }],
});
```

### 错误 3:Y 轴未格式化为百分比
```javascript
// ❌ 问题:归一化后 y 轴显示 0.0 - 1.0,用户看不懂
chart.options({ transform: [{ type: 'stackY' }, { type: 'normalizeY' }] });

// ✅ 正确:添加百分比格式化
chart.options({
  transform: [{ type: 'stackY' }, { type: 'normalizeY' }],
  axis: { y: { labelFormatter: (v) => `${(v * 100).toFixed(0)}%` } },
});
```
references/transforms/g2-transform-pack.md
---
id: "g2-transform-pack"
title: "G2 Pack Transform"
description: |
  打包布局 Transform,将多个图形元素均匀排列避免重叠。
  常用于 Treemap、气泡图等需要自动布局的场景。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "打包"
  - "pack"
  - "布局"
  - "防重叠"
  - "网格"

related:
  - "g2-mark-pack"
  - "g2-mark-treemap"

use_cases:
  - "多个图形元素的自动排列"
  - "小多图网格布局"
  - "避免图形重叠"

anti_patterns:
  - "单个图形不需要打包"
  - "已有明确位置信息的数据"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform"
---

## 核心概念

Pack Transform 通过变换(translate + scale)将多个图形元素均匀排列,避免重叠。它会自动计算每个元素的位置和缩放比例。

**工作原理:**
1. 计算每个元素的边界框
2. 根据容器尺寸计算网格布局
3. 对每个元素应用 translate 和 scale 变换

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'pack',
   {
    nodes: [
      { name: 'A', value: 100 },
      { name: 'B', value: 80 },
      { name: 'C', value: 60 },
    ],
  },
  encode: {
    value: 'value',
    color: 'value',
  },
});

chart.render();
```

## 常用变体

### 作为 Transform 使用

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'category', y: 'value' },
  transform: [
    {
      type: 'pack',
      padding: 5,        // 元素间距
      direction: 'col',  // 排列方向: 'col' | 'row'
    },
  ],
});
```

### 自定义间距

```javascript
chart.options({
  type: 'pack',
  data,
  encode: { value: 'value', color: 'value' },
  transform: [
    {
      type: 'pack',
      padding: 10,  // 元素之间的间距
    },
  ],
});
```

### 按行排列

```javascript
chart.options({
  type: 'pack',
  data,
  encode: { value: 'value', color: 'value' },
  transform: [
    {
      type: 'pack',
      direction: 'row',  // 按行排列
    },
  ],
});
```

## 完整类型参考

```typescript
interface PackTransform {
  type: 'pack';
  padding?: number;       // 元素间距,默认 0
  direction?: 'col' | 'row';  // 排列方向,默认 'col'
}
```

## 与 Pack Mark 的关系

Pack Mark 内部使用 Pack Transform 进行布局:
- **Pack Mark**:用于创建圆形打包图(Circle Packing)
- **Pack Transform**:用于任意图形元素的网格排列

## 常见错误与修正

### 错误 1:padding 值过大

```javascript
// ❌ 错误:padding 过大会导致元素被过度压缩
transform: [{ type: 'pack', padding: 50 }]

// ✅ 正确:合理的 padding 值
transform: [{ type: 'pack', padding: 5 }]
```

### 错误 2:direction 参数错误

```javascript
// ❌ 错误
transform: [{ type: 'pack', direction: 'horizontal' }]

// ✅ 正确
transform: [{ type: 'pack', direction: 'row' }]
```
references/transforms/g2-transform-sample.md
---
id: "g2-transform-sample"
title: "G2 Sample 数据采样变换"
description: |
  sample 变换在数据超过阈值(默认 2000 条)时自动对数据降采样,
  避免大数据集渲染过慢或视觉过于密集。
  支持 first、last、min、max、median、lttb(最大三角形,保留趋势)等多种策略。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "sample"
  - "采样"
  - "大数据"
  - "性能优化"
  - "lttb"
  - "降采样"
  - "transform"

related:
  - "g2-mark-line-basic"
  - "g2-transform-filter"

use_cases:
  - "折线图数据超过 2000 条时保留视觉趋势的采样"
  - "实时数据流的性能优化"
  - "股票K线等大时间序列可视化"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform/sample"
---

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

// 模拟 5000 条时间序列数据
const data = Array.from({ length: 5000 }, (_, i) => ({
  time: new Date(2020, 0, 1 + Math.floor(i / 10)).toISOString(),
  value: Math.sin(i / 50) * 100 + Math.random() * 20,
}));

const chart = new Chart({ container: 'container', width: 800, height: 400 });

chart.options({
  type: 'line',
  data,
  encode: { x: 'time', y: 'value' },
  transform: [
    {
      type: 'sample',
      thresholds: 500,     // 超过 500 条才触发采样
      strategy: 'lttb',   // 最大三角形采样,最佳保留视觉趋势
    },
  ],
});

chart.render();
```

## 采样策略对比

```javascript
// lttb(推荐):最大三角形三桶算法,视觉保真度最高
transform: [{ type: 'sample', strategy: 'lttb', thresholds: 500 }]

// median:取每桶中位数,平滑但可能丢失极值
transform: [{ type: 'sample', strategy: 'median', thresholds: 1000 }]

// min/max:保留每桶最小/最大值,适合保留极值场景
transform: [{ type: 'sample', strategy: 'max', thresholds: 800 }]

// first/last:取每桶第一/最后一条,性能最好但精度最低
transform: [{ type: 'sample', strategy: 'first', thresholds: 2000 }]
```

## 多系列分组采样

```javascript
// groupBy 指定分组字段,每个系列独立采样
chart.options({
  type: 'line',
  data: multiSeriesData,
  encode: { x: 'time', y: 'value', color: 'series' },
  transform: [
    {
      type: 'sample',
      thresholds: 300,
      strategy: 'lttb',
      groupBy: ['series', 'color'],  // 按系列分组,每组独立降采样
    },
  ],
});
```

## 配置项

```javascript
transform: [
  {
    type: 'sample',
    strategy: 'median',   // 采样策略:'first'|'last'|'min'|'max'|'median'|'lttb'|function
                          // 默认 'median'
    thresholds: 2000,     // 触发采样的数据量阈值,默认 2000
    groupBy: ['series', 'color'],  // 分组字段,默认 ['series', 'color']
  },
]
```

## 常见错误与修正

### 错误 1:thresholds 设置太高——数据虽大但不触发采样
```javascript
// ❌ 10000 条数据,thresholds 是默认的 2000,但策略不对
transform: [{ type: 'sample' }]  // 默认 thresholds: 2000,strategy: 'median'
// ⚠️  对 10000 条数据只降到 2000 条,可能还是太多

// ✅ 根据渲染目标明确设置 thresholds
transform: [{ type: 'sample', thresholds: 300, strategy: 'lttb' }]
```

### 错误 2:对柱状图使用 sample——破坏完整分类
```javascript
// ❌ 柱状图采样后,某些分类会消失,视觉上有断层
chart.options({
  type: 'interval',
  encode: { x: 'category', y: 'value' },
  transform: [{ type: 'sample' }],  // ❌ 柱状图通常不需要采样
});

// ✅ sample 主要用于折线图等连续数据
chart.options({
  type: 'line',
  encode: { x: 'time', y: 'value' },
  transform: [{ type: 'sample', strategy: 'lttb' }],  // ✅
});
```
references/transforms/g2-transform-select.md
---
id: "g2-transform-select"
title: "G2 Select / SelectX / SelectY 筛选变换"
description: |
  select 系列变换从分组数据中筛选出特定的数据行用于标注。
  selectX 按 x 通道分组后筛选(常用于折线图末端标签),
  selectY 按 y 通道分组后筛选。
  selector 支持 'first'、'last'、'min'、'max' 等预设值或自定义函数。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "select"
  - "selectX"
  - "selectY"
  - "筛选"
  - "末端标签"
  - "极值标注"
  - "transform"

related:
  - "g2-mark-line-basic"
  - "g2-mark-text"
  - "g2-comp-annotation"

use_cases:
  - "在折线图末端显示最新数据标签"
  - "标注每条线的最大值或最小值"
  - "在特定 x 位置放置注释标签"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform/select"
---

## 最小可运行示例(折线图末端标签)

```javascript
import { Chart } from '@antv/g2';

const data = [
  { month: 'Jan', type: 'A', value: 83 },
  { month: 'Feb', type: 'A', value: 90 },
  { month: 'Mar', type: 'A', value: 76 },
  { month: 'Jan', type: 'B', value: 50 },
  { month: 'Feb', type: 'B', value: 65 },
  { month: 'Mar', type: 'B', value: 72 },
];

const chart = new Chart({ container: 'container', width: 640, height: 400 });

// 主折线图
chart.options({
  type: 'view',
  children: [
    {
      type: 'line',
       data,
      encode: { x: 'month', y: 'value', color: 'type' },
    },
    // 末端标签:text mark + selectX(取每条线的最后一个点)
    {
      type: 'text',
       data,
      encode: { x: 'month', y: 'value', color: 'type', text: 'type' },
      transform: [
        {
          type: 'selectX',
          selector: 'last',   // 取每组(每条线)x 最大的点
        },
      ],
      style: { textAnchor: 'start', dx: 6 },
    },
  ],
});

chart.render();
```

## 标注最大值

```javascript
// 在折线图最高点添加标注
{
  type: 'point',
  data,
  encode: { x: 'date', y: 'value', color: 'type' },
  transform: [
    {
      type: 'selectY',
      selector: 'max',   // 每组取 y 值最大的点
    },
  ],
  style: { r: 6, lineWidth: 2 },
  labels: [{ text: (d) => `最高: ${d.value}`, position: 'top' }],
}
```

## selector 速查

```javascript
// 取最后一个点(常用于末端标签)
transform: [{ type: 'selectX', selector: 'last' }]

// 取第一个点
transform: [{ type: 'selectX', selector: 'first' }]

// 取 y 值最大的点
transform: [{ type: 'selectY', selector: 'max' }]

// 取 y 值最小的点
transform: [{ type: 'selectY', selector: 'min' }]

// 自定义:取第 N 个点
transform: [{ type: 'selectX', selector: (data) => data[Math.floor(data.length / 2)] }]
```

## 常见错误与修正

### 错误:select 用在 line mark 自身——应用在独立的 text/point mark
```javascript
// ❌ 在 line mark 上用 selectX,整条线只剩一个点
chart.options({
  type: 'line',
  data,
  encode: { x: 'month', y: 'value' },
  transform: [{ type: 'selectX', selector: 'last' }],  // ❌ 会把折线变成单点
});

// ✅ select 用在额外的 text 或 point mark,与 line 并列
chart.options({
  type: 'view',
  children: [
    { type: 'line', data, encode: { x: 'month', y: 'value' } },
    {
      type: 'text',
      data,
      encode: { x: 'month', y: 'value', text: 'value' },
      transform: [{ type: 'selectX', selector: 'last' }],  // ✅ 独立 mark
    },
  ],
});
```
references/transforms/g2-transform-selectx.md
---
id: "g2-transform-selectx"
title: "G2 SelectX Transform"
description: |
  按 X 通道选择数据子集。用于筛选每个 X 类别的特定数据点,
  如最大值、最小值、首个、末个等。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "选择"
  - "筛选"
  - "X轴"
  - "极值"

related:
  - "g2-transform-select"
  - "g2-transform-selecty"

use_cases:
  - "只显示每个类别的最大值"
  - "筛选每个 X 分组的首个/末个数据点"
  - "突出显示极值点"

anti_patterns:
  - "需要保留所有数据时不应使用"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform"
---

## 核心概念

SelectX Transform 按 X 通道分组,然后从每组中选择特定的数据点。选择器支持:
- `max`:Y 值最大的点
- `min`:Y 值最小的点
- `first`:首个数据点
- `last`:末个数据点
- 自定义选择函数

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'point',
  data: [
    { category: 'A', value: 10 },
    { category: 'A', value: 25 },
    { category: 'A', value: 15 },
    { category: 'B', value: 20 },
    { category: 'B', value: 35 },
    { category: 'B', value: 30 },
  ],
  encode: {
    x: 'category',
    y: 'value',
  },
  transform: [
    {
      type: 'selectX',
      selector: 'max',  // 只保留每个类别的最大值点
    },
  ],
});

chart.render();
```

## 常用变体

### 选择最小值

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'category', y: 'value' },
  transform: [
    { type: 'selectX', selector: 'min' },
  ],
});
```

### 选择首个/末个

```javascript
// 选择每个类别的第一个数据点
chart.options({
  type: 'point',
  data,
  encode: { x: 'category', y: 'value' },
  transform: [
    { type: 'selectX', selector: 'first' },
  ],
});

// 选择每个类别的最后一个数据点
chart.options({
  type: 'point',
  data,
  encode: { x: 'category', y: 'value' },
  transform: [
    { type: 'selectX', selector: 'last' },
  ],
});
```

### 自定义选择器

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'category', y: 'value' },
  transform: [
    {
      type: 'selectX',
      selector: (I, Y) => {
        // I: 组内索引数组
        // Y: Y 通道的值数组
        // 返回选中的索引
        return I.reduce((maxIdx, i) => Y[i] > Y[maxIdx] ? i : maxIdx, I[0]);
      },
    },
  ],
});
```

## 完整类型参考

```typescript
interface SelectXTransform {
  type: 'selectX';
  selector: 'max' | 'min' | 'first' | 'last' | ((I: number[], Y: any[]) => number);
}
```

## 与 Select/SelectY 的对比

| Transform | 分组维度 | 常用场景 |
|-----------|---------|---------|
| select | 按指定通道 | 通用选择 |
| selectX | 按 X 通道 | X 轴分类筛选 |
| selectY | 按 Y 通道 | Y 轴分类筛选 |

## 常见错误与修正

### 错误 1:selector 拼写错误

```javascript
// ❌ 错误
transform: [{ type: 'selectX', selector: 'maximum' }]

// ✅ 正确
transform: [{ type: 'selectX', selector: 'max' }]
```

### 错误 2:自定义选择器返回值错误

```javascript
// ❌ 错误:返回了值而非索引
selector: (I, Y) => Math.max(...I.map(i => Y[i]))

// ✅ 正确:返回索引
selector: (I, Y) => I.reduce((maxIdx, i) => Y[i] > Y[maxIdx] ? i : maxIdx, I[0])
```
references/transforms/g2-transform-selecty.md
---
id: "g2-transform-selecty"
title: "G2 SelectY Transform"
description: |
  按 Y 通道选择数据子集。用于筛选每个 Y 类别的特定数据点,
  如最大值、最小值、首个、末个等。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "选择"
  - "筛选"
  - "Y轴"
  - "极值"

related:
  - "g2-transform-select"
  - "g2-transform-selectx"

use_cases:
  - "水平条形图中筛选极值"
  - "转置坐标系下的数据选择"
  - "按 Y 分类筛选数据点"

anti_patterns:
  - "需要保留所有数据时不应使用"

difficulty: "beginner"
completeness: "full"
created: "2025-03-26"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform"
---

## 核心概念

SelectY Transform 按 Y 通道分组,然后从每组中选择特定的数据点。选择器支持:
- `max`:X 值最大的点
- `min`:X 值最小的点
- `first`:首个数据点
- `last`:末个数据点
- 自定义选择函数

## 最小可运行示例

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  width: 640,
  height: 480,
});

chart.options({
  type: 'point',
  data: [
    { value: 10, category: 'A' },
    { value: 25, category: 'A' },
    { value: 15, category: 'A' },
    { value: 20, category: 'B' },
    { value: 35, category: 'B' },
    { value: 30, category: 'B' },
  ],
  encode: {
    x: 'value',
    y: 'category',
  },
  transform: [
    {
      type: 'selectY',
      selector: 'max',  // 只保留每个 Y 类别的最大值点
    },
  ],
});

chart.render();
```

## 常用变体

### 选择最小值

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'value', y: 'category' },
  transform: [
    { type: 'selectY', selector: 'min' },
  ],
});
```

### 选择首个/末个

```javascript
// 选择每个 Y 类别的第一个数据点
chart.options({
  type: 'point',
  data,
  encode: { x: 'value', y: 'category' },
  transform: [
    { type: 'selectY', selector: 'first' },
  ],
});

// 选择每个 Y 类别的最后一个数据点
chart.options({
  type: 'point',
  data,
  encode: { x: 'value', y: 'category' },
  transform: [
    { type: 'selectY', selector: 'last' },
  ],
});
```

### 自定义选择器

```javascript
chart.options({
  type: 'point',
  data,
  encode: { x: 'value', y: 'category' },
  transform: [
    {
      type: 'selectY',
      selector: (I, X) => {
        // I: 组内索引数组
        // X: X 通道的值数组
        // 返回选中的索引
        return I.reduce((maxIdx, i) => X[i] > X[maxIdx] ? i : maxIdx, I[0]);
      },
    },
  ],
});
```

## 完整类型参考

```typescript
interface SelectYTransform {
  type: 'selectY';
  selector: 'max' | 'min' | 'first' | 'last' | ((I: number[], X: any[]) => number);
}
```

## 与 Select/SelectX 的对比

| Transform | 分组维度 | 常用场景 |
|-----------|---------|---------|
| select | 按指定通道 | 通用选择 |
| selectX | 按 X 通道 | X 轴分类筛选 |
| selectY | 按 Y 通道 | Y 轴分类筛选 |

## 常见错误与修正

### 错误 1:selector 拼写错误

```javascript
// ❌ 错误
transform: [{ type: 'selectY', selector: 'minimum' }]

// ✅ 正确
transform: [{ type: 'selectY', selector: 'min' }]
```

### 错误 2:自定义选择器返回值错误

```javascript
// ❌ 错误:返回了值而非索引
selector: (I, X) => Math.max(...I.map(i => X[i]))

// ✅ 正确:返回索引
selector: (I, X) => I.reduce((maxIdx, i) => X[i] > X[maxIdx] ? i : maxIdx, I[0])
```
references/transforms/g2-transform-sort-color.md
---
id: "g2-transform-sort-color"
title: "G2 SortColor 按颜色分组排序变换"
description: |
  sortColor 是 G2 v5 中的排序 Transform,对颜色(color)通道的比例尺域(domain)进行排序。
  与 sortX(对 x 轴排序)类似,但排序作用于 color 通道的类别顺序。
  常用于图例排序、堆叠图层顺序调整等场景。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "sortColor"
  - "颜色排序"
  - "图例顺序"
  - "transform"
  - "sort"
  - "color"

related:
  - "g2-transform-sortx"
  - "g2-transform-sorty"
  - "g2-mark-interval-stacked"

use_cases:
  - "按数值大小对图例顺序排序"
  - "堆叠柱状图的颜色分层顺序调整"
  - "折线图系列颜色分配顺序控制"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform/sort-color"
---

## 核心概念

`sortColor` 通过计算各 color 分组的聚合值(默认 y 通道的均值),
对 color scale 的 domain 进行重新排序,影响:
- 图例的显示顺序
- 堆叠图中各层的叠放顺序
- 颜色分配顺序

## 基本用法

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data: [
    { month: 'Jan', type: 'A', value: 50 },
    { month: 'Jan', type: 'B', value: 80 },
    { month: 'Jan', type: 'C', value: 30 },
    { month: 'Feb', type: 'A', value: 60 },
    { month: 'Feb', type: 'B', value: 70 },
    { month: 'Feb', type: 'C', value: 40 },
  ],
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [
    { type: 'stackY' },
    { type: 'sortColor', channel: 'y', order: 'descending' },  // 按 y 均值降序排列颜色
  ],
});

chart.render();
```

## 配置项

```javascript
chart.options({
  transform: [
    {
      type: 'sortColor',
      channel: 'y',           // 用于计算排序依据的通道,默认 'y'
      order: 'ascending',     // 'ascending'(升序)| 'descending'(降序),默认 'ascending'
      reducer: 'mean',        // 聚合方式:'mean' | 'sum' | 'max' | 'min' | 函数,默认 'mean'
      reverse: false,         // 是否反转排序结果
    },
  ],
});
```

## 与 sortX 对比

```javascript
// sortX:对 x 轴类别顺序排序(影响柱子/点的 x 轴位置顺序)
transform: [{ type: 'sortX', channel: 'y', order: 'descending' }]

// sortColor:对颜色分组(图例/堆叠层)排序(不影响 x 轴顺序)
transform: [{ type: 'sortColor', channel: 'y', order: 'descending' }]
```

## 常见错误与修正

### 错误:期望改变柱子位置但用了 sortColor
```javascript
// ❌ 错误:sortColor 只改变颜色/图例顺序,不改变 x 轴柱子位置
chart.options({
  encode: { x: 'type', y: 'value' },
  transform: [{ type: 'sortColor', channel: 'y', order: 'descending' }],
  // x 轴柱子顺序没有变化!
});

// ✅ 要改变 x 轴柱子位置,使用 sortX
chart.options({
  encode: { x: 'type', y: 'value' },
  transform: [{ type: 'sortX', channel: 'y', order: 'descending' }],  // ✅
});
```
references/transforms/g2-transform-sortx.md
---
id: "g2-transform-sortx"
title: "G2 SortX 排序变换"
description: |
  SortX 对 x 轴的分类数据按指定字段或函数进行排序,
  常用于将柱状图按数值从大到小排列,创建排名图表。
  多系列按分组总量排序用内置 reducer: 'sum',无需自定义函数。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "sortX"
  - "排序"
  - "排名"
  - "transform"
  - "柱状图排序"
  - "spec"

related:
  - "g2-mark-interval-basic"
  - "g2-transform-dodgex"

use_cases:
  - "创建按值降序排列的柱状图(排名图)"
  - "对分类轴自定义排序顺序"
  - "多系列堆叠图按分组总量排序"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-04-02"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform/sort-x"
---

## 最小可运行示例(按值降序排列)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data: [
    { city: '北京', gdp: 3.6 },
    { city: '上海', gdp: 4.3 },
    { city: '广州', gdp: 2.8 },
    { city: '深圳', gdp: 3.2 },
    { city: '杭州', gdp: 1.8 },
    { city: '成都', gdp: 2.0 },
  ],
  encode: { x: 'city', y: 'gdp' },
  transform: [
    {
      type: 'sortX',
      by: 'y',           // 按 y 通道值排序
      reverse: true,     // true = 降序(最大值在左)
    },
  ],
  coordinate: { transform: [{ type: 'transpose' }] },   // 转为水平排名图
});

chart.render();
```

## 配置项

```javascript
transform: [
  {
    type: 'sortX',
    by: 'y',          // 排序依据的 channel 名('y' | 'x' | 'color' 等)
    reducer: 'max',   // 分组聚合方式(见下方说明),默认 'max'
    reverse: true,    // 是否反转顺序(默认 false = 升序)
    slice: 10,        // 只保留前 N 个(用于 Top N 图表)
  },
],
```

**`reducer` 内置值**(多系列/堆叠场景下对分组内的多个 y 值做聚合):

| 值 | 含义 |
|----|------|
| `'max'` | 取分组最大值(默认) |
| `'min'` | 取分组最小值 |
| `'sum'` | 取分组总和 ← **多系列按总量排序用这个** |
| `'mean'` | 取分组平均值 |
| `'median'` | 取分组中位数 |
| `'first'` | 取分组第一个值 |
| `'last'` | 取分组最后一个值 |

## Top N 排名图(只展示前 10)

```javascript
chart.options({
  type: 'interval',
  data: fullData,
  encode: { x: 'name', y: 'score' },
  transform: [
    {
      type: 'sortX',
      by: 'y',
      reverse: true,
      slice: 10,   // 只取前 10 名
    },
  ],
  coordinate: { transform: [{ type: 'transpose' }] },
  axis: { x: { title: null } },
});
```

## 自定义排序(按指定字段)

```javascript
// 数据中有 rank 字段,按 rank 排序
chart.options({
  type: 'interval',
  data,
  encode: { x: 'name', y: 'value' },
  transform: [
    { type: 'sortX', by: 'rank', reverse: false },
  ],
});
```

## 按分组总量排序(多系列堆叠图)

多系列图中每个 x 分组有多条数据,用内置 `reducer: 'sum'` 按各分组 y 值之和排序,**不需要自定义函数**:

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'city', y: 'value', color: 'type' },
  transform: [
    { type: 'stackY' },
    {
      type: 'sortX',
      by: 'y',
      reducer: 'sum',   // ✅ 内置求和,按各城市所有系列总和排序
      reverse: true,
    },
  ],
});
```

## 径向坐标系中的排序注意事项

在使用 `radial` 径向坐标系时,SortX 的行为与常规笛卡尔坐标系一致,但需要注意以下几点:

1. **x 和 y 通道映射**:在径向坐标系中,x 通常映射为角度(即圆周方向),y 映射为半径(即距离中心的距离)。因此,`by: 'y'` 实际上是对半径进行排序。
2. **排序必要性**:由于径向图表存在“半径反馈效应”,即外圈即使数值较小也可能看起来比内圈数值大的条目长,因此**强烈建议在使用径向坐标系时对数据进行排序**,以确保视觉准确性。
3. **排序方向控制**:`reverse: true` 会使数据按降序排列,即数值最大的项靠近最外圈;`reverse: false` 则相反。

```javascript
// ✅ 正确:在径向坐标系中按 y 值排序并渲染
chart.options({
  type: 'interval',
  data: [
    { movie: '电影A', rating: 9.2, genre: '科幻' },
    { movie: '电影B', rating: 8.7, genre: '动作' },
    { movie: '电影C', rating: 8.5, genre: '科幻' },
    { movie: '电影D', rating: 7.9, genre: '喜剧' },
    { movie: '电影E', rating: 7.2, genre: '动作' },
    { movie: '电影F', rating: 6.8, genre: '喜剧' }
  ].sort((a, b) => b.rating - a.rating), // 数据预排序
  coordinate: { type: 'radial', innerRadius: 0.35 },
  encode: {
    x: 'movie',
    y: 'rating',
    color: 'rating',
  },
  scale: {
    y: { domain: [0, 10] },
  },
  style: {
    radius: 5,
    fillOpacity: 0.95,
  },
  labels: [{
    text: 'rating',
    position: 'inside',
    style: { fontWeight: 'bold', fill: 'white' },
  }],
  axis: {
    x: { label: { autoRotate: true, style: { fontSize: 10 } } },
    y: { label: true, grid: false, style: { fontSize: 9 } },
  },
  interaction: [{ type: 'elementHighlightByColor' }],
});
```

## 常见错误与修正

### 错误:用自定义函数代替内置 reducer,且误用不存在的 `{ value }` 参数

`sortX` 没有 `by: ({ value }) => ...` 这种 API。`by` 只接受 **channel 名字符串**,聚合逻辑通过 `reducer` 控制。自定义 `reducer` 函数的签名是 `(GI, V) => number`(`GI` = 该分组的行索引数组,`V` = 整列数值数组),而不是接收数据对象数组。

```javascript
// ❌ 错误:by 不接受函数,({ value }) 参数不存在
transform: [
  {
    type: 'sortX',
    by: ({ value }) => d3.sum(value, (d) => d.sales),   // ❌ by 只能是字符串
    reverse: true,
  },
],

// ❌ 同样错误:即使不用 d3,函数形式也不对
transform: [
  {
    type: 'sortX',
    by: ({ value }) => value.reduce((sum, d) => sum + d.value, 0),  // ❌ by 不支持函数
    reverse: true,
  },
],

// ✅ 正确:按分组总和排序用内置 reducer: 'sum'
transform: [
  {
    type: 'sortX',
    by: 'y',
    reducer: 'sum',   // ✅ 内置聚合,无需自定义函数
    reverse: true,
  },
],
```

### 错误:在任何回调中使用未导入的 `d3`

G2 内部使用 d3,但 `d3` 对象不会暴露到用户代码作用域。调用 `d3.sum()`、`d3.max()` 等会抛出 `ReferenceError: d3 is not defined`。如确需自定义逻辑,用原生 JS 替代:

```javascript
// d3.sum(arr, d => d.v)  →  arr.reduce((s, d) => s + d.v, 0)
// d3.max(arr, d => d.v)  →  Math.max(...arr.map(d => d.v))
// d3.min(arr, d => d.v)  →  Math.min(...arr.map(d => d.v))
// d3.mean(arr, d => d.v) →  arr.reduce((s, d) => s + d.v, 0) / arr.length
```

### 错误:在径向坐标系中错误使用 x/y 映射导致排序无效

在径向坐标系中,如果将本应作为排序依据的字段错误地映射到 x 通道,而将角度映射到 y 通道,则 `sortX` 将无法达到预期效果。正确的做法是将排序依据字段映射到 y 通道,并确保数据已按该字段排序。

```javascript
// ❌ 错误:在径向坐标系中错误地将 rating 映射到 x 通道
chart.options({
  type: 'interval',
  data: [
    { movie: '电影A', rating: 9.2, genre: '科幻' },
    { movie: '电影B', rating: 8.7, genre: '动作' },
    // ...
  ],
  coordinate: { type: 'radial', innerRadius: 0.2 },
  encode: {
    x: 'rating',       // ❌ 错误:rating 应该映射到 y 通道
    y: 'movie',        // ❌ 错误:movie 应该映射到 x 通道
    color: 'rating',
  },
  transform: [
    {
      type: 'sortX',
      by: 'rating',    // ❌ 错误:by 应该是 'y'
      reverse: false,
    },
  ],
});

// ✅ 正确:rating 映射到 y 通道,movie 映射到 x 通道,并预排序
chart.options({
  type: 'interval',
  data: [
    { movie: '电影A', rating: 9.2, genre: '科幻' },
    { movie: '电影B', rating: 8.7, genre: '动作' },
    // ...
  ].sort((a, b) => b.rating - a.rating),
  coordinate: { type: 'radial', innerRadius: 0.35 },
  encode: {
    x: 'movie',        // ✅ 正确:movie 映射到 x 通道(角度)
    y: 'rating',       // ✅ 正确:rating 映射到 y 通道(半径)
    color: 'rating',
  },
});
```
references/transforms/g2-transform-sorty.md
---
id: "g2-transform-sorty"
title: "G2 SortY 按 Y 值排序变换"
description: |
  sortY 在每个 x 分组内按 y 值对数据记录排序,常用于堆叠图中控制各分类的堆叠顺序,
  确保较大的值在底部或顶部。sortX 按 x 通道值对全局数据排序,
  sortColor 则按颜色通道值排序。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "sortY"
  - "sortX"
  - "排序"
  - "堆叠顺序"
  - "transform"

related:
  - "g2-transform-sortx"
  - "g2-transform-stacky"
  - "g2-mark-interval-stacked"

use_cases:
  - "堆叠柱状图中控制各分类的堆叠顺序(大值在底)"
  - "确保视觉上更稳定的堆叠布局"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform/sort"
---

## 最小可运行示例(堆叠柱状图排序)

```javascript
import { Chart } from '@antv/g2';

const data = [
  { month: 'Jan', type: 'A', value: 100 },
  { month: 'Jan', type: 'B', value: 200 },
  { month: 'Jan', type: 'C', value: 50 },
  { month: 'Feb', type: 'A', value: 120 },
  { month: 'Feb', type: 'B', value: 80 },
  { month: 'Feb', type: 'C', value: 180 },
];

const chart = new Chart({ container: 'container', width: 640, height: 400 });

chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [
    { type: 'sortY', reverse: false },  // 每个 x 分组内按 y 值升序排列
    { type: 'stackY' },                 // 再堆叠(大值在顶部)
  ],
});

chart.render();
```

## sortX(全局按 x 值排序)

```javascript
// 条形图按数值降序排列(最大值在顶)
chart.options({
  type: 'interval',
  data: rankingData,
  encode: { x: 'name', y: 'value' },
  transform: [
    { type: 'sortX', by: 'y', reverse: true },  // 按 y 值降序
  ],
  coordinate: { transform: [{ type: 'transpose' }] },
});
```

## 配置项

```javascript
// sortY:在每个 x 分组内排序
transform: [
  {
    type: 'sortY',
    reverse: false,   // false = 升序(小值先),true = 降序(大值先),默认 false
    by: 'y',          // 排序依据通道,默认 'y'
  },
]

// sortX:全局按 x 通道值排序
transform: [
  {
    type: 'sortX',
    by: 'y',          // 排序依据:'x'(按 x 值)或 'y'(按 y 值大小)
    reverse: true,    // true = 降序,默认 false
  },
]
```

## 常见错误与修正

### 错误:sortY 在 stackY 之后执行——堆叠后 y 值已变化,排序基准错误
```javascript
// ❌ 错误顺序:stackY 之后的 y 值是累积值,sortY 基于累积值排序
transform: [
  { type: 'stackY' },  // ❌ 先堆叠
  { type: 'sortY' },   // ❌ 后排序,此时 y 已是累积值
]

// ✅ 正确:先排序后堆叠
transform: [
  { type: 'sortY' },   // ✅ 先按原始 y 值排序
  { type: 'stackY' },  // ✅ 再堆叠(堆叠顺序按排序结果)
]
```
references/transforms/g2-transform-stack-enter.md
---
id: "g2-transform-stack-enter"
title: "G2 StackEnter 入场动画堆叠变换"
description: |
  stackEnter 是 G2 v5 中用于分组入场动画的 Transform,
  将同一分组内的元素按序错开入场时间(enterDelay),
  实现"一组一组依次出现"的入场动画效果。
  常用于柱状图、折线图的分组逐步入场展示。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "stackEnter"
  - "入场动画"
  - "enterDelay"
  - "分组动画"
  - "transform"
  - "animation"

related:
  - "g2-animation-intro"
  - "g2-transform-stacky"
  - "g2-mark-interval-grouped"

use_cases:
  - "柱状图各分组逐批入场(X 分组依次出现)"
  - "折线图系列逐条依次绘制"
  - "数据讲述场景中按节奏逐步呈现数据"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform/stack-enter"
---

## 核心概念

`stackEnter` 为每条数据分配 `enterDelay` 值:
- 将数据按 `groupBy` 通道(默认 `['x']`)分组
- 同一组内的元素共享相同的入场延迟
- 不同组之间依次叠加延迟时间

每组的延迟 = 前面所有组的 `enterDuration` 之和。

## 基本用法(柱状图分组入场)

```javascript
import { Chart } from '@antv/g2';

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data: [
    { month: 'Jan', value: 83 },
    { month: 'Feb', value: 60 },
    { month: 'Mar', value: 95 },
    { month: 'Apr', value: 72 },
    { month: 'May', value: 110 },
  ],
  encode: { x: 'month', y: 'value', color: 'month' },
  transform: [
    {
      type: 'stackEnter',
      groupBy: ['x'],          // 按 x 分组(每个月份一批)
      orderBy: null,           // 不额外排序
      duration: 300,           // 每组动画持续时长(毫秒),默认使用 enterDuration
    },
  ],
  animate: {
    enter: {
      type: 'scaleInY',        // 每组柱子从下往上生长
      duration: 300,
    },
  },
});

chart.render();
```

## 折线图多系列依次入场

```javascript
chart.options({
  type: 'line',
  data: multiSeriesData,
  encode: { x: 'date', y: 'value', color: 'series' },
  transform: [
    {
      type: 'stackEnter',
      groupBy: ['color'],    // 按颜色(系列)分组,每条线依次入场
      duration: 800,
    },
  ],
  animate: {
    enter: {
      type: 'pathIn',        // 折线从左向右绘制
      duration: 800,
    },
  },
});
```

## 配置项

```javascript
chart.options({
  transform: [
    {
      type: 'stackEnter',
      groupBy: ['x'],          // 分组通道,默认 ['x']
                               // 可以是单个字符串或数组:['x', 'color']
      orderBy: null,           // 组间排序依据:null | 'x' | 函数
      reverse: false,          // 是否反转组的顺序
      duration: undefined,     // 每组入场时长(毫秒),不设则使用 animate.enter.duration
    },
  ],
});
```

## 常见错误与修正

### 错误:忘记配置 animate.enter
```javascript
// ❌ 有 stackEnter 但没有 animate.enter,看不到动画效果
chart.options({
  transform: [{ type: 'stackEnter', groupBy: ['x'] }],
  // 缺少 animate 配置!
});

// ✅ 必须配合 animate.enter 使用
chart.options({
  transform: [{ type: 'stackEnter', groupBy: ['x'], duration: 400 }],
  animate: {
    enter: {
      type: 'scaleInY',    // 选择合适的入场动画类型
      duration: 400,
    },
  },
});
```

### 错误:duration 与 animate.enter.duration 不一致导致动画不连贯
```javascript
// ❌ stackEnter duration 与 animate.enter.duration 不匹配
chart.options({
  transform: [{ type: 'stackEnter', duration: 500 }],  // 500ms 每组
  animate: { enter: { type: 'scaleInY', duration: 200 } },  // ❌ 200ms 动画(组还没完成就切换)

// ✅ 保持一致
chart.options({
  transform: [{ type: 'stackEnter', duration: 400 }],
  animate: { enter: { type: 'scaleInY', duration: 400 } },   // ✅ 一致
});
```
references/transforms/g2-transform-stacky.md
---
id: "g2-transform-stacky"
title: "G2 StackY 堆叠变换"
description: |
  StackY 是 G2 v5 中用于实现数据堆叠的 Mark Transform,
  将同一 x 位置的多个数值依次叠加,生成 y0/y1 区间。
  配置在 transform 数组中(与 data、encode 同级),是堆叠柱状图、堆叠面积图、饼图的核心依赖。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "StackY"
  - "堆叠"
  - "stackY"
  - "mark transform"
  - "堆叠柱状图"
  - "堆叠面积图"
  - "spec"

related:
  - "g2-mark-interval-stacked"
  - "g2-mark-area-stacked"
  - "g2-transform-normalizey"
  - "g2-transform-dodgex"
  - "g2-data-fold"

use_cases:
  - "创建堆叠柱状图"
  - "创建堆叠面积图"
  - "创建饼图(配合 theta 坐标系)"

difficulty: "beginner"
completeness: "full"
created: "2024-01-01"
updated: "2025-03-26"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform/stack-y"
---

## 核心概念

**StackY 是标记变换(Mark Transform),不是数据变换(Data Transform)**

- 标记变换配置在 `transform` 数组中(与 `data`、`encode` 同级)
- 在标记渲染过程中执行,修改视觉通道值
- **不要**放在 `data.transform` 中

StackY 对每个 x 分组内的数据进行累积计算:
- 输入:`y` 值(各子类别的原始数值)
- 输出:`y0`(底部位置)和 `y1`(顶部位置),驱动柱体/面积的起止位置

```javascript
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],  // ✅ Mark Transform:与 data/encode 同级
});
```

## 基本用法(Spec 模式)

```javascript
import { Chart } from '@antv/g2';

// 堆叠柱状图
const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data: [
    { month: 'Jan', type: 'A', value: 100 },
    { month: 'Jan', type: 'B', value: 200 },
    { month: 'Feb', type: 'A', value: 120 },
    { month: 'Feb', type: 'B', value: 180 },
  ],
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],   // 声明堆叠变换
});

chart.render();
```

## 配置项

```javascript
chart.options({
  type: 'interval',
  data: [...],
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [
    {
      type: 'stackY',
      orderBy: null,     // null | 'value' | 'sum' | 'series' — 控制堆叠顺序
      reverse: false,    // 是否反转堆叠顺序
      y: 'y',            // 输入 y 通道名(默认 'y')
      y1: 'y1',          // 输出底部通道名(默认 'y1')
    },
  ],
});
```

## 与 normalizeY 组合(百分比堆叠)

```javascript
// transform 数组支持多个变换链式执行
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [
    { type: 'stackY' },       // 先堆叠
    { type: 'normalizeY' },   // 再归一化到 [0, 1]
  ],
  axis: {
    y: { labelFormatter: (v) => `${(v * 100).toFixed(0)}%` },
  },
});
```

## 用于饼图(配合 theta 坐标系)

```javascript
chart.options({
  type: 'interval',
  data: [
    { type: '分类一', value: 40 },
    { type: '分类二', value: 30 },
    { type: '分类三', value: 30 },
  ],
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],           // 将数值转为角度区间
  coordinate: { type: 'theta', outerRadius: 0.8 },
});
```

## 用于堆叠面积图

```javascript
chart.options({
  type: 'area',
  data: [...],
  encode: { x: 'date', y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],
});
```

## 常见错误与修正

### 错误 1:stackY 放在 data.transform 中

```javascript
// ❌ 错误:stackY 是 Mark Transform,不能放在 data.transform 中
chart.options({
  type: 'interval',
   {
    type: 'inline',
    value: data,
    transform: [{ type: 'stackY' }],  // ❌ 错误位置
  },
});

// ✅ 正确:stackY 放在 mark 的 transform 中(与 data/encode 同级)
chart.options({
  type: 'interval',
  data,
  encode: { x: 'month', y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],  // ✅ 正确
});
```

### 错误 2:transform 写成对象而非数组
```javascript
// ❌ 错误:transform 必须是数组
chart.options({ transform: { type: 'stackY' } });

// ✅ 正确
chart.options({ transform: [{ type: 'stackY' }] });
```

### 错误 3:饼图忘记 stackY
```javascript
// ❌ 错误:theta 坐标系中没有 stackY,所有扇形角度从 0 开始,完全重叠
chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'type' },
  coordinate: { type: 'theta' },
  // 缺少 transform!
});

// ✅ 正确
chart.options({
  type: 'interval',
  data,
  encode: { y: 'value', color: 'type' },
  transform: [{ type: 'stackY' }],   // 必须!
  coordinate: { type: 'theta' },
});
```

### 错误 4:多系列数据不堆叠直接显示导致重叠
```javascript
// ❌ 错误:多类型 interval 没有 stackY 或 dodgeX,柱体在同位置堆叠
chart.options({
  type: 'interval',
  data: multiTypeData,
  encode: { x: 'month', y: 'value', color: 'type' },
  // 既没有 stackY(堆叠)也没有 dodgeX(分组)
});

// ✅ 堆叠展示
chart.options({ transform: [{ type: 'stackY' }], ... });

// ✅ 分组展示
chart.options({ transform: [{ type: 'dodgeX' }], ... });
```
references/transforms/g2-transform-symmetryy.md
---
id: "g2-transform-symmetryy"
title: "G2 SymmetryY 对称变换(蝴蝶图 / 人口金字塔)"
description: |
  symmetryY 对 y 通道应用偏移使数据关于 y=0 轴对称,
  典型应用是人口金字塔(两个方向的柱状图对称展示)和蝴蝶图。
  通常与 transpose(转置)坐标系配合,实现水平对称条形图。

library: "g2"
version: "5.x"
category: "transforms"
tags:
  - "symmetryY"
  - "对称"
  - "人口金字塔"
  - "蝴蝶图"
  - "population pyramid"
  - "transform"

related:
  - "g2-transform-stacky"
  - "g2-coord-transpose"
  - "g2-mark-interval-stacked"

use_cases:
  - "人口金字塔(男女年龄分布对称展示)"
  - "A/B 对比的蝴蝶图"
  - "正负值关于中心对称的图表"

difficulty: "intermediate"
completeness: "full"
created: "2025-03-24"
updated: "2025-03-24"
author: "antv-team"
source_url: "https://g2.antv.antgroup.com/manual/core/transform/symmetry-y"
---

## 最小可运行示例(人口金字塔)

```javascript
import { Chart } from '@antv/g2';

const data = [
  { age: '0-9',   gender: '男', value: 8500 },
  { age: '10-19', gender: '男', value: 9200 },
  { age: '20-29', gender: '男', value: 10300 },
  { age: '30-39', gender: '男', value: 9800 },
  { age: '40-49', gender: '男', value: 8900 },
  { age: '0-9',   gender: '女', value: 8100 },
  { age: '10-19', gender: '女', value: 8800 },
  { age: '20-29', gender: '女', value: 9900 },
  { age: '30-39', gender: '女', value: 9500 },
  { age: '40-49', gender: '女', value: 8700 },
];

const chart = new Chart({ container: 'container', width: 640, height: 480 });

chart.options({
  type: 'interval',
  data,
  encode: {
    x: 'age',
    y: 'value',
    color: 'gender',
  },
  transform: [
    { type: 'stackY' },     // 先堆叠
    { type: 'symmetryY' },  // 再对称(以 y=0 为中轴)
  ],
  coordinate: { transform: [{ type: 'transpose' }] },  // 转置为水平条形
  axis: {
    y: {
      labelFormatter: (v) => Math.abs(v).toLocaleString(),  // 负值显示为正数
    },
  },
});

chart.render();
```

## 配置项

```javascript
transform: [
  {
    type: 'symmetryY',
    groupBy: 'x',   // 按哪个通道分组,默认 'x'
  },
]
```

## 蝴蝶图(两个类别左右对称)

```javascript
chart.options({
  type: 'interval',
  data: abTestData,
  encode: { x: 'metric', y: 'value', color: 'group' },
  transform: [
    { type: 'stackY' },
    { type: 'symmetryY' },
  ],
  coordinate: { transform: [{ type: 'transpose' }] },
  style: { fillOpacity: 0.85 },
});
```

## 常见错误与修正

### 错误:symmetryY 前忘记 stackY——分组数据不会对称
```javascript
// ❌ 没有 stackY,两个 gender 的柱体重叠在同侧,对称失效
transform: [
  { type: 'symmetryY' },  // ❌ 少了前置 stackY
]

// ✅ 必须先 stackY 再 symmetryY
transform: [
  { type: 'stackY' },     // ✅ 先堆叠(分组数据叠在一起)
  { type: 'symmetryY' },  // ✅ 再对称(两组各自偏移到两侧)
]
```