Vue3 Echarts 3D饼图(3D环形图)实现讲解附带源码

news2025/5/17 8:46:14

文章目录

  • 前言
  • 一、准备工作
    • 1. 所需工具
    • 2. 引入依赖
      • 方式一:CDN 快速引入
      • 方式二:npm 本地安装(推荐)
  • 二、实现原理解析
  • 三、echarts-gl 3D插件 使用回顾
    • grid3D 常用通用属性:
    • series 常用通用属性:
    • surface(曲面图)常用专属属性:
    • 快速示例:绘制球体
  • 四、代码实战
    • 4.1 封装一个echarts通用组件
    • 4.2 实现3D饼图主体
    • 4.2 添加指示线和标签
    • 完整代码:
      • 控制参数说明:
      • 通过调整参数实现3D环形图:
  • 总结


前言

在数据可视化场景中,3D 饼图凭借立体效果和空间层次感,能让数据展示更具视觉冲击力。ECharts 作为优秀的数据可视化库,通过 echarts-gl 插件轻松支持 3D 图表渲染。本文将详细阐述如何利用 ECharts实现3D饼图、3D环形图

在这里插入图片描述

在这里插入图片描述


一、准备工作

1. 所需工具

ECharts 核心库:版本需 ≥5.0(支持新特性)
echarts-gl 插件:专门处理 3D 渲染的扩展库

2. 引入依赖

方式一:CDN 快速引入

<!-- 引入 ECharts 核心库 -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
<!-- 引入 echarts-gl 3D 插件 -->
<script src="https://cdn.jsdelivr.net/npm/echarts-gl@2.0.8/dist/echarts-gl.min.js"></script>

方式二:npm 本地安装(推荐)

npm install echarts echarts-gl --save

二、实现原理解析

3D饼图的实现主要通过echarts-gl 3D插件去实现,分为3部分。首先通过 自定义曲面图 (type: “surface”)绘制3D饼图主体,再者通过三维折线图( type: “line3D”)绘制指示线,最后通过三维散点图(type: “scatter3D”)绘制标签

三、echarts-gl 3D插件 使用回顾

echarts-gl 3D插件使用跟echarts类似,包括xAxis3D、yAxis3D、zAxis3D、grid3D、series等配置项,可以发现就是在echarts基础上后缀加了3D

grid3D 常用通用属性:

  • viewControl 用于鼠标的旋转,缩放等视角控制,是个对象

    viewControl 的属性:

  • distance :默认视角距离主体的距离,值越大主体显示越小

  • alpha :视角绕 x 轴,即上下旋转的角度

  • beta :视角绕 y 轴,即左右旋转的角度

  • zoomSensitivity 缩放操作的灵敏度,值越大越灵敏,设置0可禁用缩放功能

  • rotateSensitivity 旋转操作的灵敏度,值越大越灵敏 ,设置0可禁用旋转功能

  • panSensitivity 平移操作的灵敏度,值越大越灵敏,设置0可禁用平移功能

  • autoRotate 是否开启视角绕物体的自动旋转查看


series 常用通用属性:

  • type :图表类型,支持surface、bar3D、line3D等值
  • itemStyle :样式包括颜色和透明度。
  • name:系列名称,用于 tooltip 的显示,legend 的图例筛选
  • data:图表项数据

surface(曲面图)常用专属属性:

series :

  • wireframe:曲面图的网格线设置
  • equation:曲面的函数表达式。如果需要展示的是函数曲面,可以不设置 data,通过 equation 去声明函数表达式
  • parametric:是否为参数曲面。
  • parametricEquation:曲面的参数方程。在data没被设置的时候,可以通过 parametricEquation 去声明参数参数方程。在 parametric 为true时有效。

其中parametricEquation属性是实现3D饼图关键,通过曲面的参数方程(parametricEquation)可以绘制复杂的 3D 曲面和曲线比如球体、圆柱体等 ,通过它 我们可以自定义绘制3D扇形图,最终由多个3D扇形组合成3D饼图。

参数方程(parametricEquation是个对象)通常使用两个参数( u 和 v)在特定区间内生成点的坐标 (x, y, z)。

参数方程形式:

parametricEquation:{
  u: { min: 0, max: 2 * Math.PI, step: 0.1 }, // u 参数范围
  v: { min: 0, max: Math.PI, step: 0.1 },     // v 参数范围
  x: function(u, v) { /* 返回 x 坐标 */ },
  y: function(u, v) { /* 返回 y 坐标 */ },
  z: function(u, v) { /* 返回 z 坐标 */ }
}

(1)参数范围 (u 和 v):

min 和 max 定义参数的取值范围。
step 控制参数步长,影响曲面的细分精度(值越小,曲面越精细,但性能开销越大)。

(2)坐标方程 (x, y, z)
定义曲面形状的核心部分。例如:

  • 圆柱体:
x: function(u, v) { return Math.cos(u); },
y: function(u, v) { return Math.sin(u); },
z: function(u, v) { return v; }
  • 环面
x: function(u, v) {
  return (2 + Math.cos(v)) * Math.cos(u);
},
y: function(u, v) {
  return (2 + Math.cos(v)) * Math.sin(u);
},
z: function(u, v) {
  return Math.sin(v);
}

快速示例:绘制球体

option = {
  xAxis3D: { type: 'value' },
  yAxis3D: { type: 'value' },
  zAxis3D: { type: 'value' },
  grid3D: {},
  series: [{
    type: 'surface',
    parametric: true, // 启用参数方程模式
    parametricEquation: {
      u: { min: 0, max: 2 * Math.PI, step: 0.1 }, // u 参数范围
      v: { min: 0, max: Math.PI, step: 0.1 },     // v 参数范围
      x: function(u, v) {
        return Math.sin(v) * Math.cos(u); // x 坐标方程
      },
      y: function(u, v) {
        return Math.sin(v) * Math.sin(u); // y 坐标方程
      },
      z: function(u, v) {
        return Math.cos(v); // z 坐标方程
      }
    },
    itemStyle: {
      color: '#2290ff', // 曲面颜色
      opacity: 0.7
    }
  }]
};

更多属性请查看官方文档


四、代码实战

以vue3为代码为示例,实现3D饼图、环形图

4.1 封装一个echarts通用组件

echarts.vue

<template>
	<div class="echarts-box">
		<div ref="echartRef" class="charts" ></div>
	</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, watch, nextTick, markRaw } from 'vue';
import * as echarts from 'echarts';
const props = defineProps({
	// 图表配置
	data: {
		type: Object,
		default: () => {},
	},
});
const echartRef = ref();

let dom = null;

//设置图表配置
const setOptions = (options) => {
	//清除画布
	dom && dom.clear();
	//重新渲染
	dom && dom.setOption(options);
};


watch(
	() => props.data,
	(val) => {
		nextTick(() => {
			//默认关闭动画
			setOptions({animation: false,...val});
		});
	},
	{ deep: true, immediate: true }
);
const emits= defineEmits(['click'])
onMounted(() => {
	//初始化
	dom = markRaw(echarts.init(echartRef.value));
	//点击事件
    dom.on('click',  (param)=> {
		emits('click',param)
	  } )
});
onBeforeUnmount(() => {
	//离开销毁
	echarts.dispose(dom);
	dom = null;
});

defineExpose({
	setOptions,
});
</script>
<style lang="scss" scoped>
.echarts-box {
	width: 100%;
	height: 100%;
	box-sizing: border-box;
}

.charts {
	width: 100%;
	height: 100%;
}
</style>


上述代码封装了一个echarts通用组件,只需传入data图表配置数据就会重新渲染,需要注意的是组件默认继承父元素的宽高(100%),所以父元素需要设置尺寸。

4.2 实现3D饼图主体

3D饼图主体不包含指示线和标签

demo.vue

<template>
  <div class="container">
    <div class="echarts-view">
      <Echarts :data="options" />
    </div>
  </div>
</template>
<script setup>
import { ref, computed } from "vue";
import Echarts from "./echarts.vue";

//数据
const data = ref([
  { name: "已完成", value: 25, color: "#D13DF2" },
  { name: "申请中", value: 45, color: "#6442ee" },
  { name: "已撤销", value: 12, color: "#6DCDE6" },
  { name: "审核中", value: 7, color: "#2F54F3" },
  { name: "已驳回", value: 3, color: "#8E56E0" },
]);

// 图表配置
const options = computed(() => {
  //总数
  let total = data.value.reduce((a, b) => a + b.value, 0);
  //当前累加值
  let sumValue = 0;
  //辅助参数,控制饼图半径,(0-1)范围内控制环形大小,值越小环形内半径越大
  let k = 1;

  //series配置(每个扇形)
  let series = data.value.map((item) => {
    //当前扇形起始位置占饼图比例
    let startRatio = sumValue / total;
    //值累加
    sumValue += item.value;
    //当前扇形结束位置占饼图比例
    let endRatio = sumValue / total;

    return {
      name: item.name ?? null,
      type: "surface", //曲面图
      itemStyle: {
        color: item.color ?? null, //颜色
      },

      wireframe: {
        show: false, //不显示网格线
      },
      pieData: item, //数据
      //饼图状态
      pieStatus: {
        k, //辅助参数
        startRatio, //起始位置比例
        endRatio, //结束位置比例
        value: item.value, //数值
      },
      parametric: true, //参数曲面
      //曲面的参数方程
      parametricEquation: getParametricEquation(
        startRatio,
        endRatio,
        k,
        item.value
      ),
    };
  });


  //返回配置
  return {
    //提示框
    tooltip: {
      formatter: (params) => {
        if (
          params.seriesName !== "mouseoutSeries" &&
          params.seriesName !== "pie2d"
        ) {
          return `${
            params.seriesName
          }<br/><span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${
            params.color
          };"></span>${series[params.seriesIndex].pieData.value}`;
        }
        return "";
      },
    },
    xAxis3D: {
      min: -1,
      max: 1,
    },
    yAxis3D: {
      min: -1,
      max: 1,
    },
    zAxis3D: {
      min: -1,
      max: 1,
    },
    //
    grid3D: {
      show: false, //不显示坐标系
      boxHeight: 15, //饼图高度
      // 用于鼠标的旋转,缩放等视角控制
      viewControl: {
        alpha: 30, //视角
        distance: 300, //距离,值越大饼图越小
        rotateSensitivity: 0, //禁止旋转
        zoomSensitivity: 0, //禁止缩放
        panSensitivity: 0, //禁止平移
        autoRotate: false, //禁止自动旋转
      },
    },
    series,
  };
});

/**
 * 获取面的参数方程
 * @param {*} startRatio 扇形起始位置比例
 * @param {*} endRatio 扇形结束位置比例
 * @param {*} k 辅助参数,控制饼图半径
 * @param {*} value 数值
 */
const getParametricEquation = (startRatio, endRatio, k, value) => {
  const startRadian = startRatio * Math.PI * 2;
  const endRadian = endRatio * Math.PI * 2;

  k = typeof k === "number" && !isNaN(k) ? k : 1 / 3; //默认1/3

  // 返回曲面参数方程
  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(u, v) {
      if (u < startRadian) {
        return Math.cos(startRadian) * (1 + Math.cos(v) * k);
      }
      if (u > endRadian) {
        return Math.cos(endRadian) * (1 + Math.cos(v) * k);
      }
      return Math.cos(u) * (1 + Math.cos(v) * k);
    },

    y(u, v) {
      if (u < startRadian) {
        return Math.sin(startRadian) * (1 + Math.cos(v) * k);
      }
      if (u > endRadian) {
        return Math.sin(endRadian) * (1 + Math.cos(v) * k);
      }
      return Math.sin(u) * (1 + Math.cos(v) * k);
    },

    z(u, v) {
      if (u < -Math.PI * 0.5) {
        return Math.sin(u);
      }
      if (u > Math.PI * 2.5) {
        return Math.sin(u) * value * 0.1;
      }
      // 扇形高度根据value值计算
      return Math.sin(v) > 0 ? value * 0.1 : -1;
    },
  };
};


</script>
<style scoped>
.container {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  background-color: #203598;
  align-items: center;
}
.echarts-view {
  height: 700px;
  width: 1200px;
}
</style>

运行效果:

在这里插入图片描述

4.2 添加指示线和标签

....
....
....
// 图表配置
const options = computed(() => {
   
   //总数
  let total = data.value.reduce((a, b) => a + b.value, 0);
  //当前累加值
  let sumValue = 0;
  //辅助参数,控制饼图半径,(0-1)范围内控制环形大小,值越小环形内半径越大
  let k = 1;

//series配置(每个扇形)
let series =....
    ....
    .....

/新增/

//添加指示线
  series.forEach((item, index) => {
    let {
      itemStyle: { color },
      pieStatus: { startRatio, endRatio, value },
    } = item;

    addLabelLine(series, startRatio, endRatio, value, k, index, color);
  });
///



  return {
  ....
  ....
  ....
  }
})


///新增//
//添加label指示线
/**
 * @param {*} series 配置
 * @param {*} startRatio 扇形起始位置比例
 * @param {*} endRatio 扇形结束位置比例
 * @param {*} value 数值
 * @param {*} k 辅助参数,饼图半径相关
 * @param {*} i  在series中索引
 * @param {*} color 指示线颜色
 */
const addLabelLine = (
  series,
  startRatio,
  endRatio,
  value,
  k,
  i,
  color = "#fff"
) => {
  //计算扇形中心弧度
  const midRadian = (startRatio + endRatio) * Math.PI;

  // 计算起点位置坐标(扇形边缘中心)
  const radius = 1 + k; // 外径
  const posX = Math.cos(midRadian) * radius; //x坐标
  const posY = Math.sin(midRadian) * radius; //y坐标
  const posZ = 0.1 * value; //z坐标

  let flag =
    (midRadian >= 0 && midRadian <= Math.PI / 2) ||
    (midRadian >= (3 * Math.PI) / 2 && midRadian <= Math.PI * 2)
      ? 1
      : -1;

  //计算拐点坐标
  let turningPosArr = [
    posX * 1.1 + i * 0.1 * flag + (flag < 0 ? -0.2 : 0),
    posY * 1.1 + i * 0.1 * flag + (flag < 0 ? -0.2 : 0),
    posZ,
  ];
  //计算结束位置坐标
  let endPosArr = [
    posX * 1.2 + i * 0.1 * flag + (flag < 0 ? -0.2 : 0),
    posY * 1.2 + i * 0.1 * flag + (flag < 0 ? -0.2 : 0),
    posZ * 3,
  ];

  //添加label+指示线
  series.push(
    //  指示线
    {
      type: "line3D",
      lineStyle: {
        color: "#fff",//线颜色
        width:2,//线宽
      },
      data: [[posX, posY, posZ], turningPosArr, endPosArr],
    },
    //label
    {
      type: "scatter3D",
      label: {
        show: true,
        distance: 0,
        position: "center",
        textStyle: {
          color: "#fff",//文字颜色
          backgroundColor: "rgba(0,0,0,0)", //透明背景
          fontSize: 18,//文字尺寸
          fontWeight :'bold', //文字加粗
          padding: 5,
        },
        formatter: "{b}",
      },
      symbolSize: 0,
      data: [
        {
          name: series[i].name + ":" + value+'个',
          value: endPosArr,
        },
      ],
    }
  );
};
//



运行效果:

在这里插入图片描述

完整代码:

demo.vue

<template>
  <div class="container">
    <div class="echarts-view">
      <Echarts :data="options" />
    </div>
  </div>
</template>
<script setup>
import { ref, computed } from "vue";
import Echarts from "./echarts.vue";

//数据
const data = ref([
  { name: "已完成", value: 25, color: "#D13DF2" },
  { name: "申请中", value: 45, color: "#6442ee" },
  { name: "已撤销", value: 12, color: "#6DCDE6" },
  { name: "审核中", value: 7, color: "#2F54F3" },
  { name: "已驳回", value: 3, color: "#8E56E0" },
]);

// 图表配置
const options = computed(() => {
  //总数
  let total = data.value.reduce((a, b) => a + b.value, 0);
  //当前累加值
  let sumValue = 0;
  //辅助参数,控制饼图半径,(0-1)范围内控制环形大小,值越小环形内半径越大
  let k = 1;

  //series配置(每个扇形)
  let series = data.value.map((item) => {
    //当前扇形起始位置占饼图比例
    let startRatio = sumValue / total;
    //值累加
    sumValue += item.value;
    //当前扇形结束位置占饼图比例
    let endRatio = sumValue / total;

    return {
      name: item.name ?? null,
      type: "surface", //曲面图
      itemStyle: {
        color: item.color ?? null, //颜色
      },

      wireframe: {
        show: false, //不显示网格线
      },
      pieData: item, //数据
      //饼图状态
      pieStatus: {
        k, //辅助参数
        startRatio, //起始位置比例
        endRatio, //结束位置比例
        value: item.value, //数值
      },
      parametric: true, //参数曲面
      //曲面的参数方程
      parametricEquation: getParametricEquation(
        startRatio,
        endRatio,
        k,
        item.value
      ),
    };
  });

  //添加指示线
  series.forEach((item, index) => {
    let {
      itemStyle: { color },
      pieStatus: { startRatio, endRatio, value },
    } = item;

    addLabelLine(series, startRatio, endRatio, value, k, index, color);
  });

  //返回配置
  return {
    //提示框
    tooltip: {
      formatter: (params) => {
        if (
          params.seriesName !== "mouseoutSeries" &&
          params.seriesName !== "pie2d"
        ) {
          return `${
            params.seriesName
          }<br/><span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${
            params.color
          };"></span>${series[params.seriesIndex].pieData.value}`;
        }
        return "";
      },
    },
    xAxis3D: {
      min: -1,
      max: 1,
    },
    yAxis3D: {
      min: -1,
      max: 1,
    },
    zAxis3D: {
      min: -1,
      max: 1,
    },
    //
    grid3D: {
      show: false, //不显示坐标系
      boxHeight: 15, //饼图高度
      // 用于鼠标的旋转,缩放等视角控制
      viewControl: {
        alpha: 30, //视角
        distance: 300, //距离,值越大饼图越小
        rotateSensitivity: 0, //禁止旋转
        zoomSensitivity: 0, //禁止缩放
        panSensitivity: 0, //禁止平移
        autoRotate: false, //禁止自动旋转
      },
    },
    series,
  };
});

/**
 * 获取面的参数方程
 * @param {*} startRatio 扇形起始位置比例
 * @param {*} endRatio 扇形结束位置比例
 * @param {*} k 辅助参数,控制饼图半径
 * @param {*} value 数值
 */
const getParametricEquation = (startRatio, endRatio, k, value) => {
  const startRadian = startRatio * Math.PI * 2;
  const endRadian = endRatio * Math.PI * 2;

  k = typeof k === "number" && !isNaN(k) ? k : 1 / 3; //默认1/3

  // 返回曲面参数方程
  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(u, v) {
      if (u < startRadian) {
        return Math.cos(startRadian) * (1 + Math.cos(v) * k);
      }
      if (u > endRadian) {
        return Math.cos(endRadian) * (1 + Math.cos(v) * k);
      }
      return Math.cos(u) * (1 + Math.cos(v) * k);
    },

    y(u, v) {
      if (u < startRadian) {
        return Math.sin(startRadian) * (1 + Math.cos(v) * k);
      }
      if (u > endRadian) {
        return Math.sin(endRadian) * (1 + Math.cos(v) * k);
      }
      return Math.sin(u) * (1 + Math.cos(v) * k);
    },

    z(u, v) {
      if (u < -Math.PI * 0.5) {
        return Math.sin(u);
      }
      if (u > Math.PI * 2.5) {
        return Math.sin(u) * value * 0.1;
      }
      // 扇形高度根据value值计算
      return Math.sin(v) > 0 ? value * 0.1 : -1;
    },
  };
};

//添加label指示线
/**
 * @param {*} series 配置
 * @param {*} startRatio 扇形起始位置比例
 * @param {*} endRatio 扇形结束位置比例
 * @param {*} value 数值
 * @param {*} k 辅助参数,饼图半径相关
 * @param {*} i  在series中索引
 * @param {*} color 指示线颜色
 */
const addLabelLine = (
  series,
  startRatio,
  endRatio,
  value,
  k,
  i,
  color = "#fff"
) => {
  //计算扇形中心弧度
  const midRadian = (startRatio + endRatio) * Math.PI;

  // 计算起点位置坐标(扇形边缘中心)
  const radius = 1 + k; // 外径
  const posX = Math.cos(midRadian) * radius; //x坐标
  const posY = Math.sin(midRadian) * radius; //y坐标
  const posZ = 0.1 * value; //z坐标

  let flag =
    (midRadian >= 0 && midRadian <= Math.PI / 2) ||
    (midRadian >= (3 * Math.PI) / 2 && midRadian <= Math.PI * 2)
      ? 1
      : -1;

  //计算拐点坐标
  let turningPosArr = [
    posX * 1.1 + i * 0.1 * flag + (flag < 0 ? -0.2 : 0),
    posY * 1.1 + i * 0.1 * flag + (flag < 0 ? -0.2 : 0),
    posZ,
  ];
  //计算结束位置坐标
  let endPosArr = [
    posX * 1.2 + i * 0.1 * flag + (flag < 0 ? -0.2 : 0),
    posY * 1.2 + i * 0.1 * flag + (flag < 0 ? -0.2 : 0),
    posZ * 3,
  ];

  //添加label+指示线
  series.push(
    //  指示线
    {
      type: "line3D",
      lineStyle: {
        color: "#fff",//线颜色
        width:2,//线宽
      },
      data: [[posX, posY, posZ], turningPosArr, endPosArr],
    },
    //label
    {
      type: "scatter3D",
      label: {
        show: true,
        distance: 0,
        position: "center",
        textStyle: {
          color: "#fff",//文字颜色
          backgroundColor: "rgba(0,0,0,0)", //透明背景
          fontSize: 18,//文字尺寸
          fontWeight :'bold', //文字加粗
          padding: 5,
        },
        formatter: "{b}",
      },
      symbolSize: 0,
      data: [
        {
          name: series[i].name + ":" + value+'个',
          value: endPosArr,
        },
      ],
    }
  );
};
</script>
<style scoped>
.container {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  background-color: #203598;
  align-items: center;
}
.echarts-view {
  height: 700px;
  width: 1200px;
}
</style>

控制参数说明:

代码中几个比较重要的控制参数需要注意,根据实际需求修改:

  • k(第28行):辅助参数,用来控制饼图大小,一般设置(0-1)范围,当值为小于1时会出现环形,值越小环形内半径越大
  • boxHeight(第113行):饼图高度主要控制参数(不是唯一影响参数,高度视觉效果还受到距离影响)
  • viewControl (第115行):用于鼠标的旋转,缩放等视角控制,其中 alpha控制3D视角角度,distance控制眼睛与3D饼图距离,值越大饼图越显小。rotateSensitivity开启鼠标旋转功能,zoomSensitivity开启鼠标缩放功能。

通过调整参数实现3D环形图:

k=0.2 //半径相关
boxHeight:8 //高度
distance: 180, //距离
rotateSensitivity: 1, //开启鼠标控制旋转
autoRotate: true, //开启自动旋转

运行效果:

请添加图片描述

3D环形图完整代码:

demo.vue

<template>
  <div class="container">
    <div class="echarts-view">
      <Echarts :data="options" />
    </div>
  </div>
</template>
<script setup>
import { ref, computed } from "vue";
import Echarts from "./echarts.vue";

//数据
const data = ref([
  { name: "已完成", value: 25, color: "#D13DF2" },
  { name: "申请中", value: 45, color: "#6442ee" },
  { name: "已撤销", value: 12, color: "#6DCDE6" },
  { name: "审核中", value: 7, color: "#2F54F3" },
  { name: "已驳回", value: 3, color: "#8E56E0" },
]);

// 图表配置
const options = computed(() => {
  //总数
  let total = data.value.reduce((a, b) => a + b.value, 0);
  //当前累加值
  let sumValue = 0;
  //辅助参数,控制饼图半径,(0-1)范围内控制环形大小,值越小环形内半径越大
  let k = 0.2;

  //series配置(每个扇形)
  let series = data.value.map((item) => {
    //当前扇形起始位置占饼图比例
    let startRatio = sumValue / total;
    //值累加
    sumValue += item.value;
    //当前扇形结束位置占饼图比例
    let endRatio = sumValue / total;

    return {
      name: item.name ?? null,
      type: "surface", //曲面图
      itemStyle: {
        color: item.color ?? null, //颜色
      },

      wireframe: {
        show: false, //不显示网格线
      },
      pieData: item, //数据
      //饼图状态
      pieStatus: {
        k, //辅助参数
        startRatio, //起始位置比例
        endRatio, //结束位置比例
        value: item.value, //数值
      },
      parametric: true, //参数曲面
      //曲面的参数方程
      parametricEquation: getParametricEquation(
        startRatio,
        endRatio,
        k,
        item.value
      ),
    };
  });

  //添加指示线
  series.forEach((item, index) => {
    let {
      itemStyle: { color },
      pieStatus: { startRatio, endRatio, value },
    } = item;

    addLabelLine(series, startRatio, endRatio, value, k, index, color);
  });

  //返回配置
  return {
    //提示框
    tooltip: {
      formatter: (params) => {
        if (
          params.seriesName !== "mouseoutSeries" &&
          params.seriesName !== "pie2d"
        ) {
          return `${
            params.seriesName
          }<br/><span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${
            params.color
          };"></span>${series[params.seriesIndex].pieData.value}`;
        }
        return "";
      },
    },
    xAxis3D: {
      min: -1,
      max: 1,
    },
    yAxis3D: {
      min: -1,
      max: 1,
    },
    zAxis3D: {
      min: -1,
      max: 1,
    },
    //
    grid3D: {
      show: false, //不显示坐标系
      boxHeight: 8, //饼图高度
      // 用于鼠标的旋转,缩放等视角控制
      viewControl: {
        alpha: 30, //视角
        distance: 180, //距离,值越大饼图越小
        rotateSensitivity: 1, //禁止旋转
        zoomSensitivity: 0, //禁止缩放
        panSensitivity: 0, //禁止平移
        autoRotate: true, //禁止自动旋转
      },
    },
    series,
  };
});

/**
 * 获取面的参数方程
 * @param {*} startRatio 扇形起始位置比例
 * @param {*} endRatio 扇形结束位置比例
 * @param {*} k 辅助参数,控制饼图半径
 * @param {*} value 数值
 */
const getParametricEquation = (startRatio, endRatio, k, value) => {
  const startRadian = startRatio * Math.PI * 2;
  const endRadian = endRatio * Math.PI * 2;

  k = typeof k === "number" && !isNaN(k) ? k : 1 / 3; //默认1/3

  // 返回曲面参数方程
  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(u, v) {
      if (u < startRadian) {
        return Math.cos(startRadian) * (1 + Math.cos(v) * k);
      }
      if (u > endRadian) {
        return Math.cos(endRadian) * (1 + Math.cos(v) * k);
      }
      return Math.cos(u) * (1 + Math.cos(v) * k);
    },

    y(u, v) {
      if (u < startRadian) {
        return Math.sin(startRadian) * (1 + Math.cos(v) * k);
      }
      if (u > endRadian) {
        return Math.sin(endRadian) * (1 + Math.cos(v) * k);
      }
      return Math.sin(u) * (1 + Math.cos(v) * k);
    },

    z(u, v) {
      if (u < -Math.PI * 0.5) {
        return Math.sin(u);
      }
      if (u > Math.PI * 2.5) {
        return Math.sin(u) * value * 0.1;
      }
      // 扇形高度根据value值计算
      return Math.sin(v) > 0 ? value * 0.1 : -1;
    },
  };
};

//添加label指示线
/**
 * @param {*} series 配置
 * @param {*} startRatio 扇形起始位置比例
 * @param {*} endRatio 扇形结束位置比例
 * @param {*} value 数值
 * @param {*} k 辅助参数,饼图半径相关
 * @param {*} i  在series中索引
 * @param {*} color 指示线颜色
 */
const addLabelLine = (
  series,
  startRatio,
  endRatio,
  value,
  k,
  i,
  color = "#fff"
) => {
  //计算扇形中心弧度
  const midRadian = (startRatio + endRatio) * Math.PI;

  // 计算起点位置坐标(扇形边缘中心)
  const radius = 1 + k; // 外径
  const posX = Math.cos(midRadian) * radius; //x坐标
  const posY = Math.sin(midRadian) * radius; //y坐标
  const posZ = 0.1 * value; //z坐标

  let flag =
    (midRadian >= 0 && midRadian <= Math.PI / 2) ||
    (midRadian >= (3 * Math.PI) / 2 && midRadian <= Math.PI * 2)
      ? 1
      : -1;

  //计算拐点坐标
  let turningPosArr = [
    posX * 1.1 + i * 0.1 * flag + (flag < 0 ? -0.2 : 0),
    posY * 1.1 + i * 0.1 * flag + (flag < 0 ? -0.2 : 0),
    posZ,
  ];
  //计算结束位置坐标
  let endPosArr = [
    posX * 1.2 + i * 0.1 * flag + (flag < 0 ? -0.2 : 0),
    posY * 1.2 + i * 0.1 * flag + (flag < 0 ? -0.2 : 0),
    posZ * 3,
  ];

  //添加label+指示线
  series.push(
    //  指示线
    {
      type: "line3D",
      lineStyle: {
        color: "#fff",//线颜色
        width:2,//线宽
      },
      data: [[posX, posY, posZ], turningPosArr, endPosArr],
    },
    //label
    {
      type: "scatter3D",
      label: {
        show: true,
        distance: 0,
        position: "center",
        textStyle: {
          color: "#fff",//文字颜色
          backgroundColor: "rgba(0,0,0,0)", //透明背景
          fontSize: 18,//文字尺寸
          fontWeight :'bold', //文字加粗
          padding: 5,
        },
        formatter: "{b}",
      },
      symbolSize: 0,
      data: [
        {
          name: series[i].name + ":" + value+'个',
          value: endPosArr,
        },
      ],
    }
  );
};
</script>
<style scoped>
.container {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  background-color: #203598;
  align-items: center;
}
.echarts-view {
  height: 700px;
  width: 1200px;
}
</style>


总结

通过echarts-gl 3D插件灵活应用我们成功实现了 基础版3D 饼图、3D环形图。你可以根据实际需求,进一步调整图表的样式和参数,创造出更加美观、实用、符合实际需求的可视化效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2377507.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Kafka快速安装与使用

引言 这篇文章是一篇Ubuntu(Linux)环境下的Kafka安装与使用教程&#xff0c;通过本文&#xff0c;你可以非常快速搭建一个kafka的小单元进行日常开发与调测。 安装步骤 下载与解压安装 首先我们需要下载一下Kafka&#xff0c;这里笔者采用wget指令&#xff1a; wget https:…

Java EE初阶——wait 和 notify

1. 线程饥饿 线程饥饿是指一个或多个线程因长期无法获取所需资源&#xff08;如锁&#xff0c;CPU时间等&#xff09;而持续处于等待状态&#xff0c;导致其任务无法推进的现象。 典型场景 优先级抢占&#xff1a; 在支持线程优先级的系统中&#xff0c;高优先级线程可能持续…

RPA vs. 传统浏览器自动化:效率与灵活性的终极较量

1. 引言 在数字化转型的大潮下&#xff0c;企业和开发者对浏览器自动化的需求日益增长。无论是网页数据抓取、自动化测试&#xff0c;还是用户行为模拟&#xff0c;浏览器自动化已经成为提升效率的关键工具。然而&#xff0c;面对越来越严格的反自动化检测、复杂的 Web 结构和…

docker 快速部署若依项目

1、首先创建一个自定义网络&#xff0c;作用是使连接到该网络的容器能够通过容器名称进行通信&#xff0c;无需使用复杂的IP地址配置&#xff0c;方便了容器化应用中各个服务之间的交互。 sudo docker network create ruoyi 2、创建一个文件夹&#xff0c;创建compose.yml文件…

polarctf-web-[rce1]

考点&#xff1a; (1)RCE(exec函数) (2)空格绕过 (3)执行函数(exec函数) (4)闭合(ping命令闭合) 题目来源&#xff1a;Polarctf-web-[rce1] 解题&#xff1a; 这段代码实现了一个简单的 Ping 测试工具&#xff0c;用户可以通过表单提交一个 IP 地址&#xff0c;服务器会执…

Redis+Caffeine构造多级缓存

一、背景 项目中对性能要求极高&#xff0c;因此使用多级缓存&#xff0c;最终方案决定是RedisCaffeine。其中Redis作为二级缓存&#xff0c;Caffeine作为一级本地缓存。 二、Caffeine简单介绍 Caffeine是一款基于Java 8的高性能、灵活的本地缓存库。它提供了近乎最佳的命中…

docker(四)使用篇二:docker 镜像

在上一章中&#xff0c;我们介绍了 docker 镜像仓库&#xff0c;本文就来介绍 docker 镜像。 一、什么是镜像 docker 镜像本质上是一个 read-only 只读文件&#xff0c; 这个文件包含了文件系统、源码、库文件、依赖、工具等一些运行 application 所必须的文件。 我们可以把…

AXI4总线协议 ------ AXI_LITE协议

一、AXI 相关知识介绍 https://download.csdn.net/download/mvpkuku/90841873 AXI_LITE 选出部分重点&#xff0c;详细文档见上面链接。 1.AXI4 协议类型 2.握手机制 二、AXI_LITE 协议的实现 1. AXI_LITE 通道及各通道端口功能介绍 2.实现思路及框架 2.1 总体框架 2.2 …

Ubuntu24.04 安装 5080显卡驱动以及cuda

前言 之前使用Ubuntu22.04版本一直报错,然后换了24.04版本才能正常安装 一. 配置基础环境 Linux系统进行环境开发环境配置-CSDN博客 二. 安装显卡驱动 1.安装驱动 按以下步骤来&#xff1a; sudo apt update && sudo apt upgrade -y#下载最新内核并安装 sudo add…

SpringAI-RC1正式发布:移除千帆大模型!

续 Spring AI M8 版本之后&#xff08;5.1 发布&#xff09;&#xff0c;前几日 Spring AI 悄悄的发布了最新版 Spring AI 1.0.0 RC1&#xff08;5.13 发布&#xff09;&#xff0c;此版本也将是 GA&#xff08;Generally Available&#xff0c;正式版&#xff09;发布前的最后…

操作系统之进程和线程听课笔记

计算机的上电运行就是构建进程树,进程调度就是在进程树节点进程进行切换 进程间通信的好处 经典模型 生产者和消费者 进程和线程的区别 线程引入带来的问题线程的优势 由于unix70年代产生,90年代有线程,当时数据库系统操作需要线程,操作系统没有来得及重造,出现了用户态线…

COMSOL随机参数化表面流体流动模拟

基于粗糙度表面的裂隙流研究对于理解地下水的流动、污染物传输以及与之相关的地质灾害&#xff08;如滑坡&#xff09;等方面具有重要意义。本研究通过蒙特卡洛方法生成随机表面形貌&#xff0c;并利用COMSOL Multiphysics对随机参数化表面的微尺度流体流动进行模拟。 参数化…

JavaSwing中的容器之--JScrollPane

JavaSwing中的容器之–JScrollPane 在Java Swing中&#xff0c;容器是用于容纳其他组件&#xff08;如按钮、标签等&#xff09;的组件。Swing提供了多种容器&#xff0c;它们可以嵌套使用以创建复杂的用户界面。 JScrollPane是一个轻量级组件&#xff0c;提供可滚动视图。JSc…

使用 Cookie 实现认证跳转功能

使用 Cookie 实现认证跳转功能的实践与解析 在 Web 开发中&#xff0c;用户身份认证是一个基础而关键的功能点。本文将通过一个简单的前后端示例系统&#xff0c;介绍如何基于 Cookie 实现 Token 保存与自动跳转认证的功能&#xff0c;并结合 Cookie 与 Header 的区别、使用场…

LED接口设计

一个LED灯有3种控制状态&#xff0c;常亮、常灭和闪烁&#xff0c;要做到这种控制最简单的一种方法是使用任何一款处理器的普通IO去控制。 用IO控制方式有两种&#xff0c;一种是高有效&#xff0c;如下图1所示IO口为高电平时LED亮&#xff0c;IO为低电平时LED不亮。IO口出一个…

SpringBoot项目使用POI-TL动态生成Word文档

近期项目工作需要动态生成Word文档的需求&#xff0c;特意调研了动态生成Word的技术方案。主要有以下两种&#xff1a; 第一种是FreeMarker模板来进行填充&#xff1b;第二种是POI-TL技术使用Word模板来进行填充&#xff1b; 以下是关于POI-TL的官方介绍 重点关注&#xff1…

YOLOv3深度解析:多尺度特征融合与实时检测的里程碑

一、YOLOv3的诞生&#xff1a;继承与突破的起点 YOLOv3作为YOLO系列的第三代算法&#xff0c;于2018年由Joseph Redmon等人提出。它在YOLOv2的基础上&#xff0c;针对小目标检测精度低、多类别标签预测受限等问题进行了系统性改进。通过引入多尺度特征图检测、残差网络架构和独…

uniapp-商城-60-后台 新增商品(属性的选中和页面显示)

前面添加了属性&#xff0c;添加属性的子级项目。也分析了如何回显&#xff0c;但是在添加新的商品的时&#xff0c;我们也同样需要进行选择&#xff0c;还要能正常的显示在界面上。下面对页面的显示进行分析。 1、界面情况回顾 属性显示其实是个一嵌套的数据显示。 2、选中的…

虹科技术 | 简化汽车零部件测试:LIN/CAN总线设备的按键触发功能实现

汽车零部件测试领域对操作的便捷性要求越来越高&#xff0c;虹科Baby-LIN-RC系列产品为这一需求提供了完美的解决方案。从基础的按键设置到高级的Shift键应用&#xff0c;本文将一步步引导您了解虹科Baby-LIN-RC系列产品的智能控制之道。 虹科Baby-LIN-3-RC 想象一下&#xff0…

单片机ESP32天气日历闹铃语音播报

自制Arduino Esp32 单片机 可以整点语音播报&#xff0c;闹铃语音播报&#xff0c;农历显示&#xff0c;白天晚上天气&#xff0c;硬件有 Esp32&#xff0c;ST7789显示屏&#xff0c;Max98357 喇叭驱动&#xff0c;小喇叭一枚。有需要源码的私信我。#单片机 #闹钟 #嵌入式 #智能…