简介这是一份面向 Web 前端开发者与数据分析师的 eCharts 地图资源包目标是在中国县级行政区划层面实现可视化展示与交互操作。资源提供 344 个县区的地图数据并围绕两种常见集成方式给出可运行的 demo其一是直接引用内置预置地图适合快速搭建其二是通过外部 JSON 文件加载自定义县区边界信息适合需要定制行政区划或更新边界的项目包内 JSON 已整理为可直接配套 ECharts 使用的格式省去手动拼接行政区划边界坐标的步骤。两种方式均覆盖 ECharts 地图组件从初始化、数据绑定、渲染到提示框与区域高亮等交互配置的完整过程示例中还包含 344 个县区与中国基础地图的对应关系便于核对区域编码与展示层级也可直接在此基础上扩展缩放、拖拽等地图交互。压缩包共 391 个文件以 380 个 JSON 地图数据文件为主体另有 6 个 JS 脚本负责加载地图与构建图表、2 个 HTML 页面作为示例入口、1 份 docx 文档说明常见的地图数据加载问题整体仅有 2.82MB轻量方便。资源目前已有 1552 人学习/下载适合需要快速上手 eCharts 县区地图的入门与中级开发者也可作为区域经济、人口统计、疫情分布等场景的可视化参考。1. 拿到 344 个县区的 eCharts 地图资源先别急着 setOption当初要在一张大屏里展示某省 344 个县区的业务指标我写好 setOption 后刷新页面结果地图区域一片空白只剩网格底图和缩放按钮。排查了两小时最后发现是 GeoJSON 里properties.name和业务数据的 name 对不上——这类问题在 eCharts 地图可视化里太常见了。这份资源解决的正是这个麻烦它包含 344 个县区的边界数据并配了两种可运行 demo一种是 script 标签静态引入一种是 fetch 动态加载后注册渲染。适合做区县数据大屏、地图下钻和离线展示的前端开发者也适合想搞懂 registerMap 工作原理的初学者。拿到手后我把两个 demo 都跑了一遍又拆了数据结构和源码下面把运行步骤、目录说明和踩过的坑整理出来。2. 资源结构拆解一份 GeoJSON 和两条渲染路径解压这份资源会看到一个典型的「数据 两个 demo」布局。最值钱的部分是map-data/county344.json它是 344 个县区边界的标准 GeoJSON 文件整个 eCharts 地图的所有区域轮廓都来自它。页面上的每一条边界线、每一次缩放看到的细节都是从这份坐标数据里算出来的。2.1 目录里每个文件的职责路径作用map-data/county344.json344 个县区边界的 GeoJSON 数据FeatureCollection格式map-data/county344.js把同一份 GeoJSON 封装成全局变量的脚本供静态 demo 用 script 标签引入demo1-static/index.html方式一完整页面静态引入 GeoJSON 后渲染demo2-dynamic/index.html方式二完整页面fetch 请求 JSON 后注册渲染data/biz-data.json示例业务数据演示 name 和 value 如何与地图关联README.md运行说明、字段说明和常见问题map-data下有两个文件内容其实都是同一份边界数据只是承载形式不同。county344.json是纯粹的 JSON方便 fetch 和接口中转county344.js里写的是window.COUNTY344_GEO {...}把数据挂到全局变量上因为 script 标签没法直接消费一个.json文件只能用 JS 文件的方式注入。后面跑 demo1 时你会看到页面先加载这个 JS再从全局变量里取数据。这里要先建立两个概念地图边界数据是「原料」业务数据是「配料」。344 个县区的区域轮廓由原料决定每个区域显示什么颜色、tooltip 出什么内容由配料决定。两份数据靠县区名字段撮合名字对不上渲染出来就是白屏或漏区域。2.2 registerMap 的工作原理eCharts 自定义地图的核心入口是一行代码echarts.registerMap(county344, geoJson);这行代码做的事可以拆成三步解析 GeoJSON 里的features数组把每个 Feature 的 geometry 坐标换算成 canvas 绘制用的路径再以county344这个 mapName 为 key 存入内部地图存储。之后无论geo组件还是series里的map类型只要引用同一个 mapName就能拿到这份边界数据。GeoJSON 的关键结构长这样{ type: FeatureCollection, features: [ { type: Feature, properties: { name: 示例县A, adcode: 110101 }, geometry: { type: MultiPolygon, coordinates: [[[[10.1, 10.2], [10.3, 10.2], [10.3, 10.4]]]] } } ] }注意看properties里的name字段它是渲染时匹配业务数据的唯一凭证。上面的坐标是我编的占位值真实文件里是 344 个 Feature每个县区一组经纬度闭合环。adcode一般是区划代码做下钻、联动或请求下级数据时会用到。如果你拿到的 GeoJSON 里没有 adcode 也没关系只做展示的话有 name 就够。2.3 静态引入和动态加载怎么选对比项静态引入demo1动态加载demo2数据获取方式script 标签同步加载 JS数据挂全局变量fetch 异步请求 JSON 文件首屏行为HTML 加载时数据已经就位多一次网络请求可先显示 loading数据更新改 JS 文件后刷新页面重新 fetch配合接口可以随时换适用场景内网离线、数据基本不变数据频繁更新、需要对接后端如果你在内网离线环境部署或者边界数据一年半载不变静态引入最省事少一次请求少一个出错点。如果业务数据随时在变甚至要按条件切换显示区域就用动态加载。两份 demo 的渲染链路完全一致区别只在「数据什么时候进入 registerMap」。我一般会先用 demo1 验证地图能渲染、数据能对上再根据实际部署环境决定要不要改成 fetch 方式。这个顺序能帮你把「地图问题」和「接口问题」分开排查。3. Demo 一静态引入把 344 个县区边界直接挂到页面方式一的核心思路是用 script 标签把county344.js引进来GeoJSON 数据作为一个全局变量存在页面加载完成就能直接注册渲染。适合离线演示、内网部署和快速验证数据。3.1 完整可运行的 index.html!DOCTYPE html html langzh-CN head meta charsetUTF-8 titleeCharts 344县区地图 - 静态引入/title !-- 1. 引入 eCharts 核心库 -- script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script !-- 2. 引入本地边界数据脚本内部挂到 window.COUNTY344_GEO -- script src../map-data/county344.js/script /head body !-- 3. 地图容器必须有宽度和高度 -- div idmap-container stylewidth: 1000px; height: 700px;/div script // 4. 从全局变量取出 GeoJSON注册为名为 county344 的地图 const geoJson window.COUNTY344_GEO; echarts.registerMap(county344, geoJson); // 5. 初始化图表实例 const chart echarts.init(document.getElementById(map-container)); // 6. 业务数据name 必须与 GeoJSON 的 properties.name 一致 const bizData [ { name: 示例县A, value: 231 }, { name: 示例县B, value: 87 } // 真实场景这里补齐 344 条 ]; // 7. 配置项 const option { tooltip: { trigger: item, formatter: {b}: {c} }, visualMap: { min: 0, max: 300, left: 20, bottom: 20, inRange: { color: [#e0f3f8, #abd9e9, #2c7bb6] } }, geo: { map: county344, roam: true, itemStyle: { areaColor: #e0e0e0, borderColor: #ffffff, borderWidth: 0.5 }, emphasis: { itemStyle: { areaColor: #ffd666 }, label: { show: true } } }, series: [{ type: map, map: county344, geoIndex: 0, data: bizData }] }; chart.setOption(option); // 8. 窗口尺寸变化时自适应 window.addEventListener(resize, function () { chart.resize(); }); /script /body /html这段代码里值得注意的点有几个。geo组件负责地图的通用样式、缩放和拖拽series里的 map 类型负责绑定业务数据两者通过geoIndex: 0建立关联。业务数据在 series.data 里而区域样式在 geo 里拆开配的好处是后续只改 data 不用动样式。3.2 从全局变量到地图渲染的完整链路执行顺序是这样的浏览器先加载 eCharts 核心库再加载 county344.js此时window.COUNTY344_GEO已经就位。然后 registerMap 把 344 个县区的坐标解析进内部存储等 eCharts 初始化容器并 setOption 时geo 组件按 mapName 找到边界数据开始绘制series 再按 name 字段把 value 逐个映射到区域上。如果这一步没出图优先检查两处容器 div 是否有明确宽度高度以及 registerMap 是否在 setOption 之前执行。容器宽高缺失会导致 eCharts 渲染出零尺寸画布这在第一次写地图时很容易忽略。roam 参数决定用户能不能缩放拖拽设为 true 后鼠标滚轮缩放和拖拽平移默认开启。visualMap会根据 value 值给区域着色值小偏浅蓝值大偏深蓝视觉上能直接看出各县区指标差异。3.3 业务数据如何对到 344 个县区上不管你数据来自接口还是本地文件最终都要转成[{ name: xxx, value: 123 }]的结构。最常见的做法是把接口返回的列表 map 一遍// 假设接口返回 [{ countyName: 示例县A, count: 231 }, ...] const rows await fetch(./api/stats).then(res res.json()); const bizData rows.map(row ({ name: row.countyName, value: row.count }));这里最大的坑是 name 对不上。业务库里可能叫「示例县A区」GeoJSON 里叫「示例县A」哪怕多一个字这个县区也不会渲染数据表现为区域显示默认灰色、tooltip 没有内容。遇到这种情况我一般先打印一遍 GeoJSON 里的全部 nameconsole.log(geoJson.features.map(f f.properties.name));再和业务数据的 name 做 diff就知道差在哪些县区了。也可以维护一个映射表把业务名映射成地图名代码层面解决字段不一致的问题。还有一类遗漏某个县区没有业务值如果你直接跳过它这个区域会不显示数据。真正稳妥的做法是给所有 344 个县区都补一条{ name: 某县, value: 0 }保证每个区域都有对应的数据项。4. Demo 二动态加载fetch 拿到 GeoJSON 再注册渲染方式二把边界数据的加载从 script 标签换成了 fetch 请求。页面启动时先显示 loading拿到 JSON 后再注册地图、填充数据。这个模式更接近真实生产环境——边界数据放在服务器上业务数据从接口取两者互不阻塞。4.1 完整可运行的 index.html!DOCTYPE html html langzh-CN head meta charsetUTF-8 titleeCharts 344县区地图 - 动态加载/title script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script /head body div idmap-container stylewidth: 1000px; height: 700px;/div script async function initMap() { const container document.getElementById(map-container); const chart echarts.init(container); // 1. 显示加载提示防止用户面对空白页 chart.showLoading({ text: 地图数据加载中... }); try { // 2. 请求边界数据注意 file:// 协议会被浏览器拦截跨域 const res await fetch(./data/county344.json); if (!res.ok) { throw new Error(GeoJSON 请求失败HTTP 状态码: res.status); } const geoJson await res.json(); // 3. 重复注册前先注销避免同名地图残留旧边界数据 echarts.unregister(county344); echarts.registerMap(county344, geoJson); chart.hideLoading(); // 4. 业务数据可以来自另一个接口 const bizData [ { name: 示例县A, value: 231 }, { name: 示例县B, value: 87 } ]; // 5. 渲染配置和方式一基本一致 chart.setOption({ tooltip: { trigger: item, formatter: {b}: {c} }, geo: { map: county344, roam: true, itemStyle: { areaColor: #f5e6ca, borderColor: #ffffff, borderWidth: 0.5 } }, series: [{ type: map, map: county344, geoIndex: 0, data: bizData }] }); } catch (err) { chart.hideLoading(); // 失败时把错误渲染到页面方便定位是网络问题还是解析问题 container.innerHTML p stylecolor: #d00; padding: 20px;地图加载失败: err.message /p; } } initMap(); /script /body /html这段代码比方式一多了一层异步逻辑。fetch先拿响应对象res.ok检查 HTTP 状态res.json()解析成 JS 对象再进入 registerMap。一旦 fetch 失败、JSON 格式不对或接口返回 404异常会被 catch 接住错误信息直接显示在容器里不用开控制台就能定位。4.2 从 fetch 到 registerMap 的完整链路执行顺序是页面加载完成了才发起 fetch 请求。此时 eCharts 实例已经创建loading 动画先顶上。请求回来后先 unregister 掉旧的 county344 地图再注册新的最后 setOption。为什么强调 unregister因为 registerMap 同名注册不会自动覆盖历史数据同一个 mapName 注册两次可能导致边界新旧交错的奇怪效果尤其是你切换了不同版本的 GeoJSON 时。有一个很容易踩的坑直接双击 index.html 用 file:// 协议打开浏览器会拦截 fetch 请求控制台报跨域错误。这不是代码的问题是浏览器的安全机制。解决方法是起一个本地静态服务这是我跑这类 demo 的标准操作cd 项目根目录 python3 -m http.server 8080然后浏览器访问http://localhost:8080/demo2-dynamic/。没有 Python 的也可以用npx serve或任意静态服务器工具。只要是 http 协议打开fetch 就能正常工作。4.3 两种方式的运行时差异动态加载比静态引入多了一次网络往返但换来的是数据可更新、可缓存、可按需加载。边界数据如果很大几百个县区的经纬度全部打进 HTML 会让首屏明显变慢拆成独立 JSON 文件后浏览器可以缓存它后续刷新页面甚至不需要重新下载。从代码维护角度看动态加载把「地图数据」和「页面逻辑」完全解耦。你可以只替换 JSON 文件得到一份新的边界范围也可以把 fetch 改成 axiosurl 指向后端接口由服务端决定返回哪几个县的边界。两种模式的数据流不一样但 registerMap setOption 的核心链路完全一致。提示demo2 的 fetch 请求在 file:// 下会被跨域拦截务必用本地静态服务打开这一点是静态引入方式不需要关心的。5. 常见问题和避坑记录地图跑不起来的五个原因第一次跑这两个 demo 时我翻车了好几次每次都是玄学一样的怪现象查到最后全是基础问题。下面按数据类问题和运行类问题分成两组把排查思路写清楚。5.1 数据映射类问题现象一地图渲染出来了但所有区域都是默认灰色tooltip 没有任何内容。原因几乎都是 series.data 里的 name 和 GeoJSON 的 properties.name 对不上eCharts 匹配不到数据项就把区域按无数据处理。解决方法是先打印geoJson.features.map(f f.properties.name)和业务数据做一次全量 diff再把不一致的字段用映射表修正。多一个空格、多一个「区」字都会匹配失败别看它们是汉字就觉得不会错。现象二能出图但县区边界和真实地理位置明显错位或者整块区域漂到海里。这类问题通常是坐标系不一致GeoJSON 用的是 GCJ-02而 eCharts 默认按 WGS-84 经纬度理解。解决方法是换用坐标系正确的数据源或者用地图工具做坐标纠偏千万别在渲染层强行偏移 X 像素那只是视觉自欺欺人。验证坐标系最直观的办法是拿几个周边县区的相对位置和真实地图对照一下。现象三地图有轮廓线但内部没有填充色只有描边。这个现象容易误判成样式问题其实根因是 series 的 map 类型没有正确挂到 geo 组件上。检查配置里 series 是否写了geoIndex: 0如果没有series 会自己尝试再画一层地图和 geo 组件出现叠边或填充异常。或者你也可以不用 geo直接把样式写在 series 的 itemStyle 里两条路都能走通别混用产生歧义。5.2 运行与渲染类问题现象四页面卡顿明显滚轮缩放像掉帧一样。344 个县区的路径加起来顶点数非常多如果 GeoJSON 没有抽稀渲染性能会很差。常见做法有两种一是对 GeoJSON 做顶点抽稀用 simplify 算法把边界精度降到缩放可接受的程度二是打开 eCharts 的渐进渲染让浏览器分帧绘制。我自己更倾向第一种把数据量直接降下来一劳永逸。另外要确认容器尺寸固定resize 频繁触发也可能导致不必要的重绘。现象五本地双击 HTML 文件直接打开demo2 的 fetch 请求报 CORS 错误地图一直 loading。这是 file:// 协议下的浏览器安全限制不是代码问题。解决方式是起本地静态服务前面已经写了python3 -m http.server 8080跑起来后用 http 访问。如果这个请求被部署到 Nginx 下还要确认服务器给 JSON 文件返回了正确的 Content-Type否则res.json()解析阶段可能报错。6. 从 demo 到二次开发点击下钻、热力着色和图层叠加把两个 demo 跑通之后最常见的改造方向有三个。第一个是点击下钻把某个县区边界替换成它下辖的乡镇边界。实现思路是监听 chart 的 click 事件用 params.name 拿到县区名然后请求下一级的 GeoJSON重新 registerMap 再 setOptionchart.on(click, function (params) { if (!params.name) return; // 请求这个县区下辖乡镇的边界数据 fetch(./api/sub-region/ encodeURIComponent(params.name)) .then(res res.json()) .then(geoJson { // 先换边界再换业务数据 echarts.unregister(county344); echarts.registerMap(county344, geoJson); chart.setOption({ series: [{ type: map, map: county344, geoIndex: 0, data: nextLevelData }] }); }); });第二个是改成热力效果。你已经在 demo 里见过 visualMap 的配置了只要把业务数据的 value 范围设好visualMap 会自动按数值分档上色。如果想让颜色更明显可以改 inRange 里的 color 数组三段渐变比两段渐变过渡自然很多。第三个是叠加散点图层。在 series 数组里加一个 scatter 类型用每个县区的中心经纬度做坐标就能把学校、医院、门店之类的点位叠到地图上。散点数据的经纬度可以从 GeoJSON 的 geometry 里算出来用 turf 库的 center 方法最省事。每次改完我习惯先用一条 mock 数据验证再换真实数据。验证步骤是固定的三行打印 geoJson.features.length 确认边界数量打印 features[0].properties 确认 name 字段再点击两个县区确认下钻逻辑没有串数据。从那以后我每次拿到任何地图数据都会先强制走一遍这三步再开始写 setOption。这个习惯救了我很多次希望帮到你。本文还有配套的精品资源点击获取