echarts迁徙图

ECharts 5.x 迁徙图完整教程:从基础飞线到高级可视化

不管是人口流动、物流运输、客流分析,还是品牌门店辐射范围,迁徙图都是数据可视化大屏里最吸睛、最常用的效果。
今天就从基础原理到完整实现,再到进阶玩法,把ECharts 5.x迁徙图的所有知识点讲透,附完整可运行代码,哪怕是新手也能直接做出丝滑的飞线效果。


补充说明

本次教程基于 ECharts 5.x 最新稳定版 编写,参考的旧版ECharts 2.x资源链接 http://echarts.baidu.com/build/dist 已解析失败,该地址是2016年ECharts 2.x的旧版资源,目前已失效。ECharts 5.x对地图、飞线API做了全面优化,本文会同步标注新旧版本的核心差异,方便老项目迁移。


一、先搞懂:迁徙图的核心原理

ECharts的迁徙图,本质是 「地理坐标系(geo)」+「线图系列(lines)」+「特效动画(effect)」 三者的组合,核心逻辑非常清晰:

  1. geo地理坐标系:提供地图底图,把真实的经纬度坐标映射到屏幕像素位置,是所有可视化的基础;
  2. lines系列:绘制起点到终点的飞线路径,默认支持贝塞尔平滑曲线,完美模拟真实的飞线弧度;
  3. effect特效:给飞线添加流动动画,模拟箭头/光点的移动效果,实现「迁徙」的视觉表达,是整个效果的灵魂。

和ECharts 2.x旧版相比,5.x的迁徙图API更简洁、特效性能更好,同时保留了100%的自定义能力,旧版常用的effectScatter光点点缀+lines飞线的组合,在5.x中依然完美兼容,配置更灵活。

二、前置准备:2步搞定环境与数据

和上一篇省市区地图教程完全兼容,无需额外更换环境,直接复用即可。

1. 引入ECharts 5.x

优先用CDN快速引入,也可以用npm安装,和之前的教程保持一致:

1
2
<!-- ECharts 5.x 核心库,稳定版 -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>

工程化项目安装:

1
npm install echarts --save

2. 准备2份核心数据

迁徙图的实现,离不开2份关键数据,90%的踩坑都来自数据问题:

(1)地图底图GeoJSON数据

和上一篇教程一致,推荐用阿里云DataV地图选择器下载(免费、无版权、坐标系匹配):https://datav.aliyun.com/portal/school/atlas/area_selector

  • 全国迁徙图:下载「中国.json」;
  • 省份内迁徙图:下载对应省份的GeoJSON文件。

(2)城市经纬度映射表

迁徙图的飞线,必须通过起点、终点的经纬度定位,这里给大家整理了国内主要城市的经纬度(GCJ02火星坐标系,和阿里云地图、高德地图完全匹配,不会出现坐标偏移):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 国内主要城市经纬度(GCJ02坐标系,单位:度)
const cityGeo = {
'北京': [116.4074, 39.9042],
'上海': [121.4737, 31.2304],
'广州': [113.2644, 23.1291],
'深圳': [114.0579, 22.5431],
'武汉': [114.3055, 30.5931],
'成都': [104.0668, 30.5728],
'西安': [108.9398, 34.3416],
'杭州': [120.1551, 30.2741],
'重庆': [106.5516, 29.5630],
'南京': [118.7969, 32.0603],
'郑州': [113.6254, 34.7466],
'长沙': [112.9388, 28.2282],
'沈阳': [123.4328, 41.8045],
'哈尔滨': [126.5348, 45.8038],
'乌鲁木齐': [87.6177, 43.7928],
'海口': [110.1999, 20.0442]
};

⚠️ 避坑提醒:必须保证「地图数据」和「经纬度」是同一坐标系,国内通用GCJ02,不要混用GPS的WGS84坐标系,否则会出现严重的坐标偏移。

三、基础版:单中心全国迁徙图(完整可运行代码)

最常用的场景:以一个城市为中心,展示全国其他城市到该城市的迁徙流向(比如以上海为中心的全国客流迁徙),复制以下代码到HTML文件,直接打开就能看到效果。

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
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>ECharts 5.x 全国迁徙图</title>
<!-- 引入ECharts 5.x -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* 地图容器必须设置明确宽高 */
#map-container {
width: 100vw;
height: 100vh;
background: #050d3d; /* 深色背景,飞线效果更明显 */
}
</style>
</head>
<body>
<div id="map-container"></div>

<script>
// 1. 城市经纬度映射表
const cityGeo = {
'北京': [116.4074, 39.9042],
'上海': [121.4737, 31.2304],
'广州': [113.2644, 23.1291],
'深圳': [114.0579, 22.5431],
'武汉': [114.3055, 30.5931],
'成都': [104.0668, 30.5728],
'西安': [108.9398, 34.3416],
'杭州': [120.1551, 30.2741],
'重庆': [106.5516, 29.5630],
'南京': [118.7969, 32.0603],
'郑州': [113.6254, 34.7466],
'长沙': [112.9388, 28.2282],
'沈阳': [123.4328, 41.8045],
'哈尔滨': [126.5348, 45.8038],
'乌鲁木齐': [87.6177, 43.7928],
'海口': [110.1999, 20.0442]
};

// 2. 迁徙中心城市
const centerCity = '上海';
const centerCoord = cityGeo[centerCity];

// 3. 构造迁徙数据:起点→终点,value控制飞线权重
const migrateData = [
{ from: '北京', to: centerCity, value: 1200 },
{ from: '广州', to: centerCity, value: 980 },
{ from: '深圳', to: centerCity, value: 950 },
{ from: '武汉', to: centerCity, value: 860 },
{ from: '成都', to: centerCity, value: 780 },
{ from: '西安', to: centerCity, value: 650 },
{ from: '杭州', to: centerCity, value: 1100 },
{ from: '重庆', to: centerCity, value: 720 },
{ from: '南京', to: centerCity, value: 920 },
{ from: '郑州', to: centerCity, value: 680 },
{ from: '长沙', to: centerCity, value: 620 },
{ from: '沈阳', to: centerCity, value: 550 },
{ from: '哈尔滨', to: centerCity, value: 420 },
{ from: '乌鲁木齐', to: centerCity, value: 280 },
{ from: '海口', to: centerCity, value: 360 }
];

// 4. 处理飞线数据:转换成ECharts需要的格式
const lineData = migrateData.map(item => {
const fromCoord = cityGeo[item.from];
return {
name: `${item.from}${item.to}`,
value: item.value,
coords: [fromCoord, centerCoord] // 核心:[起点经纬度, 终点经纬度]
};
});

// 5. 处理终点光点数据:给起点和终点加高亮圆点
const scatterData = migrateData.map(item => {
return {
name: item.from,
value: cityGeo[item.from].concat(item.value)
};
});
// 加入中心城市的光点
scatterData.push({
name: centerCity,
value: centerCoord.concat(2000)
});

// 6. 初始化ECharts实例
const chartDom = document.getElementById('map-container');
const myChart = echarts.init(chartDom);
let mapDataCache = {}; // 地图数据缓存

// 7. 加载并注册中国地图
async function loadChinaMap() {
if (mapDataCache['china']) {
return mapDataCache['china'];
}
try {
// 这里替换成你自己的中国地图GeoJSON路径
const res = await fetch('./map-data/china.json');
const geoJSON = await res.json();
echarts.registerMap('china', geoJSON);
mapDataCache['china'] = geoJSON;
return geoJSON;
} catch (err) {
console.error('地图加载失败:', err);
alert('地图数据加载失败,请检查GeoJSON文件路径');
}
}

// 8. 生成地图配置项
function getOption() {
return {
backgroundColor: 'transparent',
// 提示框
tooltip: {
trigger: 'item',
formatter: (params) => {
if (params.seriesType === 'lines') {
return `${params.name}<br/>迁徙量:${params.value}`;
}
if (params.seriesType === 'effectScatter') {
return `${params.name}<br/>流量:${params.value[2]}`;
}
return params.name;
}
},
// 地理坐标系:地图底图核心配置
geo: {
map: 'china', // 注册的地图名称
roam: true, // 开启缩放和平移
scaleLimit: { min: 0.8, max: 3 }, // 缩放范围限制
zoom: 1.2, // 初始缩放比例
label: {
show: false, // 默认不显示省份名称,避免拥挤
color: '#fff'
},
itemStyle: {
areaColor: '#102a83', // 地图区域颜色
borderColor: '#3b8cff', // 省份边界颜色
borderWidth: 1
},
emphasis: { // 鼠标悬停样式
label: { show: true, color: '#fff', fontSize: 14 },
itemStyle: { areaColor: '#1a46b8' }
}
},
// 系列配置
series: [
// 第一组:飞线系列(迁徙图核心)
{
name: '迁徙飞线',
type: 'lines',
coordinateSystem: 'geo', // 绑定地理坐标系
zlevel: 2, // 层级,必须大于geo,否则会被地图覆盖
effect: {
show: true, // 开启飞线特效
symbol: 'arrow', // 特效形状:arrow箭头、circle圆形
symbolSize: 6, // 特效箭头/圆点大小
period: 4, // 动画周期,单位秒,数值越小速度越快
trailLength: 0.3, // 拖尾长度,0-1,越大拖尾越长
constantSpeed: 30, // 固定动画速度,替代period,长短线动画更均匀
},
lineStyle: {
color: '#3b8cff', // 飞线颜色
width: 1.5, // 飞线宽度
curveness: 0.2, // 飞线曲率,0是直线,越大弯曲越明显
opacity: 0.7 // 飞线透明度
},
// 飞线数据
data: lineData
},
// 第二组:高亮光点系列(起点终点点缀)
{
name: '城市光点',
type: 'effectScatter',
coordinateSystem: 'geo',
zlevel: 3, // 层级大于飞线,在最上层
rippleEffect: { // 涟漪特效
brushType: 'stroke', // 描边样式,fill是填充
scale: 4, // 涟漪缩放倍数
period: 3 // 动画周期
},
label: {
show: true,
position: 'right',
formatter: '{b}',
color: '#fff',
fontSize: 12
},
symbolSize: (val) => {
// 按数值大小调整光点大小
return val[2] / 100 + 8;
},
itemStyle: {
color: '#ffd700', // 光点颜色,金色更醒目
shadowBlur: 10,
shadowColor: '#ffd700'
},
// 光点数据
data: scatterData
}
]
};
}

// 9. 初始化地图
loadChinaMap().then(() => {
const option = getOption();
myChart.setOption(option);
});

// 10. 窗口大小变化自适应
window.addEventListener('resize', () => {
myChart.resize();
});
</script>
</body>
</html>

四、核心配置详解:飞线效果的灵魂

上面的代码可以直接运行,但想要做出符合业务需求的个性化效果,必须搞懂每一个核心配置的作用,这里把最关键的参数拆解开讲透。

1. geo地理坐标系:底图的基础配置

参数 作用 调优建议
map 绑定已注册的地图名称,必须和registerMap的第一个参数一致 全国地图填china,省份地图填省份名
roam 开启/关闭缩放和平移 大屏建议开启,固定展示的小地图关闭
scaleLimit 缩放的最小/最大比例 避免用户缩放过小/过大,导致地图超出屏幕
itemStyle 地图的填充色、边界色 深色背景用冷色调,浅色背景用暖色调,和飞线形成视觉对比

2. lines系列:飞线的核心配置

这是迁徙图的灵魂,90%的效果调整都在这里:

参数 作用 关键细节
coordinateSystem: 'geo' 绑定地理坐标系 必须配置,否则飞线无法在地图上定位
data.coords 飞线的起点和终点 固定格式:[起点经纬度数组, 终点经纬度数组],顺序不能反
polyline 直线/曲线模式 false(默认)为贝塞尔平滑曲线,true为直线,长距离迁徙推荐曲线,短距离省内迁徙推荐直线
zlevel 图层层级 必须大于geo的zlevel(默认0),否则飞线会被地图底图覆盖,完全看不到
effect.show 开启/关闭飞线动画 必须设为true,否则只有静态线条,没有流动效果
effect.symbol 特效形状 常用arrow(箭头,迁徙感更强)、circle(圆形圆点,更柔和)
effect.period 动画周期 单位秒,数值越小,飞线流动速度越快,建议2-6之间
effect.trailLength 拖尾长度 0-1之间,数值越大,拖尾越长,视觉效果越明显,建议0.2-0.5
lineStyle.curveness 飞线曲率 0为直线,0.2-0.3适合全国长距离飞线,0.1适合省内短距离飞线,负数会反向弯曲
lineStyle.width 飞线宽度 可以通过函数width: (params) => params.value / 500,按迁徙量动态调整宽度,更有层次感

3. effectScatter系列:光点点缀

用来高亮起点和终点,增强视觉效果,核心参数:

  • rippleEffect:涟漪特效,控制光点的扩散动画;
  • symbolSize:光点大小,建议按数值大小动态调整,突出核心城市;
  • zlevel:必须大于lines的层级,保证光点在飞线最上层。

五、进阶玩法:解锁更多可视化效果

1. 多中心互迁迁徙图

如果需要展示多个城市之间的互相迁徙,只需要修改migrateData,支持多起点多终点,示例数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const migrateData = [
{ from: '北京', to: '上海', value: 1200 },
{ from: '上海', to: '北京', value: 1100 },
{ from: '广州', to: '深圳', value: 980 },
{ from: '深圳', to: '广州', value: 950 },
{ from: '武汉', to: '成都', value: 650 },
{ from: '成都', to: '武汉', value: 620 }
];
// 处理飞线数据时,动态获取起点和终点坐标即可
const lineData = migrateData.map(item => {
const fromCoord = cityGeo[item.from];
const toCoord = cityGeo[item.to];
return {
name: `${item.from}${item.to}`,
value: item.value,
coords: [fromCoord, toCoord]
};
});

2. 省份内城市迁徙图

结合上一篇的省份地图,实现省内城市的迁徙,只需要修改2个地方:

  1. 加载并注册对应省份的GeoJSON(比如hubei.json);
  2. 替换cityGeo为该省份的城市经纬度,修改迁徙数据即可。

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
// 系列配置中,通过函数动态设置样式
series: [
{
name: '迁徙飞线',
type: 'lines',
coordinateSystem: 'geo',
zlevel: 2,
effect: {
show: true,
symbol: 'arrow',
// 按数值调整特效大小
symbolSize: (params) => params.data.value / 200 + 4,
period: 4,
trailLength: 0.3
},
lineStyle: {
// 按数值调整颜色和宽度
color: (params) => {
const value = params.data.value;
return value > 1000 ? '#ff4d4f' : '#3b8cff';
},
width: (params) => params.data.value / 500 + 1,
curveness: 0.2,
opacity: 0.7
},
data: lineData
}
]

4. 交互联动:点击飞线展示详情

绑定click事件,实现点击飞线/城市,展示对应的迁徙详情:

1
2
3
4
5
6
7
8
9
10
11
// 绑定点击事件
myChart.on('click', function (params) {
console.log('点击数据:', params);
if (params.seriesType === 'lines') {
alert(`迁徙线路:${params.name}\n迁徙总量:${params.value}`);
// 这里可以自定义弹窗,展示详细数据
}
if (params.seriesType === 'effectScatter') {
alert(`城市:${params.name}\n总流量:${params.value[2]}`);
}
});

六、常见问题排查:90%的坑都在这里

1. 飞线完全不显示

  • 检查是否调用了echarts.registerMap()注册地图,geo的map参数是否和注册名称一致;
  • 检查lines系列的coordinateSystem是否设为geo
  • 检查zlevel是否大于geo的层级,避免被地图底图覆盖;
  • 检查coords的经纬度格式是否正确,必须是[经度, 纬度]的数组,不要写反。

2. 特效动画不生效

  • 检查effect.show是否设为true
  • 检查period数值是否过大(超过10秒几乎看不到动画),建议设为2-6;
  • 检查trailLength是否为0,导致没有拖尾效果。

3. 坐标严重偏移

  • 核心原因:地图数据和经纬度的坐标系不匹配,国内必须统一用GCJ02火星坐标系;
  • 不要直接用GPS的WGS84经纬度,需要先转换为GCJ02坐标系再使用。

4. 飞线太多,页面卡顿

  • 优化方案1:减少飞线数量,只展示TOP20的核心线路,不要一次性渲染上百条飞线;
  • 优化方案2:关闭不必要的特效,降低symbolSize和涟漪动画的缩放倍数;
  • 优化方案3:简化GeoJSON地图数据(用mapshaper工具压缩:https://mapshaper.org/)。

5. 旧版ECharts 2.x代码迁移5.x注意事项

  1. 地图配置:旧版mapType改为新版map
  2. 地图注册:5.x必须手动调用echarts.registerMap()注册地图数据,不再内置;
  3. 事件绑定:旧版ecConfig.EVENT.MAP_SELECTED改为直接用事件名click/mouseover
  4. 特效配置:5.x的effect配置更简洁,旧版的特效参数可以直接复用,无需大改。

七、写在最后

迁徙图的核心,从来不是炫酷的飞线特效,而是用可视化的方式,清晰地传递数据背后的业务逻辑。不要为了炫酷而堆砌过多的飞线和动画,重点突出核心的迁徙流向和数据差异,才是可视化的本质。

上一篇的省市区三级地图切换,加上这一篇的迁徙图,已经能覆盖绝大多数地图可视化大屏的需求。


echarts迁徙图
https://cszy.top/2016-05-10 echarts迁徙图/
作者
csorz
发布于
2016年5月10日
许可协议