本文详细讲解ECharts地图的开发流程,涵盖从基础配置、数据渲染、文字嵌入到多级下钻及VisualMap视觉映射的完整实现。通过Vue框架示例,演示如何处理地图注册、点击事件、动态加载JSON以及解决事件冲突等常见问题,帮助开发者构建交互丰富的地图应用。
ECharts地图制作
离线地图数据下载地址:https://datav.aliyun.com/tools/atlas/index.html
ECharts官方文档地址:https://echarts.apache.org/zh/option.html
本文基于Vue框架编写,其他前端框架用户可参考思路进行转换,核心逻辑基本一致。
基础配置
首先实现地图的基本显示,npm安装步骤省略,具体请参考官方文档。注意创建的div容器必须明确设置宽度和高度,关于图表宽高的自适应处理,可参考我的另一篇文章。
<template>
<div class="content">
<div ref="charts" style="width: 1000px; height: 800px"></div>
</div>
</template>
<script>
import * as echarts from "echarts";
import zhongguo from "@/assets/mapJson/data-city.json"
export default {
created () {
this.$nextTick(() => {
this.initCharts();
})
},
methods: {
initCharts() {
const charts = echarts.init(this.$refs["charts"]);
const option = {
// 背景颜色
backgroundColor: "#404a59",
// 提示浮窗样式
tooltip: {
show: true,
trigger: "item",
alwaysShowContent: false,
backgroundColor: "#0C121C",
borderColor: "rgba(0, 0, 0, 0.16);",
hideDelay: 100,
triggerOn: "mousemove",
enterable: true,
textStyle: {
color: "#DADADA",
fontSize: "12",
width: 20,
height: 30,
overflow: "break",
},
showDelay: 100
},
// 地图配置
geo: {
map: "china",
label: {
// 通常状态下的样式
normal: {
show: true,
textStyle: {
color: "#fff",
},
},
// 鼠标放上去的样式
emphasis: {
textStyle: {
color: "#fff",
},
},
},
// 地图区域的样式设置
itemStyle: {
normal: {
borderColor: "rgba(147, 235, 248, 1)",
borderWidth: 1,
areaColor: {
type: "radial",
x: 0.5,
y: 0.5,
r: 0.8,
colorStops: [
{
offset: 0,
color: "rgba(147, 235, 248, 0)", // 0% 处的颜色
},
{
offset: 1,
color: "rgba(147, 235, 248, .2)", // 100% 处的颜色
},
],
globalCoord: false, // 缺省为 false
},
shadowColor: "rgba(128, 217, 248, 1)",
shadowOffsetX: -2,
shadowOffsetY: 2,
shadowBlur: 10,
},
// 鼠标放上去高亮的样式
emphasis: {
areaColor: "#389BB7",
borderWidth: 0,
},
},
},
};
// 地图注册,第一个参数的名字必须和option.geo.map一致
echarts.registerMap("china",zhongguo)
charts.setOption(option);
},
},
};
</script>
这是最基础的配置,并添加了一些自定义样式以美化地图。如果您完整复制代码并引入了china.json文件,您将看到如下效果。

其中比较关键的一点是:在注册地图时,如果option.geo.map配置项中的名称使用“china”,南海诸岛会如上图所示以缩略图形式展示;但如果使用其他名称,则不会展示该缩略图。
再次强调,如果注册名称与配置名称不一致,将导致异常,致使地图无法正确显示。
数据渲染
实际开发中,通常需要将后台数据渲染到地图上。我们在option配置中添加series属性,以下是两个参考示例:
series: [
{
type: "scatter",
coordinateSystem: "geo",
symbol: "pin",
legendHoverLink: true,
symbolSize: [60, 60],
// 这里渲染标志里的内容以及样式
label: {
show: true,
formatter(value) {
return value.data.value[2];
},
color: "#fff",
},
// 标志的样式
itemStyle: {
normal: {
color: "rgba(255,0,0,.7)",
shadowBlur: 2,
shadowColor: "D8BC37",
},
},
// 数据格式,其中name,value是必要的,value的前两个值是数据点的经纬度,其他的数据格式可以自定义
// 至于如何展示,完全是靠上面的formatter来自己定义的
data: [
{ name: "西藏", value: [91.23, 29.5, 2333] },
{ name: "黑龙江", value: [128.03, 47.01, 1007] },
],
showEffectOn: "render",
rippleEffect: {
brushType: "stroke",
},
hoverAnimation: true,
zlevel: 1,
},
// {
// type: "effectScatter",
// coordinateSystem: "geo",
// effectType: "ripple",
// showEffectOn: "render",
// rippleEffect: {
// period: 10,
// scale: 10,
// brushType: "fill",
// },
// hoverAnimation: true,
// itemStyle: {
// normal: {
// color: "rgba(255, 235, 59, .7)",
// shadowBlur: 10,
// shadowColor: "#333",
// },
// },
// zlevel: 1,
// data: [
// { name: "西藏", value: [91.23, 29.5, 2333] },
// { name: "黑龙江", value: [128.03, 47.01, 1007] },
// ],
// },
],
两种渲染方式如下:


使用备注部分时,需要在option.tooltip配置中添加formatter属性,我的实现如下:
const option = {
// ...
tooltip: {
// ...
formatter(params) {
return `地区:${params.name}</br>数值:${params.value[2]}`;
}
}
}
更多效果需要自行试验,这需要耐心和尝试,您甚至可以将柱状图叠加到地图上。如果有更炫酷的效果,欢迎分享。
嵌入文字
使用option.graphic配置可以实现简单的水印效果。
const option = {
// ...
graphic:{
// 水印类型
type: 'text',
// 相对于容器的位置
left:'10%',
top: '10%',
// 样式设置
style: {
// 文本内容
text: "create by cRack_cLick",
// 字体粗细、大小、字体
font: 'bolder 1.5rem "Microsoft YaHei", sans-serif',
// 字体颜色
fill: "#fff"
}
}
}
效果如下:

利用graphic的type设置为“group”,还可以组合出一些有趣的效果(参考官方文档示例):
graphic: {
type: "group",
rotation: Math.PI / 4,
bounding: "raw",
left: 110,
top: 110,
z: 100,
children: [
{
type: "rect",
left: "center",
top: "center",
z: 100,
shape: {
width: 400,
height: 50,
},
style: {
fill: "rgba(0,0,0,0.3)",
},
},
{
type: "text",
left: "center",
top: "center",
z: 100,
style: {
fill: "#ddd",
text: "create by cRack_cLick",
font: 'bolder 1.5rem "Microsoft YaHei", sans-serif',
},
},
],
},

地图下钻
常见需求是点击某个省份后,切换到该省份的详细地图,甚至支持下钻到市、县级别。
为了实现点击下钻,我们需要先了解ECharts中的点击事件,具体文档参考。
就当前功能而言,暂时不需要添加其他业务逻辑或省级数据渲染,仅实现地图切换。因此,点击事件中我们需要获取点击的省份名称,根据该名称选择对应的地图JSON文件,最后重新渲染ECharts图表。以下是简单示例:
// 新增加北京的地图JSON文件
import beijing from "@/assets/mapJson/data-beijing.json";
// ...
initCharts(){
const charts = echarts.init(this.$refs["charts"]);
// ...
// 注意这里是echarts的实例对象,而不是echarts组件本身。
charts.on('click', ({name}) => {
if (name === "北京") {
// 修改option的配置,可以继续自定义
option.geo.zoom = 0.8
// 就像上面提到的,这里必须要和注册地图时的名字一致
option.geo.map = "beijing"
// 注册地图
echarts.registerMap("beijing", beijing)
// 重新渲染
charts.setOption(option, true)
}
})
}
需要注意的是,在重新调用setOption时,我们加入了第二个参数。按照官方文档说明:
参数说明:
调用方式示例:
chart.setOption(option, notMerge, lazyUpdate);或者
chart.setOption(option, {
notMerge: ...,
lazyUpdate: ...,
silent: ...
});
- option
图表的配置项和数据。
- notMerge
可选,是否不跟之前设置的option进行合并,默认为false,即合并。
- lazyUpdate
可选,在设置完option后是否不立即更新图表,默认为false,即立即更新。
- silent
可选,阻止调用setOption时抛出事件,默认为false,即抛出事件。
将第二个参数设置为true以让图表重新渲染,而不合并配置。当然,具体取决于您的开发需求,此处仅为演示,并非偷懒。
另外,在ECharts v3.x版本中,切换地图默认带有过渡动画;而在v4.x和v5.x版本中则没有过渡动画。如果知道如何添加,欢迎私信交流。
上述方法虽可实现地图切换,但显然开发中如此编写会被批评。下钻三十多个地图要写三十多个if判断,显然不理智。一种方式是通过axios或ajax异步请求,但这需要您与生产环境运维协商好,否则可能导致JSON文件请求失败。
以下是我在前端编写的一个简单工具方法,仅供参考:
import zhongguo from "@/assets/mapJson/data-city.json";
import neimenggu from "@/assets/mapJson/data-neimenggu.json";
import beijing from "@/assets/mapJson/data-beijing.json";
// ...
const mapDict = {
"北京": "beijing",
"内蒙古": "neimenggu",
// ...
}
const mapData = {
beijing,
neimenggu,
// ...
}
export function getMap(mapName) {
const cityName = mapDict[mapName]
if(cityName){
return [cityName, mapData[cityName]]
}
return ['china', zhongguo]
}
需要建立两个字典:一个是汉字和拼音的对照映射,另一个是拼音和JSON文件的映射。此配置可灵活调整,并非唯一方案。
优化上述代码:
// 删除地图json文件的引用,修改为上面的工具方法
import { getMap } from "./maputil";
methods: {
initCharts() {
const charts = echarts.init(this.$refs["charts"]);
const option = {
// ...
};
// 不传name默认会返回中国地图
const [mapName, mapJson] = getMap();
option.geo.map = mapName;
// 地图注册,第一个参数的名字必须和option.geo.map一致
echarts.registerMap(mapName, mapJson);
charts.setOption(option);
charts.on("click", ({ name }) => {
// 这里和上面一样,其实还可以再优化一下。为了方便阅读,这里不再封装。
const [mapName, mapJson] = getMap(name);
option.geo.zoom = 0.8;
option.geo.map = mapName;
echarts.registerMap(mapName, mapJson);
charts.setOption(option, true);
});
}
}
效果如下:

结合水印制作级联效果
现在的地图可以下钻了,但操作起来似乎还有些别扭。
我们期望的效果是:每下钻一层,水印部分就会加上当前地区的名称。点击水印中的地区名称,即可跳转到对应地区的地图。我们需要改造ECharts示例的click事件。
首先将option.graphic的默认值修改为中国地图,为方便阅读,此处仅使用text格式演示:
// ...
graphic: [
{
type: "text",
left: "10%",
top: "10%",
style: {
text: "中国",
font: 'bolder 1.5rem "Microsoft YaHei", sans-serif',
fill: "#fff",
},
},
],
以数组形式编写后,思路就清晰了。只要在click事件中将下钻地图的信息push进来,并且为了防止重合,稍微移动一下定位即可。我的示例如下:
charts.on("click", ({ name }) => {
const [mapName, mapJson] = getMap(name);
option.geo.zoom = 0.8;
option.geo.map = mapName;
// 为了重新定位,这里使用了length
const idx = option.graphic.length + 1;
option.graphic.push({
type: "text",
left: `${idx * 10}%`,
top: "10%",
style: {
text: name,
font: 'bolder 1.5rem "Microsoft YaHei", sans-serif',
fill: "#fff",
},
});
echarts.registerMap(mapName, mapJson);
charts.setOption(option, true);
});
点击后效果如下:

现在还有一个问题,就是点击地区名字没有响应。因此,我们还需要为option.graphic子元素添加click事件。
这个click事件功能类似,获取地图名称、获取地图数据、重新渲染。但需要注意,例如点击北京时,数组中需要删除密云区的元素;同理,点击中国时,后续所有元素都要删除。此处未将相同部分抽离:
// 防止graph里频繁添加click事件,在添加click事件之前先全部清空掉。
charts.off()
charts.on("click", ({name}) => {
// 如果option.graphic里已经有了城市名称,则不进行任何操作,防止频繁点击
const index = option.graphic.findIndex(i => i.style.text === name);
if (!name || index !== -1) return
const [mapName, mapJson] = getMap(name);
option.geo.zoom = 0.8;
option.geo.map = mapName;
// 为了重新定位,这里使用了length
const idx = option.graphic.length + 1;
option.graphic.push({
type: "text",
left: `${idx * 10}%`,
top: "10%",
style: {
text: name,
font: 'bolder 1.5rem "Microsoft YaHei", sans-serif',
fill: "#fff",
},
onclick: () => {
// 利用函数的作用域,可以直接拿上面的name来用
const [grahpName, graphJson] = getMap(name);
const index = option.graphic.findIndex(i => i.style.text === name);
// 点击元素之后的所有元素全部删除
option.graphic.splice(index + 1);
// 很多操作重复了,你可以将公共部分抽离出来
option.geo.map = mapName;
echarts.registerMap(grahpName, graphJson);
charts.setOption(option, true);
},
});
echarts.registerMap(mapName, mapJson);
charts.setOption(option, true);
});
这里有个坑:在graph添加click事件后,点击时会同时触发上面charts.on的click事件。想了很久没找到更好的解决方式,最后只好判断name是否为空来暂时解决。如果有更好的办法,请留言。最终效果如下:

至此绘制地图已经完毕,更多依赖自己的业务需求来进行更灵活的配置和渲染。它的API并没有太复杂的逻辑,只是我们缺少了一点耐心去实验。
visualMap
首先来看效果

增加visualMap来让地图的数据渲染更有层次感,实现起来也很简单,只需要在option中增加visualMap配置即可:
const option = {
// ...
visualMap: {
// 是否展示左下角,即是是false,也仅是不显示,不影响数据的映射
show: true,
// 上下端文字
text: ["高", "低"],
// 最小值和最大值,必须指定
min: 0,
max: 6000,
// 位置
left: "10%",
bottom: "10%",
// 是否展示滑块
calculable: true,
// 指定映射的数据,对应的是option.series,这里根据自己的实际需要进行配置
seriesIndex: [0],
// 从下到上的颜色
inRange: {
color: ['#00467F', '#A5CC82'],
},
//字体颜色
textStyle: {
color: "#fff",
map: "china",
},
}
}
如果您的代码是跟着我从上面一直写下来的,那么此时您应该发现只是定位的图标变了,相应的地图区域并未变色。因此,我们还需要把地图的数据映射上去,所以在option.series中再加一个元素,使其type=“map”,内容与geo一致即可,但要多加data属性,渲染的数据和定位图标一致。并将seriesIndex的索引做好映射,即可实现。
const option = {
// ...
visualMap: {
// 是否展示左下角,即是是false,也仅是不显示,不影响数据的映射
show: true,
// 上下端文字
text: ["高", "低"],
// 最小值和最大值,必须指定
min: 0,
max: 6000,
// 位置
left: "10%",
bottom: "10%",
// 是否展示滑块
calculable: true,
// 指定映射的数据,对应的是option.series,这里根据自己的实际需要进行配置
seriesIndex: [0],
// 从下到上的颜色
inRange: {
color: ['#00467F', '#A5CC82'],
},
//字体颜色
textStyle: {
color: "#fff",
map: "china",
},
}
}
如果您的代码是跟着我从上面一直写下来的,那么此时您应该发现只是定位的图标变了,相应的地图区域并未变色。因此,我们还需要把地图的数据映射上去,所以在option.series中再加一个元素,使其type=“map”,内容与geo一致即可,但要多加data属性,渲染的数据和定位图标一致。并将seriesIndex的索引做好映射,即可实现。
如果出现了缩放重影,说明生成了两个地图组件。需要在新的series中加上geoIndex属性,值是geo中的索引,这样只会共享一个组件,不会出现缩放重影的问题。
以下为源文档:
默认情况下,map series会自己生成内部专用的geo组件。但是也可以用这个geoIndex指定一个geo组件。这样的话,map和其他series(例如散点图)就可以共享一个geo组件了。并且,geo组件的颜色也可以被这个map series控制,从而用visualMap来更改。
当设定了geoIndex后,series-map.map属性,以及series-map.itemStyle等样式配置不再起作用,而是采用geo中的相应属性。
总结
到此这篇关于画echarts地图的文章就介绍到这了,更多相关echarts绘制地图内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!