柱状图高级特性完全指南 - 堆叠与瀑布图
📋 概述
柱状图高级特性 包括堆叠柱状图、百分比堆叠、瀑布图、双向柱状图等进阶功能。这些特性能够展示数据的构成关系、变化过程和正负对比,是商业分析的利器。
核心价值
- 堆叠展示:直观显示总量和各部分贡献
- 百分比堆叠:对比各部分占比而非绝对值
- 瀑布图:展示数据的累积变化过程
- 双向柱状图:对比正负两个方向的数据
🎯 核心概念
1. 堆叠柱状图(Stacked Bar)
javascript
option = {
series: [
{
name: '直接访问',
type: 'bar',
stack: 'total', // 堆叠组名称
data: [320, 332, 301, 334, 390]
},
{
name: '邮件营销',
type: 'bar',
stack: 'total',
data: [120, 132, 101, 134, 90]
},
{
name: '联盟广告',
type: 'bar',
stack: 'total',
data: [220, 182, 191, 234, 290]
}
]
};1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
堆叠原理:
时间点1: 320 + 120 + 220 = 660
时间点2: 332 + 132 + 182 = 646
...1
2
3
2
3
2. 百分比堆叠(Percentage Stack)
javascript
option = {
series: [
{
name: '分类A',
type: 'bar',
stack: 'total',
barWidth: '40%',
data: [320, 332, 301, 334, 390],
label: {
show: true,
formatter: function(params) {
return params.value + '%';
}
}
},
{
name: '分类B',
type: 'bar',
stack: 'total',
data: [120, 132, 101, 134, 90]
}
]
};
// 需要将数据转换为百分比格式
const percentageData = rawData.map(value =>
(value / total * 100).toFixed(1)
);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
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
3. 瀑布图(Waterfall)
javascript
// 使用透明柱子实现悬浮效果
option = {
series: [
{
// 辅助透明柱子
type: 'bar',
stack: 'total',
itemStyle: {
borderColor: 'transparent',
color: 'transparent'
},
data: [0, 120, 80, 50, 0] // 起始位置
},
{
// 实际数据柱子
type: 'bar',
stack: 'total',
data: [320, 200, 150, 100, 250], // 高度
label: {
show: true,
position: 'top'
}
}
]
};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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
🔧 详细配置项
完整堆叠柱状图配置
javascript
option = {
title: {
text: '季度销售分析',
subtext: '堆叠展示各产品线贡献',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow' // 阴影指示器
},
formatter: function(params) {
let result = '<strong>' + params[0].name + '</strong><br/>';
let total = 0;
params.forEach(param => {
total += param.value;
result += param.marker + param.seriesName + ': ¥' +
param.value.toLocaleString() + '万<br/>';
});
result += '<hr style="margin:5px 0"/>';
result += '<strong>总计: ¥' + total.toLocaleString() + '万</strong>';
return result;
}
},
legend: {
data: ['电子产品', '服装', '食品', '家居'],
top: 40
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '15%',
containLabel: true
},
xAxis: {
type: 'category',
data: ['Q1', 'Q2', 'Q3', 'Q4']
},
yAxis: {
type: 'value',
name: '销售额(万元)'
},
series: [
{
name: '电子产品',
type: 'bar',
stack: 'total',
barWidth: '50%',
emphasis: {
focus: 'series'
},
label: {
show: true,
position: 'inside',
color: '#fff',
fontSize: 10
},
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#83bff6' },
{ offset: 1, color: '#188df0' }
])
},
data: [320, 332, 301, 334]
},
{
name: '服装',
type: 'bar',
stack: 'total',
emphasis: {
focus: 'series'
},
label: {
show: true,
position: 'inside',
color: '#fff',
fontSize: 10
},
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#91cc75' },
{ offset: 1, color: '#43a047' }
])
},
data: [120, 132, 101, 134]
},
{
name: '食品',
type: 'bar',
stack: 'total',
emphasis: {
focus: 'series'
},
label: {
show: true,
position: 'inside',
color: '#fff',
fontSize: 10
},
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#fac858' },
{ offset: 1, color: '#f39c12' }
])
},
data: [220, 182, 191, 234]
},
{
name: '家居',
type: 'bar',
stack: 'total',
emphasis: {
focus: 'series'
},
label: {
show: true,
position: 'inside',
color: '#fff',
fontSize: 10
},
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#ee6666' },
{ offset: 1, color: '#e74c3c' }
])
},
data: [150, 232, 201, 154]
}
]
};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
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
瀑布图完整配置
javascript
option = {
title: {
text: '利润分析瀑布图',
subtext: '展示从收入到净利润的变化过程',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: function(params) {
const tar = params[1];
return tar.name + '<br/>' +
tar.seriesName + ' : ' +
'¥' + tar.value.toLocaleString() + '万';
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: ['总收入', '成本', '税费', '运营费用', '净利润']
},
yAxis: {
type: 'value',
name: '金额(万元)'
},
series: [
{
// 辅助列(透明)
name: '辅助',
type: 'bar',
stack: '总量',
itemStyle: {
borderColor: 'transparent',
color: 'transparent'
},
emphasis: {
itemStyle: {
borderColor: 'transparent',
color: 'transparent'
}
},
data: [0, 450, 370, 320, 0]
},
{
// 实际数据
name: '金额',
type: 'bar',
stack: '总量',
label: {
show: true,
position: 'top',
formatter: function(params) {
if (params.dataIndex === 0 || params.dataIndex === 4) {
return '¥' + params.value + '万';
}
return '-¥' + params.value + '万';
}
},
itemStyle: {
color: function(params) {
// 收入和净利润为正(绿色),其他为负(红色)
if (params.dataIndex === 0 || params.dataIndex === 4) {
return '#91cc75'; // 绿色
}
return '#ee6666'; // 红色
}
},
data: [550, 100, 80, 50, 320]
}
]
};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
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
双向柱状图(正负对比)
javascript
option = {
title: {
text: '收支对比分析',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: function(params) {
let result = params[0].name + '<br/>';
params.forEach(param => {
result += param.marker + param.seriesName + ': ';
if (param.value < 0) {
result += '-¥' + Math.abs(param.value) + '万<br/>';
} else {
result += '¥' + param.value + '万<br/>';
}
});
return result;
}
},
legend: {
data: ['收入', '支出'],
top: 30
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'value',
name: '金额(万元)',
axisLabel: {
formatter: function(value) {
return value < 0 ? '-¥' + Math.abs(value) : '¥' + value;
}
}
},
yAxis: {
type: 'category',
data: ['一月', '二月', '三月', '四月', '五月']
},
series: [
{
name: '收入',
type: 'bar',
stack: 'total',
label: {
show: true,
position: 'right'
},
itemStyle: {
color: '#91cc75'
},
data: [320, 332, 301, 334, 390]
},
{
name: '支出',
type: 'bar',
stack: 'total',
label: {
show: true,
position: 'left'
},
itemStyle: {
color: '#ee6666'
},
data: [-220, -182, -191, -234, -290]
}
]
};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
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
💡 实战案例
案例1:销售团队业绩堆叠分析
javascript
option = {
title: {
text: '销售团队业绩构成分析',
subtext: '按产品类型堆叠',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {
data: ['软件许可', '技术服务', '培训费用', '维护合同'],
top: 40
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '15%',
containLabel: true
},
xAxis: {
type: 'category',
data: ['张三', '李四', '王五', '赵六', '钱七']
},
yAxis: {
type: 'value',
name: '销售额(万元)'
},
series: [
{
name: '软件许可',
type: 'bar',
stack: 'total',
emphasis: { focus: 'series' },
itemStyle: {
color: '#5470c6'
},
data: [120, 150, 180, 140, 160]
},
{
name: '技术服务',
type: 'bar',
stack: 'total',
emphasis: { focus: 'series' },
itemStyle: {
color: '#91cc75'
},
data: [80, 100, 120, 90, 110]
},
{
name: '培训费用',
type: 'bar',
stack: 'total',
emphasis: { focus: 'series' },
itemStyle: {
color: '#fac858'
},
data: [40, 50, 60, 45, 55]
},
{
name: '维护合同',
type: 'bar',
stack: 'total',
emphasis: { focus: 'series' },
itemStyle: {
color: '#ee6666'
},
data: [60, 70, 80, 65, 75]
}
]
};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
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
案例2:预算执行瀑布图
javascript
const categories = ['年初预算', '追加预算', '人员成本', '设备采购', '运营费用', '年末结余'];
const helperData = [0, 1200, 1050, 750, 550, 0];
const values = [1200, 300, -450, -300, -200, 550];
option = {
title: {
text: '年度预算执行分析',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: function(params) {
const tar = params[1];
const value = tar.value;
let text = tar.name + '<br/>';
if (tar.dataIndex === 0 || tar.dataIndex === 1 || tar.dataIndex === 5) {
text += tar.seriesName + ': ¥' + value.toLocaleString() + '万';
} else {
text += tar.seriesName + ': -¥' + Math.abs(value).toLocaleString() + '万';
}
return text;
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: categories
},
yAxis: {
type: 'value',
name: '金额(万元)'
},
series: [
{
name: '辅助',
type: 'bar',
stack: '总量',
itemStyle: {
borderColor: 'transparent',
color: 'transparent'
},
emphasis: {
itemStyle: {
borderColor: 'transparent',
color: 'transparent'
}
},
data: helperData
},
{
name: '金额',
type: 'bar',
stack: '总量',
label: {
show: true,
position: 'top',
formatter: function(params) {
const value = params.value;
if (params.dataIndex === 0 || params.dataIndex === 1 || params.dataIndex === 5) {
return '¥' + value + '万';
}
return '-¥' + Math.abs(value) + '万';
}
},
itemStyle: {
color: function(params) {
if (params.dataIndex === 0 || params.dataIndex === 1 || params.dataIndex === 5) {
return '#91cc75'; // 绿色(正向)
}
return '#ee6666'; // 红色(负向)
}
},
data: values
}
]
};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
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
案例3:月度盈亏双向柱状图
javascript
option = {
title: {
text: '月度盈亏分析',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {
data: ['盈利', '亏损'],
top: 30
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: ['产品A', '产品B', '产品C', '产品D', '产品E']
},
yAxis: {
type: 'value',
name: '利润(万元)',
axisLabel: {
formatter: '{value}万'
}
},
series: [
{
name: '盈利',
type: 'bar',
data: [120, 200, 150, 80, 70],
itemStyle: {
color: '#91cc75'
},
label: {
show: true,
position: 'top',
formatter: '+{c}万'
}
},
{
name: '亏损',
type: 'bar',
data: [0, -50, 0, -30, -20],
itemStyle: {
color: '#ee6666'
},
label: {
show: true,
position: 'bottom',
formatter: '{c}万'
}
}
]
};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
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
⚠️ 常见问题
问题1:堆叠顺序不对
症状:柱子堆叠顺序与预期不符
解决:
javascript
// series数组中后面的系列会堆叠在上面
// 调整series顺序即可改变堆叠顺序
// ❌ 错误:希望A在最上面,但实际在最下面
series: [
{ name: 'A', stack: 'total', data: [...] },
{ name: 'B', stack: 'total', data: [...] },
{ name: 'C', stack: 'total', data: [...] }
]
// ✅ 正确:调整顺序
series: [
{ name: 'C', stack: 'total', data: [...] },
{ name: 'B', stack: 'total', data: [...] },
{ name: 'A', stack: 'total', data: [...] }
]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
问题2:瀑布图辅助线计算错误
症状:瀑布图的柱子位置不正确
解决:
javascript
// 辅助列的值 = 前面所有列的累加和
function calculateHelperData(values) {
const helper = [0];
let sum = values[0];
for (let i = 1; i < values.length - 1; i++) {
helper.push(sum);
sum += values[i];
}
helper.push(0); // 最后一项不需要辅助
return helper;
}
const values = [1200, 300, -450, -300, -200, 550];
const helperData = calculateHelperData(values);
// [0, 1200, 1500, 1050, 750, 0]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
问题3:标签重叠
症状:柱子上的文字标签重叠看不清
解决:
javascript
// 方式1:只显示重要标签
label: {
show: true,
formatter: function(params) {
return params.value > 100 ? params.value : '';
}
}
// 方式2:交替显示位置
label: {
show: true,
position: function(params) {
return params.dataIndex % 2 === 0 ? 'top' : 'inside';
}
}
// 方式3:旋转标签
label: {
show: true,
rotate: 45,
offset: [0, 10]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
🎯 最佳实践
1. 堆叠图适用场景
javascript
// ✅ 适合堆叠:
// - 展示总量和构成
// - 各部分之和有意义
// - 类别数量适中(3-6个)
// ❌ 不适合堆叠:
// - 只需要看趋势
// - 数据差异过大
// - 类别太多(>8个)1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
2. 瀑布图数据计算
javascript
class WaterfallCalculator {
static calculate(data) {
const helper = [];
let cumulative = 0;
data.forEach((value, index) => {
if (index === 0 || index === data.length - 1) {
helper.push(0);
} else {
helper.push(cumulative);
}
cumulative += value;
});
return { helper, values: data };
}
}
// 使用
const { helper, values } = WaterfallCalculator.calculate([
1200, 300, -450, -300, -200, 550
]);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
3. 响应式柱宽
javascript
// 根据数据量动态调整柱宽
const dataLength = xAxisData.length;
const barWidth = dataLength > 10 ? '30%' : '50%';
option = {
series: [{
type: 'bar',
barWidth: barWidth
}]
};1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
📊 性能指标
| 配置 | 100柱子 | 1000柱子 | 建议 |
|---|---|---|---|
| 普通柱状图 | 10ms | 50ms | 无限制 |
| 堆叠柱状图 | 15ms | 80ms | 堆叠组≤6 |
| 瀑布图 | 12ms | 60ms | 注意辅助列 |
| 渐变色柱状图 | 18ms | 100ms | 大数据简化 |
🔗 相关链接
- 柱状图基础.md)
- 折线图堆叠.md)
- visualMap视觉映射
💎 总结
高级柱状图核心价值:
- ✅ 堆叠展示构成关系
- ✅ 瀑布图展示变化过程
- ✅ 双向柱状图对比正负
- ✅ 百分比堆叠对比占比
关键决策点:
- 需要展示总量 → 使用堆叠
- 需要展示变化过程 → 使用瀑布图
- 需要对比正负 → 使用双向柱状图
- 需要对比占比 → 使用百分比堆叠
掌握高级柱状图,让商业数据分析更专业!📊
瀑布图效果
{
"title": {
"text": "利润分析瀑布图",
"left": "center"
},
"tooltip": {
"trigger": "axis",
"axisPointer": {
"type": "shadow"
}
},
"xAxis": {
"type": "category",
"data": [
"总收入",
"固定成本",
"变动成本",
"税费",
"净利润"
]
},
"yAxis": {
"type": "value"
},
"series": [
{
"type": "bar",
"stack": "total",
"data": [
{
"value": 1000
},
{
"value": -300
},
{
"value": -200
},
{
"value": -150
},
{
"value": 350
}
],
"itemStyle": {}
}
]
}