ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用ECharts-gl实现3D环形图:参数方程与实战配置解析

用ECharts-gl实现3D环形图:参数方程与实战配置解析 去年接一个数据可视化大屏的项目客户在会上提了一句“能不能整点3D效果别老是一张平面饼图”我当时看了一眼技术栈里面已经用了 ECharts就顺手调研了一下 ECharts-gl最后用不到两百行配置做了一个带默认视角、默认灯光、默认材质的 3D 立体环形图效果直接让整个大屏的档次上来了。这篇文章就是把那段经历完整复盘一遍。我会从为什么选 ECharts ECharts-gl 说起拆解 3D 环形图的几何原理再给出可以直接拿去用的默认配置模板最后把我在实际项目里踩过的坑和调优经验一并列出来。适合正在做数据可视化大屏、或者想给汇报页面加点立体感的前端工程师哪怕你只写过最简单的 ECharts 柱状图跟着这篇文章也能把 3D 环形图跑起来。1. 为什么选 ECharts ECharts-gl 做 3D 立体环形图1.1 需求场景大屏和汇报页到底需要什么样的“3D”先说说这类需求的真实背景。绝大多数要 3D 效果的项目核心目的是“视觉冲击力”而不是“信息精度”。比如一个经营驾驶舱中间放一个 3D 环形图展示年度目标完成率旁边再配一列 2D 的指标卡和折线图整个页面看起来就比纯平面的 dashboard 高级不少。这种场景对 3D 的要求有三个特点能旋转、有色差、看起来稳。这三个特点翻译成技术语言就是需要一个成熟的 WebGL 渲染方案能处理透视、光照、材质需要图表元素可以自动旋转或者通过鼠标拖拽查看需要渲染性能稳定不能在大屏上掉帧。ECharts-gl 恰好能满足前两点因为它的底层是把 ECharts 的配置模型扩展到了三维空间我只需要按 ECharts 的思维写 series 配置就行相比之下 Three.js 虽然更强但要从一个圆环自己拼几何体、自己管相机、自己写动画交付成本高很多。1.2 选型对比Three.js、原生 WebGL 和 ECharts-gl我用一个表格把当时考虑过的方案列出来给同样在纠结选型的朋友一个参考方案学习成本图表开发效率3D 自由度维护成本典型使用场景ECharts ECharts-gl低会写 ECharts 就会写高series 配置即所得中基于配置和参数化曲面低大屏、报表、数据可视化Three.js高需理解场景/相机/灯光/几何体低一切从零搭极高可做任意模型高3D 建模展示、游戏原生 WebGL极高需掌握着色器极低极高极高复杂渲染引擎研发CSS 3D / Canvas 手绘低中低很难做真实光影中简单旋转卡片、示意动画对于“后端已经有现成数据接口、前端要在两天内上线一个大屏”这种状态ECharts-gl 几乎是唯一不加班的选择。它和 ECharts 共享一套主题、Tooltip、事件体系连 resize 响应都是同一套逻辑项目里如果已经有 ECharts引入 ECharts-gl 基本是零成本。1.3 什么场景下不建议硬上 3D 环形图虽然这篇文章是教大家做 3D 环形图的但我还是想先说点逆耳的话不是所有需求都适合做成 3D。如果业务方要求用户从图上精确读出某个数值或角度3D 透视会造成很大的视觉误差人眼很难判断一个倾斜环面上的扇形边界到底对应多少度这时候 2D 环形图加 label 标注更合适。如果数据量很小只有一个百分比那 3D 环形图很容易显得“为了 3D 而 3D”不如用一个简洁的大数字卡片。另外如果项目需要兼容大量低端移动设备3D 渲染的开销很可观建议在移动端做降级切回 2D。我一般会在方案评审阶段就抛出这个判断大多数情况下客户听完就会理解。真正需要 3D 环形图的场景通常是 2D 信息密度已经足够、但整体视觉需要“压场”的页面比如领导视察用的总览大屏、品牌发布会的数据展示墙。2. 把 3D 环形图拆开看几何原理和默认选项2.1 环形图的本质是一个参数化环面在做配置之前得理解 ECharts-gl 里的 surface 系列是怎么画出一个环形的。ECharts-gl 的 surface 系列支持两种数据方式一种是你给它一个二维数组它会按行列生成一个曲面网格另一种是提供参数方程让它根据 u 和 v 两个参数自动生成网格点。3D 环形图用的就是第二种——参数化环面。你可以把环面想象成一个甜甜圈或者一条轮胎。它由两个参数决定R环心到管中心的距离也就是这个“轮胎”的总体半径r管的半径也就是这个“轮胎”有多粗。环面上任意一个点的坐标可以用下面的参数方程算出来x(u, v) (R r * cos(v)) * cos(u) y(u, v) (R r * cos(v)) * sin(u) z(u, v) r * sin(v)其中 u 绕环中心旋转取值范围是 0 到 2π控制甜甜圈整圈的长度v 绕管截面旋转范围也是 0 到 2π控制每段截面的完整度。理解了这两个参数后面的“默认选项”就都围绕它展开。2.2 核心参数主半径 R、管半径 r、起始角与扫描角如果你只是要在页面上显示一个完整的 3D 甜甜圈那直接让 u 从 0 走到 2π 就够了。但大多数业务场景要呈现的是“完成率”比如完成 80%这时候你只需要让 u 从某一角度出发只走 80% 的圆周留下一个缺口就能非常直观地表达占比。这样做还有一个好处环形图两端是平截面看起来像被切了一刀比用颜色区分整个环更接近“进度环”的语义。举个具体例子完成率 75%我会把 u 的起始角设为 -π/2让起点在圆环正上方然后扫描角为 0.75 × 2π这样缺口落在正下方视觉上比较符合阅读习惯。startAngle -Math.PI / 2 sweepAngle 0.75 * Math.PI * 2 u.min startAngle u.max startAngle sweepAngle这两个变量就是整个 3D 环形图的“数据入口”。后面接真实业务数据时只需要动态替换 sweepAngle图形就会自动变化。2.3 默认选项到底默认了哪些东西我习惯把一组“最省心”的配置整理成默认模板每次新建项目先复制过去再改业务参数。所谓默认选项核心就是五个方面默认渲染模式用 surface 系列的 parametric 参数曲面不依赖外部模型文件默认网格精度u 和 v 的 step 都取 π/24画面足够平滑性能也不差默认材质和后处理shading 使用 realistic加一点 roughness 和 metalness再开启 bloom 光晕默认视角与交互viewControl 里设置 alpha、beta、distance并打开 autoRotate默认配色深色背景暖色环形白色高亮灯光适合直接放进大屏。有了这些默认值一个空白的 3D 环形图只需要改 R、r、进度比例、颜色就能落地。下面是完整的默认模板我建议直接复制到本地跑一下。!DOCTYPE html html langzh-CN head meta charsetUTF-8 / titleECharts-GL 3D 环形图默认模板/title style #chart { width: 100%; height: 600px; background: #0a1a2f; } /style /head body div idchart/div script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script script srchttps://cdn.jsdelivr.net/npm/echarts-gl2.0.9/dist/echarts-gl.min.js/script script var R 3; var r 1.2; var progress 0.75; var startAngle -Math.PI / 2; var sweepAngle progress * Math.PI * 2; var chart echarts.init(document.getElementById(chart)); chart.setOption({ backgroundColor: #0a1a2f, series: [{ type: surface, parametric: true, shading: realistic, equation: { x: { expr: function (u, v) { return (R r * Math.cos(v)) * Math.cos(u); } }, y: { expr: function (u, v) { return (R r * Math.cos(v)) * Math.sin(u); } }, z: { expr: function (u, v) { return r * Math.sin(v); } }, u: { min: startAngle, max: startAngle sweepAngle, step: Math.PI / 24 }, v: { min: 0, max: Math.PI * 2, step: Math.PI / 24 } }, material: { color: #ff7f50, roughness: 0.4, metalness: 0.3 }, viewControl: { alpha: 20, beta: 40, distance: 250, autoRotate: true, autoRotateSpeed: 5 } }] }); /script /body /html把这个文件用浏览器打开你应该能看到一个在深色背景上缓缓旋转的半透明质感的珊瑚色环形。这就是默认模板的全部内容没有用额外插件也没有外部模型。3. 完整实操用 ECharts-gl 写出可复用的 3D 环形图3.1 引入依赖CDN 与 npm 两种方式上面用的 CDN 方式适合快速验证。如果是正式项目我建议用 npm 安装锁定版本避免 CDN 上的版本更新导致显示异常。npm install echarts5.4.3 echarts-gl2.0.9在入口文件里导入时要注意顺序。ECharts-gl 是对 ECharts 的三维扩展必须先加载 ECharts再加载 ECharts-gl否则会报错找不到 echarts 实例。import * as echarts from echarts; import echarts-gl;有一点容易忽略ECharts-gl 的包是纯扩展不需要导出任何变量直接 import 一次它就会往 ECharts 的 series 类型注册表里添加 surface、bar3D、scatter3D 这些三维系列类型。所以看到import echarts-gl没有赋值别奇怪这是正常的。3.2 核心配置代码surface 参数方程实现在默认模板的基础上我把需要留意的配置逐个解释一下。首先是series.type必须是surface这个系列是三维的。接着是parametric: true这告诉 ECharts-gl 我们不走网格数据生成而是用 equation 参数方程生成点。equation 里的 x、y、z 三个 expr 函数接收 (u, v) 两个参数返回对应的三维坐标u 和 v 的对象里则声明参数范围与步长。步长这一步非常关键。step 越小网格越细曲面越平滑但渲染的点数会指数级增加。以我的经验π/24 在大部分业务展示中已经够看如果在低配电脑上跑大屏可以加大到 π/16 或 π/12。有人在网上问为什么自己做出来的 3D 环形图表面有明显棱角多半是 step 设得太大比如直接用了 π/4那看起来就是一堆三角形拼起来的多面体。另外要注意equation 对象本身不是普通 JSON因为 expr 是函数所以在动态更新时不能简单用 JSON.stringify 做深拷贝或用文本协议传参。我一般会把它封装成一个纯函数每次更新时重新生成方程对象比较干净。3.3 让环形图表达数据用扫描角表示进度默认模板里 progress 变量就是业务数据的入口。只要从接口拿到完成率转换成扫描角再更新 option 里的 u.max就能实现数据驱动。function buildSeries(percent) { var startAngle -Math.PI / 2; var sweepAngle percent * Math.PI * 2; return { type: surface, parametric: true, shading: realistic, equation: { x: { expr: function (u, v) { return (R r * Math.cos(v)) * Math.cos(u); } }, y: { expr: function (u, v) { return (R r * Math.cos(v)) * Math.sin(u); } }, z: { expr: function (u, v) { return r * Math.sin(v); } }, u: { min: startAngle, max: startAngle sweepAngle, step: Math.PI / 24 }, v: { min: 0, max: Math.PI * 2, step: Math.PI / 24 } }, material: { color: #ff7f50, roughness: 0.4, metalness: 0.3 } }; } function updateProgress(percent) { chart.setOption({ series: [buildSeries(percent)] }); }这里我直接替换整个 series 数组而不是只更新 equation.u.max。原因有两个一是 equation 里的函数不好做增量 merge二是我在实际使用中遇到过 surface 系列增量更新后网格点不刷新必须整体替换才生效。虽然 ECharts 官方对增量更新支持得不错但三维 surface 系列目前还不够成熟老老实实重建是最稳妥的。3.4 从“默认模板”到“项目可用的完整模板”默认模板只能保证出来一个环要放进真实大屏还需要加上颜色映射、标签提示、视觉强调这些能力。我常用的完整版模板加入了 visualMap让环形表面颜色根据 z 轴高度渐变。这样做的好处是两个端面会有明显的颜色深浅差立体感会强很多。var option { backgroundColor: #0a1a2f, visualMap: { show: false, dimension: 2, min: -r, max: r, inRange: { color: [#1e6fff, #ff7f50, #ffd470] } }, series: [{ type: surface, parametric: true, shading: realistic, equation: { // ... 同上 }, material: { color: #ff7f50, roughness: 0.35, metalness: 0.4 }, viewControl: { alpha: 20, beta: 40, distance: 250, autoRotate: true, autoRotateSpeed: 5, minAlpha: -90, maxAlpha: 90, minBeta: 10, maxBeta: 60 }, postEffect: { enable: true, bloom: { enable: true, intensity: 0.3 } } }] };再说一下 viewControl 里的约束。autoRotate 是会一直转的如果大屏页面停留时间很长或者用户需要仔细看某个角度建议把自动旋转速度调到 3 到 5同时限制 beta 的上下限防止用户把视角拖到环的侧面甚至底部看到一堆杂乱的端点面。postEffect 里的 bloom 提亮效果很出片但也是性能杀手。如果大屏机的显卡一般先关掉 bloom用默认 realistic 材质加足够亮的环境光也能有很好的效果。4. 细节调优从“能显示”到“看得舒服”4.1 灯光、材质和后处理的取舍ECharts-gl 的 shading 有三种常见模式color、lambert、realistic。color 模式不计算光照直接用颜色填充网格速度最快但立体感来自透视和网格线整体偏“扁平科技风”lambert 模式计算漫反射有基本明暗变化观感比 color 舒服性能也适中realistic 模式会计算更复杂的光照和材质属性比如 roughness、metalness还能配合 postEffect 做泛光效果质感最强但性能开销也最大。我的建议是如果能接受一点金属质感优先用 realistic如果要在十几张图的大屏上大量使用则用 lambert可以通过 material.color 直接给一个带饱和度的颜色再配两盏方向光效果不差。至于灯光我习惯配置一个环境光保证整体亮度再放两个方向光做一暖一冷的对比补光这样环形表面会有明显的沟壑感立体感一下就出来了。4.2 标签与数值展示的 3D 替代方案三维标签是 ECharts-gl 的弱项。虽然可以给 2D 图形设置 label但在 3D surface 上直接显示文字经常出现层级混乱、被遮挡、字体大小和视角不联动的问题。我在项目里的做法是不在环形本身上放 label而是在环的旁边叠加一个 2D 数字卡片用绝对定位实现。如果你确实需要在图上显示百分比可以先把环形图作为背景层然后在前方叠加一个轻量的 HTML 元素居中显示。这样一方面避免三维标签遮挡另一方面让数值更清晰。另一个思路是给环形图的中心配一个 2D 的饼图中心文字通过graphic元素或者普通 div 实现实际效果非常好。对于 Tooltip3D surface 系列的悬浮提示也比较有限我通常用chart.getZr().on(mousemove, handler)自行监听鼠标移动再结合业务数据计算最近的点位并展示自定义浮层。关于 tooltip 自动换行如果是 2D 图表的 tooltip直接用 formatter 返回带br/的字符串即可在 3D 场景下因为不走标准的 tooltip 体系建议直接写 HTML 浮层别在换行上绕弯子。4.3 与 2D 图表混搭让大屏信息更完整3D 环形图适合当视觉主角但它的缺点是无法精确读取每个分类的具体数值。所以我的大屏方案通常是中央放 3D 环形图左下角放一个 2D 饼图或环形图做明细分类右下角放纵向柱状图看趋势。这样既有 3D 的视觉冲击力又有 2D 的精确信息。混搭时要注意选择合适的 2D 图表配置。比如 2D 饼图的 labelLine 末尾小圆点经常发生偏移那个常见问题的原因是labelLine.endSymbol的偏移量没有随着 label 的 length 调整可以通过设置协调的length和endSymbol尺寸解决。折线图 x 轴刻度密度也要手动控制不要让刻度自动密集展示导致文字重叠改成axisLabel: { interval: auto, rotate: 30 }会更干净。多个图表在同一个大屏上联动时我可以让鼠标 hover 到 3D 环形图时通过chart.dispatchAction高亮 2D 饼图的某个扇区这种联动会显得很专业。原理是监听 zrender 的mousemove拿到当前点位的角度换算成数据索引再触发 2D 饼图的 highlight 动作。5. 常见问题与排查实录5.1 白屏和报错版本与加载顺序我遇到最多的问题是页面一片空白检查控制台发现Unable to find echarts-gl module或Unknown series surface。这基本都是在加载顺序上出了问题。用 CDN 时必须保证 echarts.min.js 先加载echarts-gl.min.js 后加载。用 npm 时import * as echarts from echarts和import echarts-gl也要按这个顺序写。另外版本一定要匹配我用的是 echarts 5.x 配 echarts-gl 2.x如果你项目里 ECharts 还是 3.x 或 4.x建议把 ECharts 升级到 5.x否则 ECharts-gl 的配置项可能不识别。还有一个隐藏雷区某些构建工具会做 tree-shaking如果 ECharts 是按需引入的比如只import { init } from echartsECharts-gl 的注册代码可能没有被正确执行。解决办法是在引入 ECharts-gl 之后显式调用一次echarts.init来确保模块注册完成或者直接用全量引入。5.2 画面发黑、闪烁和锯齿3D 环形图发黑90% 是灯光没配好。realistic 着色模式下如果只有一个默认方向光环面背光的部分几乎看不见像套了一层黑布。解决方法是加一个环境光强度给到 0.5 以上再补一个反方向的方向光。闪烁和锯齿多半是后处理导致。postEffect 启用时如果显卡性能不足画面会明显掉帧甚至闪烁。建议把 postEffect 的 enable 改成 false或者在 resize 时重新渲染。网格本身出现锯齿时把 surface 的步长缩小到 π/32 或 π/48但也要承受性能开销建议在大屏机实际测试。另外如果你发现环形图边缘有很明显的黑线尝试给 material 增加一点metalness: 0和roughness: 0.8往往能缓解。5.3 Vue3 项目中的坑resize、销毁和 pxtorem在 Vue3 项目里用这个方案有几个和框架生命周期强相关的问题。首先是 resizeECharts 实例需要跟随容器变化必须在window.resize事件里调用chart.resize()并在组件onUnmounted时调用chart.dispose()否则快速切换页面会导致内存泄漏。其次是 pxtorem 对 ECharts 不生效的问题。很多项目的全局样式用了 rem 适配方案但 ECharts 内容是 canvas 绘制它的文字和图表大小都基于初始化时的像素尺寸不会因为根字号变化而缩放。如果大屏适配用的是 rem请直接用chart.resize()配合外层容器的实际像素宽度来重绘或者在窗口变化时重新 init。不要试图用 CSS 去缩放 canvas那只会让图表模糊。示例片段import { onMounted, onUnmounted, ref } from vue; const chartEl ref(null); let chart null; function resizeChart() { chart chart.resize(); } onMounted(() { chart echarts.init(chartEl.value); chart.setOption(buildOption(0.75)); window.addEventListener(resize, resizeChart); }); onUnmounted(() { window.removeEventListener(resize, resizeChart); chart chart.dispose(); });5.4 tooltip 与数据更新不生效ECharts-gl 的 surface 系列对 setOption 的合并处理不稳定我遇到的情况是第一次更新正常第二次更新后 u.max 变了但画面不变。这就是为什么我在前面的代码里选择每次重新构建整个 series 对象而不是做增量字段更新。如果项目里需要频繁更新数据比如每十秒拉一次接口建议把 buildSeries 函数做成纯函数更新时用setOption({ series: [buildSeries(nextPercent)] }, true)第二个参数true表示 notMerge强制替换旧实例。实测这种方案最稳。Tooltip 方面如果实在需要点击或悬浮时显示某个点的三维坐标可以用 ECharts-gl 的chart.on(click, params)事件拿到params.data里面是 [x, y, z] 坐标和对应的数值。不过别指望它和 2D 的 tooltip 一样有漂亮的悬浮框更多还是需要自己写。最后再分享一个小技巧在做 3D 环形图的时候我建议先不调任何花活用最原始的默认模板跑通再一步步加颜色渐变、灯光、后处理。因为 ECharts-gl 报错信息很多时候比较泛如果一步到位反而很难定位是哪个配置出了问题。等你把默认模板跑顺后续改 R、r、progress 都是很顺手的事。而且这种几何图一旦跑通它的配置套路可以复制到很多地方比如 3D 圆柱、3D 圆环堆叠、3D 飞线核心都是这个 surface 参数方程的思路扩展空间很大。
返回列表