之前就有小伙伴评论说让实现一下3D饼图的效果,今天它终于来了!作为一个数据可视化中经久不衰的经典图表,3D饼图不仅能更直观地展示数据比例关系,还能通过立体的视觉效果增强整体的视觉冲击力。本文将带你从最基础的扇形绘制开始,逐步实现包括升降动画、环形饼图、高低对比图、分隔效果以及标签展示在内的完整3D饼图方案。无论你是Three.js的初学者,还是希望提升数据可视化技能的老手,相信这篇文章都能为你带来实用的技术思路和实现方案。让我们一起来探索Three.js在3D数据可视化方面的无限可能吧!
本文所有效果敬请访问饼图与环形图效果
如何绘制一个3D扇形
在实现效果之前,我们来思考一个最重要的问题,那就是如何通过Three.js绘制一个3D扇形呢?笔者翻遍了文档中所有的几何体,大概有以下两种方案;第一种就是利用圆柱缓冲几何体(CylinderGeometry),也就是文档上的这个形状的物体:

通过设置它的起始角度(thetaStart)和结束角度(thetaLength),我们也能实现3D扇形效果;不过从文档上我们也能看出来,它是一个不封闭的图形,如果我们不做扇形的高度落差处理,那么不封闭是看不出来的;但是如果我们后面做扇形点击后升高和降低高度的动画,那么不封闭的图形就会有明显的问题。
另一种方式就是通过挤压缓冲几何体(ExtrudeGeometry),先用Shape绘制一个扇形,然后通过ExtrudeGeometry进行挤压,也可以得到一个3D的扇形,而且相比圆柱缓冲几何体,挤压出来的扇形是封闭的,还通过设置高度,实现高度升降的效果。
谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里
方案确定后,我们开始来实现效果;首先我们根据列表中的数据,计算总的数量,然后每个扇形对应的弧度就可以通过列表每一项数值 / 总数 * 2 * Math.PI来计算得到了;同时由于每次绘制扇形都是从上一次扇形结束的地方开始绘制,所以我们需要保存之前累计的扇形弧度:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| initMesh() { const total = list.reduce((acc, cur) => acc + cur.value, 0); let last = 0; for (let i = 0; i < list.length; i++) { const { value } = list[i]; const rad = (value / total) * 2 * Math.PI; const geo = this.createPieGeometry(last, rad) last += rad; } }
|
这样我们就通过计算得到的rad弧度来创建扇形几何体了,它表示当前列表项对应的弧度;在每次创建扇形时,我们通过last保存之前的累计弧度,然后作为当前扇形的起始弧度,这样就能保证每次创建的扇形是连续的,并且是从上一个扇形的结束地方开始绘制的。
谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| createPieGeometry(startAngle: number, angleRange: number, segments: number = 32) { const shape = new Shape() shape.absarc(0, 0, 3, startAngle, startAngle + angleRange, false)
shape.absarc(0, 0, 3, startAngle + angleRange, startAngle, true)
shape.closePath()
const extrudeSettings = { depth: this.config.depth, bevelEnabled: false, curveSegments: segments, }
const geometry = new ExtrudeGeometry(shape, extrudeSettings)
geometry.rotateX(-Math.PI / 2)
return geometry }
|
这里我们通过Shape创建一个2D扇形,然后通过ExtrudeGeometry进行拉伸,得到一个3D的扇形;然后给我们的几何体添加材质,设置颜色,并添加到场景中:
| for (let i = 0; i < list.length; i++) { const geo = this.createPieGeometry(last, rad) const material = new MeshPhongMaterial({ color: Math.random() * 0xffffff, side: DoubleSide, transparent: true, }) const pieMesh = new Mesh(geo, material) this.scene.add(pieMesh) }
|
这样我们就能看到一个基本的3D饼图效果了:

谢小飞博客专用防爬虫链接,想要看最新的前端博客请点这里
初始动画效果
我们让饼图有初始化的一个升起动画效果,经常听老板画饼的小伙伴都知道,3D饼图是一个个的3D扇形组成的,我们先设置每个扇形在Y轴方向上的缩放为0:
| export default class BasicPie { pieList: Mesh<ExtrudeGeometry, MeshPhongMaterial>[] = []
initMesh() { for (let i = 0; i < list.length; i++) { const pieMesh = new Mesh(geo, material) pieMesh.scale.set(1, 0, 1) this.pieList.push(pieMesh) this.scene.add(pieMesh) } } }
|
我们在创建几何体的时候,设置scale的Y轴为0,这样扇形默认处于缩放不呈现的状态;然后物体创建完成后,我们就可以开始执行动画了:
| initAnimate() { this.pieList.map((el, index) => { gsap.to(el.scale, { y: 1, duration: 1, ease: "circ.out", }) }) }
|
我们就能看到一个初始化的升降效果:

剩下全文敬请访问知乎文章链接阅读。