echarts 实现3D饼状图 加 label标签显示

文章目录


前言

最近有个页面需要渲染一个3D饼状图 带label标签显示 记录下实现代码效果


代码实现

javascript 复制代码
<template>
  <div class="water-eval-container">
    <div class="cityGreenLand-charts" id="cityGreenLand-charts">
    </div>
  </div>
</template>

<script>
import 'echarts-gl';
export default {
  name: "TestDemo",
  components: {},
  data() {
    return {
      optionData: [{
        name: '林地面积统计',
        value: 10000,
        itemStyle: {
          color: '#22c4ff',
        }
      }, {
        name: '草地面积统计',
        value: 12116,
        itemStyle: {
          color: '#aaff00'
        }
      }, {
        name: '耕地地面积统计',
        value: 16616,
        itemStyle: {
          color: '#ffaaff'
        }
      }],
    }
  },
  mounted() {
    this.$nextTick(()=>{
      this.getData()
    })
  },
  methods: {
    getData() {
      //构建3d饼状图
      let myChart = this.$echarts.init(document.getElementById('cityGreenLand-charts'));
      // 传入数据生成 option
      this.option = this.getPie3D(this.optionData, 0);
      myChart.setOption(this.option);
      this.bindListen(myChart);
    },

    getPie3D(pieData, internalDiameterRatio) {
      //internalDiameterRatio:透明的空心占比
      let that = this;
      let series = [];
      let sumValue = 0;
      let startValue = 0;
      let endValue = 0;
      let legendData = [];
      let legendBfb = [];
      let k = 1 - internalDiameterRatio;
      pieData.sort((a, b) => {
        return (b.value - a.value);
      });
      // 为每一个饼图数据,生成一个 series-surface 配置
      for (let i = 0; i < pieData.length; i++) {
        sumValue += pieData[i].value;
        let seriesItem = {
          name: typeof pieData[i].name === 'undefined' ? `series${i}` : pieData[i].name,
          type: 'surface',
          parametric: true,
          wireframe: {
            show: false
          },
          pieData: pieData[i],
          pieStatus: {
            selected: false,
            hovered: false,
            k: k
          },
          center: ['10%', '50%']
        };

        if (typeof pieData[i].itemStyle != 'undefined') {
          let itemStyle = {};
          typeof pieData[i].itemStyle.color != 'undefined' ? itemStyle.color = pieData[i].itemStyle.color : null;
          typeof pieData[i].itemStyle.opacity != 'undefined' ? itemStyle.opacity = pieData[i].itemStyle.opacity : null;
          seriesItem.itemStyle = itemStyle;
        }
        series.push(seriesItem);
      }

      // 使用上一次遍历时,计算出的数据和 sumValue,调用 getParametricEquation 函数,
      // 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation,也就是实现每一个扇形。
      legendData = [];
      legendBfb = [];
      for (let i = 0; i < series.length; i++) {
        endValue = startValue + series[i].pieData.value;
        series[i].pieData.startRatio = startValue / sumValue;
        series[i].pieData.endRatio = endValue / sumValue;
        series[i].parametricEquation = this.getParametricEquation(series[i].pieData.startRatio, series[i].pieData.endRatio,
            false, false, k, series[i].pieData.value);
        startValue = endValue;
        let bfb = that.fomatFloat(series[i].pieData.value / sumValue, 4);
        legendData.push({
          name: series[i].name,
          value: bfb
        });
        legendBfb.push({
          name: series[i].name,
          value: bfb
        });
      }
      let boxHeight = this.getHeight3D(series, 26);//通过传参设定3d饼/环的高度,26代表26px
      // 准备待返回的配置项,把准备好的 legendData、series 传入。
      let option = {
        legend: {
          data: legendData,
          orient: 'horizontal',
          left: 10,
          top: 10,
          itemGap: 10,
          textStyle: {
            color: '#A1E2FF',
          },
          show: true,
          icon: "circle",
          formatter: function(param) {
            let item = legendBfb.filter(item => item.name == param)[0];
            let bfs = that.fomatFloat(item.value * 100, 2) + "%";
            return `${item.name}  ${bfs}`;
          }
        },
        labelLine: {
          show: false,
          lineStyle: {
            color: '#7BC0CB'
          }
        },
        label: {
          normal: {
            show: true, // 显示标签
            position: 'inside', // 将标签放置在扇区内侧
            formatter: '{b}: {c} ({d}%)', // 自定义标签格式
            textStyle: {
              color: '#000' // 标签文字颜色
            }
          }
        },
        tooltip: {
          formatter: params => {
            if (params.seriesName !== 'mouseoutSeries' && params.seriesName !== 'pie2d') {
              let bfb = ((option.series[params.seriesIndex].pieData.endRatio - option.series[params.seriesIndex].pieData.startRatio) *
                  100).toFixed(2);
              return `${params.seriesName}<br/>` +
                  `<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${params.color};"></span>` +
                  `${ bfb }%`;
            }
          }
        },
        xAxis3D: {
          min: -1,
          max: 1
        },
        yAxis3D: {
          min: -1,
          max: 1
        },
        zAxis3D: {
          min: -1,
          max: 1
        },
        grid3D: {
          show: false,
          boxHeight: boxHeight, //圆环的高度
          viewControl: { //3d效果可以放大、旋转等,请自己去查看官方配置
            alpha: 40, //角度
            distance: 300,//调整视角到主体的距离,类似调整zoom
            rotateSensitivity: 0, //设置为0无法旋转
            zoomSensitivity: 0, //设置为0无法缩放
            panSensitivity: 0, //设置为0无法平移
            autoRotate: false //自动旋转
          }
        },
        series: series
      };

      // 在原有的getPie3D方法中添加以下配置
      option.series.push({
        name: 'pie2d',
        type: 'pie',
        radius: ['20%', '50%'], // 根据需要调整半径
        center: ['50%', '50%'], // 根据需要调整位置
        data: this.optionData.map(item => ({
          value: item.value,
          name: item.name,
          itemStyle: {
            color: 'transparent'
          }
        })),
        label: {
          show: true,
          position: 'inside', // 或者 'outside',根据需要调整
          formatter: '{b}: {c}',
          textStyle: {
            color: '#fff', // 标签文字颜色
            fontSize: 14
          }
        },
        labelLine: {
          show: true,
          length: 10,
          length2: 10,
          lineStyle: {
            color: 'rgba(0,0,0,0)' // 标签连接线也设置为透明,使其不可见
          }
        }
      });
      return option;
    },

    //获取3d丙图的最高扇区的高度
    getHeight3D(series, height) {
      series.sort((a, b) => {
        return (b.pieData.value - a.pieData.value);
      })
      return height * 25 / series[0].pieData.value;
    },

    // 生成扇形的曲面参数方程,用于 series-surface.parametricEquation
    getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h) {
      // 计算
      let midRatio = (startRatio + endRatio) / 2;
      let startRadian = startRatio * Math.PI * 2;
      let endRadian = endRatio * Math.PI * 2;
      let midRadian = midRatio * Math.PI * 2;
      // 如果只有一个扇形,则不实现选中效果。
      if (startRatio === 0 && endRatio === 1) {
        isSelected = false;
      }
      // 通过扇形内径/外径的值,换算出辅助参数 k(默认值 1/3)
      k = typeof k !== 'undefined' ? k : 1 / 3;
      // 计算选中效果分别在 x 轴、y 轴方向上的位移(未选中,则位移均为 0)
      let offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0;
      let offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0;
      // 计算高亮效果的放大比例(未高亮,则比例为 1)
      let hoverRate = isHovered ? 1.05 : 1;
      // 返回曲面参数方程
      return {
        u: {
          min: -Math.PI,
          max: Math.PI * 3,
          step: Math.PI / 32
        },
        v: {
          min: 0,
          max: Math.PI * 2,
          step: Math.PI / 20
        },
        x: function(u, v) {
          if (u < startRadian) {
            return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
          }
          if (u > endRadian) {
            return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
          }
          return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate;
        },
        y: function(u, v) {
          if (u < startRadian) {
            return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
          }
          if (u > endRadian) {
            return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
          }
          return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate;
        },
        z: function(u, v) {
          if (u < -Math.PI * 0.5) {
            return Math.sin(u);
          }
          if (u > Math.PI * 2.5) {
            return Math.sin(u) * h * .1;
          }
          return Math.sin(v) > 0 ? 1 * h * .1 : -1;
        }
      };
    },

    fomatFloat(num, n) {
      var f = parseFloat(num);
      if (isNaN(f)) {
        return false;
      }
      f = Math.round(num * Math.pow(10, n)) / Math.pow(10, n); // n 幂
      var s = f.toString();
      var rs = s.indexOf('.');
      //判定如果是整数,增加小数点再补0
      if (rs < 0) {
        rs = s.length;
        s += '.';
      }
      while (s.length <= rs + n) {
        s += '0';
      }
      return s;
    },

    bindListen(myChart) {
      // 监听鼠标事件,实现饼图选中效果(单选),近似实现高亮(放大)效果。
      let that = this;
      let selectedIndex = '';
      let hoveredIndex = '';
      // 监听点击事件,实现选中效果(单选)
      myChart.on('click', function(params) {
        // 从 option.series 中读取重新渲染扇形所需的参数,将是否选中取反。
        let isSelected = !that.option.series[params.seriesIndex].pieStatus.selected;
        let isHovered = that.option.series[params.seriesIndex].pieStatus.hovered;
        let k = that.option.series[params.seriesIndex].pieStatus.k;
        let startRatio = that.option.series[params.seriesIndex].pieData.startRatio;
        let endRatio = that.option.series[params.seriesIndex].pieData.endRatio;
        // 如果之前选中过其他扇形,将其取消选中(对 option 更新)
        if (selectedIndex !== '' && selectedIndex !== params.seriesIndex) {
          that.option.series[selectedIndex].parametricEquation = that.getParametricEquation(that.option.series[
              selectedIndex].pieData
              .startRatio, that.option.series[selectedIndex].pieData.endRatio, false, false, k, that.option.series[
              selectedIndex].pieData
              .value);
          that.option.series[selectedIndex].pieStatus.selected = false;
        }
        // 对当前点击的扇形,执行选中/取消选中操作(对 option 更新)
        that.option.series[params.seriesIndex].parametricEquation = that.getParametricEquation(startRatio, endRatio,
            isSelected,
            isHovered, k, that.option.series[params.seriesIndex].pieData.value);
        that.option.series[params.seriesIndex].pieStatus.selected = isSelected;
        // 如果本次是选中操作,记录上次选中的扇形对应的系列号 seriesIndex
        isSelected ? selectedIndex = params.seriesIndex : null;
        // 使用更新后的 option,渲染图表
        myChart.setOption(that.option);
      });
      // 监听 mouseover,近似实现高亮(放大)效果
      myChart.on('mouseover', function(params) {
        // 准备重新渲染扇形所需的参数
        let isSelected;
        let isHovered;
        let startRatio;
        let endRatio;
        let k;
        // 如果触发 mouseover 的扇形当前已高亮,则不做操作
        if (hoveredIndex === params.seriesIndex) {
          return;
          // 否则进行高亮及必要的取消高亮操作
        } else {
          // 如果当前有高亮的扇形,取消其高亮状态(对 option 更新)
          if (hoveredIndex !== '') {
            // 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 false。
            isSelected = that.option.series[hoveredIndex].pieStatus.selected;
            isHovered = false;
            startRatio = that.option.series[hoveredIndex].pieData.startRatio;
            endRatio = that.option.series[hoveredIndex].pieData.endRatio;
            k = that.option.series[hoveredIndex].pieStatus.k;
            // 对当前点击的扇形,执行取消高亮操作(对 option 更新)
            that.option.series[hoveredIndex].parametricEquation = that.getParametricEquation(startRatio, endRatio,
                isSelected,
                isHovered, k, that.option.series[hoveredIndex].pieData.value);
            that.option.series[hoveredIndex].pieStatus.hovered = isHovered;
            // 将此前记录的上次选中的扇形对应的系列号 seriesIndex 清空
            hoveredIndex = '';
          }
          // 如果触发 mouseover 的扇形不是透明圆环,将其高亮(对 option 更新)
          if (params.seriesName !== 'mouseoutSeries' && params.seriesName !== 'pie2d') {
            // 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 true。
            isSelected = that.option.series[params.seriesIndex].pieStatus.selected;
            isHovered = true;
            startRatio = that.option.series[params.seriesIndex].pieData.startRatio;
            endRatio = that.option.series[params.seriesIndex].pieData.endRatio;
            k = that.option.series[params.seriesIndex].pieStatus.k;
            // 对当前点击的扇形,执行高亮操作(对 option 更新)
            that.option.series[params.seriesIndex].parametricEquation = that.getParametricEquation(startRatio, endRatio,
                isSelected, isHovered, k, that.option.series[params.seriesIndex].pieData.value + 5);
            that.option.series[params.seriesIndex].pieStatus.hovered = isHovered;
            // 记录上次高亮的扇形对应的系列号 seriesIndex
            hoveredIndex = params.seriesIndex;
          }
          // 使用更新后的 option,渲染图表
          myChart.setOption(that.option);
        }
      });
      // 修正取消高亮失败的 bug
      myChart.on('globalout', function() {
        // 准备重新渲染扇形所需的参数
        let isSelected;
        let isHovered;
        let startRatio;
        let endRatio;
        let k;
        if (hoveredIndex !== '') {
          // 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 true。
          isSelected = that.option.series[hoveredIndex].pieStatus.selected;
          isHovered = false;
          k = that.option.series[hoveredIndex].pieStatus.k;
          startRatio = that.option.series[hoveredIndex].pieData.startRatio;
          endRatio = that.option.series[hoveredIndex].pieData.endRatio;
          // 对当前点击的扇形,执行取消高亮操作(对 option 更新)
          that.option.series[hoveredIndex].parametricEquation = that.getParametricEquation(startRatio, endRatio,
              isSelected,
              isHovered, k, that.option.series[hoveredIndex].pieData.value);
          that.option.series[hoveredIndex].pieStatus.hovered = isHovered;
          // 将此前记录的上次选中的扇形对应的系列号 seriesIndex 清空
          hoveredIndex = '';
        }
        // 使用更新后的 option,渲染图表
        myChart.setOption(that.option);
      });
    }
  }
}
</script>
<style scoped>
.water-eval-container {
  width: 100%;
  height: 100%;
}
.cityGreenLand-charts {
  height: 500px;
  width: 500px;
}
</style>

页面效果


总结

主要是采用echarts-gl实现3D饼状图 再采用一个2D圆环 设置transparent 实现覆盖表面 显示label的效果 同理 也可以添加上引导线 麻木的coding中

相关推荐
梦想CAD控件3 分钟前
在线CAD开发包结构与功能说明
前端·javascript·vue.js
张拭心8 分钟前
春节后,有些公司明确要求 AI 经验了
android·前端·人工智能
时光不负努力8 分钟前
typescript常用的dom 元素类型
前端·typescript
小怪点点14 分钟前
大文件切片上传
前端
时光不负努力14 分钟前
TS 常用工具类型
前端·javascript·typescript
SuperEugene16 分钟前
Vue状态管理扫盲篇:Vuex 到 Pinia | 为什么大家都在迁移?核心用法对比
前端·vue.js·面试
张拭心18 分钟前
Android 17 来了!新特性介绍与适配建议
android·前端
徐小夕23 分钟前
pxcharts-vue:一款专为 Vue3 打造的开源多维表格解决方案
前端·vue.js·github
Hilaku23 分钟前
我会如何考核一个在简历里大谈 AI 提效的高级前端?
前端·javascript·面试
青青家的小灰灰1 小时前
React 反模式(Anti-Patterns)排查手册:从性能杀手到逻辑陷阱
前端·javascript·react.js