# G2 — Full Documentation > G2 是一套简洁的渐进式可视化语法,用于构建仪表盘、数据探索以及数据讲故事。结合工业和学术实践,实现图形语法、动画语法和交互语法。 - Canonical site: https://g2.antv.antgroup.com/zh/ - Source repository: https://github.com/antvis/g2 - Languages: zh, en - Current version: 5.4.8 ## 中文文档 ### charts #### 📈 图表介绍 - Canonical page: https://g2.antv.antgroup.com/zh/charts/overview/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/overview.md - Description: 浏览 G2 图表类型、适用场景和可运行示例,按数据关系选择条形图、折线图、散点图、地图及关系图。 --- title: "📈 图表介绍" description: "浏览 G2 图表类型、适用场景和可运行示例,按数据关系选择条形图、折线图、散点图、地图及关系图。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/overview/" version: "5.4.8" --- [Interactive content]() #### 弧长连接图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/arcdiagram/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/arcdiagram.md - Description: 用沿直线或圆周排列的节点与弧线展示关系,了解节点排序、连接模式及 G2 弧长连接图示例。 --- title: "弧长连接图" description: "用沿直线或圆周排列的节点与弧线展示关系,了解节点排序、连接模式及 G2 弧长连接图示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/arcdiagram/" version: "5.4.8" --- arcdiagram ## 弧长连接图的简介 弧长连接图是节点-链接法的一个变种,节点-链接法是指用节点表示对象,用线(或边)表示关系的节点-链接布局(node-link)的一种可视化布局表示。弧长连接图在此概念的基础上,采用一维布局方式,即节点沿某个线性轴或环状排列,用圆弧表达节点之间的链接关系。这种方法不能像二维布局那样表达图的全局结构,但在节点良好排序后可清晰的呈现环和桥的结构。 **英文名**:Arc diagram ## 弧长连接图的构成 ### 线性弧长连接图 linear-arcdiagram | 图表类型 | 线性弧长连接图 | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | 适合的数据 | 列表:一组节点数据(包含节点 id 字段)、一组链接数据(包含源节点字段和目标节点字段) | | 功能 | 表示数据之间的链接关系 | | 数据与图形的映射 | 节点的坐标(自动计算)字段映射到节点的坐标轴的位置
链接的坐标(自动计算)映射到半圆弧的顶点坐标轴位置
节点中的分类数据也可以设置颜色增强区分度 | | 适合的数据条数 | 不少于 2 条数据 | --- ### 环形弧长连接图 circular-arcdiagram | 图表类型 | 环形弧长连接图 | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 适合的数据 | 列表:一组节点数据(包含节点 id 字段)、一组链接数据(包含源节点字段和目标节点字段) | | 功能 | 表示数据之间的链接关系 | | 数据与图形的映射 | 节点的坐标(自动计算)字段映射到节点的坐标轴的位置
链接的坐标(自动计算)映射到以圆心为控制点的贝塞尔曲线的顶点坐标轴位置
节点中的分类数据也可以设置颜色增强区分度 | | 适合的数据条数 | 不少于 5 条节点数据 | ## 弧长连接图的应用场景 ### 适合的场景 例子 1: **表达数据之间的关系,以及关系的重要性** 下图这个网络代表了维克多·雨果的经典小说"Les Misérables"中的人物关系。 **节点数据**: | name | group | id | | ------ | ----- | --- | | Myriel | 1 | 0 | | ... | ... | ... | **链接数据**: | source | target | value | | ------ | ------ | ----- | | 1 | 0 | 1 | | ... | ... | ... | ```ts import { Chart } from '@antv/g2'; const colors = [ '#ff7875', '#ffa940', '#fadb14', '#73d13d', '#40a9ff', '#b37feb', '#ff85c0', '#ffc069', '#95de64', ]; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'view', autoFit: true, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/relationship.json', transform: [ { type: 'custom', callback: (data) => { const { nodes, links } = data; const arcData = []; links.forEach((link) => { const sourceId = parseInt(link.source); const targetId = parseInt(link.target); const sourceIndex = nodes.findIndex((n) => n.id === sourceId); const targetIndex = nodes.findIndex((n) => n.id === targetId); if (sourceIndex !== -1 && targetIndex !== -1) { const sourceX = sourceIndex * 15 + 50; const targetX = targetIndex * 15 + 50; 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: x, y: y, linkId: `${sourceId}-${targetId}`, sourceName: nodes[sourceIndex].label, targetName: nodes[targetIndex].label, type: 'link', }); } } }); const nodeData = nodes.map((node, i) => ({ name: node.label, group: node.modularity_class, size: node.size, displaySize: Math.sqrt(node.size) * 4, x: i * 15 + 50, y: 600, type: 'node', })); return [...arcData, ...nodeData]; }, }, ], }, children: [ { type: 'line', data: { transform: [{ type: 'filter', callback: (d) => d.type === 'link' }], }, encode: { x: 'x', y: 'y', series: 'linkId' }, style: { stroke: '#1890ff', strokeWidth: 0.8, strokeOpacity: 0.4 }, }, { type: 'point', data: { transform: [{ type: 'filter', callback: (d) => d.type === 'node' }], }, encode: { x: 'x', y: 'y', color: 'group' }, scale: { color: { type: 'ordinal', range: colors, }, }, style: { r: 4, fill: (d) => colors[parseInt(d.group)] || '#40a9ff', stroke: 'none', fillOpacity: 0.8, }, }, ], }); chart.render(); ``` **说明**: - `group` 字段,使用了`颜色`用于区分不同的节点类型 - `value` 字段,使用`弧线粗细`表示关系的强度 例子 2: **环形布局展示复杂关系网络** 使用极坐标系可以将例 1 的数据绘制成环形弧长连接图,更适合展示具有大量节点和复杂关系的网络数据。 ```ts import { Chart } from '@antv/g2'; const colors = [ '#ff7875', '#ffa940', '#fadb14', '#73d13d', '#40a9ff', '#b37feb', '#ff85c0', '#ffc069', '#95de64', ]; const chart = new Chart({ container: 'container', theme: 'classic', width: 600, height: 600, }); const nodeFilter = { transform: [{ type: 'filter', callback: (d) => d.type === 'node' }], }; chart.options({ type: 'view', autoFit: true, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/relationship.json', transform: [ { type: 'custom', callback: (data) => { const { nodes, links } = data; const centerX = 300; const centerY = 300; const radius = 200; const nodeCount = nodes.length; const nodePositions = nodes.map((node, i) => { const angle = (i / nodeCount) * 2 * Math.PI - Math.PI / 2; const x = centerX + radius * Math.cos(angle); const y = centerY + radius * Math.sin(angle); return { ...node, x, y, angle, index: i, }; }); const arcData = []; links.forEach((link) => { const sourceId = parseInt(link.source); const targetId = parseInt(link.target); const sourceNode = nodePositions.find((n) => n.id === sourceId); const targetNode = nodePositions.find((n) => n.id === targetId); if (sourceNode && targetNode) { const steps = 20; for (let i = 0; i <= steps; i++) { const t = i / steps; const x = Math.pow(1 - t, 2) * sourceNode.x + 2 * (1 - t) * t * centerX + Math.pow(t, 2) * targetNode.x; const y = Math.pow(1 - t, 2) * sourceNode.y + 2 * (1 - t) * t * centerY + Math.pow(t, 2) * targetNode.y; arcData.push({ x, y, linkId: `${sourceId}-${targetId}`, sourceName: sourceNode.label, targetName: targetNode.label, type: 'link', }); } } }); const nodeData = nodePositions.map((node) => ({ name: node.label, group: node.modularity_class, size: node.size, displaySize: Math.sqrt(node.size) * 4, x: node.x, y: node.y, type: 'node', })); return [...arcData, ...nodeData]; }, }, ], }, children: [ { type: 'line', data: { transform: [{ type: 'filter', callback: (d) => d.type === 'link' }], }, encode: { x: 'x', y: 'y', series: 'linkId' }, style: { stroke: '#1890ff', strokeWidth: 1.2, strokeOpacity: 0.3, lineCap: 'round', }, }, { type: 'point', data: nodeFilter, encode: { x: 'x', y: 'y', color: 'group' }, scale: { color: { type: 'ordinal', range: colors, }, }, style: { r: 6, fill: (d) => colors[parseInt(d.group)] || '#40a9ff', stroke: '#fff', strokeWidth: 2, fillOpacity: 0.9, }, }, { type: 'text', data: nodeFilter, encode: { x: 'x', y: 'y', text: 'name' }, style: { textAlign: 'center', textBaseline: 'middle', fontSize: 10, fill: '#333', fontWeight: 'bold', dy: -15, }, }, ], }); chart.render(); ``` ### 不适合的场景 例子 1: **不适合表示层次结构** 弧长连接图采用线性或环形的一维布局,无法有效表达数据的层次关系,对于具有明显层级结构的数据,树形图或组织架构图更为合适。 例子 2: **不适合处理过多节点** 当节点数量过多时,弧长连接图会变得拥挤难读,连线重叠严重,影响可读性。此时应考虑使用力导向图或其他网络图布局。 ## 弧长连接图与其他图表的对比 ### 弧长连接图和[和弦图](/zh/charts/chord/) - 弧长连接图的节点使用标准线性布局,节点权重决定节点大小但不影响位置 - 弧长连接图的连线可以使用权重控制线宽,粗细均匀 - 弧长连接图的连线重叠绘制在节点上 - 和弦图的节点使用权重线性布局,节点权重即决定节点大小,又决定节点位置 - 和弦图的连线使用源权重和目标权重控制线宽,粗细非均匀 - 和弦图的节点宽度为连线宽度之和,节点处的连线平铺不重叠 ### 弧长连接图和[桑基图](/zh/charts/sankey/) - 弧长连接图主要用于表示节点之间的关系连接,不体现流量或数据流向 - 桑基图专门用于表示数据流,连线宽度表示流量大小,具有明确的流向 - 弧长连接图采用弧形连线,视觉上更加优雅 - 桑基图采用直线或曲线连接,更直观地表达流向关系 ## 相似图表 - [和弦图](/zh/charts/chord/) - 用于表示多个实体之间的关系和流向 - [桑基图](/zh/charts/sankey/) - 用于表示数据流和能量流转 - [网络图](/zh/manual/extra-topics/graph/force-graph/) - 用于表示复杂的网络关系结构 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 马赛克图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/mosaic/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/mosaic.md - Description: 用矩形面积和分割比例表达分类数据的频数关系,了解马赛克图的使用场景及 G2 示例。 --- title: "马赛克图" description: "用矩形面积和分割比例表达分类数据的频数关系,了解马赛克图的使用场景及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/mosaic/" version: "5.4.8" --- mosaic ## 马赛克图的简介 马赛克图(Mosaic Plot,又名 Marimekko Chart)是一种用于显示分类数据之间关系的图表。它通过矩形的面积和颜色来表示数据的比例和分类。 马赛克图分为均匀和非均匀两种类型,均匀马赛克图在生活中更为常见,例如地铁票价图;而非均匀马赛克图多用于统计学领域。 **英文名**:Mosaic Plot, Marimekko Chart ## 马赛克图的构成 ### 非均匀坐标轴马赛克图 mosaic-uneven | 图表类型 | 非均匀坐标轴马赛克图 | | ---------------- | ------------------------------------------------------------------ | | 适合的数据 | 多维分类数据 | | 功能 | 显示多维分类数据的分布 | | 数据与图形的映射 | 分类数据字段映射到非均匀的坐标轴
面积和颜色表示数据的比例和分类 | | 适合的数据条数 | 数据维度较多时效果更佳 | --- ### 均匀坐标轴马赛克图 mosaic-even | 图表类型 | 均匀坐标轴马赛克图 | | ---------------- | ---------------------------------------------------- | | 适合的数据 | 二维分类数据 | | 功能 | 显示二维分类数据的分布 | | 数据与图形的映射 | 分类数据字段映射到均匀的坐标轴
颜色表示数据的分类 | | 适合的数据条数 | 数据维度较少时效果更佳 | ## 马赛克图的应用场景 ### 适合的场景 例子 1: **适合应用到多维分类数据分析** ![非均匀马赛克图](https://t.alipayobjects.com/images/rmsweb/T1B.0iXcxfXXXXXXXX.png) 例子 2: **适合应用到二维分类数据分析** ```ts import * as G2 from '@antv/g2'; const chart = new G2.Chart({ container: 'container', autoFit: true, height: 400, }); chart.options({ type: 'cell', data: [ { 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', strokeWidth: 2, inset: 2, }, legend: { color: { title: '产品类别', position: 'top', }, size: { title: '销售额', position: 'right', }, }, axis: { x: { title: '产品类型', labelAutoRotate: false, }, y: { title: '销售区域', }, }, tooltip: { title: (d) => `${d.product} - ${d.region}`, items: [ { field: 'sales', name: '销售额', valueFormatter: (v) => `${v}万` }, { field: 'category', name: '类别' }, ], }, }); chart.render(); ``` 例子 3: **市场细分分析(非均匀马赛克图)** 这个例子展示了如何使用非均匀马赛克图来展示不同市场细分的分布情况,其中矩形的宽度表示市场规模,高度表示各细分市场的占比。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', width: 900, height: 800, 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轴宽度 { type: 'stackY' }, // Y轴堆叠 { type: 'normalizeY' }, // Y轴归一化 ], 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', }, { text: 'value', x: 5, y: 5, textAlign: 'start', dy: 15, fontSize: 10, fill: '#fff', }, ], }); chart.render(); ``` 例子 4: **电影评分分布分析(密度马赛克图)** 这个例子展示了如何使用密度马赛克图来分析 IMDB 和烂番茄评分的关系分布,颜色深浅表示电影数量的多少。 ```ts 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' }, }, tooltip: { title: { channel: 'color' }, items: [ (d, i, data, column) => ({ name: 'IMDB评分', value: `${column.x.value[i]}, ${column.x1.value[i]}`, }), (d, i, data, column) => ({ name: '烂番茄评分', value: `${column.y.value[i]}, ${column.y1.value[i]}`, }), ], render: () => '1', }, }); chart.render(); ``` 例子 5: **运动员生理数据分析(分组密度马赛克图)** 这个例子展示了如何使用马赛克图按性别分组展示运动员身高和体重的分布情况,不透明度表示数据点的密度。 ```ts 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/athletes.json', }, encode: { x: 'weight', y: 'height', color: 'sex', }, transform: [{ type: 'bin', opacity: 'count' }], legend: { opacity: false, }, style: { inset: 0.5, }, tooltip: { title: { channel: 'opacity' }, items: [ (d, i, data, column) => ({ name: '体重', value: `${column.x.value[i]}, ${column.x1.value[i]}`, }), (d, i, data, column) => ({ name: '身高', value: `${column.y.value[i]}, ${column.y1.value[i]}`, }), ], }, }); chart.render(); ``` ## 马赛克图与其他图表的对比 ### 马赛克图和热力图 - 从图形属性上: - 热力图表示第三维度的颜色是**线性**变化的。 - 马赛克图表示第三维度的颜色是**分类**的。 - 标准热力图要经过平滑算法,没有明显的边界。 - 马赛克图拥有清晰的边界。 - 从数据上看: - 均匀的马赛克图和热力图在连续数据上的含义非常相似。 - 从分析需求上看: - 热力图侧重于分布,可预测未知区域数据。 - 马赛克图拥有更清晰的边界,更侧重于对比。 ## 马赛克图的扩展阅读 - [A Brief History of the Mosaic Display](http://www.datavis.ca/papers/moshist.pdf) - [Are Mosaic Plots Worthwhile](https://www.perceptualedge.com/articles/visual_business_intelligence/are_mosaic_plots_worthwhile.pdf) - [Marimekko Charts](http://peltiertech.com/marimekko-charts/) - [Marimekko Charts FusionChart](http://www.fusioncharts.com/charts/marimekko-charts/) - [Marimekko Charts The Data Visualisation Catalogue](http://www.datavizcatalogue.com/methods/marimekko_chart.html#.VlJ9E98rJTY) - [Marimekko Charts in Microsoft Excel](http://www.clearlyandsimply.com/clearly_and_simply/2014/05/marimekko-charts-in-microsoft-excel.html) ## 相似图表 [Interactive content]() --- ## 分类 [Interactive content]() #### 柱状图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/bar/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/bar.md - Description: 用柱子的高度或长度比较不同类别的数值,了解柱状图的数据要求、适用场景及 G2 绘制示例。 --- title: "柱状图" description: "用柱子的高度或长度比较不同类别的数值,了解柱状图的数据要求、适用场景及 G2 绘制示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/bar/" version: "5.4.8" --- bar ## 柱状图的简介 典型的柱状图(又名条形图),使用垂直或水平的柱子显示类别之间的数值比较。其中一个轴表示需要对比的分类维度,另一个轴代表相应的数值。 柱状图有别于[直方图](/zh/charts/histogram/),柱状图无法显示数据在一个区间内的连续变化趋势。柱状图描述的是分类数据,回答的是每一个分类中“有多少?”这个问题。 需要注意的是,当柱状图显示的分类很多时会导致分类名层叠等显示问题,下面我们会举例说明。 **英文名**:Column chart, Bar chart ## 柱状图的构成 ### 纵向柱状图 bar-vertical | 图表类型 | 纵向柱状图 | | ---------------- | ------------------------------------------------------------------------------------------------------- | | 适合的数据 | 列表:一个分类数据字段、一个连续数据字段 | | 功能 | 对比分类数据的数值大小 | | 数据与图形的映射 | 分类数据字段映射到横轴的位置
连续数据字段映射到矩形的高度
分类数据也可以设置颜色增强分类的区分度 | | 适合的数据条数 | 不超过 12 条数据 | --- ### 横向柱状图 (条形图) bar-horizontal | 图表类型 | 横向柱状图 | | ---------------- | ------------------------------------------------------------ | | 适合的数据 | 列表:一个分类数据字段、一个连续数据字段 | | 功能 | 对比分类数据的数值大小 | | 数据与图形的映射 | 分类数据字段映射到纵轴的位置
连续数据字段映射到矩形的宽度 | | 适合的数据条数 | 不超过 30 条数据 | ## 柱状图的应用场景 ### 适合的场景 例子 1: **适合应用到分类数据对比** 下图是一个游戏销量的图表,展示不同游戏类型的销量对比。 | genre(游戏类型) | sold(销售量) | | ----------------- | -------------- | | Sports | 27,500 | | Strategy | 11,500 | | Action | 6,000 | | Shooter | 3,500 | | Other | 1,500 | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, 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: 'elementHighlight', background: true, region: true, }, ], }); chart.render(); ``` **说明** : - `genre` 字段,同时使用了`位置`和`颜色`用于区分不同的游戏类型 - `sold` 字段,使用矩形的`长度`,表示销售量的大小 ### 不适合的场景 例子 1: **分类太多不适合使用纵向柱状图** 如对比不同省份的人口数量。分类情况过多时,柱状图的文本为了排布合理,需要进行旋转,不利于阅读,相比于纵向柱状图,横向柱状图更适用于此类分类较多的场景。 | province(省份) | population(人口数量) | | ---------------- | ---------------------- | | 北京市 | 19,612,368 | | 天津市 | 12,938,693 | | 河北省 | 71,854,210 | | ... | ... | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, data: [ { province: '北京市', population: 19612368 }, { province: '天津市', population: 12938693 }, { province: '河北省', population: 71854210 }, { province: '山西省', population: 27500000 }, { province: '内蒙古自治区', population: 24706291 }, { province: '辽宁省', population: 43746323 }, { province: '吉林省', population: 27452815 }, { province: '黑龙江省', population: 38313991 }, { province: '上海市', population: 23019196 }, { province: '江苏省', population: 78660941 }, { province: '浙江省', population: 54426891 }, { province: '安徽省', population: 59500468 }, { province: '福建省', population: 36894217 }, { province: '江西省', population: 44567797 }, { province: '山东省', population: 95792719 }, { province: '河南省', population: 94029939 }, { province: '湖北省', population: 57237727 }, { province: '湖南省', population: 65700762 }, { province: '广东省', population: 104320459 }, { province: '广西壮族自治区', population: 46023761 }, { province: '海南省', population: 8671485 }, { province: '重庆市', population: 28846170 }, { province: '四川省', population: 80417528 }, { province: '贵州省', population: 34748556 }, { province: '云南省', population: 45966766 }, { province: '西藏自治区', population: 3002165 }, { province: '陕西省', population: 37327379 }, { province: '甘肃省', population: 25575263 }, { province: '青海省', population: 5626723 }, ], encode: { x: 'province', y: 'population' }, axis: { y: { title: null, }, x: { title: null, labelFontSize: 12, size: 100, // 必须设置 size labelAutoEllipsis: { suffix: '..', minLength: 8, maxLength: 12, }, labelAutoWrap: { wordWrapWidth: 80, maxLines: 2, recoverWhenFailed: true, }, labelAutoRotate: { optionalAngles: [0, 45, 90], // 尝试旋转 0 度、45 度、90 度 recoverWhenFailed: true, // 如果旋转后无法解决问题,恢复到默认角度 }, labelAutoHide: { keepHeader: true, // 保留第一个刻度值 keepTail: true, // 保留最后一个刻度值 }, }, }, interaction: [ { type: 'elementHighlight', background: true, region: true, }, ], }); chart.render(); ``` 使用横向柱状图,文本可以横向排布,便于用户的阅读,效果如下: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, data: [ { province: '北京市', population: 19612368 }, { province: '天津市', population: 12938693 }, { province: '河北省', population: 71854210 }, { province: '山西省', population: 27500000 }, { province: '内蒙古自治区', population: 24706291 }, { province: '辽宁省', population: 43746323 }, { province: '吉林省', population: 27452815 }, { province: '黑龙江省', population: 38313991 }, { province: '上海市', population: 23019196 }, { province: '江苏省', population: 78660941 }, { province: '浙江省', population: 54426891 }, { province: '安徽省', population: 59500468 }, { province: '福建省', population: 36894217 }, { province: '江西省', population: 44567797 }, { province: '山东省', population: 95792719 }, { province: '河南省', population: 94029939 }, { province: '湖北省', population: 57237727 }, { province: '湖南省', population: 65700762 }, { province: '广东省', population: 104320459 }, { province: '广西壮族自治区', population: 46023761 }, { province: '海南省', population: 8671485 }, { province: '重庆市', population: 28846170 }, { province: '四川省', population: 80417528 }, { province: '贵州省', population: 34748556 }, { province: '云南省', population: 45966766 }, { province: '西藏自治区', population: 3002165 }, { province: '陕西省', population: 37327379 }, { province: '甘肃省', population: 25575263 }, { province: '青海省', population: 5626723 }, ], encode: { x: 'province', y: 'population' }, coordinate: { transform: [{ type: 'transpose' }] }, axis: { y: { title: null, }, x: { title: null, }, }, interaction: [ { type: 'elementHighlight', background: true, region: true, }, ], }); chart.render(); ``` 例子 2: **不适合表示趋势** 柱状图使用矩形的长度(宽度)来对比分类数据的大小,非常方便临近的数据进行大小的对比,不适合展示连续数据的趋势。下图本想展示 ACME 这只股票在 2015 年 9 月份整个月的每日的价格走势,但是效果不尽人意。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, data: [ { day: '2015/9/1', share: 10 }, { day: '2015/9/2', share: 12 }, { day: '2015/9/3', share: 11 }, { day: '2015/9/4', share: 15 }, { day: '2015/9/5', share: 20 }, { day: '2015/9/6', share: 22 }, { day: '2015/9/7', share: 21 }, { day: '2015/9/8', share: 25 }, { day: '2015/9/9', share: 31 }, { day: '2015/9/10', share: 32 }, { day: '2015/9/11', share: 28 }, { day: '2015/9/12', share: 29 }, { day: '2015/9/13', share: 40 }, { day: '2015/9/14', share: 41 }, { day: '2015/9/15', share: 45 }, { day: '2015/9/16', share: 50 }, { day: '2015/9/17', share: 65 }, { day: '2015/9/18', share: 45 }, { day: '2015/9/19', share: 50 }, { day: '2015/9/20', share: 51 }, { day: '2015/9/21', share: 65 }, { day: '2015/9/22', share: 60 }, { day: '2015/9/23', share: 62 }, { day: '2015/9/24', share: 65 }, { day: '2015/9/25', share: 45 }, { day: '2015/9/26', share: 55 }, { day: '2015/9/27', share: 59 }, { day: '2015/9/28', share: 52 }, { day: '2015/9/29', share: 53 }, { day: '2015/9/30', share: 40 }, ], encode: { x: 'day', y: 'share' }, axis: { y: { title: null, }, x: { title: null, }, }, interaction: [ { type: 'elementHighlight', background: true, region: true, }, ], }); chart.render(); ``` 随着有序的时间变化的数值趋势,更适合使用[折线图](/zh/charts/line/)或者[面积图](/zh/charts/area/)。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'view', autoFit: true, data: [ { day: '2015/9/1', share: 10 }, { day: '2015/9/2', share: 12 }, { day: '2015/9/3', share: 11 }, { day: '2015/9/4', share: 15 }, { day: '2015/9/5', share: 20 }, { day: '2015/9/6', share: 22 }, { day: '2015/9/7', share: 21 }, { day: '2015/9/8', share: 25 }, { day: '2015/9/9', share: 31 }, { day: '2015/9/10', share: 32 }, { day: '2015/9/11', share: 28 }, { day: '2015/9/12', share: 29 }, { day: '2015/9/13', share: 40 }, { day: '2015/9/14', share: 41 }, { day: '2015/9/15', share: 45 }, { day: '2015/9/16', share: 50 }, { day: '2015/9/17', share: 65 }, { day: '2015/9/18', share: 45 }, { day: '2015/9/19', share: 50 }, { day: '2015/9/20', share: 51 }, { day: '2015/9/21', share: 65 }, { day: '2015/9/22', share: 60 }, { day: '2015/9/23', share: 62 }, { day: '2015/9/24', share: 65 }, { day: '2015/9/25', share: 45 }, { day: '2015/9/26', share: 55 }, { day: '2015/9/27', share: 59 }, { day: '2015/9/28', share: 52 }, { day: '2015/9/29', share: 53 }, { day: '2015/9/30', share: 40 }, ], encode: { x: 'day', y: 'share' }, axis: { y: { title: null, }, x: { title: null, tickFilter: (_, i) => i % 2 !== 0, // 过滤 x 轴刻度线,只显示每隔 1 个刻度线 }, }, children: [ { type: 'area', style: { opacity: 0.6, }, }, { type: 'line', style: { lineWidth: 3, }, }, ], }); chart.render(); ``` ## 柱状图与其他图表的对比 ### 柱状图和[折线图](/zh/charts/line/)、[饼图](/zh/charts/pie/) - 柱状图主要用于多个分类间的数据(大小、数值)的对比 - 折线图主要用于展示连续数值(例如时间)或者有序分类的变化趋势 - 饼图主要是展示分类之间的占比情况 ### 柱状图和南丁格尔图(玫瑰图) - 南丁格尔图(玫瑰图)通过半径的大小来对比数据 - 柱状图根据矩形的长度来对比数据 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 气泡图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/bubble-chart/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/bubble-chart.md - Description: 在散点位置之外用气泡大小表达额外数值,了解气泡图的数据映射、比较方式及 G2 示例。 --- title: "气泡图" description: "在散点位置之外用气泡大小表达额外数值,了解气泡图的数据映射、比较方式及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/bubble-chart/" version: "5.4.8" --- bubble ## 气泡图的简介 气泡图是一种多变量图表,是散点图的变体,也可以认为是散点图和百分比区域图的组合。 气泡图最基本的用法是使用三个值来确定每个数据序列,和散点图一样,气泡图将两个维度的数据值分别映射为笛卡尔坐标系上的坐标点,其中 X 和 Y 轴分别代表不同的两个维度的数据,但是不同于散点图的是,气泡图的每个气泡都有分类信息(他们显示在点旁边或者作为图例)。每一个气泡的面积代表第三个数值数据。另外还可以使用不同的颜色来区分分类数据或者其他的数值数据,或者使用亮度或者透明度。表示时间维度的数据时,可以将时间维度作为直角坐标系中的一个维度,或者结合动画来表现数据随着时间的变化情况。 气泡图通常用于比较和展示不同类别圆点(这里我们称为气泡)之间的关系,通过气泡的位置以及面积大小。从整体上看,气泡图可用于分析数据之间的相关性。 需要注意的是,气泡图的数据大小容量有限,气泡太多会使图表难以阅读。但是可以通过增加一些交互行为弥补:隐藏一些信息,当鼠标点击或者悬浮时显示,或者添加一个选项用于重组或者过滤分组类别。 另外,**气泡的大小是映射到面积而不是半径或者直径绘制的**。因为如果是基于半径或者直径的话,圆的大小不仅会呈指数级变化,而且还会导致视觉误差。 **英文名**:Bubble Chart ## 气泡图的构成 ### 基础气泡图 bubble-basic | 图表类型 | 基础气泡图 | | ---------------- | -------------------------------------------------------------------------------------------------- | | 适合的数据 | 多维度数据:至少两个连续型数据字段(X、Y 轴)和一个数值型数据字段(气泡大小) | | 功能 | 展示多个变量之间的关系,发现数据模式和相关性 | | 数据与图形的映射 | 两个连续变量映射到 XY 坐标
第三个变量映射到气泡的大小
可选的分类型变量映射到气泡的颜色或形状 | | 适合的数据条数 | 通常不超过 100 个数据点,过多会导致图表拥挤 | | 视觉设计建议 | 使用半透明气泡避免重叠遮挡
气泡大小范围应适中,避免过大或过小
添加网格线辅助阅读坐标值 | --- ### 对数气泡图 bubble-log | 图表类型 | 对数气泡图 | | ---------------- | ------------------------------------------------------------------------------------------------------ | | 适合的数据 | 数值范围差异极大的数据集,需要对数变换来展示 | | 功能 | 处理数据跨度很大的情况,如人口数据、GDP 数据等 | | 数据与图形的映射 | 与基础气泡图相同,但采用对数比例尺
两个连续变量映射到 XY 坐标
第三个变量通过对数映射到气泡的大小 | | 使用场景 | 当数据出现指数增长或数值范围跨越多个数量级时 | | 实现方法 | 使用`scale: { size: { type: 'log', range: [4, 20] } }`配置对数比例尺 | | 注意事项 | 应在图例或标签中明确指出使用了对数变换,避免误导读者 | ## 气泡图的应用场景 ### 适合的场景 气泡图最适合用于以下几类场景: 1. **多维数据分析** - 当需要同时分析三个或更多变量之间的关系 2. **比较特征聚类** - 发现数据中的分组模式和异常值 3. **资源分配可视化** - 展示多维度指标下的资源分布 4. **风险与机会分析** - 同时考虑多个因素的决策辅助 #### 示例 1: **经济与人口健康指标分析** 下图展示了不同国家人均 GDP、预期寿命与人口数量的关系,同时用颜色区分了不同大洲,有效地在一个图表中展现了四个维度的数据。 | country(国家) | GDP(人均 GDP) | lifeExpectancy(预期寿命) | population(人口) | continent(大洲) | | --------------- | --------------- | -------------------------- | ------------------ | ----------------- | | China | 12547 | 76.9 | 1403500365 | Asia | | United States | 59532 | 78.5 | 321773631 | Americas | | India | 6427 | 68.3 | 1324517249 | Asia | | Japan | 38428 | 83.9 | 127141000 | Asia | | Germany | 46136 | 81.0 | 82695000 | Europe | | ... | ... | ... | ... | ... | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'point', autoFit: true, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/bubble.json', }, encode: { x: 'GDP', y: 'LifeExpectancy', size: 'Population', color: 'continent', shape: 'point', }, scale: { size: { type: 'log', range: [4, 20] }, }, style: { fillOpacity: 0.3, lineWidth: 1, }, legend: { size: false, }, }); chart.render(); ``` **分析** : - `GDP` 字段映射到 X 轴,表示国家人均 GDP,可以看出发达国家集中在右侧 - `LifeExpectancy` 字段映射到 Y 轴,表示国民预期寿命,能够观察到人均 GDP 与预期寿命有正相关性 - `Population` 字段映射到气泡大小,通过对数比例尺处理,使得中国、印度等人口大国清晰可见 - `continent` 字段映射到颜色,可以看出不同大洲国家的聚类分布 **洞察**:通过此图,我们可以直观地观察到: 1. 经济水平与健康水平的正相关关系 2. 人口规模与经济发展之间并无必然联系 3. 不同大洲国家的发展模式存在差异 ### 不适合的场景 例子 1: **数据点过多或气泡重叠过度** 当数据点超过 100 个或气泡严重重叠时,气泡图可能变得混乱难以阅读。在这种情况下,可以考虑使用热力图或分面图表。 以下是一个包含大量数据点的气泡图示例,可以看到随着数据点的增加,气泡之间的重叠让图表变得难以解读: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); // 生成200个随机数据点 const data = Array.from({ length: 200 }, () => ({ x: Math.random() * 100, y: Math.random() * 100, size: Math.random() * 20 + 5, category: ['A', 'B', 'C', 'D'][Math.floor(Math.random() * 4)], })); chart.options({ type: 'point', autoFit: true, data, encode: { x: 'x', y: 'y', size: 'size', color: 'category', }, style: { fillOpacity: 0.3, lineWidth: 1, }, }); chart.render(); ``` 例子 2: **只有两个变量需要比较** 当只需比较两个变量之间的关系时,散点图比气泡图更简洁有效。气泡图的第三个变量(气泡大小)如果没有必要,反而会增加认知负担。 例子 3: **展示趋势变化的时序数据** 气泡图不适合展示时序趋势数据。对于需要展示随时间变化趋势的数据,折线图或面积图是更好的选择。 下面是一个不使用气泡图而使用折线图展示时序数据的例子: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'line', autoFit: true, data: [ { day: '2015/9/1', share: 10, volume: 1200 }, { day: '2015/9/5', share: 20, volume: 2100 }, { day: '2015/9/10', share: 32, volume: 3400 }, { day: '2015/9/15', share: 45, volume: 2900 }, { day: '2015/9/20', share: 51, volume: 4100 }, { day: '2015/9/25', share: 45, volume: 3800 }, { day: '2015/9/30', share: 40, volume: 2700 }, ], encode: { x: 'day', y: 'share' }, axis: { y: { title: '股价', }, x: { title: '日期', }, }, style: { lineWidth: 3, }, }); chart.render(); ``` 如果确实需要在时序数据中加入第三个维度的信息,可以使用带有大小编码的时间序列气泡图: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'point', autoFit: true, data: [ { day: '2015/9/1', share: 10, volume: 1200 }, { day: '2015/9/5', share: 20, volume: 2100 }, { day: '2015/9/10', share: 32, volume: 3400 }, { day: '2015/9/15', share: 45, volume: 2900 }, { day: '2015/9/20', share: 51, volume: 4100 }, { day: '2015/9/25', share: 45, volume: 3800 }, { day: '2015/9/30', share: 40, volume: 2700 }, ], encode: { x: 'day', y: 'share', size: 'volume', }, scale: { size: { range: [10, 40] }, }, style: { fillOpacity: 0.6, stroke: '#1890ff', fill: '#1890ff', }, axis: { y: { title: '股价' }, x: { title: '日期' }, }, }); chart.render(); ``` ## 气泡图与其他图表的对比 ### 气泡图和[散点图](/zh/charts/scatter-plot/)、[热力图](/zh/charts/heatmap/) | 图表类型 | 主要特点 | 适用场景 | 数据维度 | | -------- | -------------------------- | -------------- | ---------------------------- | | 气泡图 | 通过点的大小编码额外维度 | 多变量关系分析 | 3-4 个变量(X、Y、大小、颜色) | | 散点图 | 仅展示点的位置 | 二维相关性分析 | 2-3 个变量(X、Y、颜色) | | 热力图 | 通过颜色深浅展示密度或量值 | 展示分布密度 | 3 个变量(X、Y、颜色强度) | ### 气泡图和[散点地图](/zh/charts/dot-map/)、[气泡地图](/zh/charts/bubble-map/) | 图表类型 | 坐标系统 | 数据约束 | 应用场景 | | -------- | ---------- | -------------------------- | -------------------- | | 气泡图 | 抽象坐标系 | 可使用任意数值作为 XY 坐标 | 多维数据关系可视化 | | 气泡地图 | 地理坐标系 | 点位置受地理坐标约束 | 地理数据中多变量分析 | | 散点地图 | 地理坐标系 | 点位置受地理坐标约束 | 地理位置的简单分布 | ## 气泡图最佳实践 ### 设计建议 1. **气泡大小选择** - 设置合适的气泡大小范围,避免过大导致重叠,或过小导致难以辨认 - 使用半透明填充色,降低重叠区域的视觉干扰 2. **数据处理** - 当气泡大小差异过大时,考虑使用对数比例尺 - 适当筛选数据,避免图表过于拥挤 3. **标签和交互** - 添加悬停交互,显示详细数据信息 - 对重要数据点添加标签,但避免标签过多造成混乱 ### 代码示例:添加悬停交互的气泡图 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'point', autoFit: true, data: [ { x: 10, y: 20, size: 20, category: 'A', name: '项目1' }, { x: 20, y: 40, size: 35, category: 'A', name: '项目2' }, { x: 30, y: 30, size: 15, category: 'B', name: '项目3' }, { x: 40, y: 60, size: 40, category: 'B', name: '项目4' }, { x: 50, y: 50, size: 25, category: 'C', name: '项目5' }, ], encode: { x: 'x', y: 'y', size: 'size', color: 'category', shape: 'point', }, style: { fillOpacity: 0.6, lineWidth: 1, }, tooltip: { items: [ { field: 'name', name: '项目名称' }, { field: 'x', name: 'X值' }, { field: 'y', name: 'Y值' }, { field: 'size', name: '大小' }, ], }, interaction: [{ type: 'tooltip' }, { type: 'elementHighlight' }], }); chart.render(); ``` ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 色块图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/color-map/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/color-map.md - Description: 将数据映射到矩形色块,通过颜色比较类别组合的数值,了解色块图的适用场景及 G2 示例。 --- title: "色块图" description: "将数据映射到矩形色块,通过颜色比较类别组合的数值,了解色块图的适用场景及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/color-map/" version: "5.4.8" --- color-map ## 色块图的简介 典型的色块图,是一种使用颜色编码数值数据的可视化图表。它将矩形区域划分为网格,每个网格单元代表一组分类数据的交叉点,通过颜色的深浅表示数值的大小。与热力图不同,色块图通常用于显示离散分类数据之间的关系,而不是连续的数据分布。 色块图特别适合展示多个分类变量之间的关系和模式,例如不同时间段内不同产品的销售额、不同地区不同年份的气温变化等。通过颜色编码,用户可以快速识别出数据集中的高低值区域和整体分布模式。 需要注意的是,当色块图中的分类过多时,可能会导致标签重叠和可读性下降的问题,下面我们会举例说明。 **英文名**:Color Map, Color Block Chart ## 色块图的构成 ### 基础色块图 color-map-basic | 图表类型 | 基础色块图 | | ---------------- | -------------------------------------------------------------------------------------------- | | 适合的数据 | 具有两个分类维度和一个数值维度的数据集 | | 功能 | 展示两个分类维度和一个数值变量之间的关系 | | 数据与图形的映射 | 两个分类数据维度映射到网格的行和列
数值数据映射到颜色深浅
可以添加文本标签显示具体数值 | | 适合的数据条数 | 每个维度通常不超过 20 个分类,总单元格数建议不超过 400 个 | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'cell', autoFit: true, data: [ { month: '一月', product: '产品A', sales: 123 }, { month: '一月', product: '产品B', sales: 231 }, { month: '一月', product: '产品C', sales: 145 }, { month: '二月', product: '产品A', sales: 132 }, { month: '二月', product: '产品B', sales: 112 }, { month: '二月', product: '产品C', sales: 178 }, { month: '三月', product: '产品A', sales: 99 }, { month: '三月', product: '产品B', sales: 288 }, { month: '三月', product: '产品C', sales: 133 }, { month: '四月', product: '产品A', sales: 181 }, { month: '四月', product: '产品B', sales: 223 }, { month: '四月', product: '产品C', sales: 141 }, { month: '五月', product: '产品A', sales: 152 }, { month: '五月', product: '产品B', sales: 219 }, { month: '五月', product: '产品C', sales: 109 }, { month: '六月', product: '产品A', sales: 167 }, { month: '六月', product: '产品B', sales: 187 }, { month: '六月', product: '产品C', sales: 255 }, ], coordinate: { type: 'cartesian', }, legend: { color: { position: 'right', flipPage: false, }, }, scale: { color: { palette: 'rdBu', offset: (t) => 1 - t, }, }, encode: { x: 'month', y: 'product', color: 'sales', link: 'sales', }, style: { inset: 1, }, labels: [ { text: 'sales', style: { fill: (d) => (d.sales > 200 ? '#fff' : '#000'), }, }, ], }); chart.render(); ``` ### 带条件格式的色块图 color-map-conditional | 图表类型 | 带条件格式的色块图 | | ---------------- | ---------------------------------------------------------------------------------------------------------- | | 适合的数据 | 需要突出显示特定阈值或范围的数据集 | | 功能 | 根据数据值的不同范围应用不同的颜色编码,突出显示关键数据点 | | 数据与图形的映射 | 除基础色块图的映射外,还可以添加自定义的颜色区间和边界标记 | | 使用建议 | 为不同颜色区间添加清晰的图例说明
使用对比鲜明的颜色表示不同的数据状态
避免使用过多的颜色区间造成混淆 | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); const data = [ { month: '一月', product: '产品A', sales: 123, target: 150 }, { month: '一月', product: '产品B', sales: 231, target: 200 }, { month: '一月', product: '产品C', sales: 145, target: 150 }, { month: '二月', product: '产品A', sales: 132, target: 150 }, { month: '二月', product: '产品B', sales: 112, target: 200 }, { month: '二月', product: '产品C', sales: 178, target: 150 }, { month: '三月', product: '产品A', sales: 99, target: 150 }, { month: '三月', product: '产品B', sales: 288, target: 200 }, { month: '三月', product: '产品C', sales: 133, target: 150 }, { month: '四月', product: '产品A', sales: 181, target: 150 }, { month: '四月', product: '产品B', sales: 223, target: 200 }, { month: '四月', product: '产品C', sales: 141, target: 150 }, { month: '五月', product: '产品A', sales: 152, target: 150 }, { month: '五月', product: '产品B', sales: 219, target: 200 }, { month: '五月', product: '产品C', sales: 109, target: 150 }, { month: '六月', product: '产品A', sales: 167, target: 150 }, { month: '六月', product: '产品B', sales: 187, target: 200 }, { month: '六月', product: '产品C', sales: 255, target: 150 }, ]; // 计算每个单元格的性能状态 const processedData = data.map((d) => ({ ...d, // 计算与目标的比例 performance: d.sales / d.target, // 添加状态标签 status: d.sales < d.target * 0.8 ? '不达标' : d.sales < d.target ? '接近目标' : d.sales < d.target * 1.2 ? '达标' : '超额完成', })); chart.options({ type: 'cell', autoFit: true, data: processedData, coordinate: { type: 'cartesian', }, legend: { color: { position: 'right', flipPage: false, }, }, scale: { color: { type: 'threshold', domain: [0.8, 1, 1.2], range: ['#F4664A', '#FAAD14', '#30BF78', '#0891B2'], }, }, encode: { x: 'month', y: 'product', color: 'performance', }, style: { inset: 1, }, labels: [ { text: (d) => `${d.sales}/${d.target}`, style: { fill: (d) => (d.performance > 0.95 ? '#fff' : '#000'), textAlign: 'center', fontSize: 11, }, }, ], }); chart.render(); ``` ## 色块图的应用场景 ### 适合的场景 例子 1: **杭州地铁票价图** 下图是模拟的杭州地铁票价图。这个色块图展示了不同地铁站点之间的票价关系,通过颜色深浅表示不同的票价级别。可以快速识别票价区间以及任意两站间的出行成本。 | from(起点) | to(终点) | price(票价) | | ------------ | ---------- | ------------- | | 湘湖 | 滨和路 | 2 | | 湘湖 | 西兴 | 3 | | ... | ... | ... | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', height: 500, }); // 地铁1号线站点 const sites = [ '湘湖', '滨康路', '西兴', '滨和路', '江陵路', '近江', '婺江路', '城站', '定安路', '龙翔桥', '凤起路', '武林广场', '西湖文化广场', '打铁关', '火车东站', '闸弄口', '彭埠', '七堡', '九和路', '九堡', '客运中心', '下沙西', '金沙湖', '高沙路', '文泽路', ]; // 生成地铁票价数据 const data = []; for (let i = 0; i < sites.length; i++) { for (let j = sites.length - 1; j >= i; j--) { let price = 0; const step = Math.abs(j - i); if (step <= 2) { price = 2; } else if (step <= 4) { price = 3; } else if (step <= 7) { price = 4; } else if (step <= 13) { price = 5; } else if (step <= 16) { price = 6; } else if (step <= 21) { price = 7; } else { price = 8; } data.push({ from: sites[i], to: sites[j], price: price }); } } chart.options({ type: 'cell', autoFit: true, data, coordinate: { type: 'cartesian', }, legend: { color: { position: 'right', title: '票价(元)', flipPage: false, }, }, scale: { color: { domain: [2, 3, 4, 5, 6, 7, 8], range: [ '#ffffcc', '#c7e9b4', '#7fcdbb', '#41b6c4', '#2c7fb8', '#253494', '#081d58', ], }, from: { values: sites, }, to: { values: sites.slice().reverse(), }, }, axis: { x: { title: false, grid: false, tickLine: false, labelRotate: -Math.PI / 4, labelOffset: 5, }, y: { title: false, grid: false, tickLine: false, }, }, encode: { x: 'from', y: 'to', color: 'price', }, style: { inset: 1, stroke: '#fff', strokeWidth: 1, }, labels: [ { text: 'price', style: { fill: (d) => (d.price > 5 ? '#fff' : '#000'), textAlign: 'center', fontSize: 10, }, }, ], }); chart.render(); ``` **分析**: - 站名映射到了 `x`、`y` 轴,以确定位置 - 票价映射到颜色深浅 - 票价数据是模拟的,仅表示大概情况 - 色块图便于快速识别票价区间和站点间的出行成本 例子 2: **考试成绩分析** ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); const students = [ '张三', '李四', '王五', '赵六', '钱七', '孙八', '周九', '吴十', ]; const subjects = ['语文', '数学', '英语', '物理', '化学', '生物']; // 生成成绩数据 const data = []; students.forEach((student) => { subjects.forEach((subject) => { // 随机生成50-100之间的成绩 const score = Math.floor(Math.random() * 51) + 50; data.push({ student, subject, score, }); }); }); chart.options({ type: 'cell', autoFit: true, data, coordinate: { type: 'cartesian', }, legend: { color: { position: 'right', flipPage: false, title: '分数', }, }, scale: { color: { domain: [60, 70, 80, 90], range: ['#F4664A', '#FAAD14', '#30BF78', '#0891B2', '#5B8FF9'], }, }, encode: { x: 'subject', y: 'student', color: 'score', }, style: { inset: 1, }, labels: [ { text: 'score', style: { fill: (d) => (d.score > 85 ? '#fff' : '#000'), textAlign: 'center', }, }, ], }); chart.render(); ``` ### 不适合的场景 例子 1: **数据点过少** 当数据集只有少量的分类交叉点时,色块图可能不是最佳选择,这种情况下简单的表格或条形图可能更为直观。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); // 只有很少的数据点 const data = [ { region: '东部', year: '2022', value: 125 }, { region: '西部', year: '2022', value: 87 }, ]; chart.options({ type: 'cell', autoFit: true, data, encode: { x: 'year', y: 'region', color: 'value', }, style: { inset: 1, }, labels: [ { text: 'value', }, ], }); chart.render(); ``` 这种情况下,使用柱状图会更加直观: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, data: [ { region: '东部', year: '2022', value: 125 }, { region: '西部', year: '2022', value: 87 }, ], encode: { x: 'region', y: 'value', color: 'region', }, labels: [ { text: 'value', position: 'top', }, ], }); chart.render(); ``` 例子 2: **需要展示精确趋势变化** 当需要展示随时间的精确变化趋势时,色块图不如折线图直观。以下是一个使用色块图展示月度数据变化的例子,但这种情况更适合使用折线图。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); const months = [ '一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月', ]; const data = months.map((month, index) => ({ month, metric: '销售额', value: 100 + Math.sin(index / 2) * 50 + Math.random() * 10, })); chart.options({ type: 'cell', autoFit: true, data, encode: { x: 'month', y: 'metric', color: 'value', }, style: { inset: 1, }, labels: [ { text: (d) => d.value.toFixed(0), }, ], }); chart.render(); ``` 使用折线图更适合展示趋势: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); const months = [ '一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月', ]; const data = months.map((month, index) => ({ month, value: 100 + Math.sin(index / 2) * 50 + Math.random() * 10, })); chart.options({ type: 'line', autoFit: true, data, encode: { x: 'month', y: 'value', }, style: { lineWidth: 2, point: { fill: '#1890ff', r: 4, }, }, labels: [ { text: (d) => d.value.toFixed(0), position: 'top', }, ], }); chart.render(); ``` ## 色块图与其他图表的对比 ### 色块图和[热力图](/zh/charts/heatmap/)、[矩形树图](/zh/charts/treemap/) | 图表类型 | 主要特点 | 适用场景 | 数据要求 | | -------- | ------------------------------------ | ---------------------- | ------------------------------ | | 色块图 | 使用规则网格展示两个分类维度的交叉 | 分类数据之间的关系对比 | 两个分类维度和一个数值维度 | | 热力图 | 可以使用连续尺度,展示数据的空间分布 | 展示空间密度或强度分布 | 通常需要连续的或近似连续的数据 | | 矩形树图 | 通过嵌套矩形展示层次结构数据 | 表示层次数据的比例关系 | 需要有明确的层次结构 | ## 色块图最佳实践 ### 设计建议 1. **颜色选择** - 使用顺序色板表示连续数据的大小变化 - 使用发散色板表示与中心值的偏离程度 - 为颜色添加明确的图例和说明 2. **网格设计** - 保持网格单元大小一致,确保可读性 - 在单元格中添加数值标签,增强精确度 - 考虑在单元格之间添加间距,提高辨识度 3. **标签和交互** - 为重要数据点添加标签 - 添加悬停交互,显示详细信息 - 考虑添加排序功能,便于数据比较 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); // 生成示例数据 const rows = ['A', 'B', 'C', 'D', 'E']; const cols = ['P1', 'P2', 'P3', 'P4', 'P5', 'P6']; const data = []; rows.forEach((row) => { cols.forEach((col) => { const value = Math.floor(Math.random() * 100); const baseline = 50; data.push({ row, col, value, diff: value - baseline, performance: value >= baseline ? '达标' : '不达标', }); }); }); console.log('data', data); chart.options({ type: 'cell', autoFit: true, data, scale: { color: { type: 'threshold', domain: [0], range: ['#2B83BA', '#D7191C'], }, }, tooltip: { title: (d) => `${d.row}-${d.col}`, items: [ { field: 'value', name: '数值' }, { field: 'diff', name: '与基准差异' }, { field: 'performance', name: '达标状态' }, ], }, legend: false, interaction: [{ type: 'tooltip' }, { type: 'elementHighlight' }], encode: { x: 'col', y: 'row', color: 'diff', }, style: { inset: 2, }, labels: [ { text: 'value', style: { fill: (d) => (Math.abs(d.diff) > 25 ? '#fff' : '#000'), textAlign: 'center', fontWeight: 'bold', }, }, ], }); chart.render(); ``` ## 色块图的扩展 色块图可以通过多种方式进行扩展,使其更适应不同的数据可视化需求: 1. **分层色块图**:在传统色块图基础上添加层级结构,使用嵌套矩形表示层次关系。 2. **交互式色块图**:增加悬停、点击等交互功能,允许用户探索详细信息。 3. **动态色块图**:通过动画展示随时间变化的数据模式,适合时间序列数据的可视化。 4. **带聚类的色块图**:结合聚类算法,自动对行或列进行重新排序,以便更容易识别相似模式。 5. **带边界标记的色块图**:在特定条件下为色块添加边框或标记,突出显示重要信息。 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 分布曲线图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/distributioncurve/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/distributioncurve.md - Description: 用平滑曲线观察数值分布的形状、集中程度和多个峰值,了解分布曲线图的使用场景及 G2 示例。 --- title: "分布曲线图" description: "用平滑曲线观察数值分布的形状、集中程度和多个峰值,了解分布曲线图的使用场景及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/distributioncurve/" version: "5.4.8" --- distributioncurve ## 分布曲线图的简介 分布曲线图是一种用于展示数据频率分布的统计图表,通过平滑的曲线形式直观地反映数据在不同数值区间的分布密度和集中趋势。它是理解数据分布特征、识别数据模式的重要可视化工具。 分布曲线图特别适合用于探索性数据分析、多组数据分布比较、数据质量检查以及统计分布特征识别等场景,是统计分析和数据科学中的重要工具。 **英文名**:Distribution Curve, Frequency Curve, Density Curve ## 分布曲线图的构成 basic-distribution-curve 分布曲线图由以下元素构成: - 横轴:表示自变量 - 纵轴:表示因变量 - 表示概率分布的分布曲线 ## 分布曲线图的应用场景 ### 适合的场景 例子 1: **展示正态分布数据的分布特征** 分布曲线图非常适合展示正态分布数据,能够清晰地显示数据的集中趋势、对称性和分布形态。 ```ts import { Chart } from '@antv/g2'; // 生成正态分布数据 const generateNormalData = (count, mean, std) => { const data = []; for (let i = 0; i < count; i++) { // 使用Box-Muller变换生成正态分布数据 const u1 = Math.random(); const u2 = Math.random(); const z0 = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2); data.push({ value: mean + std * z0 }); } return data; }; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'line', data: { value: generateNormalData(1000, 100, 15), transform: [ { type: 'custom', callback: (data) => { // 提取数值数据 const values = data.map(d => d.value).filter(v => !isNaN(v)); // 计算数据范围 const min = Math.min(...values); const max = Math.max(...values); const binCount = 30; 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(value => { const binIndex = Math.min( Math.floor((value - min) / binWidth), binCount - 1 ); bins[binIndex].count++; }); // 计算频率密度并生成曲线数据 const total = values.length; return bins.map(bin => ({ x: (bin.x0 + bin.x1) / 2, // 区间中心点 y: bin.count / total, // 频率密度 frequency: bin.count, range: `${bin.x0.toFixed(1)}-${bin.x1.toFixed(1)}`, })); }, }, ], }, encode: { x: 'x', y: 'y', shape: 'smooth', }, style: { lineWidth: 3, stroke: '#1890ff', }, axis: { x: { title: '测量值' }, y: { title: '频率密度' }, }, tooltip: { title: (d) => `区间: ${d.range}`, items: [ { field: 'frequency', name: '频数' }, { field: 'y', name: '频率密度', valueFormatter: '.3f' }, ], }, }); chart.render(); ``` **说明**: - 展示了经典的钟形正态分布曲线 - 可以清晰观察到数据的对称性和集中趋势 - 适合质量控制、生物统计、心理测量等领域的数据分析 例子 2: **多组数据分布比较分析** 当需要比较不同条件或分组下的数据分布时,分布曲线图能够直观地展示各组间的分布差异。 ```ts 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) => { // 按物种分组数据 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)); if (filteredValues.length === 0) return; 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(value => { const binIndex = Math.min( Math.floor((value - min) / binWidth), binCount - 1 ); bins[binIndex].count++; }); // 生成曲线数据 const total = filteredValues.length; bins.forEach(bin => { results.push({ x: (bin.x0 + bin.x1) / 2, y: bin.count / total, species, frequency: bin.count, range: `${bin.x0.toFixed(2)}-${bin.x1.toFixed(2)}`, }); }); }); 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', }, }, tooltip: { title: (d) => `${d.species} - 区间: ${d.range}`, items: [ { field: 'frequency', name: '频数' }, { field: 'y', name: '频率密度', valueFormatter: '.3f' }, ], }, }); chart.render(); ``` **说明**: - 通过不同颜色的曲线比较多个分组的分布特征 - 便于识别各组数据的分布中心、形态和离散程度 - 适合A/B测试分析、实验对照组比较、市场细分分析等场景 ### 不适合的场景 例子 1: **数据量过少时效果不佳** 当数据点过少时,分箱统计可能不够准确,生成的分布曲线可能不能准确反映真实的分布特征。 ```ts import { Chart } from '@antv/g2'; // 模拟少量数据 const smallData = [ 12, 15, 13, 14, 16, 18, 11, 17, 15, 13 ]; const chart = new Chart({ container: 'container', theme: 'classic', height: 250, }); chart.options({ type: 'point', data: smallData.map((value, index) => ({ index: index + 1, value })), encode: { x: 'index', y: 'value', size: 6, }, style: { fill: '#1890ff', fillOpacity: 0.8, }, axis: { x: { title: '数据点序号' }, y: { title: '数值' }, }, title: '数据量少时建议使用散点图', }); chart.render(); ``` **问题说明**: - 数据点太少(少于30个)时,分箱后每个区间的数据过少 - 生成的分布曲线可能出现人工的波动和不规律的形状 - 无法准确反映真实的数据分布特征 - 建议改用散点图、箱形图或增加数据收集 例子 2: **离散型分类数据不适用** 对于离散的分类数据,连续的分布曲线没有实际意义,因为分类之间不存在连续性关系。 ```ts import { Chart } from '@antv/g2'; // 离散分类数据 const discreteData = [ { category: '产品A', sales: 45 }, { category: '产品B', sales: 67 }, { category: '产品C', sales: 33 }, { category: '产品D', sales: 52 }, { category: '产品E', sales: 28 }, ]; const chart = new Chart({ container: 'container', theme: 'classic', height: 250, }); chart.options({ type: 'interval', data: discreteData, encode: { x: 'category', y: 'sales', color: 'category', }, style: { fillOpacity: 0.8, }, axis: { x: { title: '产品类别' }, y: { title: '销售数量' }, }, title: '分类数据建议使用柱状图', }); chart.render(); ``` **问题说明**: - 分类数据之间没有连续性关系,强行连接会产生误导 - 分布曲线无法表达分类数据的真实含义 - 柱状图能更准确地表达分类数据的比较关系 - 适合使用柱状图、饼图等离散图表类型 ## 分布曲线图与其他图表的对比 ### 分布曲线图和[直方图](/zh/charts/histogram/) - 分布曲线图通过平滑的曲线展示连续的频率分布,强调整体趋势 - 直方图通过矩形柱状图展示频数分布,具有明确的区间边界 - 分布曲线图更适合展示分布的整体形态和趋势识别 - 直方图更适合精确的频数统计和区间分析 ### 分布曲线图和[小提琴图](/zh/charts/violin/) - 分布曲线图专注于展示频率分布的曲线形态 - 小提琴图结合了密度分布和箱形图的统计摘要信息 - 分布曲线图更适合纯粹的分布形态分析和多组比较 - 小提琴图更适合需要统计摘要信息的场景 ### 分布曲线图和[折线图](/zh/charts/line/) - 分布曲线图基于频率统计,展示数据的分布特征 - 折线图展示数据随时间或顺序的变化趋势 - 分布曲线图适合统计分析和分布探索 - 折线图适合时间序列分析和趋势追踪 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 点描法地图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/dot-map/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/dot-map.md - Description: 用地图上的点表达地理位置或数量分布,了解点描法地图的表现方式、适用场景及 G2 示例。 --- title: "点描法地图" description: "用地图上的点表达地理位置或数量分布,了解点描法地图的表现方式、适用场景及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/dot-map/" version: "5.4.8" --- dot-map ## 点描法地图的简介 点描法地图是一种通过在地理地图上分布密集的点来表示数据分布密度和模式的地理可视化方法。每个点通常代表一定数量的统计单位(如人口、经济活动、农业产量等),点的分布密度直观反映了数据在地理空间上的集中程度和分布特征。 点描法地图最大的优势在于能够直观地展示数据的空间分布模式,通过点的密集程度可以快速识别数据的聚集区域和稀疏区域。与[分级统计图](/zh/charts/choropleth-map/)相比,点描法地图能够更细致地展示数据在地理空间内的连续分布特征,避免了行政区划边界对数据展示的影响。 点描法地图广泛应用于人口分布分析、经济活动密度展示、自然资源分布、疾病传播分析等多种地理数据可视化场景。 **英文名**:Dot Map, Dot Density Map ## 点描法地图的构成 basic-dot-map | 图表类型 | 基础点描法地图 | | ---------------- | ------------------------------------------------------------------------------------------------------ | | 适合的数据 | 包含地理坐标(经纬度)的数据和数值字段 | | 功能 | 展示数据在地理空间上的分布密度和模式 | | 数据与图形的映射 | 经纬度字段映射到地图位置
数值字段影响点的数量或密度
分类字段可映射到颜色
其他属性可映射到形状 | | 适合的数据条数 | 适合中等到大量数据点(通常 100-10000 个点) | 基础点描法地图的主要组成部分包括: - **地理背景**:提供地理坐标系统的地图背景,如世界地图、国家地图、区域地图等 - **点标记**:表示数据单位的点,位置对应地理坐标 - **点密度**:点的分布密度反映数据的空间分布模式 - **颜色编码**:用不同颜色区分数据分类或数值范围 ## 点描法地图的应用场景 ### 适合的场景 例子 1: **美国机场分布点描法地图** 基于真实的美国地图数据和机场位置数据,展示全美机场的地理分布情况。 ```ts /** * 基于真实美国地图数据的机场分布点描法地图 */ import { Chart } from '@antv/g2'; import { feature } from 'topojson-client'; Promise.all([ fetch('https://assets.antv.antgroup.com/g2/us-10m.json').then((res) => res.json(), ), fetch('https://assets.antv.antgroup.com/g2/airports.json').then((res) => res.json(), ), ]).then((values) => { const [us, airports] = values; const states = feature(us, us.objects.states).features; const chart = new Chart({ container: 'container', autoFit: true, }); chart.options({ type: 'geoView', coordinate: { type: 'albersUsa' }, children: [ { type: 'geoPath', data: states, style: { fill: '#f5f5f5', stroke: '#d0d0d0', lineWidth: 1, }, }, { type: 'point', data: airports, encode: { x: 'longitude', y: 'latitude', color: '#1890ff', shape: 'point', size: 2, }, style: { opacity: 0.8, }, tooltip: { title: 'name', items: [ { name: '机场代码', field: 'iata' }, { name: '经度', field: 'longitude' }, { name: '纬度', field: 'latitude' }, ], }, }, ], }); chart.render(); }); ``` **说明**: - 使用真实的美国地图 TopoJSON 数据和机场坐标数据 - 通过点的分布密度可以观察到机场主要集中在东海岸、西海岸和五大湖地区 - `albersUsa` 投影适合展示美国本土的地理数据 例子 2: **美国人种分布点描法地图** > **注意**:以下示例使用虚构的人种分布数据用于演示目的 ```ts /** * 虚构数据:美国人种分布点描法地图 * 注意:本示例使用虚构的人种分布数据用于演示 */ import { Chart } from '@antv/g2'; import { feature } from 'topojson-client'; Promise.all([ fetch('https://assets.antv.antgroup.com/g2/us-10m.json').then((res) => res.json(), ), ]).then((values) => { const [us] = values; const states = feature(us, us.objects.states).features; // 虚构的美国主要城市人种分布数据(仅用于演示) const ethnicGroups = [ { type: '白人', ratio: 0.6, color: '#1890ff' }, { type: '西班牙裔', ratio: 0.18, color: '#52c41a' }, { type: '非裔美国人', ratio: 0.13, color: '#faad14' }, { type: '亚裔', ratio: 0.06, color: '#f5222d' }, { type: '其他', ratio: 0.03, color: '#722ed1' }, ]; // 美国主要城市坐标(虚构人口数据) const cities = [ { name: '纽约', lng: -74.0059, lat: 40.7128, population: 850 }, { name: '洛杉矶', lng: -118.2437, lat: 34.0522, population: 400 }, { name: '芝加哥', lng: -87.6298, lat: 41.8781, population: 270 }, { name: '休斯顿', lng: -95.3698, lat: 29.7604, population: 230 }, { name: '费城', lng: -75.1652, lat: 39.9526, population: 160 }, { name: '凤凰城', lng: -112.074, lat: 33.4484, population: 170 }, { name: '圣安东尼奥', lng: -98.4936, lat: 29.4241, population: 150 }, { name: '圣地亚哥', lng: -117.1611, lat: 32.7157, population: 140 }, { name: '达拉斯', lng: -96.797, lat: 32.7767, population: 130 }, { name: '圣何塞', lng: -121.8863, lat: 37.3382, population: 100 }, { name: '奥斯汀', lng: -97.7431, lat: 30.2672, population: 95 }, { name: '底特律', lng: -83.0458, lat: 42.3314, population: 67 }, { name: '迈阿密', lng: -80.1918, lat: 25.7617, population: 47 }, { name: '西雅图', lng: -122.3321, lat: 47.6062, population: 75 }, { name: '丹佛', lng: -104.9903, lat: 39.7392, population: 72 }, ]; // 生成虚构的人种分布点数据 const ethnicData = []; cities.forEach((city) => { ethnicGroups.forEach((group) => { // 根据人种比例和城市人口计算点数 const pointCount = Math.floor((city.population * group.ratio) / 10); // 每10万人一个点 for (let i = 0; i < pointCount; i++) { // 在城市周围随机分布点,不同人种有不同的聚集模式 let angle = Math.random() * 2 * Math.PI; let distance = Math.random() * 1.5; // 1.5度范围内分布 // 模拟不同人种的聚集特征 if (group.type === '亚裔') { // 亚裔倾向于聚集在特定区域 distance = Math.random() * 0.8; } else if (group.type === '西班牙裔') { // 西班牙裔在南部城市更集中 if (city.lat < 35) distance = Math.random() * 0.6; } ethnicData.push({ city: city.name, lng: city.lng + Math.cos(angle) * distance, lat: city.lat + Math.sin(angle) * distance, ethnicity: group.type, value: 10, // 每个点代表10万人 }); } }); }); const chart = new Chart({ container: 'container', autoFit: true, }); chart.options({ type: 'geoView', coordinate: { type: 'albersUsa' }, children: [ { type: 'geoPath', data: states, style: { fill: '#f5f5f5', stroke: '#d0d0d0', lineWidth: 1, }, }, { type: 'point', data: ethnicData, encode: { x: 'lng', y: 'lat', color: 'ethnicity', shape: 'ethnicity', size: 2, }, style: { opacity: 0.7, stroke: 'white', lineWidth: 0.5, }, scale: { color: { domain: ['白人', '西班牙裔', '非裔美国人', '亚裔', '其他'], range: ['#1890ff', '#52c41a', '#faad14', '#f5222d', '#722ed1'], }, shape: { domain: ['白人', '西班牙裔', '非裔美国人', '亚裔', '其他'], range: ['point', 'square', 'triangle', 'diamond', 'cross'], }, }, tooltip: { title: 'city', items: [ { name: '人种', field: 'ethnicity' }, { name: '人口', field: 'value', valueFormatter: (v) => `${v}万人` }, ], }, }, ], }); chart.render(); }); ``` **说明**: - 使用虚构的人种分布数据展示美国主要城市的人口构成 - 不同颜色和形状代表不同人种群体 - 通过点的分布密度可以观察各地区的人种分布特征和聚集模式 - 模拟了真实的人种地理分布规律,如亚裔在西海岸聚集、西班牙裔在南部更集中等 ### 不适合的场景 例子 1: **不适合精确数值比较** 点描法地图侧重于显示分布模式和密度,不适合需要精确比较具体数值的场景。如果需要精确对比不同地区的数值大小,应使用[分级统计地图](/zh/charts/choropleth-map/)或[带气泡的地图](/zh/charts/bubble-map/)。 例子 2: **不适合展示连续表面数据** 对于温度、降水量等连续变化的表面数据,点描法地图的离散特性无法很好地表现数据的连续性,此时应考虑使用[热力图](/zh/charts/heatmap/)或[等高线图](/zh/charts/contourline/)。 ## 点描法地图与其他图表的对比 ### 点描法地图与[带气泡的地图](/zh/charts/bubble-map/) - 点描法地图使用密集的小点表示数据分布,适合展示分布模式和密度 - 气泡地图使用大小不同的圆圈表示数值大小,适合精确比较不同地区的数据量级 - 点描法地图更适合展示连续分布,气泡地图更适合展示离散的数据点 ### 点描法地图与[分级统计地图](/zh/charts/choropleth-map/) - 点描法地图通过点的分布密度表示数据特征,不受行政区划限制 - 分级统计图通过区域颜色表示数据范围,与行政区划边界相关 - 点描法地图更适合展示连续的空间分布,分级统计图更适合展示区域间的对比 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 直方图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/histogram/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/histogram.md - Description: 将连续数据划分为区间并统计频数,了解直方图与柱状图的区别、分箱选择及 G2 示例。 --- title: "直方图" description: "将连续数据划分为区间并统计频数,了解直方图与柱状图的区别、分箱选择及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/histogram/" version: "5.4.8" --- histogram ## 直方图的简介 直方图,形状类似[柱状图](/zh/charts/bar/)却有着与柱状图完全不同的含义。直方图牵涉统计学的概念,首先要对数据进行分组,然后统计每个分组内数据元的数量。在平面直角坐标系中,横轴标出每个组的端点,纵轴表示频数,每个矩形的高代表对应的频数,称这样的统计图为频数分布直方图。频数分布直方图需要经过频数乘以组距的计算过程才能得出每个分组的数量,同一个直方图的组距是一个固定不变的值,所以如果直接用纵轴表示数量,每个矩形的高代表对应的数据元数量,既能保持分布状态不变,又能直观的看出每个分组的数量。本文的例子全部使用纵轴表示数量的非标准直方图绘制。 **相关概念**: - 组数:在统计数据时,我们把数据按照不同的范围分成几个组,分成的组的个数称为组数 - 组距:每一组两个端点的差 - 频数:分组内的数据元的数量除以组距 **直方图的作用**: - 能够显示各组频数或数量分布的情况 - 易于显示各组之间频数或数量的差别 通过直方图还可以观察和估计哪些数据比较集中,异常或者孤立的数据分布在何处。 **英文名**:Histogram ## 如何绘制直方图 在 G2 中绘制直方图需要使用以下核心要素: ### 1. 使用 rect 标记 直方图需要使用 **rect 标记**而非 interval 标记。这是因为: - rect 标记支持 `x` 和 `x1` 两个通道,可以精确表示数据区间的起始和结束位置 - 每个矩形会跨越从 `x` 到 `x1` 的区间,符合直方图的数学定义 - interval 标记只支持单一的 `x` 值,柱子会对齐在刻度点上,不适合表示连续区间 ### 2. 使用 binX 转换 **binX 转换**是绘制直方图的关键,它的作用是: - 自动将连续数值数据划分为多个区间(分箱) - 统计每个区间内的数据数量或其他聚合值 - 输出每个区间的起始位置(`x`)和结束位置(`x1`) **基本用法**: ```javascript .transform({ type: 'binX', y: 'count', // 统计每个区间的数量 thresholds: 20, // 可选:指定分箱数量 }) ``` ### 3. 完整示例 ```javascript import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', autoFit: true, }); chart.options({ type: 'rect', // 使用 rect 标记 data: { type: 'fetch', value: 'data.json', }, encode: { x: 'value', // 连续数值字段 y: 'count', // 频数 }, transform: [ { type: 'binX', y: 'count' } // binX 转换 ], }); chart.render(); ``` ## 直方图的构成 ### 频数分布直方图 basic-histogram | 图表类型 | 频数分布直方图 | | ---------------- | ------------------------------------------------------------------------------------------------------------------------ | | 适合的数据 | 列表:一个连续数据字段、一个分类字段(可选) | | 功能 | 展示数据在不同区间内的分布情况 | | 数据与图形的映射 | 分组数据字段(统计结果)映射到横轴的位置
频数字段(统计结果)映射到矩形的高度
分类数据可以设置颜色增强分类的区分度 | | 适合的数据条数 | 不低于 50 条数据 | --- ### 非标准的直方图 density-histogram | 图表类型 | 非标准的直方图 | | ---------------- | ------------------------------------------------------------------------------------------------------------------------ | | 适合的数据 | 列表:一个连续数据字段、一个分类字段(可选) | | 功能 | 展示数据在不同区间内的分布情况 | | 数据与图形的映射 | 分组数据字段(统计结果)映射到横轴的位置
数量字段(统计结果)映射到矩形的高度
分类数据可以设置颜色增强分类的区分度 | | 适合的数据条数 | 不低于 50 条数据 | ## 直方图的应用场景 ### 适合的场景 例子 1: **数据分布的统计分析** 下图是一个钻石重量分布的直方图,展示了钻石重量在不同区间的分布情况。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', 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' }, ], scale: { y: { nice: true }, }, axis: { x: { title: '钻石重量(克拉)' }, y: { title: '频数' }, }, style: { fill: '#1890FF', fillOpacity: 0.9, }, }); chart.render(); ``` **说明**: - 使用 `rect` 标记配合 `binX` 转换绘制直方图 - `binX` 转换自动将 `carat` 字段分箱,并统计每个区间的频数 - 每个矩形跨越一个数值区间(如 0.2-0.3, 0.3-0.4),表示数据是连续分布的 例子 2: **使用不同的分箱方式** 直方图的关键是如何划分数据区间(即"分箱"),不同的分箱方式会影响对数据分布的理解。下图使用了自定义的分箱数量。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', 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', thresholds: 30 }, // 指定分箱数量 ], scale: { y: { nice: true }, }, axis: { x: { title: '钻石重量(克拉)' }, y: { title: '频数' }, }, style: { fill: '#1890FF', fillOpacity: 0.9, }, }); chart.render(); ``` **说明**: - 使用 `transform: { type: 'binX', thresholds: 30 }` 指定分箱数量为 30 - 分箱数量的选择会影响分布的细节展示,较多的箱数可以显示更细致的分布情况 - 较少的箱数则可以突出主要分布趋势 ### 不适合的场景 例子 1: **不适合展示分类数据的比较** 直方图针对的是连续型数值数据的分布,不适合用于非数值型的分类数据比较。对于分类数据的计数统计,应该使用普通柱状图。 例子 2: **不适合展示时间序列的趋势** 直方图侧重于展示数据的分布特征,而非随时间变化的趋势。如果需要展示数据随时间的变化趋势,应使用折线图或面积图等时序图表。 ## 直方图的扩展 ### 多分布直方图 多分布直方图可以在同一坐标系中展示多个数据集的分布情况,便于比较不同数据集的分布特征。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); chart.options({ type: 'rect', data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/diamond.json', transform: [ { type: 'map', callback: (d) => ({ ...d, group: d.cut === 'Ideal' ? 'Ideal' : 'Others', }), }, ], }, encode: { x: 'price', y: 'count', color: 'group', }, transform: [ { type: 'binX', y: 'count', thresholds: 30, groupBy: ['group'] }, ], scale: { y: { nice: true }, color: { range: ['#1890FF', '#FF6B3B'] }, }, axis: { x: { title: '价格(美元)' }, y: { title: '频数' }, }, style: { fillOpacity: 0.7, }, legend: true, }); chart.render(); ``` **说明**: - 通过 `color: 'group'` 和 `groupBy: ['group']` 实现多分布的对比 - 使用不同颜色和透明度,便于观察不同组别的数据分布差异 ## 直方图与其他图表的对比 ### 直方图和[柱状图](/zh/charts/bar/) - 直方图用于展示连续型数值数据的分布,强调数据分布形态 - 柱状图用于比较不同类别或时间点的数值大小,强调类别间数值对比 - 直方图的条形间无间隔,柱状图的柱子间有间隔 ### 直方图和[箱型图](/zh/charts/boxplot/) - 直方图通过频数分布展示数据集中趋势和离散程度 - 箱型图通过四分位数展示数据的集中和离散情况,更便于识别异常值 - 直方图提供更详细的分布形态,箱型图则提供更简洁的统计概括 ### 直方图和[折线图](/zh/charts/line/)、[面积图](/zh/charts/area/) - 直方图侧重于展示数据的分布状况和形态特征 - 折线图和面积图侧重于展示数据随时间的变化趋势 - 直方图适合单一时间点的数据分析,折线图和面积图适合多时间点的连续数据分析 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 折线图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/line/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/line.md - Description: 连接连续观测值以展示变化趋势,了解折线图的数据要求、多系列比较及 G2 绘制示例。 --- title: "折线图" description: "连接连续观测值以展示变化趋势,了解折线图的数据要求、多系列比较及 G2 绘制示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/line/" version: "5.4.8" --- line ## 折线图的简介 折线图是一种将数据点按时间或类别顺序连接成一条折线,用于显示数据随时间或有序类别变化趋势的统计图表。通过折线的上升或下降,可以直观地表现数据变化的速度、幅度、范围及其周期性。 折线图特别适合展示连续时间序列的数据变化,能够有效地反映数据的趋势、波动、周期性和异常点。在多系列数据比较时,折线图通过不同颜色或样式的线条,清晰地展示各数据系列间的对比和关系。 相比于面积图,折线图更侧重于展示数据变化的趋势和轨迹,而非总量;相比于柱状图,折线图更适合展示连续变化的趋势,而非离散的数值比较。 **英文名**:Line Chart ## 折线图的构成 ### 基础折线图 basic-line | 图表类型 | 基础折线图 | | ---------------- | ------------------------------------------------------------------------------------------ | | 适合的数据 | 时间序列数据:一个有序/连续数据字段(通常是时间)、一个连续数据字段 | | 功能 | 展示数据随时间或有序维度的变化趋势 | | 数据与图形的映射 | 时间字段映射到横轴的位置
数值字段映射到纵轴的高度
各数据点通过线条连接,展示变化轨迹 | | 适合的场景 | 单一数据系列随时间的变化趋势 | --- ### 多系列折线图 multi-series-line | 图表类型 | 多系列折线图 | | ---------------- | --------------------------------------------------------------------------------------- | | 适合的数据 | 多序列时间数据:一个有序/连续数据字段(通常是时间)、一个连续数据字段、一个分类数据字段 | | 功能 | 展示多个数据系列随时间变化的趋势,便于比较不同系列间的关系 | | 数据与图形的映射 | 时间字段映射到横轴的位置
数值字段映射到纵轴的高度
分类字段映射到不同颜色的线条 | | 适合的场景 | 多数据系列随时间的变化趋势对比 | ## 折线图的应用场景 ### 适合的场景 例子 1: **适合展示连续时间序列的趋势** 下图是一个股票价格走势的折线图,展示了某公司股票价格随时间的变化趋势。 | date(日期) | close(收盘价) | | ------------ | --------------- | | 2015/1/5 | 121.73 | | 2015/1/6 | 115.07 | | 2015/1/7 | 116.75 | | ... | ... | ```ts 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/aapl.json', transform: [ { type: 'map', callback: (d) => ({ ...d, date: new Date(d.date), }), }, ], }, encode: { x: 'date', y: 'close' }, axis: { x: { title: null, }, y: { title: null, }, }, style: { lineWidth: 2, stroke: '#1890ff', }, }); chart.render(); ``` **说明** : - `date` 字段,映射到横轴的位置,表示时间的先后顺序 - `close` 字段,映射到纵轴的高度,展示了价格随时间的变化趋势 - 通过线条连接各个数据点,清晰地展示数据的变化轨迹 例子 2: **适合展示多个数据系列的对比趋势** 多系列折线图可以同时展示多个数据系列随时间的变化,便于进行比较分析。下图展示了不同地区失业率的变化趋势。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'line', autoFit: true, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/728a4bdc-9d0b-49e0-a92f-6320a6cddeed.csv', }, encode: { x: 'date', y: 'unemployment', color: 'division', }, axis: { x: { title: null, }, y: { title: null, }, }, }); chart.render(); ``` **说明**: - `date` 字段映射到横轴,表示时间维度 - `unemployment` 字段映射到纵轴,表示失业率 - `division` 字段映射到颜色,区分不同地区 - 多条折线便于直观比较不同地区失业率的变化趋势和差异 例子 3: **展示数据的细微变化和波动** 折线图可以清晰地展示数据的细微变化和波动,特别是当数据点较多且变化频繁时。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'line', autoFit: true, data: [ { month: 'Jan', city: 'Tokyo', temperature: 7 }, { month: 'Jan', city: 'London', temperature: 3.9 }, { month: 'Feb', city: 'Tokyo', temperature: 6.9 }, { month: 'Feb', city: 'London', temperature: 4.2 }, { month: 'Mar', city: 'Tokyo', temperature: 9.5 }, { month: 'Mar', city: 'London', temperature: 5.7 }, { month: 'Apr', city: 'Tokyo', temperature: 14.5 }, { month: 'Apr', city: 'London', temperature: 8.5 }, { month: 'May', city: 'Tokyo', temperature: 18.4 }, { month: 'May', city: 'London', temperature: 11.9 }, { month: 'Jun', city: 'Tokyo', temperature: 21.5 }, { month: 'Jun', city: 'London', temperature: 15.2 }, { month: 'Jul', city: 'Tokyo', temperature: 25.2 }, { month: 'Jul', city: 'London', temperature: 17 }, { month: 'Aug', city: 'Tokyo', temperature: 26.5 }, { month: 'Aug', city: 'London', temperature: 16.6 }, { month: 'Sep', city: 'Tokyo', temperature: 23.3 }, { month: 'Sep', city: 'London', temperature: 14.2 }, { month: 'Oct', city: 'Tokyo', temperature: 18.3 }, { month: 'Oct', city: 'London', temperature: 10.3 }, { month: 'Nov', city: 'Tokyo', temperature: 13.9 }, { month: 'Nov', city: 'London', temperature: 6.6 }, { month: 'Dec', city: 'Tokyo', temperature: 9.6 }, { month: 'Dec', city: 'London', temperature: 4.8 }, ], encode: { x: 'month', y: 'temperature', color: 'city' }, axis: { y: { title: null, labelFormatter: (d) => d + '°C', }, x: { title: null, }, }, style: { lineWidth: 2, }, }); chart.render(); ``` **说明**: - 折线图清晰地展示了东京和伦敦全年温度的变化曲线 - 通过不同颜色的折线,可以直观地比较两个城市温度的差异和变化模式 ### 不适合的场景 例子 1: **不适合展示离散类别的比较** 当水平轴的数据类型为无序的分类或者垂直轴的数据类型为连续时间时,不适合使用折线图。 我们以一个不同游戏类型的销量对比的场景为例,对于表示分类对比的数据时,我们更应该使用[柱状图](/zh/charts/bar/),而不是折线图。 **错误的做法** ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', height: 250, }); chart.options({ type: 'line', autoFit: true, data: [ { genre: 'Sports', sold: 27500 }, { genre: 'Strategy', sold: 11500 }, { genre: 'Action', sold: 6000 }, { genre: 'Shooter', sold: 3500 }, { genre: 'Other', sold: 1500 }, ], encode: { x: 'genre', y: 'sold' }, axis: { x: { title: '游戏类型', }, y: { title: '销售量', labelFormatter: (val) => val / 1000 + 'k', }, }, style: { lineWidth: 3, stroke: '#1890ff', }, }); chart.render(); ``` **正确的做法** ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', height: 250, }); chart.options({ type: 'interval', autoFit: true, data: [ { genre: 'Sports', sold: 27500 }, { genre: 'Strategy', sold: 11500 }, { genre: 'Action', sold: 6000 }, { genre: 'Shooter', sold: 3500 }, { genre: 'Other', sold: 1500 }, ], encode: { x: 'genre', y: 'sold', color: 'genre' }, axis: { x: { title: '游戏类型', }, y: { title: '销售量', labelFormatter: (val) => val / 1000 + 'k', }, }, }); chart.render(); ``` **问题说明**: - 游戏类型之间没有自然的顺序或连续性关系 - 连线暗示了类别间的趋势关系,但实际上不存在这种关系 - 折线可能误导读者认为从"Sports"到"Strategy"存在某种变化趋势 - 柱状图能更准确地表达各游戏类型的独立销量比较 例子 2: **数据点较少或变化不明显时效果不佳** 当数据点较少或变化不明显时,折线图可能无法充分发挥其展示趋势变化的优势。这种情况下,可以考虑使用柱状图或点图来强调个别数据点的比较。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'line', autoFit: true, data: [ { quarter: 'Q1', value: 100 }, { quarter: 'Q2', value: 101 }, { quarter: 'Q3', value: 99 }, { quarter: 'Q4', value: 102 }, ], encode: { x: 'quarter', y: 'value' }, axis: { x: { title: null }, y: { title: null }, }, style: { lineWidth: 2, stroke: '#1890ff', }, }); chart.render(); ``` **问题说明**: - 数据点太少(仅 4 个),无法形成明显的趋势线 - 数值变化很小(99-102),折线看起来几乎是平的 - 难以从中获得有意义的趋势信息 - 柱状图或点图能更好地强调各个独立数值的比较 ## 折线图的扩展 ### 阶梯折线图 阶梯折线图使用水平和垂直线段连接数据点,形成阶梯状的效果,适合展示在特定时间点发生突变的数据。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'line', autoFit: true, data: [ { month: 'Jan', value: 51 }, { month: 'Feb', value: 91 }, { month: 'Mar', value: 34 }, { month: 'Apr', value: 47 }, { month: 'May', value: 63 }, { month: 'June', value: 58 }, { month: 'July', value: 56 }, { month: 'Aug', value: 77 }, { month: 'Sep', value: 99 }, { month: 'Oct', value: 106 }, { month: 'Nov', value: 88 }, { month: 'Dec', value: 56 }, ], encode: { x: 'month', y: 'value', shape: 'hv' }, axis: { x: { title: null, }, y: { title: null, }, }, style: { lineWidth: 2, stroke: '#1890ff', }, }); chart.render(); ``` **说明**: - 使用 `encode: { shape: 'hv' }` 指定阶梯状的线条形状 - 适合展示在特定时间点发生变化且保持稳定直到下一变化点的数据,如电费阶梯价格、库存水平变化等 ### 虚线折线图 虚线折线图通过不同的线型来区分不同的数据系列或表示特定含义,如预测值、参考线等。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'view', autoFit: true, data: [ { year: '1991', value: 3, type: '实际值' }, { year: '1992', value: 4, type: '实际值' }, { year: '1993', value: 3.5, type: '实际值' }, { year: '1994', value: 5, type: '实际值' }, { year: '1995', value: 4.9, type: '实际值' }, { year: '1996', value: 6, type: '实际值' }, { year: '1997', value: 7, type: '实际值' }, { year: '1998', value: 9, type: '实际值' }, { year: '1999', value: 13, type: '实际值' }, { year: '1999', value: 13, type: '预测值' }, { year: '2000', value: 16, type: '预测值' }, { year: '2001', value: 21, type: '预测值' }, { year: '2002', value: 25, type: '预测值' }, { year: '2003', value: 30, type: '预测值' }, ], children: [ { type: 'line', encode: { x: 'year', y: 'value', color: 'type' }, style: { lineWidth: 2, lineDash: (d) => (d[0].type === '预测值' ? [4, 4] : null), }, }, { type: 'point', encode: { x: 'year', y: 'value', color: 'type', shape: 'circle' }, }, ], scale: { color: { range: ['#1890FF', '#FF6B3B'], }, }, axis: { x: { title: null }, y: { title: null }, }, }); chart.render(); ``` **说明**: - 使用 `lineDash: (d) => (d[0].type === '预测值' ? [4, 4] : null)` 为预测值设置虚线样式 - 实线表示已有的历史数据,虚线表示预测或估计的数据,增强了数据的可读性 ## 折线图与其他图表的对比 ### 折线图和[面积图](/zh/charts/area/) - 折线图侧重于展示数据变化的趋势和轨迹,关注变化的方向和速度 - 面积图通过填充区域强调数量的视觉效果,更适合展示总量变化 - 当需要突出变化趋势时选择折线图,当需要强调数量级变化时选择面积图 ### 折线图和[堆叠面积图](/zh/charts/stacked-area/) - 折线图适合展示多个独立数据系列的变化趋势,各系列间可以直接比较 - 堆叠面积图显示各部分对总体的贡献,强调累积效果和部分与整体的关系 - 当需要比较多个独立趋势时选择折线图,当需要展示部分构成总体的变化时选择堆叠面积图 ### 折线图和[柱状图](/zh/charts/bar/) - 折线图更适合展示连续时间序列的变化趋势,强调数据的流动性和连续性 - 柱状图更适合展示离散分类之间的数值比较,强调各个独立数值的大小对比 - 当数据点较多且关注变化趋势时选择折线图,当数据点较少且关注具体数值比较时选择柱状图 ### 折线图和[散点图](/zh/charts/scatter-plot/) - 折线图通过连线展示数据的变化趋势和连续性,强调数据点间的关系 - 散点图展示各个独立数据点的分布,不强调数据点间的连续关系 - 当需要展示数据趋势时选择折线图,当需要展示数据分布或相关性时选择散点图 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 分组柱状图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/multi-set-bar/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/multi-set-bar.md - Description: 将多个系列的柱子按类别并排放置,了解分组柱状图的比较方式、适用场景及 G2 示例。 --- title: "分组柱状图" description: "将多个系列的柱子按类别并排放置,了解分组柱状图的比较方式、适用场景及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/multi-set-bar/" version: "5.4.8" --- multi-set-bar ## 分组柱状图的简介 分组柱状图,又叫聚合柱状图。当使用者需要在同一个轴上显示各个分类下不同的分组时,需要用到分组柱状图。 跟柱状图类似,使用柱子的高度来映射和对比数据值。每个分组中的柱子使用不同的颜色或者相同颜色不同透明的方式区别各个分类,各个分组之间需要保持间隔。 分组柱状图经常用于不同组间数据的比较,这些组都包含了相同分类的数据。 但是仍需要注意,避免分组中分类过多的情况,分类过多会导致分组中柱子过多过密,非常影响图表可读性。 **英文名**:Multi-set Bar Chart ## 分组柱状图的构成 | 图表类型 | 分组柱状图 | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | 适合的数据 | 列表:两个分类数据字段、一个连续数据字段 | | 功能 | 其中一个分类数据字段作为分组,可以对比相同分组下不同分类的数据大小,也可以对比不同分组下,相同分类的数据大小 | | 数据与图形的映射 | 其中一个分类数据字段映射到坐标轴的位置用于分组,另一个分类数据在同一个分组内错开,并通过不同的颜色来区分连续数据字段映射到矩形的长度 | | 适合的数据条数 | 分组个数不要超过 12 个,每个分组下的分类不要超过 6 个 | ## 分组柱状图的应用场景 ### 适合的场景 例子 1:**对比不同分组内相同分类的大小,对比相同分组内不同分类的大小** 下图对比了『我是土豪』游戏公司在 2001、2002、2003 这三年的各类游戏的销量情况。 水平轴显示的是不同的游戏类型,每种游戏类型作为一个柱状图的分组,在每一个分组内对比不同年份的销售数量。 ```ts import { Chart } from '@antv/g2'; const data = [ { year: '2001', genre: 'Sports', sold: 27500 }, { year: '2001', genre: 'Strategy', sold: 11500 }, { year: '2001', genre: 'Action', sold: 6000 }, { year: '2001', genre: 'Shooter', sold: 3500 }, { year: '2001', genre: 'Other', sold: 1500 }, { year: '2002', genre: 'Sports', sold: 29500 }, { year: '2002', genre: 'Strategy', sold: 10500 }, { year: '2002', genre: 'Action', sold: 8000 }, { year: '2002', genre: 'Shooter', sold: 4500 }, { year: '2002', genre: 'Other', sold: 1800 }, { year: '2003', genre: 'Sports', sold: 30500 }, { year: '2003', genre: 'Strategy', sold: 12500 }, { year: '2003', genre: 'Action', sold: 4000 }, { year: '2003', genre: 'Shooter', sold: 6500 }, { year: '2003', genre: 'Other', sold: 2000 }, ]; const chart = new Chart({ container: 'container', autoFit: true, height: 400, }); chart.options({ type: 'interval', data, encode: { x: 'genre', y: 'sold', color: 'year', }, transform: [{ type: 'dodgeX' }], axis: { y: { title: '游戏销售量' }, x: { title: '游戏类型' }, }, }); chart.render(); ``` 说明: - `genre`,使用横轴的**位置**来区分不同的游戏类型 - `year`,使用**颜色**和在同一个游戏类型内的错开的**位置**,来区分不同年份的游戏销售 - `sold`,使用矩形的**长度**对比不同游戏、不同年份的销量 ### 不适合的场景 例子 1:**分组过多、分类过多** 当分组和分类过多时会导致柱子过多过密,可读性不佳,如下图所示: ```ts import { Chart } from '@antv/g2'; // 完整的barley数据集 - 演示分组过多的问题 const barleyData = [ { yield: 27, variety: 'Manchuria', year: 1931, site: 'University Farm' }, { yield: 48.87, variety: 'Manchuria', year: 1931, site: 'Waseca' }, { yield: 27.43, variety: 'Manchuria', year: 1931, site: 'Morris' }, { yield: 39.93, variety: 'Manchuria', year: 1931, site: 'Crookston' }, { yield: 32.97, variety: 'Manchuria', year: 1931, site: 'Grand Rapids' }, { yield: 28.97, variety: 'Manchuria', year: 1931, site: 'Duluth' }, { yield: 43.07, variety: 'Glabron', year: 1931, site: 'University Farm' }, { yield: 55.2, variety: 'Glabron', year: 1931, site: 'Waseca' }, { yield: 28.77, variety: 'Glabron', year: 1931, site: 'Morris' }, { yield: 38.13, variety: 'Glabron', year: 1931, site: 'Crookston' }, { yield: 29.13, variety: 'Glabron', year: 1931, site: 'Grand Rapids' }, { yield: 29.67, variety: 'Glabron', year: 1931, site: 'Duluth' }, { yield: 35.13, variety: 'Svansota', year: 1931, site: 'University Farm' }, { yield: 47.33, variety: 'Svansota', year: 1931, site: 'Waseca' }, { yield: 25.77, variety: 'Svansota', year: 1931, site: 'Morris' }, { yield: 40.47, variety: 'Svansota', year: 1931, site: 'Crookston' }, { yield: 29.67, variety: 'Svansota', year: 1931, site: 'Grand Rapids' }, { yield: 25.7, variety: 'Svansota', year: 1931, site: 'Duluth' }, { yield: 39.9, variety: 'Velvet', year: 1931, site: 'University Farm' }, { yield: 50.23, variety: 'Velvet', year: 1931, site: 'Waseca' }, { yield: 26.13, variety: 'Velvet', year: 1931, site: 'Morris' }, { yield: 41.33, variety: 'Velvet', year: 1931, site: 'Crookston' }, { yield: 23.03, variety: 'Velvet', year: 1931, site: 'Grand Rapids' }, { yield: 26.3, variety: 'Velvet', year: 1931, site: 'Duluth' }, { yield: 36.57, variety: 'Trebi', year: 1931, site: 'University Farm' }, { yield: 63.83, variety: 'Trebi', year: 1931, site: 'Waseca' }, { yield: 43.77, variety: 'Trebi', year: 1931, site: 'Morris' }, { yield: 46.93, variety: 'Trebi', year: 1931, site: 'Crookston' }, { yield: 29.77, variety: 'Trebi', year: 1931, site: 'Grand Rapids' }, { yield: 33.93, variety: 'Trebi', year: 1931, site: 'Duluth' }, ]; const chart = new Chart({ container: 'container', autoFit: true, paddingLeft: 50, }); chart.options({ type: 'interval', data: barleyData, encode: { x: 'site', y: 'yield', color: 'variety', }, transform: [{ type: 'dodgeX' }], axis: { x: { labelAutoHide: true, labelAutoRotate: true, }, }, tooltip: { title: 'site', }, }); chart.render(); ``` **优化方式 1:仅显示重要数据** 通过筛选或聚合数据,只展示排名靠前或最重要的几个分组: ```ts import { Chart } from '@antv/g2'; // 筛选后的数据 - 只显示前3个地点,演示优化效果 const filteredBarleyData = [ { yield: 27, variety: 'Manchuria', year: 1931, site: 'University Farm' }, { yield: 48.87, variety: 'Manchuria', year: 1931, site: 'Waseca' }, { yield: 27.43, variety: 'Manchuria', year: 1931, site: 'Morris' }, { yield: 43.07, variety: 'Glabron', year: 1931, site: 'University Farm' }, { yield: 55.2, variety: 'Glabron', year: 1931, site: 'Waseca' }, { yield: 28.77, variety: 'Glabron', year: 1931, site: 'Morris' }, { yield: 35.13, variety: 'Svansota', year: 1931, site: 'University Farm' }, { yield: 47.33, variety: 'Svansota', year: 1931, site: 'Waseca' }, { yield: 25.77, variety: 'Svansota', year: 1931, site: 'Morris' }, { yield: 39.9, variety: 'Velvet', year: 1931, site: 'University Farm' }, { yield: 50.23, variety: 'Velvet', year: 1931, site: 'Waseca' }, { yield: 26.13, variety: 'Velvet', year: 1931, site: 'Morris' }, { yield: 36.57, variety: 'Trebi', year: 1931, site: 'University Farm' }, { yield: 63.83, variety: 'Trebi', year: 1931, site: 'Waseca' }, { yield: 43.77, variety: 'Trebi', year: 1931, site: 'Morris' }, ]; const chart = new Chart({ container: 'container', autoFit: true, paddingLeft: 50, }); chart.options({ type: 'interval', data: filteredBarleyData, encode: { x: 'site', y: 'yield', color: 'variety', }, transform: [{ type: 'dodgeX' }], axis: { x: { labelAutoHide: true, labelAutoRotate: true, }, }, tooltip: { title: 'site', }, }); chart.render(); ``` **优化方式 2:使用堆叠柱状图** 如果需要展示完整数据,推荐使用堆叠柱状图来减少图表宽度,提高可读性: ```ts import { Chart } from '@antv/g2'; // 完整的barley数据集 - 演示堆叠柱状图优化效果 const barleyData = [ { yield: 27, variety: 'Manchuria', year: 1931, site: 'University Farm' }, { yield: 48.87, variety: 'Manchuria', year: 1931, site: 'Waseca' }, { yield: 27.43, variety: 'Manchuria', year: 1931, site: 'Morris' }, { yield: 39.93, variety: 'Manchuria', year: 1931, site: 'Crookston' }, { yield: 32.97, variety: 'Manchuria', year: 1931, site: 'Grand Rapids' }, { yield: 28.97, variety: 'Manchuria', year: 1931, site: 'Duluth' }, { yield: 43.07, variety: 'Glabron', year: 1931, site: 'University Farm' }, { yield: 55.2, variety: 'Glabron', year: 1931, site: 'Waseca' }, { yield: 28.77, variety: 'Glabron', year: 1931, site: 'Morris' }, { yield: 38.13, variety: 'Glabron', year: 1931, site: 'Crookston' }, { yield: 29.13, variety: 'Glabron', year: 1931, site: 'Grand Rapids' }, { yield: 29.67, variety: 'Glabron', year: 1931, site: 'Duluth' }, { yield: 35.13, variety: 'Svansota', year: 1931, site: 'University Farm' }, { yield: 47.33, variety: 'Svansota', year: 1931, site: 'Waseca' }, { yield: 25.77, variety: 'Svansota', year: 1931, site: 'Morris' }, { yield: 40.47, variety: 'Svansota', year: 1931, site: 'Crookston' }, { yield: 29.67, variety: 'Svansota', year: 1931, site: 'Grand Rapids' }, { yield: 25.7, variety: 'Svansota', year: 1931, site: 'Duluth' }, { yield: 39.9, variety: 'Velvet', year: 1931, site: 'University Farm' }, { yield: 50.23, variety: 'Velvet', year: 1931, site: 'Waseca' }, { yield: 26.13, variety: 'Velvet', year: 1931, site: 'Morris' }, { yield: 41.33, variety: 'Velvet', year: 1931, site: 'Crookston' }, { yield: 23.03, variety: 'Velvet', year: 1931, site: 'Grand Rapids' }, { yield: 26.3, variety: 'Velvet', year: 1931, site: 'Duluth' }, { yield: 36.57, variety: 'Trebi', year: 1931, site: 'University Farm' }, { yield: 63.83, variety: 'Trebi', year: 1931, site: 'Waseca' }, { yield: 43.77, variety: 'Trebi', year: 1931, site: 'Morris' }, { yield: 46.93, variety: 'Trebi', year: 1931, site: 'Crookston' }, { yield: 29.77, variety: 'Trebi', year: 1931, site: 'Grand Rapids' }, { yield: 33.93, variety: 'Trebi', year: 1931, site: 'Duluth' }, ]; const chart = new Chart({ container: 'container', autoFit: true, paddingLeft: 50, }); chart.options({ type: 'interval', data: barleyData, encode: { x: 'site', y: 'yield', color: 'variety', }, transform: [{ type: 'stackY' }], axis: { x: { labelAutoHide: true, labelAutoRotate: true, }, }, tooltip: { title: 'site', }, }); chart.render(); ``` **优化方式 3:使用横向堆叠柱状图** 对于大量数据的情况,横向堆叠柱状图是更好的选择,可以充分利用垂直空间展示更多分类: ```ts import { Chart } from '@antv/g2'; // 完整的barley数据集 - 演示横向堆叠柱状图的优化效果 const barleyData = [ { yield: 27, variety: 'Manchuria', year: 1931, site: 'University Farm' }, { yield: 48.87, variety: 'Manchuria', year: 1931, site: 'Waseca' }, { yield: 27.43, variety: 'Manchuria', year: 1931, site: 'Morris' }, { yield: 39.93, variety: 'Manchuria', year: 1931, site: 'Crookston' }, { yield: 32.97, variety: 'Manchuria', year: 1931, site: 'Grand Rapids' }, { yield: 28.97, variety: 'Manchuria', year: 1931, site: 'Duluth' }, { yield: 43.07, variety: 'Glabron', year: 1931, site: 'University Farm' }, { yield: 55.2, variety: 'Glabron', year: 1931, site: 'Waseca' }, { yield: 28.77, variety: 'Glabron', year: 1931, site: 'Morris' }, { yield: 38.13, variety: 'Glabron', year: 1931, site: 'Crookston' }, { yield: 29.13, variety: 'Glabron', year: 1931, site: 'Grand Rapids' }, { yield: 29.67, variety: 'Glabron', year: 1931, site: 'Duluth' }, { yield: 35.13, variety: 'Svansota', year: 1931, site: 'University Farm' }, { yield: 47.33, variety: 'Svansota', year: 1931, site: 'Waseca' }, { yield: 25.77, variety: 'Svansota', year: 1931, site: 'Morris' }, { yield: 40.47, variety: 'Svansota', year: 1931, site: 'Crookston' }, { yield: 29.67, variety: 'Svansota', year: 1931, site: 'Grand Rapids' }, { yield: 25.7, variety: 'Svansota', year: 1931, site: 'Duluth' }, { yield: 39.9, variety: 'Velvet', year: 1931, site: 'University Farm' }, { yield: 50.23, variety: 'Velvet', year: 1931, site: 'Waseca' }, { yield: 26.13, variety: 'Velvet', year: 1931, site: 'Morris' }, { yield: 41.33, variety: 'Velvet', year: 1931, site: 'Crookston' }, { yield: 23.03, variety: 'Velvet', year: 1931, site: 'Grand Rapids' }, { yield: 26.3, variety: 'Velvet', year: 1931, site: 'Duluth' }, { yield: 36.57, variety: 'Trebi', year: 1931, site: 'University Farm' }, { yield: 63.83, variety: 'Trebi', year: 1931, site: 'Waseca' }, { yield: 43.77, variety: 'Trebi', year: 1931, site: 'Morris' }, { yield: 46.93, variety: 'Trebi', year: 1931, site: 'Crookston' }, { yield: 29.77, variety: 'Trebi', year: 1931, site: 'Grand Rapids' }, { yield: 33.93, variety: 'Trebi', year: 1931, site: 'Duluth' }, ]; const chart = new Chart({ container: 'container', autoFit: true, height: 400, }); chart.options({ type: 'interval', data: barleyData, coordinate: { transform: [{ type: 'transpose' }], }, encode: { x: 'site', y: 'yield', color: 'variety', }, transform: [{ type: 'stackY' }, { type: 'sortX', by: 'y', reverse: true }], axis: { y: { labelAutoHide: false, title: 'Yield (bushels/acre)', }, x: { labelAutoHide: false, }, }, tooltip: { title: 'site', }, }); chart.render(); ``` ## 分组柱状图的扩展 ### 横向分组柱状图 当分组名称较长或者需要显示更多分组时,可以使用横向分组柱状图: ```ts import { Chart } from '@antv/g2'; const data = [ { category: '体育游戏', year: '2001', sold: 27500 }, { category: '策略游戏', year: '2001', sold: 11500 }, { category: '动作游戏', year: '2001', sold: 6000 }, { category: '射击游戏', year: '2001', sold: 3500 }, { category: '体育游戏', year: '2002', sold: 29500 }, { category: '策略游戏', year: '2002', sold: 10500 }, { category: '动作游戏', year: '2002', sold: 8000 }, { category: '射击游戏', year: '2002', sold: 4500 }, { category: '体育游戏', year: '2003', sold: 30500 }, { category: '策略游戏', year: '2003', sold: 12500 }, { category: '动作游戏', year: '2003', sold: 4000 }, { category: '射击游戏', year: '2003', sold: 6500 }, ]; const chart = new Chart({ container: 'container', autoFit: true, height: 400, }); chart.options({ type: 'interval', data, coordinate: { transform: [{ type: 'transpose' }] }, encode: { x: 'sold', y: 'category', color: 'year', }, transform: [{ type: 'dodgeX' }], axis: { x: { title: '游戏销售量' }, y: { title: '游戏类型' }, }, }); chart.render(); ``` ## 分组柱状图和其他图表的对比 ### 分组柱状图和一般柱状图 - 分组柱状图可以增加一个维度,用于对比一组数据内不同分类的数据值大小,可以表示 3 个数据字段(维度)的数据,而一般柱状图只能表示 2 个数据字段(维度) - 分组柱状图的柱子较多,所以能展示的分组较少 ### 分组柱状图和层叠柱状图 - 分组柱状图,可以对比同一个分组内部不同分类的数据大小,也可以对比不同分组内相同分类的数据大小,但无法对比各分组的总量 - 层叠柱状图,可以对比同一个分组内部不同分类的数据大小或占比,也可以对比分组的总量,但不适合对比不同分组内相同分类的数据大小 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 打包图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/pack/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/pack.md - Description: 用嵌套或排列的圆展示层级结构和数值大小,了解打包图的布局特点、适用场景及 G2 示例。 --- title: "打包图" description: "用嵌套或排列的圆展示层级结构和数值大小,了解打包图的布局特点、适用场景及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/pack/" version: "5.4.8" --- pack ## 打包图的简介 打包图是一种树形层次结构的可视化图表,使用圆形(而非矩形)一层又一层地代表整个层次结构。每个圆圈代表层次结构中的一个节点,圆圈的大小与其数值成正比,包含关系通过圆圈的嵌套来表示。 打包图特别适合展示具有层级关系的数据,如组织架构、文件系统、软件包结构等。相比于传统的树形图,打包图通过圆形的嵌套关系更加直观地展示了数据的包含关系和数值比例,使得用户能够快速理解数据的层次结构和各部分的相对重要性。 当数据具有明显的层级结构且需要同时展示包含关系和数值大小时,打包图是一个很好的选择。它既能展示整体的层次结构,又能通过圆圈大小比较各节点的数值。 **英文名**:Circle Packing, Pack Chart ## 打包图的构成 | 图表类型 | 打包图 | | ---------------- | ---------------------------------------------------------------------------------- | | 适合的数据 | 层次化数据:具有树状结构的嵌套数据,包含分类字段和数值字段 | | 功能 | 展示层次化数据的包含关系和数值比例 | | 数据与图形的映射 | 层级关系映射到嵌套圆圈
数值大小映射到圆圈半径
不同层级通过颜色和嵌套关系区分 | | 适合的场景 | 软件包结构、组织架构、文件系统等具有清晰层次和包含关系的数据展示 | ## 打包图的应用场景 ### 适合的场景 例子 1: **展示软件包层次结构** 下图展示了软件项目的模块层次结构,通过打包图可以清晰地看到各个模块和子模块的大小分布,帮助开发者了解代码结构。 ```ts import { Chart } from '@antv/g2'; import { interpolateHcl } from 'd3-interpolate'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); chart.options({ type: 'pack', width: 800, height: 600, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/flare.json', }, encode: { value: 'value', color: 'depth', }, scale: { color: { domain: [0, 5], range: ['hsl(152,80%,80%)', 'hsl(228,30%,40%)'], interpolate: interpolateHcl, }, }, style: { labelText: (d) => (d.r >= 10 && d.height === 0 ? `${d.data.name}` : ''), labelFontSize: 8, }, legend: { color: false }, tooltip: { title: (d) => d.data.name, items: [{ field: 'value', name: '大小' }], }, }); chart.render(); ``` **说明**: - `value` 字段映射到圆圈大小,表示模块的代码量 - `depth` 字段映射到颜色,区分不同层级 - 通过 `labelText` 设置标签显示条件 例子 2: **展示组织架构人数分布** 打包图非常适合展示组织架构中各部门的人数分布,让管理者一目了然地看到各部门的规模。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); const orgData = { name: '公司', value: 500, children: [ { name: '技术部', value: 200, children: [ { name: '前端团队', value: 80 }, { name: '后端团队', value: 90 }, { name: '算法团队', value: 30 }, ], }, { name: '产品部', value: 120, children: [ { name: '产品设计', value: 50 }, { name: '用户研究', value: 30 }, { name: '数据分析', value: 40 }, ], }, { name: '运营部', value: 100, children: [ { name: '市场推广', value: 60 }, { name: '客户服务', value: 40 }, ], }, { name: '行政部', value: 80, children: [ { name: '人力资源', value: 30 }, { name: '财务', value: 25 }, { name: '法务', value: 25 }, ], }, ], }; chart.options({ type: 'pack', width: 700, height: 500, data: { value: orgData }, layout: { padding: 8, sort: (a, b) => b.value - a.value, }, encode: { value: 'value', color: 'depth', }, scale: { color: { range: ['#E8F4FD', '#1890FF', '#003A8C'], }, }, labels: [ { text: (d) => d.data.name, position: 'inside', fontWeight: (d) => (d.depth <= 1 ? 'bold' : 'normal'), fontSize: (d) => Math.max(8, Math.min(14, d.r / 3)), background: (d) => d.data.children, // 背景展示 backgroundFill: '#fff', transform: [ { type: 'contrastReverse' }, { type: 'overflowStroke' }, { type: 'overlapDodgeY', padding: 8 }, ], }, ], legend: { color: false }, tooltip: { title: (d) => d.data.name, items: [{ field: 'value', name: '人数' }], }, }); chart.render(); ``` **说明**: - 使用嵌套数据结构表示组织层级 - `sort` 配置按数值大小排序,大圆圈优先显示 - 通过 `labelFontSize` 动态调整字体大小适应圆圈 - 使用不同的标签颜色区分层级 例子 3: **展示投资组合分布** 打包图可以清晰地展示投资组合中各资产类别及具体投资项目的占比关系。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); const portfolioData = { name: '投资组合', value: 1000000, children: [ { name: '股票', value: 400000, children: [ { name: '科技股', value: 180000 }, { name: '金融股', value: 120000 }, { name: '消费股', value: 100000 }, ], }, { name: '债券', value: 300000, children: [ { name: '国债', value: 150000 }, { name: '企业债', value: 100000 }, { name: '可转债', value: 50000 }, ], }, { name: '基金', value: 200000, children: [ { name: '股票型基金', value: 80000 }, { name: '债券型基金', value: 70000 }, { name: '混合型基金', value: 50000 }, ], }, { name: '现金', value: 100000, }, ], }; chart.options({ type: 'pack', width: 600, height: 600, data: { value: portfolioData }, layout: { padding: 3, }, encode: { value: 'value', color: (d) => { const parentName = d.depth === 0 ? 'root' : d.parent?.data?.name || d.data.name; return parentName; }, }, scale: { color: { range: ['#FFF7E6', '#FFE7BA', '#FFC069', '#FA8C16', '#D46B08'], }, }, style: { labelText: (d) => { if (d.height === 0) return d.data.name; if (d.depth === 0) return ''; return d.data.name; }, labelFill: '#fff', labelFontWeight: 'bold', labelFontSize: (d) => Math.max(8, Math.min(12, d.r / 4)), stroke: '#fff', strokeWidth: 1, fillOpacity: 0.8, labelBackground: (d) => d.data.children, // 背景展示 labelBackgroundFill: '#fff', labelTransform: [ { type: 'contrastReverse' }, { type: 'overflowStroke' }, { type: 'overlapDodgeY', padding: 8 }, ], }, legend: { color: false }, tooltip: { title: (d) => d.data.name, items: [ { field: 'value', name: '金额', valueFormatter: (v) => `¥${(v / 10000).toFixed(1)}万`, }, ], }, }); chart.render(); ``` **说明**: - 使用自定义颜色映射区分不同资产类别 - 通过 `fillOpacity` 设置透明度增强视觉效果 - 自定义 `valueFormatter` 格式化金额显示 ## 打包图与其他图表的对比 ### 打包图与[矩形树图](/zh/charts/treemap/) 打包图和矩形树图都能展示层次化数据,但具有不同的特征: | 对比维度 | 打包图 | 矩形树图 | | -------- | ---------------------------- | ---------------------------- | | 视觉形式 | 圆形嵌套布局 | 矩形嵌套布局 | | 空间利用 | 圆形边界,利用率较低 | 矩形填充,利用率高 | | 比例感知 | 面积比较,但圆形判断相对困难 | 面积比较,矩形更易精确判断 | | 层次展示 | 嵌套圆圈,包含关系直观 | 嵌套矩形,包含关系明确 | | 美观性 | 圆形布局更加美观和谐 | 矩形布局简洁实用 | | 适用场景 | 强调视觉美感和整体包含关系 | 强调精确的比例对比和空间效率 | ### 打包图与[旭日图](/zh/charts/sunburst/) 打包图和旭日图都使用圆形布局展示层次数据,但展现方式不同: | 对比维度 | 打包图 | 旭日图 | | -------- | ------------------------ | ---------------------------- | | 布局方式 | 嵌套圆圈,内部包含 | 同心圆环,径向展开 | | 数值映射 | 圆圈面积表示数值大小 | 扇形角度表示数值大小 | | 层次关系 | 通过嵌套层级表示包含关系 | 通过径向距离表示层级深度 | | 中心区域 | 根节点占据中心圆圈 | 中心可用于显示标题或交互 | | 交互体验 | 点击节点下钻到子层级 | 点击扇形下钻,支持面包屑导航 | | 适用场景 | 强调包含关系和整体结构 | 强调层级导航和比例分配 | ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 平行坐标系 - Canonical page: https://g2.antv.antgroup.com/zh/charts/parallel/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/parallel.md - Description: 将多个数据维度排列为平行轴,用折线连接同一记录,了解平行坐标系的多维分析方法及 G2 示例。 --- title: "平行坐标系" description: "将多个数据维度排列为平行轴,用折线连接同一记录,了解平行坐标系的多维分析方法及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/parallel/" version: "5.4.8" --- parallel ## 平行坐标系的简介 平行坐标系,是一种含有多个垂直平行坐标轴的统计图表。每个垂直坐标轴表示一个字段,每个字段又用刻度来标明范围。这样,一个多维的数据可以很容易的在每一条轴上找到"落点",从而连接起来,形成一条折线。随着数据增多,折线堆叠,分析者则有可能从中发现特性和规律,比如发现数据之间的聚类关系。 尽管表面上类似[折线图](/zh/charts/line/),但平行坐标系并不表示趋势,各个坐标轴之间也没有因果关系。因此,使用平行坐标系时,如何确定轴的顺序,是可以人为决定的。一般来说,顺序会影响阅读的感知和判断。两根坐标轴隔得越近,人们对二者的对比就感知的越强烈。因此,要得出最合适、美观的排序方式,往往需要经过多次的试验和比较。反过来讲,尝试不同的排布方式,也可能有助于得出更多的结论。 此外,平行坐标系的每个坐标轴,很可能有不同的数据范围,这一点很容易造成读者误解。作图时,最好显著的标明每一根轴上的最小值、最大值。 **英文名**:Parallel Coordinates ## 平行坐标系的构成 ### 垂直平行坐标系 parallel-vertical | 图表类型 | 垂直平行坐标系 | | ---------------- | ---------------------------------------------------------------------------------------------- | | 适合的数据 | 表格:多个连续数据字段,可选一个分类数据字段用于颜色编码 | | 功能 | 分析多个变量之间的关系,识别数据模式和聚类 | | 数据与图形的映射 | 每个数据维度映射到一个垂直坐标轴
数据记录映射到连接各轴的折线
可选分类字段映射到线条颜色 | | 适合的数据条数 | 适中数据量(建议不超过 1000 条记录,过多时建议使用透明度或筛选) | --- ### 水平平行坐标系 parallel-horizontal | 图表类型 | 水平平行坐标系 | | ---------------- | ---------------------------------------------------------------- | | 适合的数据 | 表格:多个连续数据字段,可选一个分类数据字段用于颜色编码 | | 功能 | 分析多个变量之间的关系,适合维度名称较长的情况 | | 数据与图形的映射 | 每个数据维度映射到一个水平坐标轴
数据记录映射到连接各轴的折线 | | 适合的数据条数 | 适中数据量,比垂直布局能容纳更多的维度标签 | ## 平行坐标系的应用场景 ### 适合的场景 例子 1: **多维数据关系分析** 下图展示了汽车数据集中多个性能指标之间的关系,包括燃油经济性、气缸数、排量、马力、重量等。 ```ts import { Chart } from '@antv/g2'; const axis = { zIndex: 1, titlePosition: 'right', line: true, labelStroke: '#fff', labelLineWidth: 5, labelFontSize: 10, labelStrokeLineJoin: 'round', titleStroke: '#fff', titleFontSize: 10, titleLineWidth: 5, titleStrokeLineJoin: 'round', titleTransform: 'translate(-50%, 0) rotate(-90)', lineStroke: 'black', tickStroke: 'black', lineLineWidth: 1, }; 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)', 'weight (lb)', '0-60 mph (s)', 'year', ], color: 'weight (lb)', }, style: { lineWidth: 1.5, strokeOpacity: 0.4, }, scale: { color: { palette: 'brBG', offset: (t) => 1 - t, }, }, legend: { color: { length: 400, layout: { justifyContent: 'center' } }, }, axis: { position: axis, position1: axis, position2: axis, position3: axis, position4: axis, position5: axis, position6: axis, position7: axis, }, interaction: { tooltip: { series: false }, }, }); chart.render(); ``` **说明**: - 每条线代表一辆汽车的多个性能指标 - 线条颜色根据汽车重量进行编码,便于识别重量相关的模式 - 可以观察到重量与燃油经济性、加速性能等指标的关系 例子 2: **数据聚类识别** 通过平行坐标系可以识别具有相似模式的数据群组。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); const axis = { zIndex: 1, titlePosition: 'right', line: true, labelStroke: '#fff', labelLineWidth: 5, labelFontSize: 10, labelStrokeLineJoin: 'round', titleStroke: '#fff', titleFontSize: 10, titleLineWidth: 5, titleStrokeLineJoin: 'round', titleTransform: 'translate(-50%, 0) rotate(-90)', lineStroke: 'black', tickStroke: 'black', lineLineWidth: 1, }; chart.options({ type: 'line', autoFit: true, data: [ { A: 10, B: 30, C: 20, D: 60, group: '组1' }, { A: 12, B: 32, C: 22, D: 58, group: '组1' }, { A: 8, B: 28, C: 18, D: 62, group: '组1' }, { A: 50, B: 20, C: 80, D: 30, group: '组2' }, { A: 52, B: 18, C: 82, D: 28, group: '组2' }, { A: 48, B: 22, C: 78, D: 32, group: '组2' }, { A: 80, B: 60, C: 40, D: 10, group: '组3' }, { A: 82, B: 58, C: 42, D: 12, group: '组3' }, { A: 78, B: 62, C: 38, D: 8, group: '组3' }, ], coordinate: { type: 'parallel' }, encode: { position: ['A', 'B', 'C', 'D'], color: 'group', }, style: { lineWidth: 3, strokeOpacity: 0.8, }, scale: { color: { palette: 'category10', }, }, axis: { position: axis, position1: axis, position2: axis, position3: axis, }, legend: { color: { position: 'bottom' }, }, interaction: { tooltip: { series: false }, }, }); chart.render(); ``` ### 不适合的场景 例子 1: **维度过少** 当只有 2-3 个维度时,使用散点图或其他图表类型会更直观。 ```ts // 不推荐:只有两个维度使用平行坐标系 import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'line', autoFit: true, data: [ { x: 10, y: 30, category: 'A' }, { x: 20, y: 20, category: 'B' }, { x: 30, y: 40, category: 'C' }, { x: 40, y: 35, category: 'D' }, { x: 50, y: 25, category: 'E' }, ], coordinate: { type: 'parallel' }, encode: { position: ['x', 'y'], color: 'category', }, style: { lineWidth: 2, strokeOpacity: 0.8, }, axis: { position: { zIndex: 1 }, position1: { zIndex: 1 }, }, }); chart.render(); ``` 对于二维数据,散点图更适合: ```ts // 推荐:二维数据使用散点图 import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', inset: 20, }); chart.options({ type: 'point', autoFit: true, data: [ { x: 10, y: 30, category: 'A' }, { x: 20, y: 20, category: 'B' }, { x: 30, y: 40, category: 'C' }, { x: 40, y: 35, category: 'D' }, { x: 50, y: 25, category: 'E' }, ], encode: { x: 'x', y: 'y', color: 'category', size: 20, }, scale: { color: { palette: 'category10', }, }, legend: { color: { position: 'bottom' }, }, }); chart.render(); ``` 例子 2: **数据量过大导致视觉混乱** 当数据记录过多时,线条会严重重叠,影响可读性。 ```ts import { Chart } from '@antv/g2'; // 生成大量数据模拟过多记录的情况 const generateData = (count) => { const data = []; for (let i = 0; i < count; i++) { data.push({ A: Math.random() * 100, B: Math.random() * 100, C: Math.random() * 100, D: Math.random() * 100, id: i, }); } return data; }; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'line', autoFit: true, data: generateData(500), // 大量数据 coordinate: { type: 'parallel' }, encode: { position: ['A', 'B', 'C', 'D'], }, style: { lineWidth: 1, strokeOpacity: 0.1, // 即使降低透明度仍然混乱 stroke: '#1890ff', }, axis: { position: { zIndex: 1 }, position1: { zIndex: 1 }, position2: { zIndex: 1 }, position3: { zIndex: 1 }, }, }); chart.render(); ``` ## 平行坐标系的拓展 ### 改变线的形状 ```ts import { Chart } from '@antv/g2'; const axis = { zIndex: 1, line: true, labelStroke: '#fff', labelLineWidth: 5, labelFontSize: 10, labelStrokeLineJoin: 'round', titleStroke: '#fff', titleFontSize: 10, titleLineWidth: 5, titleStrokeLineJoin: 'round', lineStroke: 'black', tickStroke: 'black', lineLineWidth: 1, }; 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', transform: [{ type: 'transpose' }], }, encode: { position: [ 'economy (mpg)', 'cylinders', 'displacement (cc)', 'power (hp)', 'weight (lb)', ], color: 'weight (lb)', shape: 'smooth', }, style: { lineWidth: 1.5, strokeOpacity: 0.4, }, scale: { color: { palette: 'brBG', offset: (t) => 1 - t, }, }, legend: { color: { length: 400, layout: { justifyContent: 'center' } }, }, axis: { position: axis, position1: axis, position2: axis, position3: axis, position4: axis, }, interaction: { tooltip: { series: false }, }, }); chart.render(); ``` ### 带交互的平行坐标系 添加轴刷选和高亮功能,增强数据探索能力: ```ts import { Chart } from '@antv/g2'; const axis = { zIndex: 1, titlePosition: 'right', line: true, labelStroke: '#fff', labelLineWidth: 5, labelFontSize: 10, labelStrokeLineJoin: 'round', titleStroke: '#fff', titleFontSize: 10, titleLineWidth: 5, titleStrokeLineJoin: 'round', titleTransform: 'translate(-50%, 0) rotate(-90)', lineStroke: 'black', tickStroke: 'black', lineLineWidth: 1, }; 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)', 'weight (lb)', ], color: 'cylinders', }, scale: { color: { palette: 'brBG', offset: (t) => 1 - t, }, }, style: { lineWidth: 1.5, strokeOpacity: 0.6, }, legend: { color: { position: 'bottom' }, }, axis: { position: axis, position1: axis, position2: axis, position3: axis, position4: axis, }, interaction: { tooltip: { series: false }, brushAxisHighlight: { maskFill: '#d8d0c0', maskOpacity: 0.3, }, }, state: { active: { lineWidth: 3, strokeOpacity: 1 }, inactive: { stroke: '#ccc', opacity: 0.3 }, }, }); chart.render(); ``` ### 世界五百强企业时间序列分析 受[Fathom Information Design](https://benfry.com/fortune500/)启发,这个例子展示了如何使用平行坐标系来分析大规模企业数据的时间变化。通过展示排名、收入和利润三个维度,可以观察企业在不同年份的表现轨迹和相互关系。 ```ts import { Chart } from '@antv/g2'; // 使用真实的财富500强数据 const realFortuneData = async () => { // 2022年财富500强数据(来源:GitHub jc22dora/2022Fortune500) const fortune2022 = [ { company: 'Walmart', ranking: 1, revenue: 572754, profit: 13673, industry: '零售', year: 2022, }, { company: 'Amazon', ranking: 2, revenue: 469822, profit: 33364, industry: '科技', year: 2022, }, { company: 'Apple', ranking: 3, revenue: 365817, profit: 94680, industry: '科技', year: 2022, }, { company: 'CVS Health', ranking: 4, revenue: 292111, profit: 7910, industry: '医疗', year: 2022, }, { company: 'UnitedHealth Group', ranking: 5, revenue: 287597, profit: 17285, industry: '医疗', year: 2022, }, { company: 'Exxon Mobil', ranking: 6, revenue: 285640, profit: 23040, industry: '能源', year: 2022, }, { company: 'Berkshire Hathaway', ranking: 7, revenue: 276094, profit: 89795, industry: '金融', year: 2022, }, { company: 'Alphabet', ranking: 8, revenue: 257637, profit: 76033, industry: '科技', year: 2022, }, { company: 'McKesson', ranking: 9, revenue: 238228, profit: 5234, industry: '医疗', year: 2022, }, { company: 'AmerisourceBergen', ranking: 10, revenue: 213989, profit: 1551, industry: '医疗', year: 2022, }, { company: 'Costco Wholesale', ranking: 11, revenue: 195929, profit: 5007, industry: '零售', year: 2022, }, { company: 'Cigna', ranking: 12, revenue: 174087, profit: 5408, industry: '医疗', year: 2022, }, { company: 'AT&T', ranking: 13, revenue: 168864, profit: -11899, industry: '科技', year: 2022, }, { company: 'Microsoft', ranking: 14, revenue: 168088, profit: 61271, industry: '科技', year: 2022, }, { company: 'Cardinal Health', ranking: 15, revenue: 162467, profit: 987, industry: '医疗', year: 2022, }, { company: 'Chevron', ranking: 16, revenue: 155607, profit: 15625, industry: '能源', year: 2022, }, { company: 'Home Depot', ranking: 17, revenue: 151157, profit: 17105, industry: '零售', year: 2022, }, { company: 'Walgreens Boots Alliance', ranking: 18, revenue: 132703, profit: -3003, industry: '医疗', year: 2022, }, { company: 'Marathon Petroleum', ranking: 19, revenue: 128050, profit: 11267, industry: '能源', year: 2022, }, { company: 'Anthem', ranking: 20, revenue: 118414, profit: 6104, industry: '医疗', year: 2022, }, { company: 'Tesla', ranking: 65, revenue: 53823, profit: 5519, industry: '科技', year: 2022, }, { company: 'Meta Platforms', ranking: 27, revenue: 117929, profit: 39370, industry: '科技', year: 2022, }, { company: 'Netflix', ranking: 115, revenue: 29698, profit: 4492, industry: '科技', year: 2022, }, { company: 'NVIDIA', ranking: 134, revenue: 26974, profit: 9752, industry: '科技', year: 2022, }, { company: 'Adobe', ranking: 235, revenue: 15785, profit: 5788, industry: '科技', year: 2022, }, ]; // 模拟2019-2021年数据(基于2022年数据进行合理变化) const generateHistoricalData = (baseData, year) => { return baseData.map((item) => { const yearFactor = year === 2020 ? 0.85 : year === 2021 ? 0.95 : year === 2019 ? 0.9 : 1; const growthVariation = (Math.random() - 0.5) * 0.2; // ±10%的随机变化 return { ...item, year, revenue: Math.round(item.revenue * yearFactor * (1 + growthVariation)), profit: Math.round( item.profit * yearFactor * (1 + growthVariation * 1.5), ), // 利润波动更大 ranking: Math.max( 1, Math.min(500, item.ranking + Math.floor((Math.random() - 0.5) * 6)), ), // 排名小幅波动 }; }); }; // 生成多年数据 const allYearsData = [ ...generateHistoricalData(fortune2022, 2019), ...generateHistoricalData(fortune2022, 2020), ...generateHistoricalData(fortune2022, 2021), ...fortune2022, ...generateHistoricalData(fortune2022, 2023), // 预测2023年 ]; return allYearsData; }; // 获取数据并渲染图表 realFortuneData().then((fortuneData) => { const chart = new Chart({ container: 'container', theme: 'classic', marginTop: 200, marginLeft: 50, marginRight: 50, height: 600, width: 800, }); const axisConfig = { zIndex: 1, titlePosition: 'right', line: true, lineStroke: '#2c3e50', lineLineWidth: 2, labelStroke: '#fff', labelLineWidth: 4, labelFontSize: 11, labelStrokeLineJoin: 'round', titleStroke: '#fff', titleFontSize: 13, titleLineWidth: 5, titleStrokeLineJoin: 'round', titleTransform: 'translate(-50%, 0) rotate(-90)', tickStroke: '#7f8c8d', tickLineWidth: 1, grid: true, gridStroke: '#ecf0f1', gridStrokeOpacity: 0.5, }; chart.options({ type: 'line', data: fortuneData.filter((d) => d.year === 2022), // 默认显示2022年数据 coordinate: { type: 'parallel' }, encode: { position: ['ranking', 'revenue', 'profit'], color: 'industry', key: 'company', }, style: { lineWidth: 2.5, strokeOpacity: 0.8, cursor: 'pointer', }, scale: { position: { range: [0, 1], nice: true, }, position1: { nice: true, }, position2: { nice: true, }, color: { type: 'ordinal', domain: ['科技', '零售', '医疗', '金融', '能源'], range: ['#3498db', '#e74c3c', '#2ecc71', '#f39c12', '#9b59b6'], }, }, legend: { color: { position: 'top', title: '行业分类', titleFontSize: 14, }, }, axis: { position: { ...axisConfig, title: '财富500强排名', labelFormatter: (d) => `#${Math.round(d)}`, }, position1: { ...axisConfig, title: '营业收入 (百万美元)', labelFormatter: (d) => `$${(d / 1000).toFixed(0)}B`, }, position2: { ...axisConfig, title: '净利润 (百万美元)', labelFormatter: (d) => d >= 0 ? `$${(d / 1000).toFixed(1)}B` : `-$${Math.abs(d / 1000).toFixed(1)}B`, }, }, tooltip: { title: (d) => `${d.company} (${d.year}年)`, items: [ { field: 'ranking', name: '财富500强排名', valueFormatter: (d) => `第 ${d} 位`, }, { field: 'revenue', name: '营业收入', valueFormatter: (d) => `$${(d / 1000).toFixed(1)} 十亿美元`, }, { field: 'profit', name: '净利润', valueFormatter: (d) => d >= 0 ? `$${(d / 1000).toFixed(1)} 十亿美元` : `-$${Math.abs(d / 1000).toFixed(1)} 十亿美元`, }, { field: 'industry', name: '行业' }, ], }, interaction: { tooltip: { series: false, }, brushAxisHighlight: { maskFill: 'rgba(52, 152, 219, 0.15)', maskOpacity: 0.6, }, }, state: { active: { lineWidth: 4, strokeOpacity: 1, shadowColor: '#2c3e50', shadowBlur: 8, }, inactive: { stroke: '#bdc3c7', strokeOpacity: 0.2, }, }, }); chart.render(); // 添加年份切换控制 setTimeout(() => { const container = document.getElementById('container'); const controlPanel = document.createElement('div'); controlPanel.style.cssText = ` position: absolute; top: 10px; left: 10px; background: rgba(255, 255, 255, 0.95); padding: 15px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); z-index: 100; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; `; controlPanel.innerHTML = `
财富500强时间序列分析
2022
💡 数据来源:GitHub
⌨️ 提示:可使用键盘 ← → 键切换年份
`; container.style.position = 'relative'; container.appendChild(controlPanel); const years = [2019, 2020, 2021, 2022, 2023]; let currentYearIndex = 3; // 从2022年开始(真实数据年份) const updateChart = (yearIndex) => { const year = years[yearIndex]; const yearData = fortuneData.filter((d) => d.year === year); document.getElementById('current-year').textContent = year; chart.changeData(yearData); // 更新按钮状态 document.getElementById('prev-year').disabled = yearIndex === 0; document.getElementById('next-year').disabled = yearIndex === years.length - 1; // 更新按钮样式 const prevBtn = document.getElementById('prev-year'); const nextBtn = document.getElementById('next-year'); prevBtn.style.opacity = yearIndex === 0 ? '0.5' : '1'; nextBtn.style.opacity = yearIndex === years.length - 1 ? '0.5' : '1'; }; // 按钮事件 document.getElementById('prev-year').onclick = () => { if (currentYearIndex > 0) { currentYearIndex--; updateChart(currentYearIndex); } }; document.getElementById('next-year').onclick = () => { if (currentYearIndex < years.length - 1) { currentYearIndex++; updateChart(currentYearIndex); } }; // 键盘事件 document.addEventListener('keydown', (e) => { if (e.key === 'ArrowLeft' && currentYearIndex > 0) { currentYearIndex--; updateChart(currentYearIndex); } else if ( e.key === 'ArrowRight' && currentYearIndex < years.length - 1 ) { currentYearIndex++; updateChart(currentYearIndex); } }); // 初始化按钮状态 updateChart(currentYearIndex); }, 500); }); ``` ## 平行坐标系与其他图表的对比 ### 平行坐标系和[折线图](/zh/charts/line/) - 平行坐标系用于展示多维数据之间的关系,每个轴代表不同的维度,轴之间没有因果关系 - 折线图用于展示数据随时间或其他连续变量的变化趋势,横轴通常表示时间序列 - 平行坐标系的轴可以任意调整顺序,而折线图的横轴顺序通常是固定的 - 平行坐标系每条线代表一个数据记录的多个维度值,折线图每条线代表一个指标随时间的变化 ### 平行坐标系和[桑基图](/zh/charts/sankey/) - 平行坐标系展示多维数据之间的关系,每条线代表一个数据记录的多个维度值 - 桑基图展示流量在不同节点间的分配和流向,线条宽度代表流量大小 - 平行坐标系的轴代表不同维度,轴之间没有流量的概念 - 桑基图的节点之间有明确的流向关系,通常用于展示资源、能量等的流动过程 ### 平行坐标系和[雷达图](/zh/charts/radar-chart/) - 平行坐标系使用平行轴展示多维数据,适合比较不同记录 - 雷达图使用径向轴展示多维数据,适合展示单个记录的多维特征 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 玉珏图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/radial-bar/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/radial-bar.md - Description: 用不同半径上的弧长表达类别数值,了解玉珏图的环形布局、比较方式及 G2 示例。 --- title: "玉珏图" description: "用不同半径上的弧长表达类别数值,了解玉珏图的环形布局、比较方式及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/radial-bar/" version: "5.4.8" --- radial-bar ## 玉珏图的简介 玉珏图(又名环形柱状图),是柱状图关于笛卡尔坐标系转换到极坐标系的仿射变换。其意义和用法与[柱状图](/zh/charts/bar/)类似。 玉珏图有**半径反馈效应**。由于玉珏图中是用**角度**表示每个珏环数值的大小,角度是决定性因素。所以,哪怕外侧(半径大的)珏环的数值小于内侧(半径小的)珏环,外侧的每个珏环会相对的比里面的珏环更长。这会造成视觉上的误解。 而且因为我们的视觉系统更善于比较直线,所以笛卡尔坐标系更适合于比较各个分类的数值比较。所以玉珏图从**实用**的角度去看,其更多的是一种**审美**上的需求。 **英文名**:Radial Bar Chart ## 玉珏图的构成 radial-bar-structure | 图表类型 | 玉珏图 | | ---------------- | -------------------------------------------------------------------------------------- | | 适合的数据 | 列表:一个分类数据字段、一个连续数据字段 | | 功能 | 以审美的方式展示分类数据的数值大小对比 | | 数据与图形的映射 | 分类数据字段映射到环形位置
连续数据字段映射到弧长(角度)
可以用颜色区分不同分类 | | 适合的数据条数 | 不超过 15 条数据 | 一个完整的玉珏图包含以下构成元素: - **珏环**:角度表示数值 - **文本**:数值、分类名 ## 玉珏图的应用场景 ### 适合的场景 例子 1: **展示分类数据的对比(需要排序)** 下图反映了美国民众对不同领域中的中美问题关注程度。 | 问题 | 关注人数比例 | | ------------------------ | ------------ | | 台海关系 | 0.21 | | 中国持续增长的军事力量 | 0.47 | | 中国对全球环境的影响 | 0.49 | | 美国对中国的贸易逆差 | 0.52 | | 中国的人权政策 | 0.53 | | 来自中国的网络攻击 | 0.54 | | 中国带走了美国的就业岗位 | 0.60 | | 中国持有美国巨额国债 | 0.67 | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', width: 1000, height: 700, }); chart.options({ type: 'interval', data: [ { question: '台海关系', percent: 0.21, odd: 0 }, { question: '中国持续增长的军事力量', percent: 0.47, odd: 1 }, { question: '中国对全球环境的影响', percent: 0.49, odd: 0 }, { question: '美国对中国的贸易逆差', percent: 0.52, odd: 1 }, { question: '中国的人权政策', percent: 0.53, odd: 0 }, { question: '来自中国的网络攻击', percent: 0.54, odd: 1 }, { question: '中国带走了美国的就业岗位', percent: 0.6, odd: 0 }, { question: '中国持有美国巨额国债', percent: 0.67, odd: 1 }, ], coordinate: { type: 'radial', innerRadius: 0.2 }, encode: { x: 'question', y: 'percent', color: 'odd', }, scale: { color: { range: ['rgb(211,0,57)', 'rgb(224,74,116)'], }, y: { domain: [0, 1] }, }, style: { radiusTopLeft: 4, radiusTopRight: 4, }, label: { text: 'percent', position: 'inside', style: { fontWeight: 'bold', fill: 'white', }, }, axis: { x: { label: { autoRotate: true, autoEllipsis: true, style: { fontSize: 10, }, }, }, y: { label: false, grid: false, }, }, interaction: [ { type: 'elementHighlight', background: true, }, ], }); chart.render(); ``` 例子 2: **展示各国数据对比** 下图反映了各国人民认为气候变化是严重问题的比例。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', width: 1200, height: 800, }); chart.options({ type: 'interval', data: [ { country: '中国', percent: 0.18, condition: '特殊' }, { country: '俄罗斯', percent: 0.33, condition: '一般' }, { country: '英国', percent: 0.41, condition: '一般' }, { country: '美国', percent: 0.45, condition: '一般' }, { country: '韩国', percent: 0.48, condition: '一般' }, { country: '加拿大', percent: 0.51, condition: '一般' }, { country: '德国', percent: 0.55, condition: '一般' }, { country: '越南', percent: 0.69, condition: '一般' }, { country: '巴西', percent: 0.86, condition: '一般' }, ], coordinate: { type: 'radial', innerRadius: 0.3, startAngle: -Math.PI, endAngle: -0.25 * Math.PI, }, encode: { x: 'country', y: 'percent', color: 'condition', }, scale: { color: { range: ['#2370AE', '#A34265', '#505051'], }, }, style: { radiusTopLeft: 2, radiusTopRight: 2, }, label: { text: 'percent', position: 'inside', style: { fontWeight: 'bold', fill: 'white', fontSize: 10, }, }, axis: { x: { label: { autoRotate: true, autoEllipsis: true, style: { fontSize: 12, }, }, }, y: { label: false, grid: false, }, }, interaction: [ { type: 'elementHighlight', background: true, }, ], }); chart.render(); ``` ### 不适合的场景 例子 1: **未排序的数据会产生误导** 简介中提到玉珏图具有半径反馈效应。视觉上半径越大的珏环会看起来更大,半径小的则小。造成玉珏图的误用。所以,我们认为玉珏图使用时必须进行**排序**。 下面展示错误和正确的对比: **错误示例(未排序):** ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', width: 1000, height: 700, }); chart.options({ type: 'interval', data: [ { country: '巴西', percent: 0.86, condition: '一般' }, { country: '越南', percent: 0.69, condition: '一般' }, { country: '德国', percent: 0.55, condition: '一般' }, { country: '加拿大', percent: 0.51, condition: '一般' }, { country: '韩国', percent: 0.48, condition: '一般' }, { country: '美国', percent: 0.45, condition: '一般' }, { country: '英国', percent: 0.41, condition: '一般' }, { country: '俄罗斯', percent: 0.33, condition: '一般' }, { country: '中国', percent: 0.18, condition: '特殊' }, ], coordinate: { type: 'radial', innerRadius: 0.2, startAngle: -Math.PI, endAngle: -0.25 * Math.PI, }, encode: { x: 'country', y: 'percent', color: 'condition', }, scale: { color: { range: ['#2370AE', '#A34265', '#505051'], }, }, style: { radiusTopLeft: 2, radiusTopRight: 2, }, axis: { x: { label: { autoRotate: true, autoEllipsis: true, style: { fontSize: 10, }, }, }, y: { label: false, grid: false, }, }, interaction: [ { type: 'elementHighlight', background: true, }, ], }); chart.render(); ``` 在上面的错误示例中,由于数据未排序,外圈的巴西(0.86)会在视觉上显得比内圈的中国(0.18)大很多,但这种视觉差异会被半径效应进一步放大,造成误导。 ## 玉珏图的扩展 ### 分组玉珏图 可以通过颜色或分组来展示多维度的数据对比: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', width: 1000, height: 700, }); chart.options({ type: 'interval', data: [ { category: 'A类产品', value: 85, quarter: 'Q1' }, { category: 'B类产品', value: 75, quarter: 'Q1' }, { category: 'C类产品', value: 65, quarter: 'Q1' }, { category: 'D类产品', value: 55, quarter: 'Q1' }, { category: 'E类产品', value: 45, quarter: 'Q1' }, ], coordinate: { type: 'radial', innerRadius: 0.2 }, encode: { x: 'category', y: 'value', color: 'category', }, scale: { y: { domain: [0, 100] }, }, style: { radiusTopLeft: 8, radiusTopRight: 8, }, label: { text: 'value', position: 'inside', style: { fontWeight: 'bold', fill: 'white', }, }, axis: { x: { label: { autoRotate: true, autoEllipsis: true, style: { fontSize: 10, }, }, }, y: { label: false, grid: false, }, }, interaction: [ { type: 'elementHighlight', background: true, }, ], }); chart.render(); ``` ## 玉珏图与其他图表的对比 ### 玉珏图和[柱状图](/zh/charts/bar/) - **玉珏图**:使用极坐标系,通过弧长表示数值,更具视觉美感,但存在半径反馈效应,数据对比准确性较低 - **柱状图**:使用直角坐标系,通过矩形长度表示数值,数据对比更准确直观 ### 玉珏图和饼图 - **玉珏图**:展示各分类的绝对数值大小 - **饼图**:展示各分类在总体中的占比关系 ### 玉珏图和雷达图 - **玉珏图**:专注于单一维度数值的分类对比 - **雷达图**:展示多维度指标的综合表现 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 回归曲线图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/regressioncurve/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/regressioncurve.md - Description: 用拟合曲线概括变量之间的关系和趋势,了解回归曲线图的分析用途及 G2 示例。 --- title: "回归曲线图" description: "用拟合曲线概括变量之间的关系和趋势,了解回归曲线图的分析用途及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/regressioncurve/" version: "5.4.8" --- regression-curve ## 回归曲线图的简介 回归曲线图是一种在散点图基础上添加回归曲线的统计图表,用于展示两个或多个变量之间的数学关系和预测趋势。回归曲线通过数学算法拟合数据点,找出变量间的最佳函数关系,帮助分析数据的内在规律和进行趋势预测。 回归曲线图结合了[散点图](/zh/charts/scatter-plot/)的数据分布展示能力和数学建模的预测功能,不仅能直观地显示数据点的分布,还能通过拟合曲线揭示变量间的潜在关系,是数据分析和科学研究中的重要工具。 常见的回归类型包括线性回归、多项式回归、指数回归、对数回归等,不同的回归方法适用于不同的数据模式和关系类型。 **英文名**:Regression Curve Chart ## 回归曲线图的构成 basic-regression | 图表类型 | 回归曲线图 | | ---------------- | ---------------------------------------------------------------------------------------------------------- | | 适合的数据 | 两个连续数据字段:一个自变量字段、一个因变量字段 | | 功能 | 展示变量间的数学关系,识别数据趋势,进行预测分析 | | 数据与图形的映射 | 自变量字段映射到横轴位置
因变量字段映射到纵轴位置
数据点显示原始观测值
回归曲线显示拟合的数学关系 | | 适合的数据条数 | 10-1000 条数据,数据点足够多才能有效进行回归分析 | **组成元素:** 1. **散点(Scatter Points)**:表示原始数据观测值的点 2. **回归曲线(Regression Curve)**:通过数学算法拟合的趋势线 3. **X 轴(X-axis)**:水平坐标轴,通常表示自变量 4. **Y 轴(Y-axis)**:垂直坐标轴,通常表示因变量 5. **回归方程(Regression Equation)**:描述变量关系的数学公式 6. **决定系数(R²)**:衡量回归模型拟合优度的指标 ## 回归曲线图的应用场景 ### 适合的场景 例子 1: **线性关系数据分析** 线性回归适用于展示两个变量之间的线性关系,如身高与体重、温度与销量等。 ```ts import { Chart } from '@antv/g2'; import { regressionLinear } from 'd3-regression'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'view', autoFit: true, data: { 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', data: { transform: [ { type: 'custom', callback: regressionLinear(), }, ], }, 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(); ``` **说明**: - 散点显示原始数据分布 - 线性回归线揭示两变量间的线性关系 - 回归方程提供了精确的数学描述 - 适用于预测和趋势分析 例子 2: **非线性关系 - 二次回归** 当数据呈现弯曲趋势时,可以使用二次回归(抛物线)来拟合数据。 ```ts import { Chart } from '@antv/g2'; import { regressionQuad } from 'd3-regression'; const chart = new Chart({ container: 'container', theme: 'classic', }); const data = [ { 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 }, ]; chart.options({ type: 'view', autoFit: true, data, children: [ { type: 'point', encode: { x: 'x', y: 'y' }, style: { fillOpacity: 0.75, fill: '#1890ff' }, }, { type: 'line', data: { 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, }, ], axis: { x: { title: '自变量 X' }, y: { title: '因变量 Y' }, }, }); chart.render(); ``` **说明**: - 二次回归适合展示具有抛物线特征的数据关系 - 能够捕获数据的弯曲趋势和极值点 - 常用于物理学、经济学中的非线性关系分析 例子 3: **指数增长趋势分析** 指数回归适用于展示指数增长或衰减的数据模式,如人口增长、细菌繁殖等。 ```ts import { Chart } from '@antv/g2'; import { regressionExp } from 'd3-regression'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'view', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/exponential-regression.json', }, children: [ { type: 'point', encode: { x: (d) => d[0], y: (d) => d[1] }, scale: { x: { domain: [0, 18] }, y: { domain: [0, 100000] }, }, style: { fillOpacity: 0.75, fill: '#1890ff' }, }, { type: 'line', data: { transform: [ { type: 'custom', callback: regressionExp(), }, ], }, encode: { x: (d) => d[0], y: (d) => d[1], shape: 'smooth', }, style: { stroke: '#30BF78', lineWidth: 2 }, labels: [ { text: 'y = 3477.32e^(0.18x)\nR² = 0.998', selector: 'last', textAlign: 'end', dy: -20, }, ], tooltip: false, }, ], axis: { x: { title: '时间' }, y: { title: '数值', labelFormatter: '~s', }, }, }); chart.render(); ``` **说明**: - 指数回归适合分析快速增长或衰减的趋势 - R² 值接近 1 表示拟合效果很好 - 常用于预测具有指数特征的未来趋势 ### 不适合的场景 例子 1: **数据点过少的情况** 当数据点太少时,回归分析可能不够可靠,容易产生误导性的结论。 ```ts import { Chart } from '@antv/g2'; import { regressionLinear } from 'd3-regression'; const chart = new Chart({ container: 'container', theme: 'classic', }); const insufficientData = [ { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 5, y: 3 }, ]; chart.options({ type: 'view', autoFit: true, data: insufficientData, children: [ { type: 'point', encode: { x: 'x', y: 'y' }, style: { fillOpacity: 0.8, fill: '#ff4d4f', r: 8, }, }, { 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] }, style: { stroke: '#ff4d4f', lineWidth: 2, strokeDasharray: [4, 4], }, tooltip: false, }, ], axis: { x: { title: '变量 X' }, y: { title: '变量 Y' }, }, title: '不适合:数据点过少的回归分析', }); chart.render(); ``` **问题说明**: - 仅有 3 个数据点,样本量不足 - 回归线的可靠性和预测能力有限 - 容易受到异常值的影响 - 建议至少需要 10 个以上的数据点进行回归分析 例子 2: **无明显相关关系的数据** 当两个变量之间没有明显的相关关系时,强行添加回归线可能会误导分析。 ```ts import { Chart } from '@antv/g2'; import { regressionLinear } from 'd3-regression'; const chart = new Chart({ container: 'container', theme: 'classic', }); // 生成随机分布的数据(无相关关系) const randomData = Array.from({ length: 30 }, (_, i) => ({ x: Math.random() * 10, y: Math.random() * 10, })); chart.options({ type: 'view', autoFit: true, data: randomData, children: [ { type: 'point', encode: { x: 'x', y: 'y' }, style: { fillOpacity: 0.8, fill: '#ff4d4f', r: 6, }, }, { 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] }, style: { stroke: '#ff4d4f', lineWidth: 2, strokeDasharray: [4, 4], }, labels: [ { text: 'R² ≈ 0.02 (极低)', selector: 'last', textAlign: 'end', dy: -8, }, ], tooltip: false, }, ], axis: { x: { title: '变量 X' }, y: { title: '变量 Y' }, }, title: '不适合:无相关关系的数据', }); chart.render(); ``` **问题说明**: - 数据点呈现随机分布,无明显的趋势模式 - R² 值极低,说明回归模型几乎无法解释变量关系 - 回归线没有实际意义,可能误导决策 - 应该先进行相关性分析,确认变量间存在关系后再进行回归 ## 回归曲线图的扩展 ### 多项式回归 当数据呈现复杂的弯曲趋势时,可以使用多项式回归来拟合更复杂的曲线。 ```ts import { Chart } from '@antv/g2'; import { regressionPoly } from 'd3-regression'; const chart = new Chart({ container: 'container', theme: 'classic', }); const polynomialData = [ { x: 0, y: 140 }, { x: 1, y: 149 }, { x: 2, y: 159.6 }, { x: 3, y: 159 }, { x: 4, y: 155.9 }, { x: 5, y: 169 }, { x: 6, y: 162.9 }, { x: 7, y: 169 }, { x: 8, y: 180 }, ]; chart.options({ type: 'view', autoFit: true, data: polynomialData, children: [ { type: 'point', encode: { x: 'x', y: 'y' }, style: { fillOpacity: 0.75, fill: '#1890ff' }, }, { type: 'line', data: { transform: [ { type: 'custom', callback: regressionPoly() .x((d) => d.x) .y((d) => d.y), }, ], }, encode: { x: (d) => d[0], y: (d) => d[1], shape: 'smooth', }, style: { stroke: '#30BF78', lineWidth: 2 }, labels: [ { text: 'y = 0.24x³ - 3.00x² + 13.45x + 139.77\nR² = 0.92', selector: 'last', textAlign: 'end', dx: -8, dy: -20, }, ], tooltip: false, }, ], axis: { x: { title: '时间' }, y: { title: '数值' }, }, }); chart.render(); ``` **说明**: - 多项式回归能够拟合复杂的弯曲关系 - 适合处理具有多个波峰波谷的数据 - 需要注意避免过拟合问题 ### 对数回归 对数回归适用于增长率逐渐减缓的数据模式。 ```ts import { Chart } from '@antv/g2'; import { regressionLog } from 'd3-regression'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'view', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/logarithmic-regression.json', }, children: [ { type: 'point', encode: { x: 'x', y: 'y' }, scale: { x: { domain: [0, 35] } }, style: { fillOpacity: 0.75, fill: '#1890ff' }, }, { type: 'line', data: { transform: [ { type: 'custom', callback: regressionLog() .x((d) => d.x) .y((d) => d.y) .domain([0.81, 35]), }, ], }, encode: { x: (d) => d[0], y: (d) => d[1], shape: 'smooth', }, style: { stroke: '#30BF78', lineWidth: 2 }, labels: [ { text: 'y = 0.881·ln(x) + 4.173\nR² = 0.958', selector: 'last', textAlign: 'end', dy: -20, }, ], tooltip: false, }, ], axis: { x: { title: '变量 X' }, y: { title: '变量 Y' }, }, }); chart.render(); ``` **说明**: - 对数回归适合展示边际递减效应 - 常用于分析学习曲线、收益递减等场景 - 能够处理增长率逐渐下降的数据模式 ### 多变量回归对比 可以在同一图表中展示多种回归方法的对比效果。 ```ts import { Chart } from '@antv/g2'; import { regressionLinear, regressionQuad } from 'd3-regression'; const chart = new Chart({ container: 'container', theme: 'classic', }); const comparisonData = [ { x: 1, y: 2.1 }, { x: 2, y: 3.9 }, { x: 3, y: 6.8 }, { x: 4, y: 10.2 }, { x: 5, y: 15.1 }, { x: 6, y: 21.5 }, { x: 7, y: 29.8 }, { x: 8, y: 40.2 }, ]; chart.options({ type: 'view', autoFit: true, data: comparisonData, children: [ { type: 'point', encode: { x: 'x', y: 'y' }, style: { fillOpacity: 0.8, fill: '#1890ff', r: 6, }, }, // 线性回归 { 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] }, style: { stroke: '#ff4d4f', lineWidth: 2, strokeDasharray: [4, 4], }, tooltip: false, }, // 二次回归 { type: 'line', data: { transform: [ { type: 'custom', callback: regressionQuad() .x((d) => d.x) .y((d) => d.y), }, ], }, encode: { x: (d) => d[0], y: (d) => d[1] }, style: { stroke: '#30BF78', lineWidth: 2, }, tooltip: false, }, ], axis: { x: { title: '变量 X' }, y: { title: '变量 Y' }, }, legends: [ { color: { position: 'top', itemMarker: (color, index) => { if (index === 0) return { symbol: 'line', style: { stroke: '#ff4d4f', strokeDasharray: [4, 4] }, }; if (index === 1) return { symbol: 'line', style: { stroke: '#30BF78' } }; }, data: [ { color: '#ff4d4f', value: '线性回归' }, { color: '#30BF78', value: '二次回归' }, ], }, }, ], }); chart.render(); ``` **说明**: - 可以同时展示多种回归方法的拟合效果 - 有助于选择最适合数据特征的回归模型 - 通过对比分析找到最佳的拟合方法 ## 回归曲线图与其他图表的对比 ### 回归曲线图和[散点图](/zh/charts/scatter-plot/) - 回归曲线图在散点图基础上增加了数学建模和预测功能 - 散点图专注于展示数据分布和相关性,回归曲线图更注重趋势分析和预测 - 当需要进行趋势预测时选择回归曲线图,当仅需观察数据分布时选择散点图 ### 回归曲线图和[折线图](/zh/charts/line/) - 回归曲线图基于数学模型拟合,折线图直接连接数据点 - 回归曲线图能够平滑噪声并进行预测,折线图准确反映每个数据点 - 当需要分析趋势规律时选择回归曲线图,当需要展示精确变化时选择折线图 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 堆叠面积图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/stacked-area/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/stacked-area.md - Description: 将多个系列的面积逐层堆叠,观察总量与各部分随时间的变化,了解堆叠面积图和 G2 示例。 --- title: "堆叠面积图" description: "将多个系列的面积逐层堆叠,观察总量与各部分随时间的变化,了解堆叠面积图和 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/stacked-area/" version: "5.4.8" --- stacked-area ## 堆叠面积图的简介 堆叠面积图和基本面积图一样,唯一的区别就是图上每一个数据集的起点不同,起点是基于前一个数据集的,用于显示每个数值所占大小随时间或类别变化的趋势线,展示的是部分与整体的关系。 堆叠面积图上的最大的面积代表了所有的数据量的总和,是一个整体。各个叠起来的面积表示各个数据量的大小,这些堆叠起来的面积图在表现大数据的总量分量的变化情况时格外有用,所以堆叠面积图不适用于表示带有负值的数据集。非常适用于对比多变量随时间变化的情况。 在堆叠面积图的基础之上,将各个面积的因变量的数据使用加和后的总量进行归一化就形成了百分比堆叠面积图。该图并不能反映总量的变化,但是可以清晰的反应每个数值所占百分比随时间或类别变化的趋势线,对于分析自变量是大数据、时变数据、有序数据时各个指标分量占比极为有用。 **英文名**:Stacked Area Chart ## 堆叠面积图的构成 | 图表类型 | 层叠面积图 | | ---------------- | ------------------------------------------------------------------ | | 适合的数据 | 一个分类字段数据,两个连续字段数据 | | 功能 | 对比每个分组数据的变化`趋势` | | 数据与图形的映射 | 两个连续字段分别映射到横轴和纵轴
另一个分类数据映射到图形的颜色 | | 适合的数据条数 | 映射到颜色的分类不超过 12 个;每种分类的数据不少于两条 | ## 堆叠面积图的应用场景 ### 适合的场景 - 展示多个系列随时间或类别的**趋势和占比变化**。 - 需要突出**整体与部分的关系**。 - 比较不同系列的**累积变化**。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic' }); chart.options({ type: 'area', autoFit: true, data: [ { country: 'Asia', year: '1750', value: 502 }, { country: 'Asia', year: '1800', value: 635 }, { country: 'Asia', year: '1850', value: 809 }, { country: 'Asia', year: '1900', value: 5268 }, { country: 'Asia', year: '1950', value: 4400 }, { country: 'Asia', year: '1999', value: 3634 }, { country: 'Asia', year: '2050', value: 947 }, { country: 'Africa', year: '1750', value: 106 }, { country: 'Africa', year: '1800', value: 107 }, { country: 'Africa', year: '1850', value: 111 }, { country: 'Africa', year: '1900', value: 1766 }, { country: 'Africa', year: '1950', value: 221 }, { country: 'Africa', year: '1999', value: 767 }, { country: 'Africa', year: '2050', value: 133 }, { country: 'Europe', year: '1750', value: 163 }, { country: 'Europe', year: '1800', value: 203 }, { country: 'Europe', year: '1850', value: 276 }, { country: 'Europe', year: '1900', value: 628 }, { country: 'Europe', year: '1950', value: 547 }, { country: 'Europe', year: '1999', value: 729 }, { country: 'Europe', year: '2050', value: 408 }, { country: 'Oceania', year: '1750', value: 200 }, { country: 'Oceania', year: '1800', value: 200 }, { country: 'Oceania', year: '1850', value: 200 }, { country: 'Oceania', year: '1900', value: 460 }, { country: 'Oceania', year: '1950', value: 230 }, { country: 'Oceania', year: '1999', value: 300 }, { country: 'Oceania', year: '2050', value: 300 }, ], encode: { x: 'year', y: 'value', color: 'country' }, axis: { x: { title: false }, y: { title: false } }, transform: [ { type: 'stackY', }, ], }); chart.render(); ``` ### 不适合的场景 **1. 分类数据的比较不要用堆叠面积图** **堆叠面积图(不推荐)** ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic' }); chart.options({ type: 'area', autoFit: true, data: [ { year: '2001', genre: 'Strategy', sold: 11500 }, { year: '2001', genre: 'Sports', sold: 27500 }, { year: '2001', genre: 'Action', sold: 6000 }, { year: '2001', genre: 'Shooter', sold: 3500 }, { year: '2001', genre: 'Other', sold: 1500 }, { year: '2002', genre: 'Strategy', sold: 10500 }, { year: '2002', genre: 'Sports', sold: 29500 }, { year: '2002', genre: 'Action', sold: 8000 }, { year: '2002', genre: 'Shooter', sold: 4500 }, { year: '2002', genre: 'Other', sold: 1800 }, { year: '2003', genre: 'Strategy', sold: 12500 }, { year: '2003', genre: 'Sports', sold: 30500 }, { year: '2003', genre: 'Action', sold: 4000 }, { year: '2003', genre: 'Shooter', sold: 6500 }, { year: '2003', genre: 'Other', sold: 2000 }, { year: '2004', genre: 'Strategy', sold: 14500 }, { year: '2004', genre: 'Sports', sold: 31500 }, { year: '2004', genre: 'Action', sold: 5000 }, { year: '2004', genre: 'Shooter', sold: 6800 }, { year: '2004', genre: 'Other', sold: 1800 }, ], encode: { x: 'year', y: 'sold', color: 'genre' }, axis: { x: { title: false }, y: { title: false } }, transform: [ { type: 'stackY', }, ], }); chart.render(); ``` **堆叠柱状图(推荐)** ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic' }); chart.options({ type: 'interval', autoFit: true, data: [ { year: '2001', genre: 'Strategy', sold: 11500 }, { year: '2001', genre: 'Sports', sold: 27500 }, { year: '2001', genre: 'Action', sold: 6000 }, { year: '2001', genre: 'Shooter', sold: 3500 }, { year: '2001', genre: 'Other', sold: 1500 }, { year: '2002', genre: 'Strategy', sold: 10500 }, { year: '2002', genre: 'Sports', sold: 29500 }, { year: '2002', genre: 'Action', sold: 8000 }, { year: '2002', genre: 'Shooter', sold: 4500 }, { year: '2002', genre: 'Other', sold: 1800 }, { year: '2003', genre: 'Strategy', sold: 12500 }, { year: '2003', genre: 'Sports', sold: 30500 }, { year: '2003', genre: 'Action', sold: 4000 }, { year: '2003', genre: 'Shooter', sold: 6500 }, { year: '2003', genre: 'Other', sold: 2000 }, { year: '2004', genre: 'Strategy', sold: 14500 }, { year: '2004', genre: 'Sports', sold: 31500 }, { year: '2004', genre: 'Action', sold: 5000 }, { year: '2004', genre: 'Shooter', sold: 6800 }, { year: '2004', genre: 'Other', sold: 1800 }, ], encode: { x: 'year', y: 'sold', color: 'genre' }, axis: { x: { title: false }, y: { title: false } }, transform: [ { type: 'stackY', }, ], }); chart.render(); ``` ## 堆叠面积图的扩展 ### 百分比堆叠面积图 将每个时间点的总和归一化为 100%,突出各部分的占比变化。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); chart.options({ type: 'view', autoFit: true, data: [ { country: 'Asia', year: '1750', value: 502 }, { country: 'Asia', year: '1800', value: 635 }, { country: 'Asia', year: '1850', value: 809 }, { country: 'Asia', year: '1900', value: 947 }, { country: 'Asia', year: '1950', value: 1402 }, { country: 'Asia', year: '1999', value: 3634 }, { country: 'Asia', year: '2050', value: 5268 }, { country: 'Africa', year: '1750', value: 106 }, { country: 'Africa', year: '1800', value: 107 }, { country: 'Africa', year: '1850', value: 111 }, { country: 'Africa', year: '1900', value: 133 }, { country: 'Africa', year: '1950', value: 221 }, { country: 'Africa', year: '1999', value: 767 }, { country: 'Africa', year: '2050', value: 1766 }, { country: 'Europe', year: '1750', value: 163 }, { country: 'Europe', year: '1800', value: 203 }, { country: 'Europe', year: '1850', value: 276 }, { country: 'Europe', year: '1900', value: 408 }, { country: 'Europe', year: '1950', value: 547 }, { country: 'Europe', year: '1999', value: 729 }, { country: 'Europe', year: '2050', value: 628 }, ], encode: { x: 'year', y: 'value', color: 'country' }, transform: [{ type: 'stackY' }, { type: 'normalizeY' }], axis: { x: { title: false }, y: { title: false, labelFormatter: '.0%' } }, children: [ { type: 'area', tooltip: { items: [{ channel: 'y0', valueFormatter: '.0%' }] }, }, { type: 'line', tooltip: false }, ], }); chart.render(); ``` ### 渐变色堆叠面积图 通过设置渐变色填充,让堆叠面积图更加美观,突出数据的层次感。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); chart.options({ type: 'area', data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/stocks.json', transform: [ { type: 'filter', callback: (d) => d.symbol === 'GOOG', }, ], }, encode: { x: (d) => new Date(d.date), y: 'price', }, style: { fill: 'linear-gradient(-90deg, white 0%, darkgreen 100%)', }, axis: { x: { title: false }, y: { title: false } }, }); chart.render(); ``` ### 河流图(对称堆叠面积图) 河流图是一种特殊的堆叠面积图,通过对称分布展示数据的流动变化,特别适合展示多个类别随时间的变化趋势。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); chart.options({ type: 'area', data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/unemployment-by-industry.json', }, encode: { x: (d) => new Date(d.date), y: 'unemployed', color: 'industry', }, transform: [ { type: 'stackY' }, { type: 'symmetryY' }, // 对称分布 ], axis: { x: { title: false }, y: { title: false } }, }); chart.render(); ``` ### 瀑布面积图 瀑布面积图展示不同类别数据的层叠效果,通过特殊的排序和平滑曲线展现数据的流动感。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); chart.options({ type: 'area', data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/f38a8ad0-6e1f-4bb3-894c-7db50781fdec.json', }, encode: { x: (d) => new Date(d.year), y: 'revenue', series: 'format', color: 'group', shape: 'smooth', // 平滑曲线 }, transform: [ { type: 'stackY', orderBy: 'maxIndex', reverse: true, }, ], axis: { x: { title: false }, y: { title: false, labelFormatter: '~s' }, }, tooltip: { channel: 'y', valueFormatter: '.2f' }, }); chart.render(); ``` --- ## 堆叠面积图与其他图表的对比 ### 堆叠面积图和[面积图](/zh/charts/area/) - 面积图适合展示单一系列的趋势,堆叠面积图适合展示多个系列的累积趋势和占比。 ### 堆叠面积图和[堆叠柱状图](/zh/charts/stacked-bar/) - 两者都能展示部分与整体的关系,堆叠面积图更适合趋势类数据,堆叠柱状图适合类别型数据。 ### 堆叠面积图和[折线图](/zh/charts/line/) - 折线图适合比较各系列的具体数值变化,堆叠面积图更突出累积和占比。 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 堆叠柱状图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/stacked-bar/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/stacked-bar.md - Description: 将各系列数值累积到同一柱中,比较类别总量及组成,了解堆叠柱状图的适用场景和 G2 示例。 --- title: "堆叠柱状图" description: "将各系列数值累积到同一柱中,比较类别总量及组成,了解堆叠柱状图的适用场景和 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/stacked-bar/" version: "5.4.8" --- stacked-bar ## 堆叠柱状图的简介 堆叠柱状图是柱状图的扩展,通过在单个柱子中堆叠多个数据系列来显示分类数据。每个柱子代表一个类别的总数,而柱子内的不同颜色段代表各个子类别的数值。堆叠柱状图不仅能够展示每个类别的总量,还能显示各个子类别对总量的贡献。 堆叠柱状图特别适合于比较多个类别的组成结构,以及观察各子类别在不同类别中的分布情况。通过颜色编码,用户可以轻松识别不同的子类别,并比较它们在各个主类别中的占比。 当需要同时展示总量和构成时,堆叠柱状图是一个很好的选择。可以进一步扩展为百分比堆叠柱状图来显示各部分的相对比例。 **英文名**:Stacked Bar Chart, Stacked Column Chart ## 堆叠柱状图的构成 ### 垂直堆叠柱状图 stacked-bar-vertical | 图表类型 | 垂直堆叠柱状图 | | ---------------- | ------------------------------------------------------------------------------------------------ | | 适合的数据 | 多系列分类数据:一个分类数据字段、一个连续数据字段、一个系列分类字段 | | 功能 | 对比不同类别的总量以及各子类别的构成 | | 数据与图形的映射 | 分类字段映射到横轴位置
数值字段映射到柱子高度
系列字段映射到颜色,通过堆叠显示在同一柱子中 | | 适合的数据条数 | 主类别不超过 12 条,子类别不超过 8 条 | --- ### 水平堆叠柱状图 stacked-bar-horizontal | 图表类型 | 水平堆叠柱状图 | | ---------------- | ---------------------------------------------------------------------- | | 适合的数据 | 多系列分类数据:一个分类数据字段、一个连续数据字段、一个系列分类字段 | | 功能 | 对比不同类别的总量以及各子类别的构成 | | 数据与图形的映射 | 分类字段映射到纵轴位置
数值字段映射到柱子宽度
系列字段映射到颜色 | | 适合的数据条数 | 主类别不超过 30 条,子类别不超过 8 条 | ## 堆叠柱状图的应用场景 ### 适合的场景 例子 1: **适合展示多城市降雨量的月度构成** 下图展示了伦敦和柏林两个城市在不同月份的降雨量分布,能够清楚地看出每个月的总降雨量以及两个城市各自的贡献。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, data: [ { name: 'London', 月份: 'Jan.', 月均降雨量: 18.9 }, { name: 'London', 月份: 'Feb.', 月均降雨量: 28.8 }, { name: 'London', 月份: 'Mar.', 月均降雨量: 39.3 }, { name: 'London', 月份: 'Apr.', 月均降雨量: 81.4 }, { name: 'London', 月份: 'May', 月均降雨量: 47 }, { name: 'London', 月份: 'Jun.', 月均降雨量: 20.3 }, { name: 'London', 月份: 'Jul.', 月均降雨量: 24 }, { name: 'London', 月份: 'Aug.', 月均降雨量: 35.6 }, { name: 'Berlin', 月份: 'Jan.', 月均降雨量: 12.4 }, { name: 'Berlin', 月份: 'Feb.', 月均降雨量: 23.2 }, { name: 'Berlin', 月份: 'Mar.', 月均降雨量: 34.5 }, { name: 'Berlin', 月份: 'Apr.', 月均降雨量: 99.7 }, { name: 'Berlin', 月份: 'May', 月均降雨量: 52.6 }, { name: 'Berlin', 月份: 'Jun.', 月均降雨量: 35.5 }, { name: 'Berlin', 月份: 'Jul.', 月均降雨量: 37.4 }, { name: 'Berlin', 月份: 'Aug.', 月均降雨量: 42.4 }, ], encode: { x: '月份', y: '月均降雨量', color: 'name' }, transform: [{ type: 'stackY' }], interaction: [ { type: 'elementHighlight', background: true, region: true, }, ], }); chart.render(); ``` **说明**: - `月份` 字段映射到横轴位置,表示时间维度 - `月均降雨量` 字段映射到柱子高度,通过 `stackY` 变换进行堆叠 - `name` 字段映射到颜色,区分不同城市 - 可以同时比较每个月的总降雨量和各城市的贡献 例子 2: **适合展示人口年龄结构的地区对比** 堆叠柱状图非常适合展示不同地区的人口年龄结构分布,既能看出各地区的总人口,又能分析年龄构成。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/f129b517-158d-41a9-83a3-3294d639b39e.csv', format: 'csv', }, encode: { x: 'state', y: 'population', color: 'age' }, transform: [{ type: 'stackY' }, { type: 'sortX', by: 'y', reverse: true }], axis: { x: { labelSpacing: 4, labelTransform: 'rotate(90)', }, y: { labelFormatter: '~s', title: null, }, }, legend: { color: { position: 'right', title: '年龄段', }, }, interaction: [ { type: 'elementHighlight', background: true, }, ], }); chart.render(); ``` **说明**: - 使用 `sortX` 变换按总人口数量排序,便于比较 - `labelFormatter: '~s'` 格式化数值显示(如 1M 表示 1 百万) - 旋转 x 轴标签以避免重叠 例子 3: **水平堆叠柱状图适合类别较多的情况** 当分类较多时,水平堆叠柱状图能够更好地展示标签,避免文字重叠。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', width: 800, height: 600, }); chart.options({ type: 'interval', autoFit: true, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/f129b517-158d-41a9-83a3-3294d639b39e.csv', format: 'csv', }, coordinate: { transform: [{ type: 'transpose' }] }, encode: { x: 'state', y: 'population', color: 'age' }, transform: [{ type: 'stackY' }, { type: 'sortX', by: 'y', reverse: true }], axis: { y: { labelFormatter: '~s', title: null, }, x: { title: null, }, }, legend: { color: { position: 'bottom', title: '年龄段', }, }, }); chart.render(); ``` **说明**: - 使用 `coordinate: { transform: [{ type: 'transpose' }] }` 实现水平方向 - 标签可以水平排列,提高可读性 - 适合有较多分类的数据 例子 4: **百分比堆叠柱状图展示比例关系** 当重点关注各部分占比而非绝对数值时,百分比堆叠柱状图是更好的选择。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', width: 800, height: 600, }); chart.options({ type: 'interval', autoFit: true, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/f129b517-158d-41a9-83a3-3294d639b39e.csv', format: 'csv', }, coordinate: { transform: [{ type: 'transpose' }] }, encode: { x: 'state', y: 'population', color: 'age' }, transform: [ { type: 'stackY' }, { type: 'normalizeY' }, { type: 'sortX', by: 'y', reverse: true }, ], axis: { y: { labelFormatter: '.0%', title: null, }, x: { title: null, }, }, legend: { color: { position: 'bottom', title: '年龄段', }, }, tooltip: { channel: 'y0', valueFormatter: '.0%', }, }); chart.render(); ``` **说明**: - 结合 `stackY` 和 `normalizeY` 变换实现百分比堆叠 - `labelFormatter: '.0%'` 将 y 轴标签格式化为百分比 - 便于比较各州的年龄结构比例 ### 不适合的场景 例子 1: **不适合展示负值数据的简单堆叠** 传统的堆叠柱状图不适合包含负值的数据,因为堆叠逻辑会导致视觉混乱。 例子 2: **子类别过多时视觉效果差** 当子类别数量超过 8-10 个时,颜色区分度下降,用户难以有效区分和比较。 ```ts import { Chart } from '@antv/g2'; // 模拟过多子类别的数据 const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, data: [ { category: 'A', type: 'Type1', value: 10 }, { category: 'A', type: 'Type2', value: 8 }, { category: 'A', type: 'Type3', value: 6 }, { category: 'A', type: 'Type4', value: 4 }, { category: 'A', type: 'Type5', value: 3 }, { category: 'A', type: 'Type6', value: 2 }, { category: 'A', type: 'Type7', value: 2 }, { category: 'A', type: 'Type8', value: 1 }, { category: 'A', type: 'Type9', value: 1 }, { category: 'A', type: 'Type10', value: 1 }, { category: 'A', type: 'Type11', value: 1 }, { category: 'A', type: 'Type12', value: 1 }, { category: 'B', type: 'Type1', value: 12 }, { category: 'B', type: 'Type2', value: 9 }, { category: 'B', type: 'Type3', value: 7 }, { category: 'B', type: 'Type4', value: 5 }, { category: 'B', type: 'Type5', value: 4 }, { category: 'B', type: 'Type6', value: 3 }, { category: 'B', type: 'Type7', value: 2 }, { category: 'B', type: 'Type8', value: 2 }, { category: 'B', type: 'Type9', value: 1 }, { category: 'B', type: 'Type10', value: 1 }, { category: 'B', type: 'Type11', value: 1 }, { category: 'B', type: 'Type12', value: 1 }, ], encode: { x: 'category', y: 'value', color: 'type' }, transform: [{ type: 'stackY' }], }); chart.render(); ``` 在这种情况下,建议将较小的类别合并为"其他"类别,或者使用其他图表类型。 ## 堆叠柱状图的扩展 ### 分散堆叠柱状图 分散堆叠柱状图可以展示正负值的对比,特别适合展示盈亏、增减等对比数据。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); const fields = [ 'lost > 100$', 'lost <= 100$', 'gained <= 100$', 'gained > 100$', ]; chart.options({ type: 'view', paddingLeft: 25, children: [ { type: 'interval', data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/nivo-gain-lost.json', transform: [ { type: 'fold', fields, }, ], }, encode: { x: 'user', y: 'value', color: 'key' }, transform: [{ type: 'stackY' }], scale: { x: { padding: 0.2 }, y: { domainMin: -100, domainMax: 100 }, color: { domain: fields, range: ['#97e3d5', '#61cdbb', '#e25c3b', '#f47560'], }, }, style: { radius: 10 }, axis: { y: { position: 'right', title: false, labelFormatter: (v) => `${v}%` }, }, legend: { color: { title: false } }, labels: [ { text: 'value', position: 'inside', formatter: (v) => (v ? `${v}%` : ''), transform: [{ type: 'overlapDodgeY' }], fill: '#000', fontSize: 10, }, ], }, { type: 'lineY', data: [0], style: { lineWidth: 2, stroke: '#e25c3b', strokeOpacity: 1 }, }, { type: 'text', style: { x: -10, y: '75%', text: 'lost', fontWeight: 'bold', dy: -10, transform: 'rotate(-90)', fill: '#61cdbb', }, }, { type: 'text', style: { x: -10, y: '20%', text: 'gain', fontWeight: 'bold', dy: -10, transform: 'rotate(-90)', fill: '#e25c3b', }, }, ], }); chart.render(); ``` **说明**: - 使用正负值数据展示盈亏对比 - 设置 `domainMin` 和 `domainMax` 控制坐标轴范围 - 添加分界线突出正负值的分界 - 使用圆角样式增强视觉效果 ## 堆叠柱状图与其他图表的对比 ### 堆叠柱状图和[柱状图](/zh/charts/bar/) - 堆叠柱状图展示多个子类别的构成关系和总量 - 普通柱状图主要用于比较不同类别的单一数值 - 堆叠柱状图能同时显示部分和整体的关系 ### 堆叠柱状图和[堆叠面积图](/zh/charts/stacked-area/) - 堆叠柱状图适合离散的分类数据比较 - 堆叠面积图适合连续的时间序列数据趋势展示 - 堆叠柱状图更便于精确数值比较 ### 堆叠柱状图和[饼图](/zh/charts/pie/) - 堆叠柱状图可以同时比较多个类别的构成 - 饼图只能展示单个整体的构成比例 - 堆叠柱状图在比较多个类别时更有优势 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 茎叶图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/stem-leaf/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/stem-leaf.md - Description: 把数值拆分为茎和叶,在保留原始数值的同时展示分布,了解茎叶图的统计用途及 G2 示例。 --- title: "茎叶图" description: "把数值拆分为茎和叶,在保留原始数值的同时展示分布,了解茎叶图的统计用途及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/stem-leaf/" version: "5.4.8" --- stem-leaf ## 茎叶图的简介 茎叶图是一种用于显示数据分布的可视化图表,它既能保留原始数据信息,又能直观地展示数据的分布形状。茎叶图通过将数据分解为"茎"(高位数字)和"叶"(低位数字)的形式来组织和显示数据。 茎叶图有别于[直方图](/zh/charts/histogram/),茎叶图保留了每个数据点的具体数值,而直方图只显示数据落在各区间内的频次。茎叶图特别适合小到中等规模的数据集,能够同时显示数据的分布形状、集中趋势和具体数值。 当需要比较两组数据的分布时,可以使用背靠背的双向茎叶图,这样能够清晰地对比两组数据的分布特征。 **英文名**:Stem-and-Leaf Plot, Stem-and-Leaf Diagram ## 茎叶图的构成 ### 单向茎叶图 single-stem-leaf | 图表类型 | 单向茎叶图 | | ---------------- | ---------------------------------------------------------------------------------------------------- | | 适合的数据 | 列表:一组连续数值数据 | | 功能 | 显示数据分布形状,保留原始数据信息 | | 数据与图形的映射 | 数值的高位数字作为茎(垂直排列)
数值的低位数字作为叶(水平排列)
每一行代表相同茎值的所有数据 | | 适合的数据条数 | 20-100 条数据,数据过多时建议使用其他可视化方式 | --- ### 双向茎叶图 dual-stem-leaf | 图表类型 | 双向茎叶图 | | ---------------- | ------------------------------------------------------------------------------------------------------ | | 适合的数据 | 列表:两组连续数值数据 | | 功能 | 对比两组数据的分布形状和特征 | | 数据与图形的映射 | 共用茎(中间垂直排列)
左侧组数据的叶向左排列
右侧组数据的叶向右排列
通过颜色区分不同的数据组 | | 适合的数据条数 | 每组 20-50 条数据 | **组成元素:** 1. **茎(Stem)**:数据的高位数字,垂直排列在图的中心 2. **叶(Leaf)**:数据的低位数字,水平排列在茎的两侧 3. **分隔线**:分隔左右两组数据的垂直线 4. **标题/图例**:标识不同数据组的含义 5. **数据排序**:叶通常按从小到大排列 ## 茎叶图的应用场景 ### 适合的场景 #### 1. 基础单向茎叶图 - 展示单组数据分布 单向茎叶图适合展示一组数据的分布情况,能够清晰地显示数据的集中趋势和分布形状。 例子:**学生考试成绩分布** 下图展示了一个班级学生的数学成绩分布: | 学生成绩 | | ------------------------------------------------------------------------------ | | 65, 67, 69, 71, 72, 73, 74, 75, 76, 78, 79, 81, 82, 83, 85, 87, 89, 92, 93, 95 | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', autoFit: true, }); // 单组数据 const rawData = [ 65, 67, 69, 71, 72, 73, 74, 75, 76, 78, 79, 81, 82, 83, 85, 87, 89, 92, 93, 95, ]; // 处理单向茎叶图数据 function processSingleStemLeaf(data) { const stemMap = new Map(); data.forEach((score) => { const stem = Math.floor(score / 10); const leaf = score % 10; if (!stemMap.has(stem)) { stemMap.set(stem, []); } stemMap.get(stem).push(leaf); }); // 排序叶 Array.from(stemMap.values()).forEach((leaves) => { leaves.sort((a, b) => a - b); }); const stems = Array.from(stemMap.keys()).sort((a, b) => b - a); // 从大到小排列 const chartData = []; stems.forEach((stem, index) => { const yPos = index; const leaves = stemMap.get(stem); // 添加茎 chartData.push({ x: 0.4, y: yPos, text: `${stem}`, type: 'stem', fill: '#333', fontSize: 18, fontWeight: 'bold', }); // 添加叶 leaves.forEach((leaf, i) => { chartData.push({ x: 0.47 + i * 0.04, y: yPos, text: `${leaf}`, type: 'leaf', fill: '#1890ff', fontSize: 14, fontWeight: 'normal', }); }); }); return { chartData, maxY: stems.length }; } const { chartData, maxY } = processSingleStemLeaf(rawData); chart.options({ type: 'view', data: chartData, children: [ { type: 'text', encode: { x: 'x', y: 'y', text: 'text', fill: 'fill', fontSize: 'fontSize', fontWeight: 'fontWeight', }, style: { textAlign: 'center', textBaseline: 'middle', }, }, // 使用 lineX 方法添加分割线 { type: 'lineX', data: [0.45], style: { lineWidth: 1, stroke: '#333', strokeOpacity: 0.6, }, }, ], scale: { x: { domain: [0, 1], nice: false }, y: { domain: [-0.5, maxY - 0.5], nice: false }, }, axis: false, }); chart.render(); ``` **说明**: - 茎表示成绩的十位数(6, 7, 8, 9) - 叶表示成绩的个位数,按升序排列 - 从图中可以看出成绩主要集中在 70-80 分区间 #### 2. 双向茎叶图 - 比较两组数据分布 双向茎叶图特别适合比较两组数据的分布差异,能够直观地展示两组数据的相对位置和分布特征。 例子:**比较两个班级的考试成绩分布** 下图展示了 A 班和 B 班学生考试成绩的对比分布: | 数据组 | 成绩范围 | | ------ | -------- | | A 班 | 45-78 | | B 班 | 43-82 | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', autoFit: true, }); const rawData = { left: [ 45, 47, 48, 52, 53, 55, 56, 57, 59, 62, 63, 64, 65, 66, 67, 68, 69, 71, 72, 73, 74, 75, 76, 77, 78, ], right: [ 43, 44, 46, 51, 54, 55, 58, 59, 61, 62, 63, 64, 65, 66, 67, 68, 72, 73, 74, 75, 76, 77, 79, 82, ], }; // 处理双向茎叶图数据 function processDualStemLeaf(data) { const stemMap = new Map(); ['left', 'right'].forEach((side) => { data[side].forEach((score) => { const stem = Math.floor(score / 10); const leaf = score % 10; if (!stemMap.has(stem)) { stemMap.set(stem, { left: [], right: [] }); } stemMap.get(stem)[side].push(leaf); }); }); // 排序 Array.from(stemMap.values()).forEach((group) => { group.left.sort((a, b) => b - a); // 左侧降序 group.right.sort((a, b) => a - b); // 右侧升序 }); const stems = Array.from(stemMap.keys()).sort((a, b) => b - a); // 茎从大到小 const chartData = []; stems.forEach((stem, index) => { const yPos = index; const { left, right } = stemMap.get(stem); // 添加茎 chartData.push({ x: 0.5, y: yPos, text: `${stem}`, type: 'stem', fill: '#333', fontSize: 18, fontWeight: 'bold', }); // 添加左侧叶(A班) left.forEach((leaf, i) => { chartData.push({ x: 0.45 - (i + 1) * 0.035, y: yPos, text: `${leaf}`, type: 'leaf-left', fill: '#1f77b4', fontSize: 14, fontWeight: 'normal', }); }); // 添加右侧叶(B班) right.forEach((leaf, i) => { chartData.push({ x: 0.55 + i * 0.035, y: yPos, text: `${leaf}`, type: 'leaf-right', fill: '#ff7f0e', fontSize: 14, fontWeight: 'normal', }); }); }); // 添加标题 chartData.push({ x: 0.35, y: stems.length, text: 'A班', type: 'title', fill: '#1f77b4', fontSize: 16, fontWeight: 'bold', }); chartData.push({ x: 0.65, y: stems.length, text: 'B班', type: 'title', fill: '#ff7f0e', fontSize: 16, fontWeight: 'bold', }); return { chartData, maxY: stems.length + 1 }; } const { chartData, maxY } = processDualStemLeaf(rawData); chart.options({ type: 'view', data: chartData, children: [ { type: 'text', encode: { x: 'x', y: 'y', text: 'text', fill: 'fill', fontSize: 'fontSize', fontWeight: 'fontWeight', }, style: { textAlign: 'center', textBaseline: 'middle', }, }, { type: 'lineX', data: [{ x: 0.47 }, { x: 0.53 }], encode: { x: 'x', }, style: { lineWidth: 2, stroke: '#000', strokeOpacity: 0.8, }, }, ], scale: { x: { domain: [0, 1], nice: false }, y: { domain: [-0.5, maxY - 0.5], nice: false }, }, axis: false, }); chart.render(); ``` **说明**: - 茎表示成绩的十位数,共用显示在中间 - 左侧叶表示 A 班学生成绩的个位数,按降序排列 - 右侧叶表示 B 班学生成绩的个位数,按升序排列 - 通过颜色区分两个班级的数据 - 可以清晰地比较两个班级的成绩分布差异 #### 3. 分组茎叶图 - 展示多个类别数据 当需要比较多个类别的数据分布时,可以使用分组茎叶图。 例子:**不同年龄段的身高分布** 下图展示了青少年、成年人、老年人三个年龄段的身高分布: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', autoFit: true, }); const ageGroupData = { 青少年: [155, 158, 160, 162, 165, 167, 168, 170, 172, 175], 成年人: [160, 163, 165, 168, 170, 172, 175, 177, 180, 182, 185], 老年人: [150, 155, 158, 160, 162, 165, 167, 168, 170, 172], }; // 处理分组茎叶图数据 function processGroupedStemLeaf(data) { const groups = Object.keys(data); const colors = ['#1f77b4', '#ff7f0e', '#2ca02c']; const chartData = []; let currentY = 0; groups.forEach((group, groupIndex) => { const groupData = data[group]; const stemMap = new Map(); // 添加组标题 chartData.push({ x: 0.1, y: currentY, text: group, type: 'group-title', fill: colors[groupIndex], fontSize: 16, fontWeight: 'bold', }); currentY += 0.5; // 处理数据 groupData.forEach((height) => { const stem = Math.floor(height / 10); const leaf = height % 10; if (!stemMap.has(stem)) { stemMap.set(stem, []); } stemMap.get(stem).push(leaf); }); // 排序叶 Array.from(stemMap.values()).forEach((leaves) => { leaves.sort((a, b) => a - b); }); const stems = Array.from(stemMap.keys()).sort((a, b) => a - b); stems.forEach((stem) => { const leaves = stemMap.get(stem); // 添加茎 chartData.push({ x: 0.2, y: currentY, text: `${stem}`, type: 'stem', fill: '#333', fontSize: 14, fontWeight: 'bold', }); // 添加叶 leaves.forEach((leaf, i) => { chartData.push({ x: 0.27 + i * 0.03, y: currentY, text: `${leaf}`, type: 'leaf', fill: colors[groupIndex], fontSize: 12, fontWeight: 'normal', }); }); currentY += 1; }); currentY += 0.5; // 组间间距 }); return { chartData, maxY: currentY }; } const { chartData, maxY } = processGroupedStemLeaf(ageGroupData); chart.options({ type: 'view', data: chartData, children: [ { type: 'text', encode: { x: 'x', y: 'y', text: 'text', fill: 'fill', fontSize: 'fontSize', fontWeight: 'fontWeight', }, style: { textAlign: 'left', textBaseline: 'middle', }, }, // 使用 lineX 方法添加分割线 { type: 'lineX', data: [0.25], style: { lineWidth: 1, stroke: '#333', strokeOpacity: 0.6, }, }, ], scale: { x: { domain: [0, 1], nice: false }, y: { domain: [0, maxY], nice: false }, }, axis: false, }); chart.render(); ``` **说明**: - 将数据按年龄段分组显示 - 每组使用不同颜色进行区分 - 可以清晰地比较不同年龄段的身高分布特征 - 适合展示多个类别数据的分布对比 ### 不适合的场景 例子 1: **数据量过大的情况** 当数据量超过 100 个时,茎叶图会变得过于拥挤,难以阅读。此时建议使用直方图或箱线图。 例子 2: **数据范围过大的情况** 当数据跨度很大(比如从 1 到 10000)时,茎的数量会过多,导致图表冗长。建议先对数据进行适当的分组或变换。 例子 3: **需要精确数值比较的场景** 虽然茎叶图保留了原始数据,但当需要进行精确的数值计算和比较时,表格形式可能更合适。 ## 茎叶图与其他图表的对比 ### 茎叶图和[直方图](/zh/charts/histogram/) - **茎叶图**:保留每个数据点的具体数值,既显示分布形状又保持数据完整性,但仅适用于小到中等规模的数据集 - **直方图**:通过分组区间展示数据分布,能处理大量数据,但会丢失原始数据的具体数值信息 ### 茎叶图和[箱线图](/zh/charts/boxplot/) - **茎叶图**:展示完整的数据分布形状和每个数据点,便于识别数据模式和异常值的具体位置 - **箱线图**:通过五数概括(最小值、Q1、中位数、Q3、最大值)展示数据分布的统计特征,更加简洁但信息有限 ### 茎叶图和[散点图](/zh/charts/scatter-plot/) - **茎叶图**:专注于单一变量的分布展示,通过茎叶结构有序排列数据 - **散点图**:展示两个变量之间的关系和相关性,更适合探索变量间的关联模式 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 旭日图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/sunburst/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/sunburst.md - Description: 用嵌套的环形扇区表示层级结构与数值占比,了解旭日图的阅读方式、适用场景及 G2 示例。 --- title: "旭日图" description: "用嵌套的环形扇区表示层级结构与数值占比,了解旭日图的阅读方式、适用场景及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/sunburst/" version: "5.4.8" --- sunburst ## 旭日图的简介 旭日图是一种层次化的数据可视化图表,通过同心圆的形式展示多层级数据结构。每个层级用一个环表示,环的内外半径表示层级的深度,环的角度大小表示数据的数值大小。旭日图融合了饼图和树状图的优点,采用圆形分割的方式来展示多层嵌套的信息结构。 旭日图特别适合展示具有明确父子关系的层次化数据,能够直观地显示各个层级之间的比例关系和层次结构。通过环形布局,旭日图有效地展现了数据间的层次关系和比例分配,比起树图具备节省空间、整体情况更加直观等优点。 旭日图还支持下钻交互,用户可以点击某个扇形区域来查看该层级的详细信息,这使得它成为商业分析、地理学研究等领域不可或缺的工具之一。 **英文名**:Sunburst Chart ## 旭日图的构成 basic-sunburst | 图表类型 | 旭日图 | | ---------------- | -------------------------------------------------------------------------------- | | 适合的数据 | 层次化数据:具有多层级父子关系的嵌套数据结构 | | 功能 | 展示层次化数据的比例关系和层级结构 | | 数据与图形的映射 | 层级关系映射到同心圆的环
数值大小映射到扇形的角度
不同类别通过颜色进行区分 | | 适合的场景 | 组织架构、文件系统、预算分配等具有清晰层次关系的数据展示 | ## 旭日图的应用场景 ### 适合的场景 例子 1: **展示文件系统层次** 下图展示了一个文件系统的层次结构,通过旭日图可以清晰地看到各个文件夹和文件的大小分布。 ```ts 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', theme: 'classic', autoFit: true, }); chart.options({ type: 'sunburst', data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/sunburst.json', }, encode: { value: 'sum', }, animate: { enter: { type: 'waveIn' } }, }); chart.render(); ``` 例子 2: **展示预算分配结构** 旭日图可以清晰地展示预算在不同项目和子项目之间的分配情况,帮助管理者了解资源配置。 ```ts 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', theme: 'classic', 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(); ``` ### 不适合的场景 例子 1: **层级过深导致的可读性问题** 当层级超过 4 层时,旭日图的外层扇形会变得非常细小,难以辨识。下面展示一个 6 层深度的旭日图,可以看到外层已经完全无法识别。 **旭日图(不推荐)** ```ts 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', theme: 'classic', autoFit: true, }); // 创建层级过深的数据(6层层级)- 通过函数生成 function generateDeepData() { const createNode = (prefix, depth, maxDepth) => { if (depth >= maxDepth) { return { label: `${prefix}-叶子节点`, count: Math.floor(Math.random() * 20) + 5, }; } const children = []; const childCount = depth < 3 ? 3 : 2; // 前3层每层3个子节点,后续每层2个 for (let i = 1; i <= childCount; i++) { children.push(createNode(`${prefix}-${i}`, depth + 1, maxDepth)); } return { label: `${prefix}`, children: children, }; }; return createNode('ROOT', 0, 6); } const deepData = generateDeepData(); chart.options({ type: 'sunburst', data: { value: deepData }, encode: { value: 'count', text: 'label', }, style: { radius: 4 }, // 小圆角 legend: false, }); chart.render(); ``` **矩形树图(推荐)** ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); // 创建层级过深的数据(6层层级)- 通过函数生成 function generateDeepData() { const createNode = (prefix, depth, maxDepth) => { if (depth >= maxDepth) { return { name: `${prefix}-叶子节点`, value: Math.floor(Math.random() * 20) + 5, }; } const children = []; const childCount = depth < 3 ? 3 : 2; // 前3层每层3个子节点,后续每层2个 for (let i = 1; i <= childCount; i++) { children.push(createNode(`${prefix}-${i}`, depth + 1, maxDepth)); } return { name: `${prefix}`, children: children, }; }; return createNode('ROOT', 0, 6); } const deepData = generateDeepData(); chart.options({ type: 'treemap', data: { value: deepData }, layout: { path: (d) => d.name.replace(/\./g, '/'), tile: 'treemapBinary', paddingInner: 1, }, encode: { value: 'value', }, style: { labelFill: '#000', labelStroke: '#fff', labelLineWidth: 1, labelFontSize: 10, labelPosition: 'top-left', labelDx: 2, labelDy: 2, }, legend: false, }); chart.render(); ``` **说明**:当数据层级过深时,旭日图的外层扇形变得极其细小,标签重叠严重,用户难以获取有效信息。此时建议使用**矩形树图**。 例子 2: **类别数量过多的问题** 当第二层类别超过 20 个时,旭日图的颜色区分和角度感知都会变得困难,标签拥挤难以阅读。 **旭日图(不推荐)** ```ts 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', theme: 'classic', autoFit: true, }); // 创建类别过多的层次数据(第二层25个子类别) const manyData = { label: '零售连锁企业', children: [ { label: '华北区', children: [ { label: '北京朝阳店', count: 142 }, { label: '北京海淀店', count: 135 }, { label: '北京西城店', count: 128 }, { label: '天津滨海店', count: 156 }, { label: '石家庄裕华店', count: 98 }, { label: '太原迎泽店', count: 89 }, { label: '呼和浩特新城店', count: 67 }, ], }, { label: '华东区', children: [ { label: '上海浦东店', count: 189 }, { label: '上海静安店', count: 167 }, { label: '杭州西湖店', count: 145 }, { label: '南京鼓楼店', count: 134 }, { label: '苏州园区店', count: 123 }, { label: '无锡新区店', count: 112 }, { label: '宁波海曙店', count: 101 }, { label: '合肥蜀山店', count: 95 }, { label: '福州鼓楼店', count: 87 }, { label: '厦门思明店', count: 79 }, { label: '南昌东湖店', count: 72 }, { label: '济南历下店', count: 88 }, { label: '青岛市南店', count: 94 }, { label: '烟台芝罘店', count: 76 }, { label: '潍坊奎文店', count: 68 }, { label: '临沂兰山店', count: 63 }, { label: '淄博张店', count: 58 }, { label: '威海环翠店', count: 54 }, ], }, ], }; chart.options({ type: 'sunburst', data: { value: manyData }, encode: { value: 'count', text: 'label', }, labels: [ { text: 'name', }, ], legend: false, }); chart.render(); ``` **柱状图(推荐)** ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); const manyCategories = [ { category: '北京朝阳店', value: 142, group: '华北区' }, { category: '北京海淀店', value: 135, group: '华北区' }, { category: '北京西城店', value: 128, group: '华北区' }, { category: '天津滨海店', value: 156, group: '华北区' }, { category: '石家庄裕华店', value: 98, group: '华北区' }, { category: '太原迎泽店', value: 89, group: '华北区' }, { category: '呼和浩特新城店', value: 67, group: '华北区' }, { category: '上海浦东店', value: 189, group: '华东区' }, { category: '上海静安店', value: 167, group: '华东区' }, { category: '杭州西湖店', value: 145, group: '华东区' }, { category: '南京鼓楼店', value: 134, group: '华东区' }, { category: '苏州园区店', value: 123, group: '华东区' }, { category: '无锡新区店', value: 112, group: '华东区' }, { category: '宁波海曙店', value: 101, group: '华东区' }, { category: '合肥蜀山店', value: 95, group: '华东区' }, { category: '福州鼓楼店', value: 87, group: '华东区' }, { category: '厦门思明店', value: 79, group: '华东区' }, { category: '南昌东湖店', value: 72, group: '华东区' }, { category: '济南历下店', value: 88, group: '华东区' }, { category: '青岛市南店', value: 94, group: '华东区' }, { category: '烟台芝罘店', value: 76, group: '华东区' }, { category: '潍坊奎文店', value: 68, group: '华东区' }, { category: '临沂兰山店', value: 63, group: '华东区' }, { category: '淄博张店', value: 58, group: '华东区' }, { category: '威海环翠店', value: 54, group: '华东区' }, ]; chart.options({ type: 'interval', data: manyCategories, encode: { x: 'category', y: 'value', color: 'group' }, transform: [{ type: 'dodgeX' }], coordinate: { transform: [{ type: 'transpose' }] }, legend: { color: { position: 'top' } }, }); chart.render(); ``` **说明**:当旭日图包含过多类别时(如上图显示的 25 个门店),第二层扇形角度过小,标签严重重叠,颜色难以区分,视觉效果混乱。此时建议使用**分组柱状图**进行清晰对比。 例子 3: **数值差异过小的问题** 当两层数据中第二层类别的数值差异很小时,旭日图的角度差异不明显,难以进行有效对比。 **旭日图(不推荐)** ```ts 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', theme: 'classic', autoFit: true, }); // 创建数值差异很小的两层数据(第二层各城市销售额差异在1%以内) const similarData = { label: '全国销售网络', children: [ { label: '华东大区', children: [ { label: '上海市', count: 2997 }, { label: '杭州市', count: 3001 }, { label: '南京市', count: 2999 }, { label: '苏州市', count: 3003 }, ], }, { label: '华北大区', children: [ { label: '北京市', count: 2998 }, { label: '天津市', count: 3002 }, { label: '石家庄市', count: 2996 }, { label: '济南市', count: 3004 }, ], }, ], }; chart.options({ type: 'sunburst', data: { value: similarData }, encode: { value: 'count', text: 'label', }, legend: { color: { position: 'bottom' } }, }); chart.render(); ``` **柱状图(推荐)** ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); // 对应的柱状图数据(8个城市) const smallDiffData = [ { category: '上海市', value: 2997, group: '华东大区' }, { category: '杭州市', value: 3001, group: '华东大区' }, { category: '南京市', value: 2999, group: '华东大区' }, { category: '苏州市', value: 3003, group: '华东大区' }, { category: '北京市', value: 2998, group: '华北大区' }, { category: '天津市', value: 3002, group: '华北大区' }, { category: '石家庄市', value: 2996, group: '华北大区' }, { category: '济南市', value: 3004, group: '华北大区' }, ]; chart.options({ type: 'interval', data: smallDiffData, encode: { x: 'category', y: 'value', color: 'group' }, transform: [{ type: 'dodgeX' }], axis: { y: { nice: false, domain: [2990, 3010] }, // 缩小Y轴范围突出差异 }, legend: { color: { position: 'top' } }, }); chart.render(); ``` **说明**:当数据中各类别的数值差异很小时(如上图中各城市销售额在 2996-3004 之间,差异不到 0.3%),旭日图的角度差异极其微小,用户无法通过视觉感知到数值的细微差别。此时建议使用**分组柱状图**并调整 Y 轴范围来突出差异。 ## 旭日图的扩展用法 ### 带标签的旭日图 为旭日图添加标签可以让数据更加清晰易读: ```ts 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(); ``` ### 自定义颜色的旭日图 通过设置颜色通道,可以更好地区分不同的类别: ```ts 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', color: 'label', }, }); chart.render(); ``` ### 自定义样式的旭日图 可以通过样式配置来定制旭日图的外观: ```ts 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', }, style: { radius: 8, // 内置透明度 fillOpacity ,根据 0.85 ** depth 层级计算 fillOpacity: (v) => v['fillOpacity'], fill: (v) => { if (v['path'] === '类别 3') return 'red'; if (v['name'] === '类别 2.1.1') return 'red'; }, }, }); chart.render(); ``` ### 带交互的旭日图 旭日图支持下钻交互,用户可以点击扇形区域进行层级导航: ```ts 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', }, ], }, ], interaction: { drillDown: { breadCrumb: { rootText: '起始', style: { fontSize: '18px', fill: '#333', }, active: { fill: 'red', }, }, // FixedColor default: true, true -> drillDown update scale, false -> scale keep. isFixedColor: false, }, }, state: { active: { zIndex: 2, stroke: 'red' }, inactive: { zIndex: 1, stroke: '#fff' }, }, }); chart.render(); ``` ## 旭日图与其他图表的对比 ### 旭日图和[饼图](/zh/charts/pie/) 旭日图可以看作是多层级的饼图,相比普通饼图有以下优势: - 能够展示多层级的层次关系,而饼图只能展示单层级数据 - 更好地利用空间,可以在一个图表中展示更多信息 - 支持下钻交互,用户可以逐层探索数据 - 当数据具有层次结构时,旭日图比饼图更合适。 ### 旭日图和矩形树图 旭日图和矩形树图都可以展示层次化数据,但有不同的特点: | 对比维度 | 旭日图 | 矩形树图 | | -------- | ------------------------ | ---------------------- | | 视觉形式 | 圆形布局 | 矩形布局 | | 空间利用 | 圆形边界,空间利用率较低 | 矩形填充,空间利用率高 | | 层次展示 | 同心圆,层次关系清晰 | 嵌套矩形,包含关系明确 | | 比例感知 | 角度比较,适中 | 面积比较,更准确 | | 适用场景 | 强调层次关系和整体结构 | 强调精确的比例对比 | ### 旭日图和环图 环图可以看作是两层的旭日图: **旭日图相比环图的优势**: - 支持三层及以上的多层级数据 - 能够展示更复杂的层次关系 - 更适合组织架构等深层次数据 **环图的优势**: - 更简洁,易于理解 - 对于两层数据的展示更清晰 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 矩形树图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/treemap/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/treemap.md - Description: 用嵌套矩形的面积表达层级数据的数值大小,了解矩形树图的布局特点、适用场景及 G2 示例。 --- title: "矩形树图" description: "用嵌套矩形的面积表达层级数据的数值大小,了解矩形树图的布局特点、适用场景及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/treemap/" version: "5.4.8" --- treemap ## 矩形树图的简介 矩形树图是一种用于展示层次化数据的可视化图表,通过一系列嵌套的矩形来表示树状结构的数据。每个矩形的面积大小与其对应的数值成正比,颜色通常用来区分不同的类别或层级。矩形树图将树形数据结构转化为平面空间的矩形填充,能够直观地展示数据的层次关系和数值比例。 矩形树图特别适合展示具有层级关系的大量数据,如文件系统、组织架构、预算分配、股票市场等。相比传统的树形结构图,矩形树图能更有效地利用空间,并且具有展示占比功能,使得用户能够快速理解数据的分布和重要性。 矩形树图还支持交互式下钻功能,用户可以点击某个矩形区域来查看该层级的详细子项,这使得它成为数据探索和分析的强大工具。 **英文名**:Treemap ## 矩形树图的构成 basic-treemap | 图表类型 | 矩形树图 | | ---------------- | ---------------------------------------------------------------------------------- | | 适合的数据 | 层次化数据:具有树状结构的嵌套数据,包含分类字段和数值字段 | | 功能 | 展示层次化数据的比例关系和分布情况 | | 数据与图形的映射 | 层级关系映射到嵌套矩形
数值大小映射到矩形面积
不同类别通过颜色和位置进行区分 | | 适合的场景 | 文件系统、组织架构、预算分配、股票组合等具有清晰层次关系的数据展示 | ## 矩形树图的应用场景 ### 适合的场景 例子 1: **展示文件系统结构** 下图展示了软件项目文件系统的层次结构,通过矩形树图可以清晰地看到各个模块和文件的大小分布,帮助开发者了解代码结构。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); chart.options({ type: 'treemap', data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/flare-treemap.json', }, layout: { path: (d) => d.name.replace(/\./g, '/'), tile: 'treemapBinary', paddingInner: 1, }, encode: { value: 'size', color: (d) => d.parent?.data.name.split('.')[1] || 'root', }, style: { labelText: (d) => { const name = d.data.name .split('.') .pop() .split(/(?=[A-Z][a-z])/g)[0]; return name; }, labelFill: '#000', labelPosition: 'top-left', labelDx: 3, labelDy: 3, fillOpacity: 0.7, }, tooltip: { title: (d) => d.path?.join?.('.') || d.data.name, items: [{ field: 'value', name: '大小' }], }, }); chart.render(); ``` **说明**: - 使用 `path` 配置将扁平化数据转换为层次结构 - `layout.tile` 设置为 `treemapBinary` 使用二分布局算法 - `paddingInner` 设置矩形间的内边距,增强视觉分离效果 例子 2: **展示预算分配情况** 矩形树图非常适合展示预算在不同项目和子项目之间的分配情况,帮助管理者快速了解资源配置。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); const budgetData = { name: '年度预算', children: [ { name: '研发部门', children: [ { name: '前端开发', value: 1200 }, { name: '后端开发', value: 1500 }, { name: '测试', value: 800 }, { name: '设计', value: 600 }, ], }, { name: '市场部门', children: [ { name: '广告投放', value: 2000 }, { name: '活动策划', value: 800 }, { name: '内容营销', value: 500 }, ], }, { name: '运营部门', children: [ { name: '客户服务', value: 700 }, { name: '数据分析', value: 400 }, { name: '运营支持', value: 300 }, ], }, { name: '其他支出', value: 1200 }, ], }; chart.options({ type: 'treemap', data: { value: budgetData }, layout: { tile: 'treemapSquarify', paddingInner: 2, }, encode: { value: 'value', color: (d) => d.path[1] || d.data.name, }, style: { labelText: (d) => d.data.name, labelFill: '#fff', labelStroke: '#000', labelLineWidth: 0.5, labelFontSize: 12, }, tooltip: { title: (d) => d.data.name, items: [ { field: 'value', name: '预算', valueFormatter: (v) => `${v}万元` }, ], }, }); chart.render(); ``` **说明**: - 使用层级结构数据,不需要配置 `path` - `tile: 'treemapSquarify'` 使用黄金比例算法,产生更接近正方形的矩形 - 通过 `labelPosition: 'center'` 将标签居中显示 例子 3: **展示销售数据层次** 矩形树图可以有效展示销售数据的多层级结构,从地区到产品类别再到具体产品。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); const salesData = { name: '全国销售', children: [ { name: '华东地区', children: [ { name: '数码产品', children: [ { name: '手机', value: 3200 }, { name: '电脑', value: 2800 }, { name: '平板', value: 1500 }, ], }, { name: '家电', children: [ { name: '冰箱', value: 2100 }, { name: '洗衣机', value: 1800 }, { name: '空调', value: 2500 }, ], }, ], }, { name: '华北地区', children: [ { name: '数码产品', children: [ { name: '手机', value: 2800 }, { name: '电脑', value: 2400 }, { name: '平板', value: 1200 }, ], }, { name: '家电', children: [ { name: '冰箱', value: 1900 }, { name: '洗衣机', value: 1600 }, { name: '空调', value: 2200 }, ], }, ], }, { name: '华南地区', children: [ { name: '数码产品', value: 2200 }, { name: '家电', value: 1800 }, ], }, ], }; chart.options({ type: 'treemap', data: { value: salesData }, layout: { tile: 'treemapSliceDice', paddingInner: 3, layer: 2, // 只显示前两层 }, encode: { value: 'value', color: (d) => d.path[1] || 'default', }, style: { labelText: (d) => { const name = d.data.name; const value = d.value; return d.depth <= 1 ? `${name}\n${Math.round(value)}万` : name; }, labelFill: '#000', labelFontSize: (d) => Math.max(10, Math.min(16, Math.sqrt(d.x1 - d.x0) * 2)), stroke: '#fff', lineWidth: 2, }, interaction: { treemapDrillDown: { breadCrumbY: 12, activeFill: '#873bf4', breadCrumbFill: 'rgba(0, 0, 0, 0.85)', breadCrumbFontSize: 12, }, }, legend: false, }); chart.render(); ``` ### 不适合的场景 例子 1: **数值差异极小的数据对比** 当数据中各项目的数值差异很小时,矩形树图的面积差异不明显,用户难以感知到数值的细微差别。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); // 数值差异很小的数据 const similarData = { name: '地区销售(相似数值)', children: [ { name: '北京', value: 9995 }, { name: '上海', value: 10001 }, { name: '广州', value: 9999 }, { name: '深圳', value: 10003 }, { name: '杭州', value: 9997 }, { name: '南京', value: 10002 }, ], }; chart.options({ type: 'treemap', data: { value: similarData }, encode: { value: 'value' }, style: { labelText: (d) => `${d.data.name}\n${d.value}`, labelFill: '#000', }, }); chart.render(); ``` **柱状图(推荐)** ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); const similarDataFlat = [ { name: '北京', value: 9995 }, { name: '上海', value: 10001 }, { name: '广州', value: 9999 }, { name: '深圳', value: 10003 }, { name: '杭州', value: 9997 }, { name: '南京', value: 10002 }, ]; chart.options({ type: 'interval', data: similarDataFlat, encode: { x: 'name', y: 'value', color: 'name' }, axis: { y: { nice: false, domain: [9990, 10010] }, // 缩小Y轴范围突出差异 }, }); chart.render(); ``` **说明**:当数据值差异小于 5% 时,矩形树图的面积差异几乎不可感知,此时建议使用柱状图并调整 Y 轴范围来突出差异。 例子 2: **非层次化的简单分类数据** 对于没有层次关系的简单分类数据,矩形树图会显得过于复杂,不如饼图或柱状图直观。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); // 简单的分类数据,没有层次关系 const simpleData = { name: '产品销量', children: [ { name: '产品A', value: 230 }, { name: '产品B', value: 180 }, { name: '产品C', value: 150 }, { name: '产品D', value: 120 }, ], }; chart.options({ type: 'treemap', data: { value: simpleData }, encode: { value: 'value' }, style: { labelText: (d) => d.data.name, labelFill: '#000', }, }); chart.render(); ``` **饼图(推荐)** ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); const simpleDataFlat = [ { name: '产品A', value: 230 }, { name: '产品B', value: 180 }, { name: '产品C', value: 150 }, { name: '产品D', value: 120 }, ]; chart.options({ type: 'interval', data: simpleDataFlat, encode: { y: 'value', color: 'name' }, transform: [{ type: 'stackY' }], coordinate: { type: 'theta' }, legend: { color: { position: 'right' }, }, }); chart.render(); ``` **说明**:对于简单的分类占比数据,饼图能更直观地展示各部分在整体中的比例关系。 ## 矩形树图的扩展用法 ### 带下钻交互的矩形树图 矩形树图最强大的功能之一是支持下钻交互,用户可以点击某个区域来深入查看该层级的详细信息: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); const drillDownData = { name: '商品', children: [ { name: '文具', children: [ { name: '笔类', children: [ { name: '铅笔', value: 430 }, { name: '圆珠笔', value: 530 }, { name: '钢笔', value: 80 }, { name: '水彩笔', value: 130 }, ], }, { name: '笔记本', value: 160 }, { name: '文件夹', value: 90 }, { name: '其他', value: 80 }, ], }, { name: '零食', children: [ { name: '饼干', value: 280 }, { name: '辣条', value: 150 }, { name: '糖果', value: 210 }, { name: '饮品', children: [ { name: '可乐', value: 122 }, { name: '矿泉水', value: 244 }, { name: '果汁', value: 49 }, { name: '牛奶', value: 82 }, ], }, { name: '其他', value: 40 }, ], }, { name: '其他商品', value: 450 }, ], }; chart.options({ type: 'treemap', data: { value: drillDownData }, layout: { tile: 'treemapBinary', paddingInner: 5, }, encode: { value: 'value' }, style: { labelFill: '#000', labelStroke: '#fff', labelLineWidth: 1.5, labelFontSize: 14, labelPosition: 'top-left', labelDx: 5, labelDy: 5, }, interaction: { treemapDrillDown: { breadCrumbY: 12, activeFill: '#873bf4', breadCrumbFill: 'rgba(0, 0, 0, 0.85)', breadCrumbFontSize: 12, }, }, tooltip: { title: (d) => d.data.name, items: [{ field: 'value', name: '数量' }], }, }); chart.render(); ``` ### 自定义样式的矩形树图 通过丰富的样式配置,可以创建个性化的矩形树图: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); const customData = { name: '技术栈', children: [ { name: '前端', children: [ { name: 'React', value: 45 }, { name: 'Vue', value: 35 }, { name: 'Angular', value: 20 }, ], }, { name: '后端', children: [ { name: 'Node.js', value: 40 }, { name: 'Python', value: 35 }, { name: 'Java', value: 25 }, ], }, { name: '数据库', children: [ { name: 'MySQL', value: 50 }, { name: 'MongoDB', value: 30 }, { name: 'Redis', value: 20 }, ], }, ], }; chart.options({ type: 'treemap', data: { value: customData }, layout: { tile: 'treemapResquarify', paddingInner: 4, paddingOuter: 2, }, encode: { value: 'value', color: (d) => d.path[1] || 'default', }, scale: { color: { range: ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD'], }, }, style: { labelText: (d) => d.data.name, labelFill: '#fff', labelStroke: '#000', labelLineWidth: 1, labelFontSize: (d) => Math.max(10, Math.min(18, (d.x1 - d.x0) * 0.1)), labelFontWeight: 'bold', stroke: '#fff', lineWidth: 3, radius: 4, // 圆角 fillOpacity: 0.9, shadowColor: 'rgba(0, 0, 0, 0.1)', shadowBlur: 4, shadowOffsetX: 2, shadowOffsetY: 2, }, interaction: [ { type: 'elementHighlight', }, ], }); chart.render(); ``` ### 带渐变色的矩形树图 通过渐变色可以增强矩形树图的视觉效果: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', autoFit: true, }); const gradientData = { name: '市场份额', children: [ { name: '阿里巴巴', value: 28 }, { name: '腾讯', value: 25 }, { name: '字节跳动', value: 18 }, { name: '美团', value: 15 }, { name: '京东', value: 12 }, { name: '百度', value: 10 }, { name: '网易', value: 8 }, { name: '小米', value: 7 }, { name: '滴滴', value: 6 }, { name: '拼多多', value: 5 }, { name: '新浪', value: 4 }, { name: '搜狐', value: 3 }, { name: '360', value: 2 }, { name: '其他', value: 7 }, ], }; chart.options({ type: 'treemap', data: { value: gradientData }, layout: { tile: 'treemapSquarify', paddingInner: 3, }, encode: { value: 'value', color: 'value', }, scale: { color: { type: 'sequential', range: ['#E8F4FD', '#1890FF'], }, }, style: { labelText: (d) => `${d.data.name}\n${d.value}%`, labelFill: (d) => (d.value > 20 ? '#fff' : '#000'), labelFontSize: 14, labelFontWeight: 'bold', stroke: '#fff', lineWidth: 2, }, tooltip: { title: (d) => d.data.name, items: [ { field: 'value', name: '市场份额', valueFormatter: (v) => `${v}%` }, ], }, }); chart.render(); ``` ## 矩形树图与其他图表的对比 ### 矩形树图和[旭日图](/zh/charts/sunburst/) 矩形树图和旭日图都可以展示层次化数据,但有不同的特点: | 对比维度 | 矩形树图 | 旭日图 | | -------- | ---------------------- | ---------------------- | | 视觉形式 | 矩形布局 | 圆形布局 | | 空间利用 | 矩形填充,利用率高 | 圆形边界,利用率较低 | | 比例感知 | 面积比较,更准确 | 角度比较,适中 | | 层次展示 | 嵌套矩形,包含关系明确 | 同心圆,层次关系清晰 | | 适用场景 | 强调精确的比例对比 | 强调层次关系和整体结构 | ### 矩形树图和[饼图](/zh/charts/pie/) 矩形树图相比饼图的优势: - **层次化数据**:矩形树图可以展示多层级数据,而饼图只能展示单层级 - **空间利用**:矩形树图能更有效利用空间,展示更多信息 - **精确比较**:矩形面积比角度更容易进行精确比较 - **交互性**:支持下钻交互,可以逐层探索数据 饼图的优势: - **整体感知**:更好地展示各部分与整体的关系 - **简洁性**:对于简单分类数据更清晰易懂 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 韦恩图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/venn/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/venn.md - Description: 用相交区域展示集合间的交集与包含关系,了解韦恩图的适用场景、数据表达方式及 G2 示例。 --- title: "韦恩图" description: "用相交区域展示集合间的交集与包含关系,了解韦恩图的适用场景、数据表达方式及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/venn/" version: "5.4.8" --- venn ## 韦恩图的简介 韦恩图是一种用圆形或其他封闭曲线来表示集合及其关系的图表,由英国数学家约翰·韦恩(John Venn)于 1880 年发明。韦恩图通过重叠的区域来显示不同集合之间的交集、并集和差集关系,是数据可视化中展示集合关系的经典图表类型。 韦恩图特别适合展示不同数据集合之间的关系,如用户群体的重叠关系、产品功能的交集、市场分析中的重叠用户等。通过直观的圆形区域和重叠部分,能够清晰地表达复杂的集合逻辑关系。 当需要分析多个群体或类别之间的共同点和差异时,韦恩图是一个非常有效的可视化工具。它能够帮助用户快速理解数据之间的包含、交叉和独立关系。 **英文名**:Venn Diagram ## 韦恩图的构成 ### 基础韦恩图 basic-venn | 图表类型 | 基础韦恩图 | | ---------------- | ---------------------------------------------------------------------------- | | 适合的数据 | 集合数据:包含集合名称、集合大小、交集关系的数据 | | 功能 | 展示不同集合之间的交集、并集和独立关系 | | 数据与图形的映射 | 集合名称映射到圆形区域
集合大小映射到圆形面积
交集关系通过重叠区域表示 | | 适合的场景 | 2-4 个集合的关系分析,用户群体分析,产品功能对比 | --- ### 空心韦恩图 hollow-venn | 图表类型 | 空心韦恩图 | | ---------------- | ------------------------------------------------------------------------ | | 适合的数据 | 集合数据:包含集合名称、集合大小、交集关系的数据 | | 功能 | 突出边界线条,减少色彩干扰,更清晰地展示集合边界 | | 数据与图形的映射 | 集合名称映射到圆形边界
集合大小映射到圆形面积
空心设计突出结构关系 | | 适合的场景 | 需要强调集合边界的场景,黑白打印友好的展示 | ## 韦恩图的应用场景 ### 适合的场景 例子 1: **用户群体重叠分析** 下图展示了不同平台用户群体的重叠关系,帮助了解用户在多个平台之间的分布情况。 | 描述 | 集合 | 用户数 | | ------------- | ------------------------ | ------ | | 仅使用微信 | ['微信'] | 1200 | | 仅使用微博 | ['微博'] | 800 | | 仅使用抖音 | ['抖音'] | 1000 | | 微信+微博用户 | ['微信', '微博'] | 300 | | 微信+抖音用户 | ['微信', '抖音'] | 400 | | 微博+抖音用户 | ['微博', '抖音'] | 200 | | 三平台都使用 | ['微信', '微博', '抖音'] | 150 | ```ts 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: 1000, label: '抖音' }, { sets: ['微信', '微博'], size: 300, label: '微信&微博' }, { sets: ['微信', '抖音'], size: 400, label: '微信&抖音' }, { sets: ['微博', '抖音'], size: 200, label: '微博&抖音' }, { sets: ['微信', '微博', '抖音'], size: 150 }, ], 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), }, state: { inactive: { opacity: 0.2 }, active: { opacity: 0.9 }, }, interactions: [{ type: 'elementHighlight' }], legend: false, }); chart.render(); ``` **说明**: - `sets` 字段定义集合关系,单个集合用一个元素,交集用多个元素 - `size` 字段映射到圆形面积大小,表示用户数量 - `padding` 参数控制画布内边距,避免图形被裁切 - 使用透明度区分单独集合和交集区域 例子 2: **产品功能对比分析** 韦恩图可以清晰地展示不同产品功能的重叠情况,帮助产品经理进行功能规划。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', autoFit: true, }); chart.options({ type: 'path', data: { type: 'inline', value: [ { sets: ['产品A'], size: 25, label: 'A' }, { sets: ['产品B'], size: 20, label: 'B' }, { sets: ['产品A', '产品B'], size: 15, label: '共同' }, ], transform: [ { type: 'venn', }, ], }, encode: { d: 'path', color: 'key', }, labels: [ { position: 'inside', text: (d) => d.label, style: { fontSize: 12, fontWeight: 'bold', }, transform: [ { type: 'overlapDodgeY', }, ], }, ], style: { fillOpacity: 0.6, stroke: '#fff', lineWidth: 2, }, scale: { color: { range: ['#1890FF', '#52C41A', '#FF7A45'], }, }, tooltip: { items: [ { name: '功能数量', field: 'size', }, { name: '集合', field: 'key', }, ], }, legend: false, }); chart.render(); ``` **说明**: - 使用自定义颜色方案突出不同产品的特色 - 添加边框线条增强视觉分离效果 - 配置详细的 tooltip 信息展示 例子 3: **技能重叠分析** 展示团队成员技能的重叠情况,帮助项目分工和团队建设。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', autoFit: true, }); 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, label: '前端+设计' }, { sets: ['后端', '设计'], size: 2, label: '后端+设计' }, { sets: ['前端', '后端', '设计'], size: 1, label: '全能' }, ], transform: [ { type: 'venn', padding: 15, }, ], }, encode: { d: 'path', color: 'key', }, labels: [ { position: 'inside', text: (d) => `${d.label}\n(${d.size}人)`, style: { fontSize: 10, textAlign: 'center', }, transform: [ { type: 'overlapDodgeY', }, ], }, ], style: { fillOpacity: 0.5, }, interactions: [{ type: 'elementHighlight' }], legend: { color: { position: 'bottom', layout: { justifyContent: 'center' }, }, }, }); chart.render(); ``` **说明**: - 在标签中同时显示技能类型和人数 - 使用交互高亮效果增强用户体验 - 适当的透明度设置便于查看重叠区域 ### 不适合的场景 例子 1: **集合数量过多时不适合使用韦恩图** 当集合数量超过 4 个时,韦恩图会变得过于复杂,重叠关系难以清晰表达。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', autoFit: true, }); // 5个集合的韦恩图 - 不推荐 chart.options({ type: 'path', data: { type: 'inline', value: [ { sets: ['A'], size: 10, label: 'A' }, { sets: ['B'], size: 10, label: 'B' }, { sets: ['C'], size: 10, label: 'C' }, { sets: ['D'], size: 10, label: 'D' }, { sets: ['E'], size: 10, label: 'E' }, { sets: ['A', 'B'], size: 3 }, { sets: ['A', 'C'], size: 3 }, { sets: ['A', 'D'], size: 3 }, { sets: ['B', 'C'], size: 3 }, { sets: ['B', 'D'], size: 3 }, { sets: ['C', 'D'], size: 3 }, { sets: ['A', 'B', 'C'], size: 1 }, { sets: ['A', 'B', 'D'], size: 1 }, { sets: ['A', 'C', 'D'], size: 1 }, { sets: ['B', 'C', 'D'], size: 1 }, ], transform: [ { type: 'venn', padding: 10, }, ], }, encode: { d: 'path', color: 'key', }, labels: [ { position: 'inside', text: (d) => d.label || '', }, ], style: { fillOpacity: 0.4, }, legend: false, }); chart.render(); ``` **说明**:上图中 5 个集合产生了过多的重叠区域,视觉上混乱难读。此种情况下,推荐使用[旭日图](/zh/charts/sunburst/)或[桑基图](/zh/charts/sankey/)。 例子 2: **数据差异极大时不适合使用韦恩图** 当集合大小差异极大时,小集合可能无法清晰显示。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', autoFit: true, }); chart.options({ type: 'path', data: { type: 'inline', value: [ { sets: ['大型企业'], size: 10000, label: '大型企业' }, { sets: ['中型企业'], size: 1000, label: '中型企业' }, { sets: ['小型企业'], size: 100, label: '小型企业' }, { sets: ['大型企业', '中型企业'], size: 200 }, { sets: ['中型企业', '小型企业'], size: 50 }, { sets: ['大型企业', '小型企业'], size: 10 }, ], transform: [ { type: 'venn', padding: 10, }, ], }, encode: { d: 'path', color: 'key', }, labels: [ { position: 'inside', text: (d) => d.label || '', }, ], style: { fillOpacity: 0.6, }, legend: false, }); chart.render(); ``` **说明**:上图中小型企业的圆形区域过小,难以清晰展示。此时建议使用[树图](/zh/charts/treemap/)或将数据进行归一化处理。 ## 韦恩图的扩展 ### 空心韦恩图 空心韦恩图通过去除填充色,仅保留边框线条,适合需要突出边界关系的场景。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', autoFit: true, }); chart.options({ type: 'path', data: { type: 'inline', value: [ { sets: ['iOS'], size: 15, label: 'iOS用户' }, { sets: ['Android'], size: 12, label: 'Android用户' }, { sets: ['Web'], size: 10, label: 'Web用户' }, { sets: ['iOS', 'Android'], size: 2, label: '双端用户' }, { sets: ['iOS', 'Web'], size: 2, label: 'iOS+Web' }, { sets: ['Android', 'Web'], size: 1, label: 'Android+Web' }, { sets: ['iOS', 'Android', 'Web'], size: 1 }, ], transform: [ { type: 'venn', padding: 10, }, ], }, encode: { d: 'path', color: 'key', shape: 'hollow', }, labels: [ { position: 'inside', text: (d) => d.label || '', fill: '#000', style: { fontSize: 11, }, }, ], style: { opacity: 0.8, lineWidth: 3, }, tooltip: false, }); chart.render(); ``` **说明**: - 使用 `shape: 'hollow'` 创建空心效果 - 适合黑白打印和需要突出结构的场景 - 标签颜色设置为黑色以确保可读性 ### 自定义样式的韦恩图 通过自定义颜色和样式,创建更具品牌特色的韦恩图。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', autoFit: true, }); chart.options({ type: 'path', data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/lastfm.json', transform: [ { type: 'venn', padding: 12, sets: 'sets', size: 'size', as: ['key', 'path'], }, ], }, encode: { d: 'path', color: 'key', }, labels: [ { position: 'inside', text: (d) => d.label || '', style: { fontSize: 12, fontWeight: 'bold', }, transform: [{ type: 'contrastReverse' }], }, ], style: { opacity: (d) => (d.sets.length > 1 ? 0.4 : 0.7), stroke: '#fff', lineWidth: 2, }, scale: { color: { range: ['#667eea', '#764ba2', '#f093fb'], }, }, state: { inactive: { opacity: 0.1 }, active: { opacity: 0.9 }, }, interactions: [{ type: 'elementHighlight' }], legend: false, }); chart.render(); ``` **说明**: - 使用渐变色彩方案提升视觉效果 - 添加白色边框增强区域分离 - 交互状态配置提升用户体验 ## 韦恩图与其他图表的对比 ### 韦恩图与[旭日图](/zh/charts/sunburst/) - 韦恩图适合展示集合的交集关系,突出重叠部分 - 旭日图适合展示层次结构和分类关系,突出包含关系 ### 韦恩图与[桑基图](/zh/charts/sankey/) - 韦恩图展示静态的集合关系,适合分析现状 - 桑基图展示流动关系,适合分析数据的流向和转化 ### 韦恩图与[树图](/zh/charts/treemap/) - 韦恩图强调集合的重叠关系,适合关系分析 - 树图强调层次结构和占比关系,适合结构分析 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 小提琴图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/violin/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/violin.md - Description: 结合密度分布形状与统计概要比较多组数据,了解小提琴图与箱形图的区别及 G2 示例。 --- title: "小提琴图" description: "结合密度分布形状与统计概要比较多组数据,了解小提琴图与箱形图的区别及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/violin/" version: "5.4.8" --- violin ## 小提琴图的简介 小提琴图(Violin Plot)是一种结合了[箱形图](/zh/charts/boxplot/)和[核密度估计](/zh/manual/core/data/kde/)的数据可视化图表,用于展示数据的分布形状和统计摘要。小提琴图的形状类似小提琴,因此得名。 小提琴图通过密度曲线展示数据在不同数值区间的分布密度,同时叠加箱形图的统计信息(如中位数、四分位数等),能够更直观地反映数据的分布特征,包括多峰性、偏态以及异常值情况。 相比传统的箱形图,小提琴图提供了更丰富的分布信息,特别适合用于比较多个组别的数据分布特征,是探索性数据分析和统计可视化的重要工具。 **英文名**:Violin Plot ## 小提琴图的构成 ### 基础小提琴图 basic-violin | 图表类型 | 小提琴图 | | ---------------- | ----------------------------------------------------------------------------------------------------------------- | | 适合的数据 | 一个分类数据字段、一个连续数据字段 | | 功能 | 展示数据分布的形状、密度和统计摘要,比较不同分组的分布特征 | | 数据与图形的映射 | 分类数据字段映射到横轴位置
连续数据字段通过 KDE 转换为密度分布
统计值映射到箱形图元素
密度映射到图形宽度 | | 适合的数据条数 | 每个分组建议至少有 20-30 个数据点 | 小提琴图的主要组成部分包括: - **密度轮廓**:通过核密度估计(KDE)生成的平滑曲线,展示数据在不同数值处的密度分布 - **箱形图**:叠加在密度轮廓上的传统箱形图,显示中位数、四分位数等统计信息 - **中位线**:标示数据的中位数位置 - **四分位数线**:标示上下四分位数的位置 - **异常值**:超出正常范围的数据点 ```ts 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(); ``` --- ### 核密度图 density | 图表类型 | 核密度图 | | ---------------- | ----------------------------------------------------------------------------------------- | | 适合的数据 | 一个分类数据字段、一个连续数据字段 | | 功能 | 展示数据的概率密度分布,识别数据的分布模式 | | 数据与图形的映射 | 分类数据字段映射到横轴位置
连续数据字段通过 KDE 转换为密度分布
密度值映射到区域大小 | | 适合的数据条数 | 建议至少有 50+ 个数据点 | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); 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, }); chart.render(); ``` --- ### 极坐标小提琴图 polar-violin | 图表类型 | 极坐标小提琴图 | | ---------------- | --------------------------------------------------------------------------------------------- | | 适合的数据 | 循环性或周期性数据:时间序列、角度数据等 | | 功能 | 在极坐标系中展示数据分布,适合周期性或循环性数据的可视化 | | 数据与图形的映射 | 分类数据字段映射到角度
连续数据字段映射到径向距离
密度信息通过 KDE 转换后映射到区域形状 | | 适合的场景 | 时间周期分析、方向性数据分析 | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); 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, }, }, ], }); chart.render(); ``` ## 小提琴图的应用场景 ### 适合的场景 例子 1: **多组数据分布比较** 当需要比较多个组别或类别的数据分布特征时,小提琴图能够同时展示每个组的分布形状、集中趋势和离散程度,便于发现组间差异。 ```ts 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, }, }, ], axis: { x: { title: '分组' }, y: { title: '数值分布' }, }, }); chart.render(); ``` 例子 2: **探索数据分布模式** 用于识别数据的分布特征,如是否为正态分布、是否存在多峰性、偏态程度等,比箱形图提供更丰富的分布信息。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'density', data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/species.json', transform: [ { type: 'kde', field: 'y', groupBy: ['x'], size: 30, }, ], }, encode: { x: 'x', y: 'y', color: 'x', size: 'size', }, style: { fillOpacity: 0.6, stroke: '#FFF', lineWidth: 1, }, axis: { x: { title: '类别' }, y: { title: '密度分布' }, }, tooltip: { title: (d) => `类别: ${d.x}`, items: [ { field: 'y', name: '密度值' }, { field: 'size', name: '概率' }, ], }, }); chart.render(); ``` 例子 3: **异常值检测** 结合密度分布和箱形图的统计信息,可以更准确地识别和理解异常值的存在及其在整体分布中的位置。 ```ts 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/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 }, }, }, ], axis: { x: { title: '实验组' }, y: { title: '光速测量值' }, }, }); chart.render(); ``` ### 不适合的场景 ❌ **数据量过少**:每个分组的数据点少于 20 个时,核密度估计可能不够准确,建议使用箱形图或散点图。 ```ts import { Chart } from '@antv/g2'; // 模拟少量数据 const smallData = [ { group: 'A', value: 12 }, { group: 'A', value: 15 }, { group: 'A', value: 13 }, { group: 'B', value: 18 }, { group: 'B', value: 16 }, { group: 'B', value: 20 }, { group: 'C', value: 25 }, { group: 'C', value: 22 }, { group: 'C', value: 24 }, ]; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'boxplot', data: smallData, encode: { x: 'group', y: 'value', color: 'group', }, style: { boxFill: 'lightblue', boxFillOpacity: 0.6, point: { fill: 'red', size: 4 }, }, axis: { x: { title: '分组' }, y: { title: '数值' }, }, title: '数据量少时建议使用箱形图', }); chart.render(); ``` ❌ **离散型数据**:对于类别型或计数型数据,小提琴图的连续密度分布没有实际意义,建议使用柱状图或条形图。 下面的例子展示了离散数据的正确可视化方式: ```ts import { Chart } from '@antv/g2'; // 模拟离散计数数据 const discreteData = [ { category: '产品A', count: 45 }, { category: '产品B', count: 67 }, { category: '产品C', count: 33 }, { category: '产品D', count: 52 }, { category: '产品E', count: 28 }, ]; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', data: discreteData, encode: { x: 'category', y: 'count', color: 'category', }, style: { fillOpacity: 0.8, }, axis: { x: { title: '产品类别' }, y: { title: '销售数量' }, }, title: '离散数据建议使用柱状图', }); chart.render(); ``` ❌ **单一数据系列**:如果只有一个数据系列且不需要分组比较,直方图或密度图可能更简洁有效。 ❌ **实时数据监控**:小提琴图需要完整的数据集进行密度估计,不适合流式或实时更新的数据场景。 ## 小提琴图与其他图表的对比 ### 小提琴图和[箱形图](/zh/charts/boxplot/) - 小提琴图展示完整的数据分布形状和密度,提供更丰富的分布信息 - 箱形图主要展示统计摘要(五数概括),更简洁但信息有限 - 小提琴图适合探索数据分布的细节特征,箱形图适合快速比较多个分组 ### 小提琴图和[直方图](/zh/charts/histogram/) - 小提琴图通过平滑的密度曲线展示分布,支持多组对比 - 直方图通过分箱统计展示频数分布,适合单一数据集的分布分析 - 小提琴图更适合连续分布的可视化,直方图适合离散化的分布统计 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 面积图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/area/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/area.md - Description: 用填充面积展示数值随时间的变化,了解面积图与折线图的区别、适用场景及 G2 配置示例。 --- title: "面积图" description: "用填充面积展示数值随时间的变化,了解面积图与折线图的区别、适用场景及 G2 配置示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/area/" version: "5.4.8" --- area ## 面积图的简介 面积图是在折线图的基础上,将折线与坐标轴之间的区域进行填充,用于强调数量的变化趋势。面积图可以更好地展示趋势变化中的峰谷,使用填充区域的视觉效果,强调数量随时间变化的程度。 面积图特别适合展示连续的时间序列数据,可以直观地表达数据变化的趋势,同时通过面积的视觉效果,强调总量在某个区间内的变化情况。 当需要同时展示多个序列的数据时,可以使用堆叠面积图或者百分比堆叠面积图,用于比较各个类别的数据在总数中的占比及其随时间的变化。 **英文名**:Area Chart, Area Graph ## 面积图的构成 ### 基础面积图 basic-area | 图表类型 | 基础面积图 | | ---------------- | ---------------------------------------------------------------------------------- | | 适合的数据 | 时间序列数据:一个有序/连续数据字段(通常是时间)、一个连续数据字段 | | 功能 | 展示数据随时间或有序维度的变化趋势 | | 数据与图形的映射 | 时间字段映射到横轴的位置
数值字段映射到纵轴的高度
面积填充强调数据变化的程度 | | 适合的场景 | 单一数据系列随时间的变化趋势 | --- ### 堆叠面积图 stacked-area | 图表类型 | 堆叠面积图 | | ---------------- | ------------------------------------------------------------------------------------------ | | 适合的数据 | 多序列时间数据:一个有序/连续数据字段(通常是时间)、一个连续数据字段、一个分类数据字段 | | 功能 | 展示多个数据系列随时间变化的趋势及其叠加总量 | | 数据与图形的映射 | 时间字段映射到横轴的位置
数值字段映射到纵轴的高度
分类字段映射到不同的颜色和堆叠区域 | | 适合的场景 | 多数据系列及其总量随时间的变化趋势对比 | ## 面积图的应用场景 ### 适合的场景 例子 1: **适合展示连续时间序列的趋势** 下图是一个股票价格走势的面积图,展示了某公司股票价格随时间的变化趋势。 | date(日期) | close(收盘价) | | ------------ | --------------- | | 2015/1/5 | 121.73 | | 2015/1/6 | 115.07 | | 2015/1/7 | 116.75 | | ... | ... | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'view', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/aapl.json', transform: [ { type: 'map', callback: (d) => ({ ...d, date: new Date(d.date), }), }, ], }, encode: { x: 'date', y: 'close' }, axis: { x: { title: null, }, y: { title: null, }, }, children: [ { type: 'area', style: { fill: 'l(270) 0:#ffffff 0.5:#7ec2f3 1:#1890ff', fillOpacity: 0.6, }, }, { type: 'line', style: { lineWidth: 2, }, }, ], }); chart.render(); ``` **说明** : - `date` 字段,映射到横轴的位置,表示时间的先后顺序 - `close` 字段,映射到纵轴的高度和填充面积,展示了价格随时间的变化趋势 - 同时使用面积填充和线条,增强了视觉效果,方便观察变化趋势 例子 2: **适合展示多个数据系列的堆叠趋势** 堆叠面积图可以同时展示多个数据系列随时间的变化,以及它们的总和趋势。下图展示了不同行业失业人数的变化趋势。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'area', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/unemployment-by-industry.json', }, encode: { x: (d) => new Date(d.date), y: 'unemployed', color: 'industry', }, transform: [ { type: 'stackY', }, ], axis: { x: { title: null, }, y: { title: null, }, }, }); chart.render(); ``` **说明**: - `date` 字段映射到横轴,表示时间维度 - `unemployed` 字段映射到纵轴,表示失业人数 - `industry` 字段映射到颜色,区分不同行业 - 使用 `stackY` 转换将各系列数据堆叠显示 例子 3: **百分比堆叠面积图展示占比变化** 当需要展示各类别在总体中的占比随时间的变化时,百分比堆叠面积图是非常合适的选择。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'area', autoFit: true, data: [ { country: 'Asia', year: '1750', value: 502 }, { country: 'Asia', year: '1800', value: 635 }, { country: 'Asia', year: '1850', value: 809 }, { country: 'Asia', year: '1900', value: 947 }, { country: 'Asia', year: '1950', value: 1402 }, { country: 'Asia', year: '1999', value: 3634 }, { country: 'Asia', year: '2050', value: 5268 }, { country: 'Africa', year: '1750', value: 106 }, { country: 'Africa', year: '1800', value: 107 }, { country: 'Africa', year: '1850', value: 111 }, { country: 'Africa', year: '1900', value: 133 }, { country: 'Africa', year: '1950', value: 221 }, { country: 'Africa', year: '1999', value: 767 }, { country: 'Africa', year: '2050', value: 1766 }, { country: 'Europe', year: '1750', value: 163 }, { country: 'Europe', year: '1800', value: 203 }, { country: 'Europe', year: '1850', value: 276 }, { country: 'Europe', year: '1900', value: 408 }, { country: 'Europe', year: '1950', value: 547 }, { country: 'Europe', year: '1999', value: 729 }, { country: 'Europe', year: '2050', value: 628 }, ], encode: { x: 'year', y: 'value', color: 'country' }, transform: [{ type: 'stackY' }, { type: 'normalizeY' }], axis: { x: { title: null, }, y: { title: null, labelFormatter: '.0%', }, }, }); chart.render(); ``` **说明**: - 组合使用 `stackY` 和 `normalizeY` 转换,将堆叠数据标准化为百分比 - 纵轴格式化为百分比显示,更直观地表示各区域的占比变化 ### 不适合的场景 例子 1: **不适合比较精确的单一数值** 面积图通过填充区域的方式展示数据变化,不适合用于需要精确比较单一数值的场景。如果主要目的是比较不同类别的具体数值,柱状图或条形图会是更好的选择。 例子 2: **数据波动剧烈时可能造成视觉混乱** 当多个数据系列波动剧烈且交叉频繁时,使用堆叠面积图可能会导致视觉混乱,难以辨别各个系列的具体变化趋势。这种情况下,可以考虑使用多条折线图或小型多图表示。 ## 面积图的扩展 ### 区间面积图 区间面积图可以表示数据的上下限范围,通常用于表示数据的不确定性或波动范围。 ```ts /** * A recreation of this demo: https://www.anychart.com/zh/products/anychart/gallery/Combined_Charts/Range_Spline-Area,_Spline_and_Marker_Chart.php */ import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', autoFit: true, }); chart.options({ type: 'view', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/range-spline-area.json', transform: [ { type: 'map', callback: ([x, low, high, v2, v3]) => ({ x, low, high, v2, v3, }), }, ], }, scale: { x: { type: 'linear', tickCount: 10 } }, axis: { y: { title: false } }, children: [ { type: 'area', encode: { x: 'x', y: ['low', 'high'], shape: 'smooth' }, style: { fillOpacity: 0.65, fill: '#64b5f6', lineWidth: 1 }, }, { type: 'point', encode: { x: 'x', y: 'v2', size: 2, shape: 'point' }, tooltip: { items: ['v2'] }, }, { type: 'line', encode: { x: 'x', y: 'v3', color: '#FF6B3B', shape: 'smooth' }, }, ], }); chart.render(); ``` **说明**: - 使用 `'y', ['low', 'high']` 指定区间的上下界 - 数据区间使用透明度较低的填充色,突出显示不确定性范围 ### 差分面积图 差分面积图用于比较两个数据系列,突出它们之间的差异区域。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'area', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/temperature-compare.json', transform: [ { type: 'fold', fields: ['New York', 'San Francisco'], key: 'city', value: 'temperature', }, ], }, transform: [{ type: 'diffY' }], encode: { x: (d) => new Date(d.date), y: 'temperature', color: 'city', }, scale: { color: { range: ['#67a9cf', '#ef8a62'], }, }, axis: { y: { title: null }, x: { title: null }, }, }); chart.render(); ``` **说明**: - 使用 `transform: [{ type: 'diffY' }]` 实现差分效果 - 颜色编码区分了两个数据系列,并突出了它们的差异区域 ## 面积图与其他图表的对比 ### 面积图和[折线图](/zh/charts/line/) - 面积图和折线图都适合展示连续数据的变化趋势 - 面积图通过填充区域强调数量的视觉效果,更适合展示总量变化 - 折线图更适合展示准确的变化轨迹和多条数据线的比较 ### 面积图和[柱状图](/zh/charts/bar/)、[饼图](/zh/charts/pie/) - 面积图侧重展示随时间变化的趋势和总量,强调变化过程 - 柱状图侧重于不同分类之间数值大小的比较,强调对比关系 - 饼图侧重展示在特定时间点上的占比关系,而非随时间的变化 ### 面积图与[堆叠面积图](/zh/charts/stacked-area/) - 基础面积图适合单一数据系列的趋势展示 - 堆叠面积图适合多数据系列及其总和的展示 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 箱形图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/boxplot/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/boxplot.md - Description: 通过中位数、四分位数和异常值概括数据分布,了解箱形图的统计含义、适用场景及 G2 示例。 --- title: "箱形图" description: "通过中位数、四分位数和异常值概括数据分布,了解箱形图的统计含义、适用场景及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/boxplot/" version: "5.4.8" --- boxplot ## 箱形图的简介 箱形图(Box Plot),又称盒须图、盒式图或箱线图,是一种用于展示一组数据分布情况的统计图表。它能够显示数据的五个重要统计量:最小值、下四分位数(Q1)、中位数(Q2)、上四分位数(Q3)和最大值,同时可以清楚地识别出数据中的异常值。 箱形图的设计简洁明了,通过箱体和须线的组合,可以快速了解数据的集中趋势、离散程度和偏态分布,是统计分析和数据探索中的重要工具。 **英文名**:Box Plot, Box-and-Whisker Plot **其他名称**:盒须图、盒式图、箱线图 ## 箱形图的构成 ### 基础箱形图 boxplot-anatomy | 图表类型 | 箱形图 | | ---------------- | -------------------------------------------------------------------------------------------- | | 适合的数据 | 一个分类数据字段、一个连续数据字段 | | 功能 | 展示数据分布情况,识别异常值,比较不同分组的数据分布 | | 数据与图形的映射 | 分类数据字段映射到横轴位置
连续数据字段自动计算统计值映射到箱体各部分
异常值显示为散点 | | 适合的数据条数 | 每个分组建议至少有 5-10 个数据点 | 箱形图的主要组成部分包括: - **箱体**:从下四分位数(Q1)到上四分位数(Q3)的矩形区域,包含了中间 50%的数据 - **中位线**:箱体内部的水平线,表示数据的中位数(Q2) - **须线**:从箱体延伸出的线段,通常延伸到 1.5 倍四分位距(IQR)范围内的最远数据点 - **异常值**:超出须线范围的数据点,以单独的点标记显示 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'boxplot', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/morley.json', }, encode: { x: 'Expt', y: 'Speed', }, style: { boxFill: '#1890ff', boxFillOpacity: 0.3, pointStroke: '#f5222d', pointR: 3, }, }); chart.render(); ``` --- ## 箱形图的应用场景 ### 适合的场景 **场景 1:数据分布分析** 箱形图是分析数据分布的理想工具,能够快速识别数据的集中趋势、离散程度和偏态分布。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'boxplot', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/penguins.json', }, encode: { x: 'species', y: 'flipper_length_mm', color: 'species', }, axis: { y: { title: '鳍长 (mm)', }, x: { title: '企鹅种类', }, }, }); chart.render(); ``` **场景 2:异常值检测** 箱形图能够直观地显示数据中的异常值,帮助识别需要进一步调查的数据点。 **场景 3:多组数据比较** 通过并排显示多个箱形图,可以有效地比较不同组别间的数据分布差异。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'boxplot', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/penguins.json', }, encode: { x: 'species', y: 'flipper_length_mm', color: 'sex', series: 'sex', }, axis: { y: { title: '鳍长 (mm)', }, x: { title: '企鹅种类', }, }, }); chart.render(); ``` ### 不适合的场景 **场景 1:数据量过少** 当每个分组的数据点少于 5 个时,箱形图的统计意义不大,建议使用散点图或条形图。 **场景 2:展示精确数值** 箱形图侧重于显示数据分布的整体特征,不适合需要精确数值的场景,此时应使用表格或条形图。 **场景 3:时间序列分析** 对于时间序列数据的趋势分析,[折线图](/zh/charts/line/)或[面积图](/zh/charts/area/)更为合适。 ## 箱形图的扩展 ### 分组箱形图 通过设置不同的颜色和系列,可以在同一图表中比较多个维度的数据分布。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'boxplot', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/penguins.json', }, encode: { x: 'species', y: 'body_mass_g', color: 'sex', series: 'sex', }, axis: { y: { title: '体重 (g)', }, x: { title: '企鹅种类', }, }, }); chart.render(); ``` ### 横向箱形图 当分类标签过长时,可以使用横向箱形图来提高可读性。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'boxplot', coordinate: { transform: [{ type: 'transpose' }] }, autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/morley.json', transform: [{ type: 'filter', callback: (d) => d.Expt <= 3 }], }, encode: { x: 'Expt', y: 'Speed', color: 'Expt', }, axis: { x: { title: '光速测量值', }, y: { title: '实验编号', }, }, }); chart.render(); ``` ### 无异常点箱形图 在某些场景下,可以隐藏异常点,只关注数据的整体分布。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'boxplot', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/morley.json', }, encode: { x: 'Expt', y: 'Speed', }, style: { point: false, boxFill: '#52c41a', boxFillOpacity: 0.4, }, axis: { y: { title: '光速测量值', }, x: { title: '实验编号', }, }, }); chart.render(); ``` ## 箱形图与其他图表的对比 ### 箱形图和[直方图](/zh/charts/histogram/) - 箱形图主要展示数据的统计摘要(五数概括),适合快速比较多个分组 - 直方图展示数据的具体分布形状,适合单个变量的分布分析 ### 箱形图和[柱状图](/zh/charts/bar/) - 箱形图适合展示数据的分布情况和统计特征 - 柱状图主要用于比较不同分类的数值大小 ### 箱形图和[散点图](/zh/charts/scatter-plot/) - 箱形图适合展示分组数据的分布概览 - 散点图适合展示具体数据点的分布和相关关系 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 带气泡的地图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/bubble-map/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/bubble-map.md - Description: 在地图上用气泡位置与大小展示地理分布和数值差异,了解带气泡地图的适用场景及 G2 示例。 --- title: "带气泡的地图" description: "在地图上用气泡位置与大小展示地理分布和数值差异,了解带气泡地图的适用场景及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/bubble-map/" version: "5.4.8" --- bubble-map > 注意:G2 5.0 目前不直接支持完整的地图背景功能,本页面主要提供概念介绍。在实际应用中,需要额外引入地图组件和数据。 ## 带气泡的地图的简介 带气泡的地图(Bubble Map)是一种在地理地图上使用气泡(圆圈)来表示不同地理位置数据大小的可视化图表。气泡的大小通常表示数值的大小,位置表示地理坐标,颜色可以用来区分不同的分类或表示另一个数值维度。 带气泡的地图比[分级统计图](/zh/charts/choropleth-map/)更适用于比较带地理信息的数据的大小。它的主要缺点是当地图上的气泡过多过大时,气泡间会相互遮盖而影响数据展示,所以在绘制时需要考虑这点。 **英文名**:Bubble Map **其他名称**:气泡图地图、地理气泡图 ## 带气泡的地图的构成 ### 基础带气泡的地图 bubble-map-anatomy | 图表类型 | 带气泡的地图 | | ---------------- | ------------------------------------------------------------------------------------------------------ | | 适合的数据 | 包含地理坐标(经纬度)的数据和一个或多个数值字段 | | 功能 | 在地理背景上展示数据的地理分布和数值大小关系 | | 数据与图形的映射 | 经纬度字段映射到地图位置
数值字段映射到气泡大小
分类字段可映射到颜色
其他数值可映射到颜色深度 | | 适合的数据条数 | 建议不超过 100 个数据点,避免气泡重叠影响阅读 | 带气泡的地图的主要组成部分包括: - **地理背景**:提供地理坐标系统的地图背景,如世界地图、国家地图等 - **气泡**:圆形标记,位置表示地理坐标,大小表示数值大小 - **颜色编码**:用不同颜色区分分类或表示数值范围 - **图例**:说明气泡大小和颜色的含义 --- ## 带气泡的地图的应用场景 ### 适合的场景 **场景 1:地理数据分布分析** 带气泡的地图是分析具有地理属性数据的理想工具,能够直观展示数据在地理空间上的分布规律。 geographic-distribution **场景 2:区域对比分析** 通过气泡大小和颜色的对比,可以有效地比较不同地区间的数据差异。 **场景 3:多维地理数据展示** 带气泡的地图可以同时展示位置、数值大小、分类等多个维度的信息。 ### 不适合的场景 **场景 1:数据点过多导致重叠** 当地图上的数据点过多时,气泡间会相互遮盖,影响数据的清晰展示,此时应考虑使用热力图或点密度图。 **场景 2:缺乏地理坐标信息** 对于没有经纬度信息的数据,需要先进行地理编码转换,或考虑使用其他图表类型。 **场景 3:精确数值展示** 带气泡的地图侧重于显示数据的地理分布趋势,不适合需要精确数值的场景,此时应使用表格或其他精确展示方式。 ## 带气泡的地图的扩展 ### 多层带气泡的地图 通过设置不同的层级和透明度,可以在同一地图上展示多个数据系列。 multi-layer ### 带地图底图的气泡图 结合地理边界数据,提供更丰富的地理背景信息。 ### 时间序列带气泡的地图 通过动画展示气泡随时间的变化。 ## 带气泡的地图与其他图表的对比 ### 带气泡的地图和[散点图](/zh/charts/scatter-plot/) - 带气泡的地图使用地理坐标系统,适合展示地理空间数据 - 散点图使用笛卡尔坐标系,适合展示两个连续变量的相关关系 ### 带气泡的地图和[分级统计图](/zh/charts/choropleth-map/) - 带气泡的地图使用气泡大小表示数值,可以精确比较不同地区的数值大小 - 分级统计图使用颜色深浅表示数值范围,适合展示数据的整体分布模式 ### 带气泡的地图和[点图](/zh/charts/scatter-plot/) - 带气泡的地图结合了地理信息,位置具有地理意义 - 普通点图位置仅表示数据维度,不具备地理属性 ## 在 G2 5.0 中实现带气泡的地图 在 G2 5.0 中,我们可以通过在 `geoView` 上绘制 `point` 来实现带气泡的地图。以下是两个实际可用的示例: ### 示例 1:伦敦人口分布气泡图 基于伦敦地图数据,展示各区域的人口分布情况: ```ts import { Chart } from '@antv/g2'; import { feature } from 'topojson-client'; Promise.all([ fetch('https://assets.antv.antgroup.com/g2/londonBoroughs.json').then((res) => res.json(), ), fetch('https://assets.antv.antgroup.com/g2/londonCentroids.json').then( (res) => res.json(), ), ]).then((values) => { const [londonBoroughs, londonCentroids] = values; const london = feature( londonBoroughs, londonBoroughs.objects.boroughs, ).features; // 为中心点数据添加模拟的人口和GDP数据 const bubbleData = londonCentroids.map((d, index) => ({ ...d, name: d.name || `区域${index + 1}`, // 确保每个数据点都有名称 population: Math.floor(Math.random() * 500000) + 100000, // 10万-60万人口 gdp: Math.floor(Math.random() * 50000) + 20000, // 2万-7万GDP category: ['商业区', '住宅区', '工业区', '混合区'][ Math.floor(Math.random() * 4) ], })); const chart = new Chart({ container: 'container', autoFit: true, }); chart.options({ type: 'geoView', children: [ { type: 'geoPath', data: london, style: { fill: 'lightgray', stroke: 'white', lineWidth: 1, }, }, { type: 'point', data: bubbleData, encode: { x: 'cx', y: 'cy', size: 'population', color: 'category', shape: 'point', }, style: { opacity: 0.7, stroke: 'white', lineWidth: 1, }, scale: { size: { range: [4, 30], }, color: { range: ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728'], }, }, tooltip: { title: 'name', items: [ { name: '人口', channel: 'size', valueFormatter: (value) => `${value ? value.toLocaleString() : 'N/A'}人`, }, { name: 'GDP', field: 'gdp', valueFormatter: (value) => `${value ? value.toLocaleString() : 'N/A'}万元`, }, { name: '类型', field: 'category' }, ], }, }, ], }); chart.render(); }); ``` ### 示例 2:全球主要城市 GDP 气泡图 展示世界主要城市的 GDP 分布: ```ts import { Chart } from '@antv/g2'; // 全球主要城市数据 - 确保数据完整性 const worldCities = [ { name: '北京', lng: 116.4074, lat: 39.9042, gdp: 4027, population: 2154, region: '亚洲', }, { name: '上海', lng: 121.4737, lat: 31.2304, gdp: 4321, population: 2424, region: '亚洲', }, { name: '纽约', lng: -74.0059, lat: 40.7128, gdp: 1700, population: 851, region: '北美', }, { name: '洛杉矶', lng: -118.2437, lat: 34.0522, gdp: 710, population: 397, region: '北美', }, { name: '伦敦', lng: -0.1276, lat: 51.5074, gdp: 653, population: 898, region: '欧洲', }, { name: '东京', lng: 139.6917, lat: 35.6895, gdp: 1617, population: 1396, region: '亚洲', }, { name: '巴黎', lng: 2.3522, lat: 48.8566, gdp: 709, population: 1068, region: '欧洲', }, { name: '柏林', lng: 13.405, lat: 52.52, gdp: 147, population: 367, region: '欧洲', }, { name: '悉尼', lng: 151.2093, lat: -33.8688, gdp: 337, population: 518, region: '大洋洲', }, { name: '多伦多', lng: -79.3832, lat: 43.6532, gdp: 324, population: 294, region: '北美', }, { name: '首尔', lng: 126.978, lat: 37.5665, gdp: 779, population: 969, region: '亚洲', }, { name: '新加坡', lng: 103.8198, lat: 1.3521, gdp: 340, population: 584, region: '亚洲', }, { name: '芝加哥', lng: -87.6298, lat: 41.8781, gdp: 689, population: 271, region: '北美', }, { name: '法兰克福', lng: 8.6821, lat: 50.1109, gdp: 731, population: 75, region: '欧洲', }, { name: '香港', lng: 114.1694, lat: 22.3193, gdp: 365, population: 745, region: '亚洲', }, { name: '孟买', lng: 72.8777, lat: 19.076, gdp: 310, population: 1284, region: '亚洲', }, { name: '圣保罗', lng: -46.6333, lat: -23.5505, gdp: 430, population: 1252, region: '南美', }, { name: '墨西哥城', lng: -99.1332, lat: 19.4326, gdp: 411, population: 912, region: '北美', }, ].map((city) => ({ ...city, // 数据验证和格式化 name: city.name || '未知城市', lng: typeof city.lng === 'number' ? city.lng : 0, lat: typeof city.lat === 'number' ? city.lat : 0, gdp: typeof city.gdp === 'number' ? city.gdp : 0, population: typeof city.population === 'number' ? city.population : 0, region: city.region || '未知地区', })); // 简化的全球地图轮廓数据 const worldOutline = [ { type: 'Feature', geometry: { type: 'Polygon', coordinates: [ [ [-180, -60], [180, -60], [180, 75], [-180, 75], [-180, -60], ], ], }, }, ]; const chart = new Chart({ container: 'container', autoFit: true, }); chart.options({ type: 'geoView', children: [ { type: 'geoPath', data: worldOutline, style: { fill: '#f0f0f0', stroke: '#d0d0d0', lineWidth: 1, }, }, { type: 'point', data: worldCities, encode: { x: 'lng', y: 'lat', size: 'gdp', color: 'region', shape: 'point', }, style: { opacity: 0.8, stroke: 'white', lineWidth: 2, }, scale: { size: { range: [8, 40], }, color: { range: [ '#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', ], }, }, tooltip: { title: 'name', items: [ { name: 'GDP', channel: 'size', valueFormatter: (value) => `${value || 0}亿美元`, }, { name: '人口', field: 'population', valueFormatter: (value) => `${value || 0}万人`, }, { name: '地区', field: 'region' }, ], }, }, ], }); chart.render(); ``` ### 核心要点 使用 G2 5.0 创建带气泡的地图的关键要素: 1. **使用 geoView**: 通过 `type: 'geoView'` 创建地理坐标系统的视图 2. **point 标记**: 在 children 中使用 `type: 'point'` 来绘制气泡 3. **编码映射**: - 在 `encode` 对象中映射 `x`, `y` 到经纬度坐标 - `size` 映射到数值大小 - `color` 映射到分类或数值 4. **样式设置**: 通过 `style` 对象设置透明度、描边等 5. **比例尺配置**: 通过 `scale` 对象控制气泡大小和颜色范围 6. **交互功能**: 添加 `tooltip` 和 `labels` 提升用户体验 7. **多层结构**: 使用 `children` 数组可以同时绘制地图背景和气泡层 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() ## 注意事项 ### 关于地图背景显示 在实际应用中,要正确显示地图背景,需要: 1. 引入地图投影组件 2. 加载完整的 GeoJSON 地图数据 3. 使用正确的 transform 配置 #### 雷达图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/radar-chart/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/radar-chart.md - Description: 将多个指标排列在放射状坐标轴上,对比对象的指标轮廓,了解雷达图的适用场景及 G2 示例。 --- title: "雷达图" description: "将多个指标排列在放射状坐标轴上,对比对象的指标轮廓,了解雷达图的适用场景及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/radar-chart/" version: "5.4.8" --- radar ## 雷达图的简介 雷达又叫戴布拉图、蜘蛛网图。传统的雷达图被认为是一种表现多维(4 维以上)数据的图表。它将多个维度的数据量映射到坐标轴上,这些坐标轴起始于同一个圆心点,通常结束于圆周边缘,将同一组的点使用线连接起来就称为了雷达图。它可以将多维数据进行展示,但是点的相对位置和坐标轴之间的夹角是没有任何信息量的。在坐标轴设置恰当的情况下雷达图所围面积能表现出一些信息量。 每一个维度的数据都分别对应一个坐标轴,这些坐标轴具有相同的圆心,以相同的间距沿着径向排列,并且各个坐标轴的刻度相同。连接各个坐标轴的网格线通常只作为辅助元素。将各个坐标轴上的数据点用线连接起来就形成了一个多边形。坐标轴、点、线、多边形共同组成了雷达图。 着重要强调的是,虽然雷达图每个轴线都表示不同维度,但使用上为了容易理解和统一比较。使用雷达图经常会人为的将将多个坐标轴都统一成一个度量,比如:统一成分数、百分比等。这样这个图就退化成一个二维图了,事实上这种雷达图在日常生活更常见、更长用。 另外,雷达图还可以展示出数据集中各个变量的权重高低情况,非常适用于展示性能数据。 雷达图的主要缺点是: (1) 如果雷达图上多边形过多会使可读性下降,使整体图形过于混乱。特别是有颜色填充的多边形的情况,上层会遮挡覆盖下层多边形。 (2) 如果变量过多,也会造成可读性下降,因为一个变量对应一个坐标轴,这样会使坐标轴过于密集,使图表给人感觉很复杂。所以最佳实践就是尽可能控制变量的数量使雷达图保持简单清晰。 注意: (1) 为了使概念尽可能简单,且贴近现今的使用习惯,下面我们暂时只介绍退化为二维的雷达图。 (2) 雷达图更注重于同类图表之间的对比(即雷达图与雷达图之间的对比) **英文名**:Radar Chart, Spider Chart, Web Chart, Polar Chart, Star Plots ## 雷达图的构成 ### 单组雷达图 radar | 图表类型 | 单组雷达图 | | ---------------- | ------------------------------------------------------------- | | 适合的数据 | 一个分类字段,一个连续字段 | | 功能 | 对比分类数据的数值大小 | | 数据与图形的映射 | 分类字段映射到极坐标下角度标定的位置 连续字段映射到半径长度。 | | 适合的数据条数 | 不超过 30 条 | --- ### 多组雷达图 radar | 图表类型 | 多组雷达图 | | ---------------- | ------------------------------------------------------------------------------------------ | | 适合的数据 | 一个连续字段,两个个分类字段 | | 功能 | 对比不同分类数据的数值大小 | | 数据与图形的映射 | 一个分类字段映射到极坐标下角度标定的位置 另一个分类字段映射到颜色 连续字段映射到半径长度。 | | 适合的数据条数 | 不超过 30 条 | --- ## 雷达图的应用场景 ### 适合的场景 例子 1: **适合应用到多维数据对比的场景** 下图是一个个人综合能力评估雷达图。 | ability | score(评分,满分 10 分) | | -------- | ------------------------- | | 语言表达 | 8.8 | | 逻辑思维 | 9.0 | | 亲和力 | 7.2 | | 运动 | 4.5 | | 学习 | 8.3 | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'area', coordinate: { type: 'polar', }, autoFit: true, data: [ { ability: '语言表达', score: 8.8 }, { ability: '逻辑思维', score: 9.0 }, { ability: '亲和力', score: 7.2 }, { ability: '运动', score: 4.5 }, { ability: '学习', score: 8.3 }, ], encode: { x: 'ability', y: 'score' }, scale: { x: { padding: 0.5, align: 0 }, y: { domainMin: 0, domainMax: 10, tickCount: 5, label: false, }, }, style: { fillOpacity: 0.5, lineWidth: 2, }, axis: { x: { grid: true }, y: { tick: false, grid: true, title: false, zIndex: 1 }, }, interaction: { tooltip: { crosshairsLineDash: [4, 4] }, }, }); chart.render(); ``` **说明** : - `ability` 字段,映射到极坐标下角度标定的位置,用于区分不同的能力类型 - `score` 字段,映射到半径长度,表示评分高低 例子 2: **适合应用到多个维度组成的能力衡量的场景** 如下面展示了华为 Mate 和 中兴 Grand Memo 两款手机的综合表现雷达图,分别从易用性、功能、拍照、跑分、续航这五个维度进行考核,可以看出两款手机在这个维度方面的性能都比较平衡,同时也可逐项对比。`虚构数据` | performance(综合表现) | type(手机类型) | score(评分,满分 100 分) | | ----------------------- | ---------------- | -------------------------- | | 易用性 | 华为 Mate | 80 | | 功能 | 华为 Mate | 90 | | 拍照 | 华为 Mate | 80 | | 跑分 | 华为 Mate | 70 | | 续航 | 华为 Mate | 90 | | 易用性 | 中兴 Grand Memo | 70 | | 功能 | 中兴 Grand Memo | 82 | | 拍照 | 中兴 Grand Memo | 81 | | 跑分 | 中兴 Grand Memo | 82 | | 续航 | 中兴 Grand Memo | 78 | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'line', coordinate: { type: 'polar', }, autoFit: true, data: [ { performance: '易用性', type: '华为 Mate', score: 80 }, { performance: '功能', type: '华为 Mate', score: 90 }, { performance: '拍照', type: '华为 Mate', score: 80 }, { performance: '跑分', type: '华为 Mate', score: 70 }, { performance: '续航', type: '华为 Mate', score: 90 }, { performance: '易用性', type: '中兴 Grand Memo', score: 70 }, { performance: '功能', type: '中兴 Grand Memo', score: 82 }, { performance: '拍照', type: '中兴 Grand Memo', score: 81 }, { performance: '跑分', type: '中兴 Grand Memo', score: 82 }, { performance: '续航', type: '中兴 Grand Memo', score: 78 }, ], encode: { x: 'performance', y: 'score', color: 'type' }, scale: { x: { padding: 0.5, align: 0 }, y: { domainMin: 0, domainMax: 100, tickCount: 5, label: false, }, }, style: { fillOpacity: 0.5, lineWidth: 2, }, axis: { x: { grid: true, tick: false, gridLineDash: [0, 0] }, y: { tick: false, grid: true, title: false, zIndex: 1, gridLineDash: [0, 0], gridAreaFill: (dataum, index, data) => { return index % 2 === 1 ? 'rgba(0, 0, 0, 0.04)' : ''; }, }, }, interaction: { tooltip: { crosshairsLineDash: [4, 4] }, }, }); chart.render(); ``` **说明** : - `performance` 字段,映射到极坐标下角度标定的位置,用于区分综合表现类型 - `type` 字段,手机类型,用于颜色区分 - `score` 字段,映射到半径长度,表示评分高低 ## 雷达图与其他图表的对比 ### 雷达图和[饼图](/zh/charts/pie/) - 雷达图主要用于多个维度间的数据(大小、数值)的对比 - 饼图主要是展示分类之间的占比情况 ### 雷达图和玉珏图 - 玉珏图通过各分类的分布角度、长度以及颜色的密度来对比数据,适用于展示各部分占整体的比例。 - 雷达图通过多边形的边缘或弧线到极坐标中的半径长度来对比数据,适合比较不同类别在多个维度上的表现。 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 双向柱状图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/bi-directional-bar/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/bi-directional-bar.md - Description: 将两组数据沿相反方向排列,比较不同群体或正负数值,了解双向柱状图的使用场景和 G2 示例。 --- title: "双向柱状图" description: "将两组数据沿相反方向排列,比较不同群体或正负数值,了解双向柱状图的使用场景和 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/bi-directional-bar/" version: "5.4.8" --- radar ## 双向柱状图的简介 双向柱状图(又名正负条形图),使用正向和反向的柱子显示类别之间的数值比较。其中分类轴表示需要对比的分类维度,连续轴代表相应的数值,分为两种情况,一种是正向刻度值与反向刻度值完全对称,另一种是正向刻度值与反向刻度值反向对称,即互为相反数。 **英文名**:Bi-Directional Bar,Bi-Directional Column ## 双向柱状图的构成 radar | 图表类型 | 双向柱状图 | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 适合的数据 | 列表:一个分类数据字段、两个连续数据字段 | | 功能 | 对比分类数据的数值大小 | | 数据与图形的映射 | 分类数据字段映射到分类轴的位置;一个连续数据字段映射到正向矩形的长度;另一个连续数据字段映射到反向矩形的长度;正反向数据可以设置颜色或形状(shape)增强区分度;分类数据可以设置颜色增强分类的区分度 | | 适合的数据条数 | 横向不超过 30 条数据、纵向不超过 12 条数据 | --- ## 双向柱状图的应用场景 ### 适合的场景 例子 1: **正反分类数据对比** 下图是模拟某个公司各个部门对某项任务的完成情况数据的对比图,完成人数使用正向柱状图表示、未完成人数使用反向柱状图表示。 | 部门 | 小组 | 完成人数 | 未完成人数 | | ------ | ------ | -------- | ---------- | | 部门 0 | 组名 0 | 37 | 9 | | ... | ... | ... | ... | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); const data = [ { department: '部门 0', group: '组名0', people: 37, type: 'completed' }, { department: '部门 0', group: '组名0', people: 9, type: 'uncompleted' }, { department: '部门 0', group: '组名1', people: 27, type: 'completed' }, { department: '部门 0', group: '组名1', people: 10, type: 'uncompleted' }, { department: '部门 1', group: '组名2', people: 37, type: 'completed' }, { department: '部门 1', group: '组名2', people: 19, type: 'uncompleted' }, { department: '部门 1', group: '组名3', people: 37, type: 'completed' }, { department: '部门 1', group: '组名3', people: 29, type: 'uncompleted' }, { department: '部门 2', group: '组名4', people: 20, type: 'completed' }, { department: '部门 2', group: '组名4', people: 2, type: 'uncompleted' }, { department: '部门 2', group: '组名5', people: 40, type: 'completed' }, { department: '部门 2', group: '组名5', people: 10, type: 'uncompleted' }, { department: '部门 3', group: '组名6', people: 25, type: 'completed' }, { department: '部门 3', group: '组名6', people: 3, type: 'uncompleted' }, { department: '部门 3', group: '组名7', people: 55, type: 'completed' }, { department: '部门 3', group: '组名7', people: 8, type: 'uncompleted' }, ]; const range = ['#7593ed', '#95e3b0', '#6c7893', '#e7c450', '#7460eb']; chart.options({ type: 'interval', coordinate: { transform: [{ type: 'transpose' }] }, autoFit: true, data: data, encode: { x: 'group', y: (d) => (d.type === 'completed' ? d.people : -d.people), color: 'department', }, scale: { x: { padding: 0.5 }, color: { type: 'ordinal', range, }, }, axis: { x: { title: '' }, y: { labelFormatter: (d) => { return Math.abs(d); }, }, }, style: { fill: ({ type }, i, data) => { if (type === 'uncompleted') { return 'transparent'; } }, stroke: (d, i, data, ...re) => { const { type } = d; if (type === 'uncompleted') { return range[i / 2]; } }, lineWidth: 2, }, tooltip: { title: (d) => { return `${d.department}-${d.group}`; }, items: [ (d, i, data, column) => ({ name: d.type === 'completed' ? '完成' : '未完成', value: d.people, }), ], }, }); chart.render(); ``` **说明** : - 使用了`颜色`用于区分不同的部门 - 使用了`形状`(空心和实心)用于区分完成人数和未完成人数 - 使用了`位置`用于区分不同小组 ### 不适合的场景 双向柱状图不适合不含相反含义的数据 ## 双向柱状图的扩展 例子 1: **层叠双向柱状图** 下图展示了不同类型的人群对某个问题的同意程度的统计数据,分为非常不同意、不同意、没意见、同意和非常同意五种程度,其中非常不同意、不同意和没意见作为反向柱状图,不同程度用不同颜色来表示。`虚构数据` | type | Strongly agree | Agree | Neither agree nor disagree | Disagree | Strongly disagree | | ---------- | -------------- | ----- | -------------------------- | -------- | ----------------- | | Question 1 | 11.7 | 59.9 | 18.5 | 9.1 | 0.7 | | ... | ... | ... | ... | ... | ... | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); const types = [ 'Strongly disagree', 'Disagree', 'Neither agree nor disagree', 'Agree', 'Strongly agree', ]; const colors = ['#c30d24', '#f3a583', '#cccccc', '#94c6da', '#1770ab']; chart.options({ type: 'interval', coordinate: { transform: [{ type: 'transpose' }] }, autoFit: true, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/82c97016-0f99-433b-ab21-9ecf14244610.csv', }, transform: [{ type: 'stackY' }], encode: { x: 'question', y: (d) => d.type === 'Disagree' || d.type === 'Strongly disagree' ? -d.percentage : d.type === 'Neither agree nor disagree' ? -d.percentage / 2 : +d.percentage, color: 'type', }, scale: { x: { padding: 0.5 }, color: { domain: types, range: colors }, }, axis: { x: { title: '' }, y: { labelFormatter: (d) => { return Math.abs(d); }, }, }, tooltip: { items: [ (d, i, data, column) => ({ name: d.type, value: d.percentage, }), ], }, }); chart.render(); ``` **说明** : - 使用了`颜色`用于区分不同程度 - 使用了`位置`用于区分不同问题 ## 双向柱状图与其他图表的对比 ### 双向柱状图和[柱状图](/zh/charts/bar/) - 柱状图只能绘制在同一个坐标轴内,每个柱子只能表示一个数据 - 双向柱状图包含正向和反向两个坐标轴,每个柱子可以表示一个正向数据和一个反向数据 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 饼图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/pie/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/pie.md - Description: 用扇形角度和面积表达各部分占总体的比例,了解饼图的适用条件、类别数量限制及 G2 示例。 --- title: "饼图" description: "用扇形角度和面积表达各部分占总体的比例,了解饼图的适用条件、类别数量限制及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/pie/" version: "5.4.8" --- pie ## 饼图的简介 饼图是一种圆形统计图表,将数据表示为整个圆的各个扇形,用于显示各个类别在总体中的占比关系。每个扇形的角度大小与其所代表的数值成正比,整个饼图代表数据的总和。 饼图特别适合展示分类数据的占比关系,可以直观地展示各个部分在整体中的相对重要性。通过不同颜色的扇形区分各个类别,使得比较各类别的占比变得简单直观。 当类别较少(通常不超过5-7个)且需要强调各部分与整体关系时,饼图是一个很好的选择。对于类别较多的情况,可以考虑将小占比的类别合并为"其他"类别。 **英文名**:Pie Chart ## 饼图的构成 ### 基础饼图 basic-pie | 图表类型 | 基础饼图 | | ---------------- | --------------------------------------------------------------------------------- | | 适合的数据 | 分类数据:一个分类数据字段、一个连续数据字段 | | 功能 | 展示各个类别在总体中的占比关系 | | 数据与图形的映射 | 分类字段映射到扇形的颜色和标签
数值字段映射到扇形的角度大小
整个圆表示数据总和 | | 适合的场景 | 类别数量较少(5-7个以内)的占比展示 | --- ## 饼图的应用场景 ### 适合的场景 例子 1: **展示市场份额分布** 下图展示了不同浏览器的市场份额占比,清晰地显示了各浏览器在市场中的相对地位。 | browser(浏览器) | value(市场份额) | | ----------------- | ----------------- | | Chrome | 61.04 | | Safari | 15.12 | | Edge | 10.52 | | Firefox | 7.19 | | Samsung Internet | 2.98 | | Opera | 3.15 | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, data: [ { browser: 'Chrome', value: 61.04 }, { browser: 'Safari', value: 15.12 }, { browser: 'Edge', value: 10.52 }, { browser: 'Firefox', value: 7.19 }, { browser: 'Samsung Internet', value: 2.98 }, { browser: 'Opera', value: 3.15 }, ], encode: { y: 'value', color: 'browser', }, transform: [{ type: 'stackY' }], coordinate: { type: 'theta' }, legend: { color: { position: 'right', rowPadding: 5, }, }, }); chart.render(); ``` **说明**: - `browser` 字段映射到颜色,区分不同浏览器 - `value` 字段映射到角度大小,表示市场份额 - 使用 `coordinate: { type: 'theta' }` 将柱状图转换为饼图 - 使用 `transform: [{ type: 'stackY' }]` 堆叠数据 例子 2: **展示预算分配情况** 饼图非常适合展示预算、支出等资源分配情况,让人一目了然地看到各项目的资源占比。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, data: [ { category: '研发', value: 35 }, { category: '营销', value: 25 }, { category: '销售', value: 20 }, { category: '运营', value: 15 }, { category: '其他', value: 5 }, ], encode: { y: 'value', color: 'category', }, transform: [{ type: 'stackY' }], coordinate: { type: 'theta', outerRadius: 0.8 }, scale: { color: { palette: 'category10', }, }, legend: { color: { position: 'right', rowPadding: 5, }, }, labels: [ { text: (d) => `${d.category}: ${d.value}%`, position: 'outside', connector: true, }, ], }); chart.render(); ``` **说明**: - 使用外部标签显示类别名称和百分比 - `connector: true` 添加标签连接线 - `outerRadius: 0.8` 调整饼图大小,为标签留出空间 ### 不适合的场景 例子 1: **类别过多时不适合使用饼图** 当类别数量超过7个时,饼图会变得难以阅读和比较。下图展示了各个省份的人口占比情况,由于包含的分类过多,很难清晰对比各个省份的人口数据占比情况。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, data: [ { province: '北京市', population: 19612368 }, { province: '天津市', population: 12938693 }, { province: '河北省', population: 71854210 }, { province: '山西省', population: 27500000 }, { province: '内蒙古自治区', population: 24706291 }, { province: '辽宁省', population: 43746323 }, { province: '吉林省', population: 27452815 }, { province: '黑龙江省', population: 38313991 }, { province: '上海市', population: 23019196 }, { province: '江苏省', population: 78660941 }, { province: '浙江省', population: 54426891 }, { province: '安徽省', population: 59500468 }, { province: '福建省', population: 36894217 }, { province: '江西省', population: 44567797 }, { province: '山东省', population: 95792719 }, { province: '河南省', population: 94029939 }, { province: '湖北省', population: 57237727 }, { province: '湖南省', population: 65700762 }, { province: '广东省', population: 104320459 }, { province: '广西壮族自治区', population: 46023761 }, { province: '海南省', population: 8671485 }, { province: '重庆市', population: 28846170 }, { province: '四川省', population: 80417528 }, { province: '贵州省', population: 34748556 }, { province: '云南省', population: 45966766 }, { province: '西藏自治区', population: 3002165 }, { province: '陕西省', population: 37327379 }, { province: '甘肃省', population: 25575263 }, { province: '青海省', population: 5626723 }, ], encode: { y: 'population', color: 'province', }, transform: [{ type: 'stackY' }], coordinate: { type: 'theta' }, }); chart.render(); ``` **说明**:此种情况下,我们推荐使用[柱状图](/zh/charts/bar/)。 例子 2: **各类别占比相近时不适合使用饼图** 下图中游戏公司的不同种类游戏的销售量相近,角度差异很小,难以通过视觉快速区分大小关系。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, data: [ { genre: 'Sports', sold: 15000 }, { genre: 'Strategy', sold: 14900 }, { genre: 'Action', sold: 15050 }, { genre: 'Shooter', sold: 13000 }, { genre: 'Other', sold: 13900 }, ], encode: { y: 'sold', color: 'genre', }, transform: [{ type: 'stackY' }], coordinate: { type: 'theta', outerRadius: 0.8 }, legend: { color: { position: 'bottom', layout: { justifyContent: 'center' }, }, }, labels: [ { text: (d, i, data) => { const total = data.reduce((acc, curr) => acc + curr.sold, 0); const percent = ((d.sold / total) * 100).toFixed(2); return `${percent}%`; }, style: { fontSize: 10, }, }, ], }); chart.render(); ``` **说明**:上图中各个分类的占比都接近20%,差异很小,不太适合使用饼图,此时可以使用[柱状图](/zh/charts/bar/)来呈现。 ## 饼图的扩展 ### 径向扰动散点图 径向扰动散点图将传统的饼图数据映射到极坐标系中的散点,通过添加径向扰动来避免重叠,适合展示分类数据的分布情况。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); chart.options({ type: 'point', autoFit: true, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/diamond.json', }, encode: { x: 'clarity', color: 'clarity' }, transform: [{ type: 'jitter' }], coordinate: { type: 'polar' }, legend: false, }); chart.render(); ``` **说明**: - 数据点按角度分布在圆形区域内,模拟饼图的扇形分布 - 添加径向扰动(随机偏移)避免数据点重叠 - 点的大小映射数值大小,颜色区分类别 - 适合展示分类数据的空间分布和数值关系 ## 饼图与其他图表的对比 ### 饼图与[环形图](/zh/charts/donut-chart/) - 饼图是最基础的形式,适合简单的占比展示 - 环形图可以在中心显示额外信息,空间利用率更高 ### 饼图与[玫瑰图](/zh/charts/rose/) - 饼图展示静态的占比关系,适合某个时间点的数据快照 - 玫瑰图适合展示分类数据的分布情况,特别是当类别数量较多时 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 环图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/donut-chart/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/donut-chart.md - Description: 用环形扇区展示各类别占总体的比例,了解环图与饼图的区别、适用场景及 G2 示例。 --- title: "环图" description: "用环形扇区展示各类别占总体的比例,了解环图与饼图的区别、适用场景及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/donut-chart/" version: "5.4.8" --- donut ## 环图的简介 环图(又叫做甜甜圈图),其本质是饼图将中间区域挖空。环图采用了让我们更关注长度而不是面积的做法,这样我们就能相对简单的对比不同的环图。 虽然环图和饼图在数据表达上很相似,但环图还是有它独特的优点: - **更好的对比性**:饼图的整体性太强,我们会将注意力集中在比较饼图内各个扇形之间占整体比重的关系。但如果我们将两个饼图放在一起,饼图很难同时对比两个图。环图解决了这个问题。 - **空间利用率更高**:环图可以使用它的空心区域显示文本信息,比如标题、总数等。 **英文名**:Donut Chart ## 环图的构成 donut-structure | 图表类型 | 环图 | | ---------------- | ------------------------------------------------------------ | | 适合的数据 | 列表:一个分类数据字段、一个连续数据字段 | | 功能 | 对比分类数据的数值大小 | | 数据与图形的映射 | 分类数据字段映射到环形的颜色
连续数据字段映射到环形的角度 | | 适合的数据条数 | 不超过 9 条数据 | ## 环图的应用场景 ### 适合的场景 例子 1: **展示分类的占比情况** 这种用法与饼图类似,下图是一个游戏公司的销售情况: | genre(游戏类型) | sold(销售量) | | ----------------- | -------------- | | Sports | 27,500 | | Strategy | 11,500 | | Action | 6,000 | | Shooter | 3,500 | | Other | 1,500 | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, transform: [{ type: 'stackY' }], // 添加堆叠转换 data: [ { genre: 'Sports', sold: 27500 }, { genre: 'Strategy', sold: 11500 }, { genre: 'Action', sold: 6000 }, { genre: 'Shooter', sold: 3500 }, { genre: 'Other', sold: 1500 }, ], coordinate: { type: 'theta', innerRadius: 0.5 }, encode: { y: 'sold', color: 'genre' }, legend: { color: { position: 'bottom', layout: { justifyContent: 'center' } }, }, labels: [ { text: 'genre', style: { fontWeight: 'bold', }, }, { text: (d, i, data) => { const total = data.reduce((acc, curr) => acc + curr.sold, 0); const percent = ((d.sold / total) * 100).toFixed(2); return `${percent}%`; }, style: { fontSize: 10, dy: 12, }, }, ], }); chart.render(); ``` **说明**: - `genre` 字段映射到`颜色`,用于区分不同的游戏类型 - `sold` 字段映射到`角度`,表示销售量占比的大小 - 使用 `coordinate: { type: 'theta', innerRadius: 0.5 }` 创建环图效果 例子 2: **在中心区域显示额外信息** 环图的空心区域可以用来显示总数、标题或其他重要信息: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); const data = [ { type: '移动端', value: 54.2 }, { type: '桌面端', value: 32.1 }, { type: '平板端', value: 13.7 }, ]; const total = data.reduce((acc, curr) => acc + curr.value, 0); chart.options({ type: 'interval', autoFit: true, transform: [{ type: 'stackY' }], // 添加堆叠转换 data, coordinate: { type: 'theta', innerRadius: 0.6 }, encode: { y: 'value', color: 'type' }, style: { stroke: '#fff', lineWidth: 2 }, legend: { color: { position: 'bottom', layout: { justifyContent: 'center' } }, }, labels: [ { text: (d) => `${d.value}%`, style: { fontWeight: 'bold', fontSize: 12, }, }, ], }); chart.render(); ``` ### 不适合的场景 例子 1: **分类过多的场景** 下图是各个省的人口的占比情况,因为这张图上包含的分类过多,很难清晰对比各个省份的人口数据占比情况,所以这种情况下,我们推荐使用[柱状图](/zh/charts/bar/)。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, transform: [{ type: 'stackY' }], // 添加堆叠转换 data: [ { province: '北京市', population: 19612368 }, { province: '天津市', population: 12938693 }, { province: '河北省', population: 71854210 }, { province: '山西省', population: 27500000 }, { province: '内蒙古自治区', population: 24706291 }, { province: '辽宁省', population: 43746323 }, { province: '吉林省', population: 27452815 }, { province: '黑龙江省', population: 38313991 }, { province: '上海市', population: 23019196 }, { province: '江苏省', population: 78660941 }, { province: '浙江省', population: 54426891 }, { province: '安徽省', population: 59500468 }, { province: '福建省', population: 36894217 }, { province: '江西省', population: 44567797 }, { province: '山东省', population: 95792719 }, { province: '河南省', population: 94029939 }, { province: '湖北省', population: 57237727 }, { province: '湖南省', population: 65700762 }, { province: '广东省', population: 104320459 }, { province: '广西壮族自治区', population: 46023761 }, { province: '海南省', population: 8671485 }, { province: '重庆市', population: 28846170 }, { province: '四川省', population: 80417528 }, { province: '贵州省', population: 34748556 }, { province: '云南省', population: 45966766 }, { province: '西藏自治区', population: 3002165 }, { province: '陕西省', population: 37327379 }, { province: '甘肃省', population: 25575263 }, { province: '青海省', population: 5626723 }, ], coordinate: { type: 'theta', innerRadius: 0.4 }, encode: { y: 'population', color: 'province' }, legend: false, // 分类太多,关闭图例 labels: false, // 分类太多,关闭标签 }); chart.render(); ``` **说明**:上图中分类过多,颜色难以区分,标签重叠严重,不适合使用环图。 例子 2: **分类占比差别不明显的场景** 下图中游戏公司的不同种类游戏的销售量相近,角度差异很小,不太适合使用环图,此时可以使用[柱状图](/zh/charts/bar/)。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, transform: [{ type: 'stackY' }], // 添加堆叠转换 data: [ { genre: 'Sports', sold: 15000 }, { genre: 'Strategy', sold: 14900 }, { genre: 'Action', sold: 15050 }, { genre: 'Shooter', sold: 13000 }, { genre: 'Other', sold: 13900 }, ], coordinate: { type: 'theta', innerRadius: 0.5 }, encode: { y: 'sold', color: 'genre' }, legend: { color: { position: 'bottom', layout: { justifyContent: 'center' } }, }, labels: [ { text: (d, i, data) => { const total = data.reduce((acc, curr) => acc + curr.sold, 0); const percent = ((d.sold / total) * 100).toFixed(2); return `${percent}%`; }, style: { fontSize: 10, }, }, ], }); chart.render(); ``` **说明**:上图中各个分类的占比都接近 20%,差异很小,难以通过角度清晰地对比数值大小。 ## 环图的扩展 ### 分面环图 使用 G2 的分面功能,可以将分组数据绘制成多个环图。下图展示了全球最大 1000 家银行所在地区在 2007 年和 2011 年的利润总额占比情况: | 年份 | 地区 | 利润总额(亿美金) | | ---- | ---------- | ------------------ | | 2007 | 亚太地区 | 1485.54 | | 2007 | 非洲及中东 | 330.12 | | 2007 | 拉丁美洲 | 196.5 | | …… | …… | …… | | 2011 | 亚太地区 | 4107.18 | | 2011 | 非洲及中东 | 495.3 | | 2011 | 拉丁美洲 | 495.3 | | …… | …… | …… | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'facetRect', autoFit: true, data: [ { year: '2007', area: '亚太地区', profit: 1485.54 }, { year: '2007', area: '西欧', profit: 3631.32 }, { year: '2007', area: '北美', profit: 2083.9 }, { year: '2007', area: '非洲及中东', profit: 330.12 }, { year: '2007', area: '中欧和东欧', profit: 141.48 }, { year: '2007', area: '拉丁美洲', profit: 196.5 }, { year: '2011', area: '亚太地区', profit: 4107.18 }, { year: '2011', area: '西欧', profit: 480.06 }, { year: '2011', area: '北美', profit: 1783.08 }, { year: '2011', area: '非洲及中东', profit: 495.3 }, { year: '2011', area: '中欧和东欧', profit: 259.08 }, { year: '2011', area: '拉丁美洲', profit: 495.3 }, ], encode: { x: 'year' }, children: [ { type: 'interval', transform: [{ type: 'stackY' }], // 添加堆叠转换 coordinate: { type: 'theta', innerRadius: 0.5 }, encode: { y: 'profit', color: 'area' }, legend: { color: { position: 'bottom', layout: { justifyContent: 'center' } }, }, labels: [ { text: (d, i, data) => { const total = data.reduce((acc, curr) => acc + curr.profit, 0); const percent = ((d.profit / total) * 100).toFixed(1); return `${percent}%`; }, style: { fontSize: 10, }, }, ], }, ], }); chart.render(); ``` ## 环图与其他图表的对比 ### 环图和[饼图](/zh/charts/pie/) - **相似性**:都用于展示分类数据的占比关系 - **区别**: - 环图中心留空,可以放置额外信息 - 环图更强调弧长(周长),饼图更强调面积 - 环图在多图对比时效果更好 ### 环图和[柱状图](/zh/charts/bar/) - **环图**:更适合展示占比关系,强调部分与整体的关系 - **柱状图**:更适合展示精确的数值对比,特别是数值差异较小的情况 ### 环图和[南丁格尔玫瑰图](/zh/charts/rose/) - **环图**:通过角度表示数值,内径固定 - **南丁格尔玫瑰图**:通过半径表示数值,更容易区分数值差异 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 热力图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/heatmap/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/heatmap.md - Description: 用颜色强度表现数值或空间密度,了解热力图的数据映射、分布分析场景及 G2 示例。 --- title: "热力图" description: "用颜色强度表现数值或空间密度,了解热力图的数据映射、分布分析场景及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/heatmap/" version: "5.4.8" --- heatmap ## 热力图的简介 热力图是一种通过颜色强度映射二维数据密度或数值大小的可视化图表,擅长揭示数据分布规律、聚类特征及异常点。热力图将两个分类/连续字段(如 x、y)映射为坐标轴,第三个数值字段(如 value)映射为颜色梯度,形成网格化的色块矩阵,通常冷色调(如蓝色)表示低值,暖色调(如红色)表示高值。 热力图特别适合展示大量数据点的分布特征,通过颜色的变化可以直观地反映数据集中的密度或强度变化,帮助识别数据中的模式和关系。在展示多维数据时,热力图比条形图或散点图更加直观,能够一目了然地显示数据的聚集区域和稀疏区域。 热力图广泛应用于地理空间分析、网站用户行为研究、科学研究中的相关性分析等多种场景。 **英文名**:Heatmap, Heat Map ## 热力图的构成 ### 边界未经平滑处理的热力图 basic-heatmap | 图表类型 | 边界未经平滑处理的热力图 | | ---------------- | ----------------------------------------------------------- | | 适合的数据 | 三个连续字段 | | 功能 | 观察数据的分布情况 | | 数据与图形的映射 | 两个连续字段分别映射到 x 轴、y 轴。一个连续元数据映射到颜色 | | 适合的数据条数 | 超过 30 条数据 | --- ### 边界经平滑处理的热力图 density-heatmap | 图表类型 | 边界经平滑处理的热力图 | | ---------------- | ----------------------------------------------------------- | | 适合的数据 | 三个连续字段 | | 功能 | 展示数据的分布情况,加上统计算法可预测未知区域数据 | | 数据与图形的映射 | 两个连续字段分别映射到 x 轴、y 轴。一个连续元数据映射到颜色 | | 适合的数据条数 | 超过 30 条数据 | ## 热力图的应用场景 ### 适合的场景 例 1: **适合展示二维数据的分布密度** 下面这张热力图展示了二维空间上的温度分布情况。通过颜色的变化可以直观地看出不同区域的温度差异。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', autoFit: true, padding: 0, }); chart.options({ type: 'view', axis: false, children: [ { type: 'image', style: { src: 'https://gw.alipayobjects.com/zos/rmsportal/NeUTMwKtPcPxIFNTWZOZ.png', x: '50%', y: '50%', width: '100%', height: '100%', }, tooltip: false, }, { type: 'heatmap', data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/heatmap.json', }, encode: { x: 'g', y: 'l', color: 'tmp', }, style: { opacity: 0, }, tooltip: false, }, ], }); chart.render(); ``` **说明**: - `g` 字段映射到 x 轴,`l` 字段映射到 y 轴,表示二维空间中的位置 - `tmp` 字段映射到颜色,表示每个位置点的温度值 - 背景图像和热力叠加,直观展示温度分布情况 例 2: **适合展示散点数据的密度分布** 密度热力图可以展示散点数据的集中区域,下面的例子展示了钻石数据集中克拉数和价格的分布关系。 ```ts import { Chart } from '@antv/g2'; import DataSet from '@antv/data-set'; const chart = new Chart({ container: 'container', autoFit: true, }); chart.options({ type: 'view', data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/diamond.json', }, scale: { x: { nice: true, domainMin: -0.5 }, y: { nice: true, domainMin: -2000 }, color: { nice: true }, }, children: [ { type: 'heatmap', data: { transform: [ { type: 'custom', callback: (data) => { const dv = new DataSet.View().source(data); dv.transform({ type: 'kernel-smooth.density', fields: ['carat', 'price'], as: ['carat', 'price', 'density'], }); return dv.rows; }, }, ], }, encode: { x: 'carat', y: 'price', color: 'density', }, style: { opacity: 0.3, gradient: [ [0, 'white'], [0.2, 'blue'], [0.4, 'cyan'], [0.6, 'lime'], [0.8, 'yellow'], [0.9, 'red'], ], }, }, { type: 'point', encode: { x: 'carat', y: 'price', }, }, ], }); chart.render(); ``` **说明**: - `carat` 字段和 `price` 字段分别映射到 x 轴和 y 轴 - 使用核密度估计(kernel density estimation)计算散点的密度分布 - 密度值映射到颜色,形成热力效果 - 叠加原始散点数据,可以同时观察数据点和密度分布 ### 不适合的场景 例 1: **不适合精确比较具体数值** 热力图通过颜色强度表现数值大小,但人眼对颜色的感知不如对长度的感知精确。如果需要准确比较具体数值,柱状图或折线图是更好的选择。 例 2: **不适合展示少量离散数据点** 当数据点较少时,热力图的密度分布优势不明显,直接使用散点图可能更为清晰直观。 ## 热力图的扩展 ### 阈值热力图 阈值热力图根据预设的阈值区间,将连续数据划分为离散的颜色区间,适合强调特定范围内的数据。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', autoFit: true, }); // 生成模拟的二维网格数据 const data = []; for (let i = 0; i < 10; i++) { for (let j = 0; j < 10; j++) { data.push({ x: i, y: j, value: Math.floor(Math.random() * 100), }); } } chart.options({ type: 'cell', data, encode: { x: 'x', y: 'y', color: 'value', }, scale: { color: { type: 'threshold', domain: [25, 50, 75], range: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b'], }, }, style: { stroke: '#fff', lineWidth: 1, }, label: { text: 'value', style: { fontSize: 10, fill: '#000', }, }, }); chart.render(); ``` **说明**: - 生成 10x10 的网格数据,模拟二维热力图数据结构 - 使用阈值(threshold)比例尺将连续数据划分为离散区间 - 设置了 25、50、75 三个阈值,将数据分为四个区间 - 每个区间使用不同的颜色表示,便于区分不同数值级别的分布 ## 热力图与其他图表的对比 ### 热力图与[散点图](/zh/charts/scatter-plot/) - 热力图强调数据密度和分布模式,通过颜色梯度直观地展示数据集中区域 - 散点图展示每个独立的数据点,更适合观察个体数据点的分布和离群点 - 热力图适合处理大量数据点,散点图在数据量大时可能出现重叠问题 ### 热力图与[气泡图](/zh/charts/bubble-chart/) - 热力图通过颜色强度表现数据密度或第三维度的数值大小 - 气泡图通过气泡大小表现第三维度的数值,同时可以显示具体的数据点位置 - 热力图更适合展示连续分布和密度模式,气泡图更适合展示离散数据点的多维关系 ### 热力图与[等高线图](/zh/charts/contourline/) - 热力图使用颜色强度来表示数值大小,提供直观的视觉对比 - 等高线图使用等值线连接相同数值的点,更适合分析连续变化的趋势和梯度 - 热力图更适合展示离散数据点的密度分布,等高线图更适合展示连续表面的变化模式 ### 热力图与[直方图](/zh/charts/histogram/) - 热力图可以展示二维空间上的数据分布,适合分析两个变量之间的关系 - 直方图展示单一变量的频率分布,更适合分析单一变量的分布特征 - 热力图可以看作是二维直方图的扩展,用颜色替代了高度来表示频率或密度 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 分级统计地图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/choropleth-map/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/choropleth-map.md - Description: 按地理区域的数值设置颜色深浅或色阶,了解分级统计地图的数据要求、区域比较方式及 G2 示例。 --- title: "分级统计地图" description: "按地理区域的数值设置颜色深浅或色阶,了解分级统计地图的数据要求、区域比较方式及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/choropleth-map/" version: "5.4.8" --- 分级统计地图是一种在地图分区上使用视觉符号(通常是颜色、阴影或者不同疏密的晕线)来表示一个范围值的分布情况的地图。在整个制图区域的若干个小的区划单元内(行政区划或者其他区划单位),根据各分区的数量(相对)指标进行分级,并用相应色级或不同疏密的晕线,反映各区现象的集中程度或发展水平的分布差别,最常见于选举和人口普查数据的可视化,这些数据以省、市等地理区域为单位。 ## 分级统计地图的简介 此法因常用色级表示,所以也叫色级统计图法。地图上每个分区的数量使用不同的色级表示,较典型的方法有:(1)一个颜色到另一个颜色混合渐变;(2)单一的色调渐变;(3)透明到不透明;(4)明到暗;(5)用一个完整的色谱变化。Choropleth 地图依靠颜色等来表现数据内在的模式,因此选择合适的颜色非常重要,当数据的值域大或者数据的类型多样时,选择合适的颜色映射相当有挑战性。 Choropleth 地图最大的问题在于数据分布和地理区域大小的不对称。通常大量数据集中于人口密集的区域,而人口稀疏的地区却占有大多数的屏幕空间,用大量的屏幕空间来表示小部分数据的做法对空间的利用非常不经济,这种不对称还常常会造成用户对数据的错误理解,不能很好地帮助用户准确得区分和比较地图上各个分区的数据值。 分级统计地图示例 **英文名**:Choropleth Map ## 分级统计地图的构成 | 图表类型 | 分级统计地图 | | ---------------- | ------------------------------------------------------------ | | 适合的数据 | 一个分类字段,一个连续字段 | | 功能 | 对比分类数据的数值大小 | | 数据与图形的映射 | 一个分类字段映射到地图的地理位置
另一个连续字段映射到颜色 | | 适合的数据条数 | 根据实际地理位置信息,暂无限制 | ## 分级统计地图的应用场景 ### 适合的场景 分级统计地图较多的是反映呈面状但属分散分布的现象,如反映人口密度、某农作物播种面积的比、人均收入等。 例子 1: **地理区域数据可视化** 下图展示了美国各州的人口分布,通过颜色的深浅反映了人口的主要分布情况,能很明显看出 California、Texas 两大州人口最多。同时也展示了 2015 年全球人口性别比例分布,其中数值表示的是每 100 位女性对应的男性数量。可以看出在欧美国家,普遍是女性略多于男性,在前苏联地区这种现象尤为突出,而中东地区却是男多女少。 | 数据类型 | 地区/国家 | 数值 | 说明 | | -------- | ---------- | -------- | ----------------------- | | 人口数 | California | 38802500 | CA | | 人口数 | Texas | 26956958 | TX | | 人口数 | Florida | 19893297 | FL | | 性别比例 | Russia | 86.8 | 每 100 女性对应的男性数 | | 性别比例 | China | 106.3 | 每 100 女性对应的男性数 | | 性别比例 | Japan | 94.7 | 每 100 女性对应的男性数 | | ... | ... | ... | ... | ```ts import { Chart } from '@antv/g2'; import { feature } from 'topojson-client'; // 加载地图和数据 Promise.all([ fetch('https://assets.antv.antgroup.com/g2/us-10m.json').then((res) => res.json(), ), fetch('https://assets.antv.antgroup.com/g2/unemployment2.json').then((res) => res.json(), ), ]).then(([us, unemployment]) => { const counties = feature(us, us.objects.counties).features; const chart = new Chart({ container: 'container', autoFit: true, }); chart.options({ type: 'geoPath', coordinate: { type: 'albersUsa', // 使用美国地图的专用投影 }, data: { value: counties, transform: [ { type: 'join', join: unemployment, on: ['id', 'id'], select: ['rate'], }, ], }, scale: { color: { palette: 'ylGnBu', // 使用从黄到蓝的渐变色板 unknown: '#fff', // 未知数据显示为白色 }, }, encode: { color: 'rate', // 将失业率映射到颜色通道 }, legend: { color: { layout: { justifyContent: 'center' }, // 调整图例布局 }, }, style: { stroke: '#666', strokeWidth: 0.5, }, tooltip: { title: (d) => d.properties.name, items: [{ field: 'rate', name: '失业率' }], }, }); chart.render(); }); ``` **说明**: - 数值字段映射到颜色,表示数据的大小 - 地理区域名称决定了在其所在的地理位置绘制区域 - 对于面积较小的区块,因为数值也较小,所以渲染的颜色也浅,导致这些区块在图上很难被看见,这也是分级统计地图的局限性 ### 不适合的场景 - **展示精确数值**:分级统计地图主要用于展示数据的相对大小和分布趋势,不适合需要精确数值读取的场景。 - **区域面积差异过大**:当区域面积差异很大时,面积较大的区域在视觉上会占据主导地位,即使其对应的数值较小,也可能给用户带来误导。此时可以考虑使用气泡地图等其他类型的地图。 - **数据不具有地理相关性**:如果数据与地理位置没有直接关系,使用分级统计地图没有意义。 例子 1: **2008 年美国总统大选结果** 选举可视化很容易给用户造成数据分布和地理区域大小不对称的错觉。民主党候选人奥巴马和共和党候选人麦凯恩胜出的州分别用蓝色和红色表示。从图上看,共和党比民主党获得了更多的投票,因为红色的区域所占的面积更大。但是在美国总统大选中,最后的结果是看候选人获得的选举人票数,每个州拥有的选举人票数是不一样的,在一个州获胜的选举人将得到该州的全部选举人票。这个时候推荐使用点描法地图。 | State(州) | 选举人票数 | 奥巴马得票 | 麦凯恩得票 | | ----------- | ---------- | ---------- | ---------- | | Alabama | 9 | 813479 | 1266546 | | Arizona | 10 | 1034707 | 638017 | | Arkansas | 6 | 422310 | 638017 | ## 分级统计地图与其他图表的对比 ### 分级统计地图与气泡地图 - **分级统计地图**:将数值映射到地图区域的颜色上,适合展示连续型数据的分布。 - **气泡地图**:在地图区域上显示一个气泡,气泡的大小表示数值的大小,适合展示离散点数据或在区域面积差异较大时避免误导。 分级统计地图经常会带来误判,面积大的区域可能数值(人口数、选举人票等)比较小。 ### 分级统计地图与点描法地图 - **点描法地图**:主要用于显示某个经纬度上的数据,而分级统计地图用于显示某个区域的统计值。 - **点描法地图**:可以显示大量的数据值,而分级统计地图受显示区域的限制。 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 漏斗图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/funnel/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/funnel.md - Description: 展示连续流程中各阶段的数量及转化情况,了解漏斗图在转化分析中的应用和 G2 示例。 --- title: "漏斗图" description: "展示连续流程中各阶段的数量及转化情况,了解漏斗图在转化分析中的应用和 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/funnel/" version: "5.4.8" --- funnel ## 漏斗图的简介 漏斗图是一种特殊的可视化图表,用于展示业务过程中数据在不同阶段的流转情况。它因形状类似漏斗而得名,从上到下依次展示各个阶段的数据量,通常上宽下窄,反映数据流失或转化的过程。 漏斗图特别适合用于可视化业务流程中的转化率,例如销售流程、用户注册流程或营销漏斗等。通过漏斗图,可以直观地观察到各个阶段的数据变化,识别出关键的转化环节或瓶颈。 漏斗图的每一层代表流程中的一个阶段,层的宽度或面积通常与该阶段的数据量成正比,从而清晰地反映出转化过程中的数据损失情况。 **英文名**:Funnel Chart, Funnel Diagram ## 漏斗图的构成 ### 基础漏斗图 basic-funnel | 图表类型 | 基础漏斗图 | | ---------------- | ---------------------------------------------------------------------------------- | | 适合的数据 | 有序分类数据:表示流程各阶段的分类字段、每个阶段的数值字段 | | 功能 | 展示业务流程各阶段的数据流转和转化率 | | 数据与图形的映射 | 分类字段映射到漏斗的各层
数值字段映射到漏斗各层的宽度或面积
颜色区分不同阶段 | | 适合的场景 | 分析多阶段流程中的转化情况和流失节点 | ## 漏斗图的应用场景 ### 适合的场景 例子 1: **适合展示销售流程的转化分析** 下图展示了销售漏斗中从获取潜在客户到最终成交的各个阶段转化情况。 | stage(阶段) | value(数量) | | ------------- | ------------- | | 访问 | 8043 | | 咨询 | 2136 | | 报价 | 908 | | 议价 | 691 | | 成交 | 527 | ```ts 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', }, }, style: { labelText: (d) => `${d.stage}: ${d.value}`, }, animate: { enter: { type: 'fadeIn' } }, axis: false, labels: [ { text: (d) => `${d.stage}\n${d.value}`, position: 'inside', transform: [{ type: 'contrastReverse' }], }, ], legend: false, }); chart.render(); ``` **说明** : - `stage` 字段映射到漏斗的各个层级,表示销售流程的不同阶段 - `value` 字段映射到各层的宽度,直观展示了从漏斗顶部到底部的转化情况 - 通过漏斗形状,可以清晰地看出各阶段之间的转化率和流失情况 例子 2: **适合展示网站流量转化分析** 漏斗图可以有效地分析网站流量从访问到最终行为的转化过程,帮助识别用户流失的关键环节。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', data: [ { stage: '浏览首页', value: 100000, percent: '100%' }, { stage: '搜索产品', value: 60000, percent: '60%' }, { stage: '查看商品详情', value: 30000, percent: '30%' }, { stage: '加入购物车', value: 10000, percent: '10%' }, { stage: '完成购买', value: 5000, percent: '5%' }, ], encode: { x: 'stage', y: 'value', color: 'stage', shape: 'funnel', }, coordinate: { transform: [{ type: 'transpose' }] }, transform: [ { type: 'symmetryY', }, ], scale: { color: { palette: 'blues', }, }, style: { labelText: (d) => `${d.stage}: ${d.percent}`, }, animate: { enter: { type: 'fadeIn' } }, axis: false, labels: [ { text: (d) => `${d.stage}\n${d.percent}`, position: 'inside', transform: [{ type: 'contrastReverse' }], }, ], legend: false, }); chart.render(); ``` **说明**: - 每一层代表网站流量转化的不同阶段 - 层的宽度反映了该阶段的用户数量 - 通过比较相邻层的差异,可以发现用户流失的主要环节 - 添加了转化百分比的标签,更直观地展示转化效果 例子 3: **对比漏斗图展示不同渠道的转化效果** 当需要比较不同渠道或时期的转化效果时,可以使用对比漏斗图进行分析。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'view', data: [ { stage: '访问', value: 8043, category: '渠道A' }, { stage: '咨询', value: 2136, category: '渠道A' }, { stage: '报价', value: 908, category: '渠道A' }, { stage: '议价', value: 691, category: '渠道A' }, { stage: '成交', value: 527, category: '渠道A' }, { stage: '访问', value: 6841, category: '渠道B' }, { stage: '咨询', value: 2761, category: '渠道B' }, { stage: '报价', value: 1098, category: '渠道B' }, { stage: '议价', value: 624, category: '渠道B' }, { stage: '成交', value: 487, category: '渠道B' }, ], axis: false, children: [ { type: 'interval', region: { start: { x: 0, y: 0 }, end: { x: 0.48, y: 1 } }, transform: [{ type: 'filter', callback: (d) => d.category === '渠道A' }], encode: { x: 'stage', y: 'value', color: 'stage', shape: 'funnel', }, coordinate: { transform: [{ type: 'transpose' }] }, transform: [ { type: 'symmetryY', }, ], scale: { color: { palette: 'spectral' }, }, style: { labelText: (d) => `${d.value}`, }, animate: { enter: { type: 'fadeIn' } }, axis: false, labels: [ { text: (d) => `${d.value}`, position: 'inside', transform: [{ type: 'contrastReverse' }], }, ], title: '渠道A', }, { type: 'interval', region: { start: { x: 0.52, y: 0 }, end: { x: 1, y: 1 } }, transform: [{ type: 'filter', callback: (d) => d.category === '渠道B' }], encode: { x: 'stage', y: 'value', color: 'stage', shape: 'funnel', }, coordinate: { transform: [{ type: 'transpose' }] }, transform: [ { type: 'symmetryY', }, ], scale: { color: { palette: 'spectral' }, }, style: { labelText: (d) => `${d.value}`, }, animate: { enter: { type: 'fadeIn' } }, axis: false, labels: [ { text: (d) => `${d.value}`, position: 'inside', transform: [{ type: 'contrastReverse' }], }, ], title: '渠道B', }, ], legend: false, }); chart.render(); ``` **说明**: - 左右并排展示两个渠道的转化漏斗,实现直观对比 - 通过相同的颜色编码对应相同的阶段,便于比较 - 可以清晰地观察到不同渠道在各个阶段的转化效率差异 例子 4:**直观、形象的图表形式,常用于展示层级结构或分布比例** 公司职级金字塔结构,根据职级高低将员工划分为多个层级,各层级在人数比例、职责与权限上存在显著差异,直观体现了公司内部的层级分化情况。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', coordinate: { transform: [{ type: 'transpose' }], }, data: [ { text: '顶层', value: 5 }, { text: '中上层', value: 10 }, { text: '中等', value: 20 }, { text: '中下层', value: 25 }, { text: '底层', value: 40 }, ], transform: [ { type: 'symmetryY', }, ], axis: { x: false, y: false, }, style: { reverse: true, }, encode: { x: 'text', y: 'value', color: 'text', shape: 'pyramid', }, scale: { x: { paddingOuter: 0, paddingInner: 0 }, color: { type: 'ordinal' }, }, labels: [ { text: (d) => d.text, position: "inside" }, { text: (d) => d.value + "%", position: "inside", style: { dy: 15 } }, ] }) chart.render(); ``` **说明**: - 使用 `reverse: true` 改变漏斗图收口位置 - 人数比例应逐层递减,反映“底宽顶尖”的结构 - 各层级人口占比清晰标注,便于比较不同群体的数量或比例 - 金字塔形状直观展现阶层分布和分层特点 ### 不适合的场景 例子 1: **不适合展示无序或无明显层级关系的数据** 漏斗图的本质是展示有序流程的转化过程,如果数据没有明确的先后顺序或层级关系,使用漏斗图会造成误导。对于这类数据,柱状图或饼图可能是更合适的选择。 例子 2: **不适合展示各阶段数值波动或增长的数据** 漏斗图通常表达的是数据从多到少的递减过程。如果流程中存在某些阶段的数值大于前一阶段(例如营销活动导致用户数增加),使用传统漏斗图会显得不合适,此时可以考虑使用其他图表形式。 ## 漏斗图的扩展 ### 对比漏斗图 对比漏斗图可以更清晰地展示两个不同流程或实体的转化效果对比,帮助识别不同方案之间的差异和优劣。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); const label = { text: 'visitor', position: 'inside', transform: [{ type: 'contrastReverse' }], }; 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: [ label, { text: 'action', position: 'right', dx: (d) => { return d.action === '完成' ? 48 : 16; }, }, ], }, { 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: [label], }, ], }); chart.render(); ``` **说明**: - 使用水平对比的布局展示两个站点的转化漏斗效果 - 上下两个漏斗分别展示不同站点的数据,便于直观对比 - 通过 y 轴负值变换实现下方漏斗的反向展示,形成镜像对比效果 - 相同的颜色编码和标签配置确保对比的一致性 ### 金字塔漏斗图 金字塔形漏斗图是一种变体,通过对称的金字塔形状展示转化流程,能够更突出各阶段的转化率变化。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); chart.options({ type: 'interval', autoFit: true, paddingRight: 80, data: { type: 'inline', value: [ { action: '浏览网站', pv: 50000 }, { action: '放入购物车', pv: 35000 }, { action: '生成订单', pv: 25000 }, { action: '支付订单', pv: 15000 }, { action: '完成交易', pv: 8000 }, ], transform: [ { type: 'custom', callback: (data) => data.map((d) => ({ ...d, rate: d.pv / data[0].pv, })), }, ], }, encode: { x: 'action', y: 'pv', color: 'action', shape: 'pyramid' }, transform: [{ type: 'symmetryY' }], scale: { x: { padding: 0 } }, coordinate: { transform: [{ type: 'transpose' }] }, animate: { enter: { type: 'fadeIn' } }, axis: false, legend: { color: { position: 'bottom' } }, labels: [ { text: (d) => `${d.action} ${d.pv}`, textAlign: 'left' }, { text: (d) => `${(d.rate * 100).toFixed(1)}%`, position: 'inside', transform: [{ type: 'contrastReverse' }], }, ], }); chart.render(); ``` **说明**: - 使用 `shape: "pyramid"` 创建对称的金字塔形状,视觉效果更加平衡 - 通过 `symmetryY` 变换实现上下对称的金字塔布局 - 自动计算并显示每个阶段的转化率百分比 - 横向展示便于阅读标签信息,特别适合阶段名称较长的场景 ## 漏斗图与其他图表的对比 ### 漏斗图和[柱状图](/zh/charts/bar/) - 漏斗图强调的是流程转化和各阶段的连续关系 - 柱状图更适合直接比较不同类别间的数值大小 - 当需要详细分析流程转化时,漏斗图能提供更直观的视觉效果 ### 漏斗图和[饼图](/zh/charts/pie/) - 漏斗图展示的是有序流程中的各个阶段及其关系 - 饼图展示的是整体中各部分的占比关系,不强调顺序 - 当关注各阶段间的转化而非单纯的占比时,漏斗图是更好的选择 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### K线图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/k-chart/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/k-chart.md - Description: 用开盘、收盘、最高和最低价格描述行情变化,了解 K 线图的结构、金融分析场景及 G2 示例。 --- title: "K线图" description: "用开盘、收盘、最高和最低价格描述行情变化,了解 K 线图的结构、金融分析场景及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/k-chart/" version: "5.4.8" --- candlestick ## K 线图的简介 K 线图,原名蜡烛图,又称阴阳图、棒线、红黑线或蜡烛线,常用于展示股票交易数据。K 线就是指将各种股票每日、每周、每月的开盘价、收盘价、最高价、最低价等涨跌变化状况,用图形的方式表现出来。 K 线图通过直观的视觉表现方式,能够快速反映某一时期的价格走势,帮助分析师和投资者判断市场趋势。每根 K 线包含四个关键价格信息:开盘价、收盘价、最高价和最低价,这四个价格构成了完整的交易信息。 K 线图的设计理念源于日本,最初用于大米期货交易,后来被广泛应用于各种金融市场的技术分析中。 **英文名**:Candlestick Chart, K Chart ## K 线图的构成 ### 单根 K 线结构 candlestick-structure K 线如图所示: 1. **上影线**:最上方的一条细线,表示最高价和收盘价(或开盘价)之间的价差 2. **实体**:中间的一条粗线,表示开盘价和收盘价之间的价差 3. **下影线**:下面的一条细线,表示开盘价(或收盘价)和最低价之间的差距 当收盘价高于开盘价,也就是股价走势呈上升趋势时,我们称这种情况下的 K 线为阳线,中部的实体通常以红色或空白表示。反之称为阴线,用绿色或黑色实体表示。 | 图表类型 | K 线图 | | ---------------- | ------------------------------------------------------------------------------------- | | 适合的数据 | 时间序列:一个时间字段,四个数值字段(开盘价、收盘价、最高价、最低价) | | 功能 | 观察数据的**趋势**变化
**对比**不同时期的价格区间 | | 数据与图形的映射 | 时间字段映射到横轴
四个价格字段映射到纵轴形成 K 线实体和影线
涨跌状态映射到颜色 | | 适合的数据条数 | 适合中长期数据,通常几十到几百个交易周期 | ## K 线图的应用场景 ### 适合的场景 **场景 1:股票价格分析** K 线图最经典的应用场景就是股票市场分析,通过观察 K 线的形态可以判断市场的多空力量对比。 ```ts 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, }, { time: '2015-11-13', start: 8.01, max: 8.75, min: 7.97, end: 8.41, }, { time: '2015-11-12', start: 7.76, max: 8.18, min: 7.61, end: 8.15, }, { time: '2015-11-11', start: 7.55, max: 7.81, min: 7.49, end: 7.8, }, { time: '2015-11-10', start: 7.5, max: 7.68, min: 7.44, end: 7.57, }, ]; const tooltip = { title: 'time', items: [ { field: 'start', name: '开盘价' }, { field: 'end', name: '收盘价' }, { field: 'min', name: '最低价' }, { field: 'max', name: '最高价' }, ], }; chart.options({ type: 'view', data, encode: { x: 'time', color: (d) => { const trend = Math.sign(d.start - d.end); return trend > 0 ? '下跌' : trend === 0 ? '不变' : '上涨'; }, }, scale: { x: { compare: (a, b) => new Date(a).getTime() - new Date(b).getTime(), }, color: { domain: ['下跌', '不变', '上涨'], range: ['#4daf4a', '#999999', '#e41a1c'], }, }, children: [ { type: 'link', encode: { y: ['min', 'max'] }, tooltip, }, { type: 'interval', encode: { y: ['start', 'end'] }, style: { fillOpacity: 1, stroke: (d) => { if (d.start === d.end) return '#999999'; }, }, axis: { y: { title: '价格', }, }, tooltip, }, ], }); chart.render(); ``` **场景 2:商品期货分析** K 线图同样适用于商品期货、外汇等金融衍生品的价格分析,帮助交易者识别价格模式和趋势。 **场景 3:数字货币分析** 在数字货币交易中,K 线图是最常用的技术分析工具,可以清楚地展示价格的波动情况。 ### 不适合的场景 **场景 1:非时间序列数据** K 线图专门用于时间序列数据,对于不涉及时间维度的分类数据比较,应该使用[柱状图](/zh/charts/bar/)或[条形图](/zh/charts/bar/)。 **场景 2:单一数值展示** 如果只需要展示简单的数值变化趋势,[折线图](/zh/charts/line/)或[面积图](/zh/charts/area/)会更加简洁明了。 ## K 线图的扩展 ### K 线图与成交量组合 将 K 线图与成交量柱状图结合,可以更全面地分析市场行为。 ```ts import { Chart } from '@antv/g2'; // 创建K线图容器 document.getElementById('container').innerHTML = `
`; const data = [ { time: '2015-11-19', start: 8.18, max: 8.33, min: 7.98, end: 8.32, volumn: 1810, }, { time: '2015-11-18', start: 8.37, max: 8.6, min: 8.03, end: 8.09, volumn: 2790, }, { time: '2015-11-17', start: 8.7, max: 8.78, min: 8.32, end: 8.37, volumn: 3729, }, { time: '2015-11-16', start: 8.48, max: 8.85, min: 8.43, end: 8.7, volumn: 2890, }, ]; const KChart = new Chart({ container: 'kChart', autoFit: true, }); KChart.options({ type: 'view', data, encode: { x: 'time', color: (d) => { const trend = Math.sign(d.start - d.end); return trend > 0 ? '下跌' : trend === 0 ? '不变' : '上涨'; }, }, scale: { color: { domain: ['下跌', '不变', '上涨'], range: ['#4daf4a', '#999999', '#e41a1c'], }, }, children: [ { type: 'link', encode: { y: ['min', 'max'] }, }, { type: 'interval', encode: { y: ['start', 'end'] }, style: { fillOpacity: 1 }, }, ], axis: { y: { title: '价格' }, }, }); // 成交量图 const VolumeChart = new Chart({ container: 'volumeChart', autoFit: true, }); VolumeChart.options({ type: 'interval', data, encode: { x: 'time', y: 'volumn', color: (d) => { const trend = Math.sign(d.start - d.end); return trend > 0 ? '下跌' : trend === 0 ? '不变' : '上涨'; }, }, scale: { color: { domain: ['下跌', '不变', '上涨'], range: ['#4daf4a', '#999999', '#e41a1c'], }, }, axis: { y: { title: '成交量' }, }, }); KChart.render(); VolumeChart.render(); ``` **说明**: - K 线图部分展示价格的四个关键信息(开盘、收盘、最高、最低) - 成交量图部分使用相同的颜色编码展示交易量 - 两个图表通过相同的时间轴对齐,便于关联分析 ### K 线图与柱状图组合 使用更完整的数据源,结合 K 线图和柱状图展示股票价格和成交量的关系,并支持图例联动交互。 ```ts import { Chart } from '@antv/g2'; // 创建K线图和柱状图容器 document.getElementById('container').innerHTML = `
`; const KChart = new Chart({ container: 'kChart', autoFit: true, height: 360, paddingLeft: 60, }); KChart.options({ type: 'view', data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/candle-sticks.json', }, encode: { x: 'time', color: (d) => { const trend = Math.sign(d.start - d.end); return trend > 0 ? '下跌' : trend === 0 ? '不变' : '上涨'; }, }, scale: { x: { compare: (a, b) => new Date(a).getTime() - new Date(b).getTime(), }, color: { domain: ['下跌', '不变', '上涨'], range: ['#4daf4a', '#999999', '#e41a1c'], }, }, children: [ { type: 'link', encode: { y: ['min', 'max'] }, tooltip: { title: 'time', items: [ { field: 'start', name: '开盘价' }, { field: 'end', name: '收盘价' }, { field: 'min', name: '最低价' }, { field: 'max', name: '最高价' }, ], }, }, { type: 'interval', encode: { y: ['start', 'end'] }, style: { fillOpacity: 1, stroke: (d) => (d.start === d.end ? '#999999' : undefined), }, axis: { x: { title: false }, y: { title: false }, }, tooltip: { title: 'time', items: [ { field: 'start', name: '开盘价' }, { field: 'end', name: '收盘价' }, { field: 'min', name: '最低价' }, { field: 'max', name: '最高价' }, ], }, }, ], }); const ColumnChart = new Chart({ container: 'columnChart', autoFit: true, paddingTop: 0, paddingBottom: 0, height: 180, paddingLeft: 60, }); ColumnChart.options({ type: 'interval', data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/candle-sticks.json', }, encode: { x: 'time', y: 'volumn', color: (d) => { const trend = Math.sign(d.start - d.end); return trend > 0 ? '下跌' : trend === 0 ? '不变' : '上涨'; }, }, scale: { x: { compare: (a, b) => new Date(a).getTime() - new Date(b).getTime(), }, color: { domain: ['下跌', '不变', '上涨'], range: ['#4daf4a', '#999999', '#e41a1c'], }, }, axis: { x: false, y: { title: false }, }, }); // 图例联动交互 KChart.on('legend:filter', (e) => { const { nativeEvent, data } = e; if (!nativeEvent) return; ColumnChart.emit('legend:filter', { data }); }); KChart.on('legend:reset', (e) => { const { nativeEvent, data } = e; if (!nativeEvent) return; ColumnChart.emit('legend:reset', { data }); }); KChart.render(); ColumnChart.render(); ``` **说明**: - 上方 K 线图展示价格走势,下方柱状图展示成交量 - 两个图表使用相同的颜色编码和时间轴对齐 - 支持图例联动,点击图例可以同时过滤两个图表的数据 - 使用真实的股票数据源,展示更完整的市场信息 ## K 线图与其他图表的对比 ### K 线图和[折线图](/zh/charts/line/) - K 线图展示完整的价格信息(开盘、收盘、最高、最低),适合详细的技术分析 - 折线图只展示单一价格线(通常是收盘价),适合展示价格趋势的整体走向 ### K 线图和[柱状图](/zh/charts/bar/) - K 线图专门用于时间序列的价格数据分析,强调时间维度上的价格变化 - 柱状图主要用于不同分类之间的数值大小对比,不涉及时间序列 ### K 线图和[箱形图](/zh/charts/boxplot/) - K 线图展示特定时间点的价格区间,侧重于价格走势分析 - 箱形图展示数据的统计分布特征,侧重于数据分布的统计分析 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 散点图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/scatter-plot/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/scatter-plot.md - Description: 将两个数值变量映射到平面位置,观察相关性、聚类与异常点,了解散点图的使用场景及 G2 示例。 --- title: "散点图" description: "将两个数值变量映射到平面位置,观察相关性、聚类与异常点,了解散点图的使用场景及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/scatter-plot/" version: "5.4.8" --- scatter ## 散点图的简介 散点图是一种通过二维坐标平面上的点来展示两个连续变量之间关系的可视化图表。每个数据点的位置由两个变量的值决定,其中一个变量确定水平位置(x 轴),另一个变量确定垂直位置(y 轴)。 散点图有别于[折线图](/zh/charts/line/),散点图主要用于探索和展示变量间的相关性、分布模式以及异常值的识别,而折线图更适合展示连续数据的趋势变化。 ![散点图变量关系示例](https://t.alipayobjects.com/images/T1xypjXj4bXXXXXXXX.png) 通过观察散点图上数据点的分布情况,我们可以推断出变量间的相关性。如果变量之间不存在相互关系,那么在散点图上就会表现为随机分布的离散的点,如果存在某种相关性,那么大部分数据点就会相对密集并以某种趋势呈现。数据的相关关系主要分为:正相关(两个变量值同时增长)、负相关(一个变量值增加另一个变量值下降)、不相关、线性相关、指数相关等。 **英文名**:Scatter plot, Scatter chart ## 散点图的构成 scatter-components | 图表类型 | 散点图 | | ---------------- | -------------------------------------------------------------------------------------------------------- | | 适合的数据 | 列表:两个连续数据字段 | | 功能 | 探索两个变量之间的相关性,识别数据模式和异常值 | | 数据与图形的映射 | 第一个连续数据字段映射到横轴位置
第二个连续数据字段映射到纵轴位置
可选分类字段映射到点的颜色或大小 | | 适合的数据条数 | 10-1000 条数据,数据过多时可考虑采样或使用密度图 | **组成元素:** 1. **数据点(Points)**:代表数据集中每个观测值的圆点或其他形状 2. **X 轴(X-axis)**:水平坐标轴,通常表示自变量 3. **Y 轴(Y-axis)**:垂直坐标轴,通常表示因变量 4. **坐标网格(Grid)**:辅助读数的参考线 5. **图例(Legend)**:当有多个数据系列时,说明不同点的含义 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); chart.options({ type: 'point', autoFit: true, data: [ { height: 161, weight: 50 }, { height: 167, weight: 55 }, { height: 171, weight: 63 }, { height: 174, weight: 58 }, { height: 176, weight: 65 }, { height: 178, weight: 70 }, { height: 180, weight: 72 }, { height: 182, weight: 75 }, { height: 185, weight: 78 }, { height: 188, weight: 82 }, ], encode: { x: 'height', y: 'weight' }, scale: { x: { range: [0, 1] }, y: { domainMin: 0, nice: true } }, axis: { x: { title: '身高 (cm)' }, y: { title: '体重 (kg)' }, }, style: { fill: '#1890ff', fillOpacity: 0.7, stroke: '#1890ff', strokeWidth: 2, r: 6, }, }); chart.render(); ``` ## 散点图的应用场景 ### 适合的场景 散点图特别适合展示数据分布和变量间的相关性。以下通过四个渐进的例子来展示散点图的不同应用: #### 1. 基础散点图 - 展示数据分布 最基础的散点图用于展示两个连续变量的分布关系: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); chart.options({ type: 'point', data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/top-30-countries-by-quality-of-life.json', }, encode: { x: 'x', y: 'y' }, scale: { x: { domain: [137.5, 212] }, y: { domain: [0, 80] } }, labels: [{ text: 'name', fontSize: 10, dy: 6 }], style: { mainStroke: '#5B8FF9', mainLineWidth: 2 }, }); chart.render(); ``` #### 2. 添加数据标注 - 增强可读性 通过添加数据标注,例如线标注、区间标注来帮助用户更好地理解数据: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); chart.options({ type: 'view', data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/top-30-countries-by-quality-of-life.json', }, style: { mainStroke: '#5B8FF9', mainLineWidth: 2 }, axis: { x: false, y: false }, children: [ { type: 'range', data: [ { x: [0, 0.5], y: [0, 0.5] }, { x: [0.5, 1], y: [0.5, 1] }, ], encode: { x: 'x', y: 'y' }, scale: { x: { independent: true, domain: [0, 1] }, y: { independent: true, domain: [0, 1] }, }, style: { stroke: '#5B8FF9', lineWidth: 1, fillOpacity: 0.15 }, animate: false, tooltip: false, }, { type: 'point', encode: { x: 'x', y: 'y', shape: 'point' }, scale: { x: { domain: [137.5, 212] }, y: { domain: [0, 80] } }, labels: [{ text: 'name', fontSize: 10, dy: 6 }], }, ], }); chart.render(); ``` #### 3. 使用颜色通道 - 展示分类信息 通过颜色通道来区分不同类别的数据: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); chart.options({ type: 'point', autoFit: true, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/basement_prod/6b4aa721-b039-49b9-99d8-540b3f87d339.json', }, encode: { x: 'height', y: 'weight', color: 'gender' }, }); chart.render(); ``` #### 4. 添加回归线 - 显示趋势关系 还可以通过自定义数据转换函数来对数据进行预处理,添加回归线来显示变量间的趋势关系: ```ts import { Chart } from '@antv/g2'; import { regressionLinear } from 'd3-regression'; const chart = new Chart({ container: 'container' }); chart.options({ type: 'view', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/linear-regression.json', }, children: [ { type: 'point', encode: { x: (d) => d[0], y: (d) => d[1], shape: 'point' }, scale: { x: { domain: [0, 1] }, y: { domain: [0, 5] } }, style: { fillOpacity: 0.75 }, }, { type: 'line', data: { transform: [ { type: 'custom', callback: regressionLinear(), }, ], }, 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, }, ], }); chart.render(); ``` ### 不适合的场景 #### 数据点重叠严重的情况 当数据在某些位置高度重叠时,普通散点图无法清晰展示数据的真实分布。例如下图展示了一个存在严重重叠问题的散点图: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); chart.options({ type: 'point', autoFit: true, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/2c813e2d-2276-40b9-a9af-cf0a0fb7e942.csv', }, encode: { y: 'Horsepower', x: 'Cylinders', shape: 'hollow', color: 'Cylinders', }, transform: [{ type: 'sortX', channel: 'x' }], scale: { x: { type: 'point' }, color: { type: 'ordinal' } }, }); chart.render(); ``` **解决方案:使用抖动变换(Jitter)** 通过添加随机偏移来避免重叠,使数据分布更加清晰: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); chart.options({ type: 'point', autoFit: true, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/2c813e2d-2276-40b9-a9af-cf0a0fb7e942.csv', }, encode: { y: 'Horsepower', x: 'Cylinders', shape: 'hollow', color: 'Cylinders', }, transform: [{ type: 'sortX', channel: 'x' }, { type: 'jitterX' }], scale: { x: { type: 'point' }, color: { type: 'ordinal' } }, }); chart.render(); ``` 例子 2: **不适合展示分类数据的对比** 散点图不适合用于展示分类数据的数值对比,如下图试图用散点图展示不同产品的销量: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'point', autoFit: true, data: [ { product: '产品A', sales: 275 }, { product: '产品B', sales: 115 }, { product: '产品C', sales: 120 }, { product: '产品D', sales: 350 }, { product: '产品E', sales: 150 }, ], encode: { x: 'product', y: 'sales' }, scale: { x: { range: [0, 1] }, y: { domainMin: 0, nice: true } }, axis: { x: { title: '产品类型' }, y: { title: '销量' }, }, title: '不适合的用法:用散点图展示分类数据', style: { fill: '#1890ff', fillOpacity: 0.8, stroke: '#1890ff', strokeWidth: 2, r: 8, }, }); chart.render(); ``` 对于分类数据的对比,更适合使用[柱状图](/zh/charts/bar/): ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'interval', autoFit: true, data: [ { product: '产品A', sales: 275 }, { product: '产品B', sales: 115 }, { product: '产品C', sales: 120 }, { product: '产品D', sales: 350 }, { product: '产品E', sales: 150 }, ], encode: { x: 'product', y: 'sales', color: 'product' }, axis: { x: { title: '产品类型' }, y: { title: '销量' }, }, title: '更适合的选择:用柱状图展示分类数据', }); chart.render(); ``` ## 散点图的扩展 ### 带趋势线的散点图 添加回归线来更清晰地展示数据趋势: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); const data = [ { x: 1, y: 2.1 }, { x: 2, y: 3.8 }, { x: 3, y: 5.2 }, { x: 4, y: 6.9 }, { x: 5, y: 8.1 }, { x: 6, y: 9.8 }, { x: 7, y: 11.2 }, { x: 8, y: 13.1 }, { x: 9, y: 14.8 }, { x: 10, y: 16.5 }, ]; chart.options({ type: 'view', autoFit: true, data, encode: { x: 'x', y: 'y' }, scale: { x: { range: [0, 1] }, y: { domainMin: 0, nice: true } }, children: [ { type: 'point', style: { fill: '#1890ff', fillOpacity: 0.8, stroke: '#1890ff', strokeWidth: 2, r: 6, }, }, { type: 'line', style: { stroke: '#ff4d4f', strokeWidth: 2, strokeDasharray: [4, 4], }, }, ], axis: { x: { title: 'X 变量' }, y: { title: 'Y 变量' }, }, title: '带趋势线的散点图', }); chart.render(); ``` ### 带标签的散点图 为重要数据点添加文本标签,提升数据可读性: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); const data = [ { product: '产品A', satisfaction: 8.5, sales: 120, category: '科技' }, { product: '产品B', satisfaction: 7.2, sales: 85, category: '家居' }, { product: '产品C', satisfaction: 9.1, sales: 200, category: '科技' }, { product: '产品D', satisfaction: 6.8, sales: 95, category: '服装' }, { product: '产品E', satisfaction: 8.9, sales: 160, category: '科技' }, { product: '产品F', satisfaction: 7.5, sales: 110, category: '家居' }, { product: '产品G', satisfaction: 6.2, sales: 70, category: '服装' }, { product: '产品H', satisfaction: 8.7, sales: 185, category: '科技' }, ]; chart.options({ type: 'view', autoFit: true, data, encode: { x: 'satisfaction', y: 'sales', color: 'category' }, children: [ { type: 'point', style: { r: 8, fillOpacity: 0.8, strokeWidth: 2, }, }, { type: 'text', encode: { text: 'product' }, style: { fontSize: 10, textAlign: 'center', dy: -12, fontWeight: 'bold', }, }, ], axis: { x: { title: '客户满意度' }, y: { title: '销量 (万件)' }, }, legend: { color: { position: 'top' } }, title: '产品满意度 vs 销量分析', }); chart.render(); ``` ### 分组对比散点图 使用 facet 来创建分面散点图,便于不同组间的对比分析: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); const data = [ // 2020年数据 { year: 2020, quarter: 'Q1', revenue: 120, profit: 15, company: '公司A' }, { year: 2020, quarter: 'Q2', revenue: 135, profit: 18, company: '公司A' }, { year: 2020, quarter: 'Q3', revenue: 145, profit: 22, company: '公司A' }, { year: 2020, quarter: 'Q4', revenue: 160, profit: 25, company: '公司A' }, { year: 2020, quarter: 'Q1', revenue: 95, profit: 12, company: '公司B' }, { year: 2020, quarter: 'Q2', revenue: 110, profit: 14, company: '公司B' }, { year: 2020, quarter: 'Q3', revenue: 125, profit: 18, company: '公司B' }, { year: 2020, quarter: 'Q4', revenue: 140, profit: 21, company: '公司B' }, // 2021年数据 { year: 2021, quarter: 'Q1', revenue: 170, profit: 28, company: '公司A' }, { year: 2021, quarter: 'Q2', revenue: 185, profit: 32, company: '公司A' }, { year: 2021, quarter: 'Q3', revenue: 195, profit: 35, company: '公司A' }, { year: 2021, quarter: 'Q4', revenue: 210, profit: 38, company: '公司A' }, { year: 2021, quarter: 'Q1', revenue: 150, profit: 23, company: '公司B' }, { year: 2021, quarter: 'Q2', revenue: 165, profit: 26, company: '公司B' }, { year: 2021, quarter: 'Q3', revenue: 175, profit: 29, company: '公司B' }, { year: 2021, quarter: 'Q4', revenue: 190, profit: 32, company: '公司B' }, ]; chart.options({ type: 'facetRect', autoFit: true, margin: 30, data, encode: { x: 'year' }, children: [ { type: 'point', encode: { x: 'revenue', y: 'profit', color: 'company', shape: 'quarter', }, scale: { shape: { range: ['circle', 'square', 'triangle', 'diamond'] }, x: { nice: true }, y: { nice: true }, }, style: { r: 6, fillOpacity: 0.8, stroke: '#fff', strokeWidth: 1, }, }, ], axis: { x: { title: '营收 (万元)' }, y: { title: '利润 (万元)' }, }, legend: { color: { position: 'top' }, shape: { position: 'right' }, }, title: '公司营收利润关系年度对比', }); chart.render(); ``` ### 矩阵散点图 使用多个散点图组成矩阵,展示多变量间的相关性: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); // 生成相关性数据 const generateCorrelationData = () => { const data = []; const variables = ['销售额', '广告投入', '客户满意度']; for (let i = 0; i < 50; i++) { const sales = 100 + Math.random() * 200; const advertising = sales * 0.1 + Math.random() * 20; const satisfaction = 6 + (sales - 100) / 50 + Math.random() * 2; data.push({ id: i, 销售额: sales, 广告投入: advertising, 客户满意度: Math.min(10, Math.max(1, satisfaction)), }); } return data; }; const rawData = generateCorrelationData(); // 转换数据为矩阵格式 const matrixData = []; const variables = ['销售额', '广告投入', '客户满意度']; variables.forEach((xVar) => { variables.forEach((yVar) => { rawData.forEach((d) => { matrixData.push({ xVar, yVar, x: d[xVar], y: d[yVar], id: d.id, }); }); }); }); const axisStyle = { title: false, labelFormatter: (d) => (typeof d === 'number' ? d.toFixed(0) : d), }; chart.options({ type: 'facetRect', autoFit: true, margin: 30, data: matrixData, encode: { x: 'xVar', y: 'yVar' }, children: [ { type: 'point', encode: { x: 'x', y: 'y' }, style: { r: 3, fillOpacity: 0.6, fill: '#1890ff', stroke: '#fff', strokeWidth: 0.5, }, scale: { x: { nice: true }, y: { nice: true }, }, }, ], axis: { x: axisStyle, y: axisStyle, }, title: '多变量相关性矩阵散点图', }); chart.render(); ``` ## 散点图与其他图表类型的对比 | 图表类型 | 适用场景 | 优势 | 劣势 | | -------- | -------------------------- | ---------------------------- | ------------------------ | | 散点图 | 探索两变量关系、异常值检测 | 直观展示相关性、易于识别模式 | 数据点过多时可能重叠 | | 折线图 | 时间序列数据、趋势展示 | 清晰显示变化趋势 | 不适合展示变量间相关性 | | 柱状图 | 分类数据比较 | 便于比较不同类别 | 无法展示连续变量关系 | | 气泡图 | 三维数据展示 | 信息量更大 | 复杂度较高,解读难度增加 | ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 仪表盘 - Canonical page: https://g2.antv.antgroup.com/zh/charts/gauge/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/gauge.md - Description: 在刻度范围内展示当前指标及其所处区间,了解仪表盘图的组成、监控场景和 G2 示例。 --- title: "仪表盘" description: "在刻度范围内展示当前指标及其所处区间,了解仪表盘图的组成、监控场景和 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/gauge/" version: "5.4.8" --- gauge ## 仪表盘的简介 仪表盘是一种半圆或圆形的可视化图表,用于展示数据在指定范围内的位置。它通过指针和刻度的方式,模拟物理仪表(如速度表、压力表)的显示效果,直观地表达数据值相对于目标值或阈值的位置关系。 仪表盘特别适合显示进度、完成率或者在一定范围内的单一数值,可以通过颜色区分不同的数值区间,增强数据的可读性和警示效果。 在数据可视化中,仪表盘常用于表现关键绩效指标(KPI)、目标达成情况、系统状态监控等,能够帮助用户快速理解数据在预设目标中的表现。 **英文名**:Gauge Chart, Dial Chart ## 仪表盘的构成 ### 基础仪表盘 basic-gauge | 图表类型 | 基础仪表盘 | | ---------------- | -------------------------------------------------------------------- | | 适合的数据 | 单一数值数据:当前值、目标值或总值、可选的阈值 | | 功能 | 展示数据在指定范围内的位置,通过指针和刻度表示数值 | | 数据与图形的映射 | 当前值映射到指针位置
总值定义刻度范围
可选的阈值映射为颜色分区 | | 适合的场景 | 表达单一指标在目标值范围内的完成度或状态 | ## 仪表盘的应用场景 ### 适合的场景 例子 1: **适合展示目标完成进度** 下面的仪表盘展示了一个评分指标的完成情况,当前得分为 120,总分为 400。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', autoFit: true, }); chart.options({ type: 'gauge', data: { value: { target: 120, total: 400, name: 'score', }, }, legend: false, }); chart.render(); ``` **说明** : - `target` 表示当前的得分,映射到仪表盘的指针位置 - `total` 表示总分,定义了仪表盘的刻度范围 - `name` 表示该指标的名称 - 仪表盘通过指针位置直观展示了当前得分在总分中的比例 例子 2: **适合展示多阈值状态监控** 仪表盘可以通过设置多个阈值和不同颜色,清晰地展示数据落在哪个区间,适用于系统状态监控、性能评估等场景。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', autoFit: true, }); chart.options({ type: 'gauge', data: { value: { target: 159, total: 400, name: 'score', thresholds: [100, 200, 400], }, }, scale: { color: { range: ['#F4664A', '#FAAD14', 'green'], }, }, style: { textContent: (target, total) => `得分:${target}\n占比:${((target / total) * 100).toFixed(0)}%`, }, legend: false, }); chart.render(); ``` **说明**: - 通过 `thresholds` 设置了三个区间:0-100、100-200 和 200-400 - 使用不同的颜色映射了各个区间:红色表示低分区间,黄色表示中等区间,绿色表示高分区间 - 自定义了文本内容,同时显示得分和占总分的百分比 - 颜色的变化直观地反映了数据所处的状态区间 例子 3: **自定义仪表盘样式** 仪表盘支持高度的样式自定义,可以根据业务需求调整外观和交互方式。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', autoFit: true, }); chart.options({ type: 'gauge', data: { value: { target: 159, total: 400, name: 'score', thresholds: [100, 200, 400], }, }, scale: { color: { range: ['#F4664A', '#FAAD14', 'green'], }, }, style: { arcShape: 'round', arcLineWidth: 2, arcStroke: '#fff', textContent: (target, total) => `得分:${target}\n占比:${((target / total) * 100).toFixed(0)}%`, }, legend: false, }); chart.render(); ``` **说明**: - 使用 `arcShape: 'round'` 设置了圆弧的形状为圆角 - 通过 `arcLineWidth` 和 `arcStroke` 设置了圆弧的线宽和边框颜色 - 指针和文本的位置自动适应仪表盘的布局 - 圆角设计和细微的样式调整使仪表盘更加现代化和美观 ### 不适合的场景 例子 1: **不适合展示多维度数据比较** 仪表盘主要适合展示单一指标值在一定范围内的位置,不适合用于多维度数据的直接比较。如果需要比较多个类别或维度的数据,柱状图、雷达图等可能是更好的选择。 例子 2: **不适合展示时间序列趋势** 仪表盘展示的是某一时刻的静态数据,无法有效地表达数据随时间的变化趋势。对于需要展示随时间变化的数据,折线图或面积图更为适合。 ## 仪表盘的扩展 ### 自定义指针形状 仪表盘支持自定义指针的形状,可以根据业务场景调整指针的样式,使图表更具个性化。 ```ts import { Chart } from '@antv/g2'; import { Path } from '@antv/g'; const chart = new Chart({ container: 'container', autoFit: true, }); function getOrigin(points) { if (points.length === 1) return points[0]; const [[x0, y0, z0 = 0], [x2, y2, z2 = 0]] = points; return [(x0 + x2) / 2, (y0 + y2) / 2, (z0 + z2) / 2]; } // 自定义指针形状 const customShape = (style) => { return (points, value, coordinate, theme) => { // 获取几何点中心坐标 const [x, y] = getOrigin(points); const [cx, cy] = coordinate.getCenter(); // 计算指针方向角度 const angle = Math.atan2(y - cy, x - cx); const length = 100; // 指针长度 const width = 8; // 指针底部宽度 // 构造指针三角形路径 return new Path({ style: { d: [ ['M', cx + Math.cos(angle) * length, cy + Math.sin(angle) * length], // 顶点 [ 'L', cx + Math.cos(angle + Math.PI / 2) * width, cy + Math.sin(angle + Math.PI / 2) * width, ], // 底部左点 [ 'L', cx + Math.cos(angle - Math.PI / 2) * width, cy + Math.sin(angle - Math.PI / 2) * width, ], // 底部右点 ['Z'], // 闭合路径 ], fill: '#30BF78', // 填充色 }, }); }; }; chart.options({ type: 'gauge', data: { value: { target: 159, total: 424, name: 'score', }, }, style: { pointerShape: customShape, pinShape: false, textContent: (target, total) => { return `得分:${target}\n占比:${((target / total) * 100).toFixed(0)}%`; }, }, }); chart.render(); ``` **说明**: - 使用 `pointerShape` 自定义了仪表盘的指针形状为三角形 - 通过 `pinShape: false` 移除了指针中心的圆点 - 可以根据业务需求完全控制指针的外观、颜色和尺寸 ## 仪表盘与其他图表的对比 ### 仪表盘和[饼图](/zh/charts/pie/) - 仪表盘侧重展示单一数值在目标范围中的位置,强调数据与目标或阈值的关系 - 饼图侧重于展示多个部分在整体中的占比关系 - 当只关注单一值与总体的关系时,仪表盘提供更突出和直观的表现形式 ### 仪表盘和[柱状图](/zh/charts/bar/) - 仪表盘适合展示单一数据点相对于固定范围的位置,强调完成度 - 柱状图更适合比较多个类别之间的数值大小差异 - 对于需要精确比较多个数值的场景,柱状图通常是更好的选择 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 南丁格尔玫瑰图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/rose/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/rose.md - Description: 在等角扇区中用半径表达类别数值,了解南丁格尔玫瑰图与饼图的区别及 G2 示例。 --- title: "南丁格尔玫瑰图" description: "在等角扇区中用半径表达类别数值,了解南丁格尔玫瑰图与饼图的区别及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/rose/" version: "5.4.8" --- rose ## 南丁格尔玫瑰图的简介 南丁格尔玫瑰图(又名鸡冠花图、极坐标区域图),是南丁格尔在克里米亚战争期间提交的一份关于士兵死伤的报告时发明的一种图表。 南丁格尔玫瑰图是在极坐标下绘制的柱状图,使用圆弧的半径长短表示数据的大小(数量的多少)。 - 由于半径和面积的关系是平方的关系,南丁格尔玫瑰图会将数据的比例大小夸大,尤其适合对比大小相近的数值。 - 由于圆形有周期的特性,所以玫瑰图也适用于表示一个周期内的时间概念,比如星期、月份。 **其他名称**:Nightingale Rose Chart, Coxcomb Chart, Polar Area Diagram ## 南丁格尔玫瑰图的构成 rose-structure | 图表类型 | 南丁格尔玫瑰图 | | ---------------- | ----------------------------------------------------------------------------------------------------------- | | 适合的数据 | 列表:一个分类数据字段、一个连续数据字段 | | 功能 | 对比分类数据的数值大小 | | 数据与图形的映射 | 分类数据字段映射到分类轴的位置
连续数据字段映射到半径轴的高度
分类数据也可以设置颜色增强分类的区分度 | | 适合的数据条数 | 不超过 30 条数据 | ## 南丁格尔玫瑰图的应用场景 ### 适合的场景 例子 1: **对比不同分类的大小** 下图是各国制造指数的对比,以美国为基准(100),中国的制造成本指数是 96,也就是说,同样一件产品,在美国制造成本是 1 美元,那么在中国则需要 0.96 美元,从下图可以看出中国的制造优势已经不明显。 | country(国家) | cost(制造指数) | | --------------- | ---------------- | | 中国 | 96 | | 德国 | 121 | | 美国 | 100 | | 日本 | 111 | | 韩国 | 102 | | ... | ... | ```ts 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 }, { country: '韩国', cost: 102 }, { country: '法国', cost: 124 }, { country: '意大利', cost: 123 }, { country: '荷兰', cost: 111 }, { country: '比利时', cost: 123 }, { country: '英国', cost: 109 }, { country: '加拿大', cost: 115 }, { country: '俄罗斯', cost: 99 }, { country: '墨西哥', cost: 91 }, { country: '印度', cost: 87 }, { country: '瑞士', cost: 125 }, { country: '澳大利亚', cost: 130 }, { country: '西班牙', cost: 109 }, { country: '巴西', cost: 123 }, { country: '泰国', cost: 91 }, { country: '印尼', cost: 83 }, { country: '波兰', cost: 101 }, { country: '瑞典', cost: 116 }, { country: '奥地利', cost: 111 }, { country: '捷克', cost: 107 }, ], encode: { x: 'country', y: 'cost', color: 'country', }, scale: { y: { nice: true }, color: { palette: 'category20' }, }, axis: { y: { labelFormatter: null }, x: { grid: true }, }, interaction: [ { type: 'elementHighlight', background: true, }, ], }); chart.render(); ``` ### 不适合的场景 例子 1: **分类过少的场景** 下图展示一个班级男女同学的个数,这种场景下使用玫瑰图和饼图的对比。当分类很少时,饼图比玫瑰图更适合。 | gender(性别) | count(人数) | | -------------- | ------------- | | 男 | 40 | | 女 | 30 | #### 玫瑰图 ```ts import { Chart } from '@antv/g2'; const chart1 = new Chart({ container: 'container', theme: 'classic', }); chart1.options({ type: 'interval', autoFit: true, coordinate: { type: 'polar', }, data: [ { gender: '男', count: 40 }, { gender: '女', count: 30 }, ], encode: { x: 'gender', y: 'count', color: 'gender', }, scale: { y: { nice: true, min: 0 }, color: { range: ['#4F81BD', '#70AD47'] }, }, style: { stroke: 'white', lineWidth: 1, }, axis: { y: { grid: true, labelFormatter: (val) => `${val}`, }, }, title: '玫瑰图', }); chart1.render(); ``` #### 饼图 ```ts import { Chart } from '@antv/g2'; const chart2 = new Chart({ container: 'container', theme: 'classic', }); chart2.options({ type: 'interval', autoFit: true, coordinate: { type: 'theta' }, data: [ { gender: '男', count: 40 }, { gender: '女', count: 30 }, ], encode: { color: 'gender', y: 'count', }, style: { stroke: 'white', lineWidth: 1, }, title: '饼图', }); chart2.render(); ``` 例子 2: **分类数值相差悬殊的场景** 下面使用南丁格尔玫瑰图和横向柱状图分别展示各个省份的人口数据,这种场景下使用玫瑰图不合适,原因是在玫瑰图中数值相差悬殊的分类会导致较小数值的分类难以观察。推荐使用横向柱状图。 | province(省份) | population(人口数量) | | ---------------- | ---------------------- | | 北京市 | 19,612,368 | | 天津市 | 12,938,693 | | 河北省 | 71,854,210 | | 山西省 | 27,500,000 | | ... | ... | **南丁格尔玫瑰图(不推荐)** ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, coordinate: { type: 'polar' }, data: [ { province: '北京市', population: 19612368 }, { province: '天津市', population: 12938693 }, { province: '河北省', population: 71854210 }, { province: '山西省', population: 27500000 }, { province: '内蒙古自治区', population: 24706291 }, { province: '辽宁省', population: 43746323 }, { province: '吉林省', population: 27452815 }, { province: '黑龙江省', population: 38313991 }, { province: '上海市', population: 23019196 }, { province: '江苏省', population: 78660941 }, { province: '浙江省', population: 54426891 }, { province: '安徽省', population: 59500468 }, { province: '福建省', population: 36894217 }, { province: '江西省', population: 44567797 }, { province: '山东省', population: 95792719 }, { province: '河南省', population: 94029939 }, { province: '湖北省', population: 57237727 }, { province: '湖南省', population: 65700762 }, { province: '广东省', population: 104320459 }, { province: '广西壮族自治区', population: 46023761 }, { province: '海南省', population: 8671485 }, { province: '重庆市', population: 28846170 }, { province: '四川省', population: 80417528 }, { province: '贵州省', population: 34748556 }, { province: '云南省', population: 45966766 }, { province: '西藏自治区', population: 3002165 }, { province: '陕西省', population: 37327379 }, { province: '甘肃省', population: 25575263 }, { province: '青海省', population: 5626723 }, ], encode: { x: 'province', y: 'population', color: 'province', }, scale: { y: { nice: true }, color: { palette: 'category20' }, }, axis: { y: { labelFormatter: null }, x: { labelFormatter: (text) => text.length > 3 ? text.slice(0, 3) + '...' : text, labelRotate: Math.PI / 4, }, }, title: '南丁格尔玫瑰图(不推荐)', }); chart.render(); ``` **横向柱状图(推荐)** ```ts import { Chart } from '@antv/g2'; const data = [ { province: '北京市', population: 19612368 }, { province: '天津市', population: 12938693 }, { province: '河北省', population: 71854210 }, { province: '山西省', population: 27500000 }, { province: '内蒙古自治区', population: 24706291 }, { province: '辽宁省', population: 43746323 }, { province: '吉林省', population: 27452815 }, { province: '黑龙江省', population: 38313991 }, { province: '上海市', population: 23019196 }, { province: '江苏省', population: 78660941 }, { province: '浙江省', population: 54426891 }, { province: '安徽省', population: 59500468 }, { province: '福建省', population: 36894217 }, { province: '江西省', population: 44567797 }, { province: '山东省', population: 95792719 }, { province: '河南省', population: 94029939 }, { province: '湖北省', population: 57237727 }, { province: '湖南省', population: 65700762 }, { province: '广东省', population: 104320459 }, { province: '广西壮族自治区', population: 46023761 }, { province: '海南省', population: 8671485 }, { province: '重庆市', population: 28846170 }, { province: '四川省', population: 80417528 }, { province: '贵州省', population: 34748556 }, { province: '云南省', population: 45966766 }, { province: '西藏自治区', population: 3002165 }, { province: '陕西省', population: 37327379 }, { province: '甘肃省', population: 25575263 }, { province: '青海省', population: 5626723 }, ]; const sortedData = [...data].sort((a, b) => b.population - a.population); const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, coordinate: { transform: [{ type: 'transpose' }] }, data: sortedData.slice(0, 15), // 截取前15个,避免过多数据 encode: { x: 'population', y: 'province', color: 'province', }, scale: { x: { nice: true }, color: { palette: 'category20' }, }, axis: { x: { title: '人口数量', labelFormatter: (val) => { return (val / 1000000).toFixed(1) + 'M'; }, }, y: { title: '省份', labelFormatter: (text) => text.length > 5 ? text.slice(0, 5) + '...' : text, }, }, legend: false, title: '横向柱状图(推荐)', style: { fillOpacity: 0.85, }, animate: { enter: { type: 'growInX' }, }, }); chart.render(); ``` 例子 3: **部分分类数值过小的场景** 下图展示了某个班级的各科及格率,使用南丁格尔玫瑰图和横向柱状图分别展示。这种场景由于存在部分分类数值过小的情况,使用南丁格尔玫瑰图会导致图形扭曲,不易于观察数据的真实比例关系。 | 科目 | 及格率 | | ---- | ------ | | 数学 | 98% | | 英语 | 95% | | 物理 | 60% | | 化学 | 55% | | 生物 | 30% | | 地理 | 5% | #### 南丁格尔玫瑰图(不推荐) ```ts import { Chart } from '@antv/g2'; const chart1 = new Chart({ container: 'container', theme: 'classic', }); chart1.options({ type: 'interval', autoFit: true, coordinate: { type: 'polar' }, data: [ { subject: '数学', pass: 98 }, { subject: '英语', pass: 95 }, { subject: '物理', pass: 60 }, { subject: '化学', pass: 55 }, { subject: '生物', pass: 30 }, { subject: '地理', pass: 5 }, ], encode: { x: 'subject', y: 'pass', color: 'subject', }, scale: { y: { nice: true, min: 0 }, color: { range: ['#4F81BD', '#70AD47', '#F79646', '#A9A9A9', '#C0504D'] }, }, style: { stroke: 'white', lineWidth: 1, }, axis: { y: { grid: true, labelFormatter: (val) => `${val}`, }, }, title: '南丁格尔玫瑰图(不推荐)', }); chart1.render(); ``` #### 横向柱状图(推荐) ```ts import { Chart } from '@antv/g2'; const chart2 = new Chart({ container: 'container', theme: 'classic', }); chart2.options({ type: 'interval', autoFit: true, coordinate: { transform: [{ type: 'transpose' }] }, data: [ { subject: '数学', pass: 98 }, { subject: '英语', pass: 95 }, { subject: '物理', pass: 60 }, { subject: '化学', pass: 55 }, { subject: '生物', pass: 30 }, { subject: '地理', pass: 5 }, ], encode: { x: 'subject', y: 'pass', color: 'subject', }, scale: { y: { nice: true, min: 0 }, color: { range: ['#4F81BD', '#70AD47', '#F79646', '#A9A9A9', '#C0504D'] }, }, style: { stroke: 'white', lineWidth: 1, }, axis: { y: { grid: true, labelFormatter: (val) => `${val}`, }, }, title: '横向柱状图(推荐)', }); chart2.render(); ``` ## 南丁格尔玫瑰图的扩展 ### 扇形玫瑰图 通过设置极坐标的起始角度可以实现扇形南丁格尔玫瑰图: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, coordinate: { type: 'polar', startAngle: Math.PI, endAngle: Math.PI * (3 / 2), }, data: [ { country: '中国', cost: 96 }, { country: '德国', cost: 121 }, { country: '美国', cost: 100 }, { country: '日本', cost: 111 }, { country: '韩国', cost: 102 }, { country: '法国', cost: 124 }, { country: '意大利', cost: 123 }, { country: '荷兰', cost: 111 }, { country: '比利时', cost: 123 }, { country: '英国', cost: 109 }, { country: '加拿大', cost: 115 }, { country: '俄罗斯', cost: 99 }, ], encode: { x: 'country', y: 'cost', color: 'country', }, scale: { y: { nice: true }, color: { range: ['#fc8f48', '#ffd787'] }, }, style: { lineWidth: 1, stroke: '#fff', }, labels: [ { text: 'cost', style: { textAlign: 'center', fontSize: 10, fontWeight: 'bold', }, transform: [{ type: 'overlapDodgeY' }], }, ], }); chart.render(); ``` ### 层叠的玫瑰图 南丁格尔玫瑰图的实现原理是将柱状图在极坐标下绘制,如果将柱状图扩展为层叠柱状图,同样可以实现层叠的玫瑰图。 下图是近十年来难民数据,其中大致可分为跨越国境的难民(refugees),未跨越国境的境内流离失所者(internally displaced persons)和尚未取得难民身份的寻求庇护者(asylum-seekers)。 | year(年份) | internally(境内流离失所者) | refugees(跨越国境的难民) | seekers(未取得难民身份) | | ------------ | ---------------------------- | -------------------------- | ------------------------- | | 2000 | 21.0 | 16 | 0.8 | | 2001 | 25.0 | 16 | 0.8 | | ... | ... | ... | ... | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, coordinate: { type: 'polar', innerRadius: 0.1, }, data: [ { year: '2000', type: '境内流离失所者', count: 21.0 }, { year: '2000', type: '跨越国境的难民', count: 16 }, { year: '2000', type: '未取得难民身份', count: 0.8 }, { year: '2001', type: '境内流离失所者', count: 25.0 }, { year: '2001', type: '跨越国境的难民', count: 16 }, { year: '2001', type: '未取得难民身份', count: 0.8 }, { year: '2002', type: '境内流离失所者', count: 25.0 }, { year: '2002', type: '跨越国境的难民', count: 15 }, { year: '2002', type: '未取得难民身份', count: 0.8 }, { year: '2003', type: '境内流离失所者', count: 25.0 }, { year: '2003', type: '跨越国境的难民', count: 14 }, { year: '2003', type: '未取得难民身份', count: 0.7 }, { year: '2004', type: '境内流离失所者', count: 25.0 }, { year: '2004', type: '跨越国境的难民', count: 14 }, { year: '2004', type: '未取得难民身份', count: 0.7 }, { year: '2005', type: '境内流离失所者', count: 24.0 }, { year: '2005', type: '跨越国境的难民', count: 13 }, { year: '2005', type: '未取得难民身份', count: 0.8 }, { year: '2006', type: '境内流离失所者', count: 24.0 }, { year: '2006', type: '跨越国境的难民', count: 14 }, { year: '2006', type: '未取得难民身份', count: 0.7 }, { year: '2007', type: '境内流离失所者', count: 26.0 }, { year: '2007', type: '跨越国境的难民', count: 16 }, { year: '2007', type: '未取得难民身份', count: 0.7 }, { year: '2008', type: '境内流离失所者', count: 26.0 }, { year: '2008', type: '跨越国境的难民', count: 15.2 }, { year: '2008', type: '未取得难民身份', count: 0.8 }, { year: '2009', type: '境内流离失所者', count: 27.1 }, { year: '2009', type: '跨越国境的难民', count: 15.2 }, { year: '2009', type: '未取得难民身份', count: 1.0 }, { year: '2010', type: '境内流离失所者', count: 27.5 }, { year: '2010', type: '跨越国境的难民', count: 15.4 }, { year: '2010', type: '未取得难民身份', count: 0.8 }, { year: '2011', type: '境内流离失所者', count: 26.4 }, { year: '2011', type: '跨越国境的难民', count: 15.2 }, { year: '2011', type: '未取得难民身份', count: 0.9 }, { year: '2012', type: '境内流离失所者', count: 28.8 }, { year: '2012', type: '跨越国境的难民', count: 15.4 }, { year: '2012', type: '未取得难民身份', count: 0.9 }, { year: '2013', type: '境内流离失所者', count: 33.3 }, { year: '2013', type: '跨越国境的难民', count: 16.7 }, { year: '2013', type: '未取得难民身份', count: 1.2 }, { year: '2014', type: '境内流离失所者', count: 38.2 }, { year: '2014', type: '跨越国境的难民', count: 19.5 }, { year: '2014', type: '未取得难民身份', count: 1.8 }, ], encode: { x: 'year', y: 'count', color: 'type', }, transform: [{ type: 'stackY' }], scale: { color: { range: ['rgb(136,186,174)', 'rgb(184,189,61)', 'rgb(107,136,138)'], }, }, style: { stroke: 'white', lineWidth: 1, }, }); chart.render(); ``` ## 南丁格尔玫瑰图与其他图表的对比 ### 南丁格尔玫瑰图与柱状图 - 南丁格尔玫瑰图本质上是柱状图在极坐标上的展示 - 柱状图使用矩形的高度表示数值的大小 - 南丁格尔玫瑰图使用扇形的半径大小表示数值的大小 ### 南丁格尔玫瑰图与饼图 - 南丁格尔玫瑰图使用扇形的半径表示数值的大小,饼图使用扇形的弧度表示数据的大小 - 南丁格尔玫瑰图可以同时表现的分类多于饼图 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 等高线图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/contourline/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/contourline.md - Description: 用等值线表达二维空间中连续数值的分布,了解等高线图在地形和密度分析中的应用及 G2 示例。 --- title: "等高线图" description: "用等值线表达二维空间中连续数值的分布,了解等高线图在地形和密度分析中的应用及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/contourline/" version: "5.4.8" --- contourline ## 等高线图的简介 等高线图是通过连接地图上相同数值的点得到的闭合曲线图。最常见的是地形图上海拔高度相等的各点所连成的等高线,在等高线上标注的数字为该等高线的海拔高度。等高线按其作用不同,可分为首曲线、计曲线、间曲线与助曲线四种。 除地形图之外,等高线也广泛应用于数据可视化中,用于显示二维平面上的三维数据分布,如温度分布、气压分布、概率密度分布等。等高线一般不相交,但有时可能会重合。在同一等高线上的各点数值相同。在等高线稀疏的地方,数值变化较缓;而在等高线稠密的地方,数值变化较陡。 **英文名**:Contour Line, Contour Plot ## 等高线图的构成 contourline-structure | 图表类型 | 等高线图 | | ---------------- | -------------------------------------------------------------------------------------------------------------- | | 适合的数据 | 三维数据:两个连续变量(X、Y 坐标)和一个数值变量(Z 值) | | 功能 | 显示二维平面上连续数据的分布模式和数值变化 | | 数据与图形的映射 | X 坐标映射到横轴位置
Y 坐标映射到纵轴位置
Z 值通过等高线的层次和标注显示
可选择性地用颜色表示数值大小 | | 适合的数据条数 | 连续的二维网格数据,数据点越多,等高线越平滑 | ## 等高线图的应用场景 ### 适合的场景 - **地形表示**:最经典的应用,用等高线表示海拔高度,清晰展示山脉、谷地等地形特征。 - **气象数据**:显示温度分布、气压分布、降雨量分布等气象要素的空间变化。 - **科学研究**:在物理学、化学、生物学等领域显示电场、磁场、浓度等二维分布。 - **商业分析**:展示市场份额、人口密度、消费水平等在地理空间上的分布。 - **概率密度**:在统计学中显示二维概率密度函数的分布形态。 ### 不适合的场景 - **离散数据**:等高线需要连续的数值分布,不适合显示离散的分类数据。 - **时间序列**:等高线主要用于空间分布,不适合显示时间维度的变化。 - **精确数值读取**:虽然可以标注数值,但等高线主要用于显示趋势和模式,不适合精确读取具体数值。 - **数据点稀疏**:当数据点过于稀疏时,插值生成的等高线可能不够准确。 以下是一个典型的等高线图应用场景: **地形海拔分布图。** 下图展示了某山区的海拔高度分布,通过等高线可以直观地看出山峰、山谷和坡度的分布情况。等高线密集的地方表示坡度陡峭,等高线稀疏的地方表示地势平缓。 ```ts 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', strokeWidth: 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(); ``` 这个例子展示了: 1. 使用网格单元格(cell)模拟等高线效果,显示地形海拔分布 2. 通过颜色渐变表示不同的海拔高度,形成类似等高线的视觉效果 3. 提供交互式的悬停提示显示具体位置和海拔信息 4. 使用合适的色彩映射(viridis 色板)来增强视觉效果 5. 添加描边来突出网格边界,增强等高线的视觉感知 ## 等高线图的扩展 ### 等高线轮廓图 使用线条绘制等高线轮廓,更接近传统地形图的表现形式。 ```ts import { Chart } from '@antv/g2'; // 生成等高线数据 const generateContourLines = () => { const lines = []; const levels = [20, 40, 60, 80, 100]; // 等高线级别 levels.forEach((level, index) => { // 为每个等高线级别生成环形线条 const points = []; const centerX = 25; const centerY = 25; const baseRadius = 5 + index * 4; for (let angle = 0; angle <= 360; angle += 5) { const radian = (angle * Math.PI) / 180; const radius = baseRadius + Math.sin((angle * Math.PI) / 45) * 2; // 添加一些变化 const x = centerX + radius * Math.cos(radian); const y = centerY + radius * Math.sin(radian); points.push({ x, y, level, lineId: `line_${level}` }); } lines.push(...points); }); return lines; }; const contourLines = generateContourLines(); const chart = new Chart({ container: 'container', autoFit: true, height: 400, }); chart.options({ type: 'line', data: contourLines, encode: { x: 'x', y: 'y', color: 'level', series: 'lineId', }, style: { strokeWidth: 2, strokeOpacity: 0.8, }, scale: { color: { type: 'sequential', palette: 'oranges', }, x: { nice: true }, y: { nice: true }, }, axis: { x: { title: '距离 (km)' }, y: { title: '距离 (km)' }, }, legend: { color: { title: '海拔高度 (m)', layout: { justifyContent: 'center' }, }, }, tooltip: { title: '等高线信息', items: [ { field: 'level', name: '海拔', valueFormatter: (value) => `${value}m` }, ], }, }); chart.render(); ``` - **3D 等高线**:结合三维可视化技术,可以创建立体的等高线图,更直观地展示数据的空间分布。 - **动态等高线**:通过动画展示数据随时间的变化,如气象数据的时间演变。 - **交互式标注**:允许用户点击等高线查看具体数值,或者动态调整等高线的间隔。 - **多层叠加**:在同一图表中叠加多个数据层的等高线,如同时显示温度和气压分布。 ## 等高线图与其他图表的对比 ### 等高线图与热力图 - **等高线图**:通过线条表示数值相等的区域,强调数值的连续性和梯度变化,适合显示渐变趋势。 - **热力图**:通过颜色填充表示数值大小,更直观地显示数值的绝对大小和空间分布模式。 ### 等高线图与散点图 - **等高线图**:显示连续分布的数据模式,通过插值生成平滑的等值线。 - **散点图**:显示离散数据点的分布,保持数据的原始性,不进行插值处理。 ### 等高线图与地形图 - **等高线图**:纯粹的数据可视化工具,可用于任何二维连续数据。 - **地形图**:专门用于地理信息,除等高线外还包含道路、河流、建筑等地理要素。 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 螺旋图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/spiral/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/spiral.md - Description: 沿螺旋路径排列数据,观察时间序列的周期与长期变化,了解螺旋图的适用场景及 G2 示例。 --- title: "螺旋图" description: "沿螺旋路径排列数据,观察时间序列的周期与长期变化,了解螺旋图的适用场景及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/spiral/" version: "5.4.8" --- 螺旋图示例 ## 螺旋图的简介 螺旋图,基于阿基米德螺旋坐标系,常用于绘制随时间变化的数据,从螺旋的中心开始向外绘制。 螺旋图有两大好处: - **绘制大量数据**:螺旋图节省空间,可用于显示大时间段数据的变化趋势 - **绘制周期性数据**:螺旋图每一圈的刻度差相同,当每一圈的刻度差是数据周期的倍数时,能够直观的表达数据的周期性 螺旋图通过将线性时间轴转换为极坐标系下的螺旋形状,从中心点开始向外延伸,既保持了时间的连续性,又实现了空间的紧凑利用。这种表现形式在处理大量时间序列数据时具有独特的视觉优势,特别适合观察数据的**周期**和变化**趋势**。 **英文名**:Spiral Chart ## 螺旋图的构成 螺旋图主要由以下几个部分构成: 螺旋图结构 | 图表类型 | 螺旋图 | |---------|--------| | 适合的数据 | 一个时间数据字段,一个连续字段 | | 功能 | 观察数据**周期**和变化**趋势** | | 数据与图形的映射 | 时间数据字段映射到旋转角度θ轴,连续字段映射到线圈间距radius轴 | | 适合的数据条数 | 100条以上 | ## 螺旋图的应用场景 ### 适合的场景 例子 1: **展示大量数据的变化趋势** 用大量数据来形成完整的螺旋形状 ```ts import { Chart } from "@antv/g2"; const chart = new Chart({ container: "container" }); chart.options({ type: "interval", autoFit: true, height: 500, data: { value: [ { time: "2025.07.11", value: 35 }, { time: "2025.07.12", value: 30 }, { time: "2025.07.13", value: 55 }, { time: "2025.07.14", value: 86 }, { time: "2025.07.15", value: 100 }, { time: "2025.07.16", value: 60 }, { time: "2025.07.17", value: 79 }, { time: "2025.07.18", value: 15 }, { time: "2025.07.19", value: 10 }, { time: "2025.07.20", value: 10 }, { time: "2025.07.21", value: 10 }, { time: "2025.07.22", value: 55 }, { time: "2025.07.23", value: 25 }, { time: "2025.07.24", value: 35 }, { time: "2025.07.25", value: 35 }, { time: "2025.07.26", value: 20 }, { time: "2025.07.27", value: 40 }, { time: "2025.07.28", value: 20 }, { time: "2025.07.29", value: 45 }, { time: "2025.07.30", value: 45 }, { time: "2025.07.31", value: 10 }, { time: "2025.08.01", value: 30 }, { time: "2025.08.02", value: 45 }, { time: "2025.08.03", value: 40 }, { time: "2025.08.04", value: 40 }, { time: "2025.08.05", value: 20 }, { time: "2025.08.06", value: 40 }, { time: "2025.08.07", value: 20 }, { time: "2025.08.08", value: 50 }, { time: "2025.08.09", value: 15 }, { time: "2025.08.10", value: 35 }, { time: "2025.08.11", value: 40 }, { time: "2025.08.12", value: 30 }, { time: "2025.08.13", value: 35 }, { time: "2025.08.14", value: 30 }, { time: "2025.08.15", value: 76 }, { time: "2025.08.16", value: 108 }, { time: "2025.08.17", value: 44 }, { time: "2025.08.18", value: 55 }, { time: "2025.08.19", value: 30 }, { time: "2025.08.20", value: 35 }, { time: "2025.08.21", value: 40 }, { time: "2025.08.22", value: 30 }, { time: "2025.08.23", value: 15 }, { time: "2025.08.24", value: 55 }, { time: "2025.08.25", value: 55 }, { time: "2025.08.26", value: 30 }, { time: "2025.08.27", value: 55 }, { time: "2025.08.28", value: 10 }, { time: "2025.08.29", value: 35 }, { time: "2025.08.30", value: 10 }, { time: "2025.08.31", value: 25 }, { time: "2025.09.01", value: 25 }, { time: "2025.09.02", value: 25 }, { time: "2025.09.03", value: 25 }, { time: "2025.09.04", value: 25 }, { time: "2025.09.05", value: 50 }, { time: "2025.09.06", value: 20 }, { time: "2025.09.07", value: 35 }, { time: "2025.09.08", value: 35 }, { time: "2025.09.09", value: 20 }, { time: "2025.09.10", value: 30 }, { time: "2025.09.11", value: 30 }, { time: "2025.09.12", value: 10 }, { time: "2025.09.13", value: 25 }, { time: "2025.09.14", value: 51 }, { time: "2025.09.15", value: 108 }, { time: "2025.09.16", value: 116 }, { time: "2025.09.17", value: 65 }, { time: "2025.09.18", value: 10 }, { time: "2025.09.19", value: 20 }, { time: "2025.09.20", value: 40 }, { time: "2025.09.21", value: 20 }, { time: "2025.09.22", value: 45 }, { time: "2025.09.23", value: 10 }, { time: "2025.09.24", value: 35 }, { time: "2025.09.25", value: 20 }, { time: "2025.09.26", value: 30 }, { time: "2025.09.27", value: 55 }, { time: "2025.09.28", value: 30 }, { time: "2025.09.29", value: 20 }, { time: "2025.09.30", value: 40 }, { time: "2025.10.01", value: 50 }, { time: "2025.10.02", value: 55 }, { time: "2025.10.03", value: 15 }, { time: "2025.10.04", value: 25 }, { time: "2025.10.05", value: 30 }, { time: "2025.10.06", value: 15 }, { time: "2025.10.07", value: 10 }, { time: "2025.10.08", value: 35 }, { time: "2025.10.09", value: 30 }, { time: "2025.10.10", value: 10 }, { time: "2025.10.11", value: 45 }, { time: "2025.10.12", value: 35 }, { time: "2025.10.13", value: 30 }, { time: "2025.10.14", value: 25 }, { time: "2025.10.15", value: 51 }, { time: "2025.10.16", value: 100 }, { time: "2025.10.17", value: 108 }, { time: "2025.10.18", value: 37 }, { time: "2025.10.19", value: 10 }, { time: "2025.10.20", value: 45 }, { time: "2025.10.21", value: 15 }, { time: "2025.10.22", value: 10 }, { time: "2025.10.23", value: 25 }, { time: "2025.10.24", value: 55 }, { time: "2025.10.25", value: 10 }, { time: "2025.10.26", value: 10 }, { time: "2025.10.27", value: 20 }, { time: "2025.10.28", value: 20 }, { time: "2025.10.29", value: 20 }, { time: "2025.10.30", value: 10 }, { time: "2025.10.31", value: 15 }, { time: "2025.11.01", value: 55 }, { time: "2025.11.02", value: 15 }, { time: "2025.11.03", value: 55 }, { time: "2025.11.04", value: 15 }, { time: "2025.11.05", value: 50 }, { time: "2025.11.06", value: 20 }, { time: "2025.11.07", value: 30 }, { time: "2025.11.08", value: 10 }, { time: "2025.11.09", value: 25 }, { time: "2025.11.10", value: 15 }, { time: "2025.11.11", value: 25 }, { time: "2025.11.12", value: 50 }, { time: "2025.11.13", value: 35 }, { time: "2025.11.14", value: 35 }, { time: "2025.11.15", value: 72 }, { time: "2025.11.16", value: 124 }, { time: "2025.11.17", value: 116 }, { time: "2025.11.18", value: 37 }, { time: "2025.11.19", value: 10 }, { time: "2025.11.20", value: 15 }, { time: "2025.11.21", value: 55 }, { time: "2025.11.22", value: 30 }, { time: "2025.11.23", value: 30 }, { time: "2025.11.24", value: 20 }, { time: "2025.11.25", value: 50 }, { time: "2025.11.26", value: 45 }, { time: "2025.11.27", value: 50 }, { time: "2025.11.28", value: 25 }, { time: "2025.11.29", value: 20 }, { time: "2025.11.30", value: 50 }, { time: "2025.12.01", value: 15 }, { time: "2025.12.02", value: 20 }, { time: "2025.12.03", value: 15 }, { time: "2025.12.04", value: 30 }, { time: "2025.12.05", value: 40 }, { time: "2025.12.06", value: 40 }, { time: "2025.12.07", value: 45 }, { time: "2025.12.08", value: 50 }, { time: "2025.12.09", value: 25 }, { time: "2025.12.10", value: 15 }, { time: "2025.12.11", value: 35 }, { time: "2025.12.12", value: 35 }, { time: "2025.12.13", value: 50 }, { time: "2025.12.14", value: 25 }, { time: "2025.12.15", value: 30 }, { time: "2025.12.16", value: 93 }, { time: "2025.12.17", value: 92 }, { time: "2025.12.18", value: 132 }, { time: "2025.12.19", value: 51 }, { time: "2025.12.20", value: 30 }, { time: "2025.12.21", value: 35 }, { time: "2025.12.22", value: 25 }, { time: "2025.12.23", value: 20 }, { time: "2025.12.24", value: 45 }, { time: "2025.12.25", value: 40 }, { time: "2025.12.26", value: 15 }, { time: "2025.12.27", value: 40 }, { time: "2025.12.28", value: 40 }, { time: "2025.12.29", value: 15 }, { time: "2025.12.30", value: 55 }, { time: "2025.12.31", value: 45 }, { time: "2026.01.01", value: 25 }, { time: "2026.01.02", value: 45 }, { time: "2026.01.03", value: 45 }, { time: "2026.01.04", value: 50 }, { time: "2026.01.05", value: 45 }, { time: "2026.01.06", value: 30 }, { time: "2026.01.07", value: 30 }, { time: "2026.01.08", value: 55 }, { time: "2026.01.09", value: 55 }, { time: "2026.01.10", value: 10 }, { time: "2026.01.11", value: 20 }, { time: "2026.01.12", value: 10 }, { time: "2026.01.13", value: 15 }, { time: "2026.01.14", value: 50 }, { time: "2026.01.15", value: 10 }, { time: "2026.01.16", value: 86 }, { time: "2026.01.17", value: 68 }, { time: "2026.01.18", value: 116 }, { time: "2026.01.19", value: 58 }, { time: "2026.01.20", value: 10 }, { time: "2026.01.21", value: 15 }, { time: "2026.01.22", value: 55 }, { time: "2026.01.23", value: 15 }, { time: "2026.01.24", value: 50 }, { time: "2026.01.25", value: 50 }, { time: "2026.01.26", value: 55 }, { time: "2026.01.27", value: 50 }, { time: "2026.01.28", value: 55 }, { time: "2026.01.29", value: 50 }, { time: "2026.01.30", value: 45 }, { time: "2026.01.31", value: 15 }, { time: "2026.02.01", value: 15 }, { time: "2026.02.02", value: 25 }, { time: "2026.02.03", value: 25 }, { time: "2026.02.04", value: 40 }, { time: "2026.02.05", value: 45 }, { time: "2026.02.06", value: 15 }, { time: "2026.02.07", value: 45 }, { time: "2026.02.08", value: 20 }, { time: "2026.02.09", value: 25 }, { time: "2026.02.10", value: 55 }, { time: "2026.02.11", value: 50 }, { time: "2026.02.12", value: 50 }, { time: "2026.02.13", value: 55 }, { time: "2026.02.14", value: 40 }, { time: "2026.02.15", value: 10 }, { time: "2026.02.16", value: 58 }, { time: "2026.02.17", value: 92 }, { time: "2026.02.18", value: 84 }, { time: "2026.02.19", value: 44 }, { time: "2026.02.20", value: 55 }, { time: "2026.02.21", value: 45 }, { time: "2026.02.22", value: 40 }, { time: "2026.02.23", value: 20 }, { time: "2026.02.24", value: 50 }, { time: "2026.02.25", value: 10 }, { time: "2026.02.26", value: 10 }, { time: "2026.02.27", value: 50 }, { time: "2026.02.28", value: 15 }, { time: "2026.03.01", value: 40 }, { time: "2026.03.02", value: 45 }, { time: "2026.03.03", value: 45 }, { time: "2026.03.04", value: 30 }, { time: "2026.03.05", value: 30 }, { time: "2026.03.06", value: 50 }, { time: "2026.03.07", value: 25 }, { time: "2026.03.08", value: 10 }, { time: "2026.03.09", value: 40 }, { time: "2026.03.10", value: 10 }, { time: "2026.03.11", value: 15 }, { time: "2026.03.12", value: 30 }, { time: "2026.03.13", value: 25 }, { time: "2026.03.14", value: 20 }, { time: "2026.03.15", value: 50 }, { time: "2026.03.16", value: 25 }, { time: "2026.03.17", value: 50 }, { time: "2026.03.18", value: 20 }, { time: "2026.03.19", value: 30 }, { time: "2026.03.20", value: 60 }, { time: "2026.03.21", value: 68 }, { time: "2026.03.22", value: 72 }, { time: "2026.03.23", value: 20 }, { time: "2026.03.24", value: 20 }, { time: "2026.03.25", value: 50 }, { time: "2026.03.26", value: 15 }, { time: "2026.03.27", value: 25 }, { time: "2026.03.28", value: 25 }, { time: "2026.03.29", value: 35 }, { time: "2026.03.30", value: 45 }, { time: "2026.03.31", value: 45 }, { time: "2026.04.01", value: 10 }, { time: "2026.04.02", value: 45 }, { time: "2026.04.03", value: 15 }, { time: "2026.04.04", value: 10 }, { time: "2026.04.05", value: 45 }, { time: "2026.04.06", value: 20 }, { time: "2026.04.07", value: 50 }, { time: "2026.04.08", value: 30 }, { time: "2026.04.09", value: 20 }, { time: "2026.04.10", value: 50 }, { time: "2026.04.11", value: 35 }, { time: "2026.04.12", value: 35 }, { time: "2026.04.13", value: 40 }, { time: "2026.04.14", value: 25 }, { time: "2026.04.15", value: 15 }, { time: "2026.04.16", value: 15 }, { time: "2026.04.17", value: 55 }, { time: "2026.04.18", value: 45 }, { time: "2026.04.19", value: 65 }, { time: "2026.04.20", value: 76 }, { time: "2026.04.21", value: 68 }, { time: "2026.04.22", value: 72 }, { time: "2026.04.23", value: 45 }, { time: "2026.04.24", value: 10 }, { time: "2026.04.25", value: 40 }, { time: "2026.04.26", value: 25 }, { time: "2026.04.27", value: 15 }, { time: "2026.04.28", value: 35 }, { time: "2026.04.29", value: 30 }, { time: "2026.04.30", value: 10 }, { time: "2026.05.01", value: 25 }, { time: "2026.05.02", value: 40 }, { time: "2026.05.03", value: 35 }, { time: "2026.05.04", value: 55 }, { time: "2026.05.05", value: 35 }, { time: "2026.05.06", value: 10 }, { time: "2026.05.07", value: 35 }, { time: "2026.05.08", value: 30 }, { time: "2026.05.09", value: 55 }, { time: "2026.05.10", value: 30 }, { time: "2026.05.11", value: 20 }, { time: "2026.05.12", value: 35 }, { time: "2026.05.13", value: 55 }, { time: "2026.05.14", value: 45 }, { time: "2026.05.15", value: 45 }, { time: "2026.05.16", value: 35 }, { time: "2026.05.17", value: 55 }, { time: "2026.05.18", value: 25 }, { time: "2026.05.19", value: 40 }, { time: "2026.05.20", value: 93 }, { time: "2026.05.21", value: 92 }, { time: "2026.05.22", value: 108 }, { time: "2026.05.23", value: 93 }, { time: "2026.05.24", value: 55 }, { time: "2026.05.25", value: 20 }, { time: "2026.05.26", value: 40 }, { time: "2026.05.27", value: 20 }, { time: "2026.05.28", value: 45 }, { time: "2026.05.29", value: 50 }, { time: "2026.05.30", value: 45 }, { time: "2026.05.31", value: 45 }, { time: "2026.06.01", value: 40 }, { time: "2026.06.02", value: 20 }, { time: "2026.06.03", value: 35 }, { time: "2026.06.04", value: 35 }, { time: "2026.06.05", value: 15 }, { time: "2026.06.06", value: 10 }, { time: "2026.06.07", value: 55 }, { time: "2026.06.08", value: 40 }, { time: "2026.06.09", value: 25 }, { time: "2026.06.10", value: 20 }, { time: "2026.06.11", value: 40 }, { time: "2026.06.12", value: 20 }, { time: "2026.06.13", value: 25 }, { time: "2026.06.14", value: 25 }, { time: "2026.06.15", value: 55 }, { time: "2026.06.16", value: 35 }, { time: "2026.06.17", value: 35 }, { time: "2026.06.18", value: 25 }, { time: "2026.06.19", value: 25 }, { time: "2026.06.20", value: 65 }, { time: "2026.06.21", value: 68 }, { time: "2026.06.22", value: 68 }, { time: "2026.06.23", value: 30 }, { time: "2026.06.24", value: 30 }, { time: "2026.06.25", value: 10 }, { time: "2026.06.26", value: 15 }, { time: "2026.06.27", value: 15 }, { time: "2026.06.28", value: 40 }, { time: "2026.06.29", value: 55 }, { time: "2026.06.30", value: 10 }, { time: "2026.07.01", value: 15 }, { time: "2026.07.02", value: 30 }, { time: "2026.07.03", value: 45 }, { time: "2026.07.04", value: 35 }, { time: "2026.07.05", value: 45 }, { time: "2026.07.06", value: 20 }, { time: "2026.07.07", value: 30 }, { time: "2026.07.08", value: 30 }, { time: "2026.07.09", value: 40 }, { time: "2026.07.10", value: 45 }, { time: "2026.07.11", value: 55 }, { time: "2026.07.12", value: 55 }, { time: "2026.07.13", value: 55 }, { time: "2026.07.14", value: 50 }, { time: "2026.07.15", value: 10 }, { time: "2026.07.16", value: 55 }, { time: "2026.07.17", value: 15 }, ], }, encode: { x: "time", y: "value", color: "value" }, scale: { color: { type: "linear", range: ["#ffffff", "#1890FF"] } }, coordinate: { type: "helix", startAngle: 1.5707963267948966, endAngle: 39.269908169872416, }, animate: { enter: { type: "fadeIn" } }, tooltip: { title: "time" }, }); chart.render(); ``` ### 不适合的场景 螺旋图虽然有很多优势,但也存在一些局限性: **1. 数据量过少的场景** 螺旋图需要足够的数据点(通常100条以上)来形成完整的螺旋形状,如果数据量太少,螺旋效果不明显,此时使用普通的折线图或散点图会更合适。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); chart.options({ type: 'interval', autoFit: true, height: 500, data: { value: [ { time: '2025.07.11', value: 15 }, { time: '2025.07.12', value: 50 }, { time: '2025.07.13', value: 50 }, { time: '2025.07.14', value: 86 }, { time: '2025.07.15', value: 60 }, ], }, encode: { x: 'time', y: 'value', color: 'value' }, scale: { color: { type: 'linear', range: ['#ffffff', '#1890FF'] } }, coordinate: { type: 'helix', startAngle: 1.5707963267948966, endAngle: 39.269908169872416, }, animate: { enter: { type: 'fadeIn' } }, tooltip: { title: 'time' }, }); chart.render(); ``` **更适合的折线图示例:** ```ts import { Chart } from "@antv/g2"; // 同样的5天数据,使用折线图展示 const data = []; const dates = ['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04', '2023-01-05']; const values = [45, 68, 52, 71, 59]; // 模拟5天的销售数据 dates.forEach((date, index) => { data.push({ time: date, value: values[index] }); }); const chart = new Chart({ container: "container", autoFit: true, height: 400, padding: [50, 50, 50, 50] }); chart.options({ type: 'line', data, encode: { x: 'time', y: 'value' }, scale: { time: { type: 'time', mask: 'yyyy.mm.dd' } }, style: { stroke: '#1890ff', strokeWidth: 3, lineJoin: 'round' }, point: { style: { fill: '#1890ff', stroke: '#ffffff', strokeWidth: 2, r: 6 } }, axis: { x: { title: '时间', labelAutoRotate: false }, y: { title: '销售额' } }, title: '推荐方案:少量数据使用折线图更清晰(5天数据)' }); chart.render(); ``` **2. 需要精确比较数值的场景** 由于螺旋图的非线性特性,不便于精确比较具体数值或变化率。 ```ts import { Chart } from '@antv/g2'; // 需要精确对比的数据示例 const data = []; const categories = ['产品A', '产品B', '产品C', '产品D']; categories.forEach((category, index) => { for (let month = 1; month <= 12; month++) { data.push({ time: `2023-${month.toString().padStart(2, '0')}`, category: category, sales: 80 + index * 5 + Math.random() * 10, // 接近的数值,需要精确比较 }); } }); const chart = new Chart({ container: 'container', autoFit: true, height: 400, padding: [50, 50, 50, 50], }); chart.options({ type: 'interval', data, coordinate: { type: 'helix', startAngle: 1 * Math.PI, endAngle: 5 * Math.PI, }, encode: { x: 'time', y: 'sales', color: 'category', }, scale: { color: { palette: ['#1890ff', '#52c41a', '#fa8c16', '#f5222d'], }, time: { type: 'time', mask: 'yyyy.mm', }, }, style: { fillOpacity: 0.8, }, }); chart.render(); ``` ## 螺旋图的扩展 ### 多层螺旋图 展示多个相关数据系列的螺旋对比: ```ts import { Chart } from "@antv/g2"; // 生成多层螺旋数据 const data = []; const series = ['系列A', '系列B', '系列C']; series.forEach((seriesName, seriesIndex) => { for (let i = 0; i < 120; i++) { const angle = (i / 120) * 4 * Math.PI; const baseRadius = 30 + seriesIndex * 15; // 不同系列不同半径 const radius = baseRadius + i * 0.8; // 不同系列有不同的数据模式 let value; if (seriesIndex === 0) { value = 50 + 20 * Math.sin(angle); } else if (seriesIndex === 1) { value = 45 + 25 * Math.cos(angle * 0.5); } else { value = 55 + 15 * Math.sin(angle * 2); } value += Math.random() * 10; data.push({ time: i, angle: angle * 180 / Math.PI, radius: radius, value: value, series: seriesName }); } }); const chart = new Chart({ container: "container", autoFit: true, height: 600, padding: [50, 50, 50, 50] }); chart.options({ type: 'line', data, coordinate: { type: 'polar' }, encode: { x: 'angle', y: 'radius', color: 'series' }, scale: { color: { palette: ['#1890ff', '#52c41a', '#fa8c16'] }, x: { type: 'linear' }, y: { type: 'linear' } }, style: { strokeWidth: 2, strokeOpacity: 0.8 }, axis: { x: { title: null }, y: { title: '螺旋进程' } }, legend: { color: { title: '数据系列' } }, title: '多层螺旋对比图' }); chart.render(); ``` ### 基因螺旋图 基因螺旋图是螺旋图在生物信息学领域的特殊应用,常用于展示基因表达数据随时间的变化模式。通过螺旋坐标系,可以清晰地观察基因在不同条件下(如野生型WT和敲除型KO)的表达差异: ```ts import { Chart } from "@antv/g2"; // 模拟基因表达数据 const data = []; const groups = ['WT', 'KO']; // 野生型和敲除型 const hours = 72; // 72小时时间序列 const baseValues = { WT: 2.0, // 野生型基础表达水平 KO: 2.3, // 敲除型基础表达水平 }; for (let i = 0; i < hours; i++) { const time = `${i}h`; groups.forEach((group) => { // 模拟基因表达的周期性变化和随机波动 const cyclicPattern = Math.sin(i / 10) * 0.3; // 周期性模式 const randomNoise = Math.random() * 0.4 - 0.2; // 随机噪声 const trendFactor = group === 'KO' ? 0.1 : 0; // 敲除型可能有轻微上升趋势 data.push({ time, group, logFPKM: baseValues[group] + cyclicPattern + randomNoise + (i * trendFactor / 100) }); }); } const chart = new Chart({ container: "container", autoFit: true, height: 600, padding: [50, 50, 50, 50] }); chart.options({ type: 'interval', data, coordinate: { type: 'helix', startAngle: 0.2 * Math.PI, endAngle: 6.5 * Math.PI, innerRadius: 0.1, }, encode: { x: 'time', y: 'group', color: 'logFPKM' }, scale: { color: { type: 'linear', range: ['#fff', '#ec4839'] } }, style: { fillOpacity: 0.8 }, tooltip: { title: 'time', items: [ { field: 'group', name: '组别' }, { field: 'logFPKM', name: 'log(FPKM)', valueFormatter: (value) => value.toFixed(2), }, ], }, animate: { enter: { type: 'fadeIn', duration: 1000 } }, axis: { x: { title: '时间进程' }, y: { title: '基因组别' } }, legend: { color: { title: '基因表达水平 log(FPKM)' } }, title: '基因表达螺旋图:WT vs KO (72小时)' }); chart.render(); ``` 这种可视化方式特别适合: - **时间序列基因表达数据**:展示基因在长时间序列中的表达变化 - **多组别比较**:同时比较不同基因型或处理条件下的表达差异 - **周期性模式识别**:识别基因表达的昼夜节律或其他周期性规律 - **表达热图展示**:通过颜色编码直观展示表达强度差异 ## 螺旋图与其他图表的对比 ### 螺旋图和[折线图](/zh/charts/line/) - 螺旋图通过螺旋形式节省空间,适合展示长时间序列数据 - 折线图在直线坐标系中展示,更直观地显示数据变化趋势 - 螺旋图更能突出周期性模式,折线图更适合分析精确的时间趋势 ### 螺旋图和[热力图](/zh/charts/heatmap/) - 螺旋图通过螺旋路径展示时间序列的连续性 - 热力图通过颜色矩阵展示数据密度和模式 - 螺旋图保持时间顺序,热力图更适合展示分类数据的分布 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 词云图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/wordcloud/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/wordcloud.md - Description: 用文字大小表达词语的重要程度或频次,了解词云图的布局、文本展示场景及 G2 示例。 --- title: "词云图" description: "用文字大小表达词语的重要程度或频次,了解词云图的布局、文本展示场景及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/wordcloud/" version: "5.4.8" --- wordcloud ## 词云图的简介 词云图是一种以可视化方式展示文本数据的图表类型,通过调整词语的大小、颜色和位置来反映词语在文本中的重要程度或出现频率。词云图将文本信息转换为直观的视觉表现,使用户能够快速识别文本中的关键词汇和主题。 词云图特别适合分析大量文本数据,如社交媒体评论、用户反馈、文章内容、调研报告等。通过词语大小的对比,用户可以迅速了解哪些词汇最为重要或出现最频繁,从而提取文本的核心信息和趋势。 词云图不仅具有实用的分析价值,还具有很强的艺术性和视觉冲击力,常被用于演示文稿、报告封面和数据可视化展示中。 **英文名**:Word Cloud, Tag Cloud ## 词云图的构成 wordcloud-components | 图表类型 | 词云图 | | ---------------- | -------------------------------------------------------------------------- | | 适合的数据 | 列表:文本字段、频率或权重字段 | | 功能 | 展示文本数据的频率分布和重要程度 | | 数据与图形的映射 | 文本字段映射到词语内容
频率字段映射到字体大小
可选分类字段映射到颜色 | | 适合的数据条数 | 20-200 个词语,过多会导致布局拥挤 | **组成元素:** 1. **词语(Words)**:文本中的关键词汇,是词云图的核心元素 2. **字体大小(Font Size)**:通常与词频或重要性成正比 3. **颜色编码(Color Encoding)**:可用于区分不同类别或强调重要程度 4. **布局算法(Layout Algorithm)**:确定词语在空间中的位置分布 5. **形状容器(Shape Container)**:词云的整体轮廓,可以是矩形、圆形或自定义形状 ## 词云图的应用场景 ### 适合的场景 #### 1. 对比大量文本 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', width: 600, }); chart.options({ type: 'wordCloud', paddingTop: 40, layout: { spiral: 'rectangular', fontSize: [20, 100] }, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/philosophy-word.json', }, encode: { color: 'text' }, }); chart.render(); ``` #### 2. 使用特定形状做边界限制 结合图片可以绘制出特定形状的词云。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); chart.options({ type: 'wordCloud', width: 700, height: 400, layout: { imageMask: 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*LKU4TYEiB-4AAAAAAAAAAAAADmJ7AQ/original', fontSize: 8, }, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/antv-keywords.json', }, encode: { color: 'name', text: 'name' }, legend: false, }); chart.render(); ``` ### 不适合的场景 #### 数据区分度不大 当数据的区分度不大时使用词云起不到突出的效果。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', width: 600, }); chart.options({ type: 'wordCloud', data: [ // 英语文学作品中频率相近的形容词(基于语料库统计) // 这些词在文学作品中出现频率都在89-115之间,差异很小 { text: 'beautiful', value: 115 }, { text: 'wonderful', value: 112 }, { text: 'excellent', value: 109 }, { text: 'amazing', value: 106 }, { text: 'brilliant', value: 103 }, { text: 'fantastic', value: 100 }, { text: 'marvelous', value: 97 }, { text: 'splendid', value: 94 }, { text: 'gorgeous', value: 91 }, { text: 'stunning', value: 89 }, ], layout: { spiral: 'rectangular', fontSize: [18, 28], // 范围很小,视觉差异不明显 padding: 3, }, encode: { color: 'text', }, scale: { color: { palette: ['#1890ff', '#52c41a', '#fa8c16', '#722ed1', '#eb2f96'], }, }, style: { fontFamily: 'Arial, sans-serif', }, axis: false, legend: false, tooltip: { items: [ { field: 'text', name: '形容词' }, { field: 'value', name: '文献频次' }, ], }, }); chart.render(); // 问题:词频差异太小(仅26个单位),词云无法有效突出重点词汇 // 所有词语在视觉上大小几乎相同,失去了词云图的核心价值 // 建议:改用柱状图、条形图或表格来精确对比这些相近的数值 ``` #### 数据太少 数据太少时很难布局出好看的词云,推荐使用[柱状图](charts/bar)。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', width: 600, }); chart.options({ type: 'wordCloud', data: [ // 莎士比亚四大悲剧中"death"一词的出现频次(真实语料库数据) { text: 'Hamlet', value: 67 }, { text: 'Macbeth', value: 45 }, { text: 'Othello', value: 23 }, { text: 'King Lear', value: 19 }, ], layout: { spiral: 'rectangular', fontSize: [18, 48], padding: 8, }, encode: { color: 'text', }, scale: { color: { palette: ['#722ed1', '#eb2f96', '#fa8c16', '#52c41a'], }, }, style: { fontFamily: 'Times New Roman, serif', fontWeight: 'bold', }, axis: false, legend: false, tooltip: { items: [ { field: 'text', name: '剧作' }, { field: 'value', name: '"death"词频' }, ], }, }); chart.render(); ``` 使用柱状图的效果: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', width: 600, height: 300, }); chart.options({ type: 'interval', data: [ { play: 'Hamlet', frequency: 67 }, { play: 'Macbeth', frequency: 45 }, { play: 'Othello', frequency: 23 }, { play: 'King Lear', frequency: 19 }, ], encode: { x: 'play', y: 'frequency', color: 'play', }, scale: { color: { palette: ['#722ed1', '#eb2f96', '#fa8c16', '#52c41a'], }, }, axis: { x: { title: '莎士比亚四大悲剧', labelTransform: 'rotate(45)', }, y: { title: '"death"一词频次' }, }, legend: false, }); chart.render(); ``` ## 词云图与其他图表的对比 ### 词云图和[气泡图](/zh/charts/bubble-chart/) - **词云图**:专门用于文本数据,通过字体大小表示重要性 - **气泡图**:用于数值数据,通过气泡大小表示数值 ### 词云图和[柱状图](/zh/charts/bar/) - **词云图**:视觉冲击力强,适合展示概览 - **柱状图**:精确对比数值,适合详细分析 ### 扩展技术要点 1. **数据格式**:确保数据包含 `text` 和 `value` 字段 2. **布局优化**:根据词汇数量选择合适的 `spiral` 类型 3. **性能考虑**:大量词汇时建议限制显示数量 4. **交互体验**:合理使用高亮和提示功能增强可读性 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 子弹图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/bullet/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/bullet.md - Description: 在紧凑空间内对照实际值、目标值与评价区间,了解子弹图的结构、指标对比场景及 G2 示例。 --- title: "子弹图" description: "在紧凑空间内对照实际值、目标值与评价区间,了解子弹图的结构、指标对比场景及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/bullet/" version: "5.4.8" --- bullet ## 子弹图的简介 子弹图(Bullet Chart)是一种线性的图表类型,最初由数据可视化专家 Stephen Few 设计,旨在替代传统的仪表盘而提供更加紧凑和信息丰富的可视化形式。子弹图能够在有限的空间内同时展示实际值、目标值和表现区间,通过对比的方式清晰地显示指标的完成度和表现情况。 子弹图的设计理念是最大化数据墨水比(data-ink ratio),在最小的空间内展示最多的有用信息,特别适合用于仪表板和绩效监控场景。 **英文名**:Bullet Chart **其他名称**:进度条图、目标比较图、弹丸图 ## 子弹图的构成 ### 基础子弹图 bullet-anatomy | 图表类型 | 子弹图 | | ---------------- | -------------------------------------------------------------------------------------------------------- | | 适合的数据 | 一个分类数据字段、一个连续数据字段(实际值)、一个目标值、可选的表现区间 | | 功能 | 展示实际值与目标值的对比,评估表现等级 | | 数据与图形的映射 | 分类数据字段映射到纵轴位置
连续数据字段映射到条形长度
目标值映射到标记线
表现区间映射到背景色带 | | 适合的数据条数 | 单个或多个指标,建议不超过 10 个 | 子弹图的主要组成部分包括: - **实际值条形**:用短粗的深色条形表示,展示当前实际达到的数值 - **目标值标记**:用垂直线或其他标记表示,代表需要达成的目标 - **表现区间**:背景使用不同深浅的色带表示,通常分为差、良、优等区间 - **刻度轴**:提供数值参考,帮助读者理解具体的数值大小 ```ts 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 }, axis: { y: { grid: true, gridLineWidth: 2, title: '完成率 (%)', }, x: { title: false, }, }, }, { type: 'interval', data, encode: { x: 'title', y: 'measures', color: '#5B8FF9' }, style: { maxWidth: 20 }, label: { text: 'measures', position: 'right', textAlign: 'left', dx: 5, formatter: (d) => `${d}%`, }, }, { type: 'point', data, encode: { x: 'title', y: 'target', shape: 'line', color: '#3D76DD', size: 8, }, tooltip: { title: false, items: [ { channel: 'y', name: '目标值', valueFormatter: (d) => `${d}%` }, ], }, }, ], }); chart.render(); ``` --- ## 子弹图的应用场景 ### 适合的场景 **场景 1:业绩指标监控** 子弹图是展示业绩指标完成情况的理想工具,能够清晰地对比实际表现与目标要求。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); const colors = { ranges: ['#ffebee', '#fff3e0', '#e8f5e8'], measures: '#1890ff', target: '#ff4d4f', }; // 转换数据结构 const transformedData = [ { title: '项目进度', value: 40, level: '差' }, { title: '项目进度', value: 30, level: '良' }, { title: '项目进度', value: 30, level: '优' }, ]; const data = [ { title: '项目进度', value: 60, type: '实际进度' }, { title: '项目进度', value: 80, type: '目标进度' }, ]; 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: colors.ranges, }, }, style: { maxWidth: 30 }, }, { type: 'interval', data: { value: data, transform: [{ type: 'filter', callback: (d) => d.type === '实际进度' }], }, encode: { x: 'title', y: 'value', color: colors.measures }, style: { maxWidth: 16 }, }, { type: 'point', data: { value: data, transform: [{ type: 'filter', callback: (d) => d.type === '目标进度' }], }, encode: { x: 'title', y: 'value', shape: 'line', color: colors.target, size: 8, }, axis: { y: { grid: true, title: '进度 (%)' }, x: { title: false } }, }, ], }); chart.render(); ``` **场景 2:预算执行跟踪** 子弹图能够有效地显示预算的执行情况,包括实际支出、预算目标和预警区间。 **场景 3:资源利用率监控** 通过子弹图可以直观地了解各类资源的使用情况,识别过度使用或利用不足的资源。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); const resourceData = [ { resource: 'CPU使用率', ranges: 100, measures: 65, target: 80, }, { resource: '内存使用率', ranges: 100, measures: 45, target: 70, }, { resource: '磁盘使用率', ranges: 100, measures: 88, target: 85, }, { resource: '网络带宽', ranges: 100, measures: 72, target: 75, }, ]; chart.options({ type: 'view', coordinate: { transform: [{ type: 'transpose' }] }, children: [ { type: 'interval', data: resourceData, encode: { x: 'resource', y: 'ranges', color: '#f5f5f5' }, style: { maxWidth: 30 }, }, { type: 'interval', data: resourceData, encode: { x: 'resource', y: 'measures', color: (d) => (d.measures > d.target ? '#ff7875' : '#52c41a'), }, style: { maxWidth: 20 }, label: { text: 'measures', position: 'right', textAlign: 'left', dx: 5, formatter: (d) => `${d}%`, }, }, { type: 'point', data: resourceData, encode: { x: 'resource', y: 'target', shape: 'line', color: '#1890ff', size: 6, }, axis: { y: { grid: true, title: '使用率 (%)', }, x: { title: '系统资源', }, }, }, ], }); chart.render(); ``` ### 不适合的场景 **场景 1:时间趋势分析** 子弹图主要展示某个时点的状态对比,不适合展示随时间变化的趋势,此时应使用折线图。 **场景 2:部分与整体关系** 如果需要展示各部分占整体的比例关系,饼图或环形图更为合适。 **场景 3:数据量过大** 当需要展示大量指标时,子弹图会导致视觉混乱,建议使用分组展示或其他图表类型。 ## 子弹图的扩展 ### 多维子弹图 通过分组显示多个相关指标的表现情况,便于横向对比。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); const multiData = [ { department: '销售部', indicator: '销售额', ranges: 100, measures: 85, target: 90, }, { department: '销售部', indicator: '客户数', ranges: 100, measures: 92, target: 85, }, { department: '市场部', indicator: '品牌知名度', ranges: 100, measures: 78, target: 80, }, { department: '市场部', indicator: '营销投入ROI', ranges: 100, measures: 88, target: 85, }, ]; chart.options({ type: 'view', coordinate: { transform: [{ type: 'transpose' }] }, children: [ { type: 'interval', data: multiData, encode: { x: 'indicator', y: 'ranges', color: '#f0f0f0' }, style: { maxWidth: 30 }, }, { type: 'interval', data: multiData, encode: { x: 'indicator', y: 'measures', color: 'department' }, style: { maxWidth: 20 }, label: { text: 'measures', position: 'right', textAlign: 'left', dx: 5, }, }, { type: 'point', data: multiData, encode: { x: 'indicator', y: 'target', shape: 'line', color: '#666', size: 6, }, axis: { y: { grid: true, title: '完成度', }, x: { title: '关键指标', }, }, }, ], }); chart.render(); ``` ### 分层表现区间 通过不同颜色深浅的背景区间,提供更细致的表现评估标准。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); const colors = { ranges: ['#ffebee', '#fff3e0', '#e8f5e8'], measures: '#1890ff', target: '#ff4d4f', }; // 转换数据结构 const transformedData = [ { title: '项目进度', value: 40, level: '差' }, { title: '项目进度', value: 30, level: '良' }, { title: '项目进度', value: 30, level: '优' }, ]; const data = [ { title: '项目进度', value: 60, type: '实际进度' }, { title: '项目进度', value: 80, type: '目标进度' }, ]; 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: colors.ranges, }, }, style: { maxWidth: 30 }, }, { type: 'interval', data: { value: data, transform: [{ type: 'filter', callback: (d) => d.type === '实际进度' }], }, encode: { x: 'title', y: 'value', color: colors.measures }, style: { maxWidth: 16 }, }, { type: 'point', data: { value: data, transform: [{ type: 'filter', callback: (d) => d.type === '目标进度' }], }, encode: { x: 'title', y: 'value', shape: 'line', color: colors.target, size: 8, }, axis: { y: { grid: true, title: '进度 (%)' }, x: { title: false } }, }, ], }); chart.render(); ``` ### 垂直子弹图 在空间受限或需要特殊布局时,可以使用垂直方向的子弹图。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); const verticalData = [ { metric: 'Q1销售', ranges: 100, measures: 80, target: 85, }, { metric: 'Q2销售', ranges: 100, measures: 92, target: 88, }, { metric: 'Q3销售', ranges: 100, measures: 76, target: 90, }, ]; chart.options({ type: 'view', children: [ { type: 'interval', data: verticalData, encode: { x: 'metric', y: 'ranges', color: '#f0f0f0' }, style: { maxWidth: 30 }, }, { type: 'interval', data: verticalData, encode: { x: 'metric', y: 'measures', color: '#52c41a' }, style: { maxWidth: 20 }, label: { text: 'measures', position: 'top', textAlign: 'center', dy: -5, }, }, { type: 'point', data: verticalData, encode: { x: 'metric', y: 'target', shape: 'line', color: '#ff4d4f', size: 6, }, axis: { y: { grid: true, title: '销售额 (万元)', }, x: { title: '季度', }, }, }, ], }); chart.render(); ``` ## 子弹图与其他图表的对比 ### 子弹图和[条形图](/zh/charts/bar/) - 子弹图在条形图的基础上增加了目标值和表现区间,提供更丰富的对比维度 - 条形图主要用于比较不同分类的数值大小,而子弹图侧重于实际值与目标值的对比 ### 子弹图和[仪表盘](/zh/charts/gauge/) - 子弹图比仪表盘更节省空间,能在相同空间内展示更多信息 - 仪表盘更直观地展示单个指标的状态,子弹图更适合多指标对比 ### 子弹图和[进度条](/zh/charts/gauge/) - 子弹图提供了更多的上下文信息,包括目标值和表现区间 - 进度条主要展示完成进度,子弹图能展示表现的优劣程度 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 桑基图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/sankey/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/sankey.md - Description: 用连接带宽度表达节点间的流量,了解桑基图在流程、能源和资金流向分析中的应用及 G2 示例。 --- title: "桑基图" description: "用连接带宽度表达节点间的流量,了解桑基图在流程、能源和资金流向分析中的应用及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/sankey/" version: "5.4.8" --- 桑基图 ## 桑基图的简介 桑基图 (Sankey Diagram),是一种特定类型的流图,用于描述一组值到另一组值的流向。上图为 1869 年,查尔斯米纳德(Charles Minard)绘制的 1812 年拿破仑征俄图(Map of Napolean's Russian Campaign of 1812),这是一个在地图上覆盖桑基图的流程图。1898 年爱尔兰人 Matthew Henry Phineas Riall Sankey 在土木工程师学会会报纪要的一篇关于蒸汽机能源效率的文章中首次推出了第一个能量流动图,此后便以其名字命名为 Sankey 图,中文音译为桑基图。 图中延伸的分支的宽度对应数据流量的大小。桑基图的特点如下: - 起始流量和结束流量相同,所有主支宽度的总和与所有分出去的分支宽度总和相等,保持能量的平衡; - 在内部,不同的线条代表了不同的流量分流情况,它的宽度成比例地显示此分支占有的流量; - 节点不同的宽度代表了特定状态下的流量大小。 桑基图通常应用于能源、材料成分、金融等数据的可视化分析。 **英文名**:Sankey Diagram ## 桑基图的构成 ### 基础桑基图 桑基图 | 图表类型 | 基础桑基图 | | ---------------- | -------------------------------------------------------------------------- | | 适合的数据 | 流向数据:包含源节点(source)、目标节点(target)和流量值(value)的数据 | | 功能 | 展示不同节点之间的流向关系和流量大小 | | 数据与图形的映射 | 源节点映射到左侧位置
目标节点映射到右侧位置
流量值映射到连接线的宽度 | | 适合的场景 | 展示能源流动、资金流向、用户路径等流程分析 | ## 桑基图的应用场景 ### 适合的场景 例子 1: **适合展示能源流动分析** 下图是一个能源流动的桑基图,展示了从原始能源到最终消费的完整流程。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'sankey', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/energy.json', transform: [ { type: 'custom', callback: (data) => ({ links: data }), }, ], }, layout: { nodeAlign: 'center', nodePadding: 0.03, }, style: { labelSpacing: 3, labelFontWeight: 'bold', nodeStrokeWidth: 1.2, linkFillOpacity: 0.4, }, }); chart.render(); ``` **说明**: - `source` 字段映射到起始节点,表示能源的来源 - `target` 字段映射到目标节点,表示能源的去向 - `value` 字段映射到连接线的宽度,表示流量的大小 - 通过颜色编码区分不同类型的能源流动 例子 2: **适合展示用户路径分析** 桑基图可以有效展示用户在网站或应用中的行为路径,帮助分析用户流失点和转化漏斗。 ```ts import { Chart } from '@antv/g2'; const data = { links: [ { source: '首页', target: '产品页', value: 1000 }, { source: '首页', target: '关于我们', value: 300 }, { source: '首页', target: '联系我们', value: 200 }, { source: '产品页', target: '购物车', value: 600 }, { source: '产品页', target: '产品详情', value: 400 }, { source: '购物车', target: '结算页', value: 450 }, { source: '购物车', target: '继续购物', value: 150 }, { source: '结算页', target: '支付成功', value: 380 }, { source: '结算页', target: '放弃支付', value: 70 }, { source: '产品详情', target: '购物车', value: 200 }, { source: '产品详情', target: '返回', value: 200 }, ], }; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'sankey', autoFit: true, data: { value: data }, layout: { nodeAlign: 'justify', nodePadding: 0.05, }, style: { labelSpacing: 5, labelFontSize: 12, nodeStrokeWidth: 2, linkFillOpacity: 0.6, }, scale: { color: { range: ['#1890ff', '#52c41a', '#faad14', '#f5222d', '#722ed1'], }, }, }); chart.render(); ``` **说明**: - 展示了用户从首页到最终转化的完整路径 - 连接线的宽度表示用户流量的大小 - 可以清晰识别用户流失的关键节点 例子 3: **适合展示资金流向分析** 桑基图特别适合展示预算分配、成本分析等财务数据的流向关系。 ```ts import { Chart } from '@antv/g2'; const budgetData = { links: [ { source: '总预算', target: '研发部门', value: 5000000 }, { source: '总预算', target: '市场部门', value: 3000000 }, { source: '总预算', target: '运营部门', value: 2000000 }, { source: '研发部门', target: '产品开发', value: 3000000 }, { source: '研发部门', target: '技术研究', value: 2000000 }, { source: '市场部门', target: '广告投放', value: 2000000 }, { source: '市场部门', target: '品牌建设', value: 1000000 }, { source: '运营部门', target: '人力成本', value: 1200000 }, { source: '运营部门', target: '办公成本', value: 800000 }, { source: '产品开发', target: '移动端', value: 1800000 }, { source: '产品开发', target: '网页端', value: 1200000 }, { source: '广告投放', target: '线上广告', value: 1500000 }, { source: '广告投放', target: '线下推广', value: 500000 }, ], }; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'sankey', autoFit: true, data: { value: budgetData }, layout: { nodeAlign: 'center', nodePadding: 0.08, }, style: { labelSpacing: 8, labelFontSize: 11, nodeStrokeWidth: 1.5, linkFillOpacity: 0.5, }, scale: { color: { range: ['#096dd9', '#36cfc9', '#52c41a', '#faad14', '#ff7a45'], }, }, }); chart.render(); ``` **说明**: - 清晰展示了预算从总体到具体项目的分配路径 - 通过流量宽度直观表示资金分配的比例 - 颜色编码帮助区分不同层级的预算项目 ### 不适合的场景 例子 1: **不适合展示简单的分类对比** 当数据主要用于比较不同类别的数值大小,而非展示流向关系时,桑基图不是最佳选择。这种情况下,柱状图或饼图会更加直观。 ```ts import { Chart } from '@antv/g2'; // 示例:销售额对比数据(不适合用桑基图) const salesData = [ { category: '手机', value: 25000 }, { category: '电脑', value: 18000 }, { category: '平板', value: 12000 }, { category: '耳机', value: 8000 }, { category: '配件', value: 5000 }, ]; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, data: { value: salesData }, encode: { x: 'category', y: 'value', color: 'category', }, axis: { y: { title: '销售额(元)' }, x: { title: '产品类别' }, }, style: { fill: '#1890ff', }, }); chart.render(); ``` **说明**:此类数据更适合使用柱状图展示,因为重点是比较各类别的数值大小,而非展示数据间的流向关系。桑基图在这种场景下会增加不必要的复杂性。 例子 2: **不适合展示过于复杂的网络关系** 当节点数量过多(超过 30 个)或流向关系过于复杂时,桑基图可能会变得难以阅读。这种情况下,可以考虑使用网络图或层次图。 ```ts import { Chart } from '@antv/g2'; // 示例:复杂的组织架构关系(不适合用桑基图) const complexData = { links: [ { source: 'CEO', target: '研发VP', value: 1 }, { source: 'CEO', target: '销售VP', value: 1 }, { source: 'CEO', target: '市场VP', value: 1 }, { source: 'CEO', target: '运营VP', value: 1 }, { source: '研发VP', target: '前端团队', value: 1 }, { source: '研发VP', target: '后端团队', value: 1 }, { source: '研发VP', target: 'QA团队', value: 1 }, { source: '研发VP', target: 'DevOps团队', value: 1 }, { source: '销售VP', target: '直销团队', value: 1 }, { source: '销售VP', target: '渠道团队', value: 1 }, { source: '销售VP', target: '商务团队', value: 1 }, { source: '销售VP', target: '客服团队', value: 1 }, { source: '市场VP', target: '品牌团队', value: 1 }, { source: '市场VP', target: '活动团队', value: 1 }, { source: '市场VP', target: '内容团队', value: 1 }, { source: '运营VP', target: '数据团队', value: 1 }, { source: '运营VP', target: '产品团队', value: 1 }, { source: '运营VP', target: '用户团队', value: 1 }, ], }; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'sankey', autoFit: true, data: { value: complexData }, layout: { nodeAlign: 'justify', nodePadding: 0.02, }, style: { labelSpacing: 2, labelFontSize: 10, nodeStrokeWidth: 1, linkFillOpacity: 0.3, }, }); chart.render(); ``` **说明**:此类层次结构数据使用桑基图会显得过于拥挤且难以阅读。更适合使用组织架构图、树形图或网络图来展示这种层次关系。桑基图的优势在于展示有意义的"流量"关系,而非仅仅是组织结构。 ## 桑基图的扩展 ### 自定义节点对齐方式 可以通过不同的节点对齐方式来优化桑基图的布局效果。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'sankey', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/energy.json', transform: [ { type: 'custom', callback: (data) => ({ links: data.slice(0, 20) }), // 使用部分数据以便展示 }, ], }, layout: { nodeAlign: 'left', // 可选值:'left', 'right', 'center', 'justify' nodePadding: 0.05, nodeWidth: 0.02, }, style: { labelSpacing: 5, labelFontWeight: 'bold', nodeStrokeWidth: 2, linkFillOpacity: 0.3, }, }); chart.render(); ``` **说明**: - `nodeAlign: 'left'` 将所有节点左对齐 - `nodePadding` 控制节点之间的间距 - `nodeWidth` 控制节点的宽度 ### 自定义颜色映射 可以根据节点类型或流量方向自定义颜色映射。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'sankey', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/energy.json', transform: [ { type: 'custom', callback: (data) => ({ links: data }), }, ], }, layout: { nodeAlign: 'justify', nodePadding: 0.03, }, encode: { color: (d, idx) => { // 根据节点名称的首字母分配颜色 const firstChar = d.key?.charAt(0).toLowerCase() || ''; if (firstChar <= 'f') return '#1890ff'; if (firstChar <= 'n') return '#52c41a'; if (firstChar <= 's') return '#faad14'; return '#f5222d'; }, }, style: { labelSpacing: 3, labelFontWeight: 'bold', nodeStrokeWidth: 1, linkFillOpacity: 0.6, }, }); chart.render(); ``` **说明**: - 通过 `encode.color` 自定义颜色映射函数 - 可以根据节点属性、流量大小等因素动态分配颜色 ## 桑基图与其他图表的对比 ### 桑基图和[漏斗图](/zh/charts/funnel/) - 桑基图展示复杂的多对多流向关系,可以有多个源节点和目标节点 - 漏斗图主要展示单一路径的转化流程,通常用于分析业务转化率 - 桑基图的连接线宽度表示流量大小,漏斗图的层级宽度表示转化数量 - 桑基图适合复杂流程分析,漏斗图适合线性转化分析 ### 桑基图和[和弦图](/zh/charts/chord/) - 桑基图是有向的流动关系,强调流向性 - 和弦图主要展示无向的关联关系,强调相互关系的强度 - 桑基图适合流程分析,和弦图适合关系网络分析 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 甘特图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/gantt/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/gantt.md - Description: 用时间轴上的横条表示任务开始、结束和持续时间,了解甘特图的进度管理场景及 G2 示例。 --- title: "甘特图" description: "用时间轴上的横条表示任务开始、结束和持续时间,了解甘特图的进度管理场景及 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/gantt/" version: "5.4.8" --- gantt ## 甘特图的简介 甘特图是一种项目管理工具,以图示的方式通过活动列表和时间刻度表示出特定项目的顺序与持续时间。甘特图内在思想简单,基本是一条线条图,横轴表示时间,纵轴表示活动(项目),线条表示在整个期间上计划和实际的活动完成情况。 甘特图通过条状图来显示项目、进度和其他时间相关的系统进展的内在关系随着时间进展的情况。这种图表通过任务列表和时间刻度形象地表达了项目计划中各项任务的起止时间以及任务之间的相互关系。 甘特图是项目管理中最重要的工具之一,广泛应用于项目规划、任务调度、资源管理等场景中。 **英文名**:Gantt Chart ## 甘特图的构成 | 图表类型 | 基础甘特图 | | ---------------- | ---------------------------------------------------------------------------------------------------------- | | 适合的数据 | 项目数据:一个任务名称字段、一个开始时间字段、一个结束时间字段 | | 功能 | 展示项目任务的时间安排和执行进度 | | 数据与图形的映射 | 任务名称字段映射到纵轴的位置
开始时间和结束时间字段映射到条形的起止位置
任务持续时间映射到条形的长度 | | 适合的数据条数 | 不超过 20 个任务 | ## 甘特图的应用场景 ### 适合的场景 例子 1: **适合应用到项目进度管理** 下图是一个活动策划项目的甘特图,展示了不同任务的时间安排和依赖关系。 | name(任务名称) | startTime(开始时间) | endTime(结束时间) | | ---------------- | --------------------- | ------------------- | | 活动策划 | 1 | 4 | | 场地物流规划 | 3 | 13 | | 选择供应商 | 5 | 8 | | 租赁场地 | 9 | 13 | | 预定餐饮服务商 | 10 | 14 | | 租赁活动装饰团队 | 12 | 17 | | 彩排 | 14 | 16 | | 活动庆典 | 17 | 18 | ```ts 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 }, { name: '租赁场地', startTime: 9, endTime: 13 }, { name: '预定餐饮服务商', startTime: 10, endTime: 14 }, { name: '租赁活动装饰团队', startTime: 12, endTime: 17 }, { name: '彩排', startTime: 14, endTime: 16 }, { name: '活动庆典', startTime: 17, endTime: 18 }, ], encode: { x: 'name', y: 'startTime', y1: 'endTime', color: 'name', }, coordinate: { transform: [{ type: 'transpose' }], }, axis: { x: { title: '任务', }, y: { title: '时间(天)', }, }, interaction: [ { type: 'elementHighlight', background: true, }, ], }); chart.render(); ``` **说明**: - `name` 字段映射到横轴位置,用于区分不同任务 - `startTime` 和 `endTime` 字段映射到 `y` 和 `y1` 通道,表示任务的起止时间 - `color` 通道使用任务名称,用不同颜色区分各个任务 - 使用 `transpose` 坐标变换,将垂直方向的时间轴转换为水平方向 例子 2: **适合展示带有时序动画的项目进度** 通过动画效果可以更生动地展示项目任务的时间顺序和执行过程。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, data: [ { name: '需求分析', startTime: 1, endTime: 5, phase: '规划阶段' }, { name: '系统设计', startTime: 4, endTime: 10, phase: '设计阶段' }, { name: '前端开发', startTime: 8, endTime: 20, phase: '开发阶段' }, { name: '后端开发', startTime: 10, endTime: 22, phase: '开发阶段' }, { name: '集成测试', startTime: 18, endTime: 25, phase: '测试阶段' }, { name: '系统部署', startTime: 24, endTime: 28, phase: '部署阶段' }, { name: '用户验收', startTime: 26, endTime: 30, phase: '验收阶段' }, ], encode: { x: 'name', y: 'startTime', y1: 'endTime', color: 'phase', enterDuration: (d) => (d.endTime - d.startTime) * 200, enterDelay: (d) => d.startTime * 100, }, coordinate: { transform: [{ type: 'transpose' }], }, scale: { enterDuration: { range: [0, 5000], }, }, axis: { x: { title: '项目任务', }, y: { title: '时间(周)', }, }, legend: { color: { title: '项目阶段', }, }, }); chart.render(); ``` **说明**: - `phase` 字段映射到颜色,用不同颜色区分项目阶段 - `enterDuration` 和 `enterDelay` 通道实现时序动画效果 - 动画持续时间与任务持续时间成正比,延迟时间与开始时间成正比 例子 3: **适合展示多项目对比的甘特图** 当需要对比多个项目的执行情况时,可以使用分组甘特图。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', autoFit: true, data: [ { project: '项目A', task: '设计', startTime: 1, endTime: 5 }, { project: '项目A', task: '开发', startTime: 4, endTime: 12 }, { project: '项目A', task: '测试', startTime: 10, endTime: 15 }, { project: '项目B', task: '设计', startTime: 2, endTime: 6 }, { project: '项目B', task: '开发', startTime: 5, endTime: 14 }, { project: '项目B', task: '测试', startTime: 12, endTime: 16 }, { project: '项目C', task: '设计', startTime: 3, endTime: 8 }, { project: '项目C', task: '开发', startTime: 7, endTime: 16 }, { project: '项目C', task: '测试', startTime: 14, endTime: 18 }, ], encode: { x: (d) => `${d.project}-${d.task}`, y: 'startTime', y1: 'endTime', color: 'project', }, coordinate: { transform: [{ type: 'transpose' }], }, axis: { x: { title: '项目任务', labelTransform: 'rotate(45)', }, y: { title: '时间(周)', }, }, legend: { color: { title: '项目', }, }, }); chart.render(); ``` **说明**: - 组合 `project` 和 `task` 字段创建唯一的标识符 - `series` 通道用于在同一位置堆叠不同任务 - 标签旋转 45 度以避免重叠 例子 4: **适合展示带有里程碑的甘特图** 在项目管理中,里程碑是重要的时间节点,可以通过不同的标记来突出显示。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'view', autoFit: true, children: [ { type: 'interval', data: [ { name: '市场调研', startTime: 1, endTime: 4, type: '任务' }, { name: '产品设计', startTime: 3, endTime: 8, type: '任务' }, { name: '技术开发', startTime: 6, endTime: 15, type: '任务' }, { name: '内测试运行', startTime: 14, endTime: 18, type: '任务' }, { name: '市场推广', startTime: 17, endTime: 22, type: '任务' }, { name: '正式发布', startTime: 20, endTime: 24, type: '任务' }, ], encode: { x: 'name', y: 'startTime', y1: 'endTime', color: 'type', }, coordinate: { transform: [{ type: 'transpose' }], }, }, { type: 'point', data: [ { name: '调研完成', time: 4, milestone: '里程碑' }, { name: '设计确认', time: 8, milestone: '里程碑' }, { name: '开发完成', time: 15, milestone: '里程碑' }, { name: '产品发布', time: 24, milestone: '里程碑' }, ], encode: { x: 'name', y: 'time', shape: 'diamond', color: 'milestone', size: 8, }, coordinate: { transform: [{ type: 'transpose' }], }, }, ], axis: { x: { title: '项目活动', }, y: { title: '时间(周)', }, }, legend: { color: { title: '类型', }, }, }); chart.render(); ``` **说明**: - 使用复合视图(view)结合 interval 和 point 图形 - interval 展示任务时间段,point 展示里程碑节点 - 里程碑使用钻石形状(diamond)突出显示 ### 不适合的场景 例子 1: **不适合展示没有明确时间维度的数据** 甘特图主要用于展示时间相关的项目或任务,对于没有时间维度的分类对比,应该使用柱状图等其他图表类型。 例子 2: **不适合展示过于细粒度的时间数据** 当任务过多或时间粒度过细时,甘特图会变得拥挤难以阅读。此时应该考虑数据聚合或使用其他可视化方式。 例子 3: **不适合展示连续性的数值变化趋势** 甘特图主要展示离散的任务时间段,对于连续性的数值变化应该使用折线图或面积图。 ## 甘特图的扩展用法 ### 带有进度显示的甘特图 可以通过颜色或填充模式来显示任务的完成进度。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'view', autoFit: true, children: [ { type: 'interval', data: [ { name: '需求分析', startTime: 1, endTime: 5, progress: 100 }, { name: 'UI设计', startTime: 3, endTime: 8, progress: 80 }, { name: '前端开发', startTime: 6, endTime: 15, progress: 60 }, { name: '后端开发', startTime: 8, endTime: 16, progress: 40 }, { name: '测试验收', startTime: 14, endTime: 18, progress: 0 }, ], encode: { x: 'name', y: 'startTime', y1: 'endTime', color: 'lightgray', }, coordinate: { transform: [{ type: 'transpose' }], }, style: { fillOpacity: 0.3, }, }, { type: 'interval', data: [ { name: '需求分析', startTime: 1, currentTime: 5, progress: 100 }, { name: 'UI设计', startTime: 3, currentTime: 6.6, progress: 80 }, { name: '前端开发', startTime: 6, currentTime: 11.4, progress: 60 }, { name: '后端开发', startTime: 8, currentTime: 11.2, progress: 40 }, ], encode: { x: 'name', y: 'startTime', y1: 'currentTime', color: (d) => d.progress >= 100 ? 'green' : d.progress >= 50 ? 'orange' : 'red', }, coordinate: { transform: [{ type: 'transpose' }], }, }, ], axis: { x: { title: '项目任务', }, y: { title: '时间(周)', }, }, }); chart.render(); ``` ### 带有依赖关系的甘特图 可以通过连线或箭头来显示任务之间的依赖关系。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'view', autoFit: true, children: [ { type: 'interval', data: [ { name: '任务A', startTime: 1, endTime: 5, id: 'A' }, { name: '任务B', startTime: 5, endTime: 10, id: 'B' }, { name: '任务C', startTime: 8, endTime: 15, id: 'C' }, { name: '任务D', startTime: 15, endTime: 20, id: 'D' }, ], encode: { x: 'name', y: 'startTime', y1: 'endTime', color: 'name', }, coordinate: { transform: [{ type: 'transpose' }], }, }, { type: 'link', data: [ { source: '任务A', target: '任务B', x1: 5, x2: 5 }, { source: '任务B', target: '任务C', x1: 10, x2: 8 }, { source: '任务C', target: '任务D', x1: 15, x2: 15 }, ], encode: { x: 'source', y: 'x1', x1: 'target', y1: 'x2', }, coordinate: { transform: [{ type: 'transpose' }], }, style: { stroke: '#666', strokeWidth: 2, lineDash: [4, 4], }, }, ], axis: { x: { title: '项目任务', }, y: { title: '时间(周)', }, }, }); chart.render(); ``` ### 可视化叙事 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); chart.options({ type: 'interval', autoFit: true, data: [ { name: 'event planning', startTime: 1, endTime: 4 }, { name: 'layout logistics', startTime: 3, endTime: 13 }, { name: 'select vendors', startTime: 5, endTime: 8 }, { name: 'hire venue', startTime: 9, endTime: 13 }, { name: 'hire caterer', startTime: 10, endTime: 14 }, { name: 'hire event decorators', startTime: 12, endTime: 17 }, { name: 'rehearsal', startTime: 14, endTime: 16 }, { name: 'event celebration', startTime: 17, endTime: 18 }, ], encode: { x: 'name', y: ['endTime', 'startTime'], color: 'name', enterDuration: (d) => d.endTime - d.startTime, enterDelay: 'startTime', }, scale: { enterDuration: { zero: true, range: [0, 3000] } }, coordinate: { transform: [{ type: 'transpose' }] }, }); chart.render(); ``` ## 甘特图与其他图表的对比 ### 甘特图和[柱状图](/zh/charts/bar/) - 甘特图主要用于展示任务的时间安排和项目进度 - 柱状图主要用于对比不同类别的数值大小 ### 甘特图和[折线图](/zh/charts/line/) - 甘特图展示任务的持续时间和相互关系 - 折线图主要展示数值随时间的连续变化趋势 ### 甘特图和[面积图](/zh/charts/area/) - 甘特图使用离散的条形展示任务时间段 - 面积图使用连续的填充区域展示数值随时间的变化趋势 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 卡吉图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/kagi/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/kagi.md - Description: 根据价格变化方向和反转幅度绘制折线,了解卡吉图的趋势表达方式、组成要素与适用场景。 --- title: "卡吉图" description: "根据价格变化方向和反转幅度绘制折线,了解卡吉图的趋势表达方式、组成要素与适用场景。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/kagi/" version: "5.4.8" --- kagi ## 卡吉图的简介 卡吉图大约发明于 19 世纪 70 年代日本股票市场开始交易的时候。在美国,史蒂文·尼森出版《烛形图之外》后,卡吉图分析法随之流行。由于能够清晰、有效地显示价格走势的路径,卡吉图是投资者用于对股票做出更好决策的图表之一。 卡吉图是一系列由短水平线连接的垂直线,水平线起连接作用,垂直线的厚度和方向取决于价格运动。垂直线方向向上代表价格上升,方向向下代表价格下降,当价格运动方向反转超过阈值时绘制一条新的垂直线,在转折处用水平线相连。当价格上升超过前一个高点转折时采用粗线绘制,称为阳线,当价格下降低于前一个低点转折时采用细线绘制,称为阴线。 **卡吉图的特点:** - 卡吉图模拟了证券的供给与需求力量的关系,非常适用于股票短线操作 - 卡吉图通过阈值标记逆转,交易者可以过滤掉日常价格波动,只关注价格的显著变化 - 卡吉图独立于时间 :::warning{title=注意} 卡吉图绘制的折线是经过卡吉图算法计算之后的,与原数据曲线有区别 ::: **英文名**:Kagi Chart ## 卡吉图的构成 kagi-structure | 图表类型 | 卡吉图 | | -------------- | ------------------------------------------------------------ | | 适合的数据 | 一个分类数据字段,一个连续字段 | | 功能 | 观察数据变化**趋势** | | 数据与图形映射 | 转折点的时间分类(自动计算)映射到横轴,转折点的值映射到纵轴 | | 适合的数据条数 | 大于 10 条 | **构成要素:** - **阳线**:价格上升超过前一个高点时的粗线,通常用较粗的线条表示 - **阴线**:价格下降低于前一个低点时的细线,通常用较细的线条表示 - **转折点**:价格运动方向发生逆转的关键点位 - **连接线**:连接垂直线段的水平线,标识转向位置 ## 卡吉图的应用场景 ### 适合的场景 **场景 1:展示价格或股票变化走势** 卡吉图最适合用于展示金融市场中股票、期货、外汇等价格变化的趋势分析。通过过滤掉日常的小幅波动,突出显示价格的重要转折点,帮助投资者识别趋势的变化。 **场景 2:技术分析和趋势判断** 卡吉图在技术分析中特别有用,因为它能够: - 过滤市场噪音,专注于重要的价格变动 - 清晰显示支撑位和阻力位 - 帮助识别趋势的延续和反转信号 ### 不适合的场景 **场景 1:对时间敏感的数据** 卡吉图与时间无关,只表示数据上升下降的走势,无法体现数据在时间上的一些特性,例如周期等。特别是当数据对时间敏感时不推荐使用卡吉图。 例如网站访问量数据通常具有明显的时间周期性(工作日 vs 周末,白天 vs 夜晚),使用卡吉图会丢失这些重要的时间特征信息。对于此类数据,推荐使用[折线图](/zh/charts/line/)或[面积图](/zh/charts/area/)。 **场景 2:多组数据对比** 卡吉图的横坐标具有特殊性(转折点时间),无法在同一个横轴上绘制多组卡吉图进行对比分析。如需对比多个数据系列,建议使用[多系列折线图](/zh/charts/line/)。 ## 卡吉图与其他图表的对比 ### 卡吉图与 K 线图 | 特征 | 卡吉图 | K 线图 | | -------- | -------------------------- | ----------------------------------- | | 时间依赖 | 独立于时间,仅表现数据走势 | 与时间相关,每个时间段对应一个 K 线 | | 数据展示 | 过滤小幅波动,突出重要转折 | 展示指定时间段内的开高低收价格 | | 分析重点 | 趋势方向和转折点 | 时间段内的价格波动和成交量 | | 适用场景 | 中长期趋势分析 | 短期交易和时间敏感分析 | ### 卡吉图与折线图 | 特征 | 卡吉图 | 折线图 | | -------- | ---------------------------------- | ------------------------ | | 数据处理 | 经过算法计算的转折点数据 | 严格按照原数据的坐标绘制 | | 坐标轴 | 横坐标为计算得出的转折点 | 横纵坐标都对应原始数据 | | 线条含义 | 颜色和粗细代表特定含义(阳线阴线) | 通常只表示数据连接关系 | | 多系列 | 无法绘制多组数据 | 可以绘制多组数据进行对比 | | 噪音过滤 | 自动过滤小幅波动 | 显示所有数据波动 | ## 卡吉图的扩展 ### 不同阈值设置 卡吉图的核心参数是**逆转阈值**,不同的阈值设置会产生不同的效果: - **较小阈值**:更敏感,会捕捉到更多的价格转折,但可能包含较多噪音 - **较大阈值**:过滤更多噪音,只显示重要的趋势变化,但可能错过一些转折信号 ### 时间加权卡吉图 传统卡吉图完全忽略时间因素,而时间加权卡吉图在计算转折点时会考虑时间间隔,使图表在保持卡吉图特色的同时,部分保留时间信息。 ## 实现说明 :::info{title=开发说明} G2 目前暂未提供内置的卡吉图标记,但可以通过以下方式实现: 1. **数据预处理**:使用卡吉图算法对原始数据进行转换,计算出转折点数据 2. **自定义标记**:结合 `path` 标记和自定义形状实现卡吉图的视觉效果 3. **算法实现**:需要实现卡吉图的核心算法,包括阈值判断、转折点计算等 详细的实现方式可以参考 [自定义标记](/zh/manual/core/mark/overview/#示例) 文档。 ::: ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() #### 和弦图 - Canonical page: https://g2.antv.antgroup.com/zh/charts/chord/ - Markdown source: https://g2.antv.antgroup.com/zh/charts/chord.md - Description: 用圆周上的分组和连接带表达群体间的联系及规模,了解和弦图的适用场景和 G2 示例。 --- title: "和弦图" description: "用圆周上的分组和连接带表达群体间的联系及规模,了解和弦图的适用场景和 G2 示例。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/charts/chord/" version: "5.4.8" --- chord ## 和弦图的简介 和弦图(Chord Diagram)是一种显示矩阵中数据间相互关系的可视化方法,节点数据沿圆周径向排列,节点之间使用带权重(有宽度)的弧线链接。特别适合展示多个实体之间的相互关系和流量。 和弦图能够直观地展示多个节点之间的复杂关系网络,特别适用于展示双向流动数据,如人口迁移、贸易流向、资金往来、软件依赖关系等场景。 **英文名**:Chord Diagram ## 和弦图的构成 chord-structure | 图表类型 | 和弦图 | | ---------------- | ---------------------------------------------------------------------------------------- | | 适合的数据 | 节点数据集(可选)、边数据集:源节点、目标节点、流量值 | | 功能 | 观察节点关系,展示多个实体间的双向流动关系 | | 数据与图形的映射 | 权重映射到节点和边的宽度
源节点和目标节点映射到圆周上的弧段位置
节点可以用颜色区分 | | 适合的数据条数 | 节点数据两组以上,5-15 个节点效果最佳 | ### 和弦图的组成部分 - **外圆弧(Outer Arcs)**:圆周上的弧段,代表各个分类或节点,弧段长度反映节点的总流量 - **连接弧(Chords)**:连接不同外圆弧的曲线,表示节点间的关系,弧线粗细表示流量大小 - **标签(Labels)**:标识各个节点的文字说明 - **颜色(Colors)**:用于区分不同的节点或流向 ## 和弦图的应用场景 ### 适合的场景 例子 1: **适合应用到流量关系展示** 下图展示了不同城市之间的人口流动情况,通过和弦图可以清晰地看到各城市间的双向流动关系。 | source(源城市) | target(目标城市) | value(流动人口) | | ---------------- | ------------------ | ----------------- | | 北京 | 上海 | 100,000 | | 北京 | 广州 | 80,000 | | 上海 | 北京 | 70,000 | | 上海 | 广州 | 90,000 | | 广州 | 深圳 | 120,000 | ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'chord', autoFit: true, data: { value: { links: [ { source: '北京', target: '上海', value: 100 }, { source: '北京', target: '广州', value: 80 }, { source: '北京', target: '深圳', value: 60 }, { source: '上海', target: '北京', value: 70 }, { source: '上海', target: '广州', value: 90 }, { source: '上海', target: '深圳', value: 50 }, { source: '广州', target: '北京', value: 40 }, { source: '广州', target: '上海', value: 85 }, { source: '广州', target: '深圳', value: 120 }, { source: '深圳', target: '北京', value: 35 }, { source: '深圳', target: '上海', value: 45 }, { source: '深圳', target: '广州', value: 110 }, ], }, }, layout: { nodeWidthRatio: 0.05 }, scale: { color: { type: 'ordinal', range: ['#5B8FF9', '#5AD8A6', '#F6BD16', '#E86452'], }, }, style: { labelFontSize: 12, labelFill: '#333', linkFillOpacity: 0.6, }, tooltip: { items: [ { field: 'source', name: '源城市' }, { field: 'target', name: '目标城市' }, { field: 'value', name: '流动人口' }, ], }, interaction: [ { type: 'elementHighlight', background: true, }, ], }); chart.render(); ``` 例子 2: **展示软件依赖关系** 下图展示了软件包之间的依赖关系,每个节点表示一个独立的包,每条边展示了包与包之间的依赖关系。边的粗细表示依赖的强度。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'chord', autoFit: true, data: { value: { links: [ { source: 'vis.core', target: 'vis.data', value: 85 }, { source: 'vis.core', target: 'vis.event', value: 45 }, { source: 'vis.core', target: 'vis.util', value: 120 }, { source: 'vis.data', target: 'vis.core', value: 30 }, { source: 'vis.data', target: 'vis.util', value: 60 }, { source: 'vis.event', target: 'vis.core', value: 25 }, { source: 'vis.event', target: 'vis.data', value: 40 }, { source: 'vis.util', target: 'vis.core', value: 15 }, { source: 'vis.render', target: 'vis.core', value: 95 }, { source: 'vis.render', target: 'vis.util', value: 55 }, { source: 'vis.layout', target: 'vis.core', value: 75 }, { source: 'vis.layout', target: 'vis.util', value: 35 }, ], }, }, layout: { nodeWidthRatio: 0.05 }, scale: { color: { type: 'ordinal', range: ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd'], }, }, style: { labelFontSize: 11, labelFill: '#333', linkFillOpacity: 0.6, }, tooltip: { items: [ { field: 'source', name: '依赖包' }, { field: 'target', name: '目标包' }, { field: 'value', name: '依赖强度' }, ], }, interaction: [ { type: 'elementHighlight', background: true, }, ], }); chart.render(); ``` **说明**: - 节点表示不同的软件包模块 - 边的粗细表示依赖关系的强度 - 颜色用于区分不同的包,便于识别循环依赖 ### 不适合的场景 例子 1: **节点过多导致可读性差** 当节点数量超过 15 个时,和弦图会变得拥挤难读,连接线重叠严重。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); // 模拟20个省份的数据,节点过多导致可读性差 const provinces = [ '北京', '上海', '广州', '深圳', '杭州', '南京', '武汉', '成都', '西安', '重庆', '天津', '苏州', '青岛', '大连', '厦门', '宁波', '长沙', '郑州', '济南', '福州', ]; const data = []; provinces.forEach((source, i) => { provinces.forEach((target, j) => { if (i !== j && Math.random() > 0.7) { data.push({ source, target, value: Math.floor(Math.random() * 50) + 10, }); } }); }); chart.options({ type: 'chord', autoFit: true, data: { value: { links: data.slice(0, 40), // 只取前40条数据演示 }, }, layout: { nodeWidthRatio: 0.05 }, style: { labelFontSize: 8, // 字体变小以适应更多标签 labelFill: '#666', linkFillOpacity: 0.5, }, }); chart.render(); ``` 例子 2: **不适合展示层次关系** 和弦图主要用于展示平等节点间的关系,不适合展示有明显层次结构的数据。 ## 和弦图的扩展 ### 自定义颜色和样式 下图展示了某个时段用户使用 Uber 软件在美国旧金山各个城市之间乘车交通的情况。图中的节点表示城市,节点大小表示了交通流量的多少,从图中可以看出,交通行为主要发生在几个主要城市之间。 uber-traffic 以下是使用自定义颜色和样式的和弦图示例: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'chord', autoFit: true, data: { value: { links: [ { source: '中国', target: '美国', value: 4200 }, { source: '中国', target: '日本', value: 3200 }, { source: '中国', target: '韩国', value: 2800 }, { source: '美国', target: '中国', value: 1200 }, { source: '美国', target: '墨西哥', value: 6100 }, { source: '日本', target: '中国', value: 1400 }, { source: '日本', target: '韩国', value: 800 }, { source: '韩国', target: '中国', value: 1100 }, ], }, }, layout: { nodeWidthRatio: 0.05 }, scale: { color: { type: 'ordinal', range: ['#5B8FF9', '#5AD8A6', '#F6BD16', '#E86452'], }, }, style: { labelFontSize: 14, labelFontWeight: 'bold', labelFill: '#000', linkFillOpacity: 0.7, }, tooltip: { items: [ { field: 'source', name: '出口国' }, { field: 'target', name: '进口国' }, { field: 'value', name: '贸易额(亿美元)' }, ], }, interaction: [ { type: 'elementHighlight', background: true, }, ], }); chart.render(); ``` ## 和弦图与其他图表的对比 ### 和弦图和雷达图 - 和弦图主要用于展示节点间的双向流动关系,强调连接和流量 - 雷达图主要用于多维度数据比较,通过多边形展示各维度的值 ### 和弦图和面积图 - 和弦图通过圆形布局和连接弧展示关系网络,适合展示流动数据 - 面积图通过填充区域展示数值变化趋势,适合时间序列数据 ## 相似图表 [Interactive content]() ## 分类 [Interactive content]() ### manual #### 概览 - Canonical page: https://g2.antv.antgroup.com/zh/manual/core/animate/overview/ - Markdown source: https://g2.antv.antgroup.com/zh/manual/core/animate/overview.md - Description: 概览 --- title: "概览" description: "概览" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/manual/core/animate/overview/" version: "5.4.8" --- 动画作为可视化的重要组成部分,能显著提高数据可视化的表现力。G2 提供了完善的动画系统,支持图形在进入(enter)、更新(update)和退出(exit)三个阶段的动画效果设置,并且能根据数据特性进行动画编码,实现丰富的交互与过渡效果。 G2 动画系统的核心特性: - **状态驱动**:基于图形的三种状态(进入、更新、退出)设置不同的动画效果 - **数据驱动**:可以将数据值映射到动画属性上,如持续时间、延迟时间等 - **过渡效果**:支持形变(morphing)、渐变、缩放等多种过渡效果 - **时序控制**:支持设置动画的顺序、缓动函数、延迟、持续时间等参数 动画属性可通过 `animate` 配置项设置,既可在图表整体配置中定义,也可针对具体标记设置。 ## 动画配置 动画可在图表配置中通过 `animate` 属性进行设置: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'line', autoFit: true, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/551d80c6-a6be-4f3c-a82a-abd739e12977.csv', }, encode: { x: 'date', y: 'close' }, animate: { enter: { type: 'pathIn', duration: 1000 } }, }); chart.render(); ``` 可把`type`设置为`null`、`undefined` 或 `false` 来关闭动画: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'line', autoFit: true, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/551d80c6-a6be-4f3c-a82a-abd739e12977.csv', }, encode: { x: 'date', y: 'close' }, animate: { enter: { type: 'false' } }, }); chart.render(); ``` 可通过`animate: false`来关闭所有动画: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'line', autoFit: true, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/551d80c6-a6be-4f3c-a82a-abd739e12977.csv', }, encode: { x: 'date', y: 'close' }, animate: false, }); chart.render(); ``` ## 基本使用 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'interval', autoFit: true, 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' }, animate: { enter: { type: 'fadeIn', duration: 1000 }, }, }); chart.render(); ``` ## 动画属性 标记是通过`mark.animate`指定动画属性的,一共有三个部分的动画可以指定: **enter**- 新增的图形 **update**- 更新的图形 **exit**- 删除的图形 ## 配置选项 ### 动画配置完整选项 | 属性 | 描述 | 类型 | 默认值 | 必选 | | -------------- | ---------------------------- | ---------------------- | ---------- | ---- | | enter | 入场动画配置 | `EnterAnimateOptions` | - | | | update | 更新动画配置 | `UpdateAnimateOptions` | - | | | exit | 退出动画配置 | `ExitAnimateOptions` | - | | | enterType | 入场动画类型 | `string` | `fadeIn` | | | enterDuration | 入场动画持续时间(毫秒) | `number` | `300` | | | enterDelay | 入场动画延迟时间(毫秒) | `number` | `0` | | | enterEasing | 入场动画缓动函数 | `string` | `ease` | | | enterFill | 入场动画非运行状态的展示效果 | `Fill` | `both` | | | updateType | 更新动画类型 | `string` | `morphing` | | | updateDuration | 更新动画持续时间(毫秒) | `number` | `300` | | | updateDelay | 更新动画延迟时间(毫秒) | `number` | `0` | | | updateEasing | 更新动画缓动函数 | `string` | `ease` | | | updateFill | 更新动画非运行状态的展示效果 | `Fill` | `both` | | | exitType | 退出动画类型 | `string` | `fadeOut` | | | exitDuration | 退出动画持续时间(毫秒) | `number` | `300` | | | exitDelay | 退出动画延迟时间(毫秒) | `number` | `0` | | | exitEasing | 退出动画缓动函数 | `string` | `ease` | | | exitFill | 退出动画非运行状态的展示效果 | `Fill` | `both` | | 复杂类型说明: - `EnterAnimateOptions`:入场动画配置对象,包含 type、duration、delay、easing、fill 属性 - `UpdateAnimateOptions`:更新动画配置对象,包含 type、duration、delay、easing、fill 属性 - `ExitAnimateOptions`:退出动画配置对象,包含 type、duration、delay、easing、fill 属性 - `Fill`:动画填充模式,可选值为 `'auto'`、`'none'`、`'forwards'`、`'backwards'`、`'both'` ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'interval', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/deaths.json', }, encode: { x: 'Month', y: 'Death', color: 'Type' }, transform: [ { type: 'stackY' }, { type: 'stackEnter', groupBy: ['color', 'x'], duration: 2000 }, ], scale: { y: { type: 'sqrt' } }, coordinate: { type: 'polar' }, animate: { enter: { type: 'waveIn' } }, axis: { y: false }, }); chart.render(); ``` ### 动画类型 Type 动画类型 `Type` 决定了动画的视觉效果。G2 提供了多种内置动画类型,也可设置为 `null`、`undefined` 或 `false` 来关闭动画。常见的动画类型包括: | 动画类型 | 作用 | | --------- | -------------------------- | | fadeIn | 淡入效果,图形从透明到可见 | | growInX | 沿 X 轴方向生长效果 | | growInY | 沿 Y 轴方向生长效果 | | scaleInX | 沿 X 轴缩放进入效果 | | scaleInY | 沿 Y 轴缩放进入效果 | | zoomIn | 整体缩放进入效果 | | pathIn | 沿路径进入效果 | | waveIn | 波浪形进入效果 | | morphing | 形变效果,平滑过渡图形变化 | | fadeOut | 淡出效果,图形从可见到透明 | | scaleOutX | 沿 X 轴缩放退出效果 | | scaleOutY | 沿 Y 轴缩放退出效果 | | zoomOut | 整体缩放退出效果 | ### 缓动函数 Easing 缓动函数控制动画过程中视觉属性变化的插值方式。G2 支持以下内置缓动函数(来自 [easings.net](https://easings.net/)): | constant | accelerate | decelerate | accelerate-decelerate | decelerate-accelerate | | ---------- | ------------------ | -------------- | --------------------- | --------------------- | | linear | ease-in / in | ease-out / out | ease-in-out / in-out | ease-out-in / out-in | | ease | in-sine | out-sine | in-out-sine | out-in-sine | | steps | in-quad | out-quad | in-out-quad | out-in-quad | | step-start | in-cubic | out-cubic | in-out-cubic | out-in-cubic | | step-end | in-quart | out-quart | in-out-quart | out-in-quart | | | in-quint | out-quint | in-out-quint | out-in-quint | | | in-expo | out-expo | in-out-expo | out-in-expo | | | in-circ | out-circ | in-out-circ | out-in-circ | | | in-back | out-back | in-out-back | out-in-back | | | in-bounce | out-bounce | in-out-bounce | out-in-bounce | | | in-elastic | out-elastic | in-out-elastic | out-in-elastic | | | spring / spring-in | spring-out | spring-in-out | spring-out-in | ### 动画填充 Fill 动画填充属性规定了图形在动画非运行状态(如开始前、结束后)时的展示效果: - `auto`/`none` - 默认值,动画在第一帧开始前和最后一帧结束后不影响图形展示效果 - `forwards` - 动画完成后停住,不恢复到初始状态 - `backwards` - 动画开始前应用第一帧效果 - `both` - 同时应用 `forwards` 和 `backwards` 的效果 ## 动画编码 G2 支持将数据值映射到动画属性上,使动画也具备数据可视化的意义。通过 `encode` 可以将数据字段映射到动画属性,如 `enterDuration`、`enterDelay` 等。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'interval', autoFit: true, data: [ { name: 'event planning', startTime: 1, endTime: 4 }, { name: 'layout logistics', startTime: 3, endTime: 13 }, { name: 'select vendors', startTime: 5, endTime: 8 }, { name: 'hire venue', startTime: 9, endTime: 13 }, { name: 'hire caterer', startTime: 10, endTime: 14 }, { name: 'hire event decorators', startTime: 12, endTime: 17 }, { name: 'rehearsal', startTime: 14, endTime: 16 }, { name: 'event celebration', startTime: 17, endTime: 18 }, ], encode: { x: 'name', y: ['endTime', 'startTime'], color: 'name', enterDuration: (d) => (d.endTime - d.startTime) * 300, enterDelay: (d) => d.startTime * 100, }, coordinate: { transform: [{ type: 'transpose' }] }, }); chart.render(); ``` ## 分组动画 G2 提供了 `stackEnter` 标记转换来实现分组动画,使图形按照特定规则依次出现。该转换会先对图形进行分组,然后将它们的出现时间和持续时间在空间上进行堆叠。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'view', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/doughnut-purchases.json', }, children: [ { type: 'line', encode: { x: 'year', y: 'count', color: 'year', shape: 'smooth' }, scale: { y: { zero: true, nice: true } }, style: { gradient: 'x', gradientColor: 'start' }, animate: { enter: { type: 'pathIn', duration: 3000 } }, axis: { y: { labelFormatter: '~s' } }, }, { type: 'point', encode: { x: 'year', y: 'count', color: 'year', shape: 'point' }, transform: [{ type: 'stackEnter' }], animate: { enter: { duration: 300 } }, }, { type: 'text', encode: { x: 'year', y: 'count', text: 'year' }, transform: [{ type: 'stackEnter' }], style: { lineWidth: 5, stroke: '#fff', textAlign: 'center', dy: -8 }, animate: { enter: { duration: 300 } }, }, ], }); chart.render(); ``` ## 关键帧动画 G2 提供了制作关键帧动画的能力,可以在不同视图间平滑过渡,展示数据变化。通过 `timingKeyframe` 创建时间容器,使用 `key` 和 `groupKey` 通道指定元素之间的对应关系。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); const data = [ { gender: 'female', height: 161.2, weight: 51.6 }, { gender: 'female', height: 167.5, weight: 59 }, { gender: 'female', height: 159.5, weight: 49.2 }, { gender: 'female', height: 157, weight: 63 }, { gender: 'female', height: 155.8, weight: 53.6 }, { gender: 'female', height: 170, weight: 59 }, { gender: 'man', height: 159.1, weight: 47.6 }, { gender: 'man', height: 166, weight: 69.8 }, { gender: 'man', height: 176.2, weight: 66.8 }, { gender: 'man', height: 160.2, weight: 75.2 }, { gender: 'man', height: 172.5, weight: 55.2 }, ]; chart.options({ type: 'timingKeyframe', autoFit: true, direction: 'alternate', iterationCount: 2, children: [ { type: 'interval', data, encode: { x: 'gender', y: 'weight', color: 'gender', key: 'gender' }, transform: [{ type: 'groupX', y: 'mean' }], }, { type: 'point', data, encode: { x: 'height', y: 'weight', color: 'gender', shape: 'point', groupKey: 'gender', }, }, ], duration: 1000, }); chart.render(); ``` ## Lottie 动画 `Lottie`能极大丰富动画的描述能力。 lottie 具体实现可以参考:[lottie](/zh/manual/extra-topics/plugin/lottie/)。 ## 时序动画 时序动画(timingSequence)还在开发中,敬请期待。 #### G2 图表组成 - Canonical page: https://g2.antv.antgroup.com/zh/manual/core/chart/chart-component/ - Markdown source: https://g2.antv.antgroup.com/zh/manual/core/chart/chart-component.md - Description: G2 图表组成 --- title: "G2 图表组成" description: "G2 图表组成" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/manual/core/chart/chart-component/" version: "5.4.8" --- 为了更好地使用 G2 进行数据可视化,我们需要了解 G2 图表的组成以及相关概念。 ## 图表组件 下面是一个基本的 G2 图表,图表由 **组件(Component)** 和 **标记(Mark)** 组成。 需要特别注意的是,G2 5.0 中不再需要单独配置标注(Annotation),标注也是一种标记,或者说某些标记也可以用来做标注,比如 Text,Image, Line 等标记。 chart-component ### 标题 title 用于一句话概要说明图表要展示的数据,图表的标题是比较常用的组件,支持标题和副标题,以及他们的样式和位置设置。 查看 [Title](/zh/manual/component/title/) 教程获取更多信息。 ### 坐标轴 axis 绘制坐标轴,目前支持直角坐标系坐标轴与极坐标系坐标轴。 每个坐标轴由坐标轴线(line)、刻度(tick)、刻度值(label)、标题(title)以及网格线(grid)组成。 查看 [Axis](/zh/manual/component/axis/) 教程获取更多信息。 ### 图例 legend 绘制图例,G2 提供了两种图例类型:分类图例(Category Legend)和连续图例(Continuous Legend),分别用于展示分类数据和连续数据。 查看 [Legend](/zh/manual/component/legend/) 教程获取更多信息。 ### 滚动条 scrollbar 滚动条(scrollbar)是一个交互组件,当显示区域大小不足以容纳全部信息时,可以将超出部分隐藏,并通过滚动条的垂直或横向滑动来显示出被隐藏部分。 内容是否超出显示区域取决于内容的多少以及显示区域的尺寸,当需要显示的内容在纵向方向上超过显示区域的大小时,应当使用垂直滚动条以控制显示的部分,横向滚动条同理。 查看 [Scrollbar](/zh/manual/component/scrollbar/) 教程获取更多信息。 ### 缩略轴 slider 缩略轴(slider)是一种辅助看数据的组件,它将大量数据浓缩到一个轴上,既可以缩小宏观看数据全貌,又可以放大微观看数据的片段,同时还可以拖拽观察数据在一定区间内的演变。 缩略轴是值域数据的浓缩,它跟位置通道 x, y 对应的比例尺的类型息息相关。一般来说时间类型上使用缩略轴的频率高,连续轴使用缩略轴频次低、分类轴几乎不会使用到缩略轴。 查看 [Slider](/zh/manual/component/slider/) 教程获取更多信息。 ### 提示信息 tooltip 当鼠标悬停在某个点上时,会以提示框的形式显示当前点对应的数据的信息,比如该点的值,数据单位等。数据提示框内提示的信息还可以通过格式化函数动态指定。 查看 [Tooltip](/zh/manual/component/tooltip/) 教程获取更多信息。 ### 数据标签 label G2 中数据标签(Label) 是给图表添加标注的手段之一。 查看 [Label](/zh/manual/component/label/) 教程获取更多信息。 ## 图表布局 ### 使用方式 不管是单视图图表还是多视图图表,布局信息(图表宽高等)都可以在选项顶层进行指定。 ```js // 标记层级 const markLevel = { type: 'interval', width: 640, height: 180, margin: 10, }; // 视图层级 const viewLevel = { type: 'view', width: 640, height: 180, margin: 10, // ... }; // 多视图图表 const compositionLevel = { type: 'spaceFlex', width: 640, height: 180, margin: 10, }; ``` 同时,也可以在初始化 `Chart` 对象的时候指定: ```js const chart = new Chart({ type: 'view', width: 640, height: 180, margin: 10, // ... }); ``` 也可以通过 spec 顶层属性指定: ```js chart.options({ type: 'interval', padding: 10, margin: 20 }); chart.options({ type: 'view', padding: 10, margin: 20 }); chart.options({ type: 'spaceFlex', padding: 10, margin: 20 }); ``` ### 视图模型 chart-component 在 G2 中,**视图模型** 用于定义图表视图的划分方式,根据划分生成的不同区域可以绘制不同的内容,并通过相应选项进行配置。可以将视图简单理解为一个独立的图表。G2 的视图模型结构如下: - **视图区域(View Area)**:表示图表的整体视图区域。在设置图表的宽度和高度时,生效的范围即为视图区域的宽度和高度。视图区域与绘制区域之间的部分称为 **外边距范围**,该区域的大小可通过配置 margin 属性进行调整,通常用于设置固定组件(如坐标轴、图例等)与边界的距离。 - **绘制区域(Plot Area)**:表示图表的绘制区域。绘制区域与主区域之间的部分称为 **内边距范围**,该区域的大小可通过配置 padding 属性进行调整,通常用于绘制图表组件,例如`title`、`legend`、`axis`等。 - **主区域(Main Area)**:表示图表内容绘制的主要区域。主区域与内容区域之间的部分称为 **呼吸范围**,该区域的大小可通过配置 inset 属性进行调整,用于制造组件和标记(图形元素)的间距,从而防止重叠,对于散点图尤其有用。 - **内容区域(Content Area)**:表示标记绘制的内容区域。主要用于绘制标记(图形元素)。 其中内容区域的大小由以下的公式得到: ```js const contentWidth = width - paddingLeft - paddingRight - marginLeft - marginRight - insetLeft - insetRight; const contentHeight = height - paddingTop - paddingBottom - marginTop - marginBottom - insetTop - insetBottom; ``` ### 布局算法 G2 在内部自己实现了一套布局算法,负责协调外边距( `margin` )、内边距( `padding` )、呼吸范围宽度( `inset` )等布局参数的计算,确保坐标轴、图例等组件在图表容器中合理布局。 #### 动态计算 也许你曾经有过疑惑,为什么在配置里手动声明 `padding` 为 `0`, 图表的 x 轴显示不全了。要回答这个问题,需要深入探究 G2 布局算法里动态计算的部分。 在 G2 的布局算法中,首先会获取所有传入的 `padding`、`margin`、`inset`属性,如果没有设置,`padding`(包括 `paddingTop`、`paddingLeft` 等)会被默认赋值为 `auto`,`margin` 会被默认赋值为 `16`,而 `inset` 会被默认赋值为 `0`。接下来将图表组件按照位置进行分组,方便后续布局计算。 以 `position = 'top'` 为例,如果配置了明确的 `paddingTop` 时,不会触发动态计算 `paddingTop` 的逻辑,此时对于未定义 `size` 的组件,使用内部定义的默认尺寸 `defaultSize`;对于分组(group)组件,递归执行上述操作,然后取最大的子组件尺寸作为父组件的尺寸;如果组件中包括坐标轴(axis)组件且未显式设置 `labelAutoHide`,设置 `labelAutoHide` 为 true,自动隐藏坐标轴标签以避免溢出。 如果没有配置 `paddingTop`,则会触发动态计算的逻辑,根据组件的实际尺寸累加边距(`crossPadding` 默认为 `12`),得出最后的实际`paddingTop` 大小。 要回答最初的问题:在 G2 中,内边距范围( `padding` )用于为图表组件预留展示空间。如果手动将 `padding` 设置为 `0` ,会关闭内部的自适应计算逻辑,从而可能导致图表组件显示不完整。因此,当需要个性化配置时,应确保为组件预留足够的空间;否则,建议使用默认的自适应逻辑来避免显示问题。 #### 防挤压机制 G2 内部对于图表显示区域设置了一个兜底机制,当视图中存在标记元素(如`line`、`interval`等),触发防挤压机制,来确保图表的主区域(Main Area)最小占比 **1/4** 。以水平方向为例,假设绘制区域的大小为 `plotWidth`,如果 `plotWidth` 减去动态计算后的左内边距 `pl0` 和 右内边距 `pr0` 后小于 `plotWidth * 1/4` 时,会优先保证主区域大小为 `plotWidth * 1/4`,将内边距等比例缩小,当用户指定 `paddingLeft` 时会动态计算 `paddingRight` 的大小,按比例分配剩余空间,尽量避免左右外边距同时设置为固定值,可能会导致自适应算法失效。垂直方向的计算规则同理。 ### 配置项 | 属性 | 描述 | 类型 | 默认值 | 必选 | | ------------- | ---------------------------------------------- | ----------------------- | ---------------------------- | ---- | | autoFit | 开启后图表的宽高和容器保持一致 | boolean | false | | | width | 设置图表的宽度 | number | `640` | | | height | 设置图表的高度 | number | `480` | | | viewStyle | 设置图表的视图样式 | [viewStyle](#viewstyle) | 详见 [viewStyle](#viewstyle) | | | margin | 设置外边距四个方向的值,优先级别比分别设置低 | number | `16` | | | marginLeft | 设置左外边距值 | number | `16` | | | marginTop | 设置上外边距值 | number | `16` | | | marginRight | 设置右外边距值 | number | `16` | | | marginBottom | 设置下外边距值 | number | `16` | | | padding | 设置内边距四个方向的值,优先级别比分别设置低 | number | `auto` | | | paddingLeft | 设置左内边距值 | number | `auto` | | | paddingTop | 设置上内边距值 | number | `auto` | | | paddingRight | 设置右内边距值 | number | `auto` | | | paddingBottom | 设置下内边距值 | number | `auto` | | | inset | 设置呼吸范围四个方向的值,优先级别比分别设置低 | number | `0` | | | insetLeft | 设置左呼吸范围宽度 | number | `0` | | | insetTop | 设置上呼吸范围宽度 | number | `0` | | | insetRight | 设置右呼吸范围宽度 | number | `0` | | | insetBottom | 设置下呼吸范围宽度 | number | `0` | | 尝试一下: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); chart.options({ type: 'point', height: 600, width: 700, margin: 100, padding: 60, paddingLeft: 100, // 单独设置paddingLeft的优先级比padding高 insetLeft: 30, insetRight: 30, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/commits.json', }, encode: { x: (d) => new Date(d.time).getUTCHours(), y: (d) => new Date(d.time).getUTCDay(), size: 'count', shape: 'point', }, transform: [{ type: 'group', size: 'sum' }, { type: 'sortY' }], scale: { y: { type: 'point' } }, style: { shape: 'point', fill: '#76b7b2' }, axis: { x: { title: 'time (hours)', tickCount: 24 }, y: { title: 'time (day)', grid: true }, }, legend: false, viewStyle: { viewFill: '#DCEEFE', plotFill: '#A2D4F6', mainFill: '#FFC6A1', contentFill: '#FF8E72', }, }); chart.render(); ``` #### autoFit 如果希望图表的宽高和容器保持一致,那么可以将 `options.autoFit` 设置为 `true`,其优先级比指定宽高低。 ```js ({ type: 'view', autoFit: true }); ``` #### viewStyle 配置图表的视图样式。 | 属性 | 描述 | 类型 | 默认值 | 必选 | | ------- | ------------------ | ------------------- | ------------------------ | ---- | | view | 配置视图区域的样式 | [view](#view) | 详见 [view](#view) | | | plot | 配置绘制区域的样式 | [plot](#plot) | 详见 [plot](#plot) | | | main | 配置主区域的样式 | [main](#main) | 详见 [main](#main) | | | content | 配置内容区域的样式 | [content](#content) | 详见 [content](#content) | | ##### view 配置图表的视图区域的样式。 | 属性 | 描述 | 类型 | 默认值 | 必选 | | ----------------- | -------------------------------- | --------------- | --------- | ---- | | viewRadius | 图表的视图区域的四个圆角半径大小 | number | `0` | | | viewFill | 图表的视图区域填充颜色 | string | - | | | viewFillOpacity | 图表的视图区域填充透明度 | number | - | | | viewStroke | 图表的视图区域描边颜色 | string | - | | | viewStrokeOpacity | 图表的视图区域描边透明度 | number | - | | | viewLineWidth | 图表的视图区域描边宽度 | number | - | | | viewLineDash | 图表的视图区域描边虚线配置 | [number,number] | - | | | viewOpacity | 图表的视图区域整体透明度 | number | - | | | viewShadowColor | 图表的视图区域阴影颜色 | string | - | | | viewShadowBlur | 图表的视图区域阴影模糊系数 | number | - | | | viewShadowOffsetX | 图表的视图区域阴影水平偏移 | number | - | | | viewShadowOffsetY | 图表的视图区域阴影垂直偏移 | number | - | | | viewCursor | 图表的视图区域鼠标样式 | string | `default` | | ##### plot 配置图表的绘制区域的样式。 | 属性 | 描述 | 类型 | 默认值 | 必选 | | ----------------- | -------------------------------- | --------------- | --------- | ---- | | plotRadius | 图表的绘制区域的四个圆角半径大小 | number | `0` | | | plotFill | 图表的绘制区域填充颜色 | string | - | | | plotFillOpacity | 图表的绘制区域填充透明度 | number | - | | | plotStroke | 图表的绘制区域描边颜色 | string | - | | | plotStrokeOpacity | 图表的绘制区域描边透明度 | number | - | | | plotLineWidth | 图表的绘制区域描边宽度 | number | - | | | plotLineDash | 图表的绘制区域描边虚线配置 | [number,number] | - | | | plotOpacity | 图表的绘制区域整体透明度 | number | - | | | plotShadowColor | 图表的绘制区域阴影颜色 | string | - | | | plotShadowBlur | 图表的绘制区域阴影模糊系数 | number | - | | | plotShadowOffsetX | 图表的绘制区域阴影水平偏移 | number | - | | | plotShadowOffsetY | 图表的绘制区域阴影垂直偏移 | number | - | | | plotCursor | 图表的绘制区域鼠标样式 | string | `default` | | ##### main 配置图表的主区域的样式。 | 属性 | 描述 | 类型 | 默认值 | 必选 | | ----------------- | ------------------------------ | --------------- | --------- | ---- | | mainRadius | 图表的主区域的四个圆角半径大小 | number | `0` | | | mainFill | 图表的主区域填充颜色 | string | - | | | mainFillOpacity | 图表的主区域填充透明度 | number | - | | | mainStroke | 图表的主区域描边颜色 | string | - | | | mainStrokeOpacity | 图表的主区域描边透明度 | number | - | | | mainLineWidth | 图表的主区域描边宽度 | number | - | | | mainLineDash | 图表的主区域描边虚线配置 | [number,number] | - | | | mainOpacity | 图表的主区域整体透明度 | number | - | | | mainShadowColor | 图表的主区域阴影颜色 | string | - | | | mainShadowBlur | 图表的主区域阴影模糊系数 | number | - | | | mainShadowOffsetX | 图表的主区域阴影水平偏移 | number | - | | | mainShadowOffsetY | 图表的主区域阴影垂直偏移 | number | - | | | mainCursor | 图表的主区域鼠标样式 | string | `default` | | ##### content 配置图表的内容区域的样式。 | 属性 | 描述 | 类型 | 默认值 | 必选 | | -------------------- | -------------------------------- | --------------- | --------- | ---- | | contentRadius | 图表的内容区域的四个圆角半径大小 | number | `0` | | | contentFill | 图表的内容区域填充颜色 | string | - | | | contentFillOpacity | 图表的内容区域填充透明度 | number | - | | | contentStroke | 图表的内容区域描边颜色 | string | - | | | contentStrokeOpacity | 图表的内容区域描边透明度 | number | - | | | contentLineWidth | 图表的内容区域描边宽度 | number | - | | | contentLineDash | 图表的内容区域描边虚线配置 | [number,number] | - | | | contentOpacity | 图表的内容区域整体透明度 | number | - | | | contentShadowColor | 图表的内容区域阴影颜色 | string | - | | | contentShadowBlur | 图表的内容区域阴影模糊系数 | number | - | | | contentShadowOffsetX | 图表的内容区域阴影水平偏移 | number | - | | | contentShadowOffsetY | 图表的内容区域阴影垂直偏移 | number | - | | | contentCursor | 图表的内容区域鼠标样式 | string | `default` | | 在配置视图样式的时候,不是以对象的形式来配置,而是以 `view`、`plot`、`main`、`content`前缀加属性的方式来配置。 ```js ({ viewStyle: { // 配置图表的视图区域的样式 viewFill: '#DCEEFE', viewRadius: 50, // 配置图表的绘制区域的样式 plotFill: '#fff', plotFillOpacity: 0.45, plotStroke: 'yellow', plotLineWidth: 4, // 配置图表的主区域的样式 mainFill: 'l(270) 0:#ffffff 0.5:#7ec2f3 1:#1890ff', mainFillOpacity: 0.75, // 配置图表的内容区域的样式 contentFill: 'l(90) 0:#ffadad 0.5:#ffd6a5 1:#fdffb6', contentShadowColor: '#5d5d5d', contentShadowBlur: 40, contentShadowOffsetX: 5, contentShadowOffsetY: 5, }, }); ``` 尝试一下: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container' }); chart.options({ viewStyle: { // 配置图表的视图区域的样式 viewFill: '#DCEEFE', viewRadius: 20, // 配置图表的绘制区域的样式 plotFill: '#fff', plotFillOpacity: 0.45, plotStroke: 'yellow', plotLineWidth: 4, // 配置图表的主区域的样式 mainFill: 'l(270) 0:#ffffff 0.5:#7ec2f3 1:#1890ff', mainFillOpacity: 0.75, // 配置图表的内容区域的样式 contentFill: 'l(90) 0:#ffadad 0.5:#ffd6a5 1:#fdffb6', contentShadowColor: '#5d5d5d', contentShadowBlur: 40, contentShadowOffsetX: 5, contentShadowOffsetY: 5, }, type: 'area', data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/aapl.json', }, encode: { x: (d) => new Date(d.date), y: 'close', }, axis: false, style: { fill: 'l(270) 0:#ffffff 0.5:#7ec2f3 1:#1890ff', fillOpacity: 0.9, }, height: 350, width: 700, margin: 30, padding: 20, inset: 15, legend: false, }); chart.render(); ``` #### 概览 - Canonical page: https://g2.antv.antgroup.com/zh/manual/core/composition/overview/ - Markdown source: https://g2.antv.antgroup.com/zh/manual/core/composition/overview.md - Description: 通过 G2 组合视图创建多图布局、分面和多标记图表,使用 children 描述层级并复用数据与配置。 --- title: "概览" description: "通过 G2 组合视图创建多图布局、分面和多标记图表,使用 children 描述层级并复用数据与配置。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/manual/core/composition/overview/" version: "5.4.8" --- G2 中**视图复合(View Composition)** 提供了在一个可视化中绘制多个图表的能力。G2 定义了一个**视图树(View Graph)** 去描述**多视图图表(Multi-View Plot)**。 ```js ({ type: 'spaceLayer', children: [{ type: 'view' }, { type: 'view' }], }); ``` ## 空间 最基础的视图复合方式就是**空间复合(Space Composition)**,只是对空间进行划分。 一个比较常见的复合方式是 `composition.spaceLayer`:将多个图表重叠在一起。使用场景是这些视图拥有的不同的坐标系,比如下面的条形图和饼图。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); const data = [ { genre: 'Shooter', sold: 350 }, { genre: 'Sports', sold: 275 }, { genre: 'Other', sold: 150 }, { genre: 'Action', sold: 120 }, { genre: 'Strategy', sold: 115 }, ]; chart.options({ type: 'spaceLayer', children: [ // 条形图 { type: 'interval', data, encode: { x: 'genre', y: 'sold' }, }, // 饼图 { type: 'interval', paddingLeft: 300, paddingBottom: 250, coordinate: { type: 'theta' }, transform: [{ type: 'stackY' }], data, encode: { y: 'sold', color: 'genre' }, legend: { color: false }, }, ], }); chart.render(); ``` 同时也可以使用 `composition.spaceFlex` 去让视图水平或者竖直排列。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); const data = [ { genre: 'Shooter', sold: 350 }, { genre: 'Sports', sold: 275 }, { genre: 'Other', sold: 150 }, { genre: 'Action', sold: 120 }, { genre: 'Strategy', sold: 115 }, ]; chart.options({ type: 'spaceFlex', children: [ // 条形图 { type: 'interval', data, encode: { x: 'genre', y: 'sold' }, }, // 饼图 { type: 'interval', coordinate: { type: 'theta' }, transform: [{ type: 'stackY' }], data, encode: { y: 'sold', color: 'genre' }, legend: { color: false }, }, ], }); chart.render(); ``` 同时这些复合方式是可以嵌套的,所以很容易通过一个单独的声明去实现一个报表。 ## 分面 **分面复合(Facet Composition)** 和空间复合的不同在于:它还会对数据划分,每个视图展现原始数据的一个子集。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', height: 260, width: 800, paddingLeft: 40, paddingBottom: 50, }); chart.options({ type: 'facetRect', data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/anscombe.json', }, // 将数据按照 series 字段划分成一个个子集, // 并且是 x 方向排列 encode: { x: 'series' }, children: [ { type: 'point', padding: 'auto', inset: 10, encode: { x: 'x', y: 'y' }, style: { stroke: '#000' }, }, ], }); chart.render(); ``` ## 重复 **重复复合(Repeat Composition)** 和分面的区别在于:它的每个视图展现的是全量数据,只不过会对编码进行重复,从而绘制出多个视图。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', width: 900, height: 900, padding: 'auto', paddingLeft: 55, paddingBottom: 45, }); chart.options({ type: 'repeatMatrix', data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/penguins.json', // 数据处理 }, // 指定需要重复的编码 // 一共会生成 4 * 4 = 16 个视图 // 每个视图的 x 和 y 编码是下面字段的叉乘 encode: { position: [ 'culmen_length_mm', 'culmen_depth_mm', 'flipper_length_mm', 'body_mass_g', ], }, children: [ { type: 'point', padding: 'auto', encode: { color: 'species' } }, ], }); chart.render(); ``` ## 时间 **时间复合**在空间上管理视图,用于做动画。 ```ts import { Chart } from '@antv/g2'; fetch( 'https://gw.alipayobjects.com/os/bmw-prod/fbe4a8c1-ce04-4ba3-912a-0b26d6965333.json', ) .then((res) => res.json()) .then((data) => { const chart = new Chart({ container: 'container', paddingTop: 60, paddingLeft: 100, }); // 参考 css animation 的描述 chart.options({ type: 'timingKeyframe', iterationCount: 2, // 迭代次数 direction: 'alternate', // 方向 duration: 1000, // 持续时间 children: [ { type: 'interval', transform: [{ type: 'groupX', y: 'mean' }], data, encode: { x: 'gender', y: 'weight', color: 'gender', key: 'gender' }, }, { type: 'point', data, encode: { x: 'height', y: 'weight', color: 'gender', shape: 'point', groupKey: 'gender', }, }, ], }); chart.render(); }); ``` #### G2 配置项总览 - Canonical page: https://g2.antv.antgroup.com/zh/manual/core/configuration/ - Markdown source: https://g2.antv.antgroup.com/zh/manual/core/configuration.md - Description: 查阅 G2 图表初始化和 Spec 配置层级,了解数据、编码、比例尺、坐标系、样式、交互与组合视图的选项。 --- title: "G2 配置项总览" description: "查阅 G2 图表初始化和 Spec 配置层级,了解数据、编码、比例尺、坐标系、样式、交互与组合视图的选项。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/manual/core/configuration/" version: "5.4.8" --- 本文档详细展示了 G2 的完整配置体系,涵盖了所有可用的配置选项和层级关系。 ## 图表初始化配置 在创建图表实例时,可以通过 `new Chart(params)` 传入以下配置项: | 配置项 | 类型 | 默认值 | 描述 | |--------|------|--------|------| | **container** | `string \| HTMLElement` | - | 指定图表绘制的 DOM,可以传入 DOM id 或 DOM 实例 | | **autoFit** | `boolean` | `false` | 图表是否自适应容器宽高 | | **clip** | `boolean` | `false` | 是否隐藏超出绘制区域的图形 | | **width** | `number` | `640` | 图表宽度 | | **height** | `number` | `480` | 图表高度 | | **depth** | `number` | `0` | 图表深度,在 3D 图表中使用 | | **padding** | `'auto' \| number` | `'auto'` | 图表内边距,使用方式参考 CSS 盒模型 | | **margin** | `number` | `16` | 图表外边距,使用方式参考 CSS 盒模型 | | **inset** | `number` | `0` | 图表呼吸范围宽度 | | **renderer** | `Canvas \| SVG \| WebGL` | `Canvas` | 指定渲染引擎 | | **theme** | `'classic' \| 'classicDark' \| 'academy' \| customTheme` | - | 配置图表主题 | | **plugins** | `any[]` | - | 指定渲染时使用的插件 | **示例:** ```javascript const chart = new Chart({ container: 'container', // DOM 容器 autoFit: true, // 自适应容器大小 clip: true, // 裁剪超出区域 padding: 20, // 内边距 margin: 16, // 外边距 theme: 'classicDark', // 深色主题 plugins: [new Plugin()] // 插件 }); ``` ## 图表配置 创建图表实例后,推荐通过 `chart.options(spec)` 传入 Spec 配置: ```javascript chart.options({ type: 'interval', data: data, encode: { x: 'field1', y: 'field2' }, scale: { x: { type: 'band' } }, axis: { x: { title: 'X轴标题' } } }); ``` 以下是完整的图表配置项层级关系: [Interactive content]() #### 概览 - Canonical page: https://g2.antv.antgroup.com/zh/manual/core/coordinate/overview/ - Markdown source: https://g2.antv.antgroup.com/zh/manual/core/coordinate/overview.md - Description: 了解 G2 坐标系及其转换,配置笛卡尔坐标、极坐标和转置等变换,控制图表的空间布局。 --- title: "概览" description: "了解 G2 坐标系及其转换,配置笛卡尔坐标、极坐标和转置等变换,控制图表的空间布局。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/manual/core/coordinate/overview/" version: "5.4.8" --- 在可视化的视觉通道(Channels)中,位置(Position)是最具有感知优势的视觉编码方式。它不仅具有最高的识别准确度,而且能够同时有效地表达定性数据(类别)和定量数据。为了系统地组织这种空间位置映射,我们引入了 **坐标系(Coordinate)** 的概念。在 G2 的实现中,坐标系统负责管理一系列的空间变换。具体来说,图形标记(Mark)的位置属性(x 和 y)首先会通过比例尺(Scale)映射到标准化的 `[0, 1]` 区间,随后坐标系统会将这些标准化的位置信息转换为最终的画布坐标,从而实现不同空间布局形式的可视化表达。 - 坐标系配置主要关注坐标转换,起始结束弧度,内外半径等,有关坐标轴刻度,刻度值配置等内容请前往 [图表组件-坐标轴](/zh/manual/component/axis/)。 ## 配置层级 坐标系可以设置在 View 层级: ```js ({ type: 'view', coordinate: { type: 'polar' }, }); ``` 每一个视图只能拥有一个坐标系。坐标系除了本身的属性之外,还包含一系列**坐标系变换(Coordinate Transform)**。 ```js ({ type: 'polar', // 类型 innerRadius: 0.6, // 本身的属性 outerRadius: 0.8, transform: [{ type: 'transpose' }], // 坐标系变换 }); ``` 也可以设置在 Mark 层级: ```js ({ type: 'interval', coordinate: { type: 'polar' }, }); ``` 标记层级的坐标系拥有 **冒泡性**。标记层级的坐标系会和视图的坐标系进行合并,并且第一个标记的坐标系优先级最高。 ```js chart.options({ type: 'view', coordinate: { type: 'theta' }, children: [ { type: 'line', coordinate: { type: 'polar' } }, { type: 'area', coordinate: { type: 'radial' } }, ], }); ``` 和下面的情况等价: ```js chart.options({ type: 'view', coordinate: { type: 'polar' }, children: [ { type: 'line' }, { type: 'area' }, ], }); ``` 这个特性有利于封装和坐标系相关的复合标记,比如饼图: ```ts import { Chart } from '@antv/g2'; function Pie({ encode = {}, ...rest } = {}) { const { value, ...restEncode } = encode; return { ...rest, type: 'interval', coordinate: { type: 'theta' }, // 封装坐标系 transform: [{ type: 'stackY' }], encode: { ...restEncode, y: value, }, }; } const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: Pie, // 使用该复合 Mark data: [ { genre: 'Sports', sold: 275 }, { genre: 'Strategy', sold: 115 }, { genre: 'Action', sold: 120 }, { genre: 'Shooter', sold: 350 }, { genre: 'Other', sold: 150 }, ], encode: { value: 'sold', color: 'genre' }, labels: [ { text: 'sold', }, ], }); chart.render(); ``` ## 常见坐标系 默认的坐标系是笛卡尔坐标系,除此之外,还有一类坐标系是把图表转换到极坐标系下,用于绘制一系列"圆"形的图,这类坐标系被称为**径向坐标系(Radial Coordinate)**。 | **坐标系名称** | **描述** | **示例** | | :------------: | :------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------: | | cartesian | 笛卡尔坐标系,G2 默认的坐标系。 | | | helix | 螺旋坐标系,基于阿基米德螺旋线。 | | | parallel | 平行坐标系,将多个数据维度映射到平行排列的坐标轴。 | | | polar | 极坐标系,角度和半径构建成的二维坐标系。 |   | | radar | 雷达坐标系,结合了平行坐标系和极坐标系的特点,用于绘制雷达图。 |   | | radial | 一种特殊的极坐标系,它通过将极坐标系进行转置得到,常用于绘制玉珏图等。 | | | theta | 一种特殊的极坐标系,半径长度固定,仅仅将数据映射到角度,常用于饼图的绘制。 |   | ### Cartesian 笛卡尔坐标系,G2 中默认的坐标系。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'interval', data: [ { genre: 'Sports', sold: 0 }, { genre: 'Strategy', sold: 115 }, { genre: 'Action', sold: 120 }, { genre: 'Shooter', sold: 350 }, { genre: 'Other', sold: 150 }, ], encode: { x: 'genre', y: 'sold', color: 'genre' }, style: { minHeight: 50 }, }); chart.render(); ``` ### Helix 螺旋坐标系是一种基于阿基米德螺旋线的极坐标系变体,通过螺旋形参数化方式将数据映射到平面上。它继承了极坐标系的角度-半径维度特性,但增加了螺旋线的动态扩展特征,特别适合展示具有周期性、累积性或多层级关系的数据。 **坐标变换**: - 半径扩展:r = r0 + kθ(r0 为起始半径,k 为螺旋间距系数,θ 为旋转角度) - 角度映射:θ 由数据值线性转换而来,范围通过 startAngle 和 endAngle 定义 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); // mock data const data = []; const n = 31; for (let i = 0; i < 372; i++) { const now = new Date(); const currentTime = new Date(now.getTime() + i * 1000 * 3600 * 24); const formattedTime = `${currentTime.getFullYear()}.${String( currentTime.getMonth() + 1, ).padStart(2, '0')}.${String(currentTime.getDate()).padStart(2, '0')}`; data[i] = {}; data[i].time = formattedTime; const random = Math.floor(Math.random() * 10); if ((i % n > 2 && i % n < 4) || (i % n >= 6 && i % n < 7)) { data[i].value = 30 + random * 7; } else if (i % n >= 4 && i % n < 6) { data[i].value = 60 + random * 8; } else { data[i].value = 10 + random * 5; } } chart.options({ type: 'interval', height: 500, data: { value: data, }, encode: { x: 'time', y: 'value', color: 'value' }, scale: { color: { type: 'linear', range: ['#ffffff', '#1890FF'] } }, coordinate: { type: 'helix', startAngle: 0.5 * Math.PI, endAngle: 12.5 * Math.PI, }, animate: { enter: { type: 'fadeIn' } }, tooltip: { title: 'time' }, }); chart.render(); ``` ### Parallel 平行坐标系是一种用于可视化多维数据的坐标系,它将多个变量映射到平行的坐标轴上,每条数据记录表示为连接各个坐标轴上对应值的折线。 ```ts import { Chart } from '@antv/g2'; const baseAxis = { zIndex: 1, titlePosition: 'right', line: true, labelStroke: '#fff', labelLineWidth: 5, labelFontSize: 10, labelStrokeLineJoin: 'round', titleStroke: '#fff', titleFontSize: 10, titleLineWidth: 5, titleStrokeLineJoin: 'round', titleTransform: 'translate(-50%, 0) rotate(-90)', lineStroke: 'black', tickStroke: 'black', lineLineWidth: 1, }; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'line', data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/cars3.json', }, encode: { position: [ 'economy (mpg)', 'cylinders', 'displacement (cc)', 'power (hp)', 'weight (lb)', '0-60 mph (s)', 'year', ], color: 'weight (lb)', }, scale: { color: { palette: 'brBG', offset: (t) => 1 - t } }, coordinate: { type: 'parallel' }, style: { lineWidth: 1.5, strokeOpacity: 0.4 }, legend: { color: { length: 400, layout: { justifyContent: 'center' } } }, interaction: { tooltip: { series: false } }, axis: { position: baseAxis, position1: baseAxis, position2: baseAxis, position3: baseAxis, position4: baseAxis, position5: baseAxis, position6: baseAxis, position7: baseAxis, }, }); chart.render(); ``` ### Polar 极坐标系,是一种非笛卡尔坐标系,将直角坐标系的点 (x, y) 转换为极坐标系下的点 (r, θ),其中 r 是半径,θ 是角度。极坐标系在可视化中常用于展示周期性数据、相对关系或分布差异。 **坐标变换**: - 极坐标系将笛卡尔坐标系中的点 (x, y) 转换为极坐标系下的点 (r, θ)。 - 其中 r 表示到原点的距离(半径),θ 表示与 x 轴的夹角(弧度)。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); 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' }, coordinate: { type: 'polar' }, axis: { y: false }, }); chart.render(); ``` ### Radar 雷达坐标系是极坐标系的特化形式,结合平行坐标系,通过等角分布的 N 个独立维度轴构建星形网格,专为多维数据对比分析设计。每个轴代表一个独立度量维度,数据点通过封闭多边形呈现多维度特征。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); const position = ['Points', 'Rebounds', 'Assists', 'Steals', 'Blocks']; const data = [ { name: 'Jordan', Points: 30.1, Rebounds: 6.2, Assists: 5.3, Steals: 2.3, Blocks: 0.8, }, { name: 'LeBron James', Points: 27.0, Rebounds: 7.4, Assists: 7.4, Steals: 1.6, Blocks: 0.8, }, ]; chart.options({ type: 'line', title: 'Jordan vs LeBron James NBA Stats Comparison', data, coordinate: { type: 'radar' }, encode: { position: ['Points', 'Rebounds', 'Assists', 'Steals', 'Blocks'], color: 'name', }, style: { lineWidth: 1.5, strokeOpacity: 0.4, }, scale: Object.fromEntries( Array.from({ length: position.length }, (_, i) => [ `position${i === 0 ? '' : i}`, { domainMin: 0, nice: true, }, ]), ), interaction: { tooltip: { series: false } }, axis: Object.fromEntries( Array.from({ length: position.length }, (_, i) => [ `position${i === 0 ? '' : i}`, { zIndex: 1, titleFontSize: 10, titleSpacing: 8, label: true, labelFill: '#000', labelOpacity: 0.45, labelFontSize: 10, line: true, lineFill: '#000', lineOpacity: 0.25, }, ]), ), }); chart.render(); ``` ### Radial 径向坐标系是极坐标系的扩展实现,通过环形空间布局增强数据层次表现力。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); 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' }, coordinate: { type: 'radial', innerRadius: 0.1, endAngle: Math.PI }, axis: { y: false, x: { title: null, }, }, legend: false, transform: [{ type: 'sortX', by: 'y' }], }); chart.render(); ``` ### Theta Theta 坐标系是极坐标系的特化形式,通过固定半径维度、强化角度维度分析能力,专为环形数据可视化设计。在 G2 中主要应用于饼图等角度主导型图表。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); 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: { y: 'sold', color: 'genre' }, coordinate: { type: 'theta' }, transform: [{ type: 'stackY' }], }); chart.render(); ``` ## 坐标系变换 上面的坐标系都可以和坐标系变换结合使用。 坐标系变换的配置方式如下,可以同时配置多个 transform : ```js ({ coordinate: { transform: [ { type: 'transpose' }, { type: 'fisheye', focusX: 0.1, focusY: 0.1, distortionX: 4, distortionY: 4, }, ], }, }); ``` ### Transpose 比较常用的一种变换是转置变换,主要用来改变图表的方向。比如绘制水平的条形图。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); 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' }, coordinate: { transform: [{ type: 'transpose' }] }, }); chart.render(); ``` ### Fisheye 鱼眼坐标系是一种特殊的坐标系变换,它对输入的维度应用笛卡尔鱼眼效果,使得焦点区域被放大,而远离焦点的区域则被压缩。这种变换类似于鱼眼镜头的视觉效果,能够在保持全局视图的同时突出显示局部细节。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ width: 800, height: 500, padding: [40, 60, 60, 80], coordinate: { transform: [ { type: 'fisheye', focusX: 0.1, focusY: 0.1, distortionX: 4, distortionY: 4, }, ], }, type: 'point', data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/bubble.json', }, encode: { x: 'GDP', y: 'LifeExpectancy', size: 'Population', color: 'continent', shape: 'point', }, scale: { size: { type: 'log', range: [4, 20], }, x: { nice: true, }, y: { nice: true, }, }, style: { fillOpacity: 0.6, lineWidth: 1, stroke: '#fff', }, legend: { color: { position: 'bottom', layout: 'horizontal', }, size: false, }, axis: { x: { title: 'GDP', titleFill: '#333', labelFontSize: 12, }, y: { title: '预期寿命', titleFill: '#333', labelFontSize: 12, }, }, tooltip: { title: (d) => d.country, items: [ (d) => ({ name: 'GDP', value: d.GDP }), (d) => ({ name: '预期寿命', value: d.LifeExpectancy }), (d) => ({ name: '人口', value: d.Population }), ], }, }); chart.render(); ``` ## 3D 坐标系 目前我们仅支持 `cartesian3D` 坐标系,cartesian3D 在 2D 笛卡尔坐标系基础上,通过增加 Z 轴扩展而来。 ```ts import { CameraType } from '@antv/g'; import { Renderer as WebGLRenderer } from '@antv/g-webgl'; import { Plugin as ThreeDPlugin, DirectionalLight } from '@antv/g-plugin-3d'; import { Plugin as ControlPlugin } from '@antv/g-plugin-control'; import { Runtime, corelib, extend } from '@antv/g2'; import { threedlib } from '@antv/g2-extension-3d'; // Create a WebGL renderer. const renderer = new WebGLRenderer(); renderer.registerPlugin(new ThreeDPlugin()); renderer.registerPlugin(new ControlPlugin()); // Customize our own Chart with threedlib. const Chart = extend(Runtime, { ...corelib(), ...threedlib() }); const chart = new Chart({ container: 'container', renderer, depth: 400, // Define the depth of chart. }); chart.options({ type: 'point3D', data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/2c813e2d-2276-40b9-a9af-cf0a0fb7e942.csv', }, encode: { x: 'Horsepower', y: 'Miles_per_Gallon', z: 'Weight_in_lbs', color: 'Origin', shape: 'cube', }, coordinate: { type: 'cartesian3D' }, scale: { x: { nice: true, }, y: { nice: true, }, z: { nice: true, }, }, legend: false, axis: { x: { gridLineWidth: 2, }, y: { gridLineWidth: 2, titleBillboardRotation: -Math.PI / 2, }, z: { gridLineWidth: 2, }, }, }); chart.render().then(() => { const { canvas } = chart.getContext(); const camera = canvas.getCamera(); camera.setType(CameraType.ORBITING); // Add a directional light into scene. const light = new DirectionalLight({ style: { intensity: 3, fill: 'white', direction: [-1, 0, 1], }, }); canvas.appendChild(light); }); ``` #### 概览 - Canonical page: https://g2.antv.antgroup.com/zh/manual/core/data/overview/ - Markdown source: https://g2.antv.antgroup.com/zh/manual/core/data/overview.md - Description: 配置 G2 数据来源和数据转换,使用内联数据或远程请求,并通过更新 Spec 刷新图表数据。 --- title: "概览" description: "配置 G2 数据来源和数据转换,使用内联数据或远程请求,并通过更新 Spec 刷新图表数据。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/manual/core/data/overview/" version: "5.4.8" --- G2 中的**数据(Data)** 主要用于指定需要可视化的数据和数据转换(预处理)。 ## 使用方式 配置数据主要有三种方式。 - 第一种:设置 `type: inline`,这种方式定义了一个内联连接器,直接传入具体的数据,这种是 G2 默认的数据源类型。 ```js ({ data: { type: 'inline', // 指定 inline 类型 value: [ { genre: 'Sports', sold: 275 }, { genre: 'Strategy', sold: 115 }, { genre: 'Action', sold: 120 }, { genre: 'Shooter', sold: 350 }, { genre: 'Other', sold: 150 }, ], }, }); ``` - 第二种:设置 `type: fetch`,这种方式定义了一个远程连接器,从远程接口获得数据,让数据源具备一定的动态性,支持 JSON、CSV 等格式解析,适配多场景数据集成,提升大屏、监控等应用的开发效率与交互体验。 ```js ({ data: { type: 'fetch', // 指定 fetch 类型 value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/scatter.json', // 远程地址 }, }); ``` - 第三种:这种方式是第一种的语法糖,如果数据满足以下三个条件可以直接指定 data 的值: 1. 内联数据 2. 是数组 3. 没有数据转换 ```js ({ data: [ { genre: 'Sports', sold: 275 }, { genre: 'Strategy', sold: 115 }, { genre: 'Action', sold: 120 }, { genre: 'Shooter', sold: 350 }, { genre: 'Other', sold: 150 }, ], }); ``` ## 配置层级 数据可以指定在 View 层级: ```js ({ type: 'view', data: [ { genre: 'Sports', sold: 275 }, { genre: 'Strategy', sold: 115 }, { genre: 'Action', sold: 120 }, { genre: 'Shooter', sold: 350 }, { genre: 'Other', sold: 150 }, ], }); ``` 视图绑定的数据具有传递性:会传递给 `view.children` 里面的标记,如果该标记没有数据,那么就设置其数据,否则没有任何影响。这意味着对于共享数据的标记,可以把数据和视图绑定。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'view', autoFit: true, data: [ { year: '1991', value: 3 }, { year: '1992', value: 4 }, { year: '1993', value: 3.5 }, { year: '1994', value: 5 }, { year: '1995', value: 4.9 }, { year: '1996', value: 6 }, { year: '1997', value: 7 }, { year: '1998', value: 9 }, { year: '1999', value: 13 }, ], children: [ { type: 'line', encode: { x: 'year', y: 'value' } }, { type: 'point', encode: { x: 'year', y: 'value' } }, ], }); chart.render(); ``` 也可以指定在 Mark 层级: ```js ({ type: 'interval', data: [ { genre: 'Sports', sold: 275 }, { genre: 'Strategy', sold: 115 }, { genre: 'Action', sold: 120 }, { genre: 'Shooter', sold: 350 }, { genre: 'Other', sold: 150 }, ], }); ``` 每一个标记都有自己的数据,这意味着我们可以在一个视图中可视化多份数据,比如下面的区间标注图: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'view', autoFit: true, children: [ { type: 'rangeX', data: [ { year: [new Date('1933'), new Date('1945')], event: 'Nazi Rule' }, { year: [new Date('1948'), new Date('1989')], event: 'GDR (East Germany)', }, ], encode: { x: 'year', color: 'event' }, scale: { color: { independent: true, range: ['#FAAD14', '#30BF78'] } }, style: { fillOpacity: 0.75 }, tooltip: false, }, { type: 'line', data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/year-population.json', }, encode: { x: (d) => new Date(d.year), y: 'population', color: '#333' }, }, ], }); chart.render(); ``` ## DataTransform 一个完整的数据声明由两部分构成:**连接器(Connector)** 和**数据转换(DataTransform)** 。其中连接器是获得数据的方式,通过 `data.type` 指定,数据转换是预处理函数,通过 `data.transform` 指定。 ```js ({ data: { type: 'fetch', // 指定连接器类型 // 指定连接器的值 value: 'https://gw.alipayobjects.com/os/basement_prod/6b4aa721-b039-49b9-99d8-540b3f87d339.json', transform: [ // 指定转换,可以多个 { type: 'filter', callback: (d) => d.sex === 'gender' }, { type: 'sort', callback: (a, b) => b.a - a.a, }, ], }, }); ``` 目前 G2 内置的数据转换有以下几种: | type | 描述 | 属性 | | ------ | -------------------------------------------------------------------- | ----------------------------------- | | custom | 自定义数据处理逻辑,允许用户定义自己的操作方式 | [custom](/zh/manual/core/data/custom/) | | ema | 计算指数移动平均值,用于数据平滑处理 | [ema](/zh/manual/core/data/ema/) | | filter | 筛选数据,根据条件过滤掉不符合要求的数据 | [filter](/zh/manual/core/data/filter/) | | fold | 将多个字段展开成按照指定的 `key` `value` 组织形式,便于处理和分析 | [fold](/zh/manual/core/data/fold/) | | join | 合并两个数据表,根据某些条件关联相应的数据行 | [join](/zh/manual/core/data/join/) | | kde | 估算数据分布的密度,通常用于概率密度分析 | [kde](/zh/manual/core/data/kde/) | | log | 将当前数据变换流中的片面数据打印到控制台,用于开发者调试数据处理过程 | [log](/zh/manual/core/data/log/) | | map | 对数据进行映射操作,将某种值转换为另一种值 | [map](/zh/manual/core/data/map/) | | pick | 从数据中选择指定的字段,用于提取特定信息 | [pick](/zh/manual/core/data/pick/) | | rename | 重命名数据字段,便于后续处理和阅读 | [rename](/zh/manual/core/data/rename/) | | slice | 截取数据的部分范围,相当于数据的分页或裁剪 | [slice](/zh/manual/core/data/slice/) | | sort | 对数据进行排序处理,支持自定义排序规则 | [sort](/zh/manual/core/data/sort/) | | sortBy | 按照指定的字段进行排序 | [sortBy](/zh/manual/core/data/sort-by/) | ## 数据更新 因为数据是和标记绑定的,所以数据更新会稍微复杂一点。以下面的案例作为例子: ```js import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); 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' }, }); chart.render(); ``` 通过读取当前 Spec 并重新提交来更新标记数据: ```js const spec = chart.options(); // 更新 interval 绑定的数据 spec.children[0].data = newData; // 提交 Spec 并更新图表的渲染 chart.options(spec); chart.render(); ``` 下面的示例展示实时数据更新。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); // 格式化函数:将时间戳转换为 hh:mm:ss 格式 function formatTime(timestamp) { const date = new Date(timestamp); const hours = String(date.getHours()).padStart(2, '0'); // 补齐两位 const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); return `${hours}:${minutes}:${seconds}`; } const data = []; chart.options({ type: 'line', data: [], encode: { x: (d) => formatTime(d.time), y: 'temperature', color: 'type', shape: 'smooth', size: 2, }, scale: { x: { nice: true, }, }, }); chart.render(); setInterval(function () { const now = new Date(); const time = now.getTime(); const temperature1 = ~~(Math.random() * 5) + 22; const temperature2 = ~~(Math.random() * 7) + 17; if (data.length >= 200) { data.shift(); data.shift(); } data.push({ time, // 使用格式化后的时间 temperature: temperature1, type: '记录1', }); data.push({ time, // 使用格式化后的时间 temperature: temperature2, type: '记录2', }); chart.changeData(data); }, 1000); ``` ## 示例 - 如何使用第三方库绘制统计回归线? 借助自定义数据转换能力,我们可以配置: ```js ({ data: { transform: [ { type: 'custom', callback: customFn(), // 自定义的数据处理函数 }, ], }, }); ``` 这里的`customFn`可以使用外部的数据处理相关的库,极大地拓展了 G2 的数据处理能力。下面的例子中,我们使用第三方库 [d3-regression](https://github.com/HarryStevens/d3-regression) 来生成线性统计回归线: ```js import { regressionLinear } from 'd3-regression'; chart.options({ data: { type: 'inline', value: data, // 利用 D3 的 regressionLinear 对数据进行线性回归处理 transform: [ { type: 'custom', callback: regressionLinear(), }, ], }, }); ``` 更多统计回归线案例见 [数据分析-回归线](/zh/examples/analysis/regression/)。 #### 概览 - Canonical page: https://g2.antv.antgroup.com/zh/manual/core/interaction/overview/ - Markdown source: https://g2.antv.antgroup.com/zh/manual/core/interaction/overview.md - Description: 为 G2 图表配置提示信息、图例筛选、元素高亮和框选等交互,了解交互的启用方式与自定义配置。 --- title: "概览" description: "为 G2 图表配置提示信息、图例筛选、元素高亮和框选等交互,了解交互的启用方式与自定义配置。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/manual/core/interaction/overview/" version: "5.4.8" --- G2 中**交互(Interaction)** 提供了按需探索数据的能力。 交互可以设置在视图层级: ```js ({ type: 'view', interaction: { tooltip: {}, brushHighlight: {}, }, }); ``` 交互也可以设置在标记层级: ```js ({ type: 'interval', interaction: { tooltip: {}, brushHighlight: {}, }, }); ``` ## 视图层级的交互 G2 的交互都是对每一个视图生效,如果希望关掉交互,可以如下: ```js ({ type: 'view', interaction: { tooltip: false, brushHighlight: false, }, }); ``` ## 标记层级的交互 交互拥有冒泡性,视图交互会被它的标记所设置交互覆盖,并且最后一个标记所对应的坐标系优先级最高。 ```js chart.options({ type: 'view', interaction: { elementHighlight: { link: true, background: true } }, children: [ { type: 'line', interaction: { elementHighlight: { link: false } } }, { type: 'area', interaction: { elementHighlight: { background: false } } }, ], }); ``` 和下面的情况等价: ```js chart.options({ type: 'view', interaction: { elementHighlight: { link: false, background: false } }, children: [{ type: 'line' }, { type: 'area' }], }); ``` ## 交互状态 在 G2 中可以通过 `mark.state` 去设置标记的交互状态,比如如下设置 select 和 unselect 的状态,当使用 elementSelect 的时候会消费这两个状态。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'interval', data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/fb9db6b7-23a5-4c23-bbef-c54a55fee580.csv', }, transform: [{ type: 'sortX', by: 'y', reverse: true }], encode: { x: 'letter', y: 'frequency' }, axis: { y: { labelFormatter: '.0%' } }, state: { selected: { fill: '#f4bb51' }, // 设置选中状态 unselected: { opacity: 0.6 }, // 设置非选中状态 }, interaction: { elementSelect: true }, }); chart.render(); ``` 除了 selected 和 unselected 之外,还有如下的内置状态类型: - active - inactive ## 交互事件 ### 监听交互事件 所以的交互事件都可以监听,语法如下: ```js chart.on('interaction name(eg: brushFilter)', (e) => {}); ``` 以鼠标刷选 [brushFilter](/zh/manual/core/interaction/brush-filter/) 为例,当用户进行鼠标刷选时,将对应的刷选阈值压入 brushHistory,当点击 reset 按钮时,依次弹出并通过 `chart.emit()` 主动触发 brushFilter 进行刷选覆盖即可: ```ts import * as G2 from '@antv/g2'; const { Chart, ChartEvent } = G2; const chart = new Chart({ container: 'container', clip: true, }); const brushHistory = []; chart.options({ type: 'point', data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/scatter.json', }, encode: { x: 'weight', y: 'height', color: 'gender', shape: 'point' }, interaction: { brushFilter: true }, }); // 监听刷选事件 chart.on('brush:filter', (e) => { if (e.target) brushHistory.push(e.data.selection); }); chart.render(); chart.on(ChartEvent.AFTER_RENDER, () => { const scale = chart.getScale(); const { x1, y1 } = scale; const domainX = x1.options.domain; const domainY = y1.options.domain; brushHistory.push([domainX, domainY]); }); const container = chart.getContainer(); const button = document.createElement('button'); button.innerText = 'reset'; button.onclick = () => { if (brushHistory.length < 2) return; brushHistory.pop(); // 主动触发刷选事件 chart.emit('brush:filter', { data: { selection: brushHistory[brushHistory.length - 1], }, }); }; container.appendChild(button); ``` ### 触发交互事件 触发和监听一般是同时出现,用于主动触发某个事件,形参中的 data 会作用于对应的交互事件,起到重置、覆盖的效果。例如重置筛选区域,以 [brushFilter](/zh/manual/core/interaction/brush-filter/) 为例,语法如下。 ```js chart.emit('brush:filter', { data: { selection: brushHistory[brushHistory.length - 1], }, }); ``` ## 自定义交互 如果内置的交互不能满足你的需求,也可以通过自定义交互的方式去实现一些交互。下面自定义一个高亮交互。 ```ts import * as G2 from '@antv/g2'; const { Chart, PLOT_CLASS_NAME, ELEMENT_CLASS_NAME, register } = G2; register('interaction.customElementHighlight', () => { return (context, _, emitter) => { const { container } = context; const plotArea = container.querySelector(`.${PLOT_CLASS_NAME}`); const elements = plotArea.querySelectorAll(`.${ELEMENT_CLASS_NAME}`); const elementSet = new Set(elements); const pointerover = (e) => { const { target: element } = e; if (!elementSet.has(element)) return; element.style.stroke = 'red'; element.style.lineWidth = 2; }; const pointerout = (e) => { const { target: element } = e; if (!elementSet.has(element)) return; element.style.stroke = null; }; plotArea.addEventListener('pointerover', pointerover); plotArea.addEventListener('pointerout', pointerout); return () => { plotArea.removeEventListener('pointerover', pointerover); plotArea.removeEventListener('pointerout', pointerout); }; }; }); const chart = new Chart({ container: 'container' }); chart.options({ type: 'interval', data: [ { name: 'London', 月份: 'Jan.', 月均降雨量: 18.9 }, { name: 'London', 月份: 'Feb.', 月均降雨量: 28.8 }, { name: 'London', 月份: 'Mar.', 月均降雨量: 39.3 }, { name: 'London', 月份: 'Apr.', 月均降雨量: 81.4 }, { name: 'London', 月份: 'May', 月均降雨量: 47 }, { name: 'London', 月份: 'Jun.', 月均降雨量: 20.3 }, { name: 'London', 月份: 'Jul.', 月均降雨量: 24 }, { name: 'London', 月份: 'Aug.', 月均降雨量: 35.6 }, { name: 'Berlin', 月份: 'Jan.', 月均降雨量: 12.4 }, { name: 'Berlin', 月份: 'Feb.', 月均降雨量: 23.2 }, { name: 'Berlin', 月份: 'Mar.', 月均降雨量: 34.5 }, { name: 'Berlin', 月份: 'Apr.', 月均降雨量: 99.7 }, { name: 'Berlin', 月份: 'May', 月均降雨量: 52.6 }, { name: 'Berlin', 月份: 'Jun.', 月均降雨量: 35.5 }, { name: 'Berlin', 月份: 'Jul.', 月均降雨量: 37.4 }, { name: 'Berlin', 月份: 'Aug.', 月均降雨量: 42.4 }, ], transform: [{ type: 'dodgeX' }], encode: { x: '月份', y: '月均降雨量', color: 'name' }, interaction: { customElementHighlight: true }, }); chart.render(); ``` #### 概览 - Canonical page: https://g2.antv.antgroup.com/zh/manual/core/mark/overview/ - Markdown source: https://g2.antv.antgroup.com/zh/manual/core/mark/overview.md - Description: 了解 G2 图形标记的类型、数据编码和样式配置,以及多个标记的组合和自定义复合标记的使用。 --- title: "概览" description: "了解 G2 图形标记的类型、数据编码和样式配置,以及多个标记的组合和自定义复合标记的使用。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/manual/core/mark/overview/" version: "5.4.8" --- 在 G2 中**没有图表的概念**,而是把 **标记(Mark)** 作为基本的视觉组成单元,任何一个图表都可以由一个或多个标记组合而成。 标记是 G2 绘图框架中最基础的图形单元,也是构成复杂图表的核心组成部分之一。它具有多样化的特性和强大的表现力,能够作为构建图表的“原子”组件被灵活运用。通过合理地组合、排列和定制标记,用户不仅可以满足常规图表绘制需求,还能创建高度个性化、复杂性较高的可视化作品。这种自由度使得标记在实现数据可视化的过程中具备广泛的应用空间,包括但不限于散点图、柱状图、饼图等基础图形,以及更加结构化或创新性的混合式图表。这种特性使得 G2 在绘图的灵活性和定制能力上展现出极大的优势。 正如上面所说,我们在一个图表中添加散点图的 Point 标记以及连接图的 Link 标记,便可以得到一个带有标注的点线连接图。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', height: 180, }); chart.options({ type: 'view', data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/penguins.json', transform: [ { type: 'map', callback: (d) => ({ ...d, body_mass_g: +d.body_mass_g, }), }, ], }, children: [ // point 标记 { type: 'point', encode: { x: 'body_mass_g', y: 'species' }, style: { stroke: '#000' }, tooltip: { items: [{ channel: 'x' }] }, }, // link 标记 { type: 'link', encode: { x: 'body_mass_g', y: 'species' }, transform: [{ type: 'groupY', x: 'min', x1: 'max' }], style: { stroke: '#000' }, tooltip: false, }, // point 标记绘制中位线 { type: 'point', encode: { y: 'species', x: 'body_mass_g', shape: 'line', size: 12 }, transform: [{ type: 'groupY', x: 'median' }], style: { stroke: 'red' }, tooltip: { items: [{ channel: 'x' }] }, }, ], }); chart.render(); ``` 根据标记可以代表的数据维度来划分,可以分为: - 零维,点是常见的零维几何标记,点仅有位置信息 - 一维,常见的一维几何标记有线 - 二维,二维平面 - 三维,常见的立方体、圆柱体都是三维的几何标记 ![](https://zos.alipayobjects.com/basement/skylark/0ad6383d14791764763234581d755f/attach/4080/900/image.png#align=left&display=inline&height=140&originHeight=140&originWidth=549&status=done&style=none&width=549) 标记的自由度与数据能够映射到图形的视觉通道 **size(大小)** 相关,这个角度上来讲: - 点可以映射两个数据字段字段到点的大小上(当然现实中我们仅仅映射一个)。 - 线可以映射一个数据字段字段到线的宽度。 - 柱状图的矩形可以映射一个数据字段到宽度上。 - 封闭的多边形无法使用数据映射到大小。 标记的表现形式与数据能够映射到图形的视觉通道 **color(颜色)** 相关,这个角度上来讲: - 表现形式为封闭的有填充色的图形。例如 point 标记的 `point` 形状、interval 标记的 `rect` 形状等,颜色通道一般表现在标记的填充颜色 `fill` 上。 - 表现形式为线和空心的图形。例如 line 标记的所有形状、interval 标记的 `hollow` 形状等,颜色通道一般表现在标记的线的颜色 `stroke` 上。 - 表现形式为文字。例如 text 标记、wordCloud 标记等,颜色通道一般表现在标记的文字颜色 `fill` 上。 目前 G2 支持的内置标记如下: | type | 描述 | 属性 | 示例 | | --------- | ------------------------------------------------ | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | area | 用面积填充展示数据趋势,适合展示堆积关系 | [area](/zh/manual/core/mark/area/) |
| | box | 基础箱线图,展示数据分布和异常值 | [box](/zh/manual/core/mark/box/) | | | boxplot | 带聚合计算的箱线图,自动计算分位数 | [boxplot](/zh/manual/core/mark/boxplot/) | | | cell | 将空间划分为方块进行可视化,常用于日历图、热力图 | [cell](/zh/manual/core/mark/cell/) | | | chord | 展示实体间关系强度的弦图 | [chord](/zh/manual/core/mark/chord/) | | | density | 核密度估计图,常用于小提琴图 | [density](/zh/manual/core/mark/density/) | | | gauge | 仪表盘图表,展示进度指标 | [gauge](/zh/manual/core/mark/gauge/) | | | heatmap | 二维密度分布图,用颜色编码数据密度 | [heatmap](/zh/manual/core/mark/heatmap/) | | | image | 在指定位置渲染图像 | [image](/zh/manual/core/mark/image/) | | | interval | 基础柱状图/条形图,通过坐标系变换可生成饼图 | [interval](/zh/manual/core/mark/interval/) |


| | line | 折线图,支持平滑曲线和阶梯线 | [line](/zh/manual/core/mark/line/) |

| | lineX | 垂直辅助线,常用于标注特定值 | [lineX](/zh/manual/core/mark/line-x/) | | | lineY | 水平辅助线,常用于标注阈值 | [lineY](/zh/manual/core/mark/line-y/) | | | link | 带方向箭头标记,展示节点间关系 | [link](/zh/manual/core/mark/link/) | | | liquid | 水波图,展示百分比进度 | [liquid](/zh/manual/core/mark/liquid/) | | | point | 散点图,通过大小/颜色编码多维度数据 | [point](/zh/manual/core/mark/point/) | | | polygon | 多边形标记,常配合布局算法使用 | [polygon](/zh/manual/core/mark/polygon/) | | | range | 矩形区域标记,用于高亮特定区间 | [range](/zh/manual/core/mark/range/) | | | rangeX | 垂直方向区域标记 | [rangeX](/zh/manual/core/mark/range-x/) | | | rangeY | 水平方向区域标记 | [rangeY](/zh/manual/core/mark/range-y/) | | | rect | 基础矩形标记,用于直方图/矩形树图 | [rect](/zh/manual/core/mark/rect/) | | | shape | 完全自定义图形标记 | [shape](/zh/manual/core/mark/shape/) | | | text | 数据标签标记,支持富文本格式 | [text](/zh/manual/core/mark/text/) | | | vector | 向量场标记,展示方向/强度双维度数据 | [vector](/zh/manual/core/mark/vector/) | | | wordCloud | 词云图,通过文字大小编码词频 | [wordCloud](/zh/manual/core/mark/wordcloud/) | | ## 使用方式 每一个图形标记都是独立的存在,通过 `type` 属性指定标记类型。标记是 G2 可视化体系的核心原子单位,也是视图树的叶子节点,作为 G2 的"一等公民",其核心构成包含以下概念: - **数据相关** - [**data**](/zh/manual/core/data/overview/) 可视化原始数据源,支持多种数据格式和动态更新机制。数据通过编码映射到图形属性空间 - [**encode**](/zh/manual/core/encode/) 数据到图形属性的编码通道。例如将身高映射到 x 轴,体重映射到 y 轴,性别映射到颜色通道 - [**scale**](/zh/manual/core/scale/overview/) 控制数据到视觉通道的映射规则。包括连续型、分类型、时间型等多种度量类型 - **图形生成** - [**transform**](/zh/manual/core/transform/overview/) 数据转换。支持数据堆叠(stack)、分组(dodge)、扰动(jitter)、对称(symmetric)等调整方法,解决图形重叠问题 - [**coordinate**](/zh/manual/core/coordinate/overview/) 坐标系变换。支持笛卡尔坐标、极坐标、螺旋坐标等,同一几何标记在不同坐标系下呈现不同形态 - **视觉表现** - [**style**](/zh/manual/core/style/) 图形元素的视觉样式。支持配置填充色、描边、透明度等属性 - [**viewStyle**](/zh/manual/core/chart/chart-component/#viewstyle) 视图容器的背景、边距等样式配置 - **交互动态** - [**animate**](/zh/manual/core/animate/overview/) 控制三类动画: enter:新增元素动画 update:数据更新动画 exit:元素销毁动画 - [**state**](/zh/manual/core/state/) 定义元素在不同交互状态(active/inactive/selected/unselected)下的样式变化 - **图表组件** - [**title**](/zh/manual/component/title/) 图表标题。支持配置标题和副标题 - [**label**](/zh/manual/component/label/) 数据标签系统。支持防重叠布局、自定义内容、连接线等特性,适配不同标记类型 - [**axis**](/zh/manual/component/axis/) 坐标轴。支持刻度、网格线、标签的自定义配置 - [**legend**](/zh/manual/component/legend/) 图例。支持连续型/分类型图例交互 - [**tooltip**](/zh/manual/component/tooltip/) 提示信息。支持字段映射、格式化、自定义内容 - [**scrollbar**](/zh/manual/component/scrollbar/) 滚动条组件。滚动条默认都是关闭的。解决图表信息过于密集而无法完全展示的问题。 - [**slider**](/zh/manual/component/slider/) 缩略轴组件。可以用于过滤数据,让用户在数据量较大的情况下一次只用关注局部的数据,是一种辅助看数据的组件 - **扩展控制** - [**theme**](/zh/manual/core/theme/overview/) 主题配置系统。可修改默认颜色板、字体、组件样式等视觉规范,支持主题切换 - [**interaction**](/zh/manual/core/interaction/overview/) 交互行为库。内置元素选取、视图缩放、提示联动等交互模式 ```js ({ type: 'mark', data: [], encode: {}, scale: {}, transform: [], coordinate: {}, style: {}, viewStyle: {}, animate: {}, state: {}, label: {}, title: {}, axis: {}, legend: {}, tooltip: {}, scrollbar: {}, slider: {}, interaction: {}, theme: {}, }); ``` ## 配置层级 标记可以作为顶层的类型如下声明: ```js ({ type: 'interval', encode: { x: 'name', y: 'value', }, }); ``` 也可以放在 View 里面添加多个标记到视图中: ```js ({ type: 'view', children: [{ type: 'line' }, { type: 'point' }], }); ``` ## 特性 G2 中的标记具有许多特性,包括模板化、可叠加、可复合等。合理地运用这些特性,可以快速定义和使用多种图形样式,将多个标记组合展现更丰富的图形效果。这些灵活且高度定制的特性,使标记能够满足从基础图表到复杂可视化的多层次需求。 ### 图形模版 每个内置标记都是一个图形模版,会生成一系列**数据驱动**的图形,其中每个图形对应一个或者多个**数据项(Data Item)**。比如下面的散点图里只有一个 Point 标记,而这个标记生成了多个圆,每个圆对应一个数据项。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'point', data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/basement_prod/6b4aa721-b039-49b9-99d8-540b3f87d339.json', }, encode: { x: 'height', y: 'weight', color: 'gender' }, }); chart.render(); ``` 而在下面的折线图中,一条线对应多个数据项。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'line', width: 900, autoFit: true, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/551d80c6-a6be-4f3c-a82a-abd739e12977.csv', }, encode: { x: 'date', y: 'close' }, }); chart.render(); ``` ### 可叠加 G2 的标记是可以叠加的,换句话说:可以在一个视图里面添加多个标记,丰富图表展示效果。 下面的例子中给图表添加了 line 和 point 两个标记: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'view', data: [ { year: '1991', value: 3 }, { year: '1992', value: 4 }, { year: '1993', value: 3.5 }, { year: '1994', value: 5 }, { year: '1995', value: 4.9 }, { year: '1996', value: 6 }, { year: '1997', value: 7 }, { year: '1998', value: 9 }, { year: '1999', value: 13 }, ], children: [ { type: 'line', encode: { x: 'year', y: 'value' }, }, { type: 'point', encode: { x: 'year', y: 'value' }, tooltip: false, // 如果不希望展示某个标记的tooltip,可以单独关闭 }, ], }); chart.render(); ``` 当然,我们也可以结合更多的标记绘制一个具有复杂图形意义的区间曲线面积图。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'view', data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/range-spline-area.json', transform: [ { type: 'map', callback: ([x, low, high, v2, v3]) => ({ x, low, high, v2, v3, }), }, ], }, scale: { x: { type: 'linear', tickCount: 10 } }, axis: { y: { title: false } }, children: [ { type: 'area', encode: { x: 'x', y: ['low', 'high'], shape: 'smooth' }, style: { fillOpacity: 0.65, fill: '#64b5f6', lineWidth: 1 }, }, { type: 'point', encode: { x: 'x', y: 'v2', size: 2, shape: 'point' }, tooltip: { items: ['v2'] }, }, { type: 'line', encode: { x: 'x', y: 'v3', color: '#FF6B3B', shape: 'smooth' }, }, ], }); chart.render(); ``` ### 可复合 G2 里面的标记可以通过一种机制复合成一个标记,然后使用,比如上面的点线图: ```ts import { Chart } from '@antv/g2'; // 定义复合 Mark function PointLine({ encode, data } = {}) { return [ { type: 'line', data, encode }, { type: 'point', data, encode }, ]; } const data = [ { year: '1991', value: 3 }, { year: '1992', value: 4 }, { year: '1993', value: 3.5 }, { year: '1994', value: 5 }, { year: '1995', value: 4.9 }, { year: '1996', value: 6 }, { year: '1997', value: 7 }, { year: '1998', value: 9 }, { year: '1999', value: 13 }, ]; const chart = new Chart({ container: 'container', }); const spec = { type: PointLine, data, encode: { x: 'year', y: 'value' }, }; // 使用 Spec 配置复合标记 chart.options(spec); chart.render(); ``` 标记可复合的特性提供了一种简单却强大的扩展 G2 能力的方式,G2 内部也是使用这个机制实现了一些比较复杂的标记,比如桑基图:用两个 Polygon 标记进行复合。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', width: 900, height: 600, }); // Sankey 标记 chart.options({ type: 'sankey', layout: { nodeAlign: 'center', nodePadding: 0.03 }, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/energy.json', transform: [ { type: 'custom', callback: (data) => ({ links: data, }), }, ], }, style: { labelSpacing: 3, labelFontWeight: 'bold', nodeStrokeWidth: 1.2, linkFillOpacity: 0.4, }, }); chart.render(); ``` ### 支持多种转换 G2 的标记支持多种 [转换(Transform)](/zh/manual/core/transform/overview/) ,通过对标记的几何形状、样式或空间布局进行灵活调整,实现丰富的视觉表现效果。这些转换不仅可以用于基础图形的变换,如分组、堆叠、分箱等,还能与数据驱动的动态调整结合,以适应复杂的可视化场景需求。通过简单的配置,用户可以在图表中实现数据与视觉元素之间的直观映射,提升图表的表现力和可读性。 以下是一个经过 [binX](/zh/manual/core/transform/bin-x/) 和 [stackY](/zh/manual/core/transform/stack-y/) 转换后的颜色分类直方图。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'rect', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/athletes.json', }, encode: { x: 'weight', color: 'sex' }, transform: [ { type: 'binX', y: 'count' }, { type: 'stackY', orderBy: 'series' }, ], style: { inset: 0.5 }, }); chart.render(); ``` 通过配置多种转换,我们可以得到特定表现形式的复杂图表,以下是一个经过 [normalizeY](/zh/manual/core/transform/normalize-y/) 和 [stackY](/zh/manual/core/transform/stack-y/) 等多个转换后得到的聚合归一化堆叠条形图。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'interval', autoFit: true, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/87b2ff47-2a33-4509-869c-dae4cdd81163.csv', transform: [{ type: 'filter', callback: (d) => d.year === 2000 }], }, encode: { x: 'age', y: 'people', color: 'sex' }, transform: [ { type: 'groupX', y: 'sum' }, { type: 'stackY' }, { type: 'normalizeY' }, ], scale: { color: { type: 'ordinal', range: ['#ca8861', '#675193'] } }, coordinate: { transform: [{ type: 'transpose' }] }, axis: { y: { labelFormatter: '.0%' } }, labels: [{ text: 'people', position: 'inside', fill: 'white' }], tooltip: { items: [{ channel: 'y', valueFormatter: '.0%' }] }, }); chart.render(); ``` ### 可作为标注 **标注(Annotation)** 是用于在可视化图表中对需要重点关注区域或信息进行说明与强调的图形元素。在 G2 5.0 中,并未单独提供专门的标注组件,而是通过灵活配置标记来实现标注功能。换言之,标注实际上是一种标记表达形式,部分标记(如 Text、Image 等)可被用于标注的场景。这种设计方式统一了标记与标注的使用逻辑,赋予用户更高的自由度与灵活性,从而能够轻松满足多种标注需求。 #### 转换 既然标注也是一种标记,那么它也可以执行转换。比如下面的 [Select](/zh/manual/core/transform/select/) 转换。 Select 标记转换提供了从一组图形中根据指定通道和 selector 选择数据的能力。比如在下面的例子中,标注出了每个大陆 Continent 中,GDP 最大的国家。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'view', data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/1ecf85d2-8279-46a1-898d-d2e1814617f9.json', }, children: [ { type: 'point', encode: { x: 'GDP', y: 'LifeExpectancy', color: 'Continent' }, }, { type: 'text', encode: { text: 'Country', x: 'GDP', y: 'LifeExpectancy', series: 'Continent', }, // 将图形按照 series 分组,也就是 Continent // 通过 x 通道选择,选择其中最大的,也就是 GDP 最大的 transform: [{ type: 'select', channel: 'x', selector: 'max' }], style: { textAlign: 'end' }, }, ], }); chart.render(); ``` 对于不需要分组的简单的文本标记,使用 [数据标签(Label)](/zh/manual/component/label/) 就可以,否则可以考虑上面的方式。 #### 定位 在图形语法中,标注的核心在于准确定位至适当的位置,以便有效传达关键信息。在 G2 中,标注的定位支持以下三种方式: - **数据驱动的定位** :基于数据值,将标注绑定到具体的图表数据点或数据范围之上。此方式能够动态适应数据变动,例如在数据更新或动画交互时,标注位置会随之调整。 - **绝对定位** :通过固定的像素坐标将标注放置在画布上的特定位置,与数据无直接关联。此方式适用于添加标题、说明或其他与数据逻辑无关的标注内容。 - **相对定位** :以坐标系或图形区域为参考,通过百分比或相对位置参数定义标注的位置。此方式适合在对图表整体进行强调或标注区域时提供灵活的布局。 ##### 数据驱动 在 G2 中可以通过 `data` 去指定数据驱动的定位,比如下面的例子中希望标注每天糖和脂肪的安全摄入量,就可以如下实现。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); const lineStyle = { stroke: '#000', strokeOpacity: 0.45, lineDash: [3, 3] }; chart.options({ type: 'view', autoFit: true, children: [ { type: 'point', data: [ { x: 95, y: 95, z: 13.8, name: 'BE', country: 'Belgium' }, { x: 86.5, y: 102.9, z: 14.7, name: 'DE', country: 'Germany' }, { x: 80.8, y: 91.5, z: 15.8, name: 'FI', country: 'Finland' }, { x: 80.4, y: 102.5, z: 12, name: 'NL', country: 'Netherlands' }, { x: 80.3, y: 86.1, z: 11.8, name: 'SE', country: 'Sweden' }, { x: 78.4, y: 70.1, z: 16.6, name: 'ES', country: 'Spain' }, { x: 74.2, y: 68.5, z: 14.5, name: 'FR', country: 'France' }, { x: 73.5, y: 83.1, z: 10, name: 'NO', country: 'Norway' }, { x: 71, y: 93.2, z: 24.7, name: 'UK', country: 'United Kingdom' }, { x: 69.2, y: 57.6, z: 10.4, name: 'IT', country: 'Italy' }, { x: 68.6, y: 20, z: 16, name: 'RU', country: 'Russia' }, { x: 65.5, y: 126.4, z: 35.3, name: 'US', country: 'United States' }, { x: 65.4, y: 50.8, z: 28.5, name: 'HU', country: 'Hungary' }, { x: 63.4, y: 51.8, z: 15.4, name: 'PT', country: 'Portugal' }, { x: 64, y: 82.9, z: 31.3, name: 'NZ', country: 'New Zealand' }, ], encode: { x: 'x', y: 'y', size: 'z', shape: 'point' }, scale: { x: { nice: true }, y: { nice: true, domainMax: 165, zero: true }, size: { range: [10, 40] }, }, style: { stroke: '#1890ff', fillOpacity: 0.3, fill: '#1890ff' }, legend: false, labels: [ { text: 'name', position: 'inside', fill: '#1890ff', stroke: '#fff' }, ], }, { type: 'lineY', data: [50], style: lineStyle, labels: [ { text: 'Safe sugar intake 50g/day', position: 'right', textBaseline: 'bottom', fill: '#000', fillOpacity: 0.45, background: true, backgroundFill: '#000', backgroundOpacity: 0.15, }, ], }, { type: 'lineX', data: [65], style: lineStyle, labels: [ { text: 'Safe fat intake 65g/day', position: 'top-left', textBaseline: 'bottom', fill: '#000', fillOpacity: 0.45, background: true, backgroundFill: '#000', backgroundOpacity: 0.15, }, ], }, ], }); chart.render(); ``` 除了数据驱动的定位,G2 也提供了非数据驱动的定位方式。通过 `style` 去指定 x 和 y 属性,x 和 y 拥有下面两种类型。分别对应标注的 **绝对定位** 和 **相对定位** 。 ##### 绝对定位 - **x 和 y 为数字**:像素为单位的坐标。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'view', children: [ { type: 'interval', data: [ { genre: 'Sports', sold: 275 }, { genre: 'Strategy', sold: 115 }, { genre: 'Action', sold: 120 }, { genre: 'Shooter', sold: 350 }, { genre: 'Other', sold: 150 }, ], encode: { y: 'sold', color: 'genre' }, transform: [{ type: 'stackY' }], coordinate: { type: 'theta', innerRadius: 0.5 }, }, { type: 'text', style: { x: 290, // 配置具体像素坐标 y: 200, text: 'hello', textAlign: 'center', fontSize: 60, textBaseline: 'middle', }, }, ], }); chart.render(); ``` ##### 相对定位 - **x 和 y 为百分比**:内容区域的百分比。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'view', children: [ { type: 'interval', data: [ { genre: 'Sports', sold: 275 }, { genre: 'Strategy', sold: 115 }, { genre: 'Action', sold: 120 }, { genre: 'Shooter', sold: 350 }, { genre: 'Other', sold: 150 }, ], encode: { y: 'sold', color: 'genre' }, transform: [{ type: 'stackY' }], coordinate: { type: 'theta', innerRadius: 0.5 }, }, { type: 'text', style: { x: '50%', // 配置百分比坐标 y: '50%', text: 'hello', textAlign: 'center', fontSize: 60, textBaseline: 'middle', }, }, ], }); chart.render(); ``` ## 示例 - 如何自定义标记的形状? 每一个标记都可以自定义形状,形状决定了标记最后的展现形式。自定义形状主要分为三步: - 定义形状组件。 - 注册形状。 - 使用形状。 首先我们来看看如何定义形状组件。一个形状组件是一个函数,该函数接受图形的样式 _style_ 和上下文 _context_,返回一个绘制函数 _render_ 。其中 _style_ 是通过 `mark.style` 指定的经过处理的选项,_context_ 包含了 [@antv/g](https://g.antv.antgroup.com/) 创建图形的 _document_ 。 返回的 _render_ 函数接受图形的控制点 _P_,映射值 _value_ 和默认值 _defaults_,返回 @antv/g 的图形。其中 _P_ 是一系列画布坐标构成的数组,_value_ 是通过 `mark.encode` 处理后的值,_defaults_ 是主题中 `theme.mark.shape` 指定的值。一个形状组件的定义大概如下: ```js function ShapeTriangle(style, context) { const { document } = context; return (P, value, defaults) => { return document.createElement('rect', { //... }); }; } ``` 接下来就是注册形状,通过调用 `G2.register('shape.${mark}.${shape}', Shape)` 来完成注册该形状。其中 _mark_ 是标记的名字,_shape_ 是形状的名字,_Shape_ 是定义好的形状组件。比如给 Interval 标记注册一个三角形的形状: ```js import { register } from '@antv/g2'; register('shape.interval.triangle', ShapeTriangle); ``` 最后就是使用该形状了,可以通过 `mark.encode` 指定,也可以通过 `mark.style` 指定. ```js ({ type: 'interval', encode: { shape: 'triangle' }, // 或者 style: { shape: 'triangle' }, }); ``` 下面是一个完整的例子,展示了如何自定义形状。 ```ts import { register, Chart } from '@antv/g2'; // 定义图形组件 function ShapeTriangle(style, context) { const { document } = context; return (P, value, defaults) => { const { color: defaultColor } = defaults; const [p0, p1, p2, p3] = P; const pm = [(p0[0] + p1[0]) / 2, p0[1]]; const { color = defaultColor } = value; return document.createElement('polygon', { style: { ...style, fill: color, points: [pm, p2, p3], }, }); }; } // 注册该三角形 register('shape.interval.triangle', ShapeTriangle); // 初始化图表 const chart = new Chart({ container: 'container', }); 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', shape: 'triangle', // 使用这个形状 }, }); chart.render(); ``` #### 概览 - Canonical page: https://g2.antv.antgroup.com/zh/manual/core/scale/overview/ - Markdown source: https://g2.antv.antgroup.com/zh/manual/core/scale/overview.md - Description: 使用 G2 比例尺将数据映射为视觉属性,配置定义域、值域、刻度和比例尺类型,并控制多个标记的比例尺同步。 --- title: "概览" description: "使用 G2 比例尺将数据映射为视觉属性,配置定义域、值域、刻度和比例尺类型,并控制多个标记的比例尺同步。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/manual/core/scale/overview/" version: "5.4.8" --- G2 中**比例尺(Scale)** 是可视化很重要的一个抽象:将抽象数据映射为视觉数据,它是抽象数据和视觉数据的桥梁。如果说编码决定了标记的哪些通道需要被可视化,那么比例尺决定了这些通道该如何被可视化。 ## 比例尺分类 G2 提供了丰富的比例尺类型,可以根据数据类型和使用场景进行分类: ### 按数据类型分类 #### 1. 连续型比例尺 处理 `连续` 数值数据,保持数据间的比例关系: - **linear(线性比例尺)**:最基础的连续比例尺,使用线性函数 y = mx + b 进行映射 - **log(对数比例尺)**:使用对数函数 y = logbase(x) + b 进行映射,适合跨度很大的指数增长数据 - **pow(幂比例尺)**:使用幂函数 y = xk + b 进行映射,可调节指数强调数据差异 - **sqrt(平方根比例尺)**:pow 比例尺的特例(k=0.5),适合压缩大数值差异 - **time(时间比例尺)**:专门处理时间序列数据的连续比例尺,支持自动计算合适的时间间隔(tickInterval)和刻度数量,可以处理 UTC 和本地时间 比如下面的散点图的 x 和 y 通道都是使用了 `linear` 比例尺。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'point', data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/scatter.json', }, encode: { x: 'weight', y: 'height', color: 'gender' }, }); chart.render(); ``` 当我们尝试改变 x 通道和 y 通道的比例尺: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'point', data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/antvdemo/assets/data/scatter.json', }, encode: { x: 'weight', y: 'height', color: 'gender' }, scale: { x: { type: 'point', }, y: { type: 'point', range: [1, 0], }, }, }); chart.render(); ``` > 对于密集的数据,更建议使用连续比例尺而非分类比例尺。 #### 2. 分类型比例尺 处理 `离散` 的分类数据: - **ordinal(序数比例尺)**:将离散数据映射到离散值域,常用于颜色、形状映射 - **band(带状比例尺)**:为每个类别分配等宽区间,常用于柱状图的 x 轴 - **point(点比例尺)**:band 比例尺的特例(bandWidth=0),用于点位置映射 比如下面的条形图的 color 通道就是用了 `ordinal` 比例尺。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); 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' }, scale: { color: { range: ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#c564be'] }, }, }); chart.render(); ``` 我们可以通过以下的例子看出 `band` 比例尺和 `point` 比例尺的区别: `point` 比例尺 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'cell', height: 640, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/bd287f2c-3e2b-4d0a-8428-6a85211dce33.json', }, encode: { x: 'x', y: 'y', color: 'index' }, scale: { x: { type: 'point' } }, style: { stroke: '#000', inset: 2 }, animate: { enter: { type: 'fadeIn' } }, }); chart.render(); ``` `band` 比例尺 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'cell', height: 640, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/bd287f2c-3e2b-4d0a-8428-6a85211dce33.json', }, encode: { x: 'x', y: 'y', color: 'index' }, scale: { x: { type: 'band' } }, style: { stroke: '#000', inset: 2 }, animate: { enter: { type: 'fadeIn' } }, }); chart.render(); ``` #### 3. 离散化比例尺 将 `连续` 数据 `离散化` 为有限类别: - **quantize(量化比例尺)**:按数值范围等宽分段 - **quantile(分位数比例尺)**:按数据分布分位数分段,每段数据量相等 - **threshold(阈值比例尺)**:按手动指定的阈值分段 以下是同一份数据分别应用 `quantile` 比例尺和`quantize` 比例尺的效果,前者按照数据分布分位数分段,每段数据量相等,后者按数值范围等宽分段。 `quantile` 比例尺 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'cell', data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/89c20fe8-0c6f-46c8-b36b-4cb653dba8ed.json', transform: [ { type: 'map', callback: (d) => ({ salary: d, }), }, ], }, encode: { y: (_, i) => (i % 5) + 1, x: (_, i) => ((i / 5) | 0) + 1, color: 'salary', }, scale: { color: { type: 'quantile', range: ['#eee', 'pink', 'red'] } }, style: { stroke: '#000', inset: 2 }, animate: { enter: { type: 'fadeIn' } }, legend: { color: { length: 400, labelFormatter: '.0s' } }, }); chart.render(); ``` `quantize` 比例尺 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'cell', data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/89c20fe8-0c6f-46c8-b36b-4cb653dba8ed.json', transform: [ { type: 'map', callback: (d) => ({ salary: d, }), }, ], }, encode: { y: (_, i) => (i % 5) + 1, x: (_, i) => ((i / 5) | 0) + 1, color: 'salary', }, scale: { color: { type: 'quantize', range: ['#eee', 'pink', 'red'] } }, style: { stroke: '#000', inset: 2 }, animate: { enter: { type: 'fadeIn' } }, legend: { color: { length: 400, labelFormatter: '.0s' } }, }); chart.render(); ``` ### 按使用场景分类 #### 位置编码比例尺 主要用于 x、y 坐标轴: - **linear**:数值型坐标轴 - **time**:时间轴 - **band**:分类型坐标轴 - **point**:分类型坐标轴 #### 视觉属性编码比例尺 主要用于颜色、大小、形状等视觉通道: - **ordinal**:分类数据的颜色、形状映射 - **linear/log/pow/sqrt**:连续数据的颜色渐变、数值大小映射 - **quantize/quantile/threshold**:连续数据的分段颜色映射 例如一个基础的柱状图,x 通道的比例尺默认为 `band`,用于实现柱状图分类型坐标轴,y 通道比例尺默认为 `linear`,将 y 通道对应的数据列的连续数据映射到柱子的长度,具有视觉属性。 总结一下: | 比例尺类型 | 数据类型 | 映射函数 | 主要用途 | 适用场景 | | ------------- | -------- | ----------------------------- | ---------------- | --------------------------------------- | | **linear** | 连续数值 | y = mx + b | 位置、颜色、大小 | 数值型数据的基础映射 | | **log** | 连续数值 | y = logbase(x) + b | 位置、颜色 | 跨度很大的指数增长数据 | | **pow** | 连续数值 | y = xk + b | 位置、颜色、大小 | 需要调节数据差异强度 | | **sqrt** | 连续数值 | y = x0.5 + b | 大小、颜色 | 压缩大数值差异(如面积映射) | | **time** | 时间数据 | 自动计算时间间隔和刻度 | 时间轴 | 时间序列数据可视化,支持 UTC 和本地时间 | | **ordinal** | 分类数据 | 一对一映射 | 颜色、形状 | 分类数据的视觉属性映射 | | **band** | 分类数据 | 等宽区间分配 | x/y 轴位置 | 柱状图、条形图 | | **point** | 分类数据 | 点位置分配 | x/y 轴位置 | 点图、折线图 | | **quantize** | 连续数值 | 等宽分段 | 颜色分段 | 数据分布均匀的分段着色 | | **quantile** | 连续数值 | 等频分段 | 颜色分段 | 数据分布不均的分段着色 | | **threshold** | 连续数值 | 自定义阈值分段 | 颜色分段 | 按特定阈值分组(如及格线) | ## 比例尺选择 1. **数值型数据** - 正常分布 → `linear` - 指数增长/跨度很大 → `log` - 需要强调小值差异 → `pow` (exponent > 1) - 需要压缩大值差异 → `sqrt` 或 `pow` (exponent < 1) 2. **时间数据** - 时间序列 → `time` 3. **分类数据** - 颜色/形状映射 → `ordinal` - 柱状图 x 轴 → `band` - 点图 x 轴 → `point` 4. **连续数据离散化** - 数据分布均匀 → `quantize` - 数据分布不均 → `quantile` - 有明确阈值要求 → `threshold` ## 配置项 ### 连续比例尺通用配置 ```js { type: 'linear', // 或 log, pow, sqrt, time domain: [min, max], // 定义域 range: [0, 1], // 值域 unknown: undefined, // 未知值的映射值 tickMethod: (min, max, count) => [1,2,3,4], // 刻度计算方法 round: false, // 是否对输出值进行取整 interpolate: (a, b) => (t) => a * (1 - t) + b * t, // 插值方法 nice: true, // 是否优化刻度显示 } ``` ### 分类比例尺通用配置 ```js { type: 'ordinal', // 或 band, point domain: ['A', 'B', 'C'], // 类别列表 range: ['red', 'green', 'blue'], // 映射值列表 unknown: undefined, // 未知值的映射值 compare: (a, b) => a.localeCompare(b), // 排序方法 } ``` ### 离散化比例尺通用配置 ```js { type: 'quantize', // 或 quantile, threshold domain: [0, 100], // 连续数据范围 range: ['low', 'medium', 'high'], // 离散类别 unknown: undefined, // 未知值的映射值 } ``` ## 配置层级 G2 内部会根据数据类型以及标记的类型,去推断比例尺的类型、定义域和值域,但是仍然可以指定对应配置。比例尺可以配置在 Mark 层级: ```js ({ type: 'interval', scale: { x: { padding: 0.5 }, y: { type: 'log', // 指定类型 domain: [10, 100], // 指定定义域 range: [0, 1], // 指定值域 }, }, }); ``` 比例尺也可以配置在 View 层级: ```js ({ type: 'view', scale: { x: { padding: 0.5 }, y: { type: 'log', // 指定类型 domain: [10, 100], // 指定定义域 range: [0, 1], // 指定值域 }, }, }); ``` ## 标记比例尺 标记的每一个通道都绑定了一个比例尺。该比例尺会对该通道绑定的列数据进行转换,将其从数据范围:**定义域(Domain)** 转换到视觉范围:**值域(Range)**。不同类型的比例尺针对不同类型的数据和使用场景。 ### 比例尺同步 同一个视图中的标记相同通道的比例尺会默认是同步的:会去同步比例尺的类型,定义域和值域以及其他配置。这意味一个视图中所有的标记都会按照一个同样的尺度去绘制。比如下图中的 LineX 标记虽然没有完整的数据,但是也绘制到了准确的位置,就是因为比例尺同步。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'view', children: [ { type: 'line', data: [ { year: '1991', value: 3 }, { year: '1992', value: 4 }, { year: '1993', value: 3.5 }, { year: '1994', value: 5 }, { year: '1995', value: 4.9 }, { year: '1996', value: 6 }, { year: '1997', value: 7 }, { year: '1998', value: 9 }, { year: '1999', value: 13 }, ], encode: { x: 'year', y: 'value' }, }, { type: 'lineX', data: ['1996'], style: { stroke: 'red', strokeWidth: 2 } }, ], }); chart.render(); ``` ### 比例尺不同步 如果希望不同步(比如绘制双轴图的时候),就需要设置 `scale.independent` 为 `true`,设置了该属性的比例尺不会和任何比例尺同步。下面的例子中的 interval 和 line 的 y 通道会使用两个不同的比例尺,从而会生成两个不同的坐标轴。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'view', data: [ { time: '10:10', call: 4, waiting: 2, people: 2 }, { time: '10:15', call: 2, waiting: 6, people: 3 }, { time: '10:20', call: 13, waiting: 2, people: 5 }, { time: '10:25', call: 9, waiting: 9, people: 1 }, { time: '10:30', call: 5, waiting: 2, people: 3 }, { time: '10:35', call: 8, waiting: 2, people: 1 }, { time: '10:40', call: 13, waiting: 1, people: 2 }, ], children: [ { type: 'interval', encode: { x: 'time', y: 'waiting' }, axis: { y: { title: 'Waiting', titleFill: '#5B8FF9' } }, }, { type: 'line', encode: { x: 'time', y: 'people', shape: 'smooth' }, scale: { y: { independent: true } }, // 设置 y 方向比例尺不同步 style: { stroke: '#fdae6b', lineWidth: 2 }, axis: { y: { position: 'right', grid: null, title: 'People', titleFill: '#fdae6b', }, }, }, ], }); chart.render(); ``` 如果希望比例尺分组同步,可以声明 `scale.key`,拥有相同 key 的 scale 会同步。比如下面的 Line 和 Point Mark y 通道的比例尺因为 key 都是 line 所以会同步。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'view', data: [ { time: '10:10', call: 4, waiting: 2, people: 2 }, { time: '10:15', call: 2, waiting: 6, people: 3 }, { time: '10:20', call: 13, waiting: 2, people: 5 }, { time: '10:25', call: 9, waiting: 9, people: 1 }, { time: '10:30', call: 5, waiting: 2, people: 3 }, { time: '10:35', call: 8, waiting: 2, people: 1 }, { time: '10:40', call: 13, waiting: 1, people: 2 }, ], children: [ { type: 'interval', encode: { x: 'time', y: 'waiting' }, axis: { y: { title: 'Waiting', titleFill: '#5B8FF9' } }, }, { type: 'line', encode: { x: 'time', y: 'people', shape: 'smooth' }, scale: { y: { key: 'line' } }, // 设置 key 为 line style: { stroke: '#fdae6b', lineWidth: 2 }, axis: { y: { position: 'right', grid: null, title: 'People', titleFill: '#fdae6b', }, }, }, { type: 'point', encode: { x: 'time', y: 'people' }, scale: { y: { key: 'line' } }, // 设置 key 为 line style: { stroke: '#fdae6b', lineWidth: 2 }, }, ], }); chart.render(); ``` ## 视图比例尺 比例尺会可以配置在视图层级,并且会传递给 `children` 指定的标记,如果该标记对应的通道没有设置比例尺,就设置,否则没有影响。在不绘制多轴图的情况下,比例尺是可以设置在视图层级的。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', theme: 'classic', }); chart.options({ type: 'view', data: [ { year: '1991', value: 3 }, { year: '1992', value: 4 }, { year: '1993', value: 3.5 }, { year: '1994', value: 5 }, { year: '1995', value: 4.9 }, { year: '1996', value: 6 }, { year: '1997', value: 7 }, { year: '1998', value: 9 }, { year: '1999', value: 13 }, ], encode: { x: 'year', y: 'value' }, scale: { y: { nice: true } }, children: [{ type: 'line' }, { type: 'point' }], }); chart.render(); ``` ## 比例尺类型推断 G2 具备智能的 scale 类型推断能力,当用户没有明确指定 scale 类型时,会根据数据特征和通道特性自动选择最适合的 scale 类型。推断机制遵循以下优先级规则: ### 推断优先级 **1. 显式指定优先级最高** 如果用户在 scale 配置中明确指定了 `type` 属性,G2 会直接使用该类型,跳过所有自动推断逻辑。 **2. 特殊数据类型判断** G2 会首先检查数据的特殊性质: - **对象类型数据**:如果数据包含严格对象(非 Date、非 null、非数组的对象),使用 `identity` 比例尺。 ```ts export function isStrictObject(d: any): boolean { return ( typeof d === 'object' && !(d instanceof Date) && d !== null && !Array.isArray(d) ); } ``` - **字符串类型 range**:当 range 参数是字符串时,使用 `linear` 比例尺。 - **多值域/定义域**:当 domain 或 range 数组长度超过 2 时,推断为分类比例尺。 **3. 基于定义域(domain)的推断** 当提供了 domain 参数时: - 如果 domain 包含字符串或布尔值,推断为分类比例尺。 - 如果数据包含 Date 对象,使用 `time` 比例尺。 - 其他情况推断为连续比例尺。 **4. 基于数据值的自动推断** 当没有 domain 参数时,G2 会分析实际数据: - **分类型检测**:数据中包含字符串或布尔值时,推断为分类比例尺。 - **时间类型检测**:数据中包含 Date 对象时,使用 `time` 比例尺。 - **其他情况**:推断为连续型比例尺。 ### 进一步推断 当推断为分类比例尺时,G2 会根据通道名称进一步细分: - 对于定量通道(x、y、position、size 开头的通道),使用 `point` 比例尺。 - 其他通道使用 `ordinal` 比例尺。 当推断为连续比例尺时: - 对于非颜色通道,使用 `linear` 比例尺。 - 对于颜色通道: - 有 range 参数时使用 `linear` 比例尺。 - 无 range 参数时使用 `sequential` 比例尺。( sequential 比例尺构造可创建一个在输入和输出之间通过插值函数进行转换的比例尺,内部比例尺处理时使用) ### 示例说明 ```js // 字符串数据 → ordinal scale ({ type: 'interval', encode: { x: 'category' } }); // category: ['A', 'B', 'C'] // 数值数据 → linear scale ({ type: 'line', encode: { y: 'value' } }); // value: [10, 20, 30] // 时间数据 → time scale ({ type: 'line', encode: { x: 'date' } }); // date: [new Date('2023-01-01'), ...] // 定量通道的字符串数据 → point scale ({ type: 'interval', encode: { x: 'month' } }); // month: ['Jan', 'Feb', 'Mar'] // 显式指定优先级最高 ({ type: 'interval', scale: { y: { type: 'log' } } }); // 强制使用 log scale // 多值 range → 推断为分类型 ({ type: 'point', scale: { color: { range: ['red', 'green', 'blue', 'yellow'], // 4个值,推断为 ordinal }, }, }); ``` #### 概览 - Canonical page: https://g2.antv.antgroup.com/zh/manual/core/theme/overview/ - Markdown source: https://g2.antv.antgroup.com/zh/manual/core/theme/overview.md - Description: 设置 G2 图表主题,切换内置主题、调整配色与组件样式,并通过自定义主题保持图表视觉一致。 --- title: "概览" description: "设置 G2 图表主题,切换内置主题、调整配色与组件样式,并通过自定义主题保持图表视觉一致。" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/manual/core/theme/overview/" version: "5.4.8" --- ## 概述 G2 中 **主题(Theme)** 是一组预定义的样式配置,用于控制图表的整体外观,包括颜色、字体、边距等视觉属性。主题系统使得图表能够保持一致的视觉风格,同时提供了灵活的自定义能力,以满足不同场景下的设计需求。 主题在 G2 中的作用主要体现在以下几个方面: 1. **统一视觉风格**:为图表提供一致的视觉语言,使得多个图表在同一应用中能够保持风格统一 2. **适应不同场景**:提供明暗两种基础主题,适应不同的应用背景和使用环境 3. **简化配置**:通过预设的样式配置,减少重复的样式定义工作 4. **增强可读性**:通过精心设计的颜色方案和布局,提升图表的可读性和美观度 G2 的主题系统支持多层级配置,可以在视图层级或标记层级进行设置,并且支持局部覆盖和完全自定义。 ### 主题层级 G2 的主题可以在不同层级进行配置: 1. **视图层级**:应用于整个图表或视图 ```js chart.options({ theme: { type: 'dark' }, }); ``` 2. **标记层级**:应用于特定的图形标记 ```js chart.options({ type: 'interval', theme: { color: 'steelblue' }, }); ``` 当同时存在多个层级的主题配置时,标记层级的配置会覆盖视图层级的配置。 ### 内置主题 G2 内置了多种主题,可以通过 `type` 属性进行切换: | 主题名称 | 描述 | 适用场景 | | ------------- | ------------ | ------------------------------------ | | `light` | 默认亮色主题 | 适用于浅色背景的应用界面 | | `dark` | 暗色主题 | 适用于深色背景的应用界面 | | `classic` | 经典主题 | 基于亮色主题的变体,使用经典配色方案 | | `classicDark` | 经典暗色主题 | 基于暗色主题的变体,使用经典配色方案 | | `academy` | 学术主题 | 适用于学术论文、报告等场景 | 使用内置主题的方式如下: ```js // Spec形式 chart.options({ theme: { type: 'dark' }, }); // classicDark chart.options({ type: 'view', theme: { type: 'classicDark' } }); ``` ## 配置项 主题配置项可以分为基础配置、视图配置、组件配置和标记配置四个部分。 | 属性 | 描述 | 类型 | 默认值 | 必选 | | ---------- | -------------------------- | ---------------------------------------------------------------------- | ---------- | ---- | | type | 指定使用的主题类型 | `'light'` \| `'dark'` \| `'classic'` \| `'classicDark'` \| `'academy'` | `'light'` | | | padding | 图表内边距 | `'auto'` \| `number` | `'auto'` | | | margin | 图表外边距 | `number` | `16` | | | inset | 图表内部图形与坐标轴的间距 | `'auto'` \| `number` | `'auto'` | | | color | 默认颜色 | `string` | 取决于主题 | | | size | 默认大小 | `number` | `1` | | | category10 | 分类颜色方案(10 色) | `string` \| `string[]` | 取决于主题 | | | category20 | 分类颜色方案(20 色) | `string` \| `string[]` | 取决于主题 | | | view | 视图区域配置 | [view](#view) | - | | | enter | 入场动画配置 | [animation](#animation) | - | | | update | 更新动画配置 | [animation](#animation) | - | | | exit | 退场动画配置 | [animation](#animation) | - | | ### view 视图区域配置项。 | 属性 | 描述 | 类型 | 默认值 | 必选 | | ----------- | -------------------- | -------- | --------------- | ---- | | viewFill | 整个视图区域的填充色 | `string` | `'transparent'` | | | plotFill | 绘图区域的填充色 | `string` | `'transparent'` | | | mainFill | 主区域的填充色 | `string` | `'transparent'` | | | contentFill | 内容区域的填充色 | `string` | `'transparent'` | | ### animation 动画配置项。 | 属性 | 描述 | 类型 | 默认值 | 必选 | | -------- | -------------------- | ----------------------------------------------------- | -------- | ---- | | duration | 动画持续时间(毫秒) | `number` | `300` | | | fill | 动画填充模式 | `'none'` \| `'forwards'` \| `'backwards'` \| `'both'` | `'both'` | | | delay | 动画延迟时间(毫秒) | `number` | `0` | | ## 自定义主题 G2 提供了两种自定义主题的方式:局部覆盖和完全自定义。 ### 局部覆盖 最简单的自定义方式是在使用主题时覆盖部分配置项: ```js // Spec形式 chart.options({ theme: { type: 'light', // 基于light主题 color: 'steelblue', // 覆盖默认颜色 margin: 20, // 覆盖默认外边距 }, }); // Spec chart.options({ type: 'view', theme: { type: 'dark', // 基于dark主题 category10: ['#ff0000', '#00ff00', '#0000ff'], // 自定义颜色方案 }, }); ``` 这种方式适合只需要调整少量样式的场景,简单直接。 ### 完全自定义 对于需要完全控制主题的场景,可以创建自定义主题函数,然后注册并使用: ```js // 1. 定义主题函数 function CustomTheme() { // 可以基于现有主题进行修改 const light = G2.Light(); // 返回修改后的主题配置 return { ...light, color: '#3366cc', category10: [ '#3366cc', '#dc3912', '#ff9900', '#109618', '#990099', '#0099c6', '#dd4477', '#66aa00', '#b82e2e', '#316395', ], // 其他自定义配置... }; } // 2. 注册主题 G2.register('theme.custom', CustomTheme); // 3. 使用自定义主题 chart.options({ theme: { type: 'custom' }, }); ``` 这种方式适合需要全面控制主题样式的场景,提供了最大的灵活性。 ## 示例 ### 使用内置主题 下面的示例展示了如何使用内置的暗色主题: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ theme: { type: 'dark', view: { viewFill: '#1f1f1f', // 设置暗色背景 }, }, type: 'interval', data: [ { genre: '运动', sold: 275 }, { genre: '策略', sold: 115 }, { genre: '角色扮演', sold: 120 }, { genre: '动作', sold: 350 }, { genre: '模拟', sold: 150 }, ], encode: { x: 'genre', y: 'sold', color: 'genre', }, }); chart.render(); ``` ### 自定义颜色 下面的示例展示了如何自定义默认颜色: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ theme: { color: '#8a2be2' }, // 设置默认颜色为紫色 type: 'line', data: [ { year: '2018', value: 30 }, { year: '2019', value: 40 }, { year: '2020', value: 45 }, { year: '2021', value: 50 }, { year: '2022', value: 56 }, { year: '2023', value: 70 }, ], encode: { x: 'year', y: 'value', }, }); chart.render(); ``` ### 自定义颜色方案 下面的示例展示了如何自定义分类颜色方案: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ theme: { // 自定义分类颜色方案 category10: [ '#ff9999', '#99ff99', '#9999ff', '#ffff99', '#ff99ff', '#99ffff', '#ffcc99', '#99ccff', '#ff9966', '#66ff99', ], }, type: 'interval', data: [ { category: 'A', value: 10 }, { category: 'B', value: 20 }, { category: 'C', value: 15 }, { category: 'D', value: 25 }, { category: 'E', value: 18 }, ], encode: { x: 'category', y: 'value', color: 'category', }, }); chart.render(); ``` ### 自定义视图区域样式 下面的示例展示了如何自定义视图区域的样式: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ theme: { view: { viewFill: '#f5f5f5', // 设置视图背景色 plotFill: '#ffffff', // 设置绘图区背景色 }, }, type: 'point', data: [ { x: 1, y: 4 }, { x: 2, y: 5 }, { x: 3, y: 7 }, { x: 4, y: 3 }, { x: 5, y: 6 }, { x: 6, y: 8 }, { x: 7, y: 5 }, { x: 8, y: 9 }, { x: 9, y: 6 }, ], encode: { x: 'x', y: 'y', shape: 'point', size: 10, }, }); chart.render(); ``` 更多主题相关的示例,可以查看[图表示例 - 主题](/zh/examples/style/theme/)页面。 #### 概览 - Canonical page: https://g2.antv.antgroup.com/zh/manual/core/transform/overview/ - Markdown source: https://g2.antv.antgroup.com/zh/manual/core/transform/overview.md - Description: 概览 --- title: "概览" description: "概览" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/manual/core/transform/overview/" version: "5.4.8" --- 在 G2 中,**标记转换(Mark Transform)** 提供了一种便捷的机制,用于修改数据和标记选项,主要用于支持数据分析。标记转换的本质是一个函数,该函数能够**筛选**、**修改**、**聚合**以及**生成**新的通道值,从而优化图形展示,提升数据的可解释性。 ## 配置层级 转换是一个数组,声明的转换会 **按照顺序执行**。转换可以配置在 Mark 层级: ```js ({ type: 'interval', transform: [{ type: 'stackY' }, { type: 'sortX' }], }); ``` 标记转换会去修改每个通道绑定的数据,从而改变图表的展示形式。比如 StackY 转换堆叠了条形图 y 和 y1 通道绑定的列数据: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'interval', autoFit: true, data: [ { city: 'London', month: 'Jan.', rainfall: 18.9 }, { city: 'London', month: 'Feb.', rainfall: 28.8 }, { city: 'London', month: 'Mar.', rainfall: 39.3 }, { city: 'London', month: 'Apr.', rainfall: 81.4 }, { city: 'London', month: 'May', rainfall: 47 }, { city: 'London', month: 'Jun.', rainfall: 20.3 }, { city: 'London', month: 'Jul.', rainfall: 24 }, { city: 'London', month: 'Aug.', rainfall: 35.6 }, { city: 'Berlin', month: 'Jan.', rainfall: 12.4 }, { city: 'Berlin', month: 'Feb.', rainfall: 23.2 }, { city: 'Berlin', month: 'Mar.', rainfall: 34.5 }, { city: 'Berlin', month: 'Apr.', rainfall: 99.7 }, { city: 'Berlin', month: 'May', rainfall: 52.6 }, { city: 'Berlin', month: 'Jun.', rainfall: 35.5 }, { city: 'Berlin', month: 'Jul.', rainfall: 37.4 }, { city: 'Berlin', month: 'Aug.', rainfall: 42.4 }, ], encode: { x: 'month', y: 'rainfall', color: 'city' }, transform: [{ type: 'stackY' }], }); chart.render(); ``` 转换也可以配置在 View 层级: ```js ({ type: 'view', transform: [{ type: 'stackY' }, { type: 'sortX' }], }); ``` 在视图上声明的转换会传递给 `children` 声明的标记。如果该标记没有转换就设置,否则没有影响。比如下面这个带有转换的堆叠面积图: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'view', autoFit: true, data: [ { city: 'London', month: 'Jan.', rainfall: 18.9 }, { city: 'London', month: 'Feb.', rainfall: 28.8 }, { city: 'London', month: 'Mar.', rainfall: 39.3 }, { city: 'London', month: 'Apr.', rainfall: 81.4 }, { city: 'London', month: 'May', rainfall: 47 }, { city: 'London', month: 'Jun.', rainfall: 20.3 }, { city: 'London', month: 'Jul.', rainfall: 24 }, { city: 'London', month: 'Aug.', rainfall: 35.6 }, { city: 'Berlin', month: 'Jan.', rainfall: 12.4 }, { city: 'Berlin', month: 'Feb.', rainfall: 23.2 }, { city: 'Berlin', month: 'Mar.', rainfall: 34.5 }, { city: 'Berlin', month: 'Apr.', rainfall: 99.7 }, { city: 'Berlin', month: 'May', rainfall: 52.6 }, { city: 'Berlin', month: 'Jun.', rainfall: 35.5 }, { city: 'Berlin', month: 'Jul.', rainfall: 37.4 }, { city: 'Berlin', month: 'Aug.', rainfall: 42.4 }, ], encode: { x: 'month', y: 'rainfall', color: 'city' }, transform: [{ type: 'stackY' }], // 视图层级的转换 children: [ { type: 'area', style: { fillOpacity: 0.5 } }, { type: 'line', style: { strokeWidth: 2 }, tooltip: false }, ], }); chart.render(); ``` ## 使用场景 常见的转换的作用一般有三种: - **防止重叠** 在将数据映射到图形时,必须通过视觉编码来实现。在所有视觉通道中,位置是最具区分度的通道( x 通道、y 通道等)。然而,在某些情况下,图形中的位置可能会出现重叠,导致数据分析变得困难。为了应对这种情况,通常需要对原始图形进行一定的转换,比如 dodgeX、stackY,jitterX 等。 - **数据聚合** 还有一类标记转换主要是用来做数据聚合:比如 bin 和 group。和传统的数据聚合不同,标记聚合是发生在绘制之中,而不是在绘制之前。这使得我们不需要去操作抽象的原始数据,而是直接操作通道值即可。这大大提高了我们探索数据的效率。 - **绘制图形标注** 在 G2 中,并未单独提供专门的标注组件,而是通过灵活配置标记来实现标注功能。既然标注也是一种标记,那么它也可以执行标记转换。 ## 防止重叠 转换的一个作用是防止重叠,对数据进行调整,使得图形在画布上不互相重叠。 💡 **数据调整的原则** 我们调整数据的目的是为了使得图形不互相遮挡,对数据的认识更加清晰,但是必须保证对数据的正确理解,所以需要遵循以下原则: - 不能改变数据的含义,给用户带来误解; - 数据调整的界限要清晰,不能混淆不同类别的数据; - 定量(连续)数据只能进行数据的累加和对称,分类数据只能在当前分类的范围内调整数据。 比如如下的数据绘制的散点图中 x 通道相同的点完全重叠在一起,很难区分。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'point', autoFit: true, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/2c813e2d-2276-40b9-a9af-cf0a0fb7e942.csv', }, encode: { y: 'Horsepower', x: 'Cylinders', shape: 'hollow', color: 'Cylinders', }, scale: { x: { type: 'point' }, color: { type: 'ordinal' } }, }); chart.render(); ``` 可以通过配置 [jitterX](/zh/manual/core/transform/jitter-x/) 转换实现在某个区域的 x 方向散开的效果。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'point', autoFit: true, data: { type: 'fetch', value: 'https://gw.alipayobjects.com/os/bmw-prod/2c813e2d-2276-40b9-a9af-cf0a0fb7e942.csv', }, encode: { y: 'Horsepower', x: 'Cylinders', shape: 'hollow', color: 'Cylinders', }, scale: { x: { type: 'point' }, color: { type: 'ordinal' } }, transform: [{ type: 'jitterX' }], //配置 jitterX 转换实现在某个区域的 x 方向散开的效果 }); chart.render(); ``` 这种情况在柱状图中也很常见,比如如下的数据绘制的柱状图中在 x 通道是分类的情况下,同一个分类下有多条记录是会出现重叠,很难区分。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'interval', autoFit: true, data: [ { city: 'London', month: 'Jan.', rainfall: 18.9 }, { city: 'London', month: 'Feb.', rainfall: 28.8 }, { city: 'London', month: 'Mar.', rainfall: 39.3 }, { city: 'London', month: 'Apr.', rainfall: 81.4 }, { city: 'London', month: 'May', rainfall: 47 }, { city: 'London', month: 'Jun.', rainfall: 20.3 }, { city: 'London', month: 'Jul.', rainfall: 24 }, { city: 'London', month: 'Aug.', rainfall: 35.6 }, { city: 'Berlin', month: 'Jan.', rainfall: 12.4 }, { city: 'Berlin', month: 'Feb.', rainfall: 23.2 }, { city: 'Berlin', month: 'Mar.', rainfall: 34.5 }, { city: 'Berlin', month: 'Apr.', rainfall: 99.7 }, { city: 'Berlin', month: 'May', rainfall: 52.6 }, { city: 'Berlin', month: 'Jun.', rainfall: 35.5 }, { city: 'Berlin', month: 'Jul.', rainfall: 37.4 }, { city: 'Berlin', month: 'Aug.', rainfall: 42.4 }, ], encode: { x: 'month', y: 'rainfall', color: 'city' }, }); chart.render(); ``` 这时候可以声明一个 [dodgeX](/zh/manual/core/transform/dodge-x/) 去绘制分组柱状图: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'interval', autoFit: true, data: [ { city: 'London', month: 'Jan.', rainfall: 18.9 }, { city: 'London', month: 'Feb.', rainfall: 28.8 }, { city: 'London', month: 'Mar.', rainfall: 39.3 }, { city: 'London', month: 'Apr.', rainfall: 81.4 }, { city: 'London', month: 'May', rainfall: 47 }, { city: 'London', month: 'Jun.', rainfall: 20.3 }, { city: 'London', month: 'Jul.', rainfall: 24 }, { city: 'London', month: 'Aug.', rainfall: 35.6 }, { city: 'Berlin', month: 'Jan.', rainfall: 12.4 }, { city: 'Berlin', month: 'Feb.', rainfall: 23.2 }, { city: 'Berlin', month: 'Mar.', rainfall: 34.5 }, { city: 'Berlin', month: 'Apr.', rainfall: 99.7 }, { city: 'Berlin', month: 'May', rainfall: 52.6 }, { city: 'Berlin', month: 'Jun.', rainfall: 35.5 }, { city: 'Berlin', month: 'Jul.', rainfall: 37.4 }, { city: 'Berlin', month: 'Aug.', rainfall: 42.4 }, ], encode: { x: 'month', y: 'rainfall', color: 'city' }, transform: [{ type: 'dodgeX' }], //配置 dodgeX 转换实现柱状图的分组 }); chart.render(); ``` 或者声明一个 [stackY](/zh/manual/core/transform/stack-y/) 去绘制堆叠柱状图: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'interval', autoFit: true, data: [ { city: 'London', month: 'Jan.', rainfall: 18.9 }, { city: 'London', month: 'Feb.', rainfall: 28.8 }, { city: 'London', month: 'Mar.', rainfall: 39.3 }, { city: 'London', month: 'Apr.', rainfall: 81.4 }, { city: 'London', month: 'May', rainfall: 47 }, { city: 'London', month: 'Jun.', rainfall: 20.3 }, { city: 'London', month: 'Jul.', rainfall: 24 }, { city: 'London', month: 'Aug.', rainfall: 35.6 }, { city: 'Berlin', month: 'Jan.', rainfall: 12.4 }, { city: 'Berlin', month: 'Feb.', rainfall: 23.2 }, { city: 'Berlin', month: 'Mar.', rainfall: 34.5 }, { city: 'Berlin', month: 'Apr.', rainfall: 99.7 }, { city: 'Berlin', month: 'May', rainfall: 52.6 }, { city: 'Berlin', month: 'Jun.', rainfall: 35.5 }, { city: 'Berlin', month: 'Jul.', rainfall: 37.4 }, { city: 'Berlin', month: 'Aug.', rainfall: 42.4 }, ], encode: { x: 'month', y: 'rainfall', color: 'city' }, transform: [{ type: 'stackY' }], //配置 stackY 转换实现柱状图的堆叠 }); chart.render(); ``` 以下是 G2 中内置的防止重叠的标记转换: | 转换 | 详细描述 | 示例 | | ------------------------------------------ | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | [diffY](/zh/manual/core/transform/diff-y/) | 将 y 通道的值进行对比,并生成差值的类型,这通常用于对两个数值集进行变化衡量。 | | | [dodgeX](/zh/manual/core/transform/dodge-x/) | 在 x 通道方向上,对元素进行分组和错位排列,以避免重叠,便于区分数据点。 | | | [jitter](/zh/manual/core/transform/jitter/) | 在 x 和 y 通道方向随机生成轻微的摆动,用于处理图形中数据点的重叠问题。 | | | [jitterX](/zh/manual/core/transform/jitter-x/) | 在 x 通道方向随机生成轻微的摆动,用于减少数据点的重叠。 | | | [jitterY](/zh/manual/core/transform/jitter-y/) | 在 y 通道方向随机生成轻微的摆动,用于减少数据点的重叠。 | 和 jitterX 只是方向上的差别 | | [pack](/zh/manual/core/transform/pack/) | 将数据点进行紧密排列,以优化空间利用率,适用于密集型分布布局。 | | | [sample](/zh/manual/core/transform/sample/) | 对数据进行采样,基于一定算法从原始数据集中选择子集,常用于大数据下处理。 | | | [stackY](/zh/manual/core/transform/stack-y/) | 在 y 通道方向对数据进行堆叠处理,通常用于展示数值累积的效果或分布情况。 | | ## 数据聚合 还有一类标记转换主要是用来做数据聚合:比如 [bin](/zh/manual/core/transform/bin/) 和 [group](/zh/manual/core/transform/group/)。和传统的数据聚合不同,标记聚合是发生在绘制之中,而不是在绘制之前。这使得我们不需要去操作抽象的原始数据,而是直接操作通道值即可。这大大提高了我们探索数据的效率。 ```ts import { table } from '@antv/astro-theme-antv/table'; await table( { url: 'https://assets.antv.antgroup.com/g2/penguins.json', }, document.getElementById('container')!, ); ``` 下面是一份企鹅集群的数据,接下来以此作为案例,介绍 G2 中一些常用的数据聚合的方式。 首先我们如下绘制一个散点图,展现了企鹅 `culmen_depth_mm` 和 `culmen_length_mm` 的相关性。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'point', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/penguins.json', transform: [ { type: 'filter', callback: (d) => d.culmen_depth_mm !== null }, ], }, encode: { x: (d) => +d.culmen_depth_mm, y: (d) => +d.culmen_length_mm }, }); chart.render(); ``` 但是散点图无法直观地看出企鹅群体某个数据的分布情况,比如 `culmen_depth_mm` 的具体分布情况,这时候就可以使用 [binX](/zh/manual/core/transform/bin-x/) 对数据进行分箱,通过直方图进行进一步的数据分析。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'rect', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/penguins.json', transform: [ { type: 'filter', callback: (d) => d.culmen_depth_mm !== null }, ], }, encode: { x: (d) => +d.culmen_depth_mm }, transform: [{ type: 'binX', y: 'count' }], style: { insetLeft: 1 }, }); chart.render(); ``` bin 主要是用来聚合数值类型的数据,group 主要针对离散数据。在上面的例子中,如果想要分析不同岛屿上不同企鹅种类的数量,可以使用 [groupX](/zh/manual/core/transform/group-x/)对 x 通道进行分组,并对 y 通道根据 count 方式进行聚合。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'interval', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/penguins.json', transform: [ { type: 'filter', callback: (d) => d.culmen_depth_mm !== null }, ], }, encode: { x: 'island', color: 'species' }, transform: [ { type: 'groupX', y: 'count', }, { type: 'stackY', }, ], }); chart.render(); ``` 如果我们不关心具体的数量多少,而是想聚焦于不同种类的企鹅的占比,可以使用 [normalizeY](/zh/manual/core/transform/normalize-y/) 进行归一化处理。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'interval', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/penguins.json', transform: [ { type: 'filter', callback: (d) => d.culmen_depth_mm !== null }, ], }, encode: { x: 'island', color: 'species' }, transform: [ { type: 'groupX', y: 'count', }, { type: 'stackY', }, { type: 'normalizeY', }, ], }); chart.render(); ``` 以下是 G2 中内置的用于数据聚合的标记转换: | 转换 | 详细描述 | 示例 | | ------------------------------------------------ | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | | [bin](/zh/manual/core/transform/bin/) | 将数据分组到固定的区间(或桶)中,通常用于直方图的构造。 | | | [binX](/zh/manual/core/transform/bin-x/) | 在 x 通道方向对数据进行分组,生成一系列区间(或桶)。 | | | [flexX](/zh/manual/core/transform/flex-x/) | 在 x 通道上灵活排列数据,用于处理自适应布局或特殊排列要求。 | | | [group](/zh/manual/core/transform/group/) | 对数据进行分组,基于某些条件或属性将数据划分为多个子集。 | | | [groupColor](/zh/manual/core/transform/group-color/) | 基于数据属性对数据的颜色进行分组,常用于分类数据的可视化。 | | | [groupX](/zh/manual/core/transform/group-x/) | 基于 x 通道的数据属性进行分组,常用于带有分类维度的数据可视化。 | | | [groupY](/zh/manual/core/transform/group-y/) | 基于 y 通道的数据属性进行分组,常用于将数据按类别排列在 y 通道上的情况。 | | | [normalizeY](/zh/manual/core/transform/normalize-y/) | 在 y 通道上将数据归一化,通常是对数据进行比例调整便于比较和可视化展示。 | | | [select](/zh/manual/core/transform/select/) | 对数据进行子集的筛选,基于指定的条件过滤出一部分数据。 | | | [selectX](/zh/manual/core/transform/select-x/) | 基于 x 通道上的数据进行筛选,选出符合范围或条件的数据子集。 | | | [selectY](/zh/manual/core/transform/select-y/) | 基于 y 通道上的数据进行筛选,选出符合范围或条件的数据子集。 | | | [sortColor](/zh/manual/core/transform/sort-color/) | 基于颜色的优先级对数据进行排序,用于生成颜色的有序视觉结构。 | | | [sortX](/zh/manual/core/transform/sort-x/) | 按 x 通道上的数据进行排序,以生成具有顺序关系的视觉图形。 | | | [sortY](/zh/manual/core/transform/sort-y/) | 按 y 通道上的数据进行排序,以生成具有顺序关系的视觉图形。 | | | [stackEnter](/zh/manual/core/transform/stack-enter/) | 对 enterDuration 和 enterDelay 通道进行堆叠,实现分组动画的效果。 | | | [symmetryY](/zh/manual/core/transform/symmetry-y/) | 在 y 通道方向生成对称分布,用于构造具有对称性的数据布局。 | | ## 绘制图形标注 在标记部分的文档里已经阐述过,在 G2 中,并未单独提供专门的标注组件,而是通过灵活配置标记来实现标注功能。既然标注也是一种标记,那么它也可以执行标记转换。 标注的数据通常与图表的数据来源保持一致,基于此数据源通过标记转换来生成标注内容。在标注构造过程中,可以借助 **group** 将数据按特定分类或属性进行分组,或使用 **select** 筛选出满足特定条件的子集,从而实现对关键信息的聚焦与强化处理。这种灵活的标记转换不仅可以帮助有效地构建标注,还能确保标注内容准确地贴合数据的逻辑与视觉需求,同时提升图表的可读性与表达效果。 ### group groupX 是常用的一种标记转换,基于 **x 通道** 对数据进行分组,并对指定的通道进行聚合处理。具体来说,它将数据按照 **x 通道**的分组维度进行聚合计算,并结合 **y 通道**上的数据进行进一步处理。例如,可以对每组 **y 数据**进行计算并取其平均值(`mean`),作为聚合结果。最终,聚合后的数据会被用于绘制标记(如 lineY),从而生成一条具有统计学意义的平均线。这种方法简化了分组与聚合的流程,使得在图形绘制时就能直接操作聚合后的数据,提升了数据处理效率与可视化表达的精准性。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'view', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/seattle-weather.json', }, children: [ { type: 'interval', encode: { x: (d) => new Date(d.date).getUTCMonth(), y: 'precipitation', }, transform: [{ type: 'groupX', y: 'mean' }], scale: { y: { tickCount: 5, domainMax: 6 } }, tooltip: { items: [{ channel: 'y', valueFormatter: '.2f' }] }, }, { type: 'lineY', encode: { y: 'precipitation' }, transform: [{ type: 'groupX', y: 'mean' }], style: { stroke: '#F4664A', strokeOpacity: 1, lineWidth: 2, lineDash: [3, 3], }, }, ], }); chart.render(); ``` 同理我们可以用 groupY 转换来绘制直方图的中位线。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'view', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/movies.json', transform: [{ type: 'filter', callback: (d) => d['IMDB Rating'] > 0 }], }, children: [ { type: 'rect', encode: { x: 'IMDB Rating' }, transform: [{ type: 'binX', y: 'count', thresholds: 9 }], scale: { y: { domainMax: 1000 } }, style: { inset: 1 }, }, { type: 'lineX', encode: { x: 'IMDB Rating' }, transform: [{ type: 'groupY', x: 'median' }], style: { stroke: '#F4664A', strokeOpacity: 1, lineWidth: 2, lineDash: [4, 4], }, }, ], }); chart.render(); ``` ### select 在可视化中,当需要突出显示某些特殊位置(如起点、终点或最大值)时,select 标记转换是一种非常灵活且高效的选择。通过 select 转换,可以基于条件筛选数据并实现对特定位置的标记。以下示例展示了如何使用 selectY 对 **折线图**的数据进行筛选,从而标注图中 **峰值位置**的具体实现: ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'view', autoFit: true, data: [ { month: 'Jan', city: 'Tokyo', temperature: 7 }, { month: 'Jan', city: 'London', temperature: 3.9 }, { month: 'Feb', city: 'Tokyo', temperature: 6.9 }, { month: 'Feb', city: 'London', temperature: 4.2 }, { month: 'Mar', city: 'Tokyo', temperature: 9.5 }, { month: 'Mar', city: 'London', temperature: 5.7 }, { month: 'Apr', city: 'Tokyo', temperature: 14.5 }, { month: 'Apr', city: 'London', temperature: 8.5 }, { month: 'May', city: 'Tokyo', temperature: 18.4 }, { month: 'May', city: 'London', temperature: 11.9 }, { month: 'Jun', city: 'Tokyo', temperature: 21.5 }, { month: 'Jun', city: 'London', temperature: 15.2 }, { month: 'Jul', city: 'Tokyo', temperature: 25.2 }, { month: 'Jul', city: 'London', temperature: 17 }, { month: 'Aug', city: 'Tokyo', temperature: 26.5 }, { month: 'Aug', city: 'London', temperature: 16.6 }, { month: 'Sep', city: 'Tokyo', temperature: 23.3 }, { month: 'Sep', city: 'London', temperature: 14.2 }, { month: 'Oct', city: 'Tokyo', temperature: 18.3 }, { month: 'Oct', city: 'London', temperature: 10.3 }, { month: 'Nov', city: 'Tokyo', temperature: 13.9 }, { month: 'Nov', city: 'London', temperature: 6.6 }, { month: 'Dec', city: 'Tokyo', temperature: 9.6 }, { month: 'Dec', city: 'London', temperature: 4.8 }, ], encode: { x: 'month', y: 'temperature', color: 'city' }, scale: { x: { range: [0, 1] }, y: { nice: true } }, axis: { y: { labelFormatter: (d) => d + '°C' } }, children: [ { type: 'line', encode: { shape: 'smooth' } }, { type: 'text', encode: { x: 'month', y: 'temperature', text: (d) => `峰值:${d.temperature}`, }, transform: [ { type: 'selectY', groupBy: 'color', selector: 'max', }, ], style: { fill: 'orange', fontSize: 16, dy: -15, }, tooltip: false, }, { type: 'point', encode: { x: 'month', y: 'temperature' }, transform: [ { type: 'selectY', groupBy: 'color', selector: 'max', }, ], tooltip: false, }, ], }); chart.render(); ``` ## 示例 ### 同时使用多个转换 我们也可以同时声明多个转换。比如在上面的企鹅的例子中,我们多考虑一个数据维度:企鹅的性别,就可以连续声明 binX 和 stackY 转换。需要注意的一点是,G2 里的转换是按顺序执行的,在下面的例子里交换 binX 和 stackY 的顺序会出错。 ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'rect', autoFit: true, data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/penguins.json', transform: [ { type: 'filter', callback: (d) => d.culmen_depth_mm !== null }, ], }, encode: { x: (d) => +d.culmen_depth_mm, color: 'sex' }, transform: [ { type: 'binX', y: 'count' }, // 声明 binX 转换 { type: 'stackY', orderBy: 'sum', reverse: true }, // 声明 stackY 转换 ], style: { insetLeft: 1 }, }); chart.render(); ``` #### geoPath - Canonical page: https://g2.antv.antgroup.com/zh/manual/extra-topics/geo/geo-path/ - Markdown source: https://g2.antv.antgroup.com/zh/manual/extra-topics/geo/geo-path.md - Description: geoPath --- title: "geoPath" description: "geoPath" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/manual/extra-topics/geo/geo-path/" version: "5.4.8" --- `geoPath` 可以用来结合 geojson 绘制地图。 ## 开始使用 geoPath ```ts import { Chart } from '@antv/g2'; import { feature } from 'topojson-client'; Promise.all([ fetch('https://assets.antv.antgroup.com/g2/us-10m.json').then((res) => res.json(), ), fetch('https://assets.antv.antgroup.com/g2/unemployment2.json').then((res) => res.json(), ), ]).then((values) => { const [us, unemployment] = values; const counties = feature(us, us.objects.counties).features; const chart = new Chart({ container: 'container', }); chart.options({ type: 'geoPath', coordinate: { type: 'albersUsa' }, data: { value: counties, transform: [ { type: 'join', join: unemployment, on: ['id', 'id'], select: ['rate'], }, ], }, scale: { color: { type: 'sequential', palette: 'ylGnBu', unknown: '#fff', }, }, encode: { color: 'rate' }, }); chart.render(); }); ``` 更多的案例,可以查看[图表示例](/zh/examples/)页面。 ## 选项 目前 `geoPath` 除了和通用 mark 相同的 API 和配置项,还有一个特殊的就是 `projection` 映射,当前 G2 内置了 [d3-geo](https://github.com/d3/d3-geo) 的所有映射。 ## FAQ ### 如何绘制一个中国地图? 地图最终是由地理数据决定,所以需要在互联网上找到一个中国行政地图的 geoJson 或 topoJson 数据,并将此数据应用于当前的示例 DEMO 中。 #### topoJson ```ts import { Chart } from '@antv/g2'; import { feature } from 'topojson-client'; const chart = new Chart({ container: 'container', }); fetch('xxx/china.topo.json').then(async (res) => { const data = await res.json(); const features = feature(data, data.objects.default).features; chart.options({ type: 'geoPath', coordinate: { type: 'mercator' }, data: features, style: { stroke: 'white' }, }); chart.render(); }); ``` #### geoJson ```ts import { Chart } from '@antv/g2'; const chart = new Chart({ container: 'container', }); fetch('xxx/china.json').then(async (res) => { const data = await res.json(); const features = data.features; chart.options({ type: 'geoPath', coordinate: { type: 'mercator' }, data: features, style: { stroke: 'white' }, }); chart.render(); }); ``` #### geoView - Canonical page: https://g2.antv.antgroup.com/zh/manual/extra-topics/geo/geo-view/ - Markdown source: https://g2.antv.antgroup.com/zh/manual/extra-topics/geo/geo-view.md - Description: geoView --- title: "geoView" description: "geoView" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/manual/extra-topics/geo/geo-view/" version: "5.4.8" --- `geoView` 是绘制地图的容器,用于多 geoPath 图层叠加。 ## 开始使用 geoView ```js /** * A recreation of this demo: https://vega.github.io/vega-lite/examples/geo_layer_line_london.html */ import { Chart } from '@antv/g2'; import { feature } from 'topojson-client'; Promise.all([ fetch('https://assets.antv.antgroup.com/g2/londonBoroughs.json').then((res) => res.json(), ), fetch('https://assets.antv.antgroup.com/g2/londonCentroids.json').then( (res) => res.json(), ), fetch('https://assets.antv.antgroup.com/g2/londonTubeLines.json').then( (res) => res.json(), ), ]).then((values) => { const [londonBoroughs, londonCentroids, londonTubeLines] = values; const london = feature( londonBoroughs, londonBoroughs.objects.boroughs, ).features; const line = feature(londonTubeLines, londonTubeLines.objects.line).features; const chart = new Chart({ container: 'container', autoFit: true, }); chart.options({ type: 'geoView', children: [ { type: 'geoPath', data: london, style: { fill: 'lightgray', stroke: 'white', strokeWidth: 2 }, }, { type: 'text', data: londonCentroids, encode: { x: 'cx', y: 'cy', text: (d) => d.name.split(/\W/)[0] }, style: { fontSize: 8, opacity: 0.6 }, }, { type: 'geoPath', data: line, encode: { color: 'id', shape: 'hollow' }, scale: { color: { domain: [ 'Bakerloo', 'Central', 'Circle', 'District', 'DLR', 'Hammersmith & City', 'Jubilee', 'Metropolitan', 'Northern', 'Piccadilly', 'Victoria', 'Waterloo & City', ], range: [ 'rgb(137,78,36)', 'rgb(220,36,30)', 'rgb(255,206,0)', 'rgb(1,114,41)', 'rgb(0,175,173)', 'rgb(215,153,175)', 'rgb(106,114,120)', 'rgb(114,17,84)', 'rgb(0,0,0)', 'rgb(0,24,168)', 'rgb(0,160,226)', 'rgb(106,187,170)', ], }, }, }, ], }); chart.render(); }); ``` ## 选项 目前 `geoView` 容器具备 geoPath 全部的属性,除了 geoPath 作为 mark 独有的 `encode` 方法。 同样,对于 `projection` 映射,当前 G2 内置了 [d3-geo](https://github.com/d3/d3-geo) 的所有映射。 #### forceGraph - Canonical page: https://g2.antv.antgroup.com/zh/manual/extra-topics/graph/force-graph/ - Markdown source: https://g2.antv.antgroup.com/zh/manual/extra-topics/graph/force-graph.md - Description: forceGraph --- title: "forceGraph" description: "forceGraph" language: "zh" canonical: "https://g2.antv.antgroup.com/zh/manual/extra-topics/graph/force-graph/" version: "5.4.8" --- 力导布局图是一种用来呈现复杂关系网络的图表。在力导布局图中,系统中的每个节点都可以看成是一个放电粒子,粒子间存在某种斥力。同时,这些粒子间被它们之间的“边”所牵连,从而产生引力。 ## 开始使用 forceGraph ```js import { Chart } from '@antv/g2'; import { schemeTableau10 } from 'd3-scale-chromatic'; const chart = new Chart({ container: 'container', }); chart.options({ type: 'forceGraph', data: { type: 'fetch', value: 'https://assets.antv.antgroup.com/g2/miserable.json', }, scale: { color: { range: schemeTableau10 } }, }); chart.render(); ``` ## 选项 | 属性 | 描述 | 类型 | 默认值 | | ---------- | -------------------------- | ---------------- | ------ | | layout | 布局配置 | `ForceTransform` | - | | style | 配置图形样式和标签样式 | - | - | | nodeLabels | 自定义节点数据标签的配置 | label[] | [] | | linkLabels | 自定义连接线数据标签的配置 | label[] | [] | ### layout | 属性 | 描述 | 类型 | 默认值 | | ------------ | -------------------- | ------------------------- | ------ | | joint | 离散力布局或引力布局 | boolean | true | | nodeStrength | 节点引力 | `number \| (d => number)` | - | | linkStrength | 连接线间引力 | `number \| (d => number)` | - | ### style 复合图形标记需要通过不同的前缀来区分图形的配置。 - `