算上这篇,Echarts可视化教程已经写到第四篇了。前几篇咱们从基础的柱状图起步,聊过坐标轴的刻度处理,也碰过折线图里tooltip的换行问题,这次轮到饼图。饼图在数据可视化里的地位很特殊——它不像柱状图那样适合精确比较,也不像折线图那样适合看趋势,它最擅长的是表达占比关系,也就是“谁占了多大份额”。电商后台的类目销售占比、用户画像里的流量来源分布、大屏上的设备状态统计,这些都是饼图的高频使用场景。
这篇博客我会围绕Echarts饼图,把基础的series配置、标签布局、数据更新这些核心点全部过一遍,然后结合完整示例演示如何绘制、如何调整、如何避坑。文中用到的数据和代码,都以一个模拟的“二季度各品类销售额占比”为背景来说明,方便直接复用。
1. 饼图绘制的整体设计与思路拆解
1.1 饼图在数据可视化中的适用场景
饼图这类图形,本质是把一个整体切成几块扇形,每一块的比例对应某个分类的数值占比。它的优点一眼就能看出来——直观,小学生也能看懂,适合快速传递“谁最大、谁最小、差距明显不明显”这类信息。但它的劣势同样突出:当分类太多(超过8个)、或者各项比例非常接近的时候,人眼很难通过扇形角度去判断精确大小,被切得密密麻麻的“千层饼”会让阅读者崩溃。
所以,我一般在下面几种情况下才会用饼图:
- 分类数量在3到7个之间,最多不超过8个。
- 数据总和可以作为“整体”去理解,也就是100%有意义,比如预算分配、流量来源。
- 重点是看比例结构,而不是精确数值对比。
- 目标读者需要快速建立直观印象,不花时间看详细数字。
如果数据量太大或者需要看趋势、看精确排序,我建议老老实实改用柱状图或者条形图。这个选型思路在教程第一篇里就强调过,图表选型和写代码一样重要,选错图等于白做。
1.2 从柱状图理解到饼图的思路迁移
柱状图的坐标轴思维是“数值轴+类目轴”,填入data之后,x轴每个类目对应一根柱子。饼图则完全不同——它没有传统坐标轴,核心是series中的type: 'pie'。
我们只需要把数据组织成一个数组,每个数组元素包含name和value两个字段,然后传给series。Echarts会自动计算每个值在总和中的占比,然后绘制出对应的扇形。
option = { series: [{ type: 'pie', data: [ { name: '数码家电', value: 453 }, { name: '服饰鞋包', value: 312 }, { name: '美妆个护', value: 289 }, { name: '食品生鲜', value: 176 }, { name: '家居百货', value: 98 } ] }] };就这么简单,一段最基础的饼图代码就完成了。但是实际项目里很少只满足于“能显示”,我们通常要调整半径、设置颜色、加上百分比标签、制作环形图、处理图例遮挡、让tooltip格式化得更友好。这些都属于配置项层面的操作,接下来一步步展开。
2. 核心配置项解析与绘制原理
2.1 基础配置:series-pie的必备参数
在Echarts中,饼图的所有核心行为都由series中的pie系列控制。常用的配置项整理如下:
| 配置项 | 类型 | 默认值 | 作用说明 |
|---|---|---|---|
type | string | 必填 | 固定为'pie' |
name | string | 无 | 系列名称,用于tooltip和图例 |
radius | number/string/array | ['0%', '75%'] | 饼图半径,可写百分比或像素值 |
center | array | ['50%', '50%'] | 圆心位置,第一个是横坐标,第二个是纵坐标 |
data | array | 无 | 数据项数组,核心内容 |
roseType | string/boolean | false | 是否显示为南丁格尔玫瑰图 |
label | object | 无 | 标签文本配置,控制名称和数值的显示 |
labelLine | object | 无 | 标签引导线配置 |
itemStyle | object | 无 | 每个扇区的样式,颜色、边框等 |
emphasis | object | 无 | 高亮状态配置,鼠标移到扇区时的样式 |
先说radius。它有两种用法:传单个数值或百分比,表示普通饼图半径;传数组[内半径, 外半径],就会生成环形图(也叫环形饼图、圆环图)。很多人做看板时喜欢用环形图,因为中间那块空出来的区域可以放总数、核心指标或者一张小图,视觉上更有层次。
radius: ['40%', '70%']这行配置生成的就是内半径40%、外半径70%的环形图。为什么用百分比而不是像素?因为百分比是相对于容器宽度和高度中较小值计算的,可以自动适配不同屏幕尺寸,尤其是做大屏展示时,这一条非常重要。
再说center。默认是['50%', '50%'],也就是容器正中。如果你在做一个看板,饼图旁边还要放几个KPI数字,可以考虑把饼图往左偏一点,右侧留出空间放统计文本。
center: ['35%', '50%']2.2 颜色控制:调色盘与itemStyle
Echarts里控制颜色的方式主要有三种,优先级从低到高分别是:全局color调色盘、系列级color、单个数据项里的itemStyle.color。
全局调色盘定义了一组默认颜色,当饼图扇区没有单独指定颜色时,Echarts会依次从调色盘中取色:
color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4']如果想针对某个数据单独指定颜色,直接在这个数据项里写:
data: [ { name: '数码家电', value: 453, itemStyle: { color: '#2f7ed8' } }, { name: '服饰鞋包', value: 312, itemStyle: { color: '#8dbbc4' } } ]项目里如果视觉风格统一,我一般用全局调色盘搞定。如果某个扇区需要突出(比如监控大屏上某个指标异常),那就单独给这一项指定一个高亮色,配合emphasis还可以做得更细致。
2.3 扇形样式:边框、透明度与高亮
扇区本身的一些细节可以通过itemStyle来控制。常用的几个属性:
borderRadius:扇区圆角,适合做那种圆润风格的卡片式大屏。borderColor:扇区边框颜色,通常设置成和背景同色,这样扇区之间会有间隙感。borderWidth:边框宽度,间隙感就是通过它实现的。opacity:整体透明度。
emphasis控制的是鼠标悬停或者选中时的状态。比如悬停时让扇区高亮,可以设置scale配合label的样式:
emphasis: { scale: true, scaleSize: 8, itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.4)' } }scale是Echarts 5.x版本里比较实用的一个新特性,悬停时扇区会轻微放大,提示感非常自然。不过要注意,scale在roseType开启时配合使用的效果好,普通饼图也很不错。
3. 标签、图例与tooltip的联动配置
3.1 label标签布局与引导线
饼图不像柱状图自带坐标轴注释,它的数据信息主要靠label和labelLine呈现。默认情况下,label显示在扇区外侧,由一根曲线引导过去。
label: { show: true, formatter: '{b}: {d}%', fontSize: 12 }formatter支持模板字符串,其中{b}是名称、{c}是数值、{d}是百分比。'{b}: {d}%'的含义就是“分类名: 百分比”,例如“数码家电: 33.4%”。这是最常见的格式。
如果扇区比较小,标签文字会被挤在一起。我的习惯是把label位置设置成outside并在labelLine上做调整,同时配合minShowLabelAngle使用。这个配置也很关键:
minShowLabelAngle: 3意思是小于3度的扇区不显示标签,避免一堆姓名和百分比糊成一团。
labelLine控制的是引导线样式。Echarts提供的默认引导线由三段组成:水平线、斜线、末尾的圆点。很多人会遇到曲线和圆点偏移的问题,大多是因为label的宽度或者文字长度溢出,导致引导线的端点位置计算出现了异常。最直接的解决方法是留出足够的grid边距,或者在富文本模式下手动指定label.width。我们下一部分专门讲这个问题。
3.2 legend图例的显示与交互
饼图的legend默认显示在顶部,可以点击图例项来隐藏对应的扇形。这个交互本身是Echarts自带行为,不需要额外写事件。
常用配置是设置orient和left/top来控制图例的摆放位置。做环形图时,我更喜欢把图例放到右侧垂直排列,这样视觉重心更平衡:
legend: { orient: 'vertical', left: 'right', top: 'middle' }formatter也可以用来给图例增加一些说明,比如在名称后面补充数值信息。需要注意:legend的data不一定要显式声明,Echarts会自动从series的name字段里收集。如果项目中需要把某些类目从图例中移除,那再手动指定data数组。
3.3 tooltip提示框与自动换行
“Echarts tooltip自动换行”是搜索热词里排名很靠前的一个,可见大家在这上面踩坑不少。饼图的tooltip默认是鼠标放在扇区上时显示一个浮层,内容是系列名、数据名和数值。
先看一个基础配置:
tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} ({d}%)' }这里{a}是系列名,{b}是数据项名称,{c}是数值,{d}是百分比。<br/>是H5的换行标签,Echarts的tooltip默认支持HTML渲染,所以直接用它就可以实现多行显示。
最常见的问题场景是:名称和数值太长了,不换行全都挤在一行,超出屏幕边界就会被截断。两种解决办法:
- 调整tooltip的
confine: true,让浮层显示在图表容器内部,不被外部遮挡。 - 在
formatter里手动拼接换行符<br/>,把长文本拆成多行。
比如遇到某个商品名称特别长,我的习惯是截断并加省略号:
formatter: function(params) { var name = params.name.length > 10 ? params.name.slice(0, 10) + '...' : params.name; return name + '<br/>' + '销售额: ' + params.value + ' 万元<br/>' + '占比: ' + params.percent.toFixed(1) + '%'; }注意formatter既支持字符串模板,也支持回调函数。回调函数里能拿到完整的params对象,自由度更高。上面这段就是先把名称截断到10个字符,再拼接换行输出,多行信息一目了然。
4. 完整实操:从数据到可交互的销售占比饼图
4.1 项目准备与数据准备
假设我们现在拿到一个模拟数据,是某电商平台二季度各品类销售额(单位:万元):
| 品类 | 销售额 | 占比 |
|---|---|---|
| 数码家电 | 453 | 32.7% |
| 服饰鞋包 | 312 | 22.5% |
| 美妆个护 | 289 | 20.8% |
| 食品生鲜 | 176 | 12.7% |
| 家居百货 | 98 | 7.1% |
| 其他 | 58 | 4.2% |
| 合计 | 1386 | 100% |
我直接用原生HTML + Echarts CDN做演示,因为教程的重点在配置项,不是工程脚手架。项目前期如果用npm管理依赖,也可以直接:
npm install echarts然后按需引入:
import * as echarts from 'echarts';演示就用CDN方式,能在浏览器里直接跑起来:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Echarts饼图实战</title> <style> #chart { width: 700px; height: 500px; margin: 40px auto; } </style> </head> <body> <div id="chart"></div> <script src="https://fastly.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script> <script src="./pie.js"></script> </body> </html>4.2 基础饼图的一步步实现
在pie.js里先初始化图表:
var chartDom = document.getElementById('chart'); var myChart = echarts.init(chartDom); var option = { color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#9a60b4'], title: { text: '二季度各品类销售额占比', subtext: '数据来源:模拟数据', left: 'center' }, tooltip: { trigger: 'item', formatter: '{b}: {c} 万元 ({d}%)' }, legend: { orient: 'horizontal', bottom: 10 }, series: [{ name: '销售额占比', type: 'pie', radius: ['0%', '70%'], center: ['50%', '50%'], data: [ { name: '数码家电', value: 453 }, { name: '服饰鞋包', value: 312 }, { name: '美妆个护', value: 289 }, { name: '食品生鲜', value: 176 }, { name: '家居百货', value: 98 }, { name: '其他', value: 58 } ], label: { formatter: '{b}\n{d}%' }, labelLine: { length: 20, length2: 10 } }] }; myChart.setOption(option);这里每行配置都值得讲一讲。radius: ['0%', '70%']写的是数组形式,但内半径是0%,所以看起来跟普通饼图没区别。label的formatter用了{b}\n{d}%,\n在label的formatter中是有效的换行,这跟tooltip里用<br/>不一样。很多新人在两个场景里混用,结果标签不换行,这里要注意区分。
labelLine的length控制引导线中靠近标签的横线段的长度,length2控制斜线段的长度。如果标签文字离扇区太远或者太近,调整这两个值就行。
4.3 转换为环形图与玫瑰图
环形图的实现非常简单,就是把radius的内半径置为一个大于0的值:
radius: ['45%', '70%']内半径45%外半径70%,这样就生成了一个标准的环形图。环形图中间那部分空出来的位置,可以用来写汇总指标。这里用title中的text和subtext来模拟就好:
title: { text: '总销售额\n1386万元', subtext: '', left: '34%', top: '42%', textAlign: 'center' }把这个title跟环形图组合,中间正好填上总数,这是很多数据大屏的标准布局。
南丁格尔玫瑰图则是另一种视觉上更“炸”的饼图变体。开启方式很简单:
roseType: 'radius'也可以用roseType: 'area'。两者的区别是:'radius'模式半径和数值成正比,扇区面积会由于半径差异更加夸张;'area'模式半径相同,但通过改变角度和面积来区分数值。视觉上,玫瑰图能在分类较多的时候提高区分度,但如果数值差距不大,反而容易误导读者。我的经验是,演示汇报时可以用玫瑰图提高观感,正式的数据报告尽量别用。
4.4 数据动态更新与动画处理
实际业务中的图表,数据不可能一成不变。有时要定时刷新,有时要再拉取新接口。Echarts在数据更新上做得特别方便——重新setOption即可,不需要销毁重建实例。
setInterval(function() { var newData = [ { name: '数码家电', value: Math.round(Math.random() * 500) }, { name: '服饰鞋包', value: Math.round(Math.random() * 350) }, { name: '美妆个护', value: Math.round(Math.random() * 300) }, { name: '食品生鲜', value: Math.round(Math.random() * 200) }, { name: '家居百货', value: Math.round(Math.random() * 120) }, { name: '其他', value: Math.round(Math.random() * 80) } ]; myChart.setOption({ series: [{ data: newData }] }); }, 3000);注意一个关键细节:如果series数组是多个图表配置,比如一个页面里既有饼图又有柱状图,那么在更新数据时最好用setOption的第二个参数控制合并逻辑。默认是“增量替换”,同名的组件和系列会更新,不存在的会新增。这里只改data,其他配置比如radius、label都不动,什么都不会乱。
如果你在开发vue/react项目,还需要注意组件销毁时要把实例一起dispose掉:
myChart.dispose();否则容易造成内存泄露。这个在“数据可视化大屏”场景里尤其重要,页面切来切去,实例越积越多,最后卡到爆。
5. 常见问题与排查技巧实录
5.1 高频问题速查表
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| 饼图只显示一个扇区,其他黑乎乎一片 | 容器宽度或高度为0,或者多个series写错了 | 检查<div>尺寸;确认series是数组 |
| 标签文字重叠、引导线乱飞 | 扇区太小或标签太多 | 设置minShowLabelAngle,或改用label.layout: 'none' |
| 图例太多,底部都放不下 | 分类数量太多 | 把legend改为type: 'scroll'滚动模式 |
| tooltip被容器边缘截断 | 浮层超出了外层容器 | 添加confine: true |
| labelLine末尾的小圆点偏移 | 这是Echarts 5.x版本常见bug | 升级到5.5.x,或者关闭emphasis中label的特殊处理 |
| 饼图在整个页面里不居中 | center设置成了像素值,没跟随容器自适应 | 改成百分比,比如['50%', '50%'] |
| 数字是string类型,计算占比全错 | 数据源里value字段是字符串 | 数据清洗,转成Number |
5.2 那些年我踩过的饼图阴影坑
第一个坑是labelLine小圆点偏移。它在热词里反复出现,说明遇到的人不止我一个。这个问题的表现是:引导线末端的圆点落在了一个奇怪的位置,和文字对不齐。我排查过的原因有两种——
一是Echarts版本太老,5.0到5.2之间的版本确实存在一些label布局计算的小问题。最简单的解法是升级到5.5.x,很多bug在5.3之后的版本修复了。
二是emphasis状态下的labelLayout或label配置干扰了正常渲染。如果你没有特殊需求,建议这样写:
emphasis: { scale: true, label: { show: true, formatter: '{b}: {d}%' } }而不是在highlight的时候去动态修改标签的位置。动态修改位置一旦取消高亮,位置没有恢复原状,小圆点就会“离家出走”。
第二个坑是数据中value为0的扇区。Echarts对value为0的扇区不会绘制扇形,但会保留图例和标签,结果就是图例里有一项,图上却找不到对应区域。你需要在数据处理时过滤或者置为极小值(比如0.01),或者不传给Echarts。
第三个坑出现在更新数据时动画被无限触发。如果你在setOption时不带notMerge参数,默认会以merge方式和新option做合并。这个行为大部分时候是符合预期的,但当你更新series的data时,旧的数据项比新的多,Echarts会保留多余数据并产生“幽灵扇区”。解决方案很简单:
myChart.setOption({ series: [{ data: newData }] }, true);第二个参数传true强制不合并,旧的series直接替换成新的,不会留下多余项。
5.3 如何做自适应和大屏适配
数据大屏是Echarts的高频场景,而大屏的尺寸往往是不固定的。要让饼图跟着容器走,最简单有效的方案是监听resize事件:
window.addEventListener('resize', function() { myChart.resize(); });如果页面里有多个图表实例,建议做一个统一的管理方法,容器变化时全部调用resize()。
如果你用的是flex布局,图表容器初始width为0,等页面渲染完成后再初始化图表也可能导致扇形比例错乱。一个实用的做法是使用nextTick或者在容器宽高计算完毕后再init,同时给容器设置一个最小宽高。
6. 从基础饼图到数据大屏的进阶方向
饼图画到这一步,你已经能处理最常见的业务需求了。接下来有几个非常自然的延伸方向,我在实际做项目时经常用到。
第一个是图表联动。比如一个销售看板,左边放饼图展示品类占比,右边放柱状图展示每个品类的子类目销售额。点击饼图的某个扇区时,柱状图自动刷新成对应品类的数据。Echarts通过myChart.on('click', function(params) {...})暴露了点击事件,参数里有params.name,这个值就是扇区对应的分类名。
myChart.on('click', function(params) { if (params.componentType === 'series') { // 通过 params.name 刷新其他图表 updateBarChart(params.name); } });第二个是用富文本标签做更高的自定义程度。Echarts的label支持rich字段,你可以在里面定义各种样式,比如百分比想放大、名称想加粗、颜色想区分,都能做到。之前搜热词里有“echarts饼图labelline末尾小圆点偏移”,其实很多时候是因为复杂富文本标签导致引导线布局算错,这时候可以先简化rich配置,排查出具体原因再逐项加回来。
第三个是把饼图和地图结合。热词里也出现了很多“echarts中国地图”相关的搜索。地图加饼图是比较经典的地图可视化方式:地图上每个省份的标记点用一个小饼图展示该省份的各类别占比。做法上,通常是散点图用coordinateSystem关联地图坐标系,然后通过symbol或custom系列绘制小型的饼图。这个以后如果有机会,我可以单独开一篇来讲。
第四个是饼图在vue/react项目里的使用。vue-echarts和echarts-for-react这两个封装库都很好用。以vue为例,核心是注意响应式数据的更新和组件销毁时的实例清理。热词里有一条“pxtorem 对echarts没起到效果 vue3”,这个多半是单位换算的坑。Echarts的内部尺寸读取自容器元素,如果你的页面开启了rem适配,但容器的font-size或者宽度没有正确换算,图表就会异常。建议在图表初始化前先确保容器尺寸已经按rem换算完成。
最后再分享一点个人经验
饼图这套配置项我反复用过几十次,最深的体会是:Echarts的配置项其实是分层递进的。先从series和data跑通一个能看的图,再去优化label、tooltip、legend,最后才考虑动画和交互。很多人一上来就把官方示例里几十行的复杂配置复制过去,改半天改不出效果,其实就是因为没搞清哪些配置控制什么、优先级怎么排。
我个人的流程一直是:“先输出,再美化,后交互”。输出指的是数据对、图能画出来;美化指的是颜色、标签、间距、引导线这些视觉细节;交互指的是点击事件、联动、钻取。按这个顺序走,饼图基本不会出大问题。希望这篇教程能帮你少走一些弯路。
如果后续遇到某个具体报错,或者某段配置怎么调都不生效,欢迎在评论里带上你的配置片段,我看到都会回复。