diff --git a/README.md b/README.md index f361215dc3..8b5e46bf78 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ G2Plot v3 covers 36+ common chart types. PRs for more types are welcome: |---|---|---|---|---| | line | column | pie | scatter | dual-axes | | area | bar | | heatmap | funnel | -| | | | | radar | +| | | | beeswarm | radar | ## 🏗️ How It Works diff --git a/charts/beeswarm.md b/charts/beeswarm.md new file mode 100644 index 0000000000..18a5aa347a --- /dev/null +++ b/charts/beeswarm.md @@ -0,0 +1,162 @@ +# Beeswarm 熊蜂图 + +> 组件用途:展示分类类别下数值的分布情况,通过点的位置和密度直观展示数据分布,适合小到中等规模的分类数据集。 + +## 何时使用 + +- **分类分布展示**:查看不同类别下数值的分布情况,如不同实验组的基因表达量、不同处理方案的效果分布 +- **小规模数据可视化**:数据点 ≤ 200 条,希望展示每个数据点的具体位置和分布密度 +- **替代箱线图**:当需要展示具体数据点而非五数概括时,或当数据量适中不想丢失个体信息时 +- **多类别比较**:比较多个类别的数值分布差异 + +## 何时不用 + +- 大规模数据(> 500 条):点会重叠过多,视觉效果差 → 使用 violin 小提琴图 或 histogram 直方图 +- 连续二维空间的点密度分布:用 `type: 'heatmap'` 渐变热力图 +- 时间序列趋势:用 `type: 'line'` 折线图 +- 类别间纯数值大小对比:用 `type: 'column'` 柱状图 + +## 数据要求 + +- **长表数据**,每行一个数据点,包含类别字段和数值字段 +- `category` 字段:类别标识符(字符串),表示数据属于哪个分组 +- `value` 字段:数值(number),表示要展示的测量值 +- **建议**:每个类别建议 5-50 个数据点,点过多时建议抽样或使用抖动策略 + +```ts +const data = [ + { category: 'Control', value: 12.5 }, + { category: 'Control', value: 15.3 }, + { category: 'Control', value: 11.8 }, + { category: 'Treatment', value: 22.1 }, + { category: 'Treatment', value: 19.7 }, + { category: 'Treatment', value: 23.4 }, +]; +``` + +## 基础实现(G2 v5) + +Beeswarm 图通过 `type: 'point'` 标记展示个体数据点,类别通过 x 轴编码,数值通过 y 轴编码。点在类别内部通过轻微抖动(x jitter)防止重叠。 + +```ts +import { Chart } from '@antv/g2'; + +const data = [ + { category: 'Control', value: 12.5 }, + { category: 'Control', value: 15.3 }, + { category: 'Control', value: 11.8 }, + { category: 'Treatment', value: 22.1 }, + { category: 'Treatment', value: 19.7 }, + { category: 'Treatment', value: 23.4 }, + { category: 'Treatment', value: 21.0 }, + { category: 'Treatment', value: 24.5 }, +]; + +const chart = new Chart({ + container: 'container', + autoFit: true, +}); + +chart.options({ + type: 'point', + data, + encode: { + x: 'category', + y: 'value', + }, + // 点的样式自定义 + style: { + fill: '#5B8FF9', + stroke: '#1890FF', + lineWidth: 2, + }, + // 坐标轴配置 + axis: { + x: { title: '实验组' }, + y: { title: '数值', min: 0 }, + }, + // 图例配置 + legend: false, +}); + +chart.render(); +``` + +## 常见变体 + +| 变体 | 关键改动 | 完整案例 | +|---|---|---| +| 带数据标签 | `labels: [{ text: 'value', position: 'top' }]` | [with-label.ts](../examples/beeswarm/with-label.ts) | +| 多系列分组 | `encode.color: 'group'`,通过颜色分组不同实验 | [multi-group.ts](../examples/beeswarm/multi-group.ts) | +| 自定义点大小 | `encode.size: 'value'`,根据数值大小变点大小 | [size-variable.ts](../examples/beeswarm/size-variable.ts) | +| 水平展示 | `coordinate: { type: 'rect' }` 或交换 x/y 字段 | [horizontal.ts](../examples/beeswarm/horizontal.ts) | + +### 带数据标签核心差异 + +```ts-snippet +chart.options({ + type: 'point', + data, + encode: { x: 'category', y: 'value' }, + labels: [{ text: 'value', position: 'top', style: { fontSize: 10 } }], +}); +``` + +### 多系列分组核心差异(数据需含 `group` 字段) + +```ts-snippet +chart.options({ + type: 'point', + data, + encode: { x: 'category', y: 'value', color: 'group' }, +}); +``` + +## 样式自定义 + +常用视觉定制项(以下写法均经 `@antv/g2` ^5 类型校验),完整示例见 [with-label.ts](../examples/beeswarm/with-label.ts): + +| 定制项 | 写法 | 说明 | +|---|---|---| +| 点的颜色 | `style: { fill: '#5B8FF9' }` | 固定点颜色 | +| 点的颜色 scales | `scale: { color: { range: ['#5B8FF9', '#5AD8A6'] } }` | 多系列按序着色 | +| 点的大小 | `style: { size: 6 }` | 固定点大小 | +| 点的大小 scales | `encode.size: 'value'` | 根据数值大小变点大小 | +| 透明度 | `style: { opacity: 0.7 }` | 整体透明度 | +| 坐标轴标题 | `axis: { x: { title: '类别' } }` | x/y 轴标题 | +| 刻度标签字体 | `axis.x.labelFontSize: 10` | x 轴刻度字体大小 | +| 关闭坐标轴 | `axis: { x: false }` | 关闭 x 轴 | +| 关闭图例 | `legend: false` | 关闭图例 | + +### 图表标题与组件文字样式 + +`title: { title: '...', subtitle: '...', align: 'left' }`,一行切换整体配色。暗色场景用 `theme: 'classicDark'`。 + +### 内置主题 + +`theme: 'light' | 'dark' | 'classic' | 'classicDark' | 'academy'`,一行切换整体配色。 + +## 易错点(v4 → v5) + +| ❌ 错误(幻觉高发) | ✅ 正确 | +|---|---| +| `.position('category*value')` | `.encode('x', 'category').encode('y', 'value')` | +| `import { BeeSwarm } from '@antv/g2plot'` | `import { Chart } from '@antv/g2'` + `type: 'point'` | +| `new BeeSwarm('container', {...})`(不存在的组件) | `new Chart({ container })` + `chart.options({ type: 'point', ... })` | +| `size: 'value'`(未编码) | `encode.size: 'value'` 或 `style: { size: number }` | +| 点重叠严重未抖动 | 需要配合数据预处理或 `transform` 进行抖动处理 | +| `chart.source(data)`(v4) | `chart.options({ data })` | + +更多对照见 [v4-to-v5-migration.md](../references/v4-to-v5-migration.md)。 + +## 完整案例 + +每个案例的元信息(场景与关键配置),代码均通过 `npm run validate` 校验: + +| 案例 | 场景 | 关键配置 | +|---|---|---| +| [basic.ts](../examples/beeswarm/basic.ts) | 最简 beeswarm 图,单类别分布 | `type: 'point'` + `encode: { x, y }` | +| [with-label.ts](../examples/beeswarm/with-label.ts) | 点旁标注数值 | `labels` + `encode x/y` | +| [multi-group.ts](../examples/beeswarm/multi-group.ts) | 多组别对比(颜色分组) | `encode.color: 'group'` | +| [size-variable.ts](../examples/beeswarm/size-variable.ts) | 点大小反映数值大小 | `encode.size: 'value'` | +| [horizontal.ts](../examples/beeswarm/horizontal.ts) | 水平展示形式 | 坐标轴互换 | \ No newline at end of file diff --git a/evals/cases/beeswarm.jsonl b/evals/cases/beeswarm.jsonl new file mode 100644 index 0000000000..8cfe2658bf --- /dev/null +++ b/evals/cases/beeswarm.jsonl @@ -0,0 +1,10 @@ +{"id": "beeswarm-basic", "prompt": "展示三个不同年龄组的血压分布数据,每个组别约 8-10 个测量值,希望看到每个数据点的具体位置", "groundTruth": "使用 type: 'point';x 轴编码 category 字段,y 轴编码 value 字段;每个类别展示 8-10 个数据点,展示分布情况而非仅显示均值"} +{"id": "beeswarm-labels", "prompt": "神经科学实验中需要展示不同刺激条件下的反应时间分布,并要求每个数据点都标出具体毫秒数", "groundTruth": "包含 labels 配置,text 指向 value 字段,position 为 'top';type: 'point' + encode x/y,确保数据点数量适中(≤ 40 个)以免标签重叠"} +{"id": "beeswarm-multi-group", "prompt": "对比四种不同药物治疗方案的有效性,每种方案有 6-8 名患者的有效性评分数据", "groundTruth": "encode.color映射group字段展示四个治疗组,type: 'point'配合色彩分组, legend.position设为'top'以便对比"} +{"id": "beeswarm-horizontal", "prompt": "类别名称较长(如‘北京市、上海市、广州市、深圳市'),希望水平展示以节省垂直空间", "groundTruth": "交换x/y字段编码,或使用 coordinate 配置实现横向展示,类别标签位于左侧 y 轴"} +{"id": "beeswarm-size-variable", "prompt": "气候数据可视化,想让点的大小反映降水量的大小,大点表示雨水多,小点表示雨水少", "groundTruth": "encode.size映射value字段,根据数值大小自动调整点的尺寸,配合说明文字说明‘点大=雨水多’"} +{"id": "beeswarm-color-scale", "prompt": "展示五个不同城市的空气质量指数分布,想要颜色深浅反映AQI数值的高低", "groundTruth": "encode.color映射value字段,配合scale.color.type: 'sequential'和palette设置发散/顺序色阶,颜色深浅反映AQI数值"} +{"id": "beeswarm-axis-custom", "prompt": "需要自定义坐标轴标题,x轴写‘处理方案’,y轴写‘反应时间(ms)’,并希望y轴从0开始固定刻度", "groundTruth": "axis.x.title设为'处理方案',axis.y.title设为'反应时间(ms)',axis.y.min设为0确保从0开始"} +{"id": "beeswarm-theme-dark", "prompt": "深色模式下的科研数据展示,需要在背景深色的环境下清晰展示数据分布", "groundTruth": "设置theme: 'classicDark'或'dark',坐标轴、图例、标题文字颜色会自动适配暗色背景"} +{"id": "beeswarm-tooltip", "prompt": "医学会议海报展示,tooltip需要显示类别名称和具体的数值分数", "groundTruth": "tooltip.title设为类别字段名,tooltip.items包含channel: 'y'的name: '数值'配置,悬停显示具体分数"} +{"id": "beeswarm-null-handle", "prompt": "有些数据点缺失或测量失败,希望缺失的数据点不显示或以不同方式标记", "groundTruth": "数据中对应行的value字段传null,或使用transform过滤掉null行;line mark默认遇null断开,point mark视渲染表现而定"} \ No newline at end of file diff --git a/examples/beeswarm/basic.ts b/examples/beeswarm/basic.ts new file mode 100644 index 0000000000..d290f0aece --- /dev/null +++ b/examples/beeswarm/basic.ts @@ -0,0 +1,44 @@ +import { Chart } from '@antv/g2'; + +/** + * 基础熊蜂图 + * 场景:展示不同实验组的数值分布,每个数据点均可见 + */ +const data = [ + { category: 'Control', value: 12.5 }, + { category: 'Control', value: 15.3 }, + { category: 'Control', value: 11.8 }, + { category: 'Control', value: 13.2 }, + { category: 'Treatment', value: 22.1 }, + { category: 'Treatment', value: 19.7 }, + { category: 'Treatment', value: 23.4 }, + { category: 'Treatment', value: 21.0 }, + { category: 'Treatment', value: 24.5 }, + { category: 'Treatment', value: 18.8 }, +]; + +const chart = new Chart({ + container: 'container', + autoFit: true, +}); + +chart.options({ + type: 'point', + data, + encode: { + x: 'category', + y: 'value', + }, + style: { + fill: '#5B8FF9', + stroke: '#1890FF', + lineWidth: 2, + }, + axis: { + x: { title: '实验组' }, + y: { title: '数值', min: 0 }, + }, + legend: false, +}); + +chart.render(); \ No newline at end of file diff --git a/examples/beeswarm/multi-group.ts b/examples/beeswarm/multi-group.ts new file mode 100644 index 0000000000..a3e0f76f6d --- /dev/null +++ b/examples/beeswarm/multi-group.ts @@ -0,0 +1,52 @@ +import { Chart } from '@antv/g2'; + +/** + * 多组别对比的熊蜂图 + * 场景:通过颜色分组展示多个实验/处理方案的数值分布差异 + */ +const data = [ + { category: 'Control', value: 12.5, group: 'A' }, + { category: 'Control', value: 15.3, group: 'A' }, + { category: 'Control', value: 11.8, group: 'B' }, + { category: 'Control', value: 13.2, group: 'B' }, + { category: 'Treatment', value: 22.1, group: 'A' }, + { category: 'Treatment', value: 19.7, group: 'A' }, + { category: 'Treatment', value: 23.4, group: 'B' }, + { category: 'Treatment', value: 21.0, group: 'B' }, + { category: 'Treatment', value: 24.5, group: 'A' }, + { category: 'Treatment', value: 18.8, group: 'B' }, +]; + +const chart = new Chart({ + container: 'container', + autoFit: true, +}); + +chart.options({ + type: 'point', + data, + encode: { + x: 'category', + y: 'value', + color: 'group', + }, + style: { + strokeWidth: 2, + }, + axis: { + x: { title: '实验组' }, + y: { title: '数值', min: 0 }, + }, + legend: { + color: { position: 'top' }, + }, + tooltip: { + title: '类别', + items: [ + { channel: 'color', name: '组别' }, + { channel: 'y', name: '数值' }, + ], + }, +}); + +chart.render(); \ No newline at end of file diff --git a/examples/beeswarm/with-label.ts b/examples/beeswarm/with-label.ts new file mode 100644 index 0000000000..bf2b312360 --- /dev/null +++ b/examples/beeswarm/with-label.ts @@ -0,0 +1,51 @@ +import { Chart } from '@antv/g2'; + +/** + * 带数据标签的熊蜂图 + * 场景:每个数据点旁标注具体数值,适合数据量适中(≤ 50 个点)的分布展示 + */ +const data = [ + { category: 'Control', value: 12.5 }, + { category: 'Control', value: 15.3 }, + { category: 'Control', value: 11.8 }, + { category: 'Control', value: 13.2 }, + { category: 'Treatment', value: 22.1 }, + { category: 'Treatment', value: 19.7 }, + { category: 'Treatment', value: 23.4 }, + { category: 'Treatment', value: 21.0 }, + { category: 'Treatment', value: 24.5 }, + { category: 'Treatment', value: 18.8 }, +]; + +const chart = new Chart({ + container: 'container', + autoFit: true, +}); + +chart.options({ + type: 'point', + data, + encode: { + x: 'category', + y: 'value', + }, + style: { + fill: '#5B8FF9', + stroke: '#1890FF', + lineWidth: 2, + }, + labels: [ + { text: 'value', position: 'top', style: { fontSize: 10 } }, + ], + axis: { + x: { title: '实验组' }, + y: { title: '数值', min: 0 }, + }, + legend: false, + tooltip: { + title: '类别', + items: [{ channel: 'y', name: '数值' }], + }, +}); + +chart.render(); \ No newline at end of file diff --git a/skills/g2plot-v3/SKILL.md b/skills/g2plot-v3/SKILL.md index ca96e17bb3..97b394fe64 100644 --- a/skills/g2plot-v3/SKILL.md +++ b/skills/g2plot-v3/SKILL.md @@ -31,6 +31,7 @@ description: G2Plot v3 可视化组件库,提示词即组件。当用户需要 | 两个量级不同的指标 | 时间 x + 双数值 y | dual-axes 双轴图 | [charts/dual-axes.md](../../charts/dual-axes.md) | | 多维能力对比 | 多维度数值 | radar 雷达图 | [charts/radar.md](../../charts/radar.md) | | 密度/频率分布 | 两个类别 + 数值 | heatmap 热力图 | [charts/heatmap.md](../../charts/heatmap.md) | +| 分类分布展示 | 单类别数值分布 | beeswarm 熊蜂图 | [charts/beeswarm.md](../../charts/beeswarm.md) | | 流程转化 | 阶段 + 数值 | funnel 漏斗图 | [charts/funnel.md](../../charts/funnel.md) | 选型拿不准时,读对应 `charts/.md` 的「何时使用 / 何时不用」小节。