简介:面向需要构建全球数据可视化地图的前端开发工程师、数据分析师及大屏展示设计人员,这份压缩包提供了基于 ECharts 的完整世界地图 js 与 json 数据文件,可直接用于人口、GDP、疫情、贸易、航班航线等地理分布场景的快速展示与二次开发。内含 77 个文件,其中 39 个 js 脚本覆盖 ECharts 地图的实例化、交互配置、视觉映射与主题扩展等关键逻辑,38 个 json 文件则保存世界各国的 GeoJSON 边界坐标数据,二者配合即可实现从地图初始化、数据绑定到样式渲染的完整流程;压缩包整体仅 3.42MB,便于下载、分发与离线部署,在资源占用与运行性能上表现友好。已有 1635 人浏览学习,说明该资源在图表开发群体中获得了一定认可与检验。借助其中封装好的地图数据与配置示例,使用者无需自行处理复杂的 GeoJSON 边界坐标,也无需从零编写地图加载代码,即可快速搭建带 tooltip 提示、visualMap 颜色分级、缩放平移等交互效果的世界地图,并可按需调整国家名称与统计值,同时支持自定义主题样式与高亮标注,适合需要快速落地数据可视化项目的初中级开发者,也为后续进阶学习 ECharts 高级地图特性提供了可直接改造的基础模板。 做全球化业务大屏,世界地图几乎是个绕不开的组件。前阵子接了个海外业务分布需求,要用 ECharts 把各国订单量铺到世界地图上。我原以为这事儿很简单——引入 echarts.min.js,抄一段 map 配置就完事了。结果手动一跑,页面先是白屏,控制台甩出一行"Map not exists",等我把地图数据注册好、图表终于渲染出来了,又发现所有国家的颜色一模一样,完全看不出数据差异。
回过头看,用 ECharts 做世界地图本身不难,难的是那些文档里没写透的细节:地图 GeoJSON 从哪拿、怎么保证国家名和数据对得上、visualMap 为什么"失灵"、geo 和 series 里的 map 到底什么关系。这篇就把我实际跑通的完整链路拆开讲,从拿数据到出图,再到 echarts-gl 做 3D 球体,每一步都给你能直接抄作业的代码和思路。
适用读者:正在做全球数据可视化大屏、海外运营看板、或者单纯想把世界地图跑通的开发者。你不需要用过 ECharts,但最好有一点点 JavaScript 基础。
1. 先解决"没地图"的问题:世界地图数据的选型与清洗
1.1 三种常见的地图数据来源
ECharts 本身不携带任何地图数据,这是设计上的选择——地图属于静态矢量数据,体积不小,如果打进主包,首屏加载压力会明显变大。官方 4.x 时代还提供过 map/json 目录,5.x 开始就完全交给开发者自己准备了。所以"Map not exists"这个报错的根因,基本就是没把地图数据注册进去。
现在拿到世界地图 GeoJSON 的渠道主要有三个:
- DataV.GeoAtlas(阿里云 DataV 官方的地图数据选择器):可以按国家、省份下载 JSON 或 GeoJSON,数据格式规范,适合做中国地图和省市级地图。世界地图它也能给,不过国家名的字段是中文和英文都带,需要自己处理。
- ECharts 官方仓库以及社区维护的 map 数据仓库:很多开源项目会定期整理 world GeoJSON,字段通常保留了
name、id、adcode这些属性,命名风格比较接近 ECharts 的使用习惯,我实际用得最多。 - 第三方开源库(比如世界地图 GeoJSON 合集、Natural Earth 数据):数据精度普遍很高,多边形顶点多、边界细,但同样要自己写字段映射。
选型的时候记住一个原则:优先选带name字段的 GeoJSON,且该name和你后端返回的国家名口径能对齐,否则后面清洗数据会很痛苦。
1.2 GeoJSON 里到底有什么
GeoJSON 本身就是一个符合固定格式的 JSON 对象,最外层通常是FeatureCollection,里面有一个features数组,每个feature代表一个区域:
{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "name": "China", "adcode": "100000" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [[[经度, 纬度], [经度, 纬度]]] ] } } ] }ECharts 在注册地图时,会遍历每个feature,取properties.name(有些数据源是properties.NAME或者properties.NAME_CHN)作为该区域的名字键。后续你的series.data数组里,name字段就是用来和它做匹配的。匹配不上,数据就渲染不进对应的区域,位置就会被跳过,表现就是"地图渲染了但颜色全空"。
1.3 国名不一致的通用处理办法
这是世界地图项目里最容易踩的坑,比地图数据本身麻烦得多。真实业务里,后端接口返回的国家名可能是中文("中国"、"美国"),可能是 ISO 国家代码("CN"、"US"),可能是英文简称("USA"),而世界地图 GeoJSON 里的name往往是官方英文名("China"、"United States")。
我的做法是在拿到地图数据之后、注册之前,先做一层名称归一化。GeoJSON 是数组结构,洗数据非常方便:
// 假设从接口拿到的是中文国名,把地图里的 name 改成中文也可以 // 但更推荐:把接口数据映射成地图 name,这样不影响地图原始数据 // 我这里选择改地图,因为业务端已经约定按中文出数 const zhNameMap = { 'China': '中国', 'United States': '美国', 'United Kingdom': '英国', 'Russia': '俄罗斯' // 其它国家继续补 }; worldGeoJson.features.forEach(feature => { const enName = feature.properties.name; if (zhNameMap[enName]) { feature.properties.name = zhNameMap[enName]; } });这里要说明一下:直接修改GeoJSON注册前的properties.name是完全合法的,因为registerMap只对这个对象做一次读取和内部转换,改完再注册就行。我还会把这个映射表单独抽成一个countryNameMap.js文件,维护起来非常省事,大屏项目里常见的中英文对照、简称全称对照都能放进去。
2. 从零搭一个世界地图页面:registerMap 与基础配置
2.1 基础 HTML 骨架
环境准备没什么特殊的,一个<div>容器、一个 ECharts 的 JS 文件就够了。我用的是 CDN 方式,开发时方便,生产环境建议下载到本地或走公司的静态资源服务:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <title>世界地图数据可视化</title> <style> #map { width: 1000px; height: 600px; background: #0a1a2f; } </style> </head> <body> <div id="map"></div> <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script> <script src="./world.js"></script> <script src="./app.js"></script> </body> </html>一种常见的做法是把 GeoJSON 放进一个.js文件里,全局挂一个变量,比如world.js内部就是const worldGeoJson = { ... }。这样可以借助普通<script>标签的同步加载保证执行顺序:地图数据先就位,app.js里再registerMap就不会找不到数据。
2.2 registerMap 到底注册了什么
echarts.registerMap(mapName, geoJson)做的事情,简单说就是把一份 GeoJSON 变成 ECharts 内部可用的地图坐标系数据,并挂在一个自定义名字下。这个名字就是后续配置里map字段要引用的键:
// 先注册 echarts.registerMap('world', worldGeoJson); // 再使用 const chart = echarts.init(document.getElementById('map')); const option = { series: [{ type: 'map', map: 'world' }] }; chart.setOption(option);有个细节值得提醒:setOption时不能把完整的 GeoJSON 塞进配置项。网上有些老文章会在option.geo.map里直接写一个对象,这在旧版本可能能跑,5.x 下是不靠谱的,老老实实走registerMap最稳。
2.3 用 series.map 展示数据
这是最常规的出图方式,数据直接挂在series.data,每个数据项用name匹配国家,value决定 visualMap 的颜色档位:
const option = { tooltip: { trigger: 'item', formatter: function (params) { if (params.value) { return params.name + ':' + params.value.toLocaleString() + ' 条'; } return params.name + ':暂无数据'; } }, visualMap: { type: 'piecewise', pieces: [ { min: 10000, label: '10K 以上' }, { min: 5000, max: 9999, label: '5K - 9.9K' }, { min: 1000, max: 4999, label: '1K - 4.9K' }, { min: 1, max: 999, label: '1 - 999' } ], left: 20, bottom: 20, textStyle: { color: '#fff' } }, series: [{ type: 'map', map: 'world', roam: true, scaleLimit: { min: 1, max: 8 }, selectedMode: 'single', label: { show: false }, emphasis: { label: { show: true, color: '#fff' }, itemStyle: { areaColor: '#ff9f43' } }, itemStyle: { areaColor: '#1a3b5d', borderColor: '#4a90d9', borderWidth: 0.6 }, data: [ { name: 'China', value: 12800 }, { name: 'United States', value: 9800 }, { name: 'Japan', value: 3200 } ] }] }; chart.setOption(option);这里有个新手特别容易忽略的点:visualMap的min和max。如果你不显式设置,ECharts 会按当前数据的最小最大值自动计算映射区间。数据差异特别大时,比如最大值 10 万、最小值 5,min会被压到 5,max到 10 万,视觉上 5000 和 50000 可能都显示成同一个深色,看起来就像"没区分度"。用pieces分段就是人为控制区间,把档位打散,视觉效果会更直观。
2.4 geo 组件和 series.map 怎么选
很多人在这一步会纠结:地图到底写在geo里还是写在series里?它们都能渲染世界地图,但定位完全不同:
series.map本身就是"数据系列",它直接消费data里的值,配合visualMap自动上色;也支持label、emphasis、select这些交互,适合展示"每个国家一个数值"的场景。geo是一个坐标系组件,它只提供底图,不会自动消费业务数据。它的最大价值是作为其它系列的"位置容器",最常见的组合是geo + scatter/effectScatter,把点坐标画在地图上,比如标记重点国家的首都、客户分布点。
实际项目中我两种都用过,选型的判断标准很简单:如果颜色深浅代表某个指标,选series.map;如果重点在地图上的点和路线,选geo做底图,然后叠加scatter系列:
option = { geo: { map: 'world', roam: true, itemStyle: { areaColor: 'transparent', borderColor: '#5b9bd5' } }, series: [{ type: 'effectScatter', coordinateSystem: 'geo', data: [ { name: '北京', value: [116.4, 39.9, 100] }, { name: '伦敦', value: [-0.12, 51.5, 80] } ], symbolSize: function (val) { return Math.max(val[2] / 5, 4); } }] };scatter的data项里,value是一个数组,前两个元素是经纬度,第三个及以后可以放业务值。coordinateSystem: 'geo'是它和普通直角坐标系的区别所在。这种"地图打点"模式在大屏里出效果很快,交互也轻。
3. 让数据"说话":visualMap、缩放与图例的细节
3.1 连续型与分段型的取舍
visualMap有两种:continuous和piecewise。世界地图这个场景,只要数值跨度不是特别平滑,我基本都用piecewise。原因有两个:一是分段图例可以带文字标签,比如"高活跃区""中活跃区",领导看大屏时理解成本低;二是分段能硬性拉出颜色差异,不会出现上面说的"全图一个色"。
分段配置的坑是需要保证pieces覆盖数据全量。比如数据里有 0 值甚至负数,但你的pieces只定义了min: 1以上,那么 0 值国家就没有对应颜色档位,会显示成itemStyle.areaColor的底色。这不算报错,但视觉上很容易误判为"数据缺失"。我的习惯是在pieces里显式加上一档{ min: 0, max: 1, label: '暂未开通' },把边界情况吃掉。
连续型的优点在于数据落差小的场景比较细腻,但地图上 200 多个国家,连续渐变的图例在暗色大屏上其实挺考验配色。如果你确实要用,建议这样写:
visualMap: { type: 'continuous', min: 0, max: 10000, inRange: { color: ['#1a3b5d', '#4a90d9', '#ff9f43', '#ff5e5e'] } }3.2 地图缩放和默认视角控制
世界地图不像中国地图,区域跨度大,默认视角下欧洲国家挤成一团,label 一开更是糊到没法看。所以roam: true几乎是必开的,它同时开启缩放和平移。scaleLimit用来限制缩放范围,我一般设{ min: 1, max: 8 },避免用户放大到边界毛刺横飞的程度。
如果你希望页面初始就聚焦到某个区域,比如默认显示欧洲,可以算一下目标区域的中心经纬度,然后用center和zoom配合:
series: [{ type: 'map', map: 'world', center: [15, 50], // 大约欧洲区域 zoom: 2.5, roam: true }]center和zoom是 map 系列和 geo 组件都支持的属性,直接写在初始化配置里就能生效。需要注意,一旦用户手动拖拽过地图,center就不再是初始化值,如果你想做一个"回到默认视角"按钮,可以用dispatchAction:
chart.dispatchAction({ type: 'geoRoam', zoom: 2.5, center: [15, 50] });3.3 地图上的点击与图例交互
大屏项目里,点击某个国家往往要联动右侧详情面板。ECharts 对 map 系列的点击事件已经做得很完备,直接监听即可:
chart.on('click', function (params) { if (params.componentType === 'series' && params.seriesType === 'map') { // 拿到国家名和值 const countryName = params.name; const value = params.value; // 可以联动高亮 chart.dispatchAction({ type: 'downplay', seriesIndex: 0 }); chart.dispatchAction({ type: 'highlight', seriesIndex: 0, name: countryName }); } });dispatchAction这个 API 值得记住,它允许你在图表渲染后继续控制和切换状态。我经常用它配合一个全局的国家列表,鼠标移出列表时取消高亮,鼠标移入时重新高亮,达到"双侧联动"的效果。
4. 排错专题:世界地图最常见的五个翻车现场
4.1 页面白屏,控制台报 "Map world not exists"
这是所有 ECharts 地图新手的第一道坎。报错的字面意思就是:配置里map字段指向的名字,没有被注册过。大概率是以下三种情况之一:
- 忘了调用
echarts.registerMap('world', worldGeoJson)。 - 调用了,但
mapName和配置里的字符串不一致,比如注册时叫'world',配置里写成了'worldMap'。 registerMap执行时worldGeoJson是undefined,通常是加载异步数据和 JS 加载顺序没控制好。
排查时直接打印一下worldGeoJson的 features 长度,能迅速定位问题。fetch拿数据的话,记得在.then里再注册和setOption。
4.2 地图渲染出来了,但所有国家颜色完全一样
数据虽然渲染在地图上,但颜色没有按值分层,优先检查三处。第一,visualMap是否真的生效,确认是否漏写、或者seriesIndex指向了别的系列。第二,min/max区间是否合理地覆盖了数据分布,如果数据是 10000 到 12000 之间波动,min默认取的可能是 10000,max是 12000,映射之后全图都是同一个深色非常正常。第三,data里的name是否和地图里的name完全一致,一旦有偏差,那个国家的value不会报错,但整张图也出不了颜色。
我分享一个实用排查方法:先给visualMap写死一个很小的min: 0, max: 1,如果数据值都在 1 以上,理论上所有国家都会被映射到最大色。这一步能快速验证是不是"数据能对得上、但区间没调好"。
4.3 地图边界错乱、区域粘连或者 label 叠成一片
边界错乱通常不是 ECharts 配置问题,而是 GeoJSON 本身的精度或拓扑问题。不同来源的 GeoJSON 在边界处理上差异很大,有的为了压缩体积做了简化,放大到一定级别就会出现锯齿和毛边。解决方法是换一个更高精度的 GeoJSON,或者限制scaleLimit的最大缩放值,别让用户怼到太近。
label 叠成一片则是另一个问题。世界地图 200 多个国家,如果label.show一开始就开成true,小国之间文字互相遮挡几乎不可避免。我的习惯是平铺时show: false,只保留 hover 的emphasis.label.show。如果 ECharts 版本在 5.3 以上,还可以用labelLayout: { hideOverlap: true },让引擎自动隐藏重叠标签,效果也不错。
4.4 应用数据是中文,但地图 name 是英文
这个问题我在 1.3 节已经给过处理方案:维护一张中英文国名映射表,在地图注册前批量改properties.name。实战中还有一种是接口返回 ISO 代码,我同样用映射表转一遍,代码逻辑完全一样。重要的不是改哪个方向,而是所有数据源最后一次对齐时统一用哪个口径。建议在整个项目里只保留一种国家名表示方式传给 ECharts,其他表示方式在数据层提前转换,不要在渲染层到处改。
4.5 大屏在低端设备上掉帧明显
世界地图的 GeoJSON 本身数据量不算大(大约 200 多个 features),但如果叠加了effectScatter动画、3D 地图、多套visualMap和频繁的setOption,低端设备照样扛不住。我在实际项目里的优化手段有三板斧:
- 地图和动画系列的 GeoJSON 用静态文件加载,不走接口;
setOption更新数据时尽量只传series.data,不要整棵 option 重传;`- 3D 地图上关闭 label,或者只对重点区域开 label,减少 GPU 渲染压力。
5. 进阶向:用 echarts-gl 把世界地图做成 3D 球体
5.1 map3D 基础配置
当你不再满足于平面地图,想做出立体的 3D 地球效果,就需要引入echarts-gl了。这东西本质上是 ECharts 的 WebGL 扩展,支持map3D、scatter3D、bar3D等系列类型。第一次跑通map3D的配置长这样:
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/echarts-gl@2.0.9/dist/echarts-gl.min.js"></script>先把地图注册好,然后配置map3D系列:
const option = { backgroundColor: '#0a1a2f', tooltip: {}, visualMap: { type: 'piecewise', pieces: [ { min: 10000, label: '10K 以上' }, { min: 1000, max: 9999, label: '1K - 9.9K' }, { min: 1, max: 999, label: '1 - 999' } ], textStyle: { color: '#fff' } }, series: [{ type: 'map3D', map: 'world', regionHeight: 3, // 区域凸起高度 shading: 'lambert', // 光照模式 boxWidth: 100, // 整个球在容器中的尺寸 boxHeight: 80, environment: '#000000', // 环境色 itemStyle: { color: '#2a5caa', opacity: 0.9, borderWidth: 0.5, borderColor: '#00d4ff' }, label: { show: false }, data: [ { name: 'China', value: 12800 }, { name: 'United States', value: 9800 } ] }] };map3D和平面map的差异点很直观:它是 3D 球体,鼠标交互从"拖拽平移"变成了"拖拽旋转",首次坐标视角也完全不同,center和zoom的配置逻辑和平面图不一致,实际调试时多试几次就能找到手感。
5.2 map3D 与 scatter3D 的联动
做全球大屏的时候,我更喜欢把map3D当底图,用scatter3D或bar3D去做数据标记。比如只在重点国家上方打一根 3D 柱条,柱条高度代表订单量,视觉冲击力比平面图强很多:
series: [ { type: 'map3D', map: 'world', regionHeight: 2, shading: 'lambert', label: { show: false }, itemStyle: { color: '#1b2c4f', opacity: 1, borderWidth: 0.8, borderColor: '#3a6ea5' } }, { type: 'bar3D', coordinateSystem: 'geo3D', data: [ { name: 'China', value: [116.4, 39.9, 12800] }, { name: 'United States', value: [-77.0, 38.9, 9800] } ], barSize: 2, shading: 'lambert', itemStyle: { color: '#ff9f43' } } ]这里有个容易忽略的点:bar3D的坐标系统要写geo3D而不是geo,而且value数组前两位是经纬度,第三位才是高度值。如果配成geo,数据不会报错,但柱子根本不会出现在 3D 地图上。
5.3 什么时候不建议上 3D
虽然 3D 效果很炫,但我还是想劝退一部分场景:如果只是内部看板、数据变化频繁、需要快速看清数值差异,2D 平面地图反而更高效。map3D的旋转交互在长时间盯屏时容易让人疲劳,而且 WebGL 渲染在一些老设备上会有兼容性和性能问题。
我自己的经验法则是:3D 地图适合展厅、汇报、对外演示这种"看一眼就 wow"的场景;日常运营分析还是用 2D 平面,配一个清晰的visualMap分段图例,信息获取效率最高。
最后分享一个小技巧:做世界地图前,先把你业务里可能出现的所有国家名都列一遍,不管是中文、英文、还是 ISO 代码,都写进映射表里,再核对一遍地图 GeoJSON 里的 name。这一步看起来琐碎,但在后面省下的排查时间绝对不止一两个钟头。地图数据准备好了,剩下的 ECharts 配置真的只是时间问题。
本文还有配套的精品资源,点击获取