Echarts学习系列之3D环形图

标签: 无 分类: 未分类 创建时间:2022-10-24 10:39:01 更新时间:2025-01-17 10:39:22

前言

我在网上找了一些例子,最后找了一个vue2.0的例子,使用效果如下,基本上符合了要求,其实代码我自己也看不懂。有时候想想啊,自己已经干了七八年的开发,却还是什么都不会,就是Ctrl+V,还是有些惭愧的。

关于如何实现一个3d环形图,可以通过类似于2d的实现思路:
(1) 扇形镂空:就是生成扇形,然后进行挖空,把靠近圆心的部分做成透明。
(2) 圆环分割:就是通过曲面方程,实现一个圆环,然后再进行分割

1.扇形镂空

刚开始我所找到的方式就是这种,就是通过以一个的三维扇形图,然后想办法把中间的部分搞掉,和二维的圆环差不多,二维的圆环不就是外面一个圈,然后里面一个圈吗。这个在网上有现成的代码,可以尝试修改和使用,大部分的代码都是大同小异的。

参考文章:
1.echarts的3D环形图 mouseover: 近似实现饼图的高亮(放大)效果,大致思路是,在饼图外部套一层透明的圆环,然后监听 mouseover 事件,获取到对应数据的系列序号 params.seriesIndex 或系列名称 params.seriesName,如果鼠标移到了扇形上,则先取消高亮之前的扇形(如果有),再高亮当前扇形;如果鼠标移到了透明圆环上,则只取消高亮之前的扇形(如果有),不做任何高亮。
2.3d环形图示例 这里是上面的代码示例。
3.echarts3d饼图,环形图(包含透明效果) 这是一个示例的代码,基本上都是代码,至于怎么用,还是看个人的水平了。还有关于鼠标高亮的代码。
4.susuhhhhhh / SUSU_CSS(blbl) 这里有实现3d环图的例子
5.3d饼图 echarts 社区给的例子,3d饼图。加上一个 internalDiameterRatio,将 k = 1 改为 k = 1 - internalDiameterRatio 透明的空心占比就可以成为圆环图了,这个只有 4.9.0 可以使用,高版本就不行了。
6.echarts 3d环形图怎么实现啊? 这个里面提到了多个例子。
7.3D 饼环图初步完成 这里实现的圆环可以拖动喝旋转

2.圆环分割

这种实现形式,就是说先实现一个手镯,然后再拍扁的形式,参考一就是这种说明,虽然有步骤,但是我还是没有看懂。

根据圆环面可以参数式地定义为:

x ( u , v ) = ( R + r cos ⁡ v ) cos ⁡ u 
y ( u , v ) = ( R + r cos ⁡ v ) sin ⁡ u 
z ( u , v ) = r sin ⁡ v 
参考文章:
1.3D 饼环图初步完成原理 这是上面的原理分析,通过一个手镯然后拍扁进行了实现。

绘制圆环

官方示例代码 的基础上,编写如下的代码,创建一个圆环,这里要注意的就是 如果出现的是一个圆柱体,要调整 zAxis3D 的范围,这样就可以出现想要的圆环效果了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
option = {
tooltip: {},
xAxis3D: {},
yAxis3D: {},
zAxis3D: {
min:-2.5,
max:2.5
},
grid3D: {},
series: [
{
type: 'surface',
parametric: true,
// shading: 'albedo',
parametricEquation: {
u: {
min:-Math.PI,
max: Math.PI,
step: Math.PI / 20
},
v: {
min: -Math.PI,
max: Math.PI,
step: Math.PI / 20
},
x: function (u, v) {
return Math.cos(u) * (2 + 0.5 * Math.cos(v));
},
y: function (u, v) {
return Math.sin(u) * (2 + 0.5 * Math.cos(v));
},
z: function (u, v) {
return 0.5 * Math.sin(v);
}
}
}
]
};

就会创建一个圆环

分割圆环

还可以创建不同颜色的圆环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
option = {
tooltip: {},
xAxis3D: {},
yAxis3D: {},
zAxis3D: {
min:-2.5,
max:2.5
},
grid3D: {},
series: [
{
type: 'surface',
parametric: true,
// shading: 'albedo',
parametricEquation: {
u: {
min:-Math.PI,
max: 0.5*Math.PI,
step: Math.PI / 20
},
v: {
min: -Math.PI,
max: Math.PI,
step: Math.PI / 20
},
x: function (u, v) {
return Math.cos(u) * (2 + 0.5 * Math.cos(v));
},
y: function (u, v) {
return Math.sin(u) * (2 + 0.5 * Math.cos(v));
},
z: function (u, v) {
return 0.5 * Math.sin(v);
}
}
},
{
type: 'surface',
parametric: true,
// shading: 'albedo',
parametricEquation: {
u: {
min:0.5*Math.PI,
max: Math.PI,
step: Math.PI / 20
},
v: {
min: -Math.PI,
max: Math.PI,
step: Math.PI / 20
},
x: function (u, v) {
return Math.cos(u) * (2 + 0.5 * Math.cos(v));
},
y: function (u, v) {
return Math.sin(u) * (2 + 0.5 * Math.cos(v));
},
z: function (u, v) {
return 0.5 * Math.sin(v);
}
}
}
]
};

结果如图:

压扁圆环

可以通过设置不同的z轴,将圆环变为圆柱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
option = {
tooltip: {},
xAxis3D: {},
yAxis3D: {},
zAxis3D: {
min:-20,
max:9
},
grid3D: {
show:false
},
series: [
{
type: 'surface',
parametric: true,
wireframe:{
show:false
},
itemStyle:{
color:"rgba(0, 232, 126, 1)"
},
// shading: 'albedo',
parametricEquation: {
u: {
min:-Math.PI,
max: 0,
step: Math.PI / 20
},
v: {
min: -Math.PI,
max: 2*Math.PI,
step: Math.PI / 20
},
x: function (u, v) {
return Math.cos(u) * (1 + 0.1 * Math.cos(v));
},
y: function (u, v) {
return Math.sin(u) * (1 + 0.1 * Math.cos(v));
},
z: function (u, v) {
return 0.1 * Math.sin(v) > 0 ? 0.5 : -0.5;
}
}
},
{
type: 'surface',
parametric: true,
wireframe:{
show:false
},
itemStyle:{
color:"rgba(254, 85, 85, 1)"
},
// shading: 'albedo',
parametricEquation: {
u: {
min:0,
max: Math.PI,
step: Math.PI / 20
},
v: {
min: -Math.PI,
max: 2*Math.PI,
step: Math.PI / 20
},
x: function (u, v) {
return Math.cos(u) * (1 + 0.1 * Math.cos(v));
},
y: function (u, v) {
return Math.sin(u) * (1 + 0.1 * Math.cos(v));
},
z: function (u, v) {
return 0.1 * Math.sin(v) > 0 ? 0.8 : -0.8;
}
}
}
]
};

效果如图:

扇面

通过进一步的配置,可以设置扇面图 startRatio:开始比例,endRatio:结束比例,k:内外半径,测试时我取 0.2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
option = {
tooltip: {},
xAxis3D: {
min: -1,
max: 1
},
yAxis3D: {
min: -1,
max: 1
},
zAxis3D: {
min: -10,
max: 10
},
grid3D: {
show:false
},
series: [
{
type: 'surface',
parametric: true,
wireframe:{
show:false
},
itemStyle:{
color:"rgba(254, 85, 0, 1)"
},
// shading: 'albedo',
parametricEquation: {
u: {
min:-Math.PI,
max: Math.PI*3,
step: Math.PI / 20
},
v: {
min: 0,
max: 2*Math.PI,
step: Math.PI / 20
},
x: function (u, v) {
if (u < startRatio*Math.PI*2) {
return Math.cos(startRatio*Math.PI*2) * (1 + Math.cos(v) * 0.2);
}
if (u > endRatio*Math.PI*2) {
return Math.cos(endRatio*Math.PI*2) * (1 + Math.cos(v) * 0.2);
}
return Math.cos(u) * (1 + Math.cos(v) * 0.2);

},
y: function (u, v) {
if (u < startRatio*Math.PI*2) {
return Math.sin(startRatio*Math.PI*2) * (1 + Math.cos(v) * 0.2);
}
if (u > endRatio*Math.PI*2){
return Math.sin(endRatio*Math.PI*2) * (1 + Math.cos(v) * 0.2);
}
return Math.sin(u) * (1 + Math.cos(v) * 0.2);
},
z: function (u, v) {
if (u < - Math.PI * 0.5 ) {
return Math.sin(u);
}
if (u > Math.PI * 2.5 ){
return Math.sin(u);
}
return Math.sin(v) > 0 ? 2 : -2;
}
}
},
{
type: 'surface',
parametric: true,
wireframe:{
show:false
},
itemStyle:{
color:"rgba(254, 85, 85, 1)"
},
// shading: 'albedo',
parametricEquation: {
u: {
min:-Math.PI,
max: Math.PI*3,
step: Math.PI / 20
},
v: {
min: 0,
max: 2*Math.PI,
step: Math.PI / 20
},
x: function (u, v) {
if (u < startRatio) {
return Math.cos(startRatio) * (1 + Math.cos(v) * 0.2);
}
if (u > endRatio*Math.PI*2) {
return Math.cos(endRatio*Math.PI*2) * (1 + Math.cos(v) * 0.2);
}
return Math.cos(u) * (1 + Math.cos(v) * 0.2);

},
y: function (u, v) {
if (u < 0) {
return Math.sin(0) * (1 + Math.cos(v) * 0.2);
}
if (u > endRatio*Math.PI*2){
return Math.sin(endRatio*Math.PI*2) * (1 + Math.cos(v) * 0.2);
}
return Math.sin(u) * (1 + Math.cos(v) * 0.2);
},
z: function (u, v) {
if (u < - Math.PI * 0.5 ) {
return Math.sin(u);
}
if (u > Math.PI * 2.5 ){
return Math.sin(u);
}
return Math.sin(v) > 0 ? 1 : -1;
}
}
}
]
};

最终效果:

折线图

如何在三维图表中绘制一根线

参考文章:
1.三维折线图正交投影 这个官方提供的一个绘制了三维螺旋线的示例
2.vue使用echart 完成3d系列1之曲面空心圆 这是一个绘制曲面空心圆的示例,也是用了参数方程的形式
3.Sphere Parametric Surface 这是使用surface实现的球
4.使用 ECharts GL 实现基础的三维可视化 三维立体散点图
5.echarts-gl中3d曲面UV参数详解

曲面方程和UV坐标

在echarts的3d surface 模块,有一个 parametricEquation 参数,这个参数的解释就是;曲面的参数方程。在data没被设置的时候,可以通过 parametricEquation 去声明参数参数方程。在 parametric 为true时有效。参数方程是 x、y、 z 关于参数 u、v 的方程。通过这个参数,就可以绘制一个连续的曲面了,比如球、比如圆环,比如其他的一些复杂的曲面。

于是我就想着先把UV坐标和曲面方程这个部分弄一下。

参考文章:
1.【微分几何】球面的参数方程和uv平面
2.【原创】NURBS表面的UV空间与三维欧式空间
3.SuperMap三维复杂模型建模之3D极坐标建模——原理篇 这篇文章中讲了曲面方程和自变量uv的关系,可以好好的看看。3D极坐标建模功能实现根据UV参数和数学表达式,构建包括球面、抛物面、双曲抛物面、柱面、圆锥面、莫比乌斯环面、螺旋面、螺旋环面以及Roman曲面等多种3D曲面模型。
4.series-surface. parametricEquation
5.一些曲面的参数方程
6.环面 维基百科中的环面,有关于参数方程的描述,就是我使用的参数方程

3.其他

还有一些其他的库可以实现圆环效果,这里摘录下。

参考文章:
1.Highcharts 演示 › 3D 环形图 这是 Highcharts 实现的3D环形图。
2.echarts echarts-gl 关闭放大与缩小

问题

(1) Unkown series surface
这是因为使用echarts 3d模块,需要引入echarts-gl

1
pnpm add echarts-gl --save

引入:

1
2
import * as echarts from 'echarts';
import 'echarts-gl';
小额赞助
本人提供免费与付费咨询服务,感谢您的支持!赞助请发邮件通知,方便公布您的善意!
**光 3.01 元
Sun 3.00 元
bibichuan 3.00 元
微信公众号
广告位
诚心邀请广大金主爸爸洽谈合作
每日一省
isNaN 和 Number.isNaN 函数的区别?

1.函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

2.函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

每日二省
为什么0.1+0.2 ! == 0.3,如何让其相等?

一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3。

每日三省
== 操作符的强制类型转换规则?

1.首先会判断两者类型是否**相同,**相同的话就比较两者的大小。

2.类型不相同的话,就会进行类型转换。

3.会先判断是否在对比 null 和 undefined,是的话就会返回 true。

4.判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number。

5.判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断。

6.判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断。

每日英语
Happiness is time precipitation, smile is the lonely sad.
幸福是年华的沉淀,微笑是寂寞的悲伤。