文章目录
- 前言
- 一、准备工作
-
- [1. 所需工具](#1. 所需工具)
- [2. 引入依赖](#2. 引入依赖)
-
- [方式一:CDN 快速引入](#方式一:CDN 快速引入)
- [方式二:npm 本地安装(推荐)](#方式二:npm 本地安装(推荐))
- 二、实现原理解析
- [三、echarts-gl 3D插件 使用回顾](#三、echarts-gl 3D插件 使用回顾)
-
- [grid3D 常用通用属性:](#grid3D 常用通用属性:)
- [series 常用通用属性:](#series 常用通用属性:)
- surface(曲面图)常用专属属性:
- 快速示例:绘制球体
- 四、代码实战
-
- [4.1 封装一个echarts通用组件](#4.1 封装一个echarts通用组件)
- [4.2 实现3D饼图主体](#4.2 实现3D饼图主体)
- [4.2 添加指示线和标签](#4.2 添加指示线和标签)
- 完整代码:
- 总结
前言
在数据可视化场景中,3D 饼图凭借立体效果和空间层次感,能让数据展示更具视觉冲击力。ECharts 作为优秀的数据可视化库,通过 echarts-gl 插件轻松支持 3D 图表渲染。本文将详细阐述如何利用 ECharts实现3D饼图、3D环形图


一、准备工作
1. 所需工具
ECharts 核心库:版本需 ≥5.0(支持新特性)
echarts-gl 插件:专门处理 3D 渲染的扩展库
2. 引入依赖
方式一:CDN 快速引入
javascript
<!-- 引入 ECharts 核心库 -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js"></script>
<!-- 引入 echarts-gl 3D 插件 -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts-gl.min.js"></script>
方式二:npm 本地安装(推荐)
javascript
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)。
参数方程形式:
javascript
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)
定义曲面形状的核心部分。例如:
- 圆柱体:
javascript
x: function(u, v) { return Math.cos(u); },
y: function(u, v) { return Math.sin(u); },
z: function(u, v) { return v; }
- 环面
javascript
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);
}
快速示例:绘制球体
javascript
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
javascript
<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
javascript
<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 添加指示线和标签
javascript
....
....
....
// 图表配置
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
javascript
<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环形图:
javascript
k=0.2 //半径相关
boxHeight:8 //高度
distance: 180, //距离
rotateSensitivity: 1, //开启鼠标控制旋转
autoRotate: true, //开启自动旋转
运行效果:

3D环形图完整代码:
demo.vue
javascript
<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环形图。你可以根据实际需求,进一步调整图表的样式和参数,创造出更加美观、实用、符合实际需求的可视化效果。