多层级饼图

1.概念

直观上来说,就是一环环饼图嵌套展示,不过每一层内环都与相邻外环构成父子关系。如同的结构一样,一个父亲节点含有多个孩子节点,每个孩子节点依次含有多个孙子节点,依次类推……

用多层级的饼图可以很直观判断出,他们相互之间的占比从属关系

2.准备工作

2.1)安装echarts

1
2
3
4
5
6
npm install echarts --save
import * as echarts from 'echarts';

或者

<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts@5.3.3/dist/echarts.min.js"></script>

2.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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
// echarts基础option 可粘贴到官网自行调校
var option = {
tooltip: {
trigger: 'item',
show: false,
position: function (point, params, dom, rect, size) {
// 固定在顶部
return [point[0], '50%'];
},
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
series: [
{
name: '数据',
type: 'pie',
radius: ['30%', '45%'],
avoidLabelOverlap: false,
label: {
position: 'inner'
},
itemStyle: {
borderWidth: 1 //设置border宽度
// borderColor:'#fff',
},
labelLine: {
show: false
},
color: ['#033bb5', '#1079b4', '#128d6a', '#199747', '#0f4e59'],
data: [
{ value: 50, name: '产品', newval: 1000 },
{ value: 20, name: '价格', newval: 200 },
{ value: 15, name: '服务', newval: 100 },
{ value: 10, name: '品牌', newval: 10 },
{ value: 5, name: '购买意向', newval: 5 }
]
},
{
name: '数据',
type: 'pie',
radius: ['45%', '65%'],
avoidLabelOverlap: false,
label: {
position: 'inner'
},
color: [
'#033bb5',
'#1079b4',
'#128d6a',
'#199747',
'#0f4e59',
'#10304c',
'#2e0bd3',
'#666',
'#333'
],
itemStyle: {
borderWidth: 1
// borderColor:'#fff',
},
data: [
{ value: 25, name: 'c1', newval: 300 },
{ value: 20, name: 'c2', newval: 200 },
{ value: 5, name: 'c3', newval: 10 },
{ value: 10, name: 'j1', newval: 10 },
{ value: 10, name: 'j2', newval: 10 },
{ value: 10, name: 'f1', newval: 10 },
{ value: 5, name: 'f2', newval: 5 },
{ value: 10, name: 'pp', newval: 10 },
{ value: 5, name: 'end1', newval: 5 }
]
},
{
name: '数据',
type: 'pie',
radius: ['65%', '70%'],
avoidLabelOverlap: false,
label: {
// position: 'outsideFill'
},
color: [
'#85a3ad',
'#61a0a9',
'#fff',
'#fff',
'#fff',
'#fff',
'#fff',
'#397c86'
],
itemStyle: {
borderWidth: 1
// borderColor:'#fff',
},
clockWise: true,
data: [
{ value: 25, name: '配置1', newval: 100 },
{ value: 25, name: 'b2', newval: 100 },
{ value: 12, name: '', newval: 0 },
{ value: 12, name: '', newval: 0 },
{ value: 11, name: '', newval: 0 },
{ value: 5, name: '', newval: 0 },
{ value: 5, name: '', newval: 0 },
{ value: 5, name: 'qita3', newval: 30 }
]
},
{
name: '数据',
type: 'pie',
radius: ['30%', '45%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '20',
formatter: (params) => {
console.log(params);
const newName =
params.name === ''
? ''
: `${params.name}\n\n${params.data.newval}`;
return newName;
}
}
},
itemStyle: {
borderWidth: 1 //设置border宽度
// borderColor:'#fff',
},
labelLine: {
show: false
},
color: ['#033bb5', '#1079b4', '#128d6a', '#199747', '#0f4e59'],
data: [
{ value: 50, name: '产品', newval: 1000 },
{ value: 20, name: '价格', newval: 200 },
{ value: 15, name: '服务', newval: 100 },
{ value: 10, name: '品牌', newval: 10 },
{ value: 5, name: '购买意向', newval: 5 }
]
},
{
name: '数据',
type: 'pie',
radius: ['45%', '65%'],
color: [
'#033bb5',
'#1079b4',
'#128d6a',
'#199747',
'#0f4e59',
'#10304c',
'#2e0bd3',
'#666',
'#333'
],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '20',
formatter: (params) => {
console.log(params);
const newName =
params.name === ''
? ''
: `${params.name}\n\n${params.data.newval}`;
return newName;
}
}
},
itemStyle: {
borderWidth: 1
// borderColor:'#fff',
},
data: [
{ value: 25, name: 'c1', newval: 300 },
{ value: 20, name: 'c2', newval: 200 },
{ value: 5, name: 'c3', newval: 10 },
{ value: 10, name: 'j1', newval: 10 },
{ value: 10, name: 'j2', newval: 10 },
{ value: 10, name: 'f1', newval: 10 },
{ value: 5, name: 'f2', newval: 5 },
{ value: 10, name: 'pp', newval: 10 },
{ value: 5, name: 'end1', newval: 5 }
]
},
{
name: '数据',
type: 'pie',
radius: ['65%', '70%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '20',
formatter: (params) => {
console.log(params);
const newName =
params.name === ''
? ''
: `${params.name}\n\n${params.data.newval}`;
return newName;
}
}
},
color: ['', '', '#fff', '#fff', '#fff', '#fff', '#fff', ''],
itemStyle: {
borderWidth: 1
// borderColor:'#fff',
},
clockWise: true,
data: [
{ value: 25, name: '配置1', newval: 100 },
{ value: 25, name: 'b2', newval: 100 },
{ value: 12, name: '', newval: 0 },
{ value: 12, name: '', newval: 0 },
{ value: 11, name: '', newval: 0 },
{ value: 5, name: '', newval: 0 },
{ value: 5, name: '', newval: 0 },
{ value: 5, name: 'qita3', newval: 30 }
]
}
]
};

example

2.3)网页展示链接

​ ps:纯原生js方法,可根据代码换成各框架内容版本

3.相似色

3.1 问题:为什么要求相似色?

从里到外依次为,第一层、第二层、第三层……往往第一层的数据都是有限且不多的(产品会限制十个左右,多了影响美观,以及考验后端查询能力)。故而第一层的颜色是可以确定下来的,然而第二层有多少不确定,更别说第三层及以后,而且为了更好的展示图的父子从属关系,求相似色是为了减少不确定且可能有大量颜色的复杂对应赋值操作。

3.2 问题:如何求相似色?

以上图例,得知第一层的“产品”数量为1000,第二层与之对应的是“c1”、“c2”、“c3”。在此,我采用的就是,以当前父亲颜色作为基础,根据子孩子个数分配出一组颜色,层层递推下去……

3.3 代码—统一封装

3.3.1 十六进制颜色转为RGB

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
export let colorRgb = (sColor) => {
if (sColor) {
sColor = sColor.toLowerCase();
// 十六进制颜色值的正则表达式
const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
// 如果是16进制颜色
if (sColor && reg.test(sColor)) {
if (sColor.length === 4) {
let sColorNew = "#";
for (let i = 1; i < 4; i += 1) {
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1));
}
sColor = sColorNew;
}
// 处理六位的颜色值
const sColorChange = [];
for (let i = 1; i < 7; i += 2) {
sColorChange.push(parseInt("0x" + sColor.slice(i, i + 2), 0));
}
return "rgba(" + sColorChange.join(",");
}
}

return sColor;
};

3.3.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
// 获取一组相似rgb颜色
export let getShallowRGB = (maxColorObject, R, G, B, len) => {
const threshold = 30 // 色阶偏差值
const offset = 7 // 色深偏差值
const result = []
for (const d in maxColorObject) {
if (d === 'R') {
for (let i = 0; i < len; i++) {
const Gdelta = Math.round((255 - (G + threshold)) / len) > 7 ? 7 : Math.round((255 - (G + threshold)) / len)
const Bdelta = Math.round((255 - (B + threshold)) / len) > 7 ? 7 : Math.round((255 - (B + threshold)) / len)
const newRColor = [R - offset, G + threshold + Gdelta * i, B + threshold + Bdelta * i]
const normalColor = `rgb(${newRColor[0]}, ${newRColor[1]}, ${newRColor[2]})`
result.push(normalColor)
}
} else if (d === 'G') {
for (let i = 0; i < len; i++) {
const Rdelta = Math.round((255 - (R + threshold)) / len) > 7 ? 7 : Math.round((255 - (R + threshold)) / len)
const Bdelta = Math.round((255 - (B + threshold)) / len) > 7 ? 7 : Math.round((255 - (B + threshold)) / len)
const newRColor = [R + threshold + Rdelta * i, G - offset, B + threshold + Bdelta * i]
const normalColor = `rgb(${newRColor[0]}, ${newRColor[1]}, ${newRColor[2]})`
result.push(normalColor)
}
} else if (d === 'B') {
for (let i = 0; i < len; i++) {
const Rdelta = Math.round((255 - (R + threshold)) / len) > 7 ? 7 : Math.round((255 - (R + threshold)) / len)
const Gdelta = Math.round((255 - (B + threshold)) / len) > 7 ? 7 : Math.round((255 - (B + threshold)) / len)
const newRColor = [R + threshold + Rdelta * i, G + threshold + Gdelta * i, B - offset]
const normalColor = `rgb(${newRColor[0]}, ${newRColor[1]}, ${newRColor[2]})`
result.push(normalColor)
}
}
}
return result
}

// 获取最大色值
export let findMaxRGB = (R, G, B) => {
let max
let index
if (R >= G && R >= B) {
max = R
index = 'R'
}
if (G >= R && G >= B) {
max = G
index = 'G'
}
if (B >= R && B >= G) {
max = B
index = 'B'
}
return {
[index]: max
}
}

// 获取一组相似颜色的数组
export let getSimilarColor=(R, G, B, len) =>{
const maxColorObject = findMaxRGB(R, G, B)
const shallowColorArray = getShallowRGB(maxColorObject, R, G, B, len)
return shallowColorArray
}

3.4 bug

这本质上是人为利用当前颜色所属于R、G、B哪一种,做加减法求得相似色而已,所以颜色不是特别精准。

4.echarts图结构设计

4.1 图形设计–思考

  1. 根据观察发现,从内到外,除了最外层,内层宽度基本上一致

    方法:for循环除了最后一层,环的宽度固定

  2. 鼠标悬浮某一块,图表文字不在内部展示,而在中央显示名称与值

    方法:复制一份一模一样的结构,一个使得文字处在图内部,一个利用emphasis属性显示环中央。除了以上区别外,两者大小结构一模一样,非常轻松做到不悬浮鼠标字体在图内部展示,悬浮就会在中央展示

  3. echarts图的父子关系结构是人为控制的,即没有实际的层级关系,接受的数组却是树状结构,如何转化

    笨方法:满足实际需求,我们肯定知道他的层级关系,比如当前是四层。我们for循环四层的数据数组,一层层写下去,再根据获得的相似色数组,一个个分配,使得每一层的数值和每一层的颜色一一对应。最后将这些各层级塞进图表option的series数组中。
    高阶方法:递归+树的遍历。

4.2 具体实现—以angular为例

4.2.1 html部分

1
<div [id]="chartId" class="chart-body"></div>

4.2.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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
 //  @Input() 代表组件接收参数,类似vue的props 
// @Output() 子函数传递给父函数的事件
@Output() private allDataChange = new EventEmitter();

@Input() chartId: string = 'container'; // 图表id 必传参数

@Input() chartName = ''; // 图表下载名称

@Input() showDownLoad = true; // 是否显示下载按钮
echartsIntance: any;
@Input() colorData = commonColor;
@Input() multiLayerPie: any[] = [
{
name: '产品',
value: 100,
children: [
{
name: 'a1',
value: 80,
children: [
{
name: 'a11',
value: 60,
children: [
{
name: 'a111',
value: 40,
},
{
name: 'a112',
value: 15,
},
{
name: 'a113',
value: 5,
},
]
},
{
name: 'a12',
value: 20,
children: [
{
name: 'a121',
value: 18,
},
{
name: 'a122',
value: 2,
},
]
},
]
},
{
name: 'a2',
value: 20,
children: [
{
name: 'a21',
value: 20,
children: [
{
name: 'a221',
value: 18,
},
{
name: 'a222',
value: 2,
},
]
},
]
}
]
},
{
name: '价格',
value: 100,
children: [
{
name: 'ba1',
value: 80,
children: [
{
name: 'ba11',
value: 60,
children: [
{
name: 'ba111',
value: 40,
},
{
name: 'ba112',
value: 15,
},
{
name: 'ba113',
value: 5,
},
]
},
{
name: 'ba12',
value: 20,
children: []
},
]
},
{
name: 'ba2',
value: 20,
children: [
{
name: 'ba21',
value: 20,
// children: [
// {
// name: 'ba221',
// value: 18,
// },
// {
// name: 'ba222',
// value: 2,
// },
// ]
},
]
}
]
},
{
name: '美貌',
value: 100,
children: [
{
name: 'ca1',
value: 80,
children: [
{
name: 'ca11',
value: 60,
// children: [
// {
// name: 'ca111',
// value: 40,
// },
// {
// name: 'ca112',
// value: 10,
// },
// {
// name: 'ca113',
// value: 10,
// },
// ]
},
{
name: 'ca12',
value: 20,
children: [
// {
// name: 'ca121',
// value: 14,
// },
{
name: 'ca122',
value: 6,
},
]
},
]
},
{
name: 'ca2',
value: 20,
children: [
{
name: 'ca21',
value: 20,
// children: [
// {
// name: 'ca221',
// value: 18,
// },
// {
// name: 'ca222',
// value: 2,
// },
// ]
},
]
}
]
},
{
name: '数码',
value: 100,
children: [
{
name: 'da1',
value: 80,
children: [
{
name: 'da11',
value: 60,
// children: [
// {
// name: 'da111',
// value: 30,
// },
// {
// name: 'da112',
// value: 30,
// },
// ]
},
{
name: 'da12',
value: 20,
children: []
},
]
},
{
name: 'da2',
value: 20,
children: [
{
name: 'da21',
value: 20,
// children: [
// {
// name: 'da221',
// value: 18,
// },
// {
// name: 'da222',
// value: 2,
// },
// ]
},
]
}
]
},
];
@Input() dataDepth = 4; // 层级 与 multiLayerPie 一致

pieOption = {
title: {
text: this.chartName,
show:false
},
tooltip: {
trigger: 'item',
show: false,
},
// 引入的toolbox样式,仅仅是下载图片按钮样式设置,网页版中有具体值,不多做赘述
toolbox: JSON.parse(JSON.stringify(toolboxStyle)),
series: []
};

4.2.3 初始化图表

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
// 数据发生改变的change事件
ngOnChanges(changes: SimpleChanges): void {
setTimeout(() => {
const dom: any = document.getElementById(this.chartId);
if (dom && this.multiLayerPie && this.multiLayerPie.length > 0) {
this.getChart(dom);
}
});
}

// 初始化函数
ngAfterViewInit(): void {
setTimeout(() => {
const dom: any = document.getElementById(this.chartId);
if (dom && this.multiLayerPie && this.multiLayerPie.length > 0) {
this.getChart(dom);
}
});
}

getChart(dom) {
this.echartsIntance = echarts.init(dom, null, {
renderer: 'canvas',
useDirtyRect: false
});

const pieOption = JSON.parse(JSON.stringify(this.pieOption));
pieOption.title.text = this.chartName;
// 是否展示下载按钮
if (this.showDownLoad) {
pieOption.toolbox.feature.saveAsImage.name = this.chartName;
pieOption.toolbox.feature.saveAsImage.title = "下载图片";
} else {
pieOption.toolbox = {}
}

// 给series塞内容
this.calcLayerData(pieOption)

// 绑定点击事件
this.echartsIntance.on("click", (params) => {
this.onChartClick(params)
})

if (pieOption && typeof pieOption === 'object') {
this.echartsIntance.clear()
this.echartsIntance.setOption(pieOption, true);
}

window.addEventListener('resize', this.echartsIntance.resize);
}

// 图表点击方法
onChartClick(event): void {
if (event.name !== '') { // 名字为空的不允许触发
console.log(event);
this.allDataChange.emit(event)
}
}

4.2.4 处理层级数据

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
123
124
125
126
127
128
129
130
131
132
133
134
135
 calcLayerData(pieOption) {
// 设置一个空对象,存储各层级颜色与数据
const colorDataObj: any = {};
// 递归处理数据
this.depthOrder(colorDataObj, this.multiLayerPie)
console.log(colorDataObj);

const seriesArr = []
// 根据所传层级,for循环
for (let i = 0; i < this.dataDepth; i++) {
// 计算饼图宽度
const radius0 = 30 + i * 15 + '%'
const radius1 = i !== this.dataDepth - 1 ? 30 + (i + 1) * 15 + '%' : 30 + i * 15 + 5 + '%'
let seriesObj: any; // 字在图表内部的series对象
let seriesObj2: any; // 字在图表空白中央的series对象
const depth = i + 1
if (i !== this.dataDepth - 1) {
seriesObj = {
name: '数据',
type: 'pie',
radius: [radius0, radius1],
avoidLabelOverlap: false,
label: {
position: 'inner', // 字展示在内部
color: '#fff'
},
itemStyle: {
borderWidth: 1 // 设置border宽度
// borderColor:'transparent',
},
labelLine: {
show: false
},
color: colorDataObj['color' + depth], // 当前层级颜色
data: colorDataObj['data' + depth] // 当前层级数据
}
seriesObj2 = {
name: '数据',
type: 'pie',
radius: [radius0, radius1],
avoidLabelOverlap: false,
label: {
show: false, // 默认隐藏
position: 'center' // 显示在中央
},
emphasis: {
label: {
show: true, // 鼠标悬浮时展示
fontSize: '20',
formatter: (params) => {
console.log(params);
const newName =
params.name === ''
? ''
: `${params.name}\n\n${params.data.newval}`;
return newName;
}
}
},
itemStyle: {
borderWidth: 1 // 设置border宽度
},
labelLine: {
show: false
},
color: colorDataObj['color' + depth],
data: colorDataObj['data' + depth]
}
} else {
seriesObj = {
name: '数据',
type: 'pie',
radius: [radius0, radius1],
avoidLabelOverlap: false,
label: {
// position: 'outsideFill',
},
itemStyle: {
borderWidth: 1 // 设置border宽度
},

clockWise: true,
color: colorDataObj['color' + depth],
data: colorDataObj['data' + depth]
}

seriesObj2 = {
name: '数据',
type: 'pie',
radius: [radius0, radius1],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '20',
formatter: (params) => {
console.log(params);
const newName =
params.name === ''
? ''
: `${params.name}\n\n${params.data.newval}`;
return newName;
}
}
},
itemStyle: {
borderWidth: 1 // 设置border宽度
},
clockWise: true,

color: colorDataObj['color' + depth],
data: colorDataObj['data' + depth]
}
}
seriesArr.push(seriesObj, seriesObj2)
}
pieOption.series = JSON.parse(JSON.stringify(seriesArr)) // 赋值给series
// emphasis.label.formatter函数需要额外再写一次,要不然echarts不生效
seriesArr.forEach((sav, idx) => {
const newIdx = idx * 2 + 1 // 奇数项设置formatter即可
if (newIdx < seriesArr.length) {
pieOption.series[newIdx].emphasis.label.formatter = (params) => {
const newName =
params.name === ''
? ''
: `${params.name}\n\n${params.data.newval}`;
return newName;
}
}
})
}

4.2.5 递归函数

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
 // 第一个obj是来存储各层级color与data的,orderArr是当前层级的数组,depth为层级默认为1
// oldPer默认为100,他是指上一次占比为多少,当前图表的占比是需要乘以父亲元素的占比,这样才能保证他们的占比一一对应
// simiColor数组 是用来存每一层产生的相似数组的,这样最里层循环结束,跳到上一层,simiColor数组 也需要回退保持层级一致
depthOrder(obj, orderArr, depth = 1, oldPer = 100, simiColor = []) {
const sum = orderArr.reduce((prev, next) => prev + next.value, 0); // 当前数组的value总和
// 当前层级的data/color存在就不作处理,否则需要初始化数组
obj['data' + depth] = obj['data' + depth] ? obj['data' + depth] : []
obj['color' + depth] = obj['color' + depth] ? obj['color' + depth] : []

orderArr.forEach((item, idx) => {
// 计算当前对象的百分比
const per = sum !== 0 ? Math.round(item.value / sum * (oldPer / 100) * 10000) / 100.00000 : 0;
// 因为echarts是根据value显示的,所以我们需要用百分比来充作value,设置新变量存实际value值
const firstObj = {
name: item.name,
value: per,
newval: item.value,
}
// 为当前层级push数据
obj['data' + depth].push(firstObj)

let itemColor; // 当前元素颜色
if (depth === 1) { // 如果是第一层,只需要取传参过来的基础色就好
const newRgbColor = []
this.colorData.forEach(cdv => {
const nowColor = colorRgb(cdv) + ',1)' // 将16进制颜色转化为rgba(这是原先写好的,所以得额外处理)
newRgbColor.push(nowColor.slice(5, nowColor.length - 3))
})
itemColor = 'rgb(' + newRgbColor[idx] + ')' // 人为变成rgb颜色
obj['color' + depth].push(itemColor)
} else {
// 如果不是第一层,则取simiColor最后一个数组对应的idx值 (取栈顶元素)
itemColor = simiColor[simiColor.length - 1][idx]
obj['color' + depth].push(itemColor)
}
// 如果当前idx对应元素是当前数组最后一个时,代表simiColor最后一个数组取完了,需要出栈
if (idx === orderArr.length - 1) {
simiColor.pop()
}

// 如果当前元素孩子存在,递归
if (item.children && JSON.stringify(item.children) !== '[]') {
// 根据当前颜色计算相似色,并push进semicolor中
simiColor.push(this.randomColor(itemColor, item.children.length))
// obj不变, orderArr 变成item.children,层级加一,当前per作为oldper传入,semicolor继续传参
this.depthOrder(obj, item.children, depth + 1, per, simiColor)
} else if (depth < this.dataDepth) {
// 当前循环下层级小于最大层级,且没有孩子时,意味着遍历到了最外层部分没有数据的对象
const emptyObj = {
name: '', // 没有名字,让人看不见
value: per, // 百分比是用来占位的
newval: 0
}
obj['data' + this.dataDepth].push(emptyObj)
obj['color' + this.dataDepth].push('transparent') // 颜色设置为透明色
}
})
}


// 随机渐变色
randomColor(color, len) {
const newRGBColor = color.slice(4, color.length - 1).split(',')
const shallowColorArray = getSimilarColor(Number(newRGBColor[0]), Number(newRGBColor[1]), Number(newRGBColor[2]), len)
return shallowColorArray
}