1. 从饼图的局限说起:旭日图是为哪种数据而生的
早先做一个电商数据后台的时候,业务方给我提了个需求:一张页面里既要看到流量渠道的整体占比,又要点进去看每个渠道下面的来源明细。按老思路,渠道占比画一张饼图,来源明细再画三张饼图,页面被撑得乱七八糟,放在一起又看不出归属关系。后来我把这些数据全塞进 ECharts 的旭日图里,状态才算稳定下来。这个经历让我意识到,很多所谓“图表不够好看”的问题,根源其实是选错了图。
旭日图可以理解成被切开的同心圆,或者说把多张饼图按半径方向叠到了一起。最内圈是最高层级,往外的每一圈都对应更细一层的维度和占比。扇区的角度和弧长代表数值大小,颜色和半径位置代表层级路径,一张图就能同时回答“整体长什么样”和“某一块里面又有什么”两个问题。ECharts 内置了这个系列类型,配置起来不算复杂,但要真正用好,还是有不少藏在表面之下的门道。
这篇文章我不打算把官方文档抄一遍。重点讲讲你在真实项目里最需要的那几件事:什么数据适合它、data 结构怎么设计、哪些配置能直接生效、哪些坑埋得特别深,以及和树图、矩形树图做选型时的判断依据。适合想快速上手、不想从头翻一遍 API 的开发者,也适合被多层级报表方案折磨过的数据可视化从业者。
1.1 哪些业务数据天然适合旭日图
先说几个我见过的高频使用场景。第一个是电商和流量分析:渠道大类、来源、关键词或素材,这种分层特别适合旭日图,因为业务方关心的是每个来源对整体流量的贡献路径。第二个是区域经营分析:省份、城市、门店,第三层能直接看出哪个门店拖了后腿。第三个是商品分类销售占比:一级类目、二级类目、SKU,把同比、环比数据挂在节点上,效果也很直观。
判断一份数据适不适合用旭日图,我总结成三个条件。第一,维度之间是包含关系,而不是平行关系;第二,数值具备可加性,子级加总后能和父级对得上;第三,最小展示粒度大概在 10 到 50 个叶子节点之间,再多就要做合并处理。层数方面,三层以内的展示最舒服,四层以上最外圈会变得非常窄,适合放在交互下钻场景里按需出现,不适合一上来就全部平铺。
1.2 旭日图和其他环形图的本质差别
普通饼图或环图只能编码一个维度和一个数值,想表达层级就得画很多张图。旭日图不一样,它把“层级”也纳入视觉通道,用半径位置来表示。这样,同一张图里可以同时看到父级的占比和子级的细分,不需要多图联动,也不需要用户在脑内把图表拼接起来,信息损耗小了很多。
不过这里有个很容易被忽略的点:旭日图的阅读成本其实不低。环状结构不像树状结构那样从左到右一路顺畅,用户需要先看中心,再一圈一圈往外读。如果你的用户只是扫一眼找结论,那它的优势可能反而变成负担。这也是为什么我常说,旭日图最适合做“带交互的汇报页”,而不是“打印出来的静态报表”。在画图之前,先想清楚用户会怎么读,才不会出现花了大功夫做出来却被否掉的尴尬。
2. 手写一个最小可用配置:先把图跑起来,再谈优化
配置旭日图的门槛其实很低,但很多人栽在最基础的数据结构上。旭日图的 data 不是扁平的数组,而是嵌套的树形结构,这个看起来简单的差异,决定了整个 series 能不能正常工作。
2.1 data 嵌套结构与 value 的归一化逻辑
旭日图中一个节点的基本形态是这样的:
{ name: '一级类目A', children: [ { name: '二级类目A1', value: 30 }, { name: '二级类目A2', value: 20 } ] }叶子节点必须写 value,因为它是计算角度的最小单位。中间节点可以写 value,也可以不写。不写时,ECharts 会自动把这个节点的所有子节点 value 加起来作为角度依据,这是最省事也最不容易出错的做法。
如果中间节点自己写了 value,那 ECharts 会优先按这个值来分配父级角度,子节点再在父级扇区内部按比例排列。这时候潜在问题就出现了:如果父级 value 和子级之和差距很大,外圈扇区的总和与内圈扇区就会产生“错位感”,视觉上看起来像数据对不上。后面的坑部分我会展开细讲,这里先记住一个原则:数据能聚合就聚合,父子口径要保持一致。
2.2 一个能直接运行的完整配置
把上面的数据结构补全,再配上基础配置,就是一份可以立刻跑起来的最小示例:
option = { tooltip: {}, series: [ { type: 'sunburst', radius: ['0%', '80%'], data: [ { name: '流量来源', children: [ { name: '自然搜索', children: [ { name: '品牌词', value: 25 }, { name: '通用词', value: 40 } ] }, { name: '付费广告', children: [ { name: '信息流', value: 30 }, { name: '搜索广告', value: 20 } ] }, { name: '直接访问', value: 15 } ] } ], label: { rotate: 'radial' } } ] };这里最值得说的是 radius 配置。它和饼图一样,用数组表示内半径和外半径,单位是相对于容器短边一半的百分比。['0%', '80%']表示最内层从圆心开始,一直延伸到半径的八成。如果你想让中心留白,比如在中间放标题或者累计数值,可以把内半径调成['20%', '80%'],视觉效果会干净很多。
2.3 levels 与 label:统一控制每一层的外观
旭日图的层级样式可以通过 levels 数组来控制。levels 的下标从 0 开始,0 对应最内层,1 对应第二层,依此类推。下面这个配置可以让内层文字大一些、外层文字小一些,并把所有层级的颜色过渡方式统一起来:
series: [ { type: 'sunburst', levels: [ { itemStyle: { borderRadius: 4 }, label: { fontSize: 18 } }, { label: { fontSize: 14 } }, { label: { fontSize: 12 } } ] } ]label 配置里,rotate 是非常实用的一个属性。设为'radial'后,文字会沿半径方向排列,从圆心向外发散,适合层数较多、文字较长的场景;设为'tangential'则文字沿弧线方向排列,适合外围空间比较充裕的图表。还有 minAngle,用来过滤过窄扇区的文字,角度小于阈值的扇区不显示 label,避免一堆数字叠在一起。
需要提醒的是,series 层面的 label 是一个“全局默认值”,levels 里的 label 是“层级覆盖值”。也就是说,你完全可以在 series.label 里定一个全局样式,再在具体层级的 levels 里按需覆盖,二者是继承加覆盖的关系,不是互斥关系。
3. 和业务真实接轨时最容易踩的坑
跑通最小示例不算难,真正让人头疼的是数据接进来之后的那些细节。这一章我把自己在项目里踩过的坑按类别整理出来,每一个都是真实发生过的。
3.1 父子层级的数值口径对不上,图会“打架”
最常见的一个坑,是业务库里父级数值和子级数值不是同一套口径。比如运营给的父级是“销售额”,子级却只统计了“已支付订单里包含的类目销售额”,中间还有退款、未归类订单,导致外圈所有子节点加起来只有父级角度的八成。旭日图不会报错,但展示出来就是外圈缺一块,或者比例对不上,看起来非常奇怪。
解决办法取决于你要表达什么。如果业务目标是展示“整体到局部的完整分解”,建议父节点不写 value,交给 ECharts 自动聚合;如果子级确实存在数据缺口,就在子级里补一个“其他”节点把差额填上。反过来,如果业务口径就是父级和子级各自独立统计,那要做好心理准备:图一定会有视觉上的“不对劲”,尽量用 tooltip 把两套数值分开讲清楚,别让用户自己去猜。
3.2 默认点击行为和用户预期不一样
ECharts 旭日图的 nodeClick 默认值是'rootNode',意思是点击任意节点,该节点会成为新的根节点并放大显示,相当于一个下钻操作。对部分场景来说这是优点,但对只想看 tooltip 的用户来说,误触一下整张图就变了,很容易造成困惑。
我在实际项目里一般会分两种情况处理。如果产品设计里本来就需要点击下钻,那默认行为可以直接用,只要在页面上配一个“返回上级”按钮即可;如果产品希望保留“点击只看详情、另外用按钮下钻”的模式,就把 nodeClick 设为'none',然后自己监听点击事件:
chart.on('click', function (params) { // params.data 是当前节点的原始数据 // params.treePathInfo 是路径信息数组 console.log(params.name, params.value, params.treePathInfo); });重置回顶层视图时,重新 setOption 一份新的 data 即可,或者维护一个“根节点栈”,配合返回按钮做逐级回退。注意这里的逐级回退需要自己记录,ECharts 不会帮你保存历史层级。
3.3 小扇区的标签、颜色与 tooltip 表达
旭日图的小扇区问题是逃不掉的。当某个节点占比只有 3% 时,对应的弧长可能只有几个像素,标签一多就会叠在一起。我在配置里通常会用label.minAngle设置在 12 到 15 之间,让过窄的扇区干脆不显示文字,把细节完全交给 tooltip。
tooltip 是旭日图的信息兜底。默认 tooltip 只显示当前节点的 name 和 value,如果想展示完整路径,可以用 formatter 基于 treePathInfo 自己拼:
tooltip: { formatter: function (params) { let path = params.treePathInfo.map((item) => item.name).join(' / '); return path + '<br/>数值:' + params.value; } }颜色分配上,我的习惯是同一个根节点下的所有子节点使用同一色系,通过明度或饱和度区分层级,而不是每层都用彩虹色。这样用户一眼就能从颜色上判断“谁属于谁”,而不是从半径里来回找。
4. 数据量变大之后:性能和可读性的取舍
旭日图好看是好看,但数据量一旦涨上去,可读性和性能都会明显下降。这一章聊聊我在处理大数据量时通常会做的几件事。
4.1 什么时候需要做预处理
我的经验阈值是:总叶子节点超过 50 个,层数超过 4 层,旭日图就会开始变得难读。最外圈扇区密集到一定程度后,标签几乎无法摆放,颜色区分度也会下降。此时与其调样式硬撑,不如在数据源头就做聚合。
常用的预处理手段有两个。一个是合并小扇区:把占比小于某个阈值(比如 2%)的叶子节点全部合并成一个“其他”节点;另一个是限制下钻深度:默认只渲染两层,第三层及以上在点击交互时再加载。这样既保住了图表的表达能力,又不会让首屏变成一团色块。
排序也是预处理的一部分。ECharts 的 sort 默认是降序,把占比大的块放在起始位置。业务上如果希望保持固定的数据顺序,可以把 sort 设为'none',否则大扇区会自动排到前面。
4.2 渲染性能和交互体验的调整
数据量上来之后,动画会成为卡顿的重要来源。尤其是每帧都在计算扇形路径的旭日图,动画时长对 CPU 的消耗比普通柱状图明显得多。我的做法是:当节点总数超过一定量级时,直接把animation: false关掉,或者把 animationDuration 调小到 100 毫秒以内,视觉上几乎无感,但交互流畅度提升明显。
另外,如果数据是异步加载,下钻时一定要处理 loading 状态。ECharts 提供了showLoading()和hideLoading()方法,加载完成之前先挡住交互,避免用户反复点击触发多次请求。这个看起来是常识,但在实际项目里经常被忽略,结果就是图表闪动、数据错乱、用户投诉。
4.3 用颜色和中心留白做视觉降噪
数据量大的时候,视觉降噪比功能优化更重要。我习惯把内层扇区之间留出细小间隙,ECharts 里可以通过 itemStyle 的borderColor和borderWidth实现,让每个扇区的边界清晰一些。中心位置也建议空出来,把“总销售额”或者“根节点名称”用自定义 HTML 放在圆心,这样既填补了视觉空白,又给用户一个明确的参照点。
还有一个进阶玩法:用颜色映射第二个指标。比如扇区角度仍然表示销售额,但填充颜色的深浅表示同比增长率。这种方法需要自己写一个从数值到颜色的映射函数,本质上相当于在角度之外多了一个视觉通道,做数据汇报时非常有冲击力,但也需要严格说明图例,否则容易误读。
5. 旭日图和树图、矩形树图怎么选:一次说清
很多人在做层级数据时会在旭日图、树图、矩形树图之间犹豫。这三者看着都能表达层级,但表达重点完全不同,选错图表的代价比配置错误更大。
5.1 三类图表的表达侧重点
| 图表类型 | 核心优势 | 典型场景 | 主要局限 |
|---|---|---|---|
| 旭日图 | 占比直觉最强,能同时看整体和局部 | 渠道构成、类目占比、区域贡献度 | 层数过多时外圈可读性差 |
| 树图 | 结构关系最清晰,父子路径一目了然 | 组织架构、分类逻辑、流程分支 | 空间利用率低,数字占比表达弱 |
| 矩形树图 | 空间利用率最高,能容纳大量叶子节点 | 磁盘占用、大数据量下的粗细粒度构成 | 不擅长体现占比的“环形直觉”,排序感弱 |
从这张表可以看出来,旭日图的核心竞争力是“占比”和“层级”两个维度同时表达。如果你最看重的是某个子级在整体中的占比,旭日图一定优先于树图。树图更适合回答“从根到叶的关系到底是什么”,而不是“这个叶子占比多大”。
矩形树图则是一个被低估的选项。它的信息密度极高,一个页面能塞下上百个节点,而且矩形排列天然适合比较大小。但它的问题是缺乏“中心感”,层级关系不像环形那样直观。我的经验是:如果叶子节点超过 100 个,直接放弃旭日图,改用矩形树图;如果叶子节点在 20 到 50 个之间,旭日图的效果是最出色的。
5.2 我的选型判断顺序
判断顺序其实很固定:先看数据是层级关系还是层级占比。纯关系用树图,重点看占比用旭日图和矩形树图之间选。第二步看数据规模,小规模用旭日图,大规模用矩形树图。第三步看交互设计,如果产品要求点击下钻、逐级展开的沉浸式体验,旭日图的手感最好,因为它的下钻是围绕当前根节点重新布局的;矩形树图也可以下钻,但视觉连续性不如旭日图。
这里顺带提一个常见的误用:把南丁格尔玫瑰图拿来当多层级图。玫瑰图的半径用来表示数值大小,确实有层级感,但它本质上是单维度比较,强行把多层数据塞进半径维度会丢失“包含关系”的表达,和旭日图不是一个用途。
6. 项目里沉淀的几条旭日图使用经验
最后分享一些我自己反复验证过、可以“抄作业”的配置思路和经验法则。它们不一定是最好的,但在多个项目里都稳定可用。
6.1 一套比较稳妥的默认配置
我现在做旭日图时会先套这组配置,再按业务微调:
series: [ { type: 'sunburst', radius: ['25%', '80%'], sort: 'desc', nodeClick: 'rootNode', label: { rotate: 'radial', minAngle: 15, fontSize: 13 }, itemStyle: { borderColor: '#fff', borderWidth: 2, borderRadius: 4 }, emphasis: { focus: 'self' } } ]圆心空出来的 25% 空间用来放 HTML 标题或汇总值。label.minAngle 设置为 15,是为了保证过窄扇区不显示文字。borderRadius 让每个扇区带一点圆角,整体视觉更柔和,这是最近比较流行的处理方式。emphasis.focus 设为'self',鼠标悬停时其他扇区不会过度变灰,方便用户继续观察整体结构。如果你的业务希望突出当前块、弱化其他块,可以把 focus 改为'ancestor'或'descendant',按场景取舍。
6.2 封装组件时的几个可维护性建议
旭日图一旦在多处使用,最好封装成独立组件。组件入参建议只保留三类:树形数据、颜色生成函数、点击回调。颜色生成函数单独提出来,是因为不同业务的品牌色、层级配色差异很大,写死在组件里反而难复用。
图表容器尺寸变化时,记得调用chart.resize(),否则会在页面刷新或侧栏折叠后出现错位。异步加载下钻数据时,用chart.showLoading()遮住整个图表,加载完成后再隐藏。还有一个容易被忽略的细节:同一份数据如果更新频率很高,建议在 setOption 时保留一个 series 索引定位,避免 ECharts 把新旧 series 当作两个不同系列处理,导致状态丢失。
6.3 几句实在的体会
用旭日图做了几个项目之后,我的总体感受是:它是一个“演示友好型”图表,做数据汇报和管理驾驶舱时效果很出彩,但不适合作为精确读数的工具。真正把它做好的关键,不是背配置项,而是想清楚三个问题:数据口径能不能闭环、层数压到多少层、颜色能不能一眼分组。把这三点想明白了,旭日图的画面不会难看到哪里去。
最后再分享一个肉眼可见的优化小技巧:给中心区域留白,把根节点名称或核心指标放在中间。很多开发者在做旭日图时会下意识把半径从 0 开始,结果从圆心到外圈全是色块,画面显得拥挤。适当留白之后,图表的“视觉入口”会更清晰,用户在汇报页上停留的时间也会更长。这个改动成本几乎为零,效果却非常直接。