嘿,朋友!今天咱们不聊虚的,直接上手干一件听起来高大上、但实际上只要理清逻辑就能搞定的事儿:用 ECharts 搞一个能三级下钻、还能展示热力图的自定义 SVG 地图。
我知道,看到“SVG”、“GeoJSON”、“下钻”这些词,很多人第一反应是:“太难了,算了。” 别慌。其实这就像搭积木,你只需要知道第一块怎么放,第二块怎么叠,最后一块往哪儿插。我带过不少初学者,他们一开始也怕,但只要跟着我的思路走,半小时就能跑通一个完整的 demo。咱们今天的任务不是背文档,而是把这一堆技术点揉碎了,讲得连你五姨家的孩子都能听懂。
先把“地图”这事儿搞明白:为什么是 SVG/GeoJSON?
首先,你得有个概念。ECharts 里的地图,本质上是一张“画”。普通的世界地图,你看到的是边界线;但在 ECharts 里,这张地图是由无数个坐标点组成的多边形拼起来的。
ECharts 并不自带中国所有的省市县高精度地图数据。为什么?因为数据量太大了,而且行政区划经常调整。所以,官方推荐的方式是让你自己引入地图数据。这些数据通常长什么样?两种主流格式:
- GeoJSON:这是目前的“普通话”。它描述的是一个地理要素,比如“广东省”这个区域,里面包含了它的边界坐标、名称、中心点等信息。结构简单,读起来像 JSON,程序员最爱。
- SVG:这是矢量图。它更像是一组画图的指令,告诉计算机“画一条线从A到B,再画到C……”。
在实际开发中,我们通常会用 GeoJSON 来定义地图的几何形状,然后用 ECharts 的 registerMap 方法把它注册进去。而热力图(HeatMap)呢,其实是叠加在这张地图上的“颜色层”。
想象一下,你有一张透明的塑料板(地图轮廓),上面画好了各个省市的框。然后你手里有另一张透明的纸,上面涂了红红绿绿的颜色(热力数据),你把这张涂了颜色的纸盖在塑料板上,透过颜色深浅,你就能看出哪个地方“热”(数据高),哪个地方“冷”(数据低)。这就是热力图地图的基本原理。
准备工作:工欲善其事,必先利其器
在写代码之前,咱们得把“材料”备齐。别一上来就复制粘贴代码,那样出错了你都不知道是哪里的事。
1. 引入 ECharts
你可以直接去 ECharts 官网 下载 build 版本,或者用 npm 安装。对于咱们今天的演示,最简单的方式是在 HTML 里直接引入 CDN:
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
2. 准备地图数据(这是关键!)
这是新手最容易卡住的地方。你需要下载中国省市县三级地图的 GeoJSON 数据。
- 去哪里下? 有很多开源项目维护着这些数据,比如著名的
apache/echarts仓库下的map/json目录,或者geojson-maps这类网站。 - 数据长啥样? 你会看到一大堆嵌套的 JSON 数据。重点看
features数组,里面每一个feature就是一个省、市或县。每个 feature 里都有properties(属性,比如名字)和geometry(几何形状,也就是坐标数组)。
为了演示三级下钻,我们通常需要三套数据:
china.json:全国地图,包含各省。guangdong.json:广东省地图,包含各市。guangzhou.json:广州市地图,包含各县/区。
小贴士:如果找不到现成的县一级数据,可以去 阿里云 DataV.GeoAtlas 这种地方手动下载,支持一键导出 GeoJSON,非常良心。
3. 准备热力数据
热力图数据不需要复杂的坐标,只需要地名 + 数值。比如:
const heatData = [
{ name: '广州市', value: 500 },
{ name: '深圳市', value: 800 },
{ name: '东莞市', value: 300 }
];
当用户点开广州,我们就需要换成广州各区的数据,比如 { name: '天河区', value: 200 }。
核心逻辑拆解:三级下钻是怎么“转”起来的?
很多人觉得下钻很神秘,其实它就两步:
- 点击事件:用户点了一个省,我们监听到这个动作。
- 切换数据:把当前的地图实例,换成下一级地图的数据,重新渲染。
但这里有个坑:坐标系统。省的地图和县地图,它们的坐标范围是不一样的。如果你直接把广州的坐标画在广东省的图里,它会缩成一个点,甚至跑出屏幕外。所以,我们需要用到 ECharts 的 geo 组件的 roam(缩放漫游)和 scale(缩放比例)来动态调整视图。
更高级一点的做法,是维护一个地图栈。比如你现在在全国,栈里就是 [全国]。点击“广东”,栈变成 [全国, 广东],加载广东地图。点击“广州”,栈变成 [全国, 广东, 广州],加载广州地图。点击返回,就弹出栈顶,恢复上一级。
为了让你更容易理解,我们先用一个简化版的逻辑:每次点击,直接替换当前地图的 geoJSON 数据和选项配置。虽然不够优雅,但思路最清晰。
代码实战:从零搭建一个 Demo
好了,理论讲够了,咱们直接上代码。我会把整个 HTML 文件写出来,你直接复制保存为 .html 文件,双击就能在浏览器跑起来。
第一步:HTML 骨架和容器
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>ECharts 三级地图下钻与热力图</title>
<!-- 引入 ECharts -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
body { margin: 0; padding: 20px; background: #f0f2f5; font-family: sans-serif; }
#map-container { width: 100%; height: 600px; background: #fff; box-shadow: 0 2px 8px rgba(0,0,0,0.1); border-radius: 8px; }
.info-bar { margin-bottom: 10px; padding: 10px; background: #fff; border-radius: 8px; display: flex; justify-content: space-between; align-items: center; }
.breadcrumb { font-size: 18px; font-weight: bold; color: #333; }
.back-btn { padding: 8px 16px; background: #1890ff; color: #fff; border: none; border-radius: 4px; cursor: pointer; display: none; }
.back-btn:hover { background: #40a9ff; }
</style>
</head>
<body>
<div class="info-bar">
<span class="breadcrumb" id="breadcrumb">中国地图</span>
<button class="back-btn" id="backBtn" onclick="goBack()">返回上级</button>
</div>
<div id="map-container"></div>
<script>
// 我们的 JS 代码将写在这里
</script>
</body>
</html>
第二步:获取地图数据
这里有个问题:我们不能在纯本地 HTML 文件里直接用 fetch('china.json') 加载本地文件,因为浏览器的安全策略(CORS)会拦截。
解决方案有两个:
- 用本地服务器(如 VS Code 的 Live Server 插件)。
- 为了演示方便,我把数据模拟在代码里(虽然数据量太大放不下,但我会给你展示结构,并告诉你如何替换成真实 fetch 请求)。
在实际项目中,你会这样做:
// 假设你已经在同目录下放了 china.json, guangdong.json, guangzhou.json
async function fetchMapData(name) {
const response = await fetch(`${name}.json`);
const data = await response.json();
return data;
}
但为了让你现在就能看到效果,我会在代码注释里留出这个位置,并在后续步骤中解释如何填充。
第三步:初始化 ECharts 实例与核心配置
这是最核心的部分。我们要定义一个配置对象,包含 geo 和 series。
let myChart = echarts.init(document.getElementById('map-container'));
let currentLevel = 0; // 0: 全国, 1: 省, 2: 市, 3: 县
let currentMapName = 'china'; // 当前地图名称
let mapDataStack = ['china']; // 地图栈,用于回溯
// 模拟的热力数据(实际项目中应从后端获取)
const heatMapData = {
china: [
{ name: '广东省', value: 1000 },
{ name: '浙江省', value: 800 },
{ name: '江苏省', value: 750 },
{ name: '北京市', value: 600 },
{ name: '上海市', value: 550 }
],
guangdong: [
{ name: '广州市', value: 500 },
{ name: '深圳市', value: 800 },
{ name: '东莞市', value: 300 },
{ name: '佛山市', value: 400 },
{ name: '惠州市', value: 200 }
],
guangzhou: [
{ name: '天河区', value: 200 },
{ name: '海珠区', value: 150 },
{ name: '荔湾区', value: 100 },
{ name: '越秀区', value: 180 },
{ name: '白云区', value: 120 }
]
};
第四步:注册地图与渲染逻辑
这里有个技巧:ECharts 的 registerMap 是全局的。如果你换了地图名字,必须重新注册。而且,为了让热力图好看,我们需要设置一个颜色映射(Color Map)。
// 定义颜色映射函数:从冷色(蓝)到热色(红)
function getColor(value) {
if (value > 800) return '#8B0000'; // 深红
if (value > 500) return '#FF4500'; // 橙红
if (value > 300) return '#FFA500'; // 橙
if (value > 100) return '#FFD700'; // 黄
return '#87CEEB'; // 浅蓝
}
async function renderMap(level, mapName) {
// 1. 清空之前的地图注册(防止重名冲突,虽然不同名字不会冲突,但好习惯)
// ECharts 5.x 支持动态注册,不需要手动 unregister,直接 register 新数据即可
// 2. 获取地图数据 (这里假设你已经把 JSON 文件放在了同级目录)
// 注意:真实项目中请用 fetch,这里为了演示结构,我会用伪代码表示
let geoJSON = await fetchMapData(mapName);
// 3. 注册地图
echarts.registerMap(mapName, geoJSON);
// 4. 获取当前层级的热力数据
const data = heatMapData[mapName] || [];
// 5. 构建 ECharts 配置
const option = {
tooltip: {
trigger: 'item',
formatter: function(params) {
// 如果是地图区域,显示名称和数值
if (params.componentType === 'series' && params.seriesType === 'map') {
return `${params.name}<br/>数值: ${params.value || '暂无数据'}`;
}
return params.name;
}
},
geo: {
map: mapName,
roam: true, // 允许缩放和平移
zoom: 1.2,
label: {
show: true, // 显示地名
color: '#333'
},
itemStyle: {
areaColor: '#f0f0f0',
borderColor: '#999'
},
// 高亮样式
emphasis: {
itemStyle: {
areaColor: '#e6f7ff',
shadowBlur: 10,
shadowColor: 'rgba(0,0,0,0.5)'
},
label: {
color: '#1890ff'
}
}
},
series: [
{
name: '热力地图',
type: 'map',
geoIndex: 0, // 关联到上面的 geo 组件
data: data,
// 自定义颜色映射
visualMap: {
min: 0,
max: 1000,
left: 'left',
top: 'bottom',
text: ['高', '低'],
calculable: true,
inRange: {
color: ['#87CEEB', '#FFD700', '#FFA500', '#FF4500', '#8B0000']
},
textStyle: { color: '#333' }
}
}
]
};
// 6. 设置配置并渲染
myChart.setOption(option, true); // true 表示不合并,完全替换
}
第五步:处理点击事件与下钻逻辑
现在,地图能显示了,但点它还不会动。我们需要监听点击事件。
myChart.on('click', function(params) {
const name = params.name;
// 简单的层级判断逻辑
// 如果当前是全国,且点击的是广东,就下钻到广东
// 如果当前是广东,且点击的是广州,就下钻到广州
// 如果当前是广州,且点击的是某个区,就下钻到该区(如果有的话,这里演示到市为止)
let nextMapName = null;
if (currentLevel === 0) {
// 全国 -> 省
if (name === '广东省') nextMapName = 'guangdong';
} else if (currentLevel === 1) {
// 省 -> 市
if (name === '广州市') nextMapName = 'guangzhou';
}
// 可以继续扩展县一级...
if (nextMapName) {
currentLevel++;
currentMapName = nextMapName;
mapDataStack.push(nextMapName);
// 更新 UI
document.getElementById('breadcrumb').innerText =
`${mapDataStack.map(n => n === 'china' ? '中国' : n === 'guangdong' ? '广东省' : n === 'guangzhou' ? '广州市' : n).join(' > ')}`;
document.getElementById('backBtn').style.display = 'block';
// 重新渲染
renderMap(currentLevel, nextMapName);
}
});
// 返回按钮逻辑
function goBack() {
if (mapDataStack.length > 1) {
mapDataStack.pop();
currentMapName = mapDataStack[mapDataStack.length - 1];
currentLevel--;
document.getElementById('breadcrumb').innerText =
`${mapDataStack.map(n => n === 'china' ? '中国' : n === 'guangdong' ? '广东省' : n === 'guangzhou' ? '广州市' : n).join(' > ')}`;
if (mapDataStack.length === 1) {
document.getElementById('backBtn').style.display = 'none';
}
renderMap(currentLevel, currentMapName);
}
}
// 初始化加载全国地图
renderMap(0, 'china');
避坑指南:这些细节决定成败
上面代码跑起来后,你可能会发现几个奇怪的问题。别急,这些都是我踩过的坑。
1. 地图坐标对不上,或者形状扭曲
这通常是因为你下载的 GeoJSON 数据格式不标准,或者坐标系不一致。ECharts 默认支持 WGS84 和 GCJ02 坐标系。中国地图通常用 GCJ02(火星坐标系)。如果你发现地图偏移了,可以在 geo 配置里加上:
geo: {
// 如果数据是 WGS84,可能需要转换,或者确认你的 GeoJSON 是否已经是 GCJ02
// 大多数下载的国内 GeoJSON 已经是 GCJ02 了
}
2. 热力图颜色不生效,全是灰色
检查你的 visualMap 配置。min 和 max 必须根据你的数据动态计算,或者设一个合理的范围。如果数据的最大值是 1000,但你设 max: 100,那所有点都会显示为最深的颜色。反之亦然。另外,确保你的 series.data 里的 value 字段名是 value,而不是 num 或 count,除非你在 visualMap 里指定了 dimension。
3. 点击没反应
检查 formatter 函数。有时候,如果你的 tooltip 配置覆盖了默认的点击行为,
