☰
用ECharts与echarts-gl实现3D立体环形图:默认选项与surface参数方程
2026/9/30 16:05:07 网站建设 项目流程

前几天有朋友问我,数据大屏上那种带厚度的立体环形占比图到底怎么做,能不能直接用 ECharts 出效果。我第一反应是:ECharts 自身的饼图只能做平面的二维环形图,要真正带高度、有厚度的立体效果,必须请出 echarts-gl 这个扩展库。折腾了一整天后,我用一个比较取巧但很稳定的方案——用 echarts-gl 的 surface 参数曲面去拼 3D 环形扇区,全程没有写一行 Shader,效果却足够撑起大屏。这篇文章就把这个方案的完整思路、可复现代码和踩过的坑一次性写清楚,尤其是标题里提到的“默认选项”:我们完全可以用最少的配置,先让 ECharts 和 ECharts-gl 的默认能力把 3D 立体环形图跑起来,再逐步精调。

1. 需求拆解:3D环形图到底是“饼图”还是“建模”

1.1 一句话描述这个需求

项目标题很简单:“Echarts与Echarts-gl构建3D立体环形图,展示默认选项”。翻译成项目语言就是:在页面上用 ECharts 和 ECharts-gl 配合,画一个能转动、有厚度、看起来像甜甜圈的环形占比图,并且先用库的默认配置把效果跑出来,不做复杂的自定义美化。

这个“默认选项”很关键。很多人拿到一个新图表需求,第一反应是找现成的大段配置,结果光样式就调了两天。我的习惯正好相反:先只用默认参数把图形完整渲染出来,确认技术路线可行,再逐步加坐标轴、光照、交互、配色。所以这篇文章也会按这个节奏来写,前面大量代码都是“能跑”的版本,不是“最好看”的版本。

1.2 需求背后的三个关键词

拆开标题,核心词有三个:

  • ECharts:国内数据可视化场景里用得最多的 2D 图表库,生态成熟,社区案例非常多。
  • ECharts-gl:ECharts 官方推出的 3D 扩展库,底层基于 WebGL,能在不改变 ECharts 使用习惯的前提下渲染 3D 图形。
  • 3D 立体环形图:本质是“环形占比图”的立体版本,常见于数据大屏、汇报演示、驾驶舱等场景,用来展示不同模块的占比关系。

这三个词放在一起,说明这不是一个纯 3D 建模需求,而是一个“在图表框架内快速获得 3D 视觉表现”的需求。因此选型不需要上重型引擎,ECharts 加 echarts-gl 正好能覆盖。

1.3 技术可行性预判

刚开始我也按“饼图 3D 化”的思路找了一圈,发现 echarts-gl 官方并没有提供pie3D这样的系列。ECharts 社区里流传的 3D 环形图方案,基本都是用surface系列去拼的。把每个数据扇区看作一个独立的曲面,多个曲面环形排列,就组合成了完整的 3D 环形占比图。

这个方案有两个明显优势:第一,surface是 echarts-gl 的内置系列,稳定性有保障;第二,参数曲面的形状可控性极强,只要给出数学公式,就能生成完整的环形扇区。所以技术路线一开始就定下来:用surface+parametricEquation参数方程,把 3D 环形图做出来。

2. 技术选型:ECharts + ECharts-gl 为什么合适

2.1 ECharts-gl 在 ECharts 生态里的位置

ECharts 负责传统的 2D 图表,echarts-gl 则是它的 3D 扩展包。二者共用同一套setOption配置体系,使用体验非常接近。echarts-gl 里比较常用的系列有bar3D、scatter3D、surface、lines3D、map3D等,每个系列都能直接嵌套在 ECharts 的 option 中。

安装方式也很简单:

  • 用 CDN:先引入echarts.min.js,再引入echarts-gl.min.js。
  • 用 npm:执行npm install echarts echarts-gl,然后在代码里import * as echarts from 'echarts'和import 'echarts-gl'。

版本方面,我实测比较稳的组合是echarts@5.4.3加echarts-gl@2.0.9。太老的版本配合 ECharts 5 可能会出现 API 兼容问题。

2.2 为什么不用 Three.js 硬撸

有人会问:“3D 环形图用 Three.js 不是更简单吗?”确实,Three.js 能做得更精细,但成本也更高。数据可视化项目的核心是数据表达,不是建模精度。使用 Three.js 需要自己处理场景、相机、灯光、坐标系、鼠标交互、窗口自适应、事件绑定等一整套机制,开发周期会明显拉长。

ECharts-gl 的价值在于它保留了 ECharts 的使用习惯,比如grid3D负责 3D 场景,viewControl负责视角控制,tooltip负责交互提示。我们用一套配置就能获得交互完整的 3D 图形,这对大屏项目来说性价比极高。

2.3 没有 pie3D,就从 surface 入手

既然 echarts-gl 没有原生的 3D 饼图系列,我们就用surface系列自己造一个。surface本质是参数曲面,能根据数学公式生成连续几何体。把环形分成多个扇区,每个扇区一个surface实例,再用数据占比控制扇区角度,就可以得到非常自然的立体环形占比图。

同样能做环形效果的还有bar3D,思路是把一个个 3D 柱子绕中心排列成环形,但那样看起来是“环形柱状图”,不是连续的“环形占比图”。两种效果我都测试过,最终选了surface,因为它在视觉上更接近甜甜圈,也更符合“3D 立体环形图”的直觉。

3. 搭建基础页面:先把3D场景跑起来

3.1 引入依赖与最小页面

写代码之前,先准备一个最小可运行页面。这个页面里什么都不做,只初始化图表,确保 CDN 依赖没有冲突。

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>ECharts-GL 3D环形图</title> <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/echarts-gl@2.0.9/dist/echarts-gl.min.js"></script> <style> #chart { width: 800px; height: 600px; margin: 0 auto; background: #0f1423; } </style> </head> <body> <div id="chart"></div> <script> var chart = echarts.init(document.getElementById('chart')); console.log('ECharts version:', echarts.version); console.log('ECharts-GL loaded:', !!echarts.gl); </script> </body> </html>

如果你打开页面能在控制台看到版本信息,说明依赖已经正常加载。这里我特意给容器加了深色背景,因为 3D 场景在深色背景下立体感更明显,也更符合大屏视觉风格。

3.2 基础 option:三个 3D 坐标轴

3D 系列和 2D 图表最大的区别是它需要完整的 3D 坐标系。在 echarts-gl 里,这个坐标系由xAxis3D、yAxis3D、zAxis3D和grid3D共同组成。

先给一个最基础的配置:

chart.setOption({ grid3D: {}, xAxis3D: { type: 'value' }, yAxis3D: { type: 'value' }, zAxis3D: { type: 'value' } });

这段代码运行后,页面上会出现一个带三维坐标轴的空场景。如果不写grid3D,某些版本可能不会正常渲染 3D 系列,所以我习惯先显式声明。之后我们要画的圆环,会落在这个 3D 坐标系里。

3.3 默认选项到底默认了什么

“默认选项”并不是一个玄学词。ECharts-gl 在grid3D、viewControl、light等模块上都有默认值。比如:

  • viewControl控制 3D 场景的视角,默认视角一般是斜上方,用户可以用鼠标拖拽旋转。
  • light.main是主光源,默认会有一个平行光或点光源,保证物体有明暗面。
  • light.ambient是环境光,默认存在,用来提亮暗部。
  • wireframe是曲面线框,surface系列默认不显示线框。

表里是我实际体验下来比较重要的默认项参考:

配置项默认值参考作用说明
viewControl.alpha约 40 度俯仰角,影响立体感
viewControl.beta0 度方位角,改变观察方向
viewControl.distance约 200相机距离,影响画面缩放
viewControl.autoRotatefalse是否自动旋转
light.main.intensity约 1主光源强度
light.ambient.intensity约 0.4环境光强度
series.wireframe.showfalse是否显示曲面线框

不同版本数值可能有差异,但思路一致:我们完全可以在不设置这些配置的情况下,利用默认值先渲染出立体环形图。接下来就直接进入核心实现。

4. 一步一步画出3D环形图

4.1 圆环面参数方程:一个数学公式搞定立体环

要做 3D 环形图,首先要会画 3D 圆环。圆环面有一个非常经典的三维参数方程,简单说就是用两个角度参数u和v扫出一个环面。

  • u表示绕环形中心的旋转角度,范围是 0 到2π。
  • v表示绕圆环截面中心的角度,范围也是 0 到2π。
  • R是主半径,决定圆环整体大小。
  • r是截面半径,决定圆环的粗细。

对应的方程是:

x = (R + r * cos(v)) * cos(u) y = (R + r * cos(v)) * sin(u) z = r * sin(v)

可以想象成一条橡皮筋绕成一个圆圈,橡皮筋本身又有一个粗细。R控制圆圈半径,r控制橡皮筋粗细。在 echarts-gl 的surface系列里,这个方程写起来非常直观。

先画一个完整的 3D 圆环:

chart.setOption({ grid3D: { show: false }, xAxis3D: { type: 'value' }, yAxis3D: { type: 'value' }, zAxis3D: { type: 'value' }, series: [{ type: 'surface', parametric: true, wireframe: { show: false }, parametricEquation: { u: { min: 0, max: Math.PI * 2, step: Math.PI / 180 }, v: { min: 0, max: Math.PI * 2, step: Math.PI / 90 }, x: function (u, v) { var R = 30; var r = 8; return (R + r * Math.cos(v)) * Math.cos(u); }, y: function (u, v) { var R = 30; var r = 8; return (R + r * Math.cos(v)) * Math.sin(u); }, z: function (u, v) { return r * Math.sin(v); } } }] });

打开页面,你应该能看到一个银白色的 3D 甜甜圈。它本身没有任何数据含义,但这就是后续所有扇区的基础形状。

4.2 把占比数据映射到扇区

完整圆环只有一个几何体,无法表达数据占比。要做成“环形图”,必须把数据按比例切成多个扇区,每个扇区由一个独立的surface系列渲染。

具体做法:把整个圆周2π按数据比例切成多段角度区间,每段角度对应一个扇区。例如有四条数据,占比分别是 30%、26%、24%、20%,那么角度区间就按value / total * 2 * Math.PI依次切分。

下面这段代码是核心逻辑:

var data = [ { name: '运营中心', value: 30 }, { name: '产品中心', value: 26 }, { name: '技术中心', value: 24 }, { name: '市场中心', value: 20 } ]; var palette = ['#5470c6', '#91cc75', '#fac858', '#ee6666']; var total = data.reduce(function (sum, item) { return sum + item.value; }, 0); var R = 30; var r = 8; var currentAngle = 0; var series = data.map(function (item, index) { var start = currentAngle; var angleLength = (item.value / total) * Math.PI * 2; currentAngle += angleLength; // 终点多加 0.02 弧度,避免相邻扇区衔接处出现裂缝 var end = currentAngle + 0.02; return { name: item.name, type: 'surface', parametric: true, wireframe: { show: false }, itemStyle: { color: palette[index], opacity: 1 }, parametricEquation: { u: { min: start, max: end, step: Math.PI / 180 }, v: { min: 0, max: Math.PI * 2, step: Math.PI / 90 }, x: function (u, v) { return (R + r * Math.cos(v)) * Math.sin(u); }, y: function (u, v) { return (R + r * Math.cos(v)) * Math.cos(u); }, z: function (u, v) { return r * Math.sin(v); } } }; });

这里每个surface系列只负责绘制一段圆环曲面,多个曲面拼在一起,就是一个按数据占比切分的 3D 立体环形图。视觉上很像把一块甜甜圈蛋糕切成了几份,每一份代表一类数据。

4.3 完整示例:默认选项下的3D立体环形图

把前面的代码合到一起,就是一个可以直接运行的完整页面。这个版本刻意没有设置viewControl、light、label等配置,全部走 ECharts-gl 的默认渲染逻辑,验证“默认选项”效果。

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <title>ECharts-GL 3D环形图 - 默认选项</title> <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/echarts-gl@2.0.9/dist/echarts-gl.min.js"></script> <style> #chart { width: 800px; height: 600px; margin: 0 auto; background: #0f1423; } </style> </head> <body> <div id="chart"></div> <script> var chart = echarts.init(document.getElementById('chart')); var data = [ { name: '运营中心', value: 30 }, { name: '产品中心', value: 26 }, { name: '技术中心', value: 24 }, { name: '市场中心', value: 20 } ]; var palette = ['#5470c6', '#91cc75', '#fac858', '#ee6666']; var total = data.reduce(function (sum, item) { return sum + item.value; }, 0); var R = 30; var r = 8; var currentAngle = 0; var series = data.map(function (item, index) { var start = currentAngle; var angleLength = (item.value / total) * Math.PI * 2; currentAngle += angleLength; var end = currentAngle + 0.02; return { name: item.name, type: 'surface', parametric: true, wireframe: { show: false }, itemStyle: { color: palette[index], opacity: 1 }, parametricEquation: { u: { min: start, max: end, step: Math.PI / 180 }, v: { min: 0, max: Math.PI * 2, step: Math.PI / 90 }, x: function (u, v) { return (R + r * Math.cos(v)) * Math.sin(u); }, y: function (u, v) { return (R + r * Math.cos(v)) * Math.cos(u); }, z: function (u, v) { return r * Math.sin(v); } } }; }); chart.setOption({ grid3D: { show: false }, xAxis3D: { type: 'value' }, yAxis3D: { type: 'value' }, zAxis3D: { type: 'value' }, series: series }); </script> </body> </html>

运行后,你会得到一个可拖拽旋转的 3D 环形占比图。各个扇区颜色不同,切分清晰,默认视角下立体感已经比较明显。这一步验证了“ECharts 与 ECharts-gl 构建 3D 立体环形图”的可行性,也展示了“默认选项”到底长什么样。

5. 默认效果之外:交互与样式优化

5.1 viewControl 视角、光照和坐标轴隐藏

默认效果虽然能跑,但大屏项目通常还希望有自动旋转、合适的相机距离、更精致的光照。这时我们可以继续在grid3D里配置viewControl。

我常用的配置是:

grid3D: { show: false, boxWidth: 80, boxDepth: 80, boxHeight: 40, viewControl: { autoRotate: true, autoRotateSpeed: 8, distance: 180, alpha: 25, beta: 0 } }

说明一下:alpha是俯仰角,角度越小越接近俯视;beta是方位角,控制左右旋转;distance是相机到物体的距离。配合自动旋转,大屏上会显得更有科技感。

光照方面,默认的主光源和环境光基本够用,但颜色偏暗时我会手动调:

light: { main: { intensity: 1.5, shadow: true }, ambient: { intensity: 0.6 } }

shadow开启后阴影会明显增强立体感,但也会增加渲染压力。数据量大或者机器性能一般时,建议关掉。

5.2 tooltip 提示和标签展示

3D 环形图如果没有提示信息,用户根本分不清每个扇区代表什么。最简单的方式是给series配置name,然后使用 ECharts 的tooltip。

我在项目中习惯维护一个valueMap,然后在 formatter 里把名称和数值拼出来:

var valueMap = {}; data.forEach(function (item) { valueMap[item.name] = item.value; }); chart.setOption({ tooltip: { trigger: 'item', formatter: function (params) { var name = params.seriesName; var value = valueMap[name]; return name + '<br/>占比:' + value + '%'; }, backgroundColor: 'rgba(13,20,40,0.8)', textStyle: { color: '#fff' } } });

这里也顺带解决了 ECharts tooltip 的一个经典问题:提示内容过长时不会换行。如果你想让多行内容正常换行,可以在tooltip里加一个:

extraCssText: 'white-space: normal;'

这样提示框内容就会按 HTML 自然的换行逻辑展示,不需要手动加<br/>。这个细节在大屏信息很多时特别实用。

5.3 自适应大屏和 rem 适配问题

大屏项目里 rem 适配几乎是标配,但这里有一个非常容易踩的坑:postcss-pxtorem只会转换 CSS 里的像素单位,对 canvas 内部绘制的 ECharts 内容完全无效。ECharts 图表内部的字体、线条、标记大小,都是在 canvas 初始化时根据容器像素尺寸计算出来的。所以你会看到 DOM 元素随 rem 缩放得很完美,但图表内部的文字大小纹丝不动。

解决办法是不要依赖 pxtorem 去处理图表尺寸,而是直接用chart.resize()监听窗口变化:

window.addEventListener('resize', function () { chart.resize(); });

如果大屏设计稿是 1920 宽,容器按 rem 设置了响应式宽度,那么在resize时最好重新计算容器尺寸,再调用chart.resize()。另一方面,ECharts 的textStyle.fontSize可以直接设置成基于当前屏幕宽度计算出来的像素值,不要写成纯固定 rem 值。这个坑在 Vue 3 项目中尤其常见,很多朋友反馈“pxtorem 对 ECharts 没起到效果”,其实就是混淆了 DOM 渲染和 canvas 渲染。

6. 常见问题与排查实录

6.1 页面只有坐标轴没有曲面

连着踩过两次这个坑,原因基本都是同一个:parametric没有设置为true,或者parametricEquation写错了位置。

surface系列要使用参数方程,必须同时满足三个条件:

  • type: 'surface'
  • parametric: true
  • parametricEquation与series平级,而不是嵌套在其他子配置里。

如果只写了parametricEquation而忘了parametric: true,echarts-gl 会认为你想用data渲染普通曲面,而你又没有传data,结果自然是空场景。排查时可以打开浏览器控制台,看是否有报错提示。

6.2 扇区之间有裂缝

当你把多个扇区拼接成一个环形时,相邻曲面之间偶尔会出现一条细线或者半个像素宽的裂缝。这不是真正的破面,而是 WebGL 在抗锯齿时对边缘像素做了透明处理,导致两个曲面交界处的颜色没有完全覆盖。

解决办法是在计算扇区终点时加一个小的重叠量,比如currentAngle + 0.02。这样相邻扇区会有轻微重叠,边缘裂缝就被覆盖掉了。注意重叠量不要太大,否则扇区颜色会互相侵染,反而显得脏。

6.3 颜色发黑或整个环不可区分

3D 图形的颜色表现和光照关系非常密切。如果扇区颜色比预期暗很多,优先检查light.main.intensity和light.ambient.intensity。默认光照在普通场景下没问题,但深色背景下往往需要额外提高环境光强度。

还有一种情况是曲面法线方向反了,导致光照计算在背面。此时可以尝试调整v方向的范围或修改z函数的正负。不过在我们这个圆环方程里,正常写法不会出现法线反向,多出现在二次开发改方程时。

6.4 Vue/React 项目里初始化失效

在 Vue 3 或 React 中,最典型的报错是“Initialize failed: invalid dom”。原因很简单:初始化时容器还没挂载,或者容器宽度高度为 0。

解决方式也很固定:在onMounted或useEffect里执行nextTick后再init。如果是异步加载数据,甚至可以等数据返回后再创建实例。组件销毁时记得调用chart.dispose(),不然会有内存泄漏风险,切路由之后连 canvas 都可能会残留。

6.5 体积和性能优化

echarts-gl 本身包含 WebGL 渲染层,体积不算小。如果你项目对首屏体积敏感,建议不要全量引入,而是用 CDN 的 gzip 版本,或者在需要 3D 图表的页面里动态加载脚本。还有一点,surface的step决定了曲面精度。步长越小曲面越光滑,但顶点数也越多,会明显增加渲染开销。像圆环这种形状,u.step用Math.PI / 90、v.step用Math.PI / 90已经足够了,不要盲目追求Math.PI / 360。

7. 扩展:同一条路还能做哪些3D可视化

7.1 3D柱状图与3D散点图

学会了surface,其实等于打开了一扇门。echarts-gl 里还有bar3D、scatter3D、lines3D等系列,都用同一个grid3D坐标系。比如常见的 3D 柱状图,写法比surface更简单:

series: [{ type: 'bar3D', data: [ [0, 0, 5], [1, 0, 8], [2, 0, 3], [0, 1, 6], [1, 1, 9], [2, 1, 4] ], shading: 'lambert', label: { show: true } }]

data里的前两个维度是 x 和 y,第三个维度是柱子高度。配合shading: 'lambert',柱子会有明暗面,立体感立刻出来。很多大屏里的 3D 城市数据展示,用的就是这种能力。

7.2 3D地图与多系列组合

除了基础图形,echarts-gl 还可以结合地图 GeoJSON 做 3D 地图。比如用map3D系列加载中国地图数据,再配置viewControl实现旋转、缩放,视觉冲击力非常强。社区里搜索“echarts中国地图”“echarts-gl示例”,能找到大量类似实现。

不过我的建议是:先从简单的surface和bar3D练手,把grid3D、viewControl、light之间的关系摸清楚,再上地图和复杂组合。3D 可视化的核心难点往往不是“画出来”,而是“调得好看又不卡”。

这篇文章里所有的实现,本质上都是同一个套路:定义几何形状,映射数据属性,控制光源和视角。只要这个套路通了,未来再做 3D 漏斗图、3D 金字塔,甚至自定义波形图,就只是改参数方程的问题了。

我个人在实际操作中最想强调的一点是:先让默认选项把效果跑起来,再逐步加配置。这个习惯帮我少踩了很多坑。很多新手一上来就复制一大段现成配置,结果不知道哪行配置改坏了,最后连图形都渲染不出来。先用默认的surface跑出完整圆环,确认坐标轴、光照、交互都正常,再按数据切扇区,最后才调色和视角。每一步改动都比较小,出了问题能立刻定位。如果你也想做 3D 环形图,建议不要跳过这个步骤,尤其要把parametricEquation的手感练熟。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询