大家好,我是Agnes。今天想和大家聊聊一个在数据可视化领域既基础又充满乐趣的话题——ECharts 自定义地图。
说实话,刚开始接触 ECharts 地图的时候,我也曾对着官方文档里那些密密麻麻的 GeoJSON 数据发呆。为什么我的地图不显示?为什么省份形状扭曲了?为什么鼠标悬停没有反应?别担心,这些问题我都踩过,今天就把我从“踩坑”到“熟练使用”的全过程,毫无保留地拆解给你看。
我们不仅仅会讲“怎么做”,更会深入讲解“为什么”,让你真正理解这套逻辑,以后无论拿到哪个城市的 GeoJSON,都能手到擒来。
一、 为什么我们需要自定义地图?
在开始写代码之前,先问自己一个问题:为什么不用 ECharts 自带的中国地图?
ECharts 确实内置了一些常用的地图(如中国、世界),但这些地图的颗粒度非常粗糙。比如:
- 你想展示 杭州市 下辖各个 区/县 的数据?默认中国地图里没有这种细分。
- 你想展示 某个大型园区、某个商场楼层、甚至 某个国家的小岛?内置地图根本不存在。
- 你觉得默认的蓝色绿色太难看,想要符合品牌色的 暗黑风 或 清新风?
这就是自定义地图存在的意义:数据驱动地理,地理承载业务。
核心原理:GeoJSON 是什么?
你可以把 GeoJSON 想象成一张“数字化的纸”。这张纸上画了各个区域的轮廓,并标注了每个点的经纬度坐标。
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"name": "西湖区",
"cp": [120.1, 30.2], // 中心点坐标
"childNum": 10
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[120.12, 30.23],
[120.15, 30.21],
[120.18, 30.25],
[120.12, 30.23]
]
]
}
}
]
}
ECharts 的工作流程非常简单:
- 注册地图:告诉 ECharts “这个 GeoJSON 叫 ‘杭州’,轮廓是这样的”。
- 绑定数据:告诉 ECharts “西湖区有 100 人,上城区有 200 人…”。
- 渲染:ECharts 根据坐标画图,并根据数据上色。
二、 实战第一步:获取并加载 GeoJSON 数据
这是最关键,也是最容易卡住的一步。数据来源通常有三个渠道:
- 阿里云 DataV.GeoAtlas(推荐):免费、稳定、支持省市区县多级联动。
- Natural Earth:适合世界地图,精度稍低但风格统一。
- 自行绘制:如果你有 CAD 图纸,可以转换成 GeoJSON(需要专业工具)。
获取某地 GeoJSON 的流程
以 浙江省 为例:
- 打开 阿里云 DataV.GeoAtlas。
- 在地图上点击“浙江省”。
- 下载 JSON 文件。
下载完后,你会得到一个 zhejiang.json。
如何加载到 ECharts?
有两种方式:静态加载 和 动态加载。
方式 A:静态导入(适合小项目,数据量小)
直接引入 JSON 文件作为变量。
// 假设你已经把 zhejiang.json 内容复制到了 geoData.js 中
import geoJson from './zhejiang.json';
echarts.registerMap('zhejiang', geoJson);
const chart = echarts.init(document.getElementById('main'));
chart.setOption({
series: [{
type: 'map',
map: 'zhejiang', // 这里对应 registerMap 的第一个参数
data: [...]
}]
});
方式 B:动态 Fetch 加载(适合生产环境,推荐)
这是最常用的方式,因为 GeoJSON 文件通常很大,动态加载更灵活。
// 使用 fetch 异步加载 GeoJSON
fetch('https://your-server.com/zhejiang.json')
.then(response => response.json())
.then(geoJson => {
// 1. 注册地图
echarts.registerMap('zhejiang', geoJson);
// 2. 初始化并配置图表
const chart = echarts.init(document.getElementById('main'));
chart.setOption({
tooltip: {
trigger: 'item',
formatter: '{b}<br/>{c} 人'
},
visualMap: {
min: 0,
max: 1000,
left: 'left',
top: 'bottom',
text: ['高', '低'],
calculable: true
},
series: [{
name: '人口分布',
type: 'map',
map: 'zhejiang', // 引用注册的地图名
roam: true, // 允许缩放和平移
label: {
show: true,
fontSize: 10
},
data: [
{ name: '杭州市', value: 850 },
{ name: '宁波市', value: 600 },
{ name: '温州市', value: 450 }
// ... 更多数据
]
}]
});
})
.catch(error => console.error('地图加载失败:', error));
💡 专家提示:
map: 'zhejiang'必须和echarts.registerMap('zhejiang', ...)中的名字完全一致,否则地图会是一片空白。
三、 样式自定义:让地图“活”起来
默认的地图通常只有灰白色线条,太单调了。我们可以通过 itemStyle 和 emphasis 来深度定制。
1. 基础样式配置
series: [{
type: 'map',
map: 'zhejiang',
// 地图默认样式
itemStyle: {
areaColor: '#eee', // 区域填充色
borderColor: '#fff', // 边框颜色
borderWidth: 1 // 边框宽度
},
// 鼠标悬停时的样式(高亮)
emphasis: {
itemStyle: {
areaColor: '#ffd700', // 悬停时变成金色
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.5)'
},
label: {
fontSize: 14,
fontWeight: 'bold',
color: '#333'
}
},
// 选中时的样式
select: {
itemStyle: {
areaColor: '#ff6b6b'
}
}
}]
2. 隐藏某些区域或显示所有标签
有时候,县一级区域太多,标签会重叠。你可以这样控制:
label: {
show: true, // 默认显示标签
fontSize: 8, // 小字体
color: '#666'
},
emphasis: {
label: {
show: true,
fontSize: 12, // 悬停时放大
color: '#000'
}
}
如果你只想显示市级标签,隐藏县级标签,这需要借助 GeoJSON 中的 properties 字段进行区分,或者通过 CSS 类名控制(ECharts 5+ 支持更灵活的控制)。
3. 添加散点图和线图(组合地图)
地图不只是展示区域颜色,还可以叠加业务数据。
series: [
// 第一层:区域地图
{
type: 'map',
map: 'zhejiang',
roam: true,
label: { show: false }, // 先隐藏标签,保持简洁
itemStyle: {
areaColor: '#e0f7fa',
borderColor: '#006064'
}
},
// 第二层:散点图(表示具体点位)
{
type: 'effectScatter',
coordinateSystem: 'geo', // 必须指定为 geo,才能和地图对齐
data: convertData([ // 你的数据需要包含 [经度, 纬度, 数值]
{name: '西湖断桥', value: [120.14, 30.25, 100]},
{name: '灵隐寺', value: [120.08, 30.24, 80]}
]),
symbolSize: 10,
rippleEffect: {
brushType: 'stroke',
scale: 3
},
label: {
show: true,
position: 'right',
formatter: '{b}'
}
},
// 第三层:飞线(表示流向)
{
type: 'lines',
coordinateSystem: 'geo',
data: convertLineData([ // 从杭州到北京
{fromName: '杭州市', toName: '北京市', coords: [[120.15, 30.25], [116.4, 39.9]]}
]),
effect: {
show: true,
period: 4,
trailLength: 0.1,
symbol: 'arrow',
symbolSize: 5
},
lineStyle: {
color: '#ff4500',
width: 2,
curveness: 0.2
}
}
]
注意:散点图和线图必须使用
coordinateSystem: 'geo',而不是'cartesian2d',这样才能正确映射到地图的经纬度上。
四、 常见问题及解决方案(避坑指南)
在实战中,90% 的问题都出在这里。
问题 1:地图显示空白,一片白
原因分析:
- GeoJSON 加载失败(路径错误、跨域问题)。
registerMap的名字和series.map不一致。- GeoJSON 数据格式不对(缺少
type: "FeatureCollection")。 - 图表容器高度为 0。
排查步骤:
// 1. 先打印 geoJson 看看是否加载成功
fetch('zhejiang.json')
.then(res => res.json())
.then(data => {
console.log(data); // 确认数据结构正确
echarts.registerMap('zhejiang', data);
// 2. 确认容器有高度
const dom = document.getElementById('main');
console.log(dom.offsetHeight); // 如果为0,检查CSS
// 3. 简单测试,不绑数据,只看地图是否存在
chart.setOption({
series: [{ type: 'map', map: 'zhejiang' }]
});
});
问题 2:地图位置和形状偏移严重
原因分析: 坐标系统不匹配。中国地图通常使用 GCJ-02(火星坐标系)或 BD-09(百度坐标系),而很多 GeoJSON 数据是 WGS-84(GPS原始坐标)。
解决方案:
- 确认数据源:阿里云 DataV 的 GeoJSON 通常是 GCJ-02 或 WGS-84,需查阅其说明。
- 使用 ECharts 提供的转换工具:如果你的数据是 WGS-84,而地图是 GCJ-02,需要做坐标转换。
- 简单测试:在地图上打一个已知坐标的散点,看是否重合。如果不重合,说明坐标系有问题。
// 如果你使用的是百度坐标,需要引入百度坐标转换
// 或者在 ECharts 中设置 projection
series: [{
type: 'map',
map: 'china',
// 如果坐标偏移,可以尝试调整 roam 或 projection
roam: true
}]
问题 3:鼠标悬停没有高亮,或 tooltip 显示乱码
原因分析:
formatter函数写错了。- GeoJSON 中的
name字段和数据中的name字段不一致。
解决方案:
GeoJSON 中的 properties.name 必须与数据中的 name 完全匹配(包括空格、大小写)。
tooltip: {
trigger: 'item',
formatter: function(params) {
// params.name 来自 GeoJSON 的 properties.name
// params.value 来自 series.data 的 value
if (params.value) {
return `${params.name}: ${params.value}`;
}
return params.name;
}
}
问题 4:数据太多,渲染卡顿
原因分析: GeoJSON 精度太高,或者数据量太大。
解决方案:
- 简化 GeoJSON:使用 mapshaper.org 工具降低 GeoJSON 的顶点精度(例如降低 90% 的顶点数),这对可视化影响不大,但性能提升巨大。
- 开启 WebGL:ECharts 5+ 支持 WebGL 渲染,对于超大数据量更友好。
chart.setOption({ renderer: 'webgl', // 尝试使用 WebGL 渲染 series: [...] });
五、 进阶案例:打造交互式热力地图
让我们结合一个具体的业务场景:展示浙江省各城市的旅游热度。
完整代码示例
”`html <!DOCTYPE html>
