Loading...
旭日图是一种层次化的数据可视化图表,通过同心圆的形式展示多层级数据结构。每个层级用一个环表示,环的内外半径表示层级的深度,环的角度大小表示数据的数值大小。旭日图融合了饼图和树状图的优点,采用圆形分割的方式来展示多层嵌套的信息结构。
旭日图特别适合展示具有明确父子关系的层次化数据,能够直观地显示各个层级之间的比例关系和层次结构。通过环形布局,旭日图有效地展现了数据间的层次关系和比例分配,比起树图具备节省空间、整体情况更加直观等优点。
旭日图还支持下钻交互,用户可以点击某个扇形区域来查看该层级的详细信息,这使得它成为商业分析、地理学研究等领域不可或缺的工具之一。
英文名:Sunburst Chart
图表类型 | 旭日图 |
---|---|
适合的数据 | 层次化数据:具有多层级父子关系的嵌套数据结构 |
功能 | 展示层次化数据的比例关系和层级结构 |
数据与图形的映射 | 层级关系映射到同心圆的环 数值大小映射到扇形的角度 不同类别通过颜色进行区分 |
适合的场景 | 组织架构、文件系统、预算分配等具有清晰层次关系的数据展示 |
例子 1: 展示文件系统层次
下图展示了一个文件系统的层次结构,通过旭日图可以清晰地看到各个文件夹和文件的大小分布。
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: 展示预算分配结构
旭日图可以清晰地展示预算在不同项目和子项目之间的分配情况,帮助管理者了解资源配置。
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 层深度的旭日图,可以看到外层已经完全无法识别。
旭日图(不推荐)
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();
矩形树图(推荐)
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 个时,旭日图的颜色区分和角度感知都会变得困难,标签拥挤难以阅读。
旭日图(不推荐)
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();
柱状图(推荐)
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: 数值差异过小的问题
当两层数据中第二层类别的数值差异很小时,旭日图的角度差异不明显,难以进行有效对比。
旭日图(不推荐)
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();
柱状图(推荐)
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 轴范围来突出差异。
为旭日图添加标签可以让数据更加清晰易读:
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();
通过设置颜色通道,可以更好地区分不同的类别:
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();
可以通过样式配置来定制旭日图的外观:
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();
旭日图支持下钻交互,用户可以点击扇形区域进行层级导航:
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();
旭日图可以看作是多层级的饼图,相比普通饼图有以下优势:
旭日图和矩形树图都可以展示层次化数据,但有不同的特点:
对比维度 | 旭日图 | 矩形树图 |
---|---|---|
视觉形式 | 圆形布局 | 矩形布局 |
空间利用 | 圆形边界,空间利用率较低 | 矩形填充,空间利用率高 |
层次展示 | 同心圆,层次关系清晰 | 嵌套矩形,包含关系明确 |
比例感知 | 角度比较,适中 | 面积比较,更准确 |
适用场景 | 强调层次关系和整体结构 | 强调精确的比例对比 |
环图可以看作是两层的旭日图:
旭日图相比环图的优势:
环图的优势: