1. 先说清楚饼图不是用来炫技的是用来回答问题的ECharts 系列教程走到第四篇前面讲过的柱状图和折线图更多是在展示“趋势”和“对比”它们的坐标轴体系本身就带着一种严谨的秩序感。但饼状图完全是另一种逻辑——它没有 X 轴和 Y 轴的概念只有一个圆心和一圈扇区。那这玩意儿到底解决什么问题一句话饼图最大的价值是回答“占比是多少”这个问题。我做了几年数据可视化项目最深的感触就是不是所有数据都适合用饼图。比如你要看一个月内销售额的波动趋势饼图就完全帮不上忙但如果你要告诉老板“华东区贡献了 42% 的营收”饼图就是最直观的选择没有之一。ECharts 作为目前社区最活跃的开源可视化库之一饼图的实现路径非常简单你只需要给它一组{ name, value }格式的数据它就能自动计算出角度、排布扇区、生成图例。但我们今天不只是把这个过程跑通而是要把它讲透——从最基础的配置结构到数据格式背后的含义再到实际项目里那些官方文档不会明说的坑。这篇教程面向的是已经了解 HTML JavaScript 基础的小伙伴如果你已经在用 ECharts 画过柱状图那完全可以直接上手要是纯零基础我建议先把我系列教程前面的文章翻一下至少把 ECharts 的引入方式和初始化流程弄明白不然后面操作起来会比较吃力。2. 学习前必须搞懂的三件事2.1 ECharts 的引入方式和版本选择不管画什么图第一步永远是引入 ECharts 库本身。目前官方推荐的方式是使用 npm 按需引入但在日常练习和中小型项目中直接用 CDN 引入echarts.min.js是最省事的。2024 年之后ECharts 的主版本已经稳定在 5.x核心 API 跟 4.x 没有颠覆性差异。如果你手头的项目用的是 4.x那今天讲的这些配置项也全部适用。唯一要留神的是 5.0 之后默认主题和样式细节有微调如果你照着网上的旧代码复制偶尔会遇到颜色对不上的情况这不是你写错了是版本差异。script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script引入之后在 script 标签里访问全局echarts对象调用echarts.init()传入一个 DOM 容器就算完成初始化了。这个容器必须要有宽度和高度而且不能是display: none状态不然图表会显示不出来——这个细节后面会专门说。2.2 搞懂 ECharts 的核心架构option 是灵魂ECharts 最大的特点也是它比很多可视化库用起来舒服的原因就是配置项驱动。你不需要像用 Canvas 原生 API 那样手动去画弧线、计算角度只需要声明一个 JavaScript 对象option把你想表达的东西写进去剩下的渲染工作 ECharts 帮你完成。这个option对象里跟饼图相关的核心字段有这么几个series系列配置饼图的核心就在这。一个图表里可以放多个系列但在饼图场景里一般情况下一个系列就够了。title标题可选配置但我强烈建议在正式交付的图表里都加上不然看图的人很容易迷失。tooltip提示框鼠标悬停时展示数据是交互的核心。legend图例告诉你每个扇区颜色代表什么类别。color调色板控制整个图表配色。理解了这几个字段的分工再去看官方文档的示例你就不会觉得那一大段配置无从下手了。2.3 饼图的数据格式到底长什么样柱状图和折线图的数据是数组比如[120, 200, 150, 80]靠xAxis的data来对应类目。但饼图不一样饼图的series.data必须是对象数组每个对象至少包含name和value两个字段series: [ { type: pie, data: [ { name: 直接访问, value: 335 }, { name: 邮件营销, value: 310 }, { name: 联盟广告, value: 234 }, { name: 视频广告, value: 135 }, { name: 搜索引擎, value: 1548 } ] } ]这里的name是扇区的名称会显示在图例上value是数值ECharts 会自动把所有value求和然后算出每个扇区的角度占比大角度就大。你不需要自己换算成百分比这是饼图最简单的部分也是最容易被人忽略的部分。3. 快速上手五分钟画出一个基础饼图3.1 从零开始的完整代码这部分我们直接跑一个最完整的实例。你新建一个 HTML 文件把下面的代码放进去浏览器打开就是一张饼图。!DOCTYPE html html langzh-CN head meta charsetUTF-8 / titleECharts 饼图入门实例/title script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script style #main { width: 700px; height: 500px; margin: 40px auto; } /style /head body div idmain/div script // 1. 初始化图表实例 var chartDom document.getElementById(main); var myChart echarts.init(chartDom); // 2. 声明 option 配置对象 var option { title: { text: 某站点用户访问来源, left: center }, tooltip: { trigger: item, formatter: {a} br/{b}{c} ({d}%) }, legend: { orient: vertical, right: 10, top: 20 }, series: [ { name: 访问来源, type: pie, radius: 55%, data: [ { value: 335, name: 直接访问 }, { value: 310, name: 邮件营销 }, { value: 234, name: 联盟广告 }, { value: 135, name: 视频广告 }, { value: 1548, name: 搜索引擎 } ] } ] }; // 3. 设置配置项并渲染图表 myChart.setOption(option); /script /body /html这段代码里面radius: 55%指的是饼图的半径占容器宽度或高度较小者的一半的比例。定义成相对值而不是固定像素是为了让图表在不同尺寸的屏幕上都能自适应缩放。页面打开之后你会看到五个扇区围成一个圆右边有竖排图例鼠标悬停到扇区上会出现一个小浮层展示名称、数值和百分比。这就算完整跑通了。3.2 逐行拆解tooltip 的 formatter 到底在干嘛注意到tooltip.formatter里那一串{a} br/{b}{c} ({d}%)了吗很多人第一次看到会懵这几个字母是 ECharts 的模板占位符对应不同的数据字段{a}系列名称也就是series.name这里是“访问来源”。{b}数据项名称也就是data里每个对象的name。{c}数值即data里的value。{d}当前项在总数中的百分比自动计算带百分号。这是 ECharts 非常贴心的设计你不用自己在 JS 里算百分比它会直接在模板中生成。如果你想自定义显示的文案不想用这个模板字符串也可以把formatter换成一个函数tooltip: { trigger: item, formatter: function (params) { return params.name 的访问量是 params.value 占比 params.percent.toFixed(1) %; } }函数写法的好处是你可以做更复杂的字符串拼接比如给百分比加上不同的颜色标记。日常开发中这两种写法都常见用哪个看需求强度。3.3 图例位置和布局调整图例legend默认在顶部居中显示这在饼图场景下经常会造成拥挤。我更推荐把图例放在右侧用orient: vertical让它竖排这样扇区空间更完整。legend: { orient: vertical, right: 10, top: 20 }如果要让图例横向排列在底部就改成legend: { orient: horizontal, bottom: 10, left: center }布局没有绝对的对错但有一个原则不要让图例遮住扇区也不要把图例放到一个让它很难读的位置。比如扇区标签文字很多的时候图例在右侧会比较合适否则底部就会被挤爆。4. 数据格式细节与动态对接4.1 从后端接口拿到数据之后怎么处理实际上项目里很少把数据硬编码进 option更多的情况是你从后端接口拿到一组数据然后转换成 ECharts 需要的格式。常见的后端返回结构有两种。第一种后端已经按 ECharts 的要求返回好了[ { name: 华东, value: 120 }, { name: 华南, value: 80 }, { name: 华北, value: 60 } ]这种直接把data指向这个数组就行。第二种后端返回的是两个平行数组{ names: [华东, 华南, 华北], values: [120, 80, 60] }这种就需要手动转换var resData response.data; // 假设 response 从后端拿到 var arr []; for (var i 0; i resData.names.length; i) { arr.push({ name: resData.names[i], value: resData.values[i] }); } myChart.setOption({ series: [{ data: arr }] });4.2 数据为 0 或缺失的时候怎么办这个坑我踩过很多次。饼图数据项如果value为 0会渲染成一个零角度的扇区本身不会有问题但 hover 上去的时候 tooltip 会显示 0看起来很不专业。有两种处理思路如果value是 0 的数据项确实有业务意义比如“某渠道本月无新增用户”保留 0但要在 tooltip 里清楚体现。如果 0 没有展示价值可以在数据处理阶段过滤掉比如data.filter(item item.value 0)这样图会干净得多。另外如果整个数据数组都是空数组ECharts 不会报错页面只会显示一个空的淡灰色圆形。这时候要特别注意页面反馈最好在setOption之前检查数据长度为空时给一个“暂无数据”的占位图不然交付出去容易被认为页面挂了。4.3 数据更新时的 setOption 合并机制ECharts 的setOption默认是合并模式也就是说你只传一个series原有的title和legend配置会被保留。这在动态刷新场景里很方便myChart.setOption({ series: [ { data: newData } ] });旧扇区的动画会自动过渡到新扇区视觉上很平滑不需要手动clear()再重新渲染。但是如果你改了series的类型或者数据结构比如从饼图变成了柱状图一定要用myChart.clear()清空实例或者调用myChart.setOption(option, true)第二个参数为true表示完全替换而不是合并。不然会出现新旧图形叠加的怪异效果。5. 样式定制从“能用”到“耐看”5.1 调色板别让 ECharts 默认色毁了你的图表ECharts 的默认配色是一套偏淡的马卡龙色系适合 demo但在正式项目里尤其是一些企业大屏或者汇报 PPT 里那个配色经常会显得不够厚重。你可以通过color数组来定义自己的调色板color: [#5470c6, #91cc75, #fac858, #ee6666, #73c0de, #3ba272]这里建议不要在色板上堆太多种颜色最好控制在 6~8 个以内不然视觉上很花。如果分类特别多超过 8 个饼图其实已经不是一个好的呈现方式了可以考虑换成横向条形图这个我在后面会展开说。5.2 环形饼图的制作一个小参数搞定环形饼图在视觉上比实心饼图更轻盈在不少大屏项目里也更常见。实现方式很简单把radius从单个值改成数组第一个值是内半径第二个值是外半径radius: [40%, 70%]这行代码意味着从距离圆心 40% 的位置开始画画到 70% 的位置中间空出的部分就是圆环的洞。想让环更细就缩小两个值的差想让环更粗就拉大差值。实际经验里内半径 50%~60%、外半径 70%~80% 这个范围视觉上是最舒服的。太小了环太细不好看太大了跟实心饼图没什么区别。5.3 扇区拉开间距的“南丁格尔玫瑰图”如果你想让饼图看起来更有设计感可以开启“玫瑰图”模式扇区半径不相等值越大半径越大。配置就一个字段roseType: radius这种图在展示“值有显著差异”的数据时比普通饼图更有视觉冲击力。但要注意玫瑰图会放大数值差异的视觉感受如果数据之间差别不大画出来会显得很平反而没有普通饼图清晰。5.4 itemStyle 里的细节边框、圆角和透明度扇区本身也能做更精细的定制常用的是加边框让扇区之间有明确的切割线itemStyle: { borderColor: #fff, borderWidth: 2, borderRadius: 4 }borderRadius这个属性从 ECharts 5.0 开始支持给每个扇区加圆角让边角不那么锐利。这个设计很适合用在“圆环图 数据占比”的现代风格大屏里视觉效果非常精致。6. 标签和提示的打磨普通用户和专家的分水岭6.1 标签位置是放在扇区里面还是外面连一条线饼图里最占空间也最影响美观的就是每个扇区旁边显示文字标签的方式。ECharts 里主要通过label配置来控制。标签默认是显示在扇区内部的label: { show: true, fontSize: 12 }但如果扇区太小文字挤在一起识别度就很差。常见的方案是把标签移到外部加一条引导线label: { show: true, position: outside, formatter: {b}{d}% }, labelLine: { show: true, length: 20, length2: 15, smooth: true }这样每个标签通过一条折线指向对应扇区视觉上更清爽。length和length2分别控制引导线靠近扇区一段和水平延伸一段的长度如果文字多适当把length2调大一点避免文字跟引导线挤在一起。6.2 最容易被忽略的 labelLine 问题这里一个非常典型的坑是标签从扇区内移到外部后如果扇区外缘很靠近图表边缘标签会被容器裁掉。解决办法是给整个图表容器留出足够的 padding或者把series.center往中心方向调一调让饼图整体缩小。另一个常见问题是小扇区的标签会重叠。如果占比很小的扇区有好几个文字全部堆在一侧很难读。常规的优化办法是调整minAngle强制让最小值占一定的角度minAngle: 5这个值是度数表示扇区最小不小于 5 度。这样即使某些数据占比只有 2%扇区也不至于窄成一条线。但这个方案只适合数据项不多的情况如果超过 10 个分类还非要用饼图那标签重叠基本无解建议换图。6.3 tooltip 在小屏和宽字符下的换行与截断网络热词里有人专门搜“echarts tooltip自动换行”说明这个问题确实困扰了不少人。tooltip 默认是单行显示的如果名称和数值都很长会超出屏幕边界。解决方案是配置confine: truetooltip: { trigger: item, confine: true }这个属性会把提示框限制在图表容器内部超出部分会换行显示。另外你也可以在formatter函数里用br/手动换行控制更精确。7. 多图叠放和嵌套饼图的高级玩法7.1 用两个系列实现嵌套环形图嵌套饼图本质上就是一个 option 里放了两个series内环一个饼图外环一个饼图通过不同的radius范围让它们叠在一起series: [ { name: 本月销量, type: pie, radius: [0%, 45%], data: innerData }, { name: 上月销量, type: pie, radius: [55%, 75%], data: outerData } ]注意内环的外半径要小于外环的内半径中间留出一小段空隙否则视觉上会糊在一起。内环可以展示“分类占比”外环展示“细分项占比”常见的应用场景是“品类 → 子品类”的层级关系。但嵌套饼图有个明显缺点当外环扇区很多时内环的占比关系会很难看。因为内环和外环的圆心相同内外角度不一定对齐对比起来很费劲。如果你要做真正的层级占比图更推荐用 ECharts 的树图treemap或者旭日图sunburst这些图的设计初衷就适合下钻结构。7.2 中心文字展示合计值环形图中间的空洞如果空着视觉上会显得不平衡。一个很实用的技巧是利用title在图表中心显示合计值title: { text: 1234, subtext: 总访问量, left: center, top: 42% }如果这个数字是动态的在setOption前用变量拼进去就行。这种设计在数据大屏里非常常见可以一眼看到总量又不会被辅助信息分散注意力。7.3 饼图和柱状图联动如果页面上同时有饼图和柱状图可以通过事件让它们联动。ECharts 提供了on方法监听点击事件myChart.on(click, function (params) { if (params.seriesType pie) { barChart.setOption({ series: [{ data: getDetailData(params.name) }] }); } });这种方式很适合做“点击某个分类右侧显示该分类的明细趋势”这种交互在大屏项目里非常出效果。思路本身不难难点在于事件回调拿到的params字段在不同图表类型之间有差异做联动前先全面打印一遍params确认字段名再往下写。8. 实际项目里踩过的五个坑8.1 容器隐藏导致的初始化白屏最常见也是最经典的问题echarts.init执行的时候容器是隐藏的比如页面的 Tab 还没切换过去图表初始化时宽度和高度计算为 0等切换到那个 Tab 时图表显示不出来。解决方案有两个在容器可见之后再调用init。调用myChart.resize()强制重新计算尺寸// 在容器可见后执行 myChart.resize();如果用的是 Vue 之类的框架可以在组件mounted之后加一个nextTick确保 DOM 已经完全渲染。8.2 数据更新之后百分比对不上这个问题看起来很奇怪tooltip 里的百分比和 legend 里的数值对不上其实是数据更新的时序问题。ECharts 在setOption时新旧 series 的数据项默认会按 index 对应所以如果你只是替换了数据内容没有保持顺序一致过渡动画和 tooltip 计算就会错位。解决办法是在更新数据时用myChart.setOption({ series: [{ data: newData }] }, true)强制全新渲染或者保证数据顺序一致。8.3 饼图标签文字相互遮挡前面提到过小扇区标签重叠的问题这里再补充一个思路如果数据项超过 6 个可以考虑把label直接关掉所有信息全部依赖 tooltip 展示label: { show: false }图例保留扇区保持简洁鼠标悬停时查看具体数值。这不是逃避问题而是在数据项过多时更务实的取舍。8.4 颜色忽变跟主题不统一如果设置了color数组但在某些老版本 ECharts 里不生效大概率是版本问题。4.x 之前的旧版本里颜色优先级跟 5.x 不一样。升级到 5.x 后基本就好了。另外如果你在单个data项里给某个扇区单独指定了itemStyle它的优先级会高于全局color混用时容易被这个优先级绕晕。8.5 resize 窗口适配窗口尺寸变化时图表不会自动调整大小需要手动监听事件window.addEventListener(resize, function () { myChart.resize(); });在 Vue 项目里记得在组件beforeDestroy或unmounted阶段移除监听事件否则页面残留的监听器多了会造成性能问题。9. 常见问题速查表问题现象可能原因解决方案饼图不显示控制台无报错容器尺寸为 0 或隐藏确保容器可见后再init或调用resize()标签文字重叠数据项过多或扇区角度太小设置minAngle关闭内部label或换图tooltip 显示不全容器边缘截断设置confine: true百分比和数值不匹配更新数据时新旧系列错位setOption(option, true)强制全量更新图例点击后扇区消失这是 ECharts 默认行为用legend.selected控制初始选择状态玫瑰图数值差异不明显数据本身差距太小改用普通饼图这张表是我在实际项目里积累下来的建议收藏。遇到问题优先比对自己的配置80% 的场景能直接对上。最后说一句我个人的体会饼图看似简单却是最容易画出“丑图”的图表类型。数据项一多、标签一乱、配色一花整个图表就废了。所以真正考验功力的不是会不会写配置而是在什么场景下克制地使用饼图、以及如何为了可读性做取舍。后续如果你对 ECharts 其他图形有疑问比如地图、漏斗图、雷达图我也有实操文章到时可以继续交流。