干数据可视化这行大家应该都有同感同样是柱状图、饼图2D 版和 3D 版放在大屏上一对比后者就是更抓眼球。ECharts 虽然本身不提供type: 3d但真正能跑起来的 3D 柱状图和 3D 饼图要么靠 echarts-gl 扩展要么用普通 ECharts 配置做伪 3D 效果。把这两条路想清楚你就能应付大多数数据可视化项目里的“高端”需求。这篇文章我打算不绕弯子直接说实现思路。3D 柱状图走 echarts-gl 的bar3D3D 饼图则分两种伪 3D 用多层 pie 硬拼真 3D 用bar3D按极坐标铺点。适合刚接触 ECharts 3D 的开发者也适合被客户要求“加个 3D 图表”却不知道从哪下手的前端。后面所有参数我都会给到可以直接抄的程度。1. 先弄明白3D 图表到底是怎么画出来的1.1 真 3D 和伪 3D先选路线再动手很多人一搜“ECharts 3D 柱状图”就以为 ECharts 原生支持其实 ECharts 本身的渲染核心是 2D Canvas 或 SVG要出现可旋转、可缩放的立体图表必须引入 echarts-gl。echarts-gl 是基于 WebGL 的扩展库注册之后会给 ECharts 增加bar3D、scatter3D、line3D、surface3D这些系列类型这才是大家口中真正的 3D。但 3D 饼图的情况不太一样。echarts-gl 里没有pie3D这个原生系列网上 90% 的“3D 饼图”效果其实是用普通 pie 系列做了好几层半透明重叠或者用饼图加位移、加投影拼出来的。这种伪 3D 的好处是轻、快、兼容性强缺点是没法真正旋转视角。所以你在选型时可以对照这个表对比项真 3Decharts-gl伪 3D多层 pie渲染方式WebGL 三维场景2D Canvas 叠加交互能力可旋转、缩放、自动旋转基本没有 3D 视角变化数据量支持较大数据但受机器性能影响数据量小主要看饼图层数学习成本需要理解 3D 轴、光照、投影只需要会配 pie适用场景大屏、汇报、可视化展示图表居中展示、移动端兼容我的建议很简单如果是放在大屏最中间当主角选真 3D 柱状图如果只是需要“看起来厚一点”的饼图别上 WebGL直接伪 3D省心得多。1.2 装包和引入的正确姿势echarts-gl 的引入方式和 ECharts 一样项目里现在最常用的是 npmnpm install echarts echarts-gl然后在你需要用的模块里这样引入import * as echarts from echarts; import echarts-gl;这里有一个关键点echarts-gl必须在echarts之后引入。因为 echarts-gl 是在 echarts 的注册机制上扩展系列如果引入顺序反了初始化图表时不会注册bar3D浏览器控制台经常会提示 “series bar3D not exists” 之类的话。还有一点要注意版本。如果你项目里锁的是 echarts 4那 echarts-gl 尽量用 1.x如果用的是 echarts 5就用 echarts-gl 2.x。这个对应关系在 npm 安装时很容易踩建议直接在 package.json 里写死版本号不要裸装 latest。2. 3D 柱状图bar3D 的核心配置与参数拆解2.1 一个最小可运行的 bar3D 示例先给一套能直接跑起来的最小配置感受一下数据格式import * as echarts from echarts; import echarts-gl; const chart echarts.init(document.getElementById(chart)); chart.setOption({ tooltip: {}, visualMap: { max: 100, inRange: { color: [#313695, #74add1, #fee090, #f46d43, #a50026] } }, xAxis3D: { type: category, data: [周一, 周二, 周三, 周四, 周五] }, yAxis3D: { type: category, data: [华东, 华北, 华南] }, zAxis3D: { type: value }, grid3D: { boxWidth: 80, boxDepth: 40, viewControl: { alpha: 25, beta: 30, distance: 180 } }, series: [ { type: bar3D, data: [ [0, 0, 62], [0, 1, 48], [0, 2, 53], [1, 0, 75], [1, 1, 82], [1, 2, 66], [2, 0, 90], [2, 1, 58], [2, 2, 73], [3, 0, 45], [3, 1, 69], [3, 2, 81], [4, 0, 38], [4, 1, 74], [4, 2, 92] ], shading: lambert, bevelSize: 0.3, label: { show: false } } ] });这里最容易迷惑的就是data里的坐标。[0, 0, 62]表示 x 轴取第 0 个类目、y 轴取第 0 个类目、数值是 62。不是直接写[周一, 华东, 62]。所以如果你的类目是动态接口返回最好先做一个“类目名到下标”的映射再组装数据否则轴对不上就是白屏或者柱子全部叠在一个位置。xAxis3D、yAxis3D、zAxis3D分别代表三个方向。一般 x、y 放类目或者数值z 放高度。grid3D是承载整个三维坐标系的外框控制整个图表的“体量感”。2.2 参数怎么调才能有质感bar3D 能出效果靠的绝不只是柱子的高度而是参数组合。我最常用的几个shading柱子的着色方式。默认是color纯色填充性能最好但塑料感强。换成lambert之后WebGL 会根据光源计算明暗柱子看起来有立体感。如果你要非常逼真的金属质感可以用realistic但要配合postEffect一起使用开销比较大。bevelSize柱子顶部和底部的倒角半径。稍微设置一点比如 0.2 到 0.5柱子边缘就不会像刀切一样锐利大屏上看更柔和。barSize柱子底面积。普通 2D 柱状图用barWidth3D 版用的是barSize。如果柱子太密、挤在一起把barSize调小一点。grid3D.viewControl视图控制。alpha是俯仰角beta是水平旋转角distance是相机距离。想要图表自动转起来可以在viewControl里加autoRotate: true并且调autoRotateSpeed大屏演示的时候很加分。grid3D.environment三维场景背景。默认是透明黑你可以设置成一个颜色或图片纹理比如environment: #1b2a4a整体会比默认好看很多。给项目落地时我的调参顺序是先搭好数据把shading设为lambert再调viewControl视角最后用visualMap上色。颜色不要一上来就花花绿绿先用连续渐变色效果最稳。2.3 渐变色到底该配在哪里很多从普通柱状图转过来的人会习惯地在itemStyle里写color试图用一个线性渐变把柱子上下染成不同颜色。bar3D 里这样做效果可能不明显或者跟你预期不一致。真 3D 柱子的“渐变”最好分成两个层面理解一是从低到高的数值渐变。这个直接用visualMap控制visualMap: { dimension: 2, max: 100, inRange: { color: [#003f5c, #7a5195, #ef5675, #ffa600] } }dimension: 2表示颜色映射到数据数组的第三个维度也就是 z 轴数值。数值越大颜色越偏后面的色值这样柱子从矮到高会自然形成一个连续变化。二是单根柱子表面的明暗。这个不是靠渐变色做的而是靠shading: lambert加光照。同样的蓝色开了 lambert 之后会因为受光角度不同出现顶部亮、侧面暗的效果视觉上就是一种很自然的 3D 渐变。顺带提一句普通 2D 柱状图的渐变色。如果你项目里还有一个普通柱状图想统一风格可以直接在itemStyle.color里写线性渐变对象itemStyle: { color: { type: linear, x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: #4fc3f7 }, { offset: 1, color: #1a237e } ] } }这招在折线图的areaStyle里同样适用只需要把同样的渐变色对象丢给areaStyle.color。之前有几个同事搜“echarts areastyle 渐变色”最后发现就是这里没写明白。3. 3D 饼图两种主流实现思路3.1 思路一多层饼图拼出厚度这是最常用、最稳的伪 3D 饼图方案。原理很简单把同一个饼图画很多层每一层在垂直方向稍微偏移一点越往下的层越透明最上面一层完全不透明并显示标签。由于圆的轮廓在多层错位后会产生“立面”的错觉看起来就是一块厚饼。直接看代码const pieData [ { name: 直客, value: 35 }, { name: 渠道, value: 25 }, { name: 老客, value: 20 }, { name: 新客, value: 20 } ]; const layerCount 4; const depth 2.2; function buildPieLayer(index) { const isTop index layerCount - 1; const centerY 50 index * depth; return { name: pie3d, type: pie, radius: [38%, 64%], center: [50%, centerY %], startAngle: 90, clockwise: true, data: pieData, label: { show: isTop, formatter: {b}: {d}% }, labelLine: { show: isTop }, itemStyle: { opacity: isTop ? 1 : 0.35, borderColor: #fff, borderWidth: 1 }, silent: !isTop }; } const series Array.from({ length: layerCount }, (_, i) buildPieLayer(i)); chart.setOption({ tooltip: { trigger: item, formatter: {b}: {c} ({d}%) }, series: series });几个关键点你务必记住centerY的偏移方向是从下往上画。先画最下面那层再一层一层往上叠最后画最上面那层。如果顺序反了半透明层会盖住顶部颜色会脏。层数控制在 3 到 5 层。层数太少边沿会出现明显断层层数太多series 数量翻倍渲染性能下降。4 层是我试过的视觉和性能平衡点。silent: !isTop是为了让中间层不响应鼠标事件。否则 tooltip 会同时弹出好几个层的数据用户体验很差。如果你要的是“碟子”而不是“厚饼”把radius拉开一点比如内外半径比设置在[42%, 68%]中间层透明之后会更有层次。这个方法最大的优点是兼容性好。普通 ECharts 环境就能跑不需要 echarts-gl不需要 WebGL移动端基本不会白屏。缺点也非常明显它只能用一个固定视角看你不能拖着鼠标转。3.2 思路二用 bar3D 按极坐标铺出真 3D 圆盘如果你确实需要可旋转的真 3D 饼图echarts-gl 没有pie3D但我们可以换个思路把一个饼图看成一个圆盘圆盘由很多个小柱子组成。每个柱子的底面中心点按极坐标排布柱子的高度代表该扇区的数值这样就能用bar3D拼出一个 3D 圆盘。大致的生成逻辑是这样const sectors [ { name: 直客, value: 35, angleStart: 0, angleEnd: Math.PI * 0.7 }, { name: 渠道, value: 25, angleStart: Math.PI * 0.7, angleEnd: Math.PI * 1.2 } ]; const data []; sectors.forEach((sector) { const angleStep Math.PI / 24; const radiusStep 0.1; for (let angle sector.angleStart; angle sector.angleEnd; angle angleStep) { for (let radius 0; radius 5; radius radiusStep) { data.push([ radius * Math.cos(angle), radius * Math.sin(angle), sector.value ]); } } }); chart.setOption({ xAxis3D: { type: value }, yAxis3D: { type: value }, zAxis3D: { type: value }, grid3D: { viewControl: { alpha: 30, beta: 40 } }, series: [ { type: bar3D, data: data, barSize: 0.08, shading: color } ] });这里的angleStep和radiusStep是采样精度。步长越小圆盘边缘越平滑但柱子数量会爆炸很容易把浏览器卡死。我建议先用较大的步长跑通再逐步调小。真 3D 饼图的数据量大时barSize一定要小于步长否则柱子之间会重叠成一面墙。不过这个方法也只是“实现思路”它拼出来的圆盘是由柱子组成的放大看会有颗粒感。如果你需要真正光滑的 3D 扇形、带曲面倒角那就不能靠bar3D了得用 echarts-gl 里的surface3D或者自定义 series 去做网格。那已经属于进阶的几何建模范畴日常报表项目不一定需要。3.3 伪 3D 饼图怎么加渐变和阴影多层饼图的基础效果出来之后还可以继续优化。我常用的一种做法是给每一瓣扇形加渐变让颜色从底部到顶部更亮import * as echarts from echarts; itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: #0f2027 }, { offset: 1, color: #2c5364 } ]) }这里要注意color写在最顶层 series 和中间层 series 上表现不一样。最顶层是用户真正看到的面渐变方向通常从中心到边缘或者从下到上中间层因为半透明颜色太重反而会盖住立体感所以中间层建议直接用纯色只用来提供厚度轮廓。还有一个小技巧每层都加borderColor: #fff和borderWidth: 1。白色描边可以很好地分隔相邻扇形模拟出“一块块切好的蛋糕”效果。没有描边的话相邻颜色的过渡会显得糊成一团。4. 生产环境必须处理的细节和坑4.1 图表白屏先查这四件事做过可视化项目的人都知道Demo 放本地没问题一上生产就白屏。3D 图表最常见的白屏原因按排查顺序排一下第一排查echarts-gl是否真的引入成功。用 npm 引入后代码里一定要有import echarts-gl这一行光安装包不引入是不会注册bar3D的。第二排查浏览器是否支持 WebGL。可以打开控制台执行document.createElement(canvas).getContext(webgl)如果返回null说明当前环境不支持 WebGL真 3D 图表没法跑只能降级到伪 3D。第三排查 script 加载顺序。CDN 方式最容易遇到 echarts.js 和 echarts-gl.min.js 顺序写反的问题。第四排查容器尺寸。3D 图表的容器不能是 0 高度大屏上如果动画刚加载时容器还没 display就会拿到一个 0 高度的 canvas。4.2 类目轴和数据下标的对应关系bar3D 的data里第一、第二个数字对应的是轴的下标不是类目值。这一点在实际项目里经常出错特别是后端返回的是{ area: 华东, month: 2024-06, value: 88 }这种结构化数据。我一般会在前端做一次数据转换先建立类目索引const areas [华东, 华北, 华南]; const months [2024-04, 2024-05, 2024-06]; const areaIndex Object.fromEntries(areas.map((name, index) [name, index])); const monthIndex Object.fromEntries(months.map((name, index) [name, index])); const data rows.map((row) [ monthIndex[row.month], areaIndex[row.area], row.value ]);这样写也许多几行代码但逻辑清晰后端字段再乱也能兜住。如果你直接用类目字符串放到 data 里不同版本 echarts-gl 的表现可能不一样为了稳妥宁可多写一次映射。4.3 性能优化数据量和绘制开销真 3D 图表的性能瓶颈主要来自 WebGL 的绘制调用。bar3D 如果数据量很大比如 1000 根柱子每个柱子都是一个几何体渲染压力会成倍增加。应对方法我常用的有三个数据抽样。后端一次性返回 5000 条明细前端绘制前按比例聚合不要直接全量塞进去。降低shading和postEffect。shading: realistic好看但最耗性能大屏机器配置一般建议用lambert。grid3D.boxWidth不要设置得过大盒子越大内部要渲染的细节越多反而会让柱子显得稀疏。伪 3D 饼图的性能问题主要出在 series 层数。10 层以上叠加鼠标 hover 时浏览器会有明显卡顿尤其是缩放大屏的时候。如果真的想要 10 层以上的厚度感就用真 3D 方案别硬堆 pie。4.4 常见问题速查表问题原因解决方案bar3D 报 series not existsecharts-gl 未引入或引入顺序错误在 echarts 后import echarts-gl3D 柱子全部叠在一个点上data 坐标没有按类目下标映射先将类目转成下标饼图 tooltip 弹出多个中间层 series 没有关闭交互给非顶层 series 加silent: true伪 3D 饼图颜色发灰中间层透明度太低、无描边降低 opacity增加白色 border真 3D 圆盘颗粒感重角度或半径步长太大调小angleStep、radiusStep移动端 3D 白屏设备不支持 WebGL降级为多层 pie 伪 3D5. 一点个人经验5.1 什么场景用真 3D什么场景用伪 3D我自己的判断标准是如果这个图表只能在一个固定的大屏页面里展示而且主要作用是撑场面那就用真 3D 柱状图因为它有默认的自动旋转能让领导多看两眼。如果图表下面还要叠加数据表格、筛选器页面本身已经比较复杂那就别引入 echarts-gl用伪 3D 饼图就够了。还有一个容易被忽略的点3D 图表尽量不要做太多交互。很多业务方一开始说“要旋转、要点击、要下钻”结果真做出来之后发现 3D 环境的旋转操作对普通鼠标用户不太友好数据标签也容易遮挡。我的做法是默认禁用手动旋转只开启autoRotate点击柱子时用click事件拿到数据做下钻这样展示效果和业务功能都能兼顾。5.2 最后说一个实际调出来的小技巧伪 3D 饼图最怕最上面那层标签挤在一起。ECharts 默认会给每个扇形留 label 位置但 3D 效果里顶部圆的面积会被“厚度”视觉压缩标签更容易重叠。我在项目里会把标签统一放到顶部图层并且设置labelLine的length和length2稍微长一点然后用avoidLabelOverlap: true让 ECharts 自动做避让。如果还是挤就把顶层的radius内半径调小一点外半径不变相当于把饼图中心位置挪出来标签会更从容。我个人在实际项目里的体会是3D 图表不是越花哨越好而是要在“视觉冲击力”和“数据可读性”之间找平衡。先用最简单的方式把数据跑通再一步步加光照、加渐变、加自动旋转。希望这些实现思路能帮你少踩几个版本和配置的坑。