☰
ECharts地图实战全攻略:GeoJSON、渲染与下钻一次讲透
2026/10/2 22:30:39 网站建设 项目流程

做数据可视化这几年,最常见的需求不是画折线图,也不是画饼图,而是“给我来一张地图”。尤其做企业大屏、运营看板、疫情统计、门店分布这类项目,几乎每个都离不开 echarts 地图。很多人一上来就卡住:不知道地图数据从哪来,不知道 GeoJSON 到底是什么,更不知道 echarts 里 map 和 geo 有什么区别,最后要么用网上的老代码改得乱七八糟,要么干脆放弃地图改用表格。

这篇就是我长期用 echarts 画地图的完整经验整理。从数据获取、环境准备,到真正把一张中国地图渲染出来,再到标注点、tooltip 换行、点击下钻这些高频功能,最后把我踩过的坑都摆在明面上。内容照顾到刚接触 echarts 的新手,代码可以直接复制跑通,同时也会解释每一步为什么这么做。如果你正打算在手头项目里塞一张地图,这篇文章应该能帮你省掉大半天的摸索时间。

1. 项目概述与核心思路

1.1 先搞清楚 echarts 地图到底在画什么

先泼一盆冷水:echarts 地图不是“地图应用”,它更准确的定位是“叠加在地理轮廓上的数据图表”。它和你在手机上用的高德地图、百度地图完全不是一回事。高德那种是瓦片地图,由无数张按层级切割的图片拼接而成,你看到的是真实道路、POI、卫星影像;而 echarts 地图是把一份叫 GeoJSON 的矢量数据读进来,用 Canvas 或 SVG 把各个行政区的边界描出来,然后在边界范围内绘制颜色、散点、飞线等统计信息。

一个常见的误区是:有人觉得 echarts 地图加载后应该像百度地图一样能放大看到街道。实际上 echarts 地图的“放大”只是矢量图形的整体缩放,边界坐标就那么多,放得再大也不会出现道路和门牌。所以做地图之前,先判断一下项目类型:如果只是看省级、市级的统计差异,比如销售额分布、人口密度、门店数量,echarts 地图是性价比最高的选择;如果要做带路线导航、实时定位、影像底图的功能,老老实实去用 Leaflet、OpenLayers 或者高德地图 SDK。

1.2 为什么在项目里选 echarts 而不是 GIS 方案

我经常被问:ArcGIS、OpenLayers、Leaflet 都能画地图,为什么偏偏选 echarts?作为在项目里都用过的过来人,我的判断标准其实很简单:数据渲染能力、开发效率、项目体量。

先说数据渲染能力。echarts 的地图底层走的是 Canvas 或 SVG 渲染,配合它自家的 visualMap 和 series 体系,做出“省份颜色随数值渐变”这种效果非常顺手。你只需要给定一个{name: '广东', value: 100}的数组,剩下的颜色映射、图例生成、悬停高亮它全包了。相比之下,Leaflet 做这种效果要自己算颜色、自己绑事件、自己写图例,工作量完全不在一个量级。

再说开发效率。一个中国地图加数据展示,用 echarts 半小时能出效果;用 GIS 框架从引入、加载底图、绑定数据到调试样式,没有两天搞不定。很多项目其实只需要“一张图 + 几个标注点”,根本不需要投影坐标系、图层管理这些高级能力。在这种场景下强行上 GIS 框架,属于杀鸡用牛刀。

当然 GIS 方案也有它的不可替代性。如果项目需要叠加卫星影像、需要地图旋转、需要比例尺或经纬网显示,那就是 GIS 框架的地盘。下面这个表格是我在项目选型时常用的对比维度,分享出来供你参考。

对比维度echarts 地图Leaflet/OpenLayers高德/百度地图 SDK
数据可视化强,原生支持 visualMap、散点、飞线中,需要手写逻辑弱,主要面向业务地图
底图类型无底图,纯矢量区域支持瓦片、影像、自定义图层自带丰富底图和 POI
交互能力缩放、点击、下钻、tooltip旋转、Draw、测量等 GIS 操作定位、路线规划、搜索
开发成本低,一套图表配置就能跑中高,需要理解图层和坐标系中,依赖对应 JS SDK 和 key
适用场景数据大屏、统计报表GIS 分析、影像叠加、精确测量位置服务、路线、移动端

我个人的经验是:只要项目核心诉求是“看数据”,优先用 echarts;只要项目核心诉求是“看地图”,才考虑 GIS。

1.3 快速理清一条完整实现路径

拿到一个“画 echarts 地图”的需求,不要上来就写代码。先拆解成一个链路:地图数据从哪来、怎么把数据注册进 echarts、怎么把业务数据绑定上去、怎么处理交互。

我拿实际项目举例。假设要做一张“全国各省销售额地图”,大屏上展示省份颜色深浅,鼠标悬停显示具体数值,点击某个省份进入该省的城市分布。这个需求拆开就是三件事:

  • 一份全国 GeoJSON 和一份省级 GeoJSON,来源可以是阿里云 DataV 地理选择器这类公开数据渠道;
  • 用echarts.registerMap注册地图,用series.map和visualMap完成颜色映射;
  • 用chart.on('click')监听省份点击,换注册新的市级地图数据,实现下钻。

想清楚这三步,代码只是执行问题。接下来的几个章节,我就按这个链路一步步展开。

2. 地图数据获取与处理

2.1 GeoJSON 是地图的“骨架”

GeoJSON 是一种基于 JSON 的地理数据格式。对一个省份地图来说,核心内容其实就是一串经纬度数组:边界上的每个点都有坐标,把这些点连起来,就构成一块闭合的多边形区域。

echarts 地图渲染的前提,就是你把这份 GeoJSON 通过registerMap注册给它。下面是一段简化后的省级 GeoJSON 结构示意:

{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "name": "广东省", "adcode": 440000 }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [113.5, 22.5], [113.6, 22.6], [113.7, 22.7] ] ] ] } } ] }

你不需要逐行读懂它,但至少要明白两件事:properties.name是 echarts 匹配数据的依据,geometry.coordinates是地图轮廓的坐标来源。后面如果出现“地图显示出来了但数据不显示”的故障,90% 是因为name跟你业务数据里的名字对不上。

2.2 几个能直接用的地图数据来源

网上搜索“GeoJSON 下载”,能找到不少资源,但质量参差不齐。有的数据坐标系不对,有的简化到轮廓失真,有的甚至缺了某个省份。我自己常用的渠道有这几个:

  • 阿里云 DataV 地理选择器:可以按省份、城市、区县逐级选择,生成对应的 GeoJSON,支持直接下载,地址是 datav.aliyun.com/portal/school/atlas/area_selector。
  • echarts 官方示例数据仓库里的 map 目录:里面有中国、世界以及部分城市的历史 GeoJSON,适合快速验证 demo。
  • 一些开源社区维护的中国行政区划数据项目:这类项目通常会把全国省、市、区县的数据打包好,提供 JSON 文件直接引用。

不管是哪类渠道,我都建议下载后先用文本编辑器打开看一眼,确认里面name字段的名称规范。以中国地图为例,你要注意省级名称到底是“广东”还是“广东省”,这会影响后面数据匹配。

2.3 坐标系问题:为什么你的标注点偏移了

这是最容易让新手困惑的问题。世界上有多种地理坐标系,国内常见的是 WGS84 和 GCJ02。WGS84 是国际通用的经纬度标准,GPS 设备输出的原始坐标就是它;GCJ02 是国测局坐标,市面上大部分地图 App 在国内使用的都是它,目的是对坐标进行加密偏移。

echarts 地图在渲染 GeoJSON 的时候,它只认经纬度数值,不会自动帮你在坐标系之间转换。如果你的省份边界数据是 WGS84,而业务数据的经纬度坐标是用高德地图拾取出来的 GCJ02,叠加在一起就会出现明显的偏移,大约几百米,城市级别数据尤其明显。

所以做标记点之前,一定要统一坐标系。一般来说,从阿里云 DataV 或公开 GeoJSON 仓库下载的地图数据大多是 WGS84;如果要在地图上标注店铺位置,我建议用天地图坐标拾取或百度坐标拾取工具获取原始坐标,再确认和地图数据是否属于同一坐标系。如果项目里既有 WGS84 又有 GCJ02,需要写一个转换方法统一。

2.4 数据体积与地图渲染性能

一个全国 GeoJSON 文件大小通常在 100KB 到 300KB 之间,市级合集会大一些,区县级更大。对单个页面来说,几百 KB 不算什么;但如果是大屏项目,同时加载全国地图、省级地图、多个散点图层,再加上其他图表,性能压力就会显现出来。

优化思路有两个方向。一是数据简化,可以用拓扑简化算法把边界坐标点抽稀,在轮廓肉眼可接受的前提下,文件体积能压缩到原来的三分之一甚至更小。二是按需加载,不要一开始就把所有层级的 GeoJSON 全部引入,而是初始只加载全国地图,用户点击某个省份时,再通过 import 动态加载对应省份的市级数据。这两个方法我都在生产环境里用过,效果很明显,首屏加载速度能快不少。

3. 环境准备与地图渲染实现

3.1 安装并接入 echarts

开始写代码之前,先确认你的项目环境。如果你用的是原生 HTML 页面,直接在页面里引入 echarts 的 script 文件就行;如果是 Vue 或 React 项目,推荐用 npm 安装依赖。

npm install echarts

这里有一个选择:全量引入还是按需引入。全量引入最简单,什么图表都能画,但打包体积会大;按需引入可以只打包地图相关的模块,体积更小。实际项目中,如果只是画地图加几个基础图表,我建议按需引入:

import * as echarts from 'echarts/core'; import { MapChart } from 'echarts/charts'; import { TooltipComponent, VisualMapComponent } from 'echarts/components'; import { CanvasRenderer } from 'echarts/renderers'; echarts.use([MapChart, TooltipComponent, VisualMapComponent, CanvasRenderer]);

代码里我用的是echarts/core这种模块化写法,配合use注册所需组件。这是目前官方推荐的方式,既能保证功能完整,又能控制打包体积。

3.2 第一张中国地图:从注册到渲染

拿到地图数据和渲染环境之后,接下来就是核心的一步。先把下载好的全国 GeoJSON 文件放到项目目录里,然后通过echarts.registerMap注册。

import chinaJson from './china.json'; echarts.registerMap('china', chinaJson);

第二个参数是地图数据的 JSON 对象,第一个参数是你要给这张地图起的名字。这个名字很重要,因为后面 series 配置里也要用到同一个名字。

接下来初始化图表实例。注意,初始化时传入的 DOM 容器必须有明确的宽度和高度,否则 echarts 会警告并画不出来:

const chart = echarts.init(document.getElementById('map-container')); chart.setOption({ tooltip: { trigger: 'item', formatter: function(params) { return params.name + '<br/>销售额:' + (params.value || 0) + ' 万元'; } }, visualMap: { min: 0, max: 1000, left: 'left', top: 'bottom', text: ['高', '低'], inRange: { color: ['#e0f3f8', '#abd9e9', '#74add1', '#4575b4', '#313695'] } }, series: [{ name: '销售额', type: 'map', map: 'china', roam: true, label: { show: true, fontSize: 10 }, data: [ { name: '广东', value: 863 }, { name: '江苏', value: 782 }, { name: '山东', value: 654 } ] }] });

这段配置里,visualMap负责把数值映射成颜色深浅,series.data里每一项的name必须和 GeoJSON 里的properties.name对齐,value就是要展示的数值。roam: true允许地图缩放和拖拽,这是一个很常用的交互选项。

3.3 geo 和 series.map 到底怎么选

新手最容易搞混的两个东西,就是geo组件和series里的type: 'map'。很多网上的老代码一会儿用geo一会用series,看得人一头雾水。

我自己的理解是:geo组件是“纯地图背景”,它只负责渲染地图轮廓,不承载数据;series.map是“地图数据系列”,它既能渲染地图轮廓,又能把业务数据映射上去。换句话说,如果你只是需要一块底图,上面再用散点图做标记,那用geo更合适;如果你希望省份本身根据数值变色,那就必须用series.map。

实际项目中,像“省份销售额地图”这种需求,基本都用series.map,搭配visualMap就完事了。至于geo,更多是用在地图底图上叠加effectScatter飞线、涟漪效果这类场景。记住这个原则,就不会在配置里绕晕。

3.4 名称映射:解决“广东”和“广东省”不一致的问题

刚才我多次强调name对齐,但不同来源的数据,名称可能不一样。GeoJSON 里叫“广东省”,你接口返回的名字叫“广东”,直接匹配就会失败。

echarts 提供了nameMap来解决这个问题。在series配置里加一个映射关系即可:

series: [{ type: 'map', map: 'china', nameMap: { '广东': '广东省', '江苏': '江苏省', '山东': '山东省' } }]

如果你有几十个省份要映射,手写会累死。我的习惯是写一个通用的映射函数:从 GeoJSON 的features里读取所有properties.name,生成一个标准名称的数组,再跟接口返回的名称做一次匹配。这样不管数据源怎么变,只要两边都有“省”或者都没有“省”,就能自动对上。

4. 三个高频实用功能实现

4.1 在地图上添加标记点和涟漪效果

光有一张省份变色图,很多时候不够。项目里经常需要在某个城市上面画一个圆点、加一个涟漪动效,表示“这个地方有门店”或者“这里发生过事件”。

标记点有两种实现方式。一种是在series.map里用markPoint,这种方式比较简单:

series: [{ type: 'map', map: 'china', markPoint: { symbol: 'circle', symbolSize: 8, itemStyle: { color: '#ff6600' }, data: [ { name: '广州', coord: [113.3, 23.1] }, { name: '上海', coord: [121.4, 31.2] } ] } }]

这里的coord是经纬度坐标,你必须确认它跟 GeoJSON 是同一个坐标系,否则标记点就会偏移。

另一种方式是直接在series里新增一个散点系列,用coordinateSystem: 'geo'指定坐标系。这种方式更灵活,可以叠加涟漪效果:

series: [{ type: 'effectScatter', coordinateSystem: 'geo', rippleEffect: { brushType: 'stroke' }, data: [ { name: '广州', value: [113.3, 23.1, 15] }, { name: '上海', value: [121.4, 31.2, 22] } ], symbolSize: function(val) { return val[2] * 2; }, label: { show: true, formatter: '{b}', position: 'right' } }]

这里value数组的格式是[经度, 纬度, 数值],后面的数值可以控制圆点大小。涟漪效果(effectScatter)我用的场景很多:运营大屏上的实时监控点、物流项目里的转运中心,加上一圈扩散波纹,视觉冲击力明显不一样。

4.2 让 tooltip 自动换行与样式定制

地图 tooltip 的默认显示方式是把所有信息排成一行,字段一多就会非常挤。很多热词里都有“echarts tooltip 自动换行”的搜索需求,说明这是共性痛点。

tooltip 换行的本质,是在formatter里返回带 HTML 换行标签的字符串:

tooltip: { trigger: 'item', formatter: function(params) { return [ '<div style="font-weight:bold;font-size:14px">' + params.name + '</div>', '销售额:' + (params.value || 0) + ' 万元', '目标完成率:' + (params.value ? (params.value / 1000 * 100).toFixed(1) + '%' : '0%'), '同比增长:+12.5%' ].join('<br/>'); } }

用数组拼接再join('<br/>'),是我比较推荐的方式,比直接+拼接字符串更清晰,也方便加更多字段。如果想让 tooltip 中的某条数据高亮或变色,可以直接在字符串里包<span style="color:#ff6600">。

有时候 tooltip 会被容器边界截断,可以在配置里加confine: true,让 tooltip 强制限制在图表容器内:

tooltip: { confine: true }

这个属性我建议在画地图和大屏项目时直接就加上,能省掉很多“tooltip 跑到页面外显示不全”的麻烦。

4.3 点击省份下钻到市级地图

点击下钻是 echarts 地图里最有交互感的功能。先监听省份点击事件,拿到点击的省份名,再加载对应的市级 GeoJSON,重新注册并 setOption。

chart.on('click', function(params) { if (params.name && params.name !== 'china') { loadCityMap(params.name); } }); function loadCityMap(provinceName) { // 这里的 getProvinceJson 是动态加载省级 GeoJSON 的方法 getProvinceJson(provinceName).then(function(provinceJson) { echarts.registerMap(provinceName, provinceJson); chart.setOption({ series: [{ type: 'map', map: provinceName, data: cityData[provinceName] || [] }] }, true); }); }

这里的setOption第二个参数传了true,表示完全替换之前的配置而不是合并。如果不传这个参数,上一次的全国地图配置还残留着,会出现“全国地图和省级地图叠在一起”的诡异现象。

下钻之后还要考虑怎么返回。我一般会在页面上放一个“返回全国”的按钮,点击时重新注册全国地图并重置配置:

function backToChina() { chart.setOption({ series: [{ type: 'map', map: 'china', data: nationalData }] }, true); }

下钻功能做好之后,整个地图的可用性会上升一个档次,领导看大屏的时候也更愿意互动。

5. 常见问题与排查技巧实录

5.1 地图空白不发亮:先查这三件事

地图画不出来,是 echarts 踩坑排行榜第一名。我遇到这种情况,基本按顺序检查:

第一,GeoJSON 有没有成功加载。如果registerMap的第一个参数注册了,但 json 文件路径写错或加载失败,地图自然渲染不出来。在浏览器控制台打印一下 json 数据,确认不是 undefined。

第二,容器有没有宽高。echarts.init要求传入的 DOM 元素有明确的宽高,很多新手把容器写成了 0 高度,或者用了display:none,结果图表完全空白。

第三,map 名称是否一致。registerMap('china', chinaJson)里的'china'和series.map: 'china'必须完全一致,大小写都不能错。我见过有人注册的是'中国',配置里写的却是'china',结果当然是白屏。

这三个地方排查完,90% 的空白问题都能解决。剩下的情况,把浏览器控制台报错信息发出来,基本都能定位到。

5.2 省份和业务数据对不上:一层一层找原因

“地图显示出来了,但是有的省份不显示颜色”“点击某省没反应”,这种问题定位思路是一样的:看 name 匹配。

先用代码跑一遍,把所有 GeoJSON 里的省份名和业务数据里的省份名拉出来对比:

const jsonNames = chinaJson.features.map(function(item) { return item.properties.name; }); const dataNames = seriesData.map(function(item) { return item.name; }); console.log(jsonNames, dataNames);

控制台同时打印出两组名字,你一眼就能看到是“广东”和“广东省”这种小差异,还是某一方名字完全对不上。前者用nameMap解决,后者就要检查业务数据本身是不是有错别字或者用了别的简称。

另外,如果你用的是visualMap,还要确认它没被误配置。visualMap的min和max范围如果设置得太窄,比如所有数据都是 800 到 900,而min是 0,那么颜色会被压缩得非常接近,看起来像没变色。这时候把min调成数据的最小值,效果马上就出来了。

5.3 页面里多个图表相互影响:resize 是重灾区

大屏项目里往往有十几个图表同时渲染,地图只是其中之一。一个很容易被忽略的坑是:窗口大小变化时,其他图表都调用了resize,但地图容器所在的 tab 一开始是隐藏的,等到显示时宽度已经变了,地图却还是旧尺寸。

解决思路有两个。一是在容器显示后手动调用地图实例的resize;二是监听 window resize 时统一对所有图表实例调用resize:

window.addEventListener('resize', function() { chart.resize(); });

如果你用的是 Vue 3,不要忘记在onUnmounted里调用chart.dispose()释放实例,否则会出现内存泄漏和切换页面报错。

5.4 大屏 rem 适配对 echarts 不生效的原因与对策

搜索热词里有一条“pxtorem 对 echarts 没起到效果 vue3”,这个坑我在大屏项目里确实踩过。

echarts 的绘制逻辑在 Canvas 里,Canvas 渲染的图形尺寸、位置、文字大小,并不会因为页面 CSS 里的 px 转 rem 而自动缩放。也就是说,你设置了fontSize: 12,它始终是 12 像素,不管根元素怎么变。这也是很多大屏在窗口缩放时文字不跟着变的原因。

我的处理方式是:大屏项目里把 echarts 图表容器设置成固定像素尺寸,然后监听窗口 resize,动态计算容器的宽高,再调用chart.resize()。配合一个统一的缩放函数,把所有图表实例一起更新。另外,如果一定要让 echarts 内部的文字跟随缩放,可以在 resize 时重新 setOption 一份带缩放比例的文字大小配置,但这样做更新成本比较高,我一般只在最重要的页面上用。

5.5 其他高频问题速查

现象常见原因解决思路
地图能渲染,但省份标签重叠严重地图面积小、label 数量多调小 fontSize,或设置label.layout相关配置
点击地图没反应事件绑定在 resize 或下钻后丢失把chart.on统一封装,在下钻后重新绑定
饼图 legend 和 labelLine 位置不对labelLine 长度和 formatter 返回值不匹配检查 labelLine 的 length、length2,配合 formatter 调整
effectScatter 动态效果不出现没有引入 effectScatter 组件或版本过低按需注册EffectScatterChart,确认 echarts 5 以上
地图数据更新后旧数据还在setOption 没有设置 notMerge使用chart.setOption(option, true)强制替换

这里面的饼图 labelLine、legend 问题虽然不属于地图核心功能,但在大屏项目里经常和地图一起出现,所以放在一起排查。遇到以上问题,先看控制台报错,再对照表格排查,效率会高很多。

6. 实操心得:养成一套顺手的工作流

每次拿到新的地图项目,我固定会走四个步骤:先确认数据格式,再检查坐标系,然后做 name 映射,最后才写渲染配置。这套工作流看着麻烦,实际上比一上来就写代码省时得多。

尤其是 name 映射这一环,我建议每个项目里都写一个独立的映射工具函数。先通过阅读 GeoJSON 的 features 拿到所有官方名称,然后和业务接口返回的名称做一次遍历匹配,遇到匹配失败的打个日志出来。这样即使以后换数据源、换接口,也不用担心地图突然“瞎掉”。我在实际项目中踩得最多的坑就是数据名不匹配,简直可以排到所有地图问题里的第一位。

另外还有一个建议:地图数据文件尽量放在静态资源目录里托管,而不是打进 JS bundle。因为地图 GeoJSON 文件往往比较大,而且不同层级需要按需加载,放静态目录能利用浏览器缓存,切层级时也不用重新下载重复的数据。

echarts 地图本身不算复杂的技能,但它涉及数据格式、坐标系统、渲染机制几个不同层面的知识。把这些串起来,以后再画什么地图都能心里有底。希望这篇文章能帮你少走一些弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询