资讯动态

ECharts实现世界地图:从GeoJSON注册到大屏适配全攻略

发布时间:2026/10/5 5:28:38 来源:尧图企业网站定制
上个月接了个海外业务看板的需求甲方开口就要“世界地图上标出仓储节点、颜色区分销量、还要能缩放拖拽”。我当时想这活儿ECharts不是分分钟搞定结果真正动手才发现ECharts从5.x开始不再内置地图数据想画一张世界地图光找GeoJSON、注册地图、校准坐标就得折腾半天。这篇文章就把我做“ECharts 实现世界地图”这套方案踩过的坑、验证过的做法完整写出来给正准备做类似数据可视化大屏、网络热词里那种“原生js/jquery/ajax/echarts结合制作网页”的人当一份参考。内容包括三块先讲清楚世界地图方案怎么选型、GeoJSON数据去哪找再拆解地图注册、标点坐标、视觉映射这些核心细节最后给一个能直接跑通的完整示例以及大屏适配和常见问题的排查实录。适合已经会点ECharts基础、但第一次做世界地图/中国地图的读者或者正在做数据大屏、想快速地图落地的前端开发。1. 世界地图项目整体设计与方案选型1.1 为什么这类大屏场景的地图用 ECharts 就够很多人在做世界地图时第一个纠结是“要不要上 Cesium / Mapbox / Leaflet”。我的结论很直接如果你的核心需求是看数据、看分布、看趋势而不是做地图交互、路径规划、地图编辑那ECharts完全够用而且成本低得多。ECharts 的本质是图表库地图只是它的一种图表形态。它的优势在于和饼图、柱状图、折线图、漏斗图共用一套配置体系做混合视图非常方便。你在世界地图旁边放一个“大洲销量占比饼图”配置方式和普通页面完全一致不用切技术栈。渲染性能在千级数据点以内完全没有压力。对于业务看板来说地图上需要展示的点一般就几个到几十个ECharts的canvas渲染足够。规模可控。不需要引入复杂的地图引擎和底图服务不会出现瓦片加载失败、地图服务商限流的问题。所以技术选型不是越重越好而是匹配业务诉求。如果客户只是要在世界地图上打几个点、染几个色用Mapbox纯粹是给自己找工作量。1.2 世界地图数据从哪来GeoJSON 才是地基ECharts 从 5.0 开始不再内置任何地图数据。你要画世界地图首先得有一份世界地图的 GeoJSON 数据文件然后用echarts.registerMap注册进去。GeoJSON 是一种用 JSON 描述地理空间数据的格式。它里面存的是一堆“面”的坐标每个面代表一个国家或地区比如{ type: FeatureCollection, features: [ { type: Feature, properties: { name: China }, geometry: { type: MultiPolygon, coordinates: [...] } } ] }ECharts 拿到这份数据后会根据properties.name来识别每个区域你在series.data里传入的name必须和它对应才能完成数据挂载。找世界地图 GeoJSON我常用的几个渠道阿里云 DataV GeoAtlas直接访问 DataV.GeoAtlas可以下载全国、各省市、以及世界地图的GeoJSON接口是https://geo.datav.aliyun.com/areas_v3/bound/世界.json这种格式缺点是分区粒度不一定适合所有场景而且地图边界以国内标准为准。ECharts 官方 GitHub仓库apache/echarts的旧版map目录里或者社区维护的echarts-maps项目有整理好的world.json用起来很省事。Natural Earth这是个开源地理数据项目提供全球尺度的国界、省界等矢量数据可以下载后自己转换成GeoJSON精度可控。第三方 npm 包比如world-geojson、surbowl/world-geography这类包装完直接import world from world-geojson适合在构建项目里用。需要注意一点不同来源的GeoJSON区域名称可能不同有的国家name字段是中文有的是英文有的用ISO代码。后面做数据映射时这些细微差别会坑你一次。1.3 两种“画地图”方式geo 组件和 map 系列ECharts 里画世界地图有两个容易混淆的概念geo组件和map系列。很多新手上来就只用一个结果需求一变就不知道怎么改。我在项目里是这样区分和使用的geo 组件可以理解为一个“地图底图容器”。它本身不控制数据怎么展示只负责把地图画出来支持roam缩放拖拽、itemStyle底图样式。最重要的是scatter散点、effectScatter涟漪散点、lines航线这些系列都可以指定coordinateSystem: geo把geo当作坐标系来用。也就是说你想在地图上打点、画线路用geo是最稳的。map 系列则是把地图当作一种“图表类型”来用它更适合做区域填色choropleth也就是根据每个国家的指标值把国家染成深浅不同的颜色。它可以配合visualMap组件做数据到颜色的映射。实际项目中两者常常组合使用。一种推荐的结构是option { geo: { map: world, roam: true, itemStyle: { areaColor: #1a2b4c } }, series: [ { name: 区域销量, type: map, geoIndex: 0, data: [ /* 国家名称和值的数组 */ ] }, { name: 仓储节点, type: effectScatter, coordinateSystem: geo, data: [ /* 经纬度和数值的数组 */ ] } ] };这里用geoIndex: 0表示map系列直接复用第一个geo组件作为底图这样地图底图只绘制一次性能更好也让散点、航线和区域填色共用同一个地图不会出现“两张地图错位”的问题。2. 核心细节解析与实操要点2.1 地图注册的常见写法与版本坑ECharts 注册地图的API是echarts.registerMap写法如下import * as echarts from echarts; import world from ./world.json; echarts.registerMap(world, world);几类最常见的坑我在项目里都踩过第一地图数据没加载完就注册。如果通过Ajax异步加载GeoJSON一定在success回调里先registerMap再setOption否则会出现“地图空白 控制台报错Map world not exists”。用原生js jquery ajax的老套路时这个问题非常容易出。第二地图区域中文名对不上。如果你的GeoJSON里properties.name是英文而业务数据是中文那么地图会全部显示灰色。解决办法有两个一是转换数据里的name为英文二是用nameMap属性做映射option { geo: { map: world, nameMap: { China: 中国, United States of America: 美国 } } };第三数据字段名不一致。有些GeoJSON的name写在properties.NAME里有些写在properties.name里。注册前最好打印一下GeoJSON结构确认字段。如果字段不对可以考虑用一个小脚本把GeoJSON里的properties.name统一规范一下。2.2 标点位置经纬度 vs 像素谁在坑你世界地图上打点最常用的两种方案是markPoint和scatter/effectScatter。如果你用scatter/effectScatter数据格式是标准的三元组data: [ { name: 上海, value: [121.47, 31.23, 100] }, { name: 纽约, value: [-74.00, 40.71, 80] } ]value数组里前两个是经纬度第三个是数值比如销量、库存量。ECharts 会自动把经纬度通过投影换算成画布上的坐标只要coordinateSystem: geo配对了位置基本不会偏。而如果你用map系列里的markPoint写法上是类似的series: [{ type: map, map: world, markPoint: { data: [ { name: 上海, coord: [121.47, 31.23] } ] } }]这里的coord也可以用经纬度。但我的实际体验是在同时使用geoIndex复用底图的情况下markPoint的坐标稳定性不如专门的scatter系列尤其在ECharts 5的几个小版本里出现过坐标偏移的反馈。所以我的建议是地图上要标点优先用scatter/effectScattercoordinateSystem: geo这是一个独立系列职责清晰坐标匹配最稳。markPoint更适合在普通图表柱状图、折线图里标峰值、标平均线虽然它也能在地图上用但没必要和主逻辑混在一起。另外经纬度数据一定要真实不要拍脑袋。比如新加坡是[103.8, 1.35]澳大利亚悉尼是[151.2, -33.87]网上有在线的经纬度查询工具几秒钟就能查到。坐标写错一个符号点就跑到了大西洋里排查起来很浪费时间。2.3 visualMap 配色分段把数据变成看得懂的色块世界地图最常见的呈现方式是“各国按数值深浅着色”这项功能要配visualMap。ECharts 里的visualMap有两种模式continuous 连续型一个颜色渐变条适合看连续数值比如销量从0到10000渐变。piecewise 分段型分成几段每段一个颜色适合把数据分成“高、中、低”几档。我做世界地图大屏时更常用piecewise因为业务方通常关心的是“哪些国家是重点市场”而不是精确到个位数的销量。示例配置visualMap: { type: piecewise, min: 0, max: 10000, text: [高, 低], splitNumber: 5, inRange: { color: [#0b1a3a, #143d7a, #2a6fc9, #4fa3e8, #b7e4ff] }, textStyle: { color: #fff } }这里inRange.color定义数据从低到高的颜色序列数组第一个颜色表示低值越往后越高。splitNumber: 5表示分5段。实操中要注意min和max不要拍脑袋写死。如果接口返回的数据最小值是20最大值是3万你却写min: 0, max: 10000那所有国家都会被映射到最高档的颜色地图一片深蓝毫无区分度。比较好的做法是拿到数据后动态计算 min/max。当数据全是同一个值时颜色可能显示异常可以加一个minOpen/maxOpen设置或者把min/max稍微扩一下。2.4 交互与提示roam、tooltip、label 的体验细节世界地图要能用起来离不开交互细节。最重要的一个配置是roamgeo: { roam: true }roam设为true后用户可以滚轮缩放、拖拽平移。如果只允许缩放不允许拖拽可以设roam: scale只允许拖拽就设roam: move。在大屏上我通常设成true让演示的人可以自由聚焦到某个具体区域。tooltip 是查看数据的关键入口要记得开启并格式化tooltip: { trigger: item, formatter: function(params) { if (params.seriesType effectScatter) { return params.name br/库存量 params.value[2]; } return params.name br/销量 (params.value || -); } }这段代码里我做了个小小分支因为地图上的区域填色数据格式和散点数据格式不一样散点的params.value是数组国家填色的params.value是数值。不区分的话tooltip会显示出一个长数组用户根本看不懂。label 的显示也需要谨慎。世界地图上的国家数量多如果全都显示名称标签会叠成一团根本没法看。我的习惯是默认不显示label只在鼠标悬浮时通过emphasis显示geo: { label: { show: false }, emphasis: { label: { show: true }, itemStyle: { areaColor: #4fa3e8 } } }这样地图干净交互时又有反馈。3. 实操过程与核心环节实现3.1 从零搭建一个可用的世界地图页面我这里用一个最经典的“原生js jquery ajax echarts”组合来写这也是很多老项目和现成网页模板里常见的做法。整个页面不需要构建工具纯静态就能跑。首先是HTML结构一个占满全屏的容器!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleECharts 世界地图示例/title script srchttps://cdn.jsdelivr.net/npm/jquery3.7.1/dist/jquery.min.js/script script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script style html, body { margin: 0; padding: 0; width: 100%; height: 100%; background: #0b1a3a; } #map-container { width: 100%; height: 100%; } /style /head body div idmap-container/div script // 1. 初始化图表实例 var chart echarts.init(document.getElementById(map-container)); // 2. 用 ajax 加载世界地图 GeoJSON这里替换成你自己可访问的 json 路径 $.getJSON(./world.json, function(worldJson) { // 3. 注册地图 echarts.registerMap(world, worldJson); // 4. 渲染配置 chart.setOption({ backgroundColor: #0b1a3a, tooltip: { trigger: item, formatter: function(params) { if (params.seriesType effectScatter) { return params.name br/库存量 params.value[2]; } return params.name br/销量 (params.value || -); } }, visualMap: { type: piecewise, min: 0, max: 5000, splitNumber: 5, text: [高, 低], inRange: { color: [#0b1a3a, #143d7a, #2a6fc9, #4fa3e8, #b7e4ff] }, textStyle: { color: #fff } }, geo: { map: world, roam: true, label: { show: false }, emphasis: { label: { show: true }, itemStyle: { areaColor: #4fa3e8 } }, itemStyle: { areaColor: #1a2b4c, borderColor: #4fa3e8, borderWidth: 0.5 } }, series: [ { name: 区域销量, type: map, geoIndex: 0, data: [ { name: China, value: 3200 }, { name: United States of America, value: 4800 }, { name: Australia, value: 900 }, // 更多国家数据... ] }, { name: 仓储节点, type: effectScatter, coordinateSystem: geo, rippleEffect: { brushType: stroke }, symbolSize: function(val) { return Math.max(6, Math.min(20, val[2] / 100)); }, data: [ { name: 上海, value: [121.47, 31.23, 500] }, { name: 新加坡, value: [103.8, 1.35, 800] }, { name: 纽约, value: [-74.0, 40.71, 900] }, { name: 悉尼, value: [151.2, -33.87, 400] } ], label: { show: true, formatter: {b}, color: #fff } } ] }); }); // 5. 窗口大小变化时自适应 $(window).on(resize, function() { chart.resize(); }); /script /body /html这段代码跑起来页面上会出现一张深蓝色底的世界地图每个国家根据data里的销量值显示不同深浅的蓝色四个仓储节点会发出涟漪效果鼠标移到国家上可以看到销量滚轮可以缩放拖动可以平移。整个过程没有依赖任何框架放进现成的html模板里就能用。3.2 接入真实业务数据Ajax 请求与数据映射上面的示例里国家和销量是写死的真实项目里数据肯定来自后端接口。假设后端返回的格式是[ { country: China, sales: 3200, warehouse: 上海, stock: 500 }, { country: USA, sales: 4800, warehouse: 纽约, stock: 900 } ]你需要做的是把接口数据转换成ECharts需要的结构。国家名称的转换是这里的核心难点因为接口里的USA和 GeoJSON 里的United States of America对不上。我的做法是维护一个映射对象var countryNameMap { USA: United States of America, UK: United Kingdom, Russia: Russia, Korea: South Korea };然后在$.ajax的success回调里统一转换$.ajax({ url: /api/business/overview, type: GET, dataType: json, success: function(list) { var mapData []; var scatterData []; var coordMap { 上海: [121.47, 31.23], 新加坡: [103.8, 1.35], 纽约: [-74.0, 40.71] }; list.forEach(function(item) { var mapName countryNameMap[item.country] || item.country; mapData.push({ name: mapName, value: item.sales }); if (coordMap[item.warehouse]) { scatterData.push({ name: item.warehouse, value: coordMap[item.warehouse].concat([item.stock]) }); } }); chart.setOption({ series: [ { type: map, geoIndex: 0, data: mapData }, { type: effectScatter, coordinateSystem: geo, data: scatterData } ] }); } });注意value: coordMap[item.warehouse].concat([item.stock])这样最终经纬度和库存量就拼成了一个三元组散点大小和tooltip里都能用上。如果接口返回的国家名跟GeoJSON对不上的太多就别一个个映射了直接让后端增加一个mapName字段返回来省心省力。3.3 大屏适配与高清导出缩放、resize 与截图世界地图通常出现在大屏里而大屏最常见的两个问题是分辨率适配和整体缩放。我这里给几个实测过很稳的方案。第一个是chart.resize()。所有响应式布局里容器尺寸变化时要调用它官方推荐在窗口resize事件里调用。但是直接绑定resize会导致频繁执行最好加上防抖var timer null; $(window).on(resize, function() { clearTimeout(timer); timer setTimeout(function() { chart.resize(); }, 100); });第二个是整体缩放。大屏经常是一块 1920x1080 的设计稿放在不同分辨率的屏幕上。很多人用rem或者百分比但地图里的文字大小、散点大小不一定跟着缩放。我习惯用一个外层的div包住地图再根据分辨率对整体做transform: scale#screen { width: 1920px; height: 1080px; transform-origin: top left; transform: scale(0.8); /* 动态计算 */ }var designWidth 1920; var designHeight 1080; function scaleScreen() { var scaleX window.innerWidth / designWidth; var scaleY window.innerHeight / designHeight; var scale Math.min(scaleX, scaleY); $(#screen).css(transform, scale( scale )); chart.resize(); } $(window).on(resize, scaleScreen); scaleScreen();第三个是高清导出。大屏上有时候需要“一键截图”汇报普通截图在大屏上看还好放到演示文档里就糊了。ECharts 提供了chart.getDataURL()可以设置像素比导出高清图片var url chart.getDataURL({ type: png, pixelRatio: 2, backgroundColor: #0b1a3a }); // 然后可以把 url 赋值给 a 标签的 download 属性触发下载pixelRatio: 2导出的图片在1080P屏幕上就是两倍清晰度投到4K大屏上也不虚。4. 常见问题与排查技巧实录4.1 地图白屏的几类原因白屏是地图项目里最打击人的问题。就我遇见的情况来看原因多半是以下三件事地图没有成功注册。如果在setOption里写了map: world但registerMap没执行成功控制台会报Map world not exists。检查办法是console.log打印一下拿到的GeoJSON确认它不是空对象、没有被后端包了一层{code: 0, data: ...}。GeoJSON 格式不对。有些后端会拿字符串返回JSON你直接传给registerMapECharts不认。要先JSON.parse一次。我见过直接把GeoJSON存到数据库再原样返回结果字段被转义的案例排查时要留意。CDN资源加载失败。用的是CDN地址但内网环境或网络策略会拦截。这种情况要提前把echarts.min.js和world.json下载到本地服务器不要依赖外网。我这里有个排查口诀先看网络再看注册最后看数据。网络里看GeoJSON是否加载成功注册里看registerMap的调用时机数据里看series.data的name是否能匹配。4.2 地图渲染了但全是灰色没填上颜色地图能显示说明底图没问题问题基本出在数据匹配上。我把常见的匹配错误整理成一张速查表现象原因解决办法个别国家灰色data里的name与该国家在GeoJSON里的name不一致查看GeoJSON里properties.name的实际值调整映射所有国家灰色data为空数组或visualMap没有配置打印series.data检查数据确认visualMap存在且字段正确只有个别国家有颜色数据只传了几个国家确认数据覆盖范围缺失国家本身无数据灰色是正常的颜色深浅不明显min/max设置过大或过小根据数据实际范围动态设置visualMap.min/max这里要特别提醒国家名称匹配是精确匹配不是模糊匹配。像China和CHINA、USA和United States of America这种ECharts不会自动识别。一定要在数据预处理阶段统一。4.3 标点乱跑或不显示标点类问题通常是坐标系和格式导致的。我整理了几个高频原因scatter系列忘了写coordinateSystem: geo。如果不写ECharts默认按直角坐标系处理经纬度就会被当成普通的x/y值画到左下方看起来就是“点跑到了奇怪的位置”。经纬度写反了。有的坐标是[纬度, 经度]有的数据源是[经度, 纬度]ECharts约定的是经度在前纬度在后。写反了一个正负号点就可能出现在海里。value格式不对。如果散点数据是{name: x, value: 123}这种纯数值ECharts没法从里面取出经纬度。应该传三元组[经度, 纬度, 数值]或者至少二元组[经度, 纬度]。如果项目里用了markPoint且出现乱跑我的建议是直接改成effectScatter。这不是绕弯子而是让职责更清晰地图上动态打点scatter系列才是正统方案。4.4 地图和饼图、折线图联动时的一些注意点大屏项目里世界地图通常不是孤岛。旁边会有饼图展示大洲占比下面会有折线图展示趋势这就离不开联动。最常见的是点击国家联动其他图表。ECharts 为地图绑定了click事件chart.on(click, function(params) { // 判断点击的是不是区域填色的 map 系列 if (params.seriesType map) { var countryName params.name; // 发起请求更新旁边的饼图、折线图 updatePieByCountry(countryName); updateLineByCountry(countryName); } });实现上要注意如果同时存在map系列和effectScatter系列点击散点也会触发click所以要在回调里判断params.seriesType避免拿散点的数据去请求国家详情。另外当你用chart.setOption更新其他图表时要留意notMerge参数。默认情况下的setOption是合并模式如果新数据里的系列名变了旧的系列可能会残留。我在联动场景里经常写pieChart.setOption({ series: [{ name: 大洲销量, data: pieData }] }, true);第二个参数传true表示不合并完全替换。这在数据维度变化时非常有用缺点是会丢掉一些之前配置的动画、状态权衡一下即可。如果你用了 echarts-gl 做3D饼图记得地图点击回调里的params.name依然拿得到但3D饼图的更新方式略有不同需要调用setOption后手动触发resize这个细节容易遗漏。写在最后的一些个人体会世界地图这个项目做下来我最大的感受是ECharts地图本身不难难的是数据准备和数据匹配。很多人卡住不是因为不会写配置项而是GeoJSON没找到、name对不上、坐标写反了这类脏活。所以我这次把数据来源、注册流程、匹配规则这些基础环节写得特别细就是希望后来的人能少走一点弯路。最后再分享一个我后来一直在用的小技巧如果项目里有多种地图世界、中国、省份来回切换可以把所有GeoJSON在应用启动时一次性都registerMap注册掉再在切换时只设置geo.map/series.map的值为对应的注册名。这样切换地图只做setOption的更新不需要重复注册和重复初始化的逻辑渲染速度也快很多。这个思路对做那种“一屏看全球、二屏看全国、三屏看省份”的穿透式大屏特别有用建议你下一次项目里直接试一试。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑