尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

ECharts饼图实战:从绘制到避坑的完整指南

发布时间:2026/9/29 4:18:21

资讯中心
01
ARTICLE

ECharts饼图实战:从绘制到避坑的完整指南

ECharts饼图实战:从绘制到避坑的完整指南
做可视化项目做了这些年我养成一个习惯拿到一组数据先问自己要解决的是“看大小”还是“看构成”。如果是后者几乎默认就是饼状图。ECharts把饼图做得表面上很简单series里写上type: pie就能出图可真正进到项目里标签重叠、引导线乱飞、动态刷新卡顿、rem 适配失效这些问题每个都能让人浪费一下午。这篇是 Echarts 可视化教程系列的第四篇我打算抛开官方文档那种平铺直叙把饼状图的绘制拆成数据准备、样式打磨、交互联动、坑点排查几个角度完整过一遍我在真实项目中画饼图的流程。如果你刚接触 ECharts照着代码敲一遍就能跑通如果你已经在做大屏项目重点看后面避坑和性能相关的部分。1. 饼状图的应用边界什么时候该用什么时候别硬用1.1 饼图擅长和不适用的场景饼状图的本质是用扇区面积表达“整体 各部分之和”的构成关系。它最擅长的场景是销售区域占比、流量来源构成、库存结构、预算分配、性别比例这一类数据。这类数据的特点是分类数量不多每个分类之间互斥加在一起等于一个明确的整体。用户看图的诉求也只有一个——知道谁大谁小、差距明显不明显。但它有明显不擅长的地方。第一它不适合做精确对比。人对角度的判断远不如对长度和位置敏感两个扇区如果分别是 18% 和 21%不配文字说明的话肉眼基本分辨不出来。第二分类太多时不要硬画。超过 8 个分类较小的扇区会被挤成一条细线标签叠成一团这时候饼图的可读性急剧下降。第三数据之间有包含关系或者本质是时间序列也不要套饼图层级关系交给旭日图或嵌套饼图时间趋势交给折线图。数据诉求推荐图表原因占比构成5~8 类饼图 / 环形图直观展示部分与整体的关系精确数值比较柱状图长度比较比角度比较更精确时间趋势折线图突出变化方向和速度多层级构成嵌套饼图 / 旭日图同时表达父级与子级结构这个“先判断场景再选图表”的习惯比背任何配置项都重要。很多所谓“图表丑”“图表难读”的问题根源不在代码而在选型一开始就错了。1.2 一张饼图的最小 option 结构在 ECharts 里画饼图最小配置其实少得惊人const option { series: [ { type: pie, data: [ { name: 已完成, value: 68 }, { name: 进行中, value: 22 }, { name: 未开始, value: 10 } ] } ] };把这段配置交给setOptionECharts 就能画出一个带有默认图例、默认 tooltip、默认标签的饼图。series是一个数组意味着一个图表实例可以容纳多个系列data里的每一项推荐使用对象格式name是分类名称value是对应的数值。ECharts 会自动根据所有value的总和计算每个扇区的角度比例。这个最小结构是理解后面所有配置的基础。你会发现标题、图例、tooltip、颜色、标签全部是“可选项”它们的默认值足够让一张图的“骨架”先立起来。我的建议是新项目里先把最小结构跑通确认数据和容器没有问题再逐步叠加样式配置而不是一上来就堆几百行 option。1.3 为什么饼图不需要坐标系柱状图和折线图必须配置xAxis和yAxis否则无法确定数据在直角坐标系里的位置。饼图不一样它不依赖直角坐标系ECharts 默认以容器中心为圆心以radius为半径绘制扇区。这带来一个便利不需要处理坐标轴范围、刻度间隔、轴标签旋转这些问题。但也带来一个容易忽略的约束饼图的尺寸完全由容器宽高和radius决定。如果容器高度设成了 0或者容器在初始化时是隐藏状态饼图可能直接渲染不出来或者画成一个点。后面避坑章节我会专门讲这个。另一个隐含问题是饼图没有坐标轴所以也不存在“缩放”和“平移”的数据窗口概念交互上更多靠事件和dispatchAction来做高亮、选中这类操作。2. 从零渲染一个完整饼状图数据、容器和配置三步走2.1 开发环境与 ECharts 引入方式先解决环境问题。目前 ECharts 已经到 5.x 版本官方包名统一为echarts。常见的引入方式有两种。第一种是 npm 方式适合 Vue、React 这类工程化项目npm install echarts然后在组件里引入import * as echarts from echarts;第二种是 CDN 方式适合写 HTML 静态页面、做实训作业、临时验证 Demoscript srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script无论是哪种方式接下来都需要一个具备宽高的容器。ECharts 初始化时读取容器的clientWidth和clientHeight所以容器必须有明确尺寸div idchart stylewidth: 700px; height: 480px;/div我看到不少新手在这一步把高度写成height: 100%但父容器没有设置高度结果图表死活不出现。如果确实要填满父容器可以给父容器也设height: 100%并确保body或外层容器有实际高度或者用固定像素值最省心。2.2 数据格式name/value 是饼图的血液饼图的数据格式是整个绘制过程的核心。data数组支持两种写法// 写法一精简数组只有数值 data: [4200, 3100, 1800, 950, 650] // 写法二对象数组含名称和数值 data: [ { name: 家电, value: 4200 }, { name: 数码, value: 3100 } ]第一种写法省代码但图例、tooltip 里只能显示“系列 1、系列 2”这类占位名。绝大多数真实场景都要用第二种对象写法因为图例和标签都依赖name来做标识。对象写法还有一个优势可以在单个数据项里覆盖全局样式。比如想把“家电”这个扇区单独标红data: [ { name: 家电, value: 4200, itemStyle: { color: #c23531 } }, { name: 数码, value: 3100 } ]value除了数字也支持数组用于带上下界的区间数据但饼图里很少用到我就不展开说了。2.3 完整示例一个可交互的销售占比饼图下面这个例子是我在本地跑通的完整页面它包含标题、图例、悬浮提示、百分比标签所有交互都是 ECharts 默认行为不需要额外写事件!DOCTYPE html html langzh-CN head meta charsetUTF-8 / titleECharts 饼状图示例/title style #chart { width: 700px; height: 480px; } /style /head body div idchart/div script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script script const chartDom document.getElementById(chart); const myChart echarts.init(chartDom); const option { title: { text: 各品类销售额占比, left: center }, tooltip: { trigger: item }, legend: { bottom: 0, orient: horizontal }, series: [ { name: 销售额, type: pie, radius: 60%, data: [ { name: 家电, value: 4200 }, { name: 数码, value: 3100 }, { name: 服饰, value: 1800 }, { name: 美妆, value: 950 }, { name: 食品, value: 650 } ], label: { formatter: {b}: {d}% } } ] }; myChart.setOption(option); /script /body /htmlradius: 60%表示扇区半径占容器较短边的 60%ECharts 会自动计算不用管具体像素。图例默认在顶部我把它放到底部避免和标题抢位置。label.formatter用了模板字符串{b}代表 name{d}代表百分比渲染出来的标签就是“家电: 35.2%”这种格式。这个配置跑起来后鼠标悬浮会自动显示带“名称、数值、百分比”的 tooltip图例点击可以隐藏或显示对应扇区这些都是内置行为。如果你只是交作业或做内部看板到这里其实已经够用了。2.4 用 dataset 管理数据ECharts 5 对dataset的支持已经相当成熟。如果你的接口返回的是二维表格结构比如[{ category: 家电, sales: 4200 }, ...]用dataset可以让配置更规整option { dataset: { dimensions: [category, sales], source: [ { category: 家电, sales: 4200 }, { category: 数码, sales: 3100 }, { category: 服饰, sales: 1800 } ] }, series: [ { type: pie, encode: { itemName: category, value: sales } } ] };encode指定了哪一列是名称、哪一列是数值。这样做的好处是数据和配置分离接口加字段时不用改series。不过我自己在实际项目中还是更常用对象数组直接塞给data因为饼图的数据量不大结构也简单dataset的优势要等折线图、柱状图那种多系列场景才更明显。3. 常见变体环形图、南丁格尔玫瑰图与嵌套饼图3.1 环形图radius 数组与中心留白环形图本质上是饼图把中间挖空语法上只需要把radius从字符串改成数组series: [ { type: pie, radius: [45%, 70%], data: [...] } ]数组的第一个值是内径第二个值是外径。内径和外径都用百分比表示ECharts 会根据容器尺寸自动换算实际像素。内径占外径的比例越大环越细官方推荐内径 55%~65%、外径 75% 左右视觉比较舒服太细了标签引导线会显得突兀。环形图相比实心饼图有一个非常大的好处中心区域可以放 KPI 数字。在大屏项目里环形图中心放“本月销售额 4238 万”这类核心指标既省空间又能突出重点。实现方式是用title定位到中心title: { text: 4238, subtext: 本月总销售额(万元), left: center, top: middle, textAlign: center }注意textAlign要设成center否则标题整体会偏右。这个细节我第一次写的时候没注意数字和副标题怎么都对不齐后来才发现是标题默认左对齐。3.2 玫瑰图roseType 两种模式的取舍南丁格尔玫瑰图是饼图的一个经典变体ECharts 里开启方式非常简单series: [ { type: pie, roseType: area, radius: 65%, data: [...] } ]roseType有两个可选值。radius模式扇区的半径角度由占比决定半径大小也随数值变化area模式每个扇区角度均分通过半径长度来控制面积大小让数值差异更直观。实际效果上area模式对极值更敏感数据差距大时“一眼就能看出哪个分类最大”适合做品牌知名度、城市人口这类对比radius模式更接近原始饼图的阅读习惯。玫瑰图好看但有两个使用前提。一是分类数量最好控制在 5~8 个之间太多会变成一朵“菊花”二是数值之间不要出现 0 或负值否则玫瑰图会渲染出奇怪的空隙。我在项目里一般只在对外汇报、需要视觉冲击力的页面上用玫瑰图内部管理后台还是老老实实用普通环形图。3.3 嵌套饼图的层级表达嵌套饼图适合表达“父级类别 子级类别”的两层构成。比如母饼图是“自营、第三方、海外”子饼图是对应的细分品牌。实现方式是在series数组里放两个类型为pie的系列通过radius区分内外series: [ { name: 大品类, type: pie, radius: [0%, 45%], data: [ { name: 自营, value: 5200 }, { name: 第三方, value: 2800 }, { name: 海外, value: 1000 } ] }, { name: 细分品类, type: pie, radius: [55%, 75%], data: [ { name: 自营-家电, value: 3000 }, { name: 自营-数码, value: 2200 }, { name: 第三方-服饰, value: 1800 }, { name: 第三方-美妆, value: 1000 }, { name: 海外-食品, value: 1000 } ] } ]内层半径从 0 开始就是实心圆外环从 55% 开始形成圆环。这种方案的难点不在配置而在数据结构外层每个扇区的子项总和必须和内层对应扇区的值相等否则读图的人会对不上。另一个坑是图例。默认图例会同时展示两个系列的所有 name如果分类多图例区会被塞满。建议用legend: { selectedMode: true }允许用户自己开关图例或者直接只显示某几个关键项。嵌套饼图在移动端效果一般屏幕窄、两层标签容易叠做 PC 大屏或宽屏页面时再用。4. 细节决定成败标签、引导线和配色优化4.1 label 的内容、位置与换行处理饼图默认会把标签显示在扇区外部内容只有 name。多数项目希望显示“名称 百分比”这时候可以配置label.formatterlabel: { formatter: function (params) { return params.name \n params.value ( params.percent %); } }params.percent是 ECharts 计算好的百分比默认保留一位小数可以直接用。这里用\n做标签内部的换行ECharts 在label里支持\n换行。label.position有几种常用取值outside是外部标签配引导线inside把文字放在扇区内部适合扇区较大的情况center只能用于单个数据系列的中间位置适合做“单指标完成率”这种场景比如一个圆环显示“完成度 78%”。内部标签的问题是扇区太小会挤一般建议配合minAngle使用minAngle: 5minAngle: 5的作用是小于 5 度的扇区强制按 5 度渲染虽然实际占比被轻微“夸大”但标签有地方放了视觉上也更均匀。注意它不会改变 name 对应的数据值只是影响扇区的视觉角度。4.2 引导线样式与“末尾小圆点偏移”的修复外部标签靠引导线连接扇区和文字。ECharts 里通过labelLine控制labelLine: { length: 20, length2: 15, smooth: 0.2, lineStyle: { color: #999 } }length是靠近扇区那一段的直线长度length2是靠近文字那一段的长度smooth控制拐角是否平滑。很多人在完成一张饼图后会觉得外边框不太对劲其实就是smooth: 0到smooth: 0.4之间视觉差异很大曲线更圆润但有时会和旁边扇区的引导线纠缠。“末尾小圆点偏移”是很多人遇到过的问题开启扇区圆角后引导线靠近扇区的端点和扇区边缘对不上看起来小圆点是悬空的。原因很简单itemStyle.borderRadius给扇区加了圆角但 ECharts 计算引导线连接点时默认还是按没有圆角的扇形边界来定位。解决办法有几个圆角半径别太大borderRadius: 4~6已经是比较自然的效果过大偏移会非常明显不要同时开启labelLine.smooth曲线会让连接点位置更难判断如果项目允许直接减小labelLine.length让引导线从扇区末端稍微延伸出来观感会舒服很多。这个坑在 ECharts 5.x 里不是彻底修复的官方各版本的渲染策略略有差异我一般是先改borderRadius看效果再决定要不要动smooth。4.3 颜色、高亮和动画调优颜色是饼图观感的最大变量。ECharts 默认配色能看但没有品牌感。建议在 option 里显式指定color数组color: [#4E79A7, #F28E2B, #E15759, #76B7B2, #59A14F, #EDC948, #B07AA1]这个配色是从 Tableau 经典 10 色里选出来的色相差、明度适中非常适合饼图做分类。自定义颜色时注意相邻扇区颜色不要选邻近色否则边界分辨不清。不需要把所有扇区颜色都写进数组ECharts 的color数组会按顺序自动循环使用。高亮状态是“要不要给人一种精致的反馈”的关键。默认 hover 时扇区会轻微高亮可以进一步配置emphasisemphasis: { scaleSize: 10, itemStyle: { shadowBlur: 20, shadowColor: rgba(0, 0, 0, 0.4) } }scaleSize: 10让 hover 的扇区向外偏移 10 像素形成“弹出来”的效果配上阴影更明显。shadowBlur和shadowColor控制阴影大小和颜色。这个配置在汇报场景特别加分用户操作起来反馈很直观。最后说动画。ECharts 默认首次渲染会播放一个从 0 开始的生长动画这个不建议关。但animationDuration默认是 1000ms如果你做的是数据实时刷新的页面每次更新都播放动画会很晃眼可以把animationDurationUpdate调小或者更新数据时直接设置animation: false。5. 交互与数据刷新把饼图接入真实业务5.1 click 事件与 dispatchAction 高亮联动一张静态饼图在真实项目里基本只用来“看”但业务方往往还希望“点”。ECharts 为饼图提供了非常成熟的点击事件机制myChart.on(click, function (params) { console.log(params.name, params.value); });params里包含当前点击扇区的 name、value、percent、seriesIndex、dataIndex 等字段。常见的联动是点击某个分类后页面其他部分展示对应详情或者跳转到筛选后的列表页。如果只是希望点击扇区时高亮它用dispatchAction更合适myChart.on(click, function (params) { myChart.dispatchAction({ type: highlight, seriesIndex: 0, name: params.name }); }); myChart.dispatchAction({ type: downplay, seriesIndex: 0 });highlight配合emphasis配置可以做出非常强烈的聚焦效果比如把其他扇区变灰、只放大当前扇区。做演示大屏时我经常用这个方式实现“点谁谁弹出来”的交互。注意downplay要先执行否则上一次高亮状态会残留。5.2 setOption 更新数据避免销毁重建很多项目在拿到新数据后的第一反应是重新init一次这是绝对错误的。echarts.init是重量级操作反复初始化会带来性能问题也会丢失之前的交互状态。正确做法是一开始初始化一次之后每次数据变化直接调用setOptionmyChart.setOption({ series: [ { data: newData } ] });setOption默认是“合并”模式新配置会和旧配置深度合并所以上面这段不会覆盖掉原来的类型、标题、颜色等配置。如果你的数据结构变化比较大希望完全重置可以传入第二个参数true即notMerge把旧配置全部清掉再重新渲染。做实时刷新时需要注意频率。如果接口每 2 秒轮询一次每次都带动画页面会很卡。我的做法是setInterval(async () { const data await fetchData(); myChart.setOption({ series: [{ data }], animation: false }); }, 5000);实时数据场景下把animation关掉或把animationDurationUpdate降到 200ms 左右视觉上既能感知数据变化又不会因为动画排队造成卡顿。5.3 tooltip 自定义自动换行与富文本ECharts 默认的 tooltip 在饼图中的展示形式是“名称后跟数值”如果想让信息更丰富可以用tooltip.formatter。这里最容易踩的坑是换行在 formatter 里用\n是没用的必须用 HTML 标签br/tooltip: { trigger: item, formatter: function (params) { return ( b params.name /bbr/ 销售额 params.value 万元br/ 占比 params.percent % ); } }这种写法的输出是一段 HTMLECharts 会把 tooltip 内容当作富文本渲染。如果项目里有统一的 UI 风格还可以在 formatter 里拼div、span配合tooltip.extraCssText或者给 tooltip 容器加className实现自定义样式。注意不要直接用模板字符串拼接未过滤的用户数据否则可能引入 XSS 风险这个在避坑章节再细说。trigger: item表示鼠标悬浮在扇区上时触发。饼图通常不需要trigger: axis因为饼图没有坐标轴刻度axis 触发器在饼图上不生效。6. 实战避坑开发过程中容易翻车的细节6.1 空数据与全零数据导致的白屏这是饼图开发里最常见的“页面白屏”原因之一。当接口返回的data是空数组时ECharts 不会报错但页面上会出现一片空白或一个无法分割的圆。全零数据情况类似ECharts 计算百分比时所有值都是 0会出现 NaN% 或根本不渲染扇区。处理方式分两层。第一层渲染前判断if (!data || data.length 0 || data.every(item item.value 0)) { myChart.clear(); myChart.setOption({ title: { text: 暂无数据, left: center, top: middle, textStyle: { fontSize: 16, color: #999 } } }); return; }第二层如果接口可能返回负数或null要在装配数据时过滤掉非正向数值。饼图只适合正向数值负值会让视觉效果完全失控。6.2 容器尺寸、Tab 切换与 resize饼图的渲染强烈依赖容器尺寸。最常见的坑是容器在组件初始化那一刻是隐藏的宽度或高度为 0ECharts 初始化后自然画不出东西等 Tab 切到它所在的页面时图表区域仍然是空白的。解决办法是切换 Tab 后主动调用myChart.resize();resize()会重新计算容器尺寸并重绘。如果容器从隐藏变为显示时宽高发生变化只调用一次就行。另外浏览器窗口尺寸变化时要监听 resize 事件let timer null; window.addEventListener(resize, function () { clearTimeout(timer); timer setTimeout(() myChart.resize(), 200); });加setTimeout做 200ms 防抖避免拖动窗口时高频触发 resize 导致性能开销。记住图表实例一旦创建就尽量复用它不要在路由跳转时反复 init 和 dispose否则内存占用会持续上涨。6.3 rem 适配为什么 pxtorem 对 ECharts 不生效这是做可视化大屏时几乎必踩的问题。项目里用了postcss-pxtorem这类工具CSS 里的 px 会自动转 rem唯独 ECharts 内部配置找的是像素值比如radius: 60%、fontSize: 14、labelLine.length: 20这些不是 CSS不经过 postcss 编译所以在不同分辨率的屏幕上不会跟着缩放。正确做法是在大屏项目里引入一个全局缩放系数。比如设计稿是 1920 宽度实际屏幕宽度是clientWidth则可以计算const scale document.documentElement.clientWidth / 1920;然后在配置里所有涉及像素的地方乘以系数label: { fontSize: 14 * scale }, labelLine: { length: 20 * scale, length2: 15 * scale }百分比形式的radius不需要乘系数因为它本来就是相对值。还有一个更简单的方案用浏览器提供的transform: scale()把整个图表容器放大缩小这样 ECharts 配置不用改但需要额外处理容器占位和事件坐标适合对交互要求不高的展示大屏。6.4 formatter 注入风险与 XSS 预防label.formatter和tooltip.formatter返回的内容会被当作 HTML 渲染。如果 name 来自用户输入或第三方接口里面带有img srcx onerroralert(1)这类字符串就会触发 XSS。这类问题在内部工具里可能不明显但在对外产品里是安全漏洞。最直接的办法是转义。我通常在项目里维护一个工具函数function escapeHtml(str) { return String(str) .replace(//g, amp;) .replace(//g, lt;) .replace(//g, gt;) .replace(//g, quot;); }在 formatter 里对参数值先过一遍formatter: function (params) { return escapeHtml(params.name) : params.percent %; }如果希望保留富文本样式请用 ECharts 的富文本语法{name|text}配合rich配置而不是直接拼 HTML。富文本是 ECharts 提供的安全渲染机制既能控制格式又不会把数据当作原始 HTML 解析。最后说一个我自己的习惯不管是普通饼图还是玫瑰图我永远不会把超过 8 个分类的数据直接扔给它。宁可把“其他”合并成一类也要保住图表的可读性。数据可视化不是把所有细节都堆到一张图上而是把最重要的结论放大给看的人。饼状图看起来是 ECharts 里最简单的图表类型真正考验人的地方其实都藏在这些取舍和细节判断里。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。