ECharts环形图实战:从半径配置到标签布局与交互优化

发布时间:2026/10/31 17:06:21
ECharts环形图实战:从半径配置到标签布局与交互优化

环形图这东西,乍一看谁都会画——不就是个饼图中间挖个洞吗?但真到项目里把需求摊开,你会发现坑全藏在细节里:中间要放汇总数字、扇区之间要留缝、标签引线不能打架、小占比数据挤成一团、鼠标悬浮要高亮还得联动、图例要对齐、颜色要按业务分级……这些需求文档里往往一句话带过,做起来却要反复调半天。我这些年接手过的可视化页面里,环形图出现的频率高得离谱,从运营看板到数据大屏,从后台报表到移动端卡片,几乎每个项目都会碰到它。这篇就把我踩过的坑和攒下来的配置经验一次讲清楚,围绕 ECharts 环形图这个主题,从最基础的半径参数到进阶的标签布局、交互联动、性能取舍,全部拆开揉碎讲。不管你是刚接触 ECharts 的新手,还是已经画过几十个图表、想系统梳理一遍的老手,下面这些内容应该都能直接拿去用。

1. 环形图不是饼图加个洞:先搞懂半径体系

很多人上手写环形图,第一反应是把type设成pie,然后随手加个radius就完事。能出图,但一旦要控制环的粗细、内外径比例、多环嵌套,就开始抓瞎。ECharts 的饼图(环形图本质是饼图的一种形态)半径参数是整个图表几何结构的根,理解它比记任何配置项都重要。

1.1 radius 的两种写法与内径外径的真实含义

radius支持两种写法:一种是单个值,比如radius: '60%',这时候画出来的是实心饼图;另一种是数组,比如radius: ['40%', '70%'],第一个值是内径,第二个值是外径,这才构成环形。这里有个特别容易混淆的点:内径和外径都是相对于容器短边的一半来计算的,不是相对于容器宽度,也不是相对于外径。也就是说,如果容器是 400×300,短边是 300,一半就是 150,那么radius: ['40%', '70%']实际对应的像素内径是 60px、外径是 105px。

我见过有同学为了让环变细,把内径调到 65%、外径 70%,结果环薄得像根线,鼠标根本点不中。这就是没算清楚比例关系。环的厚度等于外径减内径,用百分比表示时,这个差值直接决定了视觉上的"环宽"。一般经验值:环宽占外径的 25% 到 40% 之间比较舒服,太厚显得笨重,太薄又不好交互。

option = { series: [{ type: 'pie', radius: ['45%', '70%'], // 环宽 = 25% 的外径比例 center: ['50%', '50%'], data: [ { value: 1048, name: '线上渠道' }, { value: 735, name: '线下门店' }, { value: 580, name: '合作分销' }, { value: 484, name: '其他' } ] }] };

上面这段是最小可运行配置。center控制圆心位置,默认就是['50%', '50%'],但如果你要在环心放文字,或者图表旁边有图例占位,圆心往往需要微调。我个人的习惯是先把center和radius定死,再往里填数据,这样布局不会因为数据变化而漂移。

1.2 圆心偏移与容器留白的配合技巧

center的百分比同样是相对于容器宽高计算的。这里有个实战中很常见的需求:环形图左边要放图例,右边是环,或者环心要放一个大的汇总数字。这时候如果圆心还在正中间,视觉上会偏。我的做法是把center往反方向挪一点,比如图例在左侧占 30% 宽度,圆心就设成['60%', '50%'],给左侧留出空间。

但要注意,center挪动之后,radius的百分比基准没变,还是容器短边的一半。所以如果容器本身比较扁,环可能会超出容器边界被裁掉。判断方法很简单:外径像素值 = 短边一半 × 外径百分比,圆心到容器边缘的距离必须大于这个值。容器 400×300、外径 70% 时,外径像素是 105px,圆心在 x 方向 60% 即 240px 处,距离右边缘 160px,够用;但如果圆心挪到 80%,距离右边缘只剩 80px,环就被切了。这种边界计算在响应式布局里尤其要留意,容器尺寸一变,比例关系就得重新核对。

提示:调试阶段可以临时给容器加个边框,肉眼确认环有没有被裁、圆心是否居中,比反复改配置快得多。

2. 环心放汇总数字:title 还是 graphic 还是 label

环形图最经典的用法,就是中间空出来的那块地方放个总数或者核心指标。这个需求看起来简单,实现方式却有好几种,各有各的适用场景和坑。选错了方式,后面做交互联动时会非常难受。

2.1 三种实现方式的对比与选型逻辑

先说结论:静态展示用title最省事,需要跟随数据动态变化用graphic或label,需要参与交互高亮就用label的富文本方案。下面这张表是我总结的对比,实际项目里按需选。

实现方式优点缺点适用场景
title定位到环心配置简单,支持主副标题不随数据自动更新,需手动 setOption静态看板、数字不常变
graphic图形元素定位精确,可放任意内容配置略繁琐,层级需注意环心放图标+文字组合
series.label富文本与数据天然绑定,自动更新定位依赖position: 'center'数字随数据变化、需高亮联动

用title的方案,核心是把title的left、top设成和圆心一致,再用textAlign、textBaseline居中。但title的定位是相对于整个容器的,圆心一旦因为center调整而偏移,title也得跟着改,两个地方要同步维护,容易出错。

option = { title: { text: '3,847', subtext: '总订单量', left: '50%', top: '42%', textAlign: 'center', textStyle: { fontSize: 28, fontWeight: 'bold', color: '#333' }, subtextStyle: { fontSize: 13, color: '#999' } }, series: [{ type: 'pie', radius: ['45%', '70%'], center: ['50%', '50%'], label: { show: false }, data: [/* ... */] }] };

注意top我设的是42%而不是50%,因为主副标题两行文字整体要垂直居中,得往上提一点,让两行文字的中线落在圆心上。这个偏移量跟字号有关,字号越大偏移越明显,需要微调。

2.2 label 富文本方案:让环心数字跟着数据走

如果环心数字需要随数据变化,或者希望鼠标悬浮某个扇区时环心显示该扇区的数值,label的富文本方案是最顺手的。原理是给series配一个label,position设为'center',然后用formatter的富文本语法拼出多行内容。

option = { series: [{ type: 'pie', radius: ['45%', '70%'], center: ['50%', '50%'], label: { show: true, position: 'center', formatter: '{total|3,847}\n{sub|总订单量}', rich: { total: { fontSize: 28, fontWeight: 'bold', color: '#333', lineHeight: 36 }, sub: { fontSize: 13, color: '#999', lineHeight: 20 } } }, emphasis: { label: { show: true, formatter: '{total|{c}}\n{sub|{b}}' } }, data: [/* ... */] }] };

这里有个关键细节:position: 'center'的 label 默认每个扇区都会渲染一个,全都叠在圆心,看起来像加粗了。正确做法是只让一个数据项显示 label,其余隐藏。常见技巧是把 label 配置放在series级别,然后通过label.show配合数据项的label: { show: false }逐个控制,或者干脆用emphasis只在悬浮时显示。我一般用后者:常态下环心显示总数(用title或graphic),悬浮时用emphasis.label显示当前扇区信息,两者不冲突。

注意:emphasis.label的formatter里{c}是数值、{b}是名称,但如果你在series.label里已经用了富文本,emphasis里也要保持富文本语法一致,否则样式会跳。

3. 扇区之间的缝隙:padAngle 与 itemStyle 的取舍

设计稿里环形图的扇区经常不是紧挨着的,中间留一条细缝,看起来更透气。这个缝隙怎么实现,不同 ECharts 版本给的路子不一样,踩过的坑也不少。

3.1 新版 padAngle:一行配置搞定扇区间隙

较新版本的 ECharts 提供了padAngle参数,直接控制扇区之间的角度间隙,单位是角度值。这是最干净的方案,一行配置就能出效果,而且缝隙是均匀的、沿着半径方向贯穿的。

series: [{ type: 'pie', radius: ['45%', '70%'], padAngle: 2, // 每个扇区之间留 2 度间隙 data: [/* ... */] }]

padAngle的取值建议在 1 到 3 度之间。太小看不出效果,太大扇区会显得零碎,尤其是数据项多的时候,每个扇区本来角度就小,再扣掉间隙可能就剩一条线了。数据项超过 8 个时,我一般把padAngle压到 1 度甚至干脆不用,改用其他方式区分。

3.2 老版本兼容:borderWidth 模拟缝隙的隐患

如果项目锁定的 ECharts 版本不支持padAngle,常见替代方案是给每个扇区加itemStyle.borderWidth和borderColor,用边框色模拟缝隙。比如borderWidth: 2, borderColor: '#fff',视觉上就是扇区之间有条白缝。

series: [{ type: 'pie', radius: ['45%', '70%'], itemStyle: { borderWidth: 2, borderColor: '#fff', borderRadius: 4 // 扇区圆角,配合缝隙更柔和 }, data: [/* ... */] }]

这个方案能用,但有两个隐患必须知道。第一,边框是画在扇区内部的,会吃掉扇区的实际显示宽度,数据项多的时候扇区看起来比实际占比更窄,视觉比例失真。第二,borderColor必须和图表背景色一致,一旦背景是渐变或者有底图,白边就会露馅。所以这个方案只适合纯色背景的简单场景,复杂背景还是老老实实升级版本用padAngle。

提示:borderRadius配合padAngle用效果最好,扇区两端带点圆角,缝隙看起来更自然。但圆角半径别超过环宽的一半,否则扇区会变形。

4. 标签与引线:小占比数据的标签保卫战

环形图最让人头疼的,永远是标签。数据项一多,小占比的扇区挤在一起,标签互相重叠、引线交叉,整个图看起来像一团乱麻。这块没有银弹,只能靠参数组合加人工微调,但有几个套路能大幅降低返工率。

4.1 labelLine 的长度控制与拐点逻辑

ECharts 的标签引线由两段组成:从扇区边缘出来的一段(length)和水平延伸的一段(length2)。默认配置下,小扇区的引线会挤在一起。我的调参顺序是:先缩短第一段length,再调整第二段length2,最后用minTurnAngle控制拐点角度。

series: [{ type: 'pie', radius: ['45%', '70%'], label: { show: true, formatter: '{b}\n{d}%', fontSize: 12 }, labelLine: { show: true, length: 12, // 第一段引线长度 length2: 20, // 第二段水平引线长度 minTurnAngle: 90, // 拐点最小角度,避免引线过于平缓 lineStyle: { color: '#bbb', width: 1 } }, data: [/* ... */] }]

minTurnAngle这个参数很多人不知道,它的作用是当引线拐角小于设定角度时,强制把标签往外推,避免引线贴着扇区走。设成 90 度时,标签会尽量往两侧展开,减少上下堆叠。实测下来,数据项在 6 到 10 个之间时,这套配置能解决大部分重叠问题。

4.2 标签重叠的兜底方案:隐藏小占比标签

当数据项超过 10 个,或者存在大量占比低于 3% 的碎片数据时,再怎么调引线也救不回来。这时候最务实的做法是隐藏小占比数据的标签,只保留引线或者干脆合并成"其他"。ECharts 提供了label.show的函数式配置,可以按数据值动态决定是否显示。

label: { show: true, formatter: function (params) { // 占比小于 3% 的不显示标签文字 return params.percent < 3 ? '' : params.name + '\n' + params.percent + '%'; } }

但光隐藏文字还不够,引线还在。更彻底的做法是在数据预处理阶段就把小占比项合并。我通常设一个阈值(比如 2%),低于阈值的全部归入"其他"项,这样既保证了图表可读性,又不丢失总量信息。合并逻辑放在数据处理的工具函数里,图表配置保持干净。

function mergeSmallSlices(data, threshold) { const total = data.reduce((sum, item) => sum + item.value, 0); const main = []; let otherValue = 0; data.forEach(item => { if (item.value / total < threshold) { otherValue += item.value; } else { main.push(item); } }); if (otherValue > 0) { main.push({ name: '其他', value: otherValue }); } return main; }

这个函数看着简单,但阈值的选择有讲究。阈值太低(比如 0.5%)合并效果不明显,阈值太高(比如 5%)又会把本该单独展示的项吞掉。我的经验是:数据项总数在 8 个以内时阈值设 1%,超过 8 个时设 2% 到 3%,具体还得看业务方对"其他"这个聚合项的接受度。

4.3 标签位置的内外切换与视觉平衡

环形图的标签可以放在环外侧(默认)或环内侧。外侧标签空间大、可读性好,但引线长、占地方;内侧标签紧凑,但环宽不够时文字会溢出。切换方式是label.position设成'outside'或'inside'。

内侧标签适合环比较宽(环宽占外径 35% 以上)、数据项少(5 个以内)的场景。文字颜色要跟扇区颜色形成对比,浅色扇区配深色字,深色扇区配浅色字,这个得手动配或者用回调函数按数据项设置。外侧标签则是通用方案,配合前面说的引线调参,大部分场景都能应付。

5. 交互与联动:hover 高亮、图例点击与多环协同

环形图不是画完就完事的静态图,实际项目里几乎都会涉及交互:鼠标悬浮高亮、点击图例筛选、多个环之间联动。这些交互 ECharts 大部分开箱即用,但默认行为未必符合业务预期,需要针对性调整。

5.1 emphasis 高亮的缩放与样式定制

默认情况下,鼠标悬浮扇区时会有个轻微的放大效果,这是emphasis.scale在起作用,默认scale: true、scaleSize: 10。放大本身没问题,但环形图放大后扇区会往外扩,可能盖住旁边的标签或者超出容器。我的习惯是把scaleSize调小到 5 左右,或者干脆关掉缩放,改用颜色加深来体现高亮。

series: [{ type: 'pie', radius: ['45%', '70%'], emphasis: { scale: true, scaleSize: 6, itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0, 0, 0, 0.2)' }, label: { show: true, fontWeight: 'bold' } }, data: [/* ... */] }]

加个阴影比单纯放大更有质感,而且不会改变扇区的几何尺寸,布局更稳定。shadowBlur别设太大,10 左右刚好,太大整个图会显得脏。

5.2 图例点击的默认筛选行为与自定义

ECharts 的图例点击默认是"切换该数据项的显示/隐藏",这个行为在环形图上有个副作用:隐藏某项后,剩余扇区会重新计算角度,整个环的布局会跳一下。如果业务希望点击图例只是高亮而不隐藏,需要改legend.selectedMode。

legend: { orient: 'vertical', left: 'left', top: 'center', selectedMode: 'single', // 单选模式,点击只高亮不隐藏 textStyle: { fontSize: 12 } }

selectedMode有三个值:true(默认,多选切换显示)、'single'(单选高亮)、false(禁用点击)。选'single'时,点击图例会高亮对应扇区,其他扇区变淡,但不会消失,布局稳定。这个模式在数据大屏上特别实用,因为大屏的布局是固定的,扇区跳动会显得很突兀。

5.3 多环嵌套时的数据对齐与联动

有些场景需要画同心多环,比如内环是品类占比、外环是各品类下的子类占比。这种图的关键是内外环的数据顺序必须对齐,否则颜色和扇区对不上,看起来会很乱。ECharts 本身不强制对齐,得靠数据预处理保证。

option = { series: [ { type: 'pie', radius: ['30%', '45%'], label: { position: 'inner', fontSize: 11 }, data: [ { value: 300, name: '品类A' }, { value: 200, name: '品类B' }, { value: 150, name: '品类C' } ] }, { type: 'pie', radius: ['50%', '65%'], label: { position: 'outside' }, data: [ { value: 120, name: 'A-1' }, { value: 100, name: 'A-2' }, { value: 80, name: 'A-3' }, { value: 200, name: 'B-1' }, { value: 150, name: 'C-1' } ] } ] };

外环的数据要按内环的顺序分组排列,并且每个内环项对应的外环子项之和应该等于内环值(或者至少视觉上能对应)。如果内外环数据量差异大,外环扇区角度和内环对不上,就得考虑用startAngle统一起始角度,让两个环从同一个位置开始画。startAngle默认是 90 度(12 点钟方向),多环场景下统一设成同一个值,对齐效果会好很多。

注意:多环嵌套时,legend的数据默认取第一个 series 的,如果内外环名称不同,图例会只显示内环的。需要手动配legend.data把两环的名称都列上,或者给每个 series 单独配图例(ECharts 5 支持 series 级 legend,但配置较复杂)。

6. 配色与视觉细节:让环形图不土的关键

同样一个环形图,配色和细节处理到位,观感能差出好几个档次。这块没有绝对标准,但有些原则和参数能帮你避开"一眼廉价"的坑。

6.1 调色板的选取与业务语义绑定

ECharts 默认调色板是那套经典的蓝绿黄橙红,用多了确实审美疲劳。自定义color数组是最直接的改法,但颜色不能随便挑,最好跟业务语义挂钩。比如渠道类数据,线上用蓝色系、线下用橙色系、合作方用绿色系,用户扫一眼就能建立颜色和类别的对应关系。

option = { color: ['#5470c6', '#fac858', '#91cc75', '#ee6666', '#73c0de'], series: [/* ... */] };

选色时注意两点:一是相邻扇区的颜色对比度要够,别把两个相近的蓝色放一起;二是整体色调要统一,要么全用低饱和的莫兰迪色系,要么全用高饱和的活力色系,混搭容易翻车。我一般从现成的配色方案里挑,比如按色相均匀分布生成,或者直接参考成熟设计系统的色板。

6.2 环心留白与整体呼吸感

环形图相比饼图的最大优势就是中间那块留白,它让图表有了"呼吸感"。但留白不是越大越好,内径太小环太粗显得笨重,内径太大环太细又显得单薄。前面说过环宽占外径 25% 到 40% 比较合适,这里补充一个视觉经验:内径占外径的比例在 55% 到 70% 之间时,环心放文字最舒服。内径太小,文字挤;内径太大,环太细,文字显得空。

另外,环心如果放数字,数字的字号要跟环的尺寸匹配。环大数字大,环小数字小,比例失调会很难看。我通常按外径像素值的 0.25 到 0.3 倍来定主数字字号,副标题字号取主数字的 0.45 倍左右,这个比例在大多数尺寸下都协调。

6.3 动画与加载体验的平衡

ECharts 环形图默认带一个展开动画,数据加载时扇区从 0 度扫出来,观感不错。但如果图表在页面里是次要元素,或者页面本身加载就慢,这个动画反而拖节奏。可以通过animationDuration和animationEasing控制。

series: [{ type: 'pie', radius: ['45%', '70%'], animationDuration: 800, animationEasing: 'cubicOut', data: [/* ... */] }]

animationDuration设 600 到 1000 毫秒比较自然,太短显得仓促,太长用户等得不耐烦。animationEasing用cubicOut或quarticOut收尾更利落。如果页面里有多个图表同时加载,建议错开动画起始时间,或者统一关掉动画(animation: false),避免视觉上乱成一团。

7. 响应式与性能:容器变化和大数据量下的处理

环形图在真实项目里很少是固定尺寸的,容器会随窗口缩放、侧边栏折叠而变化。数据量也可能从几条涨到几十条。这两块处理不好,图表要么变形,要么卡顿。

7.1 resize 的正确姿势与容器监听

ECharts 实例不会自动跟随容器尺寸变化,必须手动调resize()。最基础的做法是监听窗口的resize事件,但窗口不变、容器单独变化(比如侧边栏展开收起)时,窗口事件不触发,图表就会错位。稳妥方案是用ResizeObserver监听容器本身。

const chart = echarts.init(document.getElementById('chart')); const observer = new ResizeObserver(() => { chart.resize(); }); observer.observe(document.getElementById('chart'));

用ResizeObserver要注意两点:一是回调里最好加个防抖,容器连续变化时避免频繁重绘;二是组件销毁时记得observer.disconnect(),否则会内存泄漏。如果项目环境不支持ResizeObserver,退而求其次监听窗口事件,但要在容器样式变化的地方手动补调resize()。

7.2 大数据量下的取舍:合并、降采样与关闭动画

环形图的数据项超过 20 个时,扇区细得几乎点不中,标签更是没法看。这时候与其硬画,不如做数据聚合。前面提过的合并小占比项是基础操作,更激进的做法是按业务维度做二级聚合,比如把几十个细分类目归到几个大类,环形图只展示大类,点击大类再下钻看明细。

性能方面,环形图本身不算重,但数据项多、标签多、还开了动画和阴影时,渲染压力会明显上升。几个优化点:数据项超过 15 个时关掉animation;标签用label.show: false或者只显示部分;emphasis的阴影效果在大数据量下可以去掉。这些取舍没有统一标准,得根据实际帧率和用户设备来定,但原则是先保证可读性,再谈视觉效果。

提示:调试性能时可以用浏览器开发者工具的性能面板录一段交互,看渲染耗时集中在哪。环形图的瓶颈通常在标签布局计算上,减少标签数量往往比调其他参数更有效。

8. 几个反复踩到的具体坑与解法

前面按主题讲了配置和原理,这里集中列几个我在项目里反复遇到的具体问题,都是那种"不看不知道、一看就明白"的类型,希望能帮你少走弯路。

8.1 数据全为 0 或空数组时的图表异常

当所有数据项的value都是 0,或者data是空数组时,ECharts 环形图会画出一个完整的圆环(因为角度分配时除零处理),或者干脆空白一片,取决于版本。这在数据加载失败或者初始状态时很常见,用户看到个莫名其妙的圆环会困惑。

解法是在渲染前做数据校验,空数据时用graphic画个"暂无数据"的占位,或者给series配一个兜底的假数据项(透明色、值为 1),让图表至少有个正常形态。我一般封装一个renderPieChart(data)的工具函数,内部先判断data.length和总和,再决定走正常渲染还是空状态。

8.2 数值精度导致的百分比加和不等于 100%

环形图的百分比是 ECharts 内部按数值比例算的,浮点数运算下,几个扇区的百分比加起来可能是 99.9% 或 100.1%。如果业务方要求标签上的百分比必须严格加和 100%,就得自己算百分比并处理尾差。

常见做法是算出各项百分比后,把尾差补到最大项或者"其他"项上。比如三项分别是 33.33%、33.33%、33.34%,显示时保留一位小数变成 33.3%、33.3%、33.3%,加起来 99.9%,就把最后一项调成 33.4%。这个逻辑放在数据预处理里,图表配置里直接用算好的百分比,不走 ECharts 的{d}占位符。

8.3 tooltip 在小扇区上的触发困难

扇区角度很小时,鼠标很难精确悬停上去,tooltip 出不来。ECharts 的 tooltip 触发依赖鼠标位置落在图形上,小扇区天然吃亏。缓解办法是给series加tooltip.trigger: 'item'(默认就是),然后适当增大emphasis.scaleSize,让悬浮时扇区放大一点,更容易命中。但根本解法还是前面说的数据聚合,把碎片数据合并掉。

8.4 图例过多时的换行与滚动

数据项多的时候,图例一行放不下,默认会换行,但换行后可能挤压图表区域。如果图例在底部,可以配legend.type: 'scroll'开启滚动,或者限制legend.width让它内部滚动。图例在侧边时,用orient: 'vertical'配合固定高度,超出部分滚动。这些配置不难,但容易被忽略,导致图例和图表打架。

legend: { type: 'scroll', orient: 'horizontal', bottom: 0, width: '80%', pageIconColor: '#666', pageTextStyle: { color: '#999' } }

type: 'scroll'会显示翻页箭头,交互上比换行更干净,尤其适合数据项动态变化的场景。

环形图这个主题,配置项翻来覆去就那么些,但真正决定成品质量的,是对半径体系的理解、对标签布局的耐心调参、以及对业务场景的判断。我个人的体会是,先把radius和center这两个几何参数定死,再往里填数据和样式,返工率会低很多;标签重叠的问题,八成靠数据聚合解决,剩下两成靠引线参数微调;至于配色和动画,属于锦上添花,别在功能没跑通之前就纠结这些。最后再分享一个小技巧:调试环形图时,把label和labelLine先全关掉,只看扇区比例和颜色,确认数据映射没问题了,再逐步打开标签和引线,这样排查问题会清晰很多。