ECharts 飞线图实现完全指南
文档类型: 实战指南
难度等级: ⭐⭐⭐
源码版本: ECharts 5.x
本文行数: 约480行
📋 目录
🎯 飞线基础原理
什么是飞线?
飞线是ECharts中用于展示两点之间流动关系的可视化效果,常用于:
- 人口迁徙
- 物流运输
- 航班路线
- 网络流量
typescript
const option = {
geo: {
map: 'china'
},
series: [{
type: 'lines',
coordinateSystem: 'geo',
// 飞线数据: [起点坐标, 终点坐标]
data: [
{
coords: [
[116.4074, 39.9042], // 北京
[121.4737, 31.2304] // 上海
]
}
],
// 线样式
lineStyle: {
color: '#a6c84c',
width: 1,
curveness: 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
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
✈️ lines系列配置
完整配置示例
typescript
const option = {
geo: {
map: 'china',
roam: true,
itemStyle: {
areaColor: '#323c48',
borderColor: '#404a59'
}
},
series: [
{
// === 第一层: 静态线 ===
name: '路线',
type: 'lines',
zlevel: 1,
coordinateSystem: 'geo',
data: generateRoutes(),
lineStyle: {
color: '#a6c84c',
width: 1,
opacity: 0.4,
curveness: 0.2
}
},
{
// === 第二层: 动态效果 ===
name: '流动',
type: 'lines',
zlevel: 2,
coordinateSystem: 'geo',
effect: {
show: true,
period: 6, // 动画周期(秒)
trailLength: 0.7, // 尾迹长度(0-1)
color: '#fff', // 动画标记颜色
symbol: 'arrow', // 箭头符号
symbolSize: 6 // 箭头大小
},
data: generateRoutes(),
lineStyle: {
color: '#a6c84c',
width: 0, // 隐藏线,只显示动画
opacity: 0,
curveness: 0.2
}
}
]
};
function generateRoutes() {
const beijing = [116.4074, 39.9042];
const cities = [
[121.4737, 31.2304], // 上海
[113.2644, 23.1291], // 广州
[114.0579, 22.5431], // 深圳
[104.0665, 30.5723] // 成都
];
return cities.map(coords => ({
coords: [beijing, coords]
}));
}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
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
🎬 动态飞线效果
实时数据流
typescript
class RealtimeFlightLines {
private chart: echarts.ECharts;
private routes: any[] = [];
constructor(container: HTMLElement) {
this.chart = echarts.init(container);
this.init();
this.startDataStream();
}
private init() {
const option = {
geo: {
map: 'china',
roam: false,
itemStyle: {
areaColor: '#323c48',
borderColor: '#404a59'
}
},
series: [{
type: 'lines',
zlevel: 2,
coordinateSystem: 'geo',
effect: {
show: true,
period: 4,
trailLength: 0.5,
color: '#f4e925',
symbolSize: 5
},
lineStyle: {
color: '#f4e925',
width: 0,
curveness: 0.2
},
data: this.routes
}]
};
this.chart.setOption(option);
}
private startDataStream() {
// 模拟实时添加新路线
setInterval(() => {
this.addNewRoute();
}, 2000);
}
private addNewRoute() {
const from = this.getRandomCity();
const to = this.getRandomCity();
if (from !== to) {
this.routes.push({
coords: [from.coords, to.coords],
name: `${from.name} → ${to.name}`
});
// 保持最多50条路线
if (this.routes.length > 50) {
this.routes.shift();
}
this.chart.setOption({
series: [{
data: this.routes
}]
});
}
}
private getRandomCity() {
const cities = [
{ name: '北京', coords: [116.4074, 39.9042] },
{ name: '上海', coords: [121.4737, 31.2304] },
{ name: '广州', coords: [113.2644, 23.1291] },
{ name: '深圳', coords: [114.0579, 22.5431] }
];
return cities[Math.floor(Math.random() * cities.length)];
}
dispose() {
this.chart.dispose();
}
}
// 使用
const flightLines = new RealtimeFlightLines(document.getElementById('chart')!);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
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
带权重的飞线
typescript
const option = {
visualMap: {
show: false,
min: 0,
max: 100,
inRange: {
color: ['#ffffb2', '#fecc5c', '#fd8d3c', '#f03b20', '#bd0026']
}
},
series: [{
type: 'lines',
coordinateSystem: 'geo',
data: [
{
coords: [[116.4074, 39.9042], [121.4737, 31.2304]],
value: 80 // 权重值
},
{
coords: [[116.4074, 39.9042], [113.2644, 23.1291]],
value: 60
},
{
coords: [[116.4074, 39.9042], [104.0665, 30.5723]],
value: 40
}
],
lineStyle: {
width: (params: any) => {
return params.data.value / 20; // 根据权重调整宽度
}
}
}]
};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
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
🚀 性能优化
大数据量优化
typescript
const option = {
series: [{
type: 'lines',
data: largeRoutes, // 1000+ 路线
// 性能优化
progressive: 100, // 渐进式渲染
progressiveThreshold: 500,
// 简化效果
effect: {
show: true,
period: 10, // 延长周期减少计算
trailLength: 0.3 // 缩短尾迹
},
// 关闭不必要的特性
polyline: false, // 不使用折线
large: true // 启用大数据模式
}]
};1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
分层渲染策略
typescript
// 重要路线 - 高质量渲染
const importantRoutes = {
type: 'lines',
zlevel: 2,
data: mainRoutes,
effect: {
show: true,
period: 4,
trailLength: 0.7
},
lineStyle: {
width: 2
}
};
// 次要路线 - 简化渲染
const secondaryRoutes = {
type: 'lines',
zlevel: 1,
data: minorRoutes,
effect: {
show: false // 无动画
},
lineStyle: {
width: 0.5,
opacity: 0.3
}
};
series: [secondaryRoutes, importantRoutes]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
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
🎯 最佳实践总结
✅ DO - 推荐做法
使用双层lines实现飞线
typescript// 第一层: 静态线(背景) // 第二层: 动态效果(前景)1
2控制飞线数量
typescript// 保持在50条以内 if (routes.length > 50) routes.shift();1
2合理设置curveness
typescriptcurveness: 0.2 // 0.1-0.3为宜1
❌ DON'T - 避免做法
- 避免过多飞线重叠typescript
// ❌ 不好 - 100条线从一个点出发 data: Array.from({length: 100}, ...) // ✅ 好 - 分散起点 data: distributedRoutes1
2
3
4
5
