ECharts饼图中心文字添加全攻略:三种方案详解与最佳实践

📅 2026/8/18 4:04:33
ECharts饼图中心文字添加全攻略:三种方案详解与最佳实践
1. 项目概述为什么要在饼图中间加文字如果你用过ECharts画饼图大概率会遇到一个很实际的需求怎么在饼图那个圆圆的“洞”里放上一些说明性的文字比如你想在中间显示这个饼图的总数、一个总结性的标题或者一句关键的结论。这个需求太常见了但ECharts的官方配置项散落在好几个地方新手很容易配得不对要么文字不显示要么位置跑偏要么样式丑得没法看。我自己在项目里就踩过不少坑。最早我以为用title配置就能轻松搞定结果发现title默认是悬浮在整个图表上方的很难精准地怼到饼图正中心。后来折腾series.label发现它主要是用来标注每个扇区的想在中心区域独立显示内容得用series.label.normal.position: center配合富文本但这样又会和扇区标签打架管理起来很麻烦。直到我把graphic组件、series的center坐标以及title的left和top属性玩明白了才算真正找到了稳定、美观的解决方案。这篇文章我就把自己趟出来的路结合几个最实用的场景给你彻底讲清楚。不管你是想加个静态标题还是放动态计算的总和甚至是搞点交互效果都能在这里找到可以直接“抄作业”的代码。我们不止讲“怎么做”更重点讲“为什么这么做”以及不同方案之间的取舍。毕竟能跑通的代码网上很多但知道为什么这么写、以及怎么写得更好才是咱们进阶的关键。2. 核心思路拆解不止一种方法关键看场景给ECharts饼图中心加文字听起来是个单一功能但背后对应着不同的使用场景和实现思路。选错了方法要么实现起来特别拧巴要么后期维护成本陡增。咱们先抛开代码从设计层面看看这几种主流方案的底层逻辑和适用边界。2.1 方案一使用title组件进行定位这是最容易被想到也最容易被用错的方法。ECharts的title组件设计初衷是用于图表的整体标题比如“2023年公司销售占比”。它的定位系统默认是基于整个Canvas画布的百分比或像素值。如果你简单设置text: 总计它默认会出现在画布顶部居中位置。那么怎么把它“按”到饼图中心呢核心在于两点联动计算确定饼图的圆心坐标通过series.pie.center来设置比如[50%, 50%]就是画布正中心。将title的定位对齐到这个圆心设置title.left和title.top也为50%同时将title.textAlign和title.textVerticalAlign都设置为center。这样title文本的几何中心点就会和饼图的圆心重合。这个方案的优点很明显配置相对直观复用现有组件并且title组件本身支持丰富的样式字体、颜色、边框、背景等和副标题subtext功能非常适合展示如“销售额总计\n1,234万元”这类多行、带格式的静态或半静态文本。但它有个致命的缺点title是全局组件。当一个页面有多个饼图实例时你需要为每个饼图单独计算并设置其title的位置如果饼图位置或大小发生动态变化比如响应式布局你需要用JavaScript重新计算并更新title的left和top值否则文字就会错位。这增加了状态管理的复杂度。2.2 方案二使用graphic组件绘制文本graphic是ECharts的图形组件你可以把它理解为一个底层绘图API的封装可以在图表任意位置绘制基础图形矩形、圆形或文字。用它在饼图中心加文字思路非常直接以饼图圆心为坐标画一个文字元素。具体操作在option.graphic数组里配置一个type: text的元素。其left和top同样需要设置为饼图的圆心坐标值例如50%。更重要的是你需要设置style.textAlign和style.textVerticalAlign为center来实现居中。这个方案的灵活性是最大的。因为它是一个独立的图形元素你可以为它添加交互事件onclick可以很容易地让它跟随饼图动画通过关联animation配置甚至可以用它绘制更复杂的背景或装饰图标。比如我做过一个需求要在文字周围加一个淡淡的圆形背景用graphic组合一个circle和一个text元素就轻松实现了。但它的缺点在于配置稍显繁琐。所有样式都需要在graphic.style里详细定义不像title有一些预设的样式模板。而且当文本内容需要根据数据动态更新时比如显示实时总和你需要手动获取graphic组件实例并更新其style.text属性不如series.label那样与数据天然绑定。2.3 方案三使用series.label的position: center这个方案非常巧妙它利用了饼图系列series自身的标签label系统。series.label默认行为是在每个扇区外部或内部显示该扇区的数据标签。但当你把position设置为center时标签就会渲染在整个饼图的中心位置。它的最大优势是“数据驱动”。因为它是series的一个配置项所以它天生可以访问到系列的数据。你可以通过formatter函数动态地计算并显示所有扇区值的总和。例如formatter: function(params) { const total params.data.reduce((sum, item) sum item.value, 0); return 总计\n${total}; }数据一更新中间的文字自动就变了几乎不需要额外的维护代码。这对于需要实时反映数据变化的仪表盘场景来说是最高效的方案。然而这个方案有一个重要的限制根据我的实测和官方文档的隐含说明当position设为center时这个label实际上会为数据的第一项即data[0]在中心位置显示标签。这意味着formatter里虽然能拿到全部数据params.data但它的触发和绑定机制依然和某个数据项关联。在极端情况下如果data数组为空或第一项数据被过滤可能会导致中心标签不显示。你需要确保逻辑的健壮性。2.4 方案对比与选型建议为了让你一目了然我把这三个核心方案的关键差异整理成了表格特性维度title组件方案graphic组件方案series.label中心方案本质全局图表标题底层自定义图形数据系列的标签定位方式基于画布百分比/像素基于画布百分比/像素基于饼图系列中心数据绑定弱需外部计算无需手动更新强可访问系列全部数据动态更新难度中需重算位置中需手动更新文本低自动随数据更新样式丰富度高支持副标题、边框、背景等极高可自由绘制任何图形中支持富文本但限于标签样式多饼图支持差每个需独立计算好每个独立配置好每个系列独立交互支持中支持事件高支持丰富事件与动画低与数据项事件可能冲突适用场景静态标题、多行格式化文本复杂自定义图形、交互式元素、带背景的文本动态显示数据总和、与数据强关联的文本我的个人选型心得是追求简单和静态展示用title方案省心样式好看。需要高度定制和交互用graphic方案你就是设计师想怎么画就怎么画。文本内容随数据实时变化尤其是显示总和用series.label方案这是最“ECharts”的方式数据变文字自动变维护成本最低。在接下来的实操部分我会为每种方案都提供一个完整、可运行的代码示例并附上详细的参数讲解和效果截图你可以根据自己的需求直接选用或组合使用。3. 三种方案的完整实现与参数详解光说不练假把式下面我们直接上代码把三种方案从基础到进阶一个个拆解清楚。我会假设一个场景展示一个公司各部门的年度预算占比饼图并在中心显示“年度预算”标题和总金额。3.1 方案一使用title组件精确定位首先我们构建一个基础的饼图。// 基础饼图配置 const baseOption { tooltip: { trigger: item, formatter: {a} br/{b}: {c} ({d}%) }, legend: { orient: vertical, left: left }, series: [ { name: 部门预算, type: pie, radius: [40%, 70%], // 空心饼图内半径40%外半径70% center: [50%, 50%], // 饼图圆心位于画布中心 data: [ { value: 1048, name: 研发部 }, { value: 735, name: 市场部 }, { value: 580, name: 销售部 }, { value: 484, name: 行政部 }, { value: 300, name: 财务部 } ], emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: rgba(0, 0, 0, 0.5) } } } ] };现在我们要把“年度预算”和总金额放到中间。关键就在于title配置const optionWithTitle { ...baseOption, title: { text: 年度预算, // 主标题 subtext: 总计3147万元, // 副标题 left: 50%, // 水平定位到50%处 top: 50%, // 垂直定位到50%处 textAlign: center, // 文本水平居中 textVerticalAlign: middle, // 文本垂直居中 // 样式细化 textStyle: { fontSize: 18, fontWeight: bold, color: #333 }, subtextStyle: { fontSize: 14, color: #999, fontWeight: normal }, // 可选添加一个淡淡的背景框增强可读性 backgroundColor: rgba(255,255,255,0.8), borderColor: #eee, borderWidth: 1, borderRadius: 4, padding: [10, 15] // [上下左右]内边距 } };几个必须注意的细节left和top的50%指的是title组件自身包围盒的中心点对准画布的50%位置。textAlign和textVerticalAlign的center和middle是让文本在这个包围盒内居中。两者结合才实现了文本内容与饼图圆心的精确对准。计算总金额“3147”是手动算的。在实际项目中你应该用JavaScript动态计算data中所有value的和然后赋值给subtext。padding背景框的内边距很重要不加的话文字会紧贴边框很丑。根据字体大小调整padding值。如果饼图的center不是[50%, 50%]比如你做了双饼图布局那么title的left和top也必须同步调整。例如饼图center是[30%, 50%]那么title的left也必须是30%。3.2 方案二使用graphic组件自由绘制graphic方案给了我们像素级的控制权。我们来实现一个带圆形背景的居中文本。const optionWithGraphic { ...baseOption, // 沿用上面的基础配置 graphic: { // graphic 可以包含多个元素放在一个数组里 elements: [ { type: circle, // 先画一个圆形背景 left: center, // 水平居中等同于 50% top: center, // 垂直居中 shape: { r: 60 }, // 圆的半径根据你的文本长度调整 style: { fill: rgba(255,255,255,0.9), // 填充色 stroke: #5470c6, // 描边色 lineWidth: 2 }, // 让这个圆在饼图“之上”zlevel控制层级 z: 100 }, { type: text, // 再画文字 left: center, top: center, style: { text: 年度预算\n3147万元, // 支持换行符 \n font: bold 16px Microsoft YaHei, // 字体设置语法同CSS fill: #333, // 文字颜色 align: center, // 文字水平居中 verticalAlign: middle, // 文字垂直居中 lineHeight: 24 // 行高用于多行文本间距 }, // 文字层要在圆形背景之上 z: 101 } ] } };graphic方案的核心技巧层级 (z/zlevel)graphic元素默认绘制在图表系列之上。如果你希望背景圆在饼图下面可以把它的z值设小比如0或者调整zlevel。通过z值可以轻松管理多个图形元素的叠放次序。坐标与定位left: center和top: center是便捷写法。你也可以用像素值100或百分比50%。更强大的是你可以使用[x, y]数组形式进行绝对定位或者使用[50%, 50%]这样的百分比数组。动态更新如果需要更新文字你需要获取ECharts实例然后更新对应配置。const myChart echarts.init(dom); // 假设要更新文字内容 const newText 年度预算\n${newTotal}万元; // 找到 graphic 中 type 为 text 的元素更新其 style.text myChart.setOption({ graphic: { elements: [/* 保留circle配置 */, { type: text, style: { text: newText } }] } }); // 注意setOption 会合并选项。更稳妥的做法是获取当前option修改后重新setOption3.3 方案三使用series.label实现数据驱动这是我最推荐用于显示动态总和的方案。配置直接写在series里与数据融为一体。const optionWithSeriesLabel { tooltip: { trigger: item, formatter: {a} br/{b}: {c} ({d}%) }, legend: { orient: vertical, left: left }, series: [ { name: 部门预算, type: pie, radius: [40%, 70%], center: [50%, 50%], data: [ { value: 1048, name: 研发部 }, { value: 735, name: 市场部 }, { value: 580, name: 销售部 }, { value: 484, name: 行政部 }, { value: 300, name: 财务部 } ], // 核心标签配置 label: { // 正常情况下的标签样式扇区上的标签 normal: { show: true, formatter: {b}: {d}% // 显示名称和百分比 }, // 重点中心位置的标签配置 emphasis: { // 注意这里用 emphasis 作用域也可以放在 normal 里但用 emphasis 更符合语义 show: true, position: center, fontSize: 18, fontWeight: bold, formatter: function (params) { // params 是一个对象params.data 是当前系列的所有数据 // 注意在 position: center 时params 通常指向第一个数据项但我们可以通过其所属系列访问全部数据 const seriesData params.data; // 这里就是 series.data 数组 const total seriesData.reduce((sum, item) sum item.value, 0); // 使用富文本样式 return {title|年度预算}\n{value|${total}万元}; }, rich: { title: { fontSize: 16, color: #333, lineHeight: 20 }, value: { fontSize: 20, color: #5470c6, fontWeight: bolder, lineHeight: 24 } } } }, // 将鼠标悬浮emphasis时的状态与中心标签关联 emphasis: { label: { show: true // 确保在鼠标悬浮时中心标签的 emphasis 配置生效 } } } ] };这个方案的注意事项和高级用法formatter函数的参数params在position: center时params对象通常代表饼图第一个数据项。但幸运的是params.data在这个上下文里指向的是整个series.data数组。这是一个关键点让你能计算总和。触发时机上述配置将中心标签放在了label.emphasis下这意味着默认状态下中心文字不显示只有当鼠标悬浮到该饼图系列上时才会显示。如果你希望一直显示有两个方法方法A将position: center等相关配置移到label.normal下。但这样会和扇区本身的标签配置冲突可能需要用show: false关闭扇区标签。方法B推荐保持上述配置但再添加一个永远显示的graphic或title来放静态标题用这个series.label专用于显示动态总和。这样交互感更好平时只看到标题鼠标放上去才看到具体总和。富文本richformatter里可以用{styleName|text}的语法并在rich里定义styleName的样式。这能轻松实现同一段文字内不同样式比如让数字更大、颜色更突出。4. 常见问题、避坑指南与性能优化在实际开发中把功能跑通只是第一步让它在各种边界情况下稳定、美观、高性能地工作才是体现经验的地方。下面是我总结的几个典型问题和优化技巧。4.1 文字不显示或位置不对逐项排查清单文字完全看不见检查容器宽高ECharts需要显式设置容器的width和height。如果容器尺寸为0图表无法渲染文字自然不显示。确保你的div idchart/div有确定的尺寸通过CSS设置或内联样式。检查层级z/zlevel如果使用了graphic并且设置了z值过小可能被其他图形元素如饼图扇区遮挡。尝试调大z值如设为1000。检查颜色文字颜色color/fill是否与背景色相同比如白色文字放在白色背景上。给文字加个textShadow或背景色 (backgroundColor/fill) 可以避免这个问题。检查show属性无论是title.show、graphic.elements[i].ignore为false才显示还是label.show都要确保是true。文字位置偏离中心坐标基准不一致这是最常见的原因。饼图的center是[50%, 50%]但title的left设成了center。在大多数情况下center等同于50%但为了绝对一致建议统一使用50%这种百分比字符串或相同的像素值。对齐属性未设置title必须同时设置textAlign: center和textVerticalAlign: middle。graphic的text必须设置style.align: center和style.verticalAlign: middle。缺一不可。饼图不是标准圆形如果你的饼图半径是[30%, 80%]这种环形或者radius是一个单一值圆心计算是准确的。但如果你的饼图是“南丁格尔玫瑰图”roseType: area它的形状并非标准圆视觉中心可能和几何中心有细微偏差这种情况需要微调left/top值。4.2 多饼图旭日图、嵌套饼图的中心文字处理当页面有多个饼图或者使用旭日图sunburst时每个系列都有自己的中心。多个独立饼图为每个series配置独立的center如[30%, 50%]和[70%, 50%]。然后为每个饼图对应的中心文字你需要如果使用title配置多个title对象数组并分别指定各自的left/top为对应的圆心坐标。如果使用graphic在elements数组中为每个饼图添加一组circle和text元素并分别指定坐标。如果使用series.label这是最简单的因为配置是写在每个series内部的它会自动跟随自己的圆心。只需在每个series的label中配置position: center即可。旭日图Sunburst旭日图是多层级的。它的中心文字通常指的是最内层。你可以使用series.label的position: inside并配合rotate: radial等实现扇区内标签。如果要在整个旭日图正中心加文字graphic方案是最灵活的你可以计算最外层矩形的中心来定位。4.3 动态数据更新时的文字同步这是series.label方案的主场。// 假设初始图表配置为 optionWithSeriesLabel使用label的方案 myChart.setOption(optionWithSeriesLabel); // 动态更新数据 function updateChartData(newData) { const newOption { series: [{ data: newData, label: { // 重新设置label的formatter计算新的总和 emphasis: { formatter: function(params) { const total params.data.reduce((sum, item) sum item.value, 0); return 总计\n${total}; } } } }] }; myChart.setOption(newOption); // ECharts 会智能合并选项并更新 }使用graphic或title方案时你需要手动更新对应的文本内容// 更新 graphic 中的文字 myChart.setOption({ graphic: { elements: [/* 其他图形 */, { type: text, style: { text: 新总计: ${newTotal} } }] } }); // 更新 title myChart.setOption({ title: { subtext: 新总计: ${newTotal} } });4.4 性能与体验优化要点避免过度使用graphicgraphic是底层绘制如果在一个页面中创建成百上千个复杂的graphic元素比如在数据量很大的关系图中可能会影响渲染性能。对于简单的中心文字三种方案性能差异不大。动画协调ECharts饼图有默认的入场动画。如果你使用graphic绘制中心文字并希望它和饼图一起淡入或放大可以配置graphic.elements.enterAnimation和animationDuration等属性让动画同步。响应式适配在窗口大小变化时window.onresize或使用ECharts的resize方法饼图会重绘并自适应。对于title和graphic方案由于其定位是相对于画布Canvas的百分比所以通常能自动适应无需额外处理。但如果你用了像素值px定位就需要在resize事件回调中重新计算并setOption。高清屏Retina适配ECharts 默认会对设备像素比window.devicePixelRatio进行适配以保证清晰度。你一般不需要为此特别处理中心文字。但如果你发现graphic中绘制的文字在某些屏幕上模糊可以检查是否在初始化图表时传入了正确的devicePixelRatio参数通常ECharts会自动检测。4.5 一个综合性的最佳实践示例最后分享一个我项目中常用的“组合拳”方案兼顾了静态美观和动态交互静态标题使用title组件一直显示在中心内容为“部门预算占比”样式精美。动态总和使用series.label的emphasis配置仅在鼠标悬停时在中心显示实时数据总和。 这样平时界面干净专业交互时又能提供关键数据用户体验非常好。const bestPracticeOption { title: { text: 部门预算占比, left: 50%, top: 50%, textAlign: center, textVerticalAlign: middle, textStyle: { fontSize: 16, color: #666 }, subtextStyle: { fontSize: 12, color: #999 } }, series: [{ type: pie, radius: [45%, 70%], center: [50%, 50%], data: [...], // 你的数据 label: { normal: { show: true, formatter: {b}: {d}%, position: outside }, emphasis: { show: true, position: center, fontSize: 20, fontWeight: bold, color: #5470c6, formatter: function(params) { const total params.data.reduce((s, i) s i.value, 0); return 总计\n${total}; } } }, emphasis: { // 触发emphasis状态 scale: false, // 可选关闭悬浮时扇区放大的效果避免视觉干扰 label: { show: true } } }] };这个方案把title的“常驻展示”和series.label的“数据驱动交互”结合了起来代码清晰效果出众是我经过多次迭代后认为最优雅的实现方式。希望这些详细的拆解和实战经验能帮你彻底掌握ECharts饼图中心文字这个“小”功能背后的“大”学问。