ECharts GL 3D饼图组件深度解析:从数学原理到工程实践
本文将深入分析一个基于 ECharts GL 的 3D 饼图 Vue 组件,揭示其背后的数学原理、技术实现和性能优化策略。
效果预览:

组件概述
pie3D.vue 是一个基于 ECharts GL 的高级 3D 饼图组件,它通过参数方程和 WebGL 技术实现了真正的三维饼图效果,而非简单的伪3D视觉效果。该组件在数据可视化、仪表盘展示等场景中具有重要价值。
核心特性
-
真正的3D渲染:基于 WebGL 的硬件加速渲染
-
参数方程建模:使用数学方程精确控制3D形状
-
丰富的交互:支持悬停、选中、自动旋转等效果
-
完整的3D场景:包含底座、圆环等装饰元素
-
响应式设计:基于 ResizeObserver 的智能尺寸适配
数学原理深度解析
1. 参数方程基础
3D饼图的核心是参数方程,它定义了三维空间中的曲面:
function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, height) {// 计算弧度const startRadian = startRatio * Math.PI * 2const endRadian = endRatio * Math.PI * 2const midRadian = (startRatio + endRatio) * Math.PIreturn {u: { min: -Math.PI, max: Math.PI * 3, step: Math.PI / 32 },v: { min: 0, max: Math.PI * 2, step: Math.PI / 20 },x: function(u, v) {// 扇形x坐标计算if (u < startRadian) return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRateif (u > endRadian) return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRatereturn offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate},y: function(u, v) {// 扇形y坐标计算if (u < startRadian) return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRateif (u > endRadian) return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRatereturn offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate},z: function(u, v) {// 扇形高度计算,包含前后权重const anglePos = (u + Math.PI) / (Math.PI * 4)const frontWeight = Math.max(0, 1 - Math.abs(anglePos - 0.5) * 2)let baseZif (u < -Math.PI * 0.5 || u > Math.PI * 2.5) {baseZ = Math.sin(u)} else {baseZ = Math.sin(v) > 0 ? props.heightProportion * height : -1}return baseZ + frontWeight * 0.3}}
}
数学原理说明:
-
u参数:控制扇形在圆周方向的位置
-
v参数:控制扇形在径向的厚度
-
k值:控制内径与外径的比例
k = (1 - internalDiameterRatio) / (1 + internalDiameterRatio) -
hoverRate:悬停时的缩放系数
2. 3D坐标变换
// 位置偏移计算(选中效果)
const offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0
const offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0// 高度计算策略
const baseZ = Math.sin(v) > 0 ? props.heightProportion * height : -1
技术架构详解
1. 组件参数设计
const props = defineProps({data: {type: Array,default: () => [{ name: '默认数据1', val: 100, color: 'rgba(56, 136, 235, 1)' }]},unit: { type: String, default: '%' },heightProportion: { type: Number, default: 0.3 }, // 高度比例系数internalDiameterRatio: { type: Number, default: 0 }, // 内径比例backgroundColor: { type: String, default: '#FFFFFF' }
})
2. 系列数据生成算法
function getPie3D(pieData) {const series = []let sumValue = 0let startValue = 0let endValue = 0const k = (1 - props.internalDiameterRatio) / (1 + props.internalDiameterRatio)// 计算数据总和pieData.forEach(item => { sumValue += item.val })// 生成每个扇形pieData.forEach((item, i) => {endValue = startValue + item.valconst seriesItem = {name: item.name,type: 'surface',parametric: true,wireframe: { show: false },pieData: {...item,startRatio: startValue / sumValue,endRatio: endValue / sumValue,value: Number(((item.val / sumValue) * 100).toFixed(2))},parametricEquation: getParametricEquation(...)}series.push(seriesItem)startValue = endValue})return series
}
3. 指示线系统
// 计算指示线位置
const midRadian = (seriesItem.pieData.startRatio + seriesItem.pieData.endRatio) * Math.PI
const radius = 1 + k
const posX = Math.cos(midRadian) * radius
const posY = Math.sin(midRa