封面图

第176期:threejs 绘制3D-柱状图_前端

如果你厌倦了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标签对象,用来渲染柱子对应的值。

第176期:threejs 绘制3D-柱状图_渲染器_02

其他的都是些常规的操作,比如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()

就达到了下面的效果:

第176期:threejs 绘制3D-柱状图_JavaScript_03

最后

这里基于上篇文章讲到的基本几何图形,以这个简单的示例来演示几何图形的用法,希望对大家有所帮助,谢谢