vue3之echarts3D环柱饼图
效果:
版本
"echarts": "^5.4.1", "echarts-gl": "^2.0.9"
核心代码:
<template><div class="content"><div ref="eCharts" class="chart"></div></div>
</template><script setup>
import { onMounted, ref } from "vue";
import * as echarts from "echarts";
import "echarts-gl";const eCharts = ref(null);
let myChart = null;let boxHeight;
let optionData = ref([{name: "林地",value: 5,itemStyle: {color: "#22c4ff",},},{name: "草地",value: 13,itemStyle: {color: "#aaff00",},},{name: "耕地",value: 38,itemStyle: {color: "#bbaaf1",},},
]);const 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 * 0.1;}return Math.sin(v) > 0 ? 1 * h * 0.1 : -1;},};
};const getPie3D = (pieData, internalDiameterRatio) => {// internalDiameterRatio:透明的空心占比let series = [];let sumValue = 0;let startValue = 0;let endValue = 0;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,也就是实现每一个扇形。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 = getParametricEquation(series[i].pieData.startRatio,series[i].pieData.endRatio,false,false,k,series[i].pieData.value // 控制各模块高度一致100 控制各模块高度根据value改变);startValue = endValue;}boxHeight = getHeight3D(series, 26); //通过传参设定3d饼/环的高度,26代表26PXreturn series;
};const getHeight3D = (series, height) => {series.sort((a, b) => {return b.pieData.value - a.pieData.value;});return (height * 25) / series[0].pieData.value;
};const initCharts = () => {myChart = echarts.init(eCharts.value);const series = getPie3D(optionData.value, 0);series.push({name: "pie2d",type: "pie",label: {opacity: 1,fontSize: 28,lineHeight: 20,},labelLine: {length: 20,length2: 50,},startAngle: -40, //起始角度,支持范围[0, 360]。clockwise: false, //饼图的扇区是否是顺时针排布。上述这两项配置主要是为了对齐3d的样式radius: ["50%", "32%"],center: ["50%", "50%"],data: optionData.value,itemStyle: {opacity: 0,},});optionData.value.forEach((item) => {item.label = {color: item.itemStyle.color,show: true,formatter: (item) => {return `{b|${item.name} \n}`;},rich: {b: {fontSize: 16,lineHeight: 20,},},};});let option = {xAxis3D: {min: -1,max: 1,},yAxis3D: {min: -1,max: 1,},zAxis3D: {min: -1,max: 1,},grid3D: {show: false,top: 0, // 距离上边的间距boxHeight, // 圆环的高度viewControl: {// 3d效果可以放大、旋转等,请自己去查看官方配置alpha: 22, // 角度distance: 400, // 调整视角到主体的距离,类似调整zoomrotateSensitivity: 0, // 设置为0无法旋转zoomSensitivity: 0, // 设置为0无法缩放panSensitivity: 0, // 设置为0无法平移autoRotate: false, // 自动旋转},},series,};myChart?.setOption(option);
};onMounted(() => {initCharts();
});
</script><style lang="scss" scoped>
.content {position: relative;.chart {position: absolute;top: -20px;left: -20px;width: 500px;height: 300px;}
}
</style>