使用ThreeJS绘制一个饼图

Posted 曾胖神父

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用ThreeJS绘制一个饼图相关的知识,希望对你有一定的参考价值。

总体思路

使用THREE.Shape绘制一个外边圆弧和内边圆弧,让外边圆弧剔除内边圆弧,由于内边圆弧半径是可控的,所以也能通过这种办法绘制正常圆弧。最后使用THREE.ExtrudeGeometry挤出最终的图形,形成3D扇形图。制作单个扇形图之后,以此类推,制作剩下的扇形,最终将获得的扇形图集合,置入同一个THREE.Group中,形成一个3D饼图

具体代码(饼图)

import * as THREE from'three'
//饼图继承Group(对象组合)类
class Pie extends THREE.Group 
    constructor (option) 
      super()
      
      this.option = Object.assign(
        intervalAngle: 3,//每个扇形之间间距度数,代表每个扇形之间的间距,为0的话则无间距
        interval: 0,//每个扇形距离原本中心坐标点的额外距离,如果过高会导致扇形离中心点散开
      , option)
      //总值
      let sum = 0
      //总间距=扇形数量x每个扇形之间间距度数
      this.totalInterval = this.option.intervalAngle * this.option.items.length
       //遍历传入的数组,获取总值
      this.option.items.forEach(item => 
        sum += item.value
      )
      //绘制扇形总角度为
      this.totalAngle = 360 - this.totalInterval
      //随机起始度数
      let current = Math.random() * 360
      //根据传入的数组绘制多个扇形
      for (let i = 0, len = this.option.items.length; i < len; i++) 
        let item = this.option.items[i]
        //扇形
        let geometry = this.sector(5, 15, current, current + item.value / sum * this.totalAngle, this.option.height)
        // 赋予扇形材质
        let mesh = new THREE.Mesh(geometry, new THREE.MeshPhongMaterial( color: item.color, opacity: 1, transparent: true ))
        //扇形中心坐标点=(起始度数+最终度数)/2*Π/180
        let center = (current + current + item.value / sum * this.totalAngle) / 2 * Math.PI / 180
        //扇形坐标点=扇形中心坐标点*离中心点距离
        mesh.position.x += this.option.interval * Math.cos(center);
        mesh.position.y += this.option.interval * Math.sin(center);
  
        this.add(mesh)
        //起始度数加上值对应的间隔度数(值对应的间隔度数=总间隔度数*(值与总值的比例)) 
        current += item.value / sum * this.totalAngle
        //起始度数加上单位间隔度数
        current += this.option.intervalAngle
      
      //调整整体饼图的旋转角度,可加。也可在外部调整
      // this.rotation.x = -Math.PI / 3
    
    //绘制扇形(内边圆半径,外边圆半径,起始角度,终止角度,高度)
    sector (inRadius, outRadius, begin, end, height) 
      var shape = new THREE.Shape()
      //如果内边圆半径为0
      if (inRadius == 0) 
        //那么绘制扇形的圆心点为0
        shape.moveTo(0, 0)
       else 
        //否则设置绘制扇形的圆心点内边圆半径
        shape.moveTo(inRadius, 0)
        //绘制内边框扇形圆弧
        shape.absarc(0, 0, inRadius, begin / 180 * Math.PI, end / 180 * Math.PI, false)
      
      
      shape.lineTo(outRadius * Math.cos(end / 180 * Math.PI), outRadius * Math.sin(end / 180 * Math.PI))
       //绘制外边框扇形圆弧
      shape.absarc(0, 0, outRadius, end / 180 * Math.PI, begin / 180 * Math.PI, true)
      
      shape.lineTo(inRadius * Math.cos(begin / 180 * Math.PI), inRadius * Math.sin(begin / 180 * Math.PI))
      //将绘制的平面扇形挤出设置的高度,使其变成3D扇形(shape平面扇形,amount挤压高度,bevelEnabled是否设置斜角,steps指定拉伸体沿深度方向分成多少段)
      return new THREE.ExtrudeGeometry(shape,  amount: height, bevelEnabled: false, steps: 1 )
    
    //添加文字(暂无)
    addText (text, x, y, z) 
  
    
  
  
  export default Pie

具体代码(使用场景实例)

<template>
  <div class="hisotyChart">

  </div>
</template>
<style scoped lang="scss">
.hisotyChart 
  width: 90%;
    height:100%;
  // height: 100vh;
  // width: 100vw;
  margin-bottom: 3%;
  background-color: cornflowerblue;
  canvas
    width: 100%;
    height:100%;
  

</style>
<script>
import * as THREE from 'three';
import  OrbitControls  from 'three/examples/jsm/controls/OrbitControls';
import Pie from './3DChart/3DPie';


const $ = s => document.querySelector(s);
let pyramid = null;
//展示模型
let showModel = null;
//摄像头
let camera = null;
//场景
let scene = null;

//灯光
let light = null;
//渲染器
let render = null;
//用户交互插件
let controls = null;
let group = new THREE.Group();

export default 
  name: 'HisotyChart',
  data() 
    return 

    ;
  ,
  methods: 
  

    //场景初始化
    initScene() 
      console.log("绘制场景");
      scene = new THREE.Scene();
      scene.background = new THREE.Color(0xa0a0a0);
      console.log("绘制场景结束");
    ,
    //初始化摄像头
    initCamera() 
      console.log("绘制场景");
      camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
      // -7.951022465039643, y: 50.66599857875026, z: 84.02389415272548
      camera.position.set(0, 0, 0);
      console.log(camera);

      camera.lookAt(new THREE.Vector3(0, 0, 0));
    ,
    //初始化灯光
    initLight() 
      scene.add(new THREE.AmbientLight(0x444444));
      light = new THREE.SpotLight(0xffffff);
      // light.position.set(0, 1.25, 1.25);
      // light.position.set(0, -110, 20);
      light.matrixWorldAutoUpdate = true;
      light.position.set(165.8, 35.6, 80.6);

      //告诉点光需要开启阴影投射
      light.castShadow = true;
      light.shadow.bias = -0.000005;
      light.shadow.mapSize.width = 2048; //阴影贴图宽度设置为1024像素
      light.shadow.mapSize.height = 2048; //阴影贴图高度设置为1024像素
      var ligntCameraHelper = new THREE.SpotLightHelper(light, 20);
      ligntCameraHelper.visible = false;
      // let Ambient = new THREE.AmbientLight(0x404040, 2);
      scene.add(ligntCameraHelper);
      scene.add(light);
      var shadowCameraHelper = new THREE.CameraHelper(light.shadow.camera);
      shadowCameraHelper.visible = false;
      // let Ambient = new THREE.AmbientLight(0x404040, 2);
      scene.add(shadowCameraHelper);
    ,
    //3D饼图初始化
    initPie() 
      pyramid = new Pie(
        items: [
          value: Math.random() * 10,
          color: '#DE5347'
        , 
          value: Math.random() * 10,
          color: '#3DCE3D'
        , 
          value: Math.random() * 10,
          color: '#0080FF'
        ,
      
        value: Math.random() * 10,
          color: '#A473EA'
      ],
        text: (item) => 
          return 'value-' + item.value
        
      );
      pyramid.rotation.x = -Math.PI / 3;
      group.add(pyramid);
      scene.add(group);
    ,

    //渲染器初始化
    initRender() 
      console.log("渲染渲染器");
      render = new THREE.WebGLRenderer( antialias: true );
     // render.setSize(window.innerWidth, window.innerHeight);
      //修改渲染器输出格式
      render.outputEncoding = THREE.sRGBEncoding;
      render.shadowMap.enabled = true;
      render.shadowMap.type = THREE.PCFSoftShadowMap;
      //渲染器添加toneMapping效果
      // render.toneMapping = THREE.ACESFilmicToneMapping;
      //告诉渲染器需要阴影效果 
      render.setClearColor('#1F2025', 1.0);
      render.domElement.style.width="100%";
      render.domElement.style.height="100%";
      $('.hisotyChart').appendChild(render.domElement);

    ,
    //用户插件初始化
    initControls() 
      controls = new OrbitControls(camera, render.domElement);
      // 使动画循环使用时阻尼或自转 意思是否有惯性
      controls.enableDamping = true;
      //动态阻尼系数 就是鼠标拖拽旋转灵敏度
      //controls.dampingFactor = 0.25;
      //是否可以缩放
      controls.enableZoom = true;
      //是否自动旋转
      controls.autoRotate = true;
      controls.autoRotateSpeed = 0.5;
      //设置相机距离原点的最远距离
      controls.minDistance = 1;
      //设置相机距离原点的最远距离
      controls.maxDistance = 500;
      //是否开启右键拖拽
      controls.enablePan = true;
    ,


    render() 
      render.render(scene, camera);
     

    ,

    //窗口变动触发的函数
    onWindowResize() 
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      this.render();
      //render.setSize(window.innerWidth, window.innerHeight);
    ,
    animate() 
      //更新
      requestAnimationFrame(this.animate);

      this.render();
    ,

    //绘制
    draw() 
      console.log("开始绘制");
      this.initScene();
      this.initCamera();
      this.initRender();
      this.initLight();
      this.initControls();
      this.initPie();
     
      this.animate();
      window.onresize = this.onWindowResize;
    

  ,
  created()
    
  ,
  mounted() 
    this.draw();
  

</script>

<!-- <template>
  <div class="hisotyChart">
      
  </div>
</template>
<script>
  export default
     name:"HisotyChart"
  
</script>
<style scoped lang="scss">
   .hisotyChart
     height: 90%;
     width: 100%;
     margin-bottom: 3%;
     background-color: cornflowerblue;

   
</style> -->

最终效果(空心圆饼图)

最终效果(标准实心圆饼图)


设置内边圆为0,即可获得实心圆

设置扇形间距距离为0,使其无间距。
最终效果如下

以上是关于使用ThreeJS绘制一个饼图的主要内容,如果未能解决你的问题,请参考以下文章

R语言 | 绘制饼图(扇形图)方法示例

python添加饼图扇形面积

自定义UI 绘制饼图

自定义UI 绘制饼图

自定义UI 绘制饼图

扇形图+雷达图+箱型图绘制