diff --git a/README.md b/README.md index f361215dc3..ec7a8014a9 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ G2Plot v3 covers 36+ common chart types. PRs for more types are welcome: | Trend | Comparison | Proportion | Distribution | Relationship/Other | |---|---|---|---|---| | line | column | pie | scatter | dual-axes | -| area | bar | | heatmap | funnel | +| area | bar | progress | heatmap | funnel | | | | | | radar | diff --git a/README.zh-CN.md b/README.zh-CN.md index 3f4db8a370..1a48dec637 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -45,7 +45,7 @@ G2Plot v3 覆盖 36+ 个常用图表类型,更多类型,欢迎提交 PR: | 趋势 | 对比 | 占比 | 分布 | 关系/其他 | |---|---|---|---|---| | line 折线图 | column 柱状图 | pie 饼图 | scatter 散点图 | dual-axes 双轴图 | -| area 面积图 | bar 条形图 | | heatmap 热力图 | funnel 漏斗图 | +| area 面积图 | bar 条形图 | progress 进度条 | heatmap 热力图 | funnel 漏斗图 | | | | | | radar 雷达图 | diff --git a/charts/progress.md b/charts/progress.md new file mode 100644 index 0000000000..8466bf21d6 --- /dev/null +++ b/charts/progress.md @@ -0,0 +1,180 @@ +# Progress 进度条 + +> 组件用途:展示单一指标相对目标的完成进度,突出「已完成 / 剩余」的比例关系。 + +## 何时使用 + +- 完成度对比目标:季度目标完成率、KPI 达成度、任务进度 +- 资源消耗比例:存储已用 / 总容量、预算消耗 +- 少量指标(1~6 条)横向对比进度,且每条都有明确的目标上限 +- 仪表盘、任务详情页里替代裸数字,让人一眼看出「还差多少」 + +## 何时不用 + +- 多类别构成占比(各部分拼成整体,但不是「完成 / 剩余」)→ 用 pie 饼图 +- 没有目标值的类别数值对比 → 用 bar 条形图 +- 指标随时间变化 → 用 line 折线图 +- 主要诉求是精确比较多个数值,进度只是附属信息 → 用 column 柱状图 +- 二维交叉的密度分布 → 用 heatmap 热力图 + +## 数据要求 + +- 长表数据,每行一个进度项,见 [数据模式](../references/data-patterns.md) +- `x` 字段:进度项名称(字符串);单条进度也要给语义化标签(如 `'季度目标完成率'`) +- `y` 字段:当前值(`number`),与 `target` 同单位;**不要**写成 `'72%'` 字符串 +- `target` 字段:目标值 / 上限(`number`),用于画背景轨道;不需要轨道时改用 `scale.y.domain` 固定上限 +- 百分比不要在数据里拼成字符串,标签用 `text` 回调格式化 + +## 基础实现(G2 v5) + +G2 v5 **没有** `type: 'progress'` mark:进度条 = `interval` + 转置坐标系,背景轨道用第二个 `interval` 叠在底层。 + +```ts +import { Chart } from '@antv/g2'; + +const data = [ + { metric: '季度目标完成率', completed: 72, target: 100 }, +]; + +const chart = new Chart({ + container: 'container', + autoFit: true, +}); + +chart.options({ + type: 'view', + data, + coordinate: { transform: [{ type: 'transpose' }] }, + scale: { y: { domain: [0, 100] } }, + axis: { x: false, y: false }, + legend: false, + children: [ + { + type: 'interval', + encode: { x: 'metric', y: 'target', size: 28 }, + style: { fill: '#EEF2F7', radius: 14 }, + tooltip: false, + }, + { + type: 'interval', + encode: { x: 'metric', y: 'completed', size: 28 }, + style: { fill: '#5B8FF9', radius: 14 }, + labels: [ + { + text: (d: { completed: number }) => `${d.completed}%`, + position: 'right', + style: { fontSize: 14, fontWeight: 600, fill: '#1d1d1d' }, + }, + ], + }, + ], +}); + +chart.render(); +``` + +关键点: + +- `coordinate: { transform: [{ type: 'transpose' }] }` 把竖向柱转成横向条 +- `scale: { y: { domain: [0, 100] } }` 固定值域,轨道与进度条长度才可比;不设会按数据自适应,72% 也可能撑满整条 +- `encode: { size: 28 }` 控制条高(px,转置后即厚度);`style.radius` 取条高一半得到胶囊形 +- 轨道与进度条必须共享同一个 `x` 字段和 y 域(放在同一 `type: 'view'` 下即可自动共享 scale) +- 单条进度关闭坐标轴(`axis: { x: false, y: false }`) + +## 常见变体 + +| 变体 | 关键改动 | 完整案例 | +|---|---|---| +| 多指标进度对比 | 多条数据 + `encode.size` 调细,保留 x 轴展示名称 | [multi-progress.ts](../examples/progress/multi-progress.ts) | +| 按达标状态着色 | `encode.color` 映射状态字段,`scale.color.domain/range` 固定颜色顺序 | [multi-progress.ts](../examples/progress/multi-progress.ts) | +| 环形进度 | `transform: [{ type: 'stackY' }]` + `coordinate: { type: 'theta', innerRadius }` | [ring-progress.ts](../examples/progress/ring-progress.ts) | + +极简版(不需要背景轨道,靠固定值域表达剩余量): + +```ts-snippet +chart.options({ + type: 'interval', + data: [{ metric: '任务完成度', completed: 72 }], + encode: { x: 'metric', y: 'completed', size: 16 }, + coordinate: { transform: [{ type: 'transpose' }] }, + scale: { y: { domain: [0, 100] } }, + style: { fill: '#5B8FF9', radius: 8 }, + axis: false, + legend: false, +}); +``` + +分段进度(已完成 / 进行中 / 未开始,同一 x 上堆叠): + +```ts-snippet +chart.options({ + type: 'interval', + data: [ + { stage: '上线进度', status: '已完成', value: 60 }, + { stage: '上线进度', status: '进行中', value: 25 }, + { stage: '上线进度', status: '未开始', value: 15 }, + ], + encode: { x: 'stage', y: 'value', color: 'status' }, + transform: [{ type: 'stackY' }], + coordinate: { transform: [{ type: 'transpose' }] }, + scale: { y: { domain: [0, 100] } }, + style: { radius: 8 }, + axis: { x: false, y: false }, + legend: { color: { position: 'bottom' } }, +}); +``` + +## 样式自定义 + +| 定制项 | 写法 | 说明 | +|---|---|---| +| 条高 | `encode: { size: 16 }` | px;转置后即进度条厚度,多条目建议 12~20 | +| 圆角 | `style: { radius: 8 }` | 取条高一半即为胶囊形 | +| 进度色 | 单色 `style: { fill: '#5B8FF9' }`;按状态 `encode: { color: 'status' }` + `scale.color.range` | 状态色需显式给 `scale.color.domain` 固定顺序 | +| 轨道色 | 轨道 interval 的 `style: { fill: '#EEF2F7' }` | 明显浅于进度色,暗色主题可换 `'#2A2A2A'` | +| 百分比标签 | `labels: [{ text: (d) => d.completed + '%', position: 'right' }]` | 转置后 `'right'` 即条尾外侧 | +| 关闭坐标轴 / 图例 | `axis: false`、`legend: false` | 单条进度常用 | +| 内置主题 | `theme: 'classicDark'` | 暗色大屏一行切换 | + +tooltip 与标题(轨道 interval 设 `tooltip: false`,避免两条重叠时提示重复): + +```ts-snippet +title: { + title: '季度目标完成情况', + subtitle: '数据截至 2026-09-30', + align: 'left', +}, +tooltip: { + title: 'metric', + items: [ + { channel: 'y', name: '已完成', valueFormatter: (v) => `${v}%` }, + ], +}, +``` + +## 易错点(v4 → v5) + +| ❌ 错误(幻觉高发) | ✅ 正确 | +|---|---| +| `type: 'progress'`(G2 v5 无此 mark,白屏/报错) | `type: 'interval'` + `coordinate: { transform: [{ type: 'transpose' }] }` | +| `coordinate: { type: 'transpose' }` | 转置是坐标系变换:`coordinate: { transform: [{ type: 'transpose' }] }` | +| `new Progress('container', { percent: 0.72 })`(G2Plot v2) | `new Chart({ container })` + `chart.options(spec)` | +| `import { Progress } from '@antv/g2plot'` | `import { Chart } from '@antv/g2'` | +| 把 `percent` / `progress` 当配置项传 | 数据字段 `completed: 72` + `encode: { y: 'completed' }` | +| 不设 `scale.y.domain` 就画进度条 | 固定 `domain: [0, target]`,否则当前值自动撑满,看不出剩余量 | +| 用 `style: { width }` / `style: { height }` 调条高 | 条高用 `encode: { size }`;`width` / `height` 是图表尺寸 | +| 百分比存成 `'72%'` 字符串 | 存 `number`,`labels.text` 回调拼 `%` | +| 轨道和进度条各自用不同数据域 / 不同 x 字段 | 放在同一 `type: 'view'` 下共享 scale,长度才对得齐 | +| `chart.source(data)`(v4) | `chart.options({ data })` | + +更多对照见 [v4-to-v5-migration.md](../references/v4-to-v5-migration.md)。 + +## 完整案例 + +每个案例的元信息(场景与关键配置),代码均通过 `npm run validate` 校验: + +| 案例 | 场景 | 关键配置 | +|---|---|---| +| [basic.ts](../examples/progress/basic.ts) | 单指标完成度(季度目标完成率),最简起步 | `type: 'view'` + 双 interval、`transpose`、`scale.y.domain`、`labels` | +| [multi-progress.ts](../examples/progress/multi-progress.ts) | 多业务线完成率横向对比,按达标状态着色 | `encode.size`、`encode.color` + `scale.color.domain`、x 轴保留 | +| [ring-progress.ts](../examples/progress/ring-progress.ts) | 大屏 / 移动端单指标环形进度 | `stackY`、`coordinate: { type: 'theta', innerRadius: 0.72 }`、`labels` | diff --git a/evals/cases/progress.jsonl b/evals/cases/progress.jsonl new file mode 100644 index 0000000000..65240cf44b --- /dev/null +++ b/evals/cases/progress.jsonl @@ -0,0 +1,16 @@ +{"id": "progress-basic", "prompt": "任务详情页要展示本季度的目标完成率 72%,用一条横向进度条表示", "groundTruth": "使用 type: 'interval',coordinate: { transform: [{ type: 'transpose' }] } 转成横向;数据含 completed 与 target(100)字段;scale.y.domain 固定为 [0, 100];背景轨道用第二条 interval 叠加在同一 type: 'view' 下,共享 scale 保证长度对齐"} +{"id": "progress-kpi-card", "prompt": "大屏 KPI 卡片里放一个完成率 86% 的进度条,不要坐标轴和图例", "groundTruth": "interval + transpose 进度条;axis: false、legend: false;labels 显示 86%;scale.y.domain 固定 [0, 100],条高用 encode.size 控制"} +{"id": "progress-multi", "prompt": "对比华东、华南、华北、西南四个大区今年的目标完成率,画在一张图上", "groundTruth": "多条 interval + transpose(横向条形)或多组进度条;x 映射大区字段,y 映射完成率,scale.y.domain 为 [0, 100];每条的条高用 encode.size 控制,不需要 color 分组"} +{"id": "progress-threshold-color", "prompt": "完成率低于 60% 的进度条标红,其余用绿色,方便一眼看出风险", "groundTruth": "数据中增加状态字段(如 status: '预警' / '正常'),encode.color 映射该字段,并用 scale.color.domain 和 range 固定颜色顺序;或对 interval 用 style.fill 回调按值返回颜色"} +{"id": "progress-ring", "prompt": "用环形进度展示完成率 68%,中间是空的", "groundTruth": "type: 'interval' + transform: [{ type: 'stackY' }] + coordinate: { type: 'theta', innerRadius: 0.72 };数据两段「已完成 68 / 未完成 32」,未完成段用浅灰充当轨道;axis: false、legend: false"} +{"id": "progress-storage", "prompt": "展示存储使用情况:已用 320GB,总容量 1024GB", "groundTruth": "interval + transpose 进度条;y 为已用量 320,scale.y.domain 固定 [0, 1024](用总量作上限,而不是写死 100);轨道 interval 用浅灰填充;tooltip 中同时给出已用与剩余(1024-320)"} +{"id": "progress-label-decimal", "prompt": "进度条尾部显示 72.5%,保留一位小数", "groundTruth": "labels 中 text 使用回调如 (d) => `${d.completed.toFixed(1)}%`,position 为 'right'(转置后即条尾外侧);数据里 completed 存 number 72.5,不要存字符串"} +{"id": "progress-tooltip", "prompt": "鼠标悬停时显示已完成 72%、剩余 28%", "groundTruth": "tooltip.items 配置 channel: 'y' 与 valueFormatter,输出「已完成 / 剩余」两个百分比;背景轨道 interval 设置 tooltip: false 避免两条重叠时重复提示"} +{"id": "progress-dark", "prompt": "暗色大屏用的进度条,配色要适配深色背景", "groundTruth": "设置 theme: 'classicDark' 或 'dark';轨道 interval 的 fill 换成深灰(如 '#2A2A2A'),进度条用高亮色;关闭坐标轴与图例"} +{"id": "progress-title", "prompt": "进度图要有标题「季度目标完成情况」和副标题「数据截至 2026-09」,左对齐", "groundTruth": "title 必须是对象,包含 title 与 subtitle 字段,align: 'left';不能把 title 直接写成字符串"} +{"id": "progress-brand-color", "prompt": "品牌色是 #FF6B35,画一个胶囊圆角的进度条", "groundTruth": "进度 interval 的 style.fill 设为 '#FF6B35';style.radius 取 encode.size 的一半(如 size: 24 / radius: 12);轨道用浅色 fill 并保持同样的 radius"} +{"id": "progress-thin", "prompt": "进度条细一点,高度 6px 就够了", "groundTruth": "用 encode.size: 6 控制条高(不是 style.width / style.height);style.radius 取 3;scale.y.domain 仍固定 [0, 100]"} +{"id": "progress-segmented", "prompt": "一个进度条拆成已完成、进行中、未开始三段,用不同颜色区分", "groundTruth": "同一 x 下三段 interval 堆叠:encode.color 映射状态字段 + transform: [{ type: 'stackY' }] + coordinate: { transform: [{ type: 'transpose' }] },scale.y.domain 为 [0, 100],legend.color.position 设为 'bottom'"} +{"id": "progress-vertical", "prompt": "画一个竖向进度条,从下往上增长", "groundTruth": "interval 不转置坐标系(去掉 transpose),encode x 为进度项名称、y 为当前值,scale.y.domain 固定 [0, target];用 encode.size 控制条宽;axis 与 legend 关闭"} +{"id": "progress-type-antipattern", "prompt": "直接用 type: 'progress' 画一个进度条", "groundTruth": "G2 v5 没有 progress mark(写 type: 'progress' 会报错/白屏);必须用 type: 'interval' + coordinate: { transform: [{ type: 'transpose' }] } 实现,并用 scale.y.domain 固定 [0, 100]"} +{"id": "progress-percent-string", "prompt": "我数据里的 percent 字段是 '72%' 这种字符串,直接拿来画进度条", "groundTruth": "先把字符串转成 number(parseFloat 或去掉 %),再用 encode: { y: 'completed' } 映射;百分比符号只在 labels.text 回调里拼接,数值字段必须保持 number 类型"} diff --git a/examples/progress/basic.ts b/examples/progress/basic.ts new file mode 100644 index 0000000000..1e179becd2 --- /dev/null +++ b/examples/progress/basic.ts @@ -0,0 +1,46 @@ +import { Chart } from '@antv/g2'; + +/** + * 基础进度条 + * 场景:单一指标相对目标的完成度(如季度目标完成率),需要一眼看出已完成与剩余部分 + * 要点:interval + transpose 横向排布;view 叠加灰色轨道与彩色进度;scale.y 固定 [0, target] 保证长度可比 + */ +const data = [ + { metric: '季度目标完成率', completed: 72, target: 100 }, +]; + +const chart = new Chart({ + container: 'container', + autoFit: true, +}); + +chart.options({ + type: 'view', + data, + coordinate: { transform: [{ type: 'transpose' }] }, + scale: { y: { domain: [0, 100] } }, + axis: { x: false, y: false }, + legend: false, + children: [ + { + type: 'interval', + encode: { x: 'metric', y: 'target', size: 28 }, + style: { fill: '#EEF2F7', radius: 14 }, + tooltip: false, + }, + { + type: 'interval', + encode: { x: 'metric', y: 'completed', size: 28 }, + style: { fill: '#5B8FF9', radius: 14 }, + labels: [ + { + text: (d: { completed: number }) => `${d.completed}%`, + position: 'right', + style: { fontSize: 14, fontWeight: 600, fill: '#1d1d1d' }, + }, + ], + }, + ], +}); + +chart.render(); diff --git a/examples/progress/multi-progress.ts b/examples/progress/multi-progress.ts new file mode 100644 index 0000000000..c5cae5849f --- /dev/null +++ b/examples/progress/multi-progress.ts @@ -0,0 +1,52 @@ +import { Chart } from '@antv/g2'; + +/** + * 多指标进度对比 + * 场景:看板中横向对比多条业务线的目标完成率,并按是否达标着色 + * 要点:interval + transpose 多条并列;view 叠加轨道;color 通道映射达标状态,需显式给 scale.color.domain 固定颜色顺序 + */ +const data = [ + { team: '华东大区', completed: 92, target: 100, status: '达成' }, + { team: '华南大区', completed: 78, target: 100, status: '达成' }, + { team: '华北大区', completed: 64, target: 100, status: '预警' }, + { team: '西南大区', completed: 45, target: 100, status: '预警' }, +]; + +const chart = new Chart({ + container: 'container', + autoFit: true, +}); + +chart.options({ + type: 'view', + data, + coordinate: { transform: [{ type: 'transpose' }] }, + scale: { + y: { domain: [0, 100] }, + color: { domain: ['达成', '预警'], range: ['#52C41A', '#FAAD14'] }, + }, + axis: { x: { title: false }, y: false }, + legend: false, + children: [ + { + type: 'interval', + encode: { x: 'team', y: 'target', size: 16 }, + style: { fill: '#EEF2F7', radius: 8 }, + tooltip: false, + }, + { + type: 'interval', + encode: { x: 'team', y: 'completed', size: 16, color: 'status' }, + style: { radius: 8 }, + labels: [ + { + text: (d: { completed: number }) => `${d.completed}%`, + position: 'right', + style: { fontSize: 12, fill: '#595959' }, + }, + ], + }, + ], +}); + +chart.render(); diff --git a/examples/progress/ring-progress.ts b/examples/progress/ring-progress.ts new file mode 100644 index 0000000000..33d989d38b --- /dev/null +++ b/examples/progress/ring-progress.ts @@ -0,0 +1,38 @@ +import { Chart } from '@antv/g2'; + +/** + * 环形进度(Donut Progress) + * 场景:大屏 / 移动端单指标完成度,环形比条状更省空间且视觉更聚焦 + * 要点:interval + stackY + theta 坐标系;已完成/未完成两段堆叠,未完成段用浅灰充当轨道;innerRadius 挖空成环 + */ +const data = [ + { status: '已完成', value: 68 }, + { status: '未完成', value: 32 }, +]; + +const chart = new Chart({ + container: 'container', + autoFit: true, +}); + +chart.options({ + type: 'interval', + data, + encode: { y: 'value', color: 'status' }, + transform: [{ type: 'stackY' }], + coordinate: { type: 'theta', innerRadius: 0.72 }, + scale: { color: { domain: ['已完成', '未完成'], range: ['#5B8FF9', '#EEF2F7'] } }, + axis: false, + legend: false, + labels: [ + { + // 只标注「已完成」一段,避免白字落在浅灰轨道上看不清 + text: (d: { status: string; value: number }) => + d.status === '已完成' ? `${d.value}%` : '', + position: 'inside', + style: { fontSize: 14, fontWeight: 600, fill: '#fff' }, + }, + ], +}); + +chart.render(); diff --git a/skills/g2plot-v3/SKILL.md b/skills/g2plot-v3/SKILL.md index ca96e17bb3..b0326072c5 100644 --- a/skills/g2plot-v3/SKILL.md +++ b/skills/g2plot-v3/SKILL.md @@ -32,6 +32,7 @@ description: G2Plot v3 可视化组件库,提示词即组件。当用户需要 | 多维能力对比 | 多维度数值 | radar 雷达图 | [charts/radar.md](../../charts/radar.md) | | 密度/频率分布 | 两个类别 + 数值 | heatmap 热力图 | [charts/heatmap.md](../../charts/heatmap.md) | | 流程转化 | 阶段 + 数值 | funnel 漏斗图 | [charts/funnel.md](../../charts/funnel.md) | +| 完成度 / 目标进度 | 单指标 0~100% 或少量进度项 | progress 进度条 | [charts/progress.md](../../charts/progress.md) | 选型拿不准时,读对应 `charts/.md` 的「何时使用 / 何时不用」小节。