折线图高级特性完全指南 - 堆叠与面积图
📋 概述
折线图高级特性 包括堆叠折线、面积图、平滑曲线、断点处理等进阶功能。这些特性能够展示数据的累积关系、趋势区间和不连续数据,是时间序列分析的利器。
核心价值
- 堆叠展示:直观显示多个系列的累积效果
- 面积填充:强调数据的变化范围和量级
- 平滑曲线:美化数据趋势,减少噪点影响
- 断点处理:优雅展示缺失数据
🎯 核心概念
1. 堆叠折线(Stack)
javascript
option = {
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量', // 相同stack值的系列会堆叠
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '联盟广告',
type: 'line',
stack: '总量', // 与上一个系列堆叠
data: [220, 182, 191, 234, 290, 330, 310]
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410]
}
]
};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: 120 + 220 + 150 = 490
时间点2: 132 + 182 + 232 = 546
...1
2
3
2
3
2. 面积图(Area)
javascript
option = {
series: [{
type: 'line',
data: [120, 200, 150, 80, 70],
areaStyle: {
opacity: 0.3, // 透明度
color: '#5470c6' // 填充颜色
}
}]
};1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
3. 平滑曲线(Smooth)
javascript
option = {
series: [{
type: 'line',
smooth: true, // 启用平滑
smoothMonotone: 'x', // 平滑方向
data: [120, 200, 150, 80, 70]
}]
};1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
🔧 详细配置项
完整堆叠配置
javascript
option = {
title: {
text: '堆叠折线图示例'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
},
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问'],
top: 30
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value',
name: '访问量'
},
series: [
{
name: '邮件营销',
type: 'line',
stack: 'total', // 堆叠组名称
symbol: 'circle',
symbolSize: 6,
lineStyle: {
width: 2
},
areaStyle: {
opacity: 0.1
},
emphasis: {
focus: 'series'
},
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '联盟广告',
type: 'line',
stack: 'total',
symbol: 'circle',
symbolSize: 6,
lineStyle: {
width: 2
},
areaStyle: {
opacity: 0.1
},
emphasis: {
focus: 'series'
},
data: [220, 182, 191, 234, 290, 330, 310]
},
{
name: '视频广告',
type: 'line',
stack: 'total',
symbol: 'circle',
symbolSize: 6,
lineStyle: {
width: 2
},
areaStyle: {
opacity: 0.1
},
emphasis: {
focus: 'series'
},
data: [150, 232, 201, 154, 190, 330, 410]
},
{
name: '直接访问',
type: 'line',
stack: 'total',
symbol: 'circle',
symbolSize: 6,
lineStyle: {
width: 2
},
areaStyle: {
opacity: 0.1
},
emphasis: {
focus: 'series'
},
data: [320, 332, 301, 334, 390, 330, 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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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
面积图完整配置
javascript
option = {
series: [{
name: '销售额',
type: 'line',
data: [820, 932, 901, 934, 1290, 1330, 1320],
// === 线条样式 ===
lineStyle: {
width: 3,
color: '#5470c6',
type: 'solid' // 'solid' | 'dashed' | 'dotted'
},
// === 面积填充 ===
areaStyle: {
// 渐变色填充
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(84, 112, 198, 0.5)' },
{ offset: 1, color: 'rgba(84, 112, 198, 0.05)' }
]),
opacity: 1
},
// === 数据点样式 ===
symbol: 'circle',
symbolSize: 8,
itemStyle: {
color: '#5470c6',
borderColor: '#fff',
borderWidth: 2
},
// === 标签 ===
label: {
show: true,
position: 'top',
formatter: '{c}',
color: '#333'
},
// === 平滑曲线 ===
smooth: true,
smoothMonotone: 'x', // 'x' | 'y'
// === 阶梯线图 ===
step: false, // 'start' | 'middle' | 'end' | false
// === 端点行为 ===
connectNulls: false // 是否连接空数据
}]
};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
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
平滑曲线算法配置
javascript
option = {
series: [{
type: 'line',
data: [120, 200, 150, 80, 70, 110, 130],
// 平滑度控制
smooth: true,
smoothMonotone: 'x', // 只在X轴方向平滑
// 自定义张力(0-1)
lineStyle: {
width: 2
}
}]
};
// ECharts使用Catmull-Rom样条插值
// 张力越大,曲线越平缓1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
💡 实战案例
案例1:销售数据堆叠分析
javascript
option = {
title: {
text: '各渠道销售贡献分析',
subtext: '堆叠展示总销售额及各渠道占比',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
},
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;border:none;border-top:1px solid #ccc"/>';
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',
boundaryGap: false,
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
},
yAxis: {
type: 'value',
name: '销售额(万元)',
axisLabel: {
formatter: '¥{value}'
}
},
series: [
{
name: '线上商城',
type: 'line',
stack: 'Total',
smooth: true,
lineStyle: { width: 2 },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(84, 112, 198, 0.4)' },
{ offset: 1, color: 'rgba(84, 112, 198, 0.05)' }
])
},
emphasis: { focus: 'series' },
data: [120, 132, 101, 134, 90, 230, 210, 250, 280, 320, 350, 380]
},
{
name: '实体店',
type: 'line',
stack: 'Total',
smooth: true,
lineStyle: { width: 2 },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(145, 204, 117, 0.4)' },
{ offset: 1, color: 'rgba(145, 204, 117, 0.05)' }
])
},
emphasis: { focus: 'series' },
data: [220, 182, 191, 234, 290, 330, 310, 340, 360, 380, 400, 420]
},
{
name: '经销商',
type: 'line',
stack: 'Total',
smooth: true,
lineStyle: { width: 2 },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(250, 200, 88, 0.4)' },
{ offset: 1, color: 'rgba(250, 200, 88, 0.05)' }
])
},
emphasis: { focus: 'series' },
data: [150, 232, 201, 154, 190, 330, 410, 380, 400, 420, 450, 480]
},
{
name: '大客户',
type: 'line',
stack: 'Total',
smooth: true,
lineStyle: { width: 2 },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(238, 102, 102, 0.4)' },
{ offset: 1, color: 'rgba(238, 102, 102, 0.05)' }
])
},
emphasis: { focus: 'series' },
data: [320, 332, 301, 334, 390, 330, 320, 360, 380, 400, 420, 450]
}
]
};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
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
案例2:温度变化面积图
javascript
option = {
title: {
text: '北京周气温变化',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value',
name: '温度(°C)'
},
series: [
{
name: '最高温',
type: 'line',
data: [28, 30, 32, 29, 27, 26, 28],
smooth: true,
lineStyle: {
color: '#ff6b6b',
width: 3
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255, 107, 107, 0.5)' },
{ offset: 1, color: 'rgba(255, 107, 107, 0.05)' }
])
},
markPoint: {
data: [
{ type: 'max', name: '最高' },
{ type: 'min', name: '最低' }
]
}
},
{
name: '最低温',
type: 'line',
data: [18, 20, 22, 19, 17, 16, 18],
smooth: true,
lineStyle: {
color: '#4ecdc4',
width: 3
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(78, 205, 196, 0.5)' },
{ offset: 1, color: 'rgba(78, 205, 196, 0.05)' }
])
}
}
]
};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
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
案例3:股票价格走势(带断点)
javascript
option = {
title: {
text: '股票收盘价走势',
subtext: '包含停牌数据(断点处理)'
},
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: [
'2023-01-03', '2023-01-04', '2023-01-05', '2023-01-06',
'2023-01-09', '2023-01-10', '2023-01-11', // 跳过周末
'2023-01-16', '2023-01-17', '2023-01-18' // 跳过长假
],
boundaryGap: false
},
yAxis: {
type: 'value',
scale: true, // 不从0开始
name: '价格(元)'
},
series: [{
name: '收盘价',
type: 'line',
data: [
100.5, 102.3, 101.8, 103.2,
104.5, 103.8, 105.2,
'-', // 停牌数据
106.8, 107.5, 108.2
],
connectNulls: false, // 不连接空数据,形成断点
symbol: 'circle',
symbolSize: 6,
lineStyle: {
width: 2,
color: '#5470c6'
},
markLine: {
data: [
{ type: 'average', name: '均价' }
]
}
}]
};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
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
案例4:多系列对比(平滑vs直线)
javascript
option = {
title: {
text: '平滑曲线与直线对比'
},
legend: {
data: ['原始数据', '平滑处理', '移动平均'],
top: 30
},
xAxis: {
type: 'category',
data: Array.from({length: 20}, (_, i) => i + 1)
},
yAxis: {
type: 'value'
},
series: [
{
name: '原始数据',
type: 'line',
smooth: false, // 直线连接
symbol: 'circle',
symbolSize: 4,
lineStyle: {
width: 1,
type: 'dashed'
},
data: [120, 132, 101, 134, 90, 230, 210, 180, 200, 190,
210, 220, 180, 170, 190, 200, 210, 220, 230, 240]
},
{
name: '平滑处理',
type: 'line',
smooth: true, // 平滑曲线
smoothMonotone: 'x',
lineStyle: {
width: 3,
color: '#5470c6'
},
data: [120, 132, 101, 134, 90, 230, 210, 180, 200, 190,
210, 220, 180, 170, 190, 200, 210, 220, 230, 240]
},
{
name: '移动平均',
type: 'line',
smooth: true,
lineStyle: {
width: 2,
color: '#91cc75'
},
// 计算5日移动平均
data: (function() {
const rawData = [120, 132, 101, 134, 90, 230, 210, 180, 200, 190,
210, 220, 180, 170, 190, 200, 210, 220, 230, 240];
const result = [];
for (let i = 0; i < rawData.length; i++) {
if (i < 4) {
result.push('-');
} else {
const sum = rawData.slice(i - 4, i + 1).reduce((a, b) => a + b, 0);
result.push(Math.round(sum / 5));
}
}
return result;
})()
}
]
};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
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
⚠️ 常见问题
问题1:堆叠后数据不正确
症状:堆叠后的数值不符合预期
原因:stack名称不一致或数据类型错误
解决:
javascript
// ❌ 错误:stack名称不同
series: [
{ stack: '总量', data: [...] },
{ stack: '总数', data: [...] } // 不同的stack,不会堆叠
]
// ✅ 正确:确保stack名称完全一致
series: [
{ stack: 'total', data: [...] },
{ stack: 'total', data: [...] }
]
// ✅ 确保数据是数字类型
data: [120, 132, 101] // 正确
data: ['120', '132', '101'] // 错误:字符串1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
问题2:面积图遮挡严重
症状:前面的系列完全遮挡后面的系列
解决:
javascript
// 方式1:降低透明度
areaStyle: {
opacity: 0.1 // 降低到10%
}
// 方式2:使用渐变色
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(84, 112, 198, 0.3)' },
{ offset: 1, color: 'rgba(84, 112, 198, 0.05)' }
])
}
// 方式3:调整系列顺序(后面的在上面)
series: [
{ data: largeValues }, // 大数据放前面
{ data: smallValues } // 小数据放后面
]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
问题3:平滑曲线过度拟合
症状:平滑后的曲线偏离原始数据太多
解决:
javascript
// ECharts的smooth使用Catmull-Rom样条
// 无法直接调整张力,可以通过数据预处理实现
// 方式1:使用移动平均预处理
function movingAverage(data, window) {
const result = [];
for (let i = 0; i < data.length; i++) {
if (i < window - 1) {
result.push(data[i]);
} else {
const sum = data.slice(i - window + 1, i + 1).reduce((a, b) => a + b, 0);
result.push(sum / window);
}
}
return result;
}
// 方式2:关闭平滑
smooth: false
// 方式3:使用阶梯线图
step: 'middle' // 阶梯状,不过度平滑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
// 适用场景:
// ✅ 展示总量和组成部分
// ✅ 各部分之和有意义
// ✅ 需要对比各部分贡献
// 不适用场景:
// ❌ 各部分之间无关联
// ❌ 只需要看趋势不看总量
// ❌ 数据差异过大导致小数据不可见1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
2. 面积图优化
javascript
// 大数据量时优化性能
series: [{
type: 'line',
data: hugeData,
areaStyle: {
opacity: 0.1 // 降低渲染复杂度
},
sampling: 'lttb', // 启用降采样
progressive: 5000 // 渐进式渲染
}]1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
3. 响应式配置
javascript
// 根据屏幕尺寸调整
const isMobile = window.innerWidth < 768;
option = {
series: [{
type: 'line',
smooth: !isMobile, // 移动端关闭平滑提升性能
symbolSize: isMobile ? 4 : 8,
lineStyle: {
width: isMobile ? 2 : 3
}
}]
};1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
📊 性能指标
| 配置 | 1000数据点 | 10000数据点 | 建议 |
|---|---|---|---|
| 直线+无面积 | 15ms | 80ms | 最佳性能 |
| 平滑+无面积 | 25ms | 150ms | 适中 |
| 直线+面积 | 20ms | 120ms | 注意透明度 |
| 平滑+面积 | 35ms | 200ms | 大数据避免 |
🔗 相关链接
- 折线图基础.md)
- dataZoom缩放
- 大数据large模式
💎 总结
堆叠和面积图核心价值:
- ✅ 堆叠展示总量和构成
- ✅ 面积强调数据范围
- ✅ 平滑曲线美化趋势
- ✅ 断点处理缺失数据
关键决策点:
- 是否需要展示总量 → 使用堆叠
- 是否需要强调量级 → 使用面积
- 数据是否有噪点 → 使用平滑
- 数据是否连续 → 处理断点
掌握高级折线图,让时间序列分析更直观!📈
堆叠面积图效果
{
"title": {
"text": "流量来源分析",
"left": "center"
},
"tooltip": {
"trigger": "axis"
},
"legend": {
"data": [
"直接访问",
"邮件营销",
"联盟广告"
],
"top": "10%"
},
"xAxis": {
"type": "category",
"boundaryGap": false,
"data": [
"周一",
"周二",
"周三",
"周四",
"周五",
"周六",
"周日"
]
},
"yAxis": {
"type": "value"
},
"series": [
{
"name": "直接访问",
"type": "line",
"stack": "Total",
"areaStyle": {
"opacity": 0.3
},
"data": [
120,
132,
101,
134,
90,
230,
210
]
},
{
"name": "邮件营销",
"type": "line",
"stack": "Total",
"areaStyle": {
"opacity": 0.3
},
"data": [
220,
182,
191,
234,
290,
330,
310
]
},
{
"name": "联盟广告",
"type": "line",
"stack": "Total",
"areaStyle": {
"opacity": 0.3
},
"data": [
150,
232,
201,
154,
190,
330,
410
]
}
]
}