效果图
第一步
在html文件里面引入相关js
<script src="http://oemcustom.gsdata.cn/hjdp/static/js/three.min.js"></script>
<script src="https://johnson2heng.github.io/three.js-demo/lib/threebsp.js"></script>
第二步
3d饼图组件代码
<!-- -->
<template>
<div class="three3div" :style="{width:`${innerWidth}px`,height:`${innerHeight}px`}">
<div id="threedPei" :style="{width:`${innerWidth}px`,height:`${innerHeight}px`}" class=" _3dpiebg"></div>
<div class="labelPie">
<div class="d-flex mt20" v-for="(item,index) in arry" :key="index" @mouseenter="getValue(item.name)" @mouseleave="noValue()">
<div class="list" :style="{background:item.color}"></div>
<div>{{item.name}}</div>
</div>
</div>
<div class="showValue" v-show="isShow">
<div style="font-size:40px;">{{column}}%</div>
<div style="font-size:24px;padding-top:7px;">{{columnName}}</div>
</div>
</div>
</template>
<script>
var scene,camera,renderer,ambientLight,directionalLight;//分别对应场景,相机,渲染器,环境光,方向光
var arr = [];
let moveBlock = '';
// 创建场景
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
// var loader = new THREE.ObjectLoader();
export default {
data () {
return {
isShow:false,
column:"",
columnName:'',
ringGroup : []
};
},
props:{
arry:{
type: Array,
default:() => []
},
innerWidth:{
type:Number,
default:640
},
innerHeight:{
type:Number,
default:320
},
},
watch: {
arry:{
handler(){
//移出渲染模型,环境和照射光源,释放内存
scene.remove(ambientLight);
scene.remove(directionalLight);
for(let item of this.ringGroup){
scene.remove(item);
}
let element,pElements = document.getElementById("threedPei").getElementsByTagName('canvas');
// 删除所有cavans
while (pElements.length>0) {
element = pElements[0];
element.parentElement.removeChild(element);
}
this.initPei();
}
}
},
created(){},
mounted(){
},
methods: {
initPei (){
var all = 0;
arr = [];
this.ringGroup = [];
for(var i of this.arry){
all += parseInt(i.value);
}
var len = this.arry.length;
for(var k in this.arry){
var startHorn = null,endHorn = null,val = null;
val = (parseInt(this.arry[k].value)/all*2).toFixed(2);
if(k == 0){
startHorn = 0;
endHorn = val;
}else if(k > 0 && k < len-1){
startHorn = arr[k-1].endHorn + arr[k-1].startHorn;
endHorn = val;
}else if(k == len-1){
startHorn = arr[k-1].endHorn + arr[k-1].startHorn;
// console.log(arr[k-1].endHorn,arr[k-1].startHorn)
endHorn = 2-startHorn;
}
arr.push({color:this.arry[k].color,startHorn:parseFloat(startHorn),endHorn:parseFloat(endHorn),name:this.arry[k].name})
}
// scene.background = new THREE.Color( 0xa0a0a0 );
// 创建相机
camera = new THREE.PerspectiveCamera(30, this.innerWidth/this.innerHeight, 0.1, 1000);
// 渲染器
renderer.setSize(this.innerWidth, this.innerHeight);
renderer.setClearAlpha(0);
// 创建环境光
ambientLight = new THREE.AmbientLight( 0xf9f9f9 );
scene.add( ambientLight );
// 创建方向光
directionalLight = new THREE.DirectionalLight( 0x444444 );
directionalLight.position.set( .5, 0.75, 0.5 ).normalize();
scene.add( directionalLight );
this.makeRing();
camera.position.x = -30;
camera.position.y = 50;
camera.position.z = 60;
camera.lookAt(new THREE.Vector3(0, 0, 0));
renderer.render(scene, camera);
this.renderScene();
document.getElementById("threedPei").addEventListener('mousemove', this.onDocumentMouseMove, false);
document.getElementById("threedPei").appendChild(renderer.domElement);
},
renderScene() {
renderer.clear();
requestAnimationFrame(this.renderScene);
renderer.render(scene, camera);
},
// 创建几何体
createMesh(geom,color) {
var cylinderMat = new THREE.MeshLambertMaterial({//创建材料
color:color,
wireframe:false
});
var mesh = new THREE.Mesh(geom, cylinderMat);
return mesh;
},
// 创建单个圆环
makeRing(){
var geometry = new THREE.BoxGeometry(40, 4, 0);
var material = new THREE.MeshBasicMaterial({
color: 0xfff
});
let start = .5;
for(let i of arr){
var cube1;
var cube2;
cube1 = new THREE.Mesh(geometry, material);
cube2 = new THREE.Mesh(geometry, material);
cube1.rotateY(Math.PI * (start+i.startHorn))
cube2.rotateY(Math.PI * (start+i.startHorn+i.endHorn))
var geoBig = this.createMesh(new THREE.CylinderGeometry(20, 20, 4, 100, 4, false, Math.PI *i.startHorn, Math.PI *i.endHorn),i.color);
var geoSm = this.createMesh(new THREE.CylinderGeometry(10, 10, 4, 100, 4, false, Math.PI *i.startHorn, Math.PI *i.endHorn),i.color);
var bigBSP = new ThreeBSP(geoBig);
var smBSP = new ThreeBSP(geoSm);
var cubeBSP1 = new ThreeBSP(cube1);
var cubeBSP2 = new ThreeBSP(cube2);
var smBlocks
//进行并集计算
var resultBSP = bigBSP//.subtract(smBSP);
if(i.endHorn > 1){
resultBSP = bigBSP.subtract(smBSP);
smBlocks = resultBSP.subtract(cubeBSP1.subtract(cubeBSP2))
}else{
var smBlock1 = resultBSP.subtract(cubeBSP1)//.subtract(cubeBSP1.intersect(cubeBSP2))
var smBlock2 = resultBSP.intersect(cubeBSP2)//.subtract(cubeBSP1.intersect(cubeBSP2))
smBlocks = smBlock1.intersect(smBlock2).subtract(smBSP)
}
//从BSP对象内获取到处理完后的mesh模型数据
var smBlock = smBlocks.toMesh();
//更新模型的面和顶点的数据
smBlock.geometry.computeFaceNormals();
smBlock.geometry.computeVertexNormals();
//重新赋值一个纹理
material = new THREE.MeshLambertMaterial({//创建材料
color:i.color,
wireframe:false
});
smBlock.material = material;
smBlock.name = i.name;
this.ringGroup.push(smBlock);
// 添加到场景中
scene.add(smBlock);
}
},
// 鼠标点击事件
onDocumentMouseMove(event) {
var vector = new THREE.Vector3(( event.offsetX / this.innerWidth ) * 2 - 1, -( event.offsetY / this.innerHeight ) * 2 + 1, 0.5);
vector = vector.unproject(camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects(this.ringGroup);
if (intersects.length > 0) {
if(moveBlock !== ''){
if(moveBlock.name !== intersects[0].object.name){
moveBlock.scale.y = 1;
moveBlock.position.y = 0;
}
this.hideValue();
}
moveBlock = intersects[0].object;
intersects[0].object.scale.y = 2;
intersects[0].object.position.y = 2;
this.shopValue(moveBlock.name);
}else{
if(moveBlock !== ''){
moveBlock.scale.y = 1;
moveBlock.position.y = 0;
}
this.hideValue();
}
},
//比例显示
shopValue(name){
this.isShow = true;
let allCount = 0;
let arrList = [];
for(let item of this.arry){
allCount+=Number(item.value);
}
for(let item of this.arry){
let arr = {};
arr.value = (item.value/allCount*100).toFixed(2);
arr.name = item.name;
arrList.push(arr);
}
for(let item of arrList){
if(item.name == name){
this.column = item.value;
this.columnName = item.name;
}
}
},
hideValue(){
this.isShow = false;
},
getValue(name){
var nameNode = scene.getObjectByName ( name );
scene.traverse(function(obj) {
obj.scale.y = 1;
obj.position.y = 0;
})
nameNode.scale.y = 2;
nameNode.position.y = 2;
this.shopValue(name);
},
noValue(){
this.hideValue();
scene.traverse(function(obj) {
obj.scale.y = 1;
obj.position.y = 0;
})
}
}
}
</script>
<style scoped>
canvas {width:100%;height:100%;}
.three3div{
position:relative;z-index:10;
}
.labelPie{
position:absolute;
top:50%;
right:30px;
margin-top:-58px;
color: #9da5b7;
}
.labelPie div{
cursor: pointer;
}
.labelPie .list{
width: 16px;
height:16px;
margin-right: 5px;
}
.showValue{
position:absolute;
top:50%;
left:50%;
z-index: 200;
width: 160px;
margin-left: -80px;
margin-top:-40px;
text-align: center;
}
.d-flex{
display: flex;
}
.mt20{
margin-top: 20px;
}
</style>
第三步
页面引入该组件
<template>
<div id="app">
<threedPei :arry="piearray" :innerWidth="640" :innerHeight="320" ></threedPei>
</div>
</template>
<script>
import threedPei from './components/threedPei.vue'
export default {
name: 'App',
components: {
threedPei
},
data(){
return{
piearray:[
],
}
},
mounted(){
//模拟数据请求
setTimeout(()=>{
this.piearray = [
{
name:'正面',
value:50,
color:"#444999"
},
{
name:'负面',
value:50,
color:"#881199"
},
{
name:'中性',
value:90,
color:"#33aa88"
}
]
},1000)
},
methods:{
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
参数说明
参数 | 说明 | 类型 |
---|---|---|
arry | 饼图的数据 | 数组 |
innerWidth | 图的宽 | int |
innerHeight | 图的高 | int |