3D饼图

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>
posted @ 2024-10-29 14:46  ---空白---  阅读(29)  评论(0编辑  收藏  举报