echarts自定义地图绘制教程手把手教你从零开始创建专属地图支持geojson数据导入和省份高亮显示
嘿,朋友!今天咱们来聊聊ECharts自定义地图这件事。说实话,我第一次看到那些炫酷的中国地图、世界地图时,内心也是震撼的——”这玩意儿咋做的?”后来研究发现,其实没那么玄乎,今天我就掰开了揉碎了讲给你听。
先说说为啥要自己搞自定义地图。官方提供的地图虽然够用,但总有那么些时候,你需要展示某个特定区域,或者你的数据涉及到一些特殊的地理边界,这时候官方的地图就有点”力不从心”了。别担心,有了GeoJSON这个神器,你基本可以画任意形状的地图。
准备工作:你得有啥
写代码之前,先把手头东西准备好,省得到时候手忙脚乱:
必备的三样东西:
- ECharts库 —— 去官网下载一个就行,或者直接用CDN引入
- GeoJSON数据 —— 这是地图的”骨架”,每个省份、每个区域的具体形状都在这里
- 一个代码编辑器 —— VS Code、WebStorm随便选,不挑
GeoJSON从哪来?
这个最实际。几个靠谱的地方:
- 官方数据源:Natural Earth(自然地球),有不同精度的全球数据
- 中国政府数据:自然资源部有标准的中国省界数据
- 在线工具:geojson.io 可以在线编辑和查看
- 国内常用:阿里云DataV有现成的中国各省市GeoJSON
拿个具体的例子说事儿。假设你想做个中国地图,每个省份能用不同颜色区分,鼠标放上去还能高亮显示。这个需求,ECharts完全能满足。
第一步:最简单的Hello World地图
别急着搞复杂的,先来个最简单的跑起来。新建一个HTML文件,比如叫map.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>
#map-container {
width: 1000px;
height: 700px;
border: 1px solid #ddd;
}
</style>
</head>
<body>
<div id="map-container"></div>
<script>
// 初始化图表
var chart = echarts.init(document.getElementById('map-container'));
// 配置项
var option = {
title: {
text: '中国地图演示',
left: 'center',
textStyle: {
fontSize: 24,
color: '#333'
}
},
// 提示框组件,鼠标放上去显示信息
tooltip: {
trigger: 'item',
formatter: '{b}' // 显示省份名称
},
// 视觉映射组件,根据数值着色
visualMap: {
min: 0,
max: 1000,
left: 'left',
top: 'bottom',
text: ['高', '低'],
calculable: true,
inRange: {
color: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']
}
},
// 地图系列
series: [{
name: '各省数据',
type: 'map',
map: 'china', // 使用内置的中国地图
roam: true, // 允许缩放和平移
zoom: 1.2,
label: {
show: true, // 显示省份名称
fontSize: 10
},
// 高亮样式
emphasis: {
label: {
fontSize: 14,
color: '#d43f3d'
},
itemStyle: {
areaColor: '#ffd700',
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
// 默认样式
itemStyle: {
areaColor: '#f3f3f3',
borderColor: '#999'
},
// 数据,随便模拟一点
data: [
{ name: '北京', value: 800 },
{ name: '上海', value: 950 },
{ name: '广东', value: 700 },
{ name: '江苏', value: 650 },
{ name: '浙江', value: 600 },
{ name: '四川', value: 400 },
{ name: '湖北', value: 350 }
]
}]
};
// 使用配置项显示图表
chart.setOption(option);
// 响应窗口大小变化
window.addEventListener('resize', function() {
chart.resize();
});
</script>
</body>
</html>
保存之后用浏览器打开,就能看到一张中国地图了。虽然数据是瞎填的,但结构已经搭起来了。
这段代码里几个关键点解释一下:
series.map = 'china' 这行是告诉ECharts用内置的中国地图。ECharts官方封装了几十个国家和地区的地图,内置的够用,但如果你想用自己定制的边界数据,就得走下一步。
roam: true 这个设置让地图可以缩放和拖动,用户体验好很多。默认是false,地图是死的。
emphasis 是鼠标悬停时的高亮样式,这里我用了金色填充加阴影效果,看起来更立体。
第二步:导入自己的GeoJSON数据
这才是自定义地图的核心。内置地图虽然方便,但限制太多了。比如你只想展示某个省、某个市,或者数据涉及到一些特殊的行政区划,内置地图就搞不定了。
先理解GeoJSON是个啥
GeoJSON本质上就是一个JSON文件,只不过它描述的是地理信息。一个典型的GeoJSON长这样:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"name": "北京市",
"adcode": "110000"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[116.4, 39.9],
[116.5, 39.9],
[116.5, 40.0],
[116.4, 40.0],
[116.4, 39.9]
]
]
}
}
]
}
看起来复杂,其实就三个东西:
type:标识数据类型properties:属性信息,比如名称、编码geometry:几何信息,就是这个区域是怎么画的,由一系列的坐标点连起来
实操:加载一个省份的GeoJSON
假设你已经下载了一个中国各省份的GeoJSON文件,叫china.json。接下来要做的就是在ECharts里注册这个地图,然后使用它。
// 引入ECharts
import * as echarts from 'echarts';
// 假设你通过ajax或者fetch获取到了GeoJSON数据
// 这里用模拟数据演示,实际项目中用fetch获取
fetch('https://geo.datav.aliyun.com/area_v2/320000_full.json')
.then(response => response.json())
.then(geoJson => {
// 注册地图,名字随便起,但后面series.map要用这个名字
echarts.registerMap('江苏', geoJson);
var chart = echarts.init(document.getElementById('map-container'));
var option = {
title: {
text: '江苏省地图(自定义GeoJSON)',
left: 'center',
textStyle: { fontSize: 20 }
},
tooltip: {
trigger: 'item',
formatter: function(params) {
// 自定义提示内容
return params.name + '<br/>点击查看详情';
}
},
visualMap: {
min: 0,
max: 10000,
left: 'left',
top: 'bottom',
text: ['高', '低'],
calculable: true,
inRange: {
color: ['#e8f5e9', '#81c784', '#388e3c']
},
seriesIndex: 0
},
series: [
{
name: '江苏省各地市数据',
type: 'map',
map: '江苏', // 这里要用注册时用的名字
roam: true,
zoom: 1.1,
label: {
show: true,
fontSize: 9,
color: '#333'
},
emphasis: {
label: {
show: true,
fontSize: 12,
color: '#fff',
fontWeight: 'bold'
},
itemStyle: {
areaColor: '#ff9800',
shadowBlur: 15,
shadowColor: 'rgba(0,0,0,0.4)'
}
},
select: {
itemStyle: {
areaColor: '#f44336'
},
label: {
color: '#fff'
}
},
itemStyle: {
areaColor: '#e3f2fd',
borderColor: '#1565c0',
borderWidth: 1
},
data: [
{ name: '南京市', value: 9500 },
{ name: '苏州市', value: 9200 },
{ name: '无锡市', value: 7800 },
{ name: '常州市', value: 6500 },
{ name: '徐州市', value: 5200 },
{ name: '南通市', value: 6800 },
{ name: '连云港市', value: 4200 },
{ name: '淮安市', value: 4800 },
{ name: '盐城市', value: 5500 },
{ name: '扬州市', value: 5000 },
{ name: '镇江市', value: 4500 },
{ name: '泰州市', value: 4200 },
{ name: '宿迁市', value: 3800 }
]
}
]
};
chart.setOption(option);
})
.catch(error => {
console.error('加载GeoJSON失败:', error);
});
几个坑你得知道:
名字要对得上。
registerMap注册时用的名字,和series.map里填的名字必须一模一样,大小写都敏感。我第一次搞的时候在这里卡了半个小时,以为是代码写错了,其实是名字多了一个空格。GeoJSON的坐标系。ECharts用的是WGS84坐标系(也就是经纬度),如果你从别的坐标系转换过来的,记得要转换。阿里云DataV提供的GeoJSON是标准的,直接用没问题。
属性名称。有些GeoJSON里的省份名称和ECharts内置地图里的不一致。比如内置地图里叫”江苏省”,但你的GeoJSON里可能叫”江苏”。这时候需要用
nameProperty配置项来指定用哪个属性作为名称。
series: [{
type: 'map',
map: '江苏',
nameProperty: 'name', // 指定用GeoJSON里的name属性
// ...
}]
第三步:实现省份高亮交互
高亮这个功能,大家第一眼看到都觉得很酷。但其实ECharts已经内置了大部分功能,你只需要会配置就行。
基础高亮
前面代码里已经用了emphasis来实现高亮效果。emphasis就是ECharts里”强调”的意思,鼠标悬停时触发。
让我详细解释一下高亮的各个配置项:
emphasis: {
// 标签的高亮样式
label: {
show: true, // 显示标签
fontSize: 14, // 字体大小
color: '#d43f3d', // 字体颜色
fontWeight: 'bold' // 加粗
},
// 地图区域的高亮样式
itemStyle: {
areaColor: '#ffd700', // 填充颜色
borderColor: '#b8860b', // 边框颜色
borderWidth: 2, // 边框宽度
shadowBlur: 10, // 阴影模糊半径
shadowColor: 'rgba(0, 0, 0, 0.5)' // 阴影颜色
}
}
点击高亮(联动选中)
有时候你需要的不只是鼠标悬停高亮,而是点击之后保持高亮状态。这个ECharts也支持:
// 监听点击事件
chart.on('click', function(params) {
console.log('点击了:', params.name);
// 可以通过dispatchAction来设置选中状态
chart.dispatchAction({
type: 'downfilter',
seriesIndex: 0,
name: params.name
});
});
// 取消选中
chart.on('brushEnd', function(params) {
chart.dispatchAction({
type: 'takeGlobalFilter',
action: {
type: 'filter',
seriesIndex: 0,
filterType: 'dimension',
dimension: params.dimensionIndex,
value: params.value
}
});
});
不过说实话,点击高亮在地图场景下不如悬停高亮常用。大多数时候,用户更想要的是:鼠标放上去,这个省就亮起来,告诉你这里有点什么。
高亮时的数据展示
光有颜色变化还不够,最好能弹出一个提示框显示详细信息。用tooltip.formatter来自定义:
tooltip: {
trigger: 'item',
formatter: function(params) {
if (params.componentType === 'series') {
// 地图系列的数据
var dataIndex = params.dataIndex;
var currentData = option.series[0].data[dataIndex];
// 自定义HTML内容
return `
<div style="padding: 10px; font-size: 14px;">
<strong style="font-size: 16px; color: #1a73e8;">${params.name}</strong>
<br/>
<span style="color: #666;">数值:</span>
<span style="color: #d93025; font-weight: bold;">${currentData.value.toLocaleString()}</span>
<br/>
<span style="color: #666;">状态:</span>
<span style="color: #1e8e3e;">正常</span>
</div>
`;
}
return params.name;
}
}
这样鼠标放上去,就能看到更丰富的信息了,而不是干巴巴的一个名字。
第四步:批量导入多个省份的GeoJSON
如果你需要展示全国地图,但又想用自定义的GeoJSON(而不是ECharts内置的),那就得批量注册了。
一个JSON文件包含所有省份
很多数据源会把所有省份合并到一个GeoJSON文件里,每个省份是一个独立的Feature。这种情况下,你需要遍历所有的Feature,逐个注册:
fetch('china.json')
.then(response => response.json())
.then(geoJson => {
// 遍历所有的省份
geoJson.features.forEach(function(feature) {
var provinceName = feature.properties.name;
var adcode = feature.properties.adcode;
// 注册每个省份的地图
echarts.registerMap(provinceName, feature);
// 同时准备数据
var dataItem = {
name: provinceName,
value: Math.floor(Math.random() * 10000), // 模拟数据
adcode: adcode
};
provinceData.push(dataItem);
});
// 初始化图表并渲染
renderChart(provinceData);
});
一个文件一个省份
如果你的GeoJSON是分成多个文件的,比如beijing.json、shanghai.json这样的,那就需要逐个加载:
const provinces = ['北京', '上海', '广东', '江苏', '浙江'];
const loadingPromises = provinces.map(province => {
return fetch(`${province}.json`)
.then(response => response.json())
.then(geoJson => {
echarts.registerMap(province, geoJson);
});
});
Promise.all(loadingPromises)
.then(() => {
// 所有地图都加载完成后再渲染
renderChart();
})
.catch(error => {
console.error('加载地图失败:', error);
});
用async/await写得更简洁
如果你不喜欢Promise链,用async/await也能写:
async function loadAllMaps() {
const provinceNames = ['北京', '天津', '河北', '山西', '内蒙古'];
for (const name of provinceNames) {
try {
const response = await fetch(`${name}.json`);
const geoJson = await response.json();
echarts.registerMap(name, geoJson);
console.log(`已加载: ${name}`);
} catch (error) {
console.error(`加载 ${name} 失败:`, error);
}
}
console.log('全部加载完成,开始渲染地图');
renderChart();
}
loadAllMaps();
第五步:实战案例——完整的数据可视化地图
光说不练假把式。下面给你一个完整的、可直接运行的案例,包含数据展示、省份高亮、点击跳转等功能。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ECharts自定义地图实战</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
h1 {
text-align: center;
color: #fff;
margin-bottom: 20px;
font-size: 28px;
text-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.map-wrapper {
background: #fff;
border-radius: 16px;
padding: 20px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
}
#map-container {
width: 100%;
height: 600px;
}
.info-panel {
margin-top: 20px;
padding: 15px 20px;
background: #f8f9fa;
border-radius: 8px;
display: flex;
justify-content: space-around;
flex-wrap: wrap;
}
.info-item {
text-align: center;
padding: 10px 20px;
}
.info-item .label {
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
.info-item .value {
font-size: 24px;
font-weight: bold;
color: #333;
}
.highlight {
color: #1a73e8;
}
.stats {
margin-top: 15px;
padding: 10px;
background: #e8f0fe;
border-radius: 6px;
text-align: center;
color: #1a73e8;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<h1>🗺️ 中国各省GDP可视化地图</h1>
<div class="map-wrapper">
<div id="map-container"></div>
<div class="info-panel">
<div class="info-item">
<div class="label">已加载省份</div>
<div class="value highlight">34</div>
</div>
<div class="info-item">
<div class="label"> hovered省份</div>
<div class="value" id="hovered-province">无</div>
</div>
<div class="info-item">
<div class="label">点击省份</div>
<div class="value" id="clicked-province">无</div>
</div>
<div class="info-item">
<div class="label">数据更新时间</div>
<div class="value" style="font-size: 16px;">2024</div>
</div>
</div>
<div class="stats" id="stats-info">鼠标悬停或点击省份查看详细信息</div>
</div>
</div>
<script>
// 模拟的GDP数据(单位:亿元)
const gdpData = [
{ name: '广东省', value: 135672.8 },
{ name: '江苏省', value: 128200.0 },
{ name: '山东省', value: 92068.3 },
{ name: '浙江省', value: 82553.0 },
{ name: '河南省', value: 71201.5 },
{ name: '四川省', value: 63850.4 },
{ name: '湖北省', value: 56357.9 },
{ name: '福建省', value: 54355.1 },
{ name: '湖南省', value: 50012.9 },
{ name: '安徽省', value: 47050.4 },
{ name: '上海市', value: 47218.7 },
{ name: '河北省', value: 43944.2 },
{ name: '北京市', value: 43760.7 },
{ name: '陕西省', value: 33786.1 },
{ name: '江西省', value: 31200.0 },
{ name: '辽宁省', value: 30209.6 },
{ name: '重庆市', value: 29129.0 },
{ name: '云南省', value: 28954.3 },
{ name: '山西省', value: 25289.5 },
{ name: '内蒙古自治区', value: 24350.8 },
{ name: '广西壮族自治区', value: 26300.7 },
{ name: '贵州省', value: 20913.2 },
{ name: '天津市', value: 16737.3 },
{ name: '新疆维吾尔自治区', value: 19125.9 },
{ name: '黑龙江省', value: 15907.1 },
{ name: '吉林省', value: 14474.9 },
{ name: '甘肃省', value: 11906.4 },
{ name: '海南省', value: 10220.9 },
{ name: '宁夏回族自治区', value: 5521.0 },
{ name: '青海省', value: 3941.1 },
{ name: '西藏自治区', value: 2396.7 },
{ name: '北京市', value: 43760.7 },
{ name: '台湾省', value: 0 }
];
// 初始化图表
var chartDom = document.getElementById('map-container');
var myChart = echarts.init(chartDom);
// 从阿里云获取中国地图GeoJSON
var chinaGeoJsonUrl = 'https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json';
fetch(chinaGeoJsonUrl)
.then(response => response.json())
.then(geoJson => {
// 注册地图
echarts.registerMap('china', geoJson);
// 计算统计数据
var totalGDP = gdpData.reduce((sum, item) => sum + item.value, 0);
var avgGDP = totalGDP / gdpData.filter(item => item.value > 0).length;
var maxGDP = Math.max(...gdpData.map(item => item.value));
var maxProvince = gdpData.find(item => item.value === maxGDP);
var option = {
// 标题
title: {
text: '中国各省2024年GDP分布图',
subtext: '数据单位:亿元 | 鼠标悬停查看详情',
left: 'center',
top: 10,
textStyle: {
fontSize: 22,
fontWeight: 'bold',
color: '#1a1a2e'
},
subtextStyle: {
fontSize: 13,
color: '#666'
}
},
// 提示框
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(255,255,255,0.95)',
borderColor: '#1a73e8',
borderWidth: 1,
textStyle: {
color: '#333'
},
formatter: function(params) {
if (params.componentType === 'series') {
// 在数据中查找对应的GDP
var dataItem = gdpData.find(item => item.name === params.name);
var gdpValue = dataItem ? dataItem.value : 0;
// 计算排名
var sortedData = [...gdpData].sort((a, b) => b.value - a.value);
var rank = sortedData.findIndex(item => item.name === params.name) + 1;
return `
<div style="padding: 8px;">
<div style="font-size: 16px; font-weight: bold; color: #1a73e8; margin-bottom: 8px;">
${params.name}
</div>
<div style="font-size: 13px; line-height: 1.8;">
<div>
<span style="color: #666;">GDP:</span>
<span style="font-weight: bold; color: #d93025; font-size: 15px;">
${gdpValue > 0 ? gdpValue.toLocaleString() : '暂无数据'}
</span>
<span style="color: #666;">亿元</span>
</div>
<div>
<span style="color: #666;">全国排名:</span>
<span style="font-weight: bold; color: #1e8e3e;">第${rank}位</span>
</div>
<div>
<span style="color: #666;">占全国比重:</span>
<span style="font-weight: bold;">${(gdpValue / totalGDP * 100).toFixed(2)}%</span>
</div>
</div>
</div>
`;
}
return params.name;
}
},
// 视觉映射
visualMap: {
min: 0,
max: 140000,
left: 'left',
top: 'bottom',
text: ['高GDP', '低GDP'],
calculable: true,
inRange: {
color: [
'#f7fbff',
'#deebf7',
'#c6dbef',
'#9ecae1',
'#6baed6',
'#4292c6',
'#2171b5',
'#08519c',
'#08306b'
]
},
textStyle: {
color: '#333'
},
seriesIndex: 0
},
// 地理坐标系
geo: {
map: 'china',
roam: true,
zoom: 1.15,
center: [105, 36],
label: {
show: true,
fontSize: 9,
color: 'rgba(0,0,0,0.5)'
},
itemStyle: {
areaColor: '#f0f4f8',
borderColor: '#8ec1ec',
borderWidth: 1.5
},
emphasis: {
label: {
show: true,
fontSize: 12,
color: '#1a73e8',
fontWeight: 'bold'
},
itemStyle: {
areaColor: '#ffd54f',
borderColor: '#f57c00',
borderWidth: 2,
shadowBlur: 10,
shadowColor: 'rgba(0,0,0,0.3)'
}
},
select: {
itemStyle: {
areaColor: '#ff8a65'
},
label: {
color: '#fff'
}
}
},
// 系列数据
series: [
{
name: 'GDP',
type: 'map',
roam: true,
zoom: 1.15,
center: [105, 36],
map: 'china',
coordinateSystem: 'geo',
label: {
show: true,
fontSize: 9,
color: 'rgba(0,0,0,0.5)'
},
itemStyle: {
areaColor: '#f0f4f8',
borderColor: '#8ec1ec',
borderWidth: 1.5
},
emphasis: {
label: {
show: true,
fontSize: 12,
color: '#1a73e8',
fontWeight: 'bold'
},
itemStyle: {
areaColor: '#ffd54f',
borderColor: '#f57c00',
borderWidth: 2
}
},
select: {
itemStyle: {
areaColor: '#ff8a65'
},
label: {
color: '#fff'
}
},
data: gdpData
}
]
};
myChart.setOption(option);
// 监听鼠标悬停事件
myChart.on('hoverChanged', function(params) {
document.getElementById('hovered-province').textContent =
params ? params.name : '无';
if (params) {
var dataItem = gdpData.find(item => item.name === params.name);
if (dataItem && dataItem.value > 0) {
document.getElementById('stats-info').textContent =
`📍 ${params.name} GDP: ${dataItem.value.toLocaleString()} 亿元`;
}
}
});
// 监听点击事件
myChart.on('click', function(params) {
document.getElementById('clicked-province').textContent = params.name;
var dataItem = gdpData.find(item => item.name === params.name);
if (dataItem) {
document.getElementById('stats-info').textContent =
`📊 ${params.name} 2024年GDP为 ${dataItem.value.toLocaleString()} 亿元,`;
// 计算排名
var sortedData = [...gdpData].sort((a, b) => b.value - a.value);
var rank = sortedData.findIndex(item => item.name === params.name) + 1;
document.getElementById('stats-info').textContent +=
`全国排名第${rank}位,占全国GDP ${(dataItem.value / totalGDP * 100).toFixed(2)}%`;
}
});
// 响应窗口大小变化
window.addEventListener('resize', function() {
myChart.resize();
});
})
.catch(error => {
console.error('加载地图数据失败:', error);
myChart.showLoading();
myChart.hideLoading();
// 加载失败时的备用方案
myChart.setOption({
title: {
text: '地图加载失败',
subtext: '请检查网络连接或稍后重试',
left: 'center',
top: 'center'
}
});
});
</script>
</body>
</html>
这个案例里,有几个值得说的地方:
首先,数据部分我用了gdpData数组,里面是各省的GDP数据。实际项目中,这些数据可能来自后端API,也可能来自Excel表格转换。关键是数据结构要对得上——每个对象要有name和value两个字段。
其次,交互部分用了两个事件:hoverChanged和click。hoverChanged是鼠标悬停时触发,click是点击时触发。我在页面下方加了两个面板来实时显示当前悬停和点击的省份,这样用户能清楚地看到自己的操作反馈。
还有,视觉映射的颜色我从浅蓝到深蓝渐变,这样GDP高的省份颜色就深,低的就浅,一目了然。当然,你也可以换成其他颜色方案,比如绿色系、橙色系,完全看你的喜好。
关于事件监听,多啰嗦两句。
ECharts的事件系统很强大,常见的有:
click:点击dblclick:双击mouseover:鼠标移入mouseout:鼠标移出mousedown:鼠标按下mouseup:鼠标抬起globalout:鼠标离开图表区域
在地图场景下,最常用的是click和mouseover。mouseover和hoverChanged有点像,但hoverChanged更精确,只在悬停的系列项发生变化时触发,而mouseover是每次鼠标移动都可能触发。
第六步:常见问题和解决方案
开发过程中,你肯定会遇到各种奇葩问题。下面列举几个最常见的,以及解决办法。
问题1:地图显示空白,啥都没有
这种情况八成是GeoJSON加载失败了。先打开浏览器的开发者工具(F12),看Console里有没有报错。常见的错误有:
- 404:文件路径写错了
- CORS错误:跨域问题,需要用本地服务器而不是直接打开HTML文件
- JSON格式错误:GeoJSON文件本身有问题
解决办法:用fetch的catch捕获错误,打印出详细的错误信息。同时确保你的GeoJSON文件是有效的,可以用geojson.io在线验证。
问题2:省份名字对不上,数据匹配不上
比如你的数据里写的是”广东省”,但GeoJSON里是”广东”。解决方法有两个:
- 统一名字。要么改数据,要么改GeoJSON的解析逻辑
- 使用
nameProperty指定用哪个属性作为名称
series: [{
type: 'map',
map: 'china',
nameProperty: 'name', // 指定用name属性
// 或者
nameProperty: 'Adcode', // 用adcode属性匹配
}]
问题3:地图不能缩放和平移
检查roam配置项。roam有三个可选值:
false:不允许缩放和平移(默认)true:允许缩放和平移'scale':只允许缩放'move':只允许平移
很多初学者忘了加roam: true,结果地图是死的,只能看不能操作。
问题4:高亮效果不明显
检查emphasis配置项。默认的highlight效果可能不够明显,你可以加强:
- 增大
shadowBlur,让阴影更明显 - 改变
areaColor,用更亮的颜色 - 增大
borderWidth,让边框更粗
emphasis: {
itemStyle: {
areaColor: '#ffeb3b',
borderColor: '#f57f17',
borderWidth: 3,
shadowBlur: 20,
shadowColor: 'rgba(0,0,0,0.5)'
}
}
问题5:数据很多,地图卡顿
当数据量很大的时候,地图渲染可能会变慢。解决办法:
- 减少数据量。如果不需要显示所有省份,只加载需要的
- 使用数据采样。对密集区域进行采样
- 开启WebGL渲染。ECharts支持WebGL,在某些情况下性能更好
- 分片加载。把数据分成多批加载,避免一次性渲染太多
// 开启WebGL
var chart = echarts.init(document.getElementById('map-container'), null, {
renderer: 'webgl'
});
第七步:进阶技巧
当你已经掌握了基础用法,不妨试试这些进阶玩法。
1. 多级下钻地图
很多商业场景需要省-市-区的多级下钻。ECharts支持这个功能,原理就是在点击某个区域时,加载更细粒度的GeoJSON,然后更新地图。
// 点击省份时,加载该省的市级别地图
myChart.on('click', function(params) {
var provinceName = params.name;
// 根据省份名拼接市地图的文件路径
var cityGeoJsonUrl = `./cities/${provinceName}.json`;
fetch(cityGeoJsonUrl)
.then(response => response.json())
.then(geoJson => {
echarts.registerMap(provinceName, geoJson);
// 更新地图配置
myChart.setOption({
geo: {
map: provinceName,
zoom: 1.2
},
series: [{
map: provinceName
}]
});
});
});
2. 自定义颜色映射
默认的视觉映射可能不够用。你可以自己定义颜色的映射规则。
visualMap: {
pieces: [
{ gte: 100000, label: '10万亿以上', color: '#8B0000' },
{ gte: 50000, lte: 99999, label: '5-10万亿', color: '#B22222' },
{ gte: 10000, lte: 49999, label: '1-5万亿', color: '#DC143C' },
{ gte: 0, lte: 9999, label: '1万亿以下', color: '#FF6347' }
],
outOfRange: {
color: '#ccc'
}
}
3. 添加自定义标记点
除了填充颜色的地图,还可以在地图上添加标记点,标注特定的位置。
series: [
{
type: 'map',
map: 'china',
// ... 地图配置
},
{
type: 'effectScatter',
coordinateSystem: 'geo',
data: [
{ name: '北京', value: [116.407526, 39.90403] },
{ name: '上海', value: [121.473701, 31.230416] },
{ name: '广州', value: [113.264385, 23.12911] }
],
symbolSize: 15,
showEffectOn: 'render',
rippleEffect: {
brushType: 'stroke'
},
label: {
show: true,
position: 'right',
formatter: '{b}'
},
itemStyle: {
color: '#ff4500'
},
emphasis: {
scale: true
}
}
]
effectScatter会在地图上添加带涟漪效果的标记点,非常适合标注重点城市或站点。
4. 地图与柱状图结合
有时候你不仅想在地图上显示数据,还想在旁边加一个柱状图做对比。ECharts支持多图表联动。
// 右侧添加柱状图
var option = {
title: { text: '各省GDP对比' },
grid: {
left: '3%',
right: '35%', // 给柱状图留空间
bottom: '3%',
containLabel: true
},
xAxis: { type: 'value' },
yAxis: {
type: 'category',
data: gdpData.map(item => item.name).reverse()
},
series: [{
type: 'bar',
data: gdpData.map(item => item.value).reverse(),
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#83bff6' },
{ offset: 0.5, color: '#188df0' },
{ offset: 1, color: '#188df0' }
])
}
}]
};
不过这里我只是示意,实际上地图和柱状图分开布局会好看一些。
第八步:性能优化建议
最后聊一下性能。地图可视化经常要处理大量数据,如果不注意优化,页面会卡得让人想摔电脑。
几个实用的优化技巧:
1. 按需加载
不要一次性加载所有数据。如果用户只需要看某个省,就先只加载这个省的GeoJSON。等用户点击了其他区域,再动态加载。
2. 数据压缩
GeoJSON文件可能很大,尤其是城市级别的精细地图。可以用一些工具进行压缩,比如mapshaper、topojson等。一般来说,压缩后文件大小能减少50%以上。
3. 使用Web Worker
如果GeoJSON解析比较耗时,可以放在Web Worker里执行,避免阻塞主线程。
// 主线程
const worker = new Worker('geo-parser.js');
worker.postMessage({ url: 'china.json' });
worker.onmessage = function(e) {
echarts.registerMap('china', e.data);
renderChart();
};
// geo-parser.js (Web Worker)
self.onmessage = function(e) {
fetch(e.data.url)
.then(res => res.json())
.then(data => self.postMessage(data));
};
4. 开启Canvas渲染
对于大数据量的地图,Canvas渲染通常比SVG更快。可以在初始化时指定:
var chart = echarts.init(container, null, {
renderer: 'canvas'
});
5. 虚拟滚动
如果侧边栏有大量的省份列表,可以考虑虚拟滚动,只渲染可见区域的元素。不过这个在地图场景下用得不多,因为地图本身已经占满了屏幕。
总结
好了,今天聊了这么多,核心就几点:
GeoJSON是地图的根基 —— 没有它,你就只能用的ECharts内置的地图。学会找和用GeoJSON,你就掌握了自定义地图的关键。
registerMap是桥梁 —— 不管你的GeoJSON从哪来,最终都要通过
echarts.registerMap(name, geoJson)注册,才能在series里使用。emphasis控制高亮 —— 鼠标悬停时的样式,全在这里配置。多试试各种属性,能找到最适合你需求的效果。
事件监听是交互的灵魂 ——
click、mouseover、hoverChanged,这几个事件基本能覆盖所有的交互场景。性能别忽视 —— 数据量大、地图精细的时候,卡顿是难免的。按需加载、数据压缩、Web Worker,这些手段能帮上大忙。
最后送你一句话:地图可视化的本质,是把地理信息和数据信息结合起来,让用户一眼就能看出规律和异常。 技术只是手段,清晰传达信息才是目的。
如果你在实践中遇到问题,随时可以来问我。祝你的地图项目一切顺利!
