1.实现思路
- Echarts本身没有这类图形,可以使用其扩展echarts-gl进行绘制,echarts-gl曲面图可以完成这类需求
<script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts-gl/dist/echarts-gl.min.js"></script>
- 图形分解:这个3D饼图分为3个圆环,每个圆环有6个面,完成这个3D饼图需要绘制3*6=18个面
2.曲面图基本使用
- echarts-gl曲面图的类别是 surface ,一个series数据对应一个面,面的数据通常通过函数 parametricEquation 进行生成
- parametricEquation参数说明:共有5个参数(u,v,x,y,z),其中u,v是自变量,u代表水平角度,v代表垂直角度,他们的值是一个区间,并且有step步进,以此融合生成(x,y,z)数据,最后所有生成的(x,y,z)数据就可以绘制出一个曲面
- parametricEquation实例解析:带经纬度的3D地球,u的范围覆盖水平方向一圈,会生成40条经度线,v的范围覆盖180度,为每条经度绘制20个点,所以会生成 40 * 20 = 800个点,连接这些点,就是一个3D地球
series: [
{
type: 'surface',
parametric: true,
// shading: 'albedo',
parametricEquation: {
u: {
min: -Math.PI,
max: Math.PI,
step: Math.PI / 20
},
v: {
min: 0,
max: Math.PI,
step: Math.PI / 20
},
x: function (u, v) {
return Math.sin(v) * Math.sin(u);
},
y: function (u, v) {
return Math.sin(v) * Math.cos(u);
},
z: function (u, v) {
return Math.cos(v);
}
}
}
3.细节及关键点
- 前面说到,一个饼图分成好几个圆环,每个圆环需要绘制6个面,但是其实绘制3个面就够了(前,顶,后),其他面因为视角遮挡的问题看不见,无需绘制
- 缩放控制:zoomSensitivity(缩放灵敏度),默认开启缩放,设为0将关闭此功能
- 旋转控制:rotateSensitivity(旋转灵敏度),默认开启旋转,设为0将关闭此功能
- 视角距离:distance,默认200,越远图形显示越小
- 曲面网格线:wireframe,建议取消展示
4.使用方式
var data = [
{
value: 10,name: "百度",color: "#ff9f7e"
},
{
value: 20,name: "谷歌",color: "#06d3c4"
},
{
value: 20,name: "必应",color: "#6173d6"
}
]
//引入3d饼图
import zyPie3d from '@/components/zy-pie-3d.vue'
<div style="height:400px;">
<zy-pie-3d :data="pie3dData"></zy-pie-3d>
</div>
- 效果:
5.组件代码:
<template>
<div style="height:100%" ref="pie3d"></div>
</template>
<script>
export default{
props: {
data: {
type:Array,
default: function(){
return []
}
},
//内径大小
radius: {
type: Number,
default: 0.7
},
//位置
top: {
type: [Number,String],
default: "-30%"
},
left: {
type: [Number,String],
default: "-15%"
},
//距离
distance: {
type: Number,
default: 170
},
//旋转灵敏度
rotateSensitivity: {
type: Number,
default: 1
},
//缩放灵敏度
zoomSensitivity: {
type: Number,
default: 0
}
},
data(){
return {
myChart: null
}
},
mounted(){
//渲染
this.renderEcharts()
},
watch: {
//监听数据变化
data(){
//渲染
this.renderEcharts()
}
},
methods: {
//渲染
renderEcharts(){
//实例化
if(!this.myChart){
this.myChart = this.$echarts.init(this.$refs.pie3d)
}
var option = {
tooltip: {
formatter: function(e){
return e.seriesName
}
},
legend: {
selectedMode: false,
orient: "vertical",
right: '20%',
top: "center",
textStyle:{
color: "rgba(255,255,255,0.8)",
},//正常状态的文本样式
},
xAxis3D: {
min: -1, max: 1
},
yAxis3D: {
min: -1, max: 1
},
zAxis3D: {
min: 0, max: 1
},
grid3D: {
show: false,
top: this.top,
left: this.left,
width: "100%", height: "100%",
viewControl: {
rotateSensitivity: this.rotateSensitivity,//禁止旋转
zoomSensitivity: this.zoomSensitivity,//禁止缩放
distance: this.distance,//距离,可以用来缩放
}
},
series: this.getPie3DConfig(this.data)
}
// 使用刚指定的配置项和数据显示图表。
this.myChart.setOption(option)
},
//生成饼图配置(每个数据想包括开启角度,结束角度,颜色)
getPie3DConfig(data = []){
//计算总数
var total = data.reduce((total,item)=>{
return total += (item.value)
},0)
//为数据添加占比和角度值
var startAngle = 0
var endAngle = 0
var _data = data.map((item,index)=>{
//拷贝数据项
var _item= {...item}
//计算比值和角度
var rotio = item.value / total
//占位角度
var angle = Math.PI * 2 * rotio
//开始角度(上一个项目的结束角度)
startAngle = endAngle
//结束角度(开始角度+占位角度)
if(index >= data.length - 1){
endAngle = Math.PI * 2
}else{
endAngle = startAngle + angle
}
//挂载到数据项中
_item.startAngle = startAngle
_item.endAngle = endAngle
_item.angle = angle
_item.rotio = rotio
//返回新的数据结构
return _item
})
//饼图固定配置项
//高度
var height = 0.1
//内径
var radius = this.radius
//步进
var step = Math.PI*2/1200
//3D饼图配置项合集
var seriesItems = []
//循环数据项,生成绘制曲面的配置
_data.forEach(item=>{
//series配置(一个配置项目绘制一个面,每个数据项至少需要配置2个面)
var baseSeries = {
type: 'surface',
name: item.name,
wireframe: {
show: false
},
parametric: true,
}
//正面
var seriesItemFront = {
...baseSeries,
parametricEquation: {
u: {
min: item.startAngle,
max: item.endAngle,
step: step
},
v: {
min: 0,
//饼图高度
max: height,
step: height
},
x: function (u, v) {
return Math.sin(u)
},
y: function (u, v) {
return Math.cos(u)
},
z: function (u, v) {
return v
}
},
itemStyle: {
color: item.color
}
}
//背面
var seriesItemBack = {
...baseSeries,
parametricEquation: {
u: {
min: item.startAngle,
max: item.endAngle,
step: step
},
v: {
min: 0,
//饼图高度
max: height,
step: height
},
x: function (u, v) {
return radius * Math.sin(u)
},
y: function (u, v) {
return radius * Math.cos(u)
},
z: function (u, v) {
return v;
}
},
itemStyle: {
color: item.color
}
}
//上面
var seriesItemAbove = {
...baseSeries,
parametricEquation: {
u: {
min: item.startAngle,
max: item.endAngle,
step: step
},
v: {
min: 0,
max: height,
step: height
},
x: function (u, v) {
if(v==0){
//内圈
return radius * Math.sin(u)
}else{
//外圈
return Math.sin(u);
}
},
y: function (u, v) {
if(v==0){
//内圈
return radius * Math.cos(u)
}else{
//外圈
return Math.cos(u);
}
},
z: function (u, v) {
return height
}
},
itemStyle: {
color: item.color
}
}
seriesItems.push(seriesItemFront, seriesItemBack, seriesItemAbove)
})
return seriesItems
}
}
}
</script>