封面图
如果你厌倦了echarts等2d可视化的方案,那么没事儿的时候可以琢磨一下3d可视化的方案。
这里简单实现一个3D的柱状图,写这个demo的原因是上节文章讲了threejs中的基本的几何体,所以写这个demo做个案例说明。一方面加深印象,一方面给大家以启示。
所用到的知识点
- 平面几何 planegeometry
- 立方体几何 boxgemoetry
- CSS2DRenderer 2d渲染器
- CSS2DObject 2d 对象
- OrbitControls 轨道控制器
基本实现的逻辑是:创建平面、创建、x轴及y轴对应的墙、创建数据所对应的柱子及具体的数值。
实现过程
第一步必然是创建场景、相机、灯光、渲染器:
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js'
import { CSS2DRenderer } from 'three/addons/renderers/CSS2DRenderer.js'
const scene = new THREE.Scene()
const xgroup = new THREE.Group()
// 光
const light = new THREE.AmbientLight(0xffffff); // soft white light
scene.add(light);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
directionalLight.position.set(50, 250, 500);
scene.add(directionalLight);
var directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.5);
directionalLight2.position.set(-400, -400, -400);
scene.add(directionalLight2)
// 相机
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.set(314, 202, 243);
camera.lookAt(scene.position);
// 渲染器
const renderer = new THREE.WebGLRenderer()
然后写个初始化方法init()用来加载我们的各种内容:
const init =() => {
renderGeomtry()
renderFloor()
addYPlane()
addXPlane()
addHelper()
renderLabel()
if (gemoDom.value) {
renderer.setSize(gemoDom.value.clientWidth, gemoDom.value.clientHeight)
renderer.render(scene, camera)
gemoDom.value.appendChild(renderer.domElement)
gemoDom.value.appendChild(labelRenderer.domElement)
}
}
我这几个方法的主要作用如下:
- renderGeomtry 加载数据对应的柱子
- renderFloor 加载地面网格
- addYPlane 加载y轴对应的面
- addXPlane 加载x轴对应的面
- addHelper 添加辅助坐标系
- renderLabel 加载数据对应的柱子顶部的数值
其中最主要的是renderGeomtry
:
const renderGeomtry = () => {
scene.clear()
for (let i = 0; i < testData.length; i++) {
const geometry = new THREE.BoxGeometry(10, 10, testData[i].value, 100);
const material = new THREE.MeshBasicMaterial({ color: color[i] });
const cube = new THREE.Mesh(geometry, material);
cube.translateX(i * 20 + 20)
cube.translateY(testData[i].value / 2)
cube.rotateX(Math.PI / 2)
xgroup.add(cube)
// 添加标签
const text = labelDom.value.cloneNode(true);
text.style.visibility = "visiblie";
text.className = "label";
text.childNodes[0].textContent = testData[i].value;
const label = new CSS2DObject(text);
label.position.copy(cube.position);
label.position.setY(cube.position.y + 20)
xgroup.add(label)
const xaxisName = labelNameDom.value.cloneNode(true);
xaxisName.style.visibility = "visiblie";
xaxisName.className = "xaxisName";
xaxisName.childNodes[0].textContent = testData[i].name;
const nameLabel = new CSS2DObject(xaxisName);
nameLabel.position.copy(cube.position);
console.log('cube.position.y', cube.position.y)
nameLabel.rotateZ(90)
xgroup.add(nameLabel)
}
scene.add(xgroup)
}
这里通过遍历数据,创建对应的柱子后,通过平移、旋转,将其放置在合适的位置后,通过CSS2DObject
对象创建2d标签对象,用来渲染柱子对应的值。
其他的都是些常规的操作,比如addYPlane
:
const addYPlane = () => {
const yPlan = new THREE.BoxGeometry(5, 100, 100, 20, 50)
const yMate = new THREE.MeshBasicMaterial({
color: 0xffffff,
wireframe: true
})
const yAxisWall = new THREE.Mesh(yPlan, yMate,)
yAxisWall.translateY(50)
scene.add(yAxisWall)
}
addYPlane
就是创建一个平面,通过平移移动到合适的位置。
另外一个可以简单说一下的就是renderLabel
这个方法,这个方法用到了2d渲染器,2d渲染器的用法和WebGLRenderer
的用法一致,我们可以看下代码:
const labelRenderer = new CSS2DRenderer()
// ...
// 加载值-用2d加载器
const renderLabel = () => {
// 2d渲染
if (gemoDom.value) {
labelRenderer.setSize(gemoDom.value.clientWidth, gemoDom.value.clientHeight);
} else {
labelRenderer.setSize(window.innerWidth, window.innerHeight);
}
labelRenderer.domElement.style.position = 'absolute'
labelRenderer.domElement.style.top = '0px'
labelRenderer.domElement.style.pointerEvents = 'none'
labelRenderer.render(scene, camera);
}
// ...
if (gemoDom.value) {
renderer.setSize(gemoDom.value.clientWidth, gemoDom.value.clientHeight)
renderer.render(scene, camera)
gemoDom.value.appendChild(renderer.domElement)
// 添加2d内容
gemoDom.value.appendChild(labelRenderer.domElement)
}
定义labelRenderer
设置好位置及样式后,通过:
labelRenderer.render(scene, camera);
// 添加2d内容
xxx.appendChild(labelRenderer.domElement)
进行渲染。
最后,我们加上动画:
function render() {
scene.rotateY(0.001)
requestAnimationFrame(render);
renderer.render(scene, camera)
labelRenderer.render(scene, camera);
}
render()
就达到了下面的效果:
最后
这里基于上篇文章讲到的基本几何图形,以这个简单的示例来演示几何图形的用法,希望对大家有所帮助,谢谢