开头我得先吐个槽但凡在可视化项目里被甲方提过“这个饼图能不能立体一点”的人应该都经历过那种感觉——拿着原生 ECharts 的pie折腾半天阴影、描边、偏移全用了出来的东西还是“一张纸片”。我自己一开始也踩过这个坑后来换到echarts-gl的pie3D系列才算真正把饼图从“盘子”变成了“蛋糕”。这篇就来聊聊怎么用 ECharts 生态把 3D 饼图跑起来从环境引入、核心配置到实战调优适合正在做数据可视化大屏、BI 看板的朋友也适合刚接触 echarts-gl 的人拿来当一份能直接抄作业的参考。要说清楚的是ECharts 本体并没有 3D 饼图这个系列你得靠官方扩展库echarts-gl。这个库底层基于 WebGL 渲染能让你用类似 ECharts 配置项的语法写出 3D 柱状图、3D 散点图、3D 地球、3D 饼图等场景。它最大的好处是学习成本低——你不需要去写 three.js 那套复杂逻辑只要在series里把type改成pie3D再配上几个三维专属参数一个能转、能倾斜、带厚度和圆角的立体饼图就出来了。下面我从选型开始一步步聊。1. 先说清楚ECharts 原生饼图是“平的”3D 需要扩展库1.1 echarts-gl 到底补了什么能力原生 ECharts 的pie系列本质上是二维矢量绘制所有扇形都在一个平面上。你可以通过shadowBlur、shadowColor加阴影用borderRadius把扇形边缘弄圆甚至用padAngle在扇区之间留缝但它终究没有“厚度”也没有“纵深”。当页面里同时有多个图表或者大屏背景偏立体风格时这种饼图放在一堆 3D 柱状图中间会显得很单薄。echarts-gl解决的就是这个痛点。它把 ECharts 的 series 系列扩展到了三维空间pie3D这一类会把每个扇形数据在三维空间里“拉伸”成一个有厚度的扇墩。你可以理解成二维饼图是俯视一张披萨3D 饼图则是把披萨竖着堆起来侧面能看到饼的厚度。通过视角控制器viewControl你还能让用户从不同角度观察或者让图表自动缓慢旋转这种交互感是原生二维图表做不到的。1.2 真 3D 与伪 3D 的取舍在实际项目里我不建议一上来就无脑上真 3D先分清场景。如果你只是想要一点“立体感”比如让饼图看起来有个底边、有厚度那用原生 ECharts 做“伪 3D”完全够用底部垫一个灰色椭圆阴影层或者给扇形加粗边框和深色下边线视觉上也能骗过大部分人。这种方式兼容性好渲染性能高代码量也少。但如果你的页面本身大量采用三维元素或者你明确需要交互旋转、灯光变化、近距离透视这种效果那就直接上echarts-gl。我这里提醒一句真 3D 的渲染依赖 GPU对低端机或数据量大的场景会有性能压力项目交付前一定要在目标设备上实测。选型没有绝对的对错只有合不合适。2. 环境准备一条 CDN 和一组版本号解决 80% 的麻烦2.1 最省事的 CDN 引入方式先给想在本地快速跑个 demo 的朋友一条可行的 CDN 组合script srchttps://cdn.jsdelivr.net/npm/echarts4.9.0/dist/echarts.min.js/script script srchttps://cdn.jsdelivr.net/npm/echarts-gl2.0.9/dist/echarts-gl.min.js/script注意这里我特意用了 ECharts 4.9.0 而不是 5.x原因后面在“常见问题”里我会详细展开。简单说echarts-gl的官方版本停在 2.0.9 已经很久了它和 ECharts 5 有兼容性坑很多人装上之后图表黑屏就是这个原因。用 4.9.0 加 2.0.9 这套组合能少踩一半坑。引入顺序也有讲究必须是先echarts.min.js再echarts-gl.min.js顺序反了直接报ECharts was not found之类的错误。加载完之后你可以在控制台打echarts.gl如果能打印出对象说明扩展库已经被 ECharts 正确识别。2.2 npm 工程里怎么装如果你在 Webpack 或 Vite 工程里开发CDN 的写法就不太合适了尤其是后续要打包上线的情况。用 npm 安装npm install echarts4.9.0 echarts-gl2.0.9然后在入口文件里引入并注册import * as echarts from echarts; import echarts-gl;echarts-gl这个包被 import 之后会自动往 ECharts 上注册扩展系列所以不需要显式调用echarts.use()。很多人在这步会踩一个坑只安装了echarts-gl但项目里的 ECharts 是通过别名或者按需引入的方式用的导致echarts-gl注册到的是另一个 ECharts 实例最后图表就是不出来。我的经验是如果项目里 ECharts 版本不统一尽量保证全项目只有一个 ECharts 实例或者干脆用全局的window.echarts挂载方式避免扩展库注册不到目标实例上。2.3 Vue3/Vite 项目的特殊处理Vue3 项目还要额外注意一点尽量用 ECharts 4 还是 5如果你必须用 ECharts 5那echarts-gl的pie3D可能初始化时直接报错或者在渲染时黑屏。这种情况下一个替代思路是用原生 ECharts 5 画平面饼图再叠加graphic元素做假立体底托或者自己封装一个基于 three.js 的 3D 饼图组件。但如果你只是想要pie3D的效果最省心的还是把 ECharts 锁在 4.x。在 Vite 中引入时我建议这样写// vite.config.js 里别配置 echarts 相关 external否则容易加载顺序错乱 // main.js 或组件里 import * as echarts from echarts/core; import { Pie3DChart } from echarts-gl/charts; // 如果是按需引入需要确认路径 import echarts-gl;这里有个很现实的问题echarts-gl的按需引入支持并不完善它内部封装得比较厚很多版本对echarts/core这种按需模式不友好。我的建议是既然都上 3D 了就别追求按需引入了直接全量引入体积大点就大点稳定性优先。3. 核心配置解析把每个参数掰开揉碎3.1 一个能跑的最小 3D 饼图先放一个最简单的完整 HTML方便你复制到本地直接看效果!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleECharts 3D饼图/title script srchttps://cdn.jsdelivr.net/npm/echarts4.9.0/dist/echarts.min.js/script script srchttps://cdn.jsdelivr.net/npm/echarts-gl2.0.9/dist/echarts-gl.min.js/script style #chart { width: 720px; height: 520px; margin: 0 auto; } /style /head body div idchart/div script var chart echarts.init(document.getElementById(chart)); var option { tooltip: { trigger: item, formatter: function (params) { return params.name br/占比 params.percent %; } }, series: [{ type: pie3D, data: [ { name: 直接访问, value: 335, itemStyle: { color: #5470c6 } }, { name: 搜索引擎, value: 310, itemStyle: { color: #91cc75 } }, { name: 邮件营销, value: 234, itemStyle: { color: #fac858 } }, { name: 联盟广告, value: 135, itemStyle: { color: #ee6666 } }, { name: 视频广告, value: 148, itemStyle: { color: #73c0de } } ], height: 30, bevelSize: 6, bevelSmoothness: 3, radius: 80, viewControl: { alpha: 40, beta: 0, distance: 200 }, label: { show: true, formatter: function (params) { return params.name \n params.percent %; } } }] }; chart.setOption(option); /script /body /html把这段存成.html文件浏览器打开你就能看到一个带厚度的彩色 3D 饼图。它默认有三个自由度绕 X 轴倾斜的alpha绕 Y 轴旋转的beta以及视距distance。你可以按住鼠标拖拽也可以滚动滚轮放大缩小交互是内置的不用额外写任何事件代码。这里有个细节alpha是俯仰角40 代表从上方往下倾斜 40 度观察这个角度越大你越能看到饼图的“顶面”beta是水平旋转角0 代表正面朝向distance是相机距离模型的距离数值越大模型在视野里越小。这三个参数配合起来决定了用户第一眼看到的构图。3.2 height、radius、bevel 系列厚度、大小和圆角height控制整个饼图的厚度单位是像素更准确说是三维世界坐标里的距离默认值好像是 20 左右。这个值越大侧面的“蛋糕边”越厚立体感越强但也不是越大越好因为太厚会遮挡顶面的数据标签而且在大屏上会显得笨重。我一般把 height 控制在20~50之间具体得看图表尺寸和数据量。radius控制饼图底面半径支持两种写法数字80或字符串70%。百分比是相对于容器宽度和高度中的较小值和原生饼图的逻辑类似。用百分比的好处是响应式适配时不容易被容器尺寸变化影响但我实际用下来觉得在大屏固定宽度场景里直接用数字更可控。bevelSize和bevelSmoothness这对参数很关键。bevelSize决定顶面和底面边缘的圆角过渡大小bevelSmoothness决定圆角过渡的平滑程度。你可以这样理解没有 bevel 时每个扇区像刀切的豆腐块棱角分明加了 bevel 之后边缘被倒角磨圆观感立刻从“工程图”变成“产品渲染图”。我习惯把bevelSize设为4~8bevelSmoothness设为2~4数值再大反而会让扇区轮廓失真。3.3 viewControl视角、旋转、自动旋转viewControl是 3D 图表交互的核心它控制的不是数据而是“相机”。常用配置有viewControl: { alpha: 40, // 俯仰角 beta: 0, // 水平旋转角 distance: 200, // 视距 autoRotate: true, // 自动旋转 autoRotateSpeed: 10,// 自动旋转速度 center: [0, 0, 0] // 观察中心 }如果你想让饼图在页面加载后自动缓慢旋转把autoRotate设为true再调整autoRotateSpeed。这个效果非常适合大屏场景因为静态饼图放在动态大屏里会显得“死气沉沉”自动旋转能带来一种“系统正在实时运行”的感觉。需要注意自动旋转开启后用户手动拖拽图表不会取消自动旋转想要停止得重新配置autoRotate: false并setOption这是很多新手容易忽略的交互细节。还有几个进阶参数值得记一下minAlpha和maxAlpha限制俯仰角的旋转范围默认是 0 到 90 度防止用户把视角拖到饼图正下方看到底面破绽minBeta和maxBeta限制水平旋转范围我一般设置minBeta: -40, maxBeta: 40让用户只能在正面半圈内观察避免视觉混乱。3.4 label 与 tooltip标签和提示框的细节3D 饼图的label和原生饼图不太一样。pie3D的标签默认显示在扇区顶面上如果扇区太小或者视角比较平标签会相互压在一起。我常用的两个处理办法第一用minShowLabelAngle参数这是pie3D特有的表示当扇区角度小于该值时跳过标签显示。比如设置minShowLabelAngle: 10那些占比太小、标签放不下的扇区就不会强行显示文字避免视觉污染。第二label.formatter用函数不要用字符串模板。pie3D的 label 对{b}\n{d}%这类模板的支持不太稳定我在 2.0.9 版本里遇到过只显示{b}不换行的情况后来统一改成函数式就稳定了label: { show: true, formatter: function (params) { return params.name \n params.percent %; }, textStyle: { color: #fff, fontSize: 12 } }tooltip这块默认trigger: item就能用但params.percent需要你自己算。以pie3D的行为来看params.value是原始值我们可以这样计算占比tooltip: { trigger: item, formatter: function (params) { var total 0; // 这里用 option.series[0].data 累加或者在外面缓存 return params.name br/数值 params.value br/占比 params.percent %; } }params.percent在某些版本里可能拿不到保险做法是自己算。另外一个容易踩的坑是如果 data 项里没有给每个扇区单独设置nametooltip 的params.name可能显示为空所以数据源里尽量把 name 写全。3.5 颜色、光照与视觉增强pie3D的颜色配置支持两种层级series 层级统一设置data 项里单独覆盖。比如我想让某个扇区高亮直接在对应数据项里写{ name: 核心用户, value: 500, itemStyle: { color: #ff8800 } }颜色选得好不好直接影响 3D 观感。二维饼图的配色只考虑相邻扇区区分度三维还要考虑光照下的明暗变化。echarts-gl默认有环境光和方向光如果你用了浅色系比如#ffffff或#fafafa可能会导致整个模型过曝侧面一片白。我的习惯是不要用纯白作为扇区颜色偏冷色或中等饱和度的颜色在默认光照下表现最好。如果你对光照效果不满意还可以通过environment设置环境贴图或者引入Light组件调整光源方向和强度。但坦白讲pie3D对自定义光源的支持文档比较少实际配置起来比较费劲我建议大多数场景直接用默认光照只通过调整颜色明度来匹配视觉风格。4. 实操案例从静态图到能交差的大屏动效4.1 完整 HTML 案例上面那个 demo 只是跑通流程真正到项目里还需要做几件事给每个扇区加上更合理的配色增加一个“总规模”的视觉强调以及把 tooltip 做得更精致。我整理了一份更接近生产环境的配置var colorList [ #37c2ff, #50d488, #ffc24b, #ff6b6b, #a876f8, #36d6a9, #ff9f68, #4a9eff ]; var data [ { name: 北京, value: 520 }, { name: 上海, value: 430 }, { name: 广州, value: 260 }, { name: 深圳, value: 310 }, { name: 杭州, value: 180 }, { name: 成都, value: 150 }, { name: 武汉, value: 120 }, { name: 西安, value: 90 } ]; data.forEach(function (item, index) { item.itemStyle { color: colorList[index % colorList.length] }; }); var option { tooltip: { trigger: item, backgroundColor: rgba(0,0,0,0.7), borderColor: #37c2ff, textStyle: { color: #fff }, formatter: function (params) { return b params.name /bbr/总量 params.value br/占比 params.percent %; } }, series: [{ type: pie3D, data: data, height: 36, bevelSize: 8, bevelSmoothness: 4, radius: 90, minShowLabelAngle: 12, viewControl: { alpha: 35, beta: 0, distance: 220, autoRotate: true, autoRotateSpeed: 8, minAlpha: 20, maxAlpha: 60, minBeta: -30, maxBeta: 30 }, label: { show: true, formatter: function (params) { return params.name \n params.percent %; }, textStyle: { color: #e8e8e8, fontSize: 13 } } }] }; chart.setOption(option);这份配置比较适合城市的占比类数据展示。我特意把minShowLabelAngle设成 12那些占比很小的城市标签不会挤在一起自动旋转速度设成 8大屏上节奏刚刚好——太快显得焦躁太慢又感觉不到在转。4.2 让饼图“活”起来静态 3D 饼图其实还不够“活”。我在项目里常用的两个增强手法一个是加数据加载动画另一个是点击联动其他图表。数据加载动画方面pie3D自带进场动画你可以通过animationDurationUpdate和animationDuration控制时长。比如想让图表加载时从“扁饼”变“厚饼”可以用这种组合series: [{ type: pie3D, height: 0, animationDurationUpdate: 1200, animationDuration: 1200 }]先把初始height设为 0等setOption后再把height改回 36ECharts 会自动做差值动画看起来就像饼图被“挤”出来一样。这个效果虽然简单但在大屏开场时相当抓眼球。点击联动方面pie3D也是支持click事件绑定的chart.on(click, function (params) { // params.name 就是被点击的扇区名称 // 可以在这里联动刷新旁边的柱状图、折线图 console.log(params.name, params.value); });如果你有一个包含多个图表的页面点击某个城市扇区右侧联动显示该城市的排名和趋势整个页面的叙事感会强很多。这类组件间通信代码不多但设计好了能让大屏从“数据堆砌”变成“有故事可讲”。4.3 构建一个沉浸式大屏场景到了大屏这个层级单张 3D 饼图本身不是难点难点在于整套页面的视觉融合。我总结几个从实际项目里摸出来的经验一是背景色系统一。大屏背景通常非常暗比如深蓝、纯黑或科技感渐变色你的 3D 饼图如果保持默认的浅色背景会像一个“白色补丁”。用echarts.init的backgroundColor参数把整个图表背景设为透明再在大屏 CSS 层做背景设计这样 3D 模型的明暗过渡才能自然融入页面。二是字体颜色平衡。3D 模型的标签默认是白色半调如果背景也是浅色标签会看不清。我一般把label.textStyle.color设成亮一点的白色或淡青色然后给标签加一个轻微的文字阴影提升识别度。当然更保险的做法是让标签颜色和扇区颜色保持同一色系但明度高一些既有整体感又不会太花。三是布局比例。3D 饼图由于有透视实际视觉占用面积会比容器稍小四周会留白。排布大屏时不要把它和其他图表贴太近给模型旋转预留出边界否则旋转过程中扇区会“怼”到别的图表上破坏整体感。5. 常见问题与排查技巧这一节最值钱5.1 ECharts 5 与 echarts-gl 的兼容性这是我在社区里被问得最多的一个问题为什么我的 echarts-gl 装上了pie3D 画出来是黑屏90% 的情况是 ECharts 版本用了 5.x 或更高而echarts-gl官方最新版本 2.0.9 已经很久没更新内部对 ECharts 5 的兼容处理不完善。如果你的项目不受 ECharts 版本约束最彻底的解决方案是把 ECharts 锁在 4.9.0。如果项目已经用了 ECharts 5并且不方便降级我的建议是不要死磕pie3D改用二维饼图加伪 3D 方案或者自己写一个基于 three.js 的轻量 3D 饼图组件。我见过有人硬改 echarts-gl 源码来解决兼容问题但那样后续升级基本就断了项目风险太大。5.2 标签重叠、tooltip 换行很多人在 3D 饼图里遇到标签重叠第一反应是调字体大小其实效果有限。更有效的手段是配合minShowLabelAngle过滤小扇区标签以及在label.formatter里只保留关键信息。比如你可以让标签只显示名称占比放到 tooltip 里展示这样扇区再小也不容易互相挤压。关于 tooltip 换行pie3D的 tooltip 和普通图表一样支持 HTML用br/换行即可。有搜索热词提到“echarts tooltip自动换行”其实最保险的做法是 formatter 里拼br/而不是用字符串模板\n。因为 tooltip 是 HTML 容器\n不会渲染成换行只会变成一个空格。如果你希望长文本按宽度自动换行可以在 formatter 里自己截断或者给tooltip.textStyle.width设置一个固定宽度ECharts 会自动换行。另外搜索词里还有“labelline末尾小圆点偏移”这是二维饼图的引导线问题labelLine的endSymbol可以控制末端符号如果小圆点位置偏了检查length和length2的差值是否合理我一般直接把endSymbol去掉或者让length2保持和圆点直径一致观感立刻整齐很多。5.3 性能与渲染卡顿pie3D的渲染依赖 GPU数据量越大、模型越复杂卡顿概率越高。单个饼图有几十个扇区时问题不大但如果同一屏里放三四个 3D 图表低端机器直接掉帧。我的经验是控制同时渲染的 3D 图表数量同一时间最多放一到两个 3D 核心图表其他指标用二维图表呈现。另一个容易忽视的点是不要频繁setOption做大范围配置更新比如自动刷新数据时尽量只更新series.data不要整个 option 重建可以让 ECharts 做局部 diff减少不必要的 WebGL 重绘。5.4 响应式与大屏适配移动端上3D 饼图的交互手感很微妙拖拽旋转在触屏上还算流畅但很多细节点击事件在大屏上可以到了手机屏幕就变得难以操作。如果你要适配移动端建议把viewControl.distance调大让模型在视野里小一点方便手指拖动。桌面大屏的适配则要注意 rem 和百分比的问题。热词里有个“pxtorem 对echarts没起到效果 vue3”这个问题我太有体会了。ECharts 图表不是 DOM 元素它渲染在 canvas 上canvas 内部绘制的字体和尺寸不会受到 CSS 的rem影响。所以你在label.textStyle.fontSize里写16它就是 canvas 上的 16 像素不会跟着根字体缩放。想让图表随容器缩放正确做法是监听容器尺寸变化并调用chart.resize()或者把容器宽度设成百分比高度用window.innerHeight计算。千万别指望 pxtorem 能帮你自动缩放 canvas 内部内容。5.5 社区扩展与后续方向echarts-gl更新停滞是现实但社区里仍然有不少人在做基于它的二次封装。除了官方 demo 里的pie3D还有人实现了带间隙的分离式 3D 饼图、3D 环形图等变体。既然聊到分离式饼图我多说一句pie3D原生不支持扇区之间的“拉开”间隔如果想做出每个扇区都独立分离的效果常见做法是在多个pie3Dseries 之间做视觉叠加或者用普通二维饼图配合阴影。原因不复杂pie3D的每个扇区是基于同一个圆柱体切分出来的网格要分离就得改网格生成逻辑这已经超出配置项能覆盖的范围了。后续如果你想深入可以从 three.js 入手用 ECharts 的graphic或纯 three.js 自定义场景来实现真正自由的 3D 数据可视化。echarts-gl本来就是封装 three.js 的产物理解了它的原理写自定义扩展会顺手很多。我个人在实际项目里的心得是先把范围控制在“能用、好看、稳定”这三条线上再谈炫技。用pie3D做项目交付版本锁死、配置精简、标签适度我这边没有一次因为图表效果被返工。希望这篇能帮你把 3D 饼图这块一次做踏实。