因为项目里同时有echarts的地图,地图需要弹跳动画,还有2d饼图和3d饼图.这里有一个坑,动画必须要ECharts 5.3.0+,而地图弹跳动画 → 需要 ECharts 5.3.0+ → 但 5.3.0+ 又和 echarts-gl 不兼容 → 3D 饼图出不来。所以这里用的是threejs,效果如下
先需要下载threejs
npm install three<template><div class="chart_3dPie_box"><div ref="chartContainer"class="chart-container"/><div class="total_num"><div class="num">{{chartData}}</div><div class="text">告警总数</div></div><div class="flex-space-between chart-legend"><div v-for="(item, index) in lableData":key="index"class="chart-item"><div class="chart_item_label flex_center"><div:style="{ backgroundColor: item.color }"class="chart_item_color"/>{{item.label}}</div><div:style="{ color: item.color }"class="chart_item_value">{{item.value}}</div></div></div></div></template><script>import*as THREE from"three";import{OrbitControls}from"three/examples/jsm/controls/OrbitControls";import fontData from"../../../../assets/font/DINPro-Regular.otf";exportdefault{name:"ThreeDPieChart",computed:{chartData(){// 总和returnthis.config.data.reduce((sum,item)=>sum+item.value,0);},},data(){return{lableData:[{label:"设备异常",value:70,color:"#FFCC26"},{label:"水质异常",value:45,color:"#00FFFF"},{label:"电气异常",value:20,color:"#FF4747"},{label:"采集异常",value:56,color:"#4BFF64"},{label:"工艺异常",value:13,color:"#FFFC19"},],config:{data:[{label:"电气异常",value:100},{label:"水质异常",value:45},{label:"电气异常",value:20},{label:"采集异常",value:56},{label:"工艺异常",value:13},],colors:["#FFCC26","#00FFFF","#FF4747","#4BFF64","#FFFC19"],height:10,heightFactor:4,},renderer:null,scene:null,camera:null,controls:null,font:null,animationId:null,};},mounted(){this.initChart();window.addEventListener("resize",this.handleResize);},beforeDestroy(){this.cleanup();},methods:{initChart(){if(!this.$refs.chartContainer)return;constcontainerWidth=this.$refs.chartContainer.clientWidth;constcontainerHeight=this.$refs.chartContainer.clientHeight;constoutR=Math.min(containerWidth,containerHeight)*0.58;// 调整饼图的大小constinnerR=outR*0.7;// 内圈大小// 1. 初始化渲染器(启用抗锯齿和更高的阴影质量)this.renderer=new THREE.WebGLRenderer({antialias:true,alpha:true,// 允许透明背景});this.renderer.setSize(containerWidth,containerHeight);this.renderer.shadowMap.enabled=true;this.renderer.shadowMap.type=THREE.PCFSoftShadowMap;// 更柔和的阴影this.renderer.outputEncoding=THREE.sRGBEncoding;// 更好的颜色渲染this.$refs.chartContainer.appendChild(this.renderer.domElement);// 2. 创建场景(设置适当的背景色)this.scene=new THREE.Scene();// 添加光源constlight1=new THREE.PointLight(0xfff3e0,0.5);light1.position.set(0,1200,2160);this.scene.add(light1);// 环境光(调整强度解决颜色变暗)constambientLight=new THREE.AmbientLight(0xffffff,0.6);this.scene.add(ambientLight);// 4. 创建相机this.camera=new THREE.OrthographicCamera(containerWidth/-2,containerWidth/2,containerHeight/2,containerHeight/-2,1,2000);// 特写镜头:相机距离拉近this.camera.position.set(0,1000,1200);this.camera.lookAt(0,0,0);// 5. 控制器设置this.controls=newOrbitControls(this.camera,this.renderer.domElement);// 6. 加载字体(添加加载状态提示)this.loadFont(outR,innerR);},loadFont(outR,innerR){constfontLoader=new THREE.FontLoader();this.font=fontLoader.parse(fontData);this.createPieChart(outR,innerR);},createPieChart(outR,innerR){constgroup=new THREE.Group();group.rotation.x=-Math.PI/2;// 更精确的旋转group.position.y=10;// 移动饼图的位置,向下移动 30 个单位this.scene.add(group);consttotalValue=this.config.data.reduce((sum,item)=>sum+item.value,0);let startAngle=0;this.config.data.forEach((item,index)=>{constangleLength=(item.value/totalValue)*Math.PI*2;// 使用弧度制更精确constheight=this.config.height+(item.value/totalValue)*this.config.height*this.config.heightFactor;// 使用更鲜艳的颜色constcolor=new THREE.Color(this.config.colors[index]);color.convertSRGBToLinear();// 确保颜色正确渲染this.createPieSegment(group,outR,innerR,height,startAngle,angleLength,color,item.value,item.label// 添加标签显示);startAngle+=angleLength;});this.animate();},createPieSegment(group,outR,innerR,height,startAngle,angleLength,color,text,label){// 1. 创建形状constshape=new THREE.Shape();shape.absarc(0,0,outR,startAngle,startAngle+angleLength,false);shape.lineTo(Math.cos(startAngle+angleLength)*innerR,Math.sin(startAngle+angleLength)*innerR);shape.absarc(0,0,innerR,startAngle+angleLength,startAngle,true);// 2. 挤出设置constextrudeSettings={curveSegments:100,steps:2,depth:height,bevelEnabled:true,bevelThickness:1,bevelSize:0,bevelOffset:0,bevelSegments:1,};// 3. 创建网格(使用更亮的材质)constgeometry=new THREE.ExtrudeGeometry(shape,extrudeSettings);constmaterial=new THREE.MeshPhongMaterial({color:color,shininess:20,roughness:0.6,});constmesh=new THREE.Mesh(geometry,material);group.add(mesh);// 4. 添加文本(如果字体已加载)if(this.font){this.addTextToSegment(mesh,outR,innerR,height,startAngle,angleLength,text);}// 5. 添加标签(可选)this.addLabelToSegment(group,outR,startAngle,angleLength,label);},addTextToSegment(mesh,outR,innerR,height,startAngle,angleLength,text){try{// 计算文本位置和角度constmidAngle=startAngle+angleLength/2;constradius=(outR+innerR)/2;// 创建文本几何体consttextGeometry=new THREE.TextGeometry(text,{font:this.font,size:11,height:2,curveSegments:12,bevelEnabled:false,});// 计算文本居中textGeometry.computeBoundingBox();consttextWidth=textGeometry.boundingBox.max.x-textGeometry.boundingBox.min.x;// 创建文本材质(更醒目的颜色)consttextMaterial=new THREE.MeshPhongMaterial({color:0xffffff,});consttextMesh=new THREE.Mesh(textGeometry,textMaterial);// 定位和旋转文本textMesh.position.set(Math.cos(midAngle)*radius-textWidth/2,Math.sin(midAngle)*radius-10,height+0);textMesh.rotation.set(120,// X轴旋转90度使文字立起来0,// Y轴不需要旋转0// Z轴旋转使文字朝向圆心);// textMesh.rotation.z = midAngle + Math.PI / 2;// textMesh.rotation.x = Math.PI / 2;mesh.add(textMesh);}catch(error){console.error("创建文本失败:",error);}},addLabelToSegment(group,radius,startAngle,angleLength,label){// 创建简单的标签(使用CSS2DRenderer或Three.js精灵)// 这里简化为控制台输出console.log(`Segment Label:${label}`);},animate(){this.animationId=requestAnimationFrame(this.animate);this.controls.update();this.renderer.render(this.scene,this.camera);},handleResize(){if(!this.renderer||!this.camera||!this.$refs.chartContainer)return;constwidth=this.$refs.chartContainer.clientWidth;constheight=this.$refs.chartContainer.clientHeight;this.camera.left=width/-2;this.camera.right=width/2;this.camera.top=height/2;this.camera.bottom=height/-2;this.camera.updateProjectionMatrix();this.renderer.setSize(width,height);},cleanup(){window.removeEventListener("resize",this.handleResize);if(this.animationId){cancelAnimationFrame(this.animationId);}if(this.renderer&&this.$refs.chartContainer&&this.$refs.chartContainer.contains(this.renderer.domElement)){this.$refs.chartContainer.removeChild(this.renderer.domElement);}// 释放资源if(this.scene){while(this.scene.children.length>0){this.scene.remove(this.scene.children[0]);}}},},};</script><style lang="scss"scoped>.chart_3dPie_box{position:relative;.chart-container{position:absolute;top:0;left:0;width:507px;height:343px;padding:0;overflow:hidden;// background: url(~@/assets/images/dz_img.png) center bottom 40%/200px 87px no-repeat;background:url("~@/assets/images/dz_img.png")no-repeat center center;background-size:100%/200px100%;}.total_num{width:507px;height:343px;position:absolute;left:0;top:0;display:flex;flex-direction:column;align-items:center;justify-content:center;.num{position:absolute;top:88px;font-family:SourceHanSansCN-Bold;font-weight:bold;font-size:54px;color:#ffffff;}.text{position:absolute;bottom:142px;font-family:Source Han Sans CN;font-weight:400;font-size:32px;color:#b0e8ff;}}.chart-legend{position:absolute;top:0;right:0;width:calc(50%-121px);padding:36px0;display:flex;flex-direction:column;// gap: 14px;.chart-item{width:100%;padding-right:94px;display:flex;align-items:center;justify-content:space-between;.chart_item_color{width:24px;height:24px;margin-right:19px;}.chart_item_label{font-family:Source Han Sans CN;font-weight:400;font-size:32px;color:#d1deee;}.chart_item_value{font-family:SourceHanSansCN-Bold;font-weight:bold;font-size:34px;color:#ffcc26;}}}}</style>