近期的开发需求,需要开发一个3D饼图。不同于echarts的二维饼图,有完善的API,开发起来比较顺手。3D类的图资料较少,就连Echarts官网提供的相关API信息也是模模糊糊的,理解起来不容易。
以饼图为例子。一个完整的2D饼图是由一个或者多个扇形组成的;而一个完整的3D饼图是由一个或者多个扇形曲面组成。
Echarts曲面绘制通过series-surface. type ="surface"配置项来设置,详细参数说明,请参考官网。
|——》任意门
其实光看官网的配置参数,会很难理解。因为没有足够的示例,无法进行调试测试,导致对于知识难掌握,容易产生厌烦情绪。(没错!说的就是俺)
最好的办法就是先去社区或者网络找找有没有相关案例,感谢这些乐于分享知识的开发者们。——》Echarts社区入口
其实也有不少,但是并不能完全满足我的开发需求。只能通过现有的满足条件的案例,拿到代码后再跑一遍,理解里面的配置项的涵义,再自己慢慢调整开发。
下面对实现流程进行简单解析。
准备工作
依赖除了必要的echarts依赖外,还需echarts-gl。
|——》任意门
在这里要注意版本匹配,否则会报错:
echarts-gl 2.x版本的是和echarts 5.X的版本相匹配的。
echarts-gl 1.x版本的是和echarts 4.X的版本相匹配的。
也就是说如果你echarts是4.x的,但是echarts-gl是2.x的,这是万万使不得的,会报错哦~
3D饼图实现
如下图的3D饼图,是由4个扇形曲面实现。
生成扇形的曲面参数方程
用于 series-surface.parametricEquation
function getParametricEquation(startRatio, endRatio, isSelected, isHovered) {
// 计算
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;
}
// 计算选中效果分别在 x 轴、y 轴方向上的位移(位移均为 0)
let offsetX = 0;
let offsetY = 0;
// 计算选中效果在 z 轴方向上的位移(未选中,位移均为 0)
let offsetZ = isSelected ? 0.15 : 0;
// 计算高亮效果的放大比例(未高亮,则比例为 1)
let hoverRate = isHovered ? 1.05 : 1;
let tmp = 0;
// 返回曲面参数方程
return {
u: {
min: 0,
max: Math.PI * 2,
step: Math.PI / 100,
},
v: {
min: 0,
max: Math.PI,
step: Math.PI / 50,
},
x: function (u, v) {
if (midRatio - 0.5 < 0) {
if (u < startRadian || u > midRadian + Math.PI) {
tmp =
u - Math.PI - midRadian < 0
? u + Math.PI - midRadian
: u - Math.PI - midRadian;
return (
offsetX +
((Math.sin(startRadian) * tmp) /
(Math.PI - midRadian + startRadian)) *
hoverRate
);
}
if (u > endRadian && u < midRadian + Math.PI) {
tmp = midRadian + Math.PI - u;
return (
offsetX +
((Math.sin(endRadian) * tmp) /
(Math.PI - midRadian + startRadian)) *
hoverRate
);
}
} else {
if (u < startRadian && u > midRadian - Math.PI) {
tmp = u + Math.PI - midRadian;
return (
offsetX +
((Math.sin(startRadian) * tmp) /
(Math.PI - midRadian + startRadian)) *
hoverRate
);
}
if (u > endRadian || u < midRadian - Math.PI) {
tmp =
midRadian - Math.PI - u < 0
? midRadian + Math.PI - u
: midRadian - Math.PI - u;
return (
offsetX +
((Math.sin(endRadian) * tmp) /
(Math.PI - midRadian + startRadian)) *
hoverRate
);
}
}
return offsetX + Math.sin(v) * Math.sin(u) * hoverRate;
},
y: function (u, v) {
if (midRatio - 0.5 < 0) {
if (u < startRadian || u > midRadian + Math.PI) {
tmp =
u - Math.PI - midRadian < 0
? u + Math.PI - midRadian
: u - Math.PI - midRadian;
return (
offsetY +
((Math.cos(startRadian) * tmp) /
(Math.PI - midRadian + startRadian)) *
hoverRate
);
}
if (u > endRadian && u < midRadian + Math.PI) {
tmp = midRadian + Math.PI - u;
return (
offsetY +
((Math.cos(endRadian) * tmp) /
(Math.PI - midRadian + startRadian)) *
hoverRate
);
}
} else {
if (u < startRadian && u > midRadian - Math.PI) {
tmp = u + Math.PI - midRadian;
return (
offsetY +
((Math.cos(startRadian) * tmp) /
(Math.PI - midRadian + startRadian)) *
hoverRate
);
}
if (u > endRadian || u < midRadian - Math.PI) {
tmp =
midRadian - Math.PI - u < 0
? midRadian + Math.PI - u
: midRadian - Math.PI - u;
return (
offsetY +
((Math.cos(endRadian) * tmp) /
(Math.PI - midRadian + startRadian)) *
hoverRate
);
}
}
return offsetY + Math.sin(v) * Math.cos(u) * hoverRate;
},
z: function (u, v) {
return offsetZ + (Math.cos(v) > 0 ? 0.1 : -0.1);
},
};
}
大部分小可爱肯定会在这一步感到疑惑,u、v、x、y、z这都是啥呀(((o(?▽?)o)))?赶紧去官网查查。
然而,
┻━┻︵╰(‵□′)╯︵┻━┻
没错,依然还是不懂呀