ECharts省市级地图下钻实战:从数据源到交互实现
发布时间:2026/9/9 2:03:12 锦皓数字建站

简介这是一份面向大数据可视化开发者的ECharts地图数据包覆盖全国所有省份及下属市的地理边界信息解决地图边界获取难、数据格式不匹配等痛点适合大屏、区域分析等场景。压缩包共863个文件以610个JSON地图文件为核心另有250个JS脚本及说明文档便于加载与二次开发整体仅12.62MB按省份独立存放可按需灵活取用。已有14511人浏览学习在同类资源中热度较高。拿到后可直接在ECharts中通过series-map配置渲染省市级地图支持tooltip、缩放等交互也可将业务数据映射至区域颜色实现人口、经济等指标的可视化分析是学术与商业场景中易上手的实用工具。 嘴上说得轻松——“echarts 做个全国省地图然后点一下某个省弹出或者切换成这个省对应的市地图”。真上手你就会发现光是地图数据从哪来就够折腾半天后面还有点击事件、地图切换、返回逻辑、行政区划编码一堆细节等着。我最近刚做完一版支持全国到省级再下钻到市级的可视化大屏地图把完整方案和踩坑记录整理出来。如果你正好要做类似功能可以直接参考这套思路至少能帮你少翻三个晚上的车。1. 先把需求拆明白省→市地图到底要什么1.1 两种容易混淆的“省→市”形态我见过太多人一上来就搜“echarts 中国地图下钻”但实际需求其实分两种。第一种是整图切换型全国地图作为主视图点击某个省份后整个画布切换成该省的市级地图全国图被替换掉需要有一个“返回全国”的按钮。第二种是局部联动型页面左侧是全国地图右侧固定一个省份的市级地图点击左侧省份右侧跟着变化。前者更像钻取后者更像联动。标题里写的是“全国的省地图每个省对应的市地图”从字面上看更接近第一种整图切换型而且这也是可视化大屏项目里出现频率最高的诉求。但不管哪种形态背后都需要解决同一个问题怎样让 ECharts 在不同层级的 geoJSON 之间切换并且保证数据能对上。所以我下面默认按整图切换型来展开同时会把联动型需要的 name 匹配逻辑也一起讲清楚因为这两者在数据准备上完全一样。1.2 交互链路拆解把需求拆成最小闭环其实就是四步加载全国地图 geoJSON注册并渲染全国省份图监听地图 click 事件拿到被点击省份的名称根据省份名称找到对应的行政区划代码adcode请求该省的市级地图 geoJSON切换地图为省级市级图并提供“返回全国”的入口回去时恢复之前的全国视图。这个链路里最容易被忽略的是“根据省份名称找到 adcode”这一步。很多教程直接给了一张写死的省名映射表而实际项目中最怕这种写死的数据——一旦地图数据源升级、名称带不带“省/市”后缀发生变化你的映射表就废了。更稳的做法是从 geoJSON 本身的 properties 里动态读取 adcode后面会细说。2. 地图数据从哪来DataV GeoAtlas 与 adcode 的取舍2.1 为什么我最终选了 DataV GeoAtlasECharts 4.x 时代官方还内置中国地图数据包到了 ECharts 5.x官方不再默认提供中国地图需要自己准备 geoJSON。自己手画肯定不现实市面上常见的选择有第三方收费地图服务、公开行政区划数据、还有阿里 DataV GeoAtlas。我最后用的是 DataV GeoAtlas原因很直接它的 URL 结构本身就是一套“地图层级目录”。全国是100000_full.json广东省是440000_full.json广州市是440100_full.json。只要拿到 adcode就能用同一套拼接规则拿到任意省、任意市的地图数据不需要额外维护每一个层级的数据表。而且 GeoAtlas 的 geoJSON 里每个 feature 的 properties 都带着adcode、name、center这些字段后面做数据匹配、地图定位都能直接复用。对一个只需要省市级下钻的大屏项目来说这个数据源的性价比非常高。2.2 先用浏览器确认你要的数据长什么样拿到接口后别急着写代码先在浏览器地址栏直接访问一次把返回内容打开看两眼。这一步能帮你避免后面所有“地图白屏”问题。以全国数据为例访问https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json你会看到type是FeatureCollectionfeatures数组里每一项对应一个省份。每个省份的properties.name大概是“北京市”“广东省”“浙江省”这种带后缀的写法properties.adcode是对应的行政区划代码比如广东是440000。然后你再用广东省的 adcode 请求一次https://geo.datav.aliyun.com/areas_v3/bound/440000_full.json这次features里就是广东下辖的各个市比如“广州市”的 adcode 是440100。把这两份数据打开看一遍后续每一步代码你都会心里有底。很多同学一上来就埋头写代码最后地图不显示才开始一点点排查浪费时间。2.3 生产环境不要裸连第三方地址DataV 的在线接口用来验证技术方案没问题但真正上生产时我强烈建议把这些 json 文件下载到本地静态目录。原因有两个。一是第三方服务的稳定性不在你控制范围内线上环境突然请求失败地图就白屏二是每次页面打开都去请求外网资源加载速度和合规性都不好把控。把全国和各省级的_full.json放到自己的静态服务器上用相对路径请求整个地图模块会稳很多。如果你只是本地写 demo 验证直接 fetch 在线地址完全够用我的示例代码也会先用在线地址方便你直接跑通。3. 核心代码链路注册、点击、返回3.1 初始化全国地图先封装一个统一的renderMap函数。这个函数负责把 geoJSON 注册进 ECharts再渲染到指定容器。之所以强调“统一入口”是因为后面下钻和返回都要重新渲染地图如果每个地方各自写一套配置改样式的时候会想哭。核心代码逻辑如下let cachedChinaGeo null; const provinceAdcodeMap {}; const historyStack []; let currentLevel { type: china }; const myChart echarts.init(document.getElementById(map)); function renderMap(mapName, data) { myChart.setOption({ tooltip: { trigger: item }, visualMap: { min: 0, max: 100, text: [高, 低], inRange: { color: [#e0f3f8, #abd9e9, #74add1, #4575b4] } }, series: [{ type: map, map: mapName, roam: true, label: { show: true, fontSize: 10 }, data: data || [] }] }, true); } async function loadChinaMap() { const res await fetch( https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json ); const geoJson await res.json(); cachedChinaGeo geoJson; echarts.registerMap(china, geoJson); geoJson.features.forEach(function (f) { provinceAdcodeMap[f.properties.name] f.properties.adcode; }); renderMap(china, []); } loadChinaMap();这段代码里最关键的是provinceAdcodeMap的生成方式完全从 geoJSON 的properties里动态读取不写死任何省份名称。后续点击省份时直接用provinceAdcodeMap[params.name]就能拿到 adcode。setOption第二个参数true表示完全覆盖旧配置这是我返回全国时保证视图干净的最简单办法。如果你喜欢更精细的控制也可以用myChart.clear()后重新 setOption效果一样。3.2 点击省份加载对应的市级地图点击省份后的逻辑是点击某个省 → 拿到 adcode → 请求该省的_full.json→ 注册成新的地图 → 渲染省级市级视图。为了防止点击到地图空白区域或者已经处于省级视图时误触发我会先做两个判断myChart.on(click, async function (params) { if (params.seriesType ! map) return; if (currentLevel.type ! china) return; const name params.name; if (!name) return; const adcode provinceAdcodeMap[name]; if (!adcode) return; historyStack.push({ type: china }); currentLevel { type: province, name: name, adcode: adcode }; const res await fetch( https://geo.datav.aliyun.com/areas_v3/bound/ adcode _full.json ); const geoJson await res.json(); const mapKey province_ adcode; echarts.registerMap(mapKey, geoJson); renderMap(mapKey, []); });这里有几个细节值得注意。第一params.seriesType ! map的判断是为了防止页面上还叠加了别的系列比如散点、气泡点散点时也触发地图事件。第二currentLevel.type的判断保证你已经在省级视图时再点某个市不会继续往下钻——除非你想做第三层区县那就把这个判断改成入栈判断。第三mapKey 用province_ adcode而不是固定写一个名字原因是不同省份的 geoJSON 各自注册不会互相覆盖。如果你在省级视图上还想展示某个市的数据比如给广东的广州、深圳标数量只需要把renderMap(mapKey, [{ name: 广州市, value: 120 }, { name: 深圳市, value: 88 }])传进去。visualMap 会自动给不同 value 上色。3.3 返回全国不要只存一份 option我最早实现返回功能时想的很简单把进入省份前的myChart.getOption()存进数组返回时再 setOption 回去。结果发现 getOption 拿到的对象里包含很多 ECharts 内部引用下一次 setOption 很可能出现数据残留、地图不刷新或者 visualMap 错乱的问题。踩过一次后我改成存“状态”而不是存“静态配置”。上面的代码已经体现了这个思路historyStack里只存{ type: china }返回全国时重新走一遍loadChinaMap里的数据缓存和渲染逻辑。返回按钮的代码很简单async function backToChina() { if (historyStack.length 0) return; historyStack.pop(); currentLevel { type: china }; if (cachedChinaGeo) { echarts.registerMap(china, cachedChinaGeo); renderMap(china, []); } else { await loadChinaMap(); } }如果你只做两层下钻这个方案足够了。如果以后要支持省→市→区县三层只需要把 historyStack 里的状态从固定china改成{ type: province, mapKey: province_440000, adcode: 440000 }返回时根据栈顶状态恢复对应层级的 mapKey 即可。结构上完全一样扩展不需要推倒重来。4. 踩坑实录四个最容易让地图“白屏”的细节4.1 直辖市和海南点下去根本没有“市”这一层需求说“每个省对应的市”但中国地图里有几个特例——北京、上海、天津、重庆。你点击北京市拿到 adcode110000请求110000_full.json返回的会是北京各区的边界而不是“市级”地图。换句话说直辖市天然多了一层区县级数据。海南的情况类似。从 GeoAtlas 拉取460000_full.json后features 里除了海口、三亚、三沙还会出现儋州、五指山、文昌这类省直辖县级市。这些在行政级别上虽然算“市”但名称和普通地级市混在一起如果你拿它跟业务数据做匹配容易被名字坑到。我的处理建议是产品不要求区县钻取的话对四个直辖市做特殊判断点击时直接提示“该省级单位无市级下钻”。如果必须显示就单独加一层判断渲染区县数据。不要试图在统一逻辑里硬适配所有省级单位最后代码全是 if else反而难维护。4.2 click 事件在你不想触发的地方触发ECharts 地图的 click 事件触发范围比你想的要大。点地图空白区域params.name可能是空字符串或者 undefined点南海诸岛附近的小块区域也会触发 click。不处理这两个情况用户点击全国地图空白处就可能触发下钻请求而 adcode 拿不到页面没有任何反应用户会以为系统坏了。更隐蔽的是如果你在地图上叠加了scatter或者effectScatter作为数据散点点击散点时params.seriesType是scatter但你监听的还是 map 的事件结果一次点击同时触发散点逻辑和地图逻辑。所以我在点击事件开头统一加三道过滤if (params.seriesType ! map) return; if (params.name undefined || params.name ) return; if (currentLevel.type ! china) return;这三行代码写的当下你觉得多余但上线后能拦住绝大部分用户误触。4.3 name 对不上数据颜色全不上用 series-map 时data数组里的name必须和地图 geoJSON 中features的properties.name完全一致颜色才会上得去。这里有个非常常见的坑业务系统里省份叫“广东”城市叫“广州”但 GeoAtlas 的 properties.name 是“广东省”“广州市”。如果你直接用业务数据名去匹配整张地图一片灰一个颜色都不显示而且还不报错。解决办法有两个方向。一是展示时用 formatter 去掉后缀只是显示层面的处理label: { show: true, formatter: function (params) { return params.name.replace(省, ).replace(市, ); } }二是数据匹配时从 geoJSON 动态生成一个“去掉后缀”的别名表。比如 GeoAtlas 里叫“广东省”业务数据里叫“广东”你可以在加载完 geoJSON 后遍历 features把name去掉“省/市/自治区/壮族/回族/维吾尔”等后缀得到一个映射关系。这样业务数据不改地图数据也能通用。4.4 地图切换后缩放中心和 zoom 乱掉全国图你可能会设置zoom: 1.2或者center: [104, 37]切到省级地图后如果不处理新地图可能仍然沿用上一个地图的 zoom 和 center结果就是你看到的是一块被放大的局部区域或者整个省只显示了一半。比较省心的做法是给 series 配置layoutCenter和layoutSizeseries: [{ type: map, map: mapKey, layoutCenter: [50%, 50%], layoutSize: 100%, roam: true }]配置了这两个属性后ECharts 会在每次切换地图时根据新地图的边界自动计算缩放让整个省正好居中显示在画布中央。实测下来这比手动算各省的 center 坐标靠谱得多尤其是你要做全国 30 多个省级单位下钻时手算坐标根本不现实。5. 从“能出图”到“好用”缓存、标记、底图叠加5.1 加载体验与数据缓存下钻交互最怕的是一边点击一边转圈。全国地图的 geoJSON 体积不算大但省级地图累计加载几十次后重复请求很容易拖慢体验。我建议在代码里维护一个简单的 Promise 缓存const geoCache new Map(); async function loadGeoJson(url) { if (geoCache.has(url)) { return geoCache.get(url); } const promise fetch(url).then(function (res) { return res.json(); }); geoCache.set(url, promise); return promise; }这样同一个 adcode 的地图数据第二次进入时直接从缓存读取几乎没有等待时间。同时建议点击省份后先显示一个轻量 loading 态数据加载完成再 renderMap视觉上不会出现“点了没反应”的错觉。5.2 给某些市标记数量需求里常出现“给某些市标记数量”的诉求。实现方式其实很简单拿到对应省的地图后给 series 的 data 传一个数组data: [ { name: 广州市, value: 120 }, { name: 深圳市, value: 88 }, { name: 佛山市, value: 66 } ]如果这些市在地图上没有正确上色大概率是 name 匹配问题回到 4.3 的别名表处理。如果你希望数量不仅能上色还能以散点形式飘在地图上可以再叠加一个scatter系列用经纬度定位视觉效果更明显。这里有一个小技巧GeoAtlas 的省级 geoJSON 里每个市的 feature 都自带properties.center字段正好是市的中心经纬度。做散点标记时直接读取它就能省去维护一份城市经纬度表的麻烦。5.3 要不要叠加高德或天地图瓦片很多大屏项目做到后面会发现纯 ECharts 矢量地图没有真实道路和地名视觉上“很空”于是想叠加高德地图瓦片。这里要明确一个事实ECharts 的 map 和 geo 组件本身不直接支持加载栅格瓦片。想叠加高德底图通常不是纯 ECharts 能解决的方案。我更推荐两种做法。第一种是地图底图交给 Leaflet 或高德 JS API 加载瓦片图层上再用 ECharts 做数据层。这种方案底图效果最好道路、POI 全都有但需要额外引入地图库项目复杂度会上去。第二种是保持 ECharts 矢量边界同时把高德/天地图的行政边界数据转成 GeoJSON 后重新 registerMap。高德的 DistrictSearch 可以拿边界坐标但需要自己组装 GeoJSON。如果你只是想换个更精细的行政边界这个方案可行想看到道路级底图就不太够。我个人的经验是大屏场景里矢量边界反而更干净叠加高德瓦片后视觉噪音很大信息密度一高反而看不清。如果甲方明确要真实底图再考虑引入 Leaflet 方案别一开始就往复杂里做。5.4 3D 地图是下一步可选方向如果你做完 2D 下钻后还想炫一点可以试试 echarts-gl 的map3D。配合geo3Dscatter3D可以把省级地图做成 3D 柱状图效果每个市立一根柱子数值越高柱子越高。但 3D 地图的视觉成本也很明显交互手感比 2D 生硬数据量大时性能下降明显而且甲方看久了容易疲劳。我的建议是先用好 2D 下钻把数据准确性、切换流畅度这些基础体验打磨扎实3D 作为大屏加分项放在最后再考虑。如果你正准备做省市级地图下钻我最大的建议是动手写代码之前先把全国和几个典型省份的 geoJSON 下载下来打印一遍 features 里的 name 和 adcode对着数据理清层级的对应关系。这一步花不了十分钟却能让你避开后面至少一半的坑。代码层面尽量把所有渲染逻辑收敛到一个renderMap函数里状态切换用 historyStack 管理不要在每个点击事件里散落各种 setOption。这样以后无论是扩展到区县第三层还是把数据源换成自己的静态 json改动成本都很小。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。