Three.js实战:打造交互式3D饼图与环形图

  之前就有小伙伴评论说让实现一下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
// 创建3D扇环形状
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的扇形;然后给我们的几何体添加材质,设置颜色,并添加到场景中:

1
2
3
4
5
6
7
8
9
10
11
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饼图是一个个的3D扇形组成的,我们先设置每个扇形在Y轴方向上的缩放为0:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default class BasicPie {
// 扇形物体
pieList: Mesh<ExtrudeGeometry, MeshPhongMaterial>[] = []

initMesh() {
for (let i = 0; i < list.length; i++) {
const pieMesh = new Mesh(geo, material)
// 设置Y轴初始化缩放为0
pieMesh.scale.set(1, 0, 1)
this.pieList.push(pieMesh)
this.scene.add(pieMesh)
}
}
}

  我们在创建几何体的时候,设置scale的Y轴为0,这样扇形默认处于缩放不呈现的状态;然后物体创建完成后,我们就可以开始执行动画了:

1
2
3
4
5
6
7
8
9
10
// 初始化动画
initAnimate() {
this.pieList.map((el, index) => {
gsap.to(el.scale, {
y: 1,
duration: 1,
ease: "circ.out",
})
})
}

  我们就能看到一个初始化的升降效果:

初始化升降效果

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


本网所有内容文字和图片,版权均属谢小飞所有,任何媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表。如需转载请关注公众号【前端壹读】后回复【转载】。