简介:面向需要在地理数据可视化中集成世界地图的前端开发者,这份ECharts世界地图JS资源提供了可直接引用的地图组件与配套脚本。无论是数据大屏、全球业务分析还是地理信息展示,都能借助它快速生成可交互的矢量地图。资源包含ECharts核心库、macarons等主题文件,以及完整的GeoJSON格式世界地图数据,覆盖各国家和地区边界坐标。压缩包共77个文件,由39个JavaScript脚本和38个JSON数据文件组成,整体约3.42MB。JS脚本负责图表渲染与交互逻辑,JSON文件存储地图边界与区域信息,结构清晰,便于按需引用或二次定制。目前已有1639人学习下载,适合前端工程师、数据分析师以及ECharts初学者参考。通过配套配置示例,可重点理解地图初始化、数据映射、tooltip提示、visualMap颜色分级等关键用法,缩减查阅文档和调试配置的时间,是搭建全球分布类图表的高效工具包。
1. echarts 世界地图 JS:为什么地图没显示,先检查数据不是画布
开发可视化大屏时,世界地图总是一个绕不开的底图需求。echarts 世界地图js 就是把一份带经纬度的 GeoJSON 数据注册进 echarts,再用series-map把业务数值映射到各个区域颜色上。很多初学者第一反映是“画布没初始化”,但我的血泪经验是:绝大多数“地图空白”“区域不显示”问题,根因都出在地图数据本身,而不是 echarts 的语法。echarts 5 之后不再内置world.js,需要自己找 GeoJSON、自己注册、自己处理区域名对齐,这一步成了整张地图能不能出来的分水岭。这份资源适合正在做全球业务分布、需要地图下钻、或者被区域命名折腾过的开发者,看完能直接落地。
2. 注册世界地图:如何把 GeoJSON 变成 echarts 认得的组件
2.1 为什么 echarts 5 之后必须自己准备 GeoJSON
在 echarts 5 之前,官方包里还有一份世界地图数据,直接require('echarts/map/js/world')就能注册。但从 5.x 开始,内置地图数据被移出核心包,官方理由也很直白:地图数据更新频率高、体积大,不该和图形库绑在一起。这就带来一个实际变化:任何用到世界地图的项目,都得自己找一份世界 GeoJSON,然后显式调用echarts.registerMap。
我一般会去开源数据社区找一份带国家边界的world.json,而不会用那种只有简笔轮廓的静态图片。区别在于:静态图片没法做 visualMap 填色,也没法绑定区域点击事件。GeoJSON 里面是一堆Feature,每个Feature的properties.name就是后续数据和它匹配的关键字段。我的习惯是拿到文件先不看渲染效果,先打开 JSON 检查其features数组长度和properties里有没有name字段。如果name字段是中文,而后端接口返回的也是中文,那后续就少一道翻译的工序;如果是英文,就要考虑做一张中英文映射表。
2.2 registerMap 注册与基础地图渲染
先看一段最基础的注册和渲染代码,这里我用虚拟区域名演示替换逻辑:
import * as echarts from 'echarts'; import worldGeoJSON from './world.json'; // 下载好的世界地图 GeoJSON // 第一步:全局注册地图,名称必须在 setOption 之前完成 echarts.registerMap('world', worldGeoJSON); // 第二步:初始化实例 const chartDom = document.getElementById('map-container'); const myChart = echarts.init(chartDom); // 第三步:基础配置 myChart.setOption({ series: [ { type: 'map', map: 'world', roam: true, selectedMode: 'single', data: [ { name: '北部区', value: 86 }, { name: '中部区', value: 120 }, { name: '南部区', value: 52 } ] } ] });这段代码里最关键的是registerMap('world', worldGeoJSON)。第一个参数是注册名,第二个参数是整个 GeoJSON 对象,不能传字符串路径。setOption里map: 'world'必须和注册名完全一致,大小写都不能错,否则 echarts 会认为地图不存在,画布上什么都不显示。data数组里的name要和 GeoJSON 中对应区域的properties.name匹配,匹配不上的区域就保持默认底色,也不会进入 visualMap 的颜色映射。
再强调一个容易忽略的点:registerMap只需要执行一次,但如果项目里用了多个图表实例,每个实例的series-map都能用同一个注册名。我遇到过一个项目,因为每次切换页面都重新加载一份地图数据,导致注册覆盖时出现样式闪烁。解决办法是先判断:echarts.getMap('world')如果已经存在,就不再重复注册。
2.3 地图数据合法性校验:避免把坏数据送进渲染层
拿到 GeoJSON 后我强烈建议先跑一遍校验,不要直接丢给registerMap。校验只看三点:features是否为空、features[0].geometry是否存在、properties.name类型是否为字符串。写一个简单的 Node 校验脚本就能挡住大多数问题:
const worldGeoJSON = require('./world.json'); const features = worldGeoJSON.features; if (!Array.isArray(features) || features.length === 0) { throw new Error('GeoJSON features 为空'); } const invalid = features.filter(f => { return !f.geometry || !f.properties || typeof f.properties.name !== 'string'; }); if (invalid.length > 0) { console.warn('存在不合法的 Feature 数量:', invalid.length); } else { console.log('校验通过,共', features.length, '个区域'); }注意这里校验的是“结构合法性”,不是“边界准确性”。结构合法只能保证地图能渲染出来,边界数据到底来自哪个机构、是否最新,那是另一层问题。涉及具体区域边界时,我建议核对数据来源是否权威,这是上线前必须确认的合规动作。
3. series-map 核心参数调优:visualMap、roam 与 label 的显示手感
3.1 series-map 和 geo 组件:两种世界地图实现方式的取舍
绘制世界地图时有两个常见方案:直接在series里写type: 'map',或者单独配置geo组件再关联 series。实际开发中两者可以互相配合,但它们的分工不一样。
| 方案 | 适合场景 | 注意点 |
|---|---|---|
| 纯 series-map | 页面只有一张地图,数据绑定简单 | visualMap 和 tooltip 配置相对集中 |
| geo 组件 + series-map | 需要地图做背景、再叠加散点或航线 | geo 的 roam 和缩放优先级高于 series |
我大多数情况下用geo + series-map的组合:geo管地图底层的缩放、拖拽、视野,series管业务数据的颜色映射。这样做的好处是,后续如果要叠加飞线图,不用复制一套地图配置。如果只是简单展示一张填色图,直接一个series-map就够了,两种方式我都用过,没有谁绝对好,看是否要叠加其他系列。
3.2 visualMap 的连续与分段:数据跨度大时该怎么选
visualMap 是地图填色的关键配置。数据从个位数到上万时,连续型 visualMap 很容易把大多数区域变成同一个浅色,失去区分度。此时应该考虑分段型。先看连续型的典型配置:
visualMap: { type: 'continuous', min: 0, max: 1000, calculable: true, inRange: { color: ['#e0f3f8', '#abd9e9', '#74add1', '#4575b4'] }, text: ['高', '低'], left: 'left' }min和max直接决定颜色映射区间。如果数据量级突然变大,比如最大值变成 10000,而max还写死成 1000,那超过 1000 的所有区域都会显示最深色。我一般不用写死的最大值,而是根据数据动态计算:max = Math.ceil(Math.max(...values) / 100) * 100,这样颜色分布不会被极端值压扁。
如果数据跨度特别大,或者产品要求看“高、中、低”三档,用分段型 visualMap 更直观:
visualMap: { type: 'piecewise', pieces: [ { min: 1000, label: '高密集' }, { min: 100, max: 999, label: '中密集' }, { min: 0, max: 99, label: '低密集' } ], inRange: { color: ['#d73027', '#fee08b', '#1a9850'] } }用pieces时,每段的边界要注意写清楚min和max,区间重叠会导致 echarts 取最后匹配的那一段。我习惯把每段写成闭合区间,例如{ min: 100, max: 999 },再用注释标注这是含 999 的闭区间。
3.3 roam、center 和 zoom:地图交互的默认手感
roam控制地图能否缩放和拖拽。它有几种取值:false禁止所有交互,true允许缩放和拖拽,'scale'只允许缩放、不允许拖拽,'move'只允许拖拽、不允许缩放。世界地图默认视野是整个地球,产品经常会要求“进来先聚焦到某个业务区域”,这时就要手动设置center和zoom:
series: [ { type: 'map', map: 'world', roam: true, center: [105, 35], zoom: 1.4 } ]center接收经纬度数组,[105, 35]是中心点坐标,zoom是缩放级别,1 表示原大小。注意center作用在地图投影后的平面上,它在数据更新后可能会被重置,所以每次setOption更新数据时,如果没有重新传入center和zoom,地图视野会回到默认的全世界视角。这是很多人调了半天觉得“图表自己会跳”的原因。
label的显示也需要单独控制。世界地图上如果每个国家都显示名称,文字会叠成一团。我的做法是默认关闭 label,只在鼠标悬停时显示:
label: { show: false }, emphasis: { label: { show: true, color: '#333', fontSize: 12 } }如果业务上要求常驻显示几个重点区域的名称,可以在label.formatter里做过滤,只返回需要显示的名称。这个比调整label全局开关要灵活得多。
4. 动态更新与地图下钻:setOption 与点击区域联动
4.1 setOption 分段更新:不要每次重建图表
很多图表的性能问题都出在“每次数据刷新就重新 init 一个图表实例”。其实 echarts 的setOption支持按需合并,只需要把变化的data重新传一次,地图的视觉映射会自动更新:
function updateMapData(newData) { myChart.setOption({ series: [ { type: 'map', map: 'world', data: newData } ] }); } // 示例:从接口拿到数据后传入 updateMapData([ { name: '北部区', value: 99 }, { name: '中部区', value: 130 } ]);这里的一个关键细节是setOption默认是“合并模式”,也就是说传入的 series 配置会覆盖同名 series,但其他例如tooltip、visualMap的配置如果不传,就不会变化。所以数据更新时,我只传series,避免把用户正在拖拽的视野也一并重置。如果非要重置视野,就在同一个setOption里显式传入geo.center和geo.zoom。
4.2 点击区域下钻:dispatchAction 与 nameMap 配合
世界地图最常见的交互是“点击某个区域,放大到该区域并展示明细数据”。监听点击事件:
myChart.on('click', function (params) { if (params.componentType === 'series' && params.seriesType === 'map') { const regionName = params.name; highlightRegion(regionName); } });拿到区域名后,可以用dispatchAction实现视野聚焦和区域高亮。常见的做法是触发geoRoam缩放,同时用highlight让当前区域保持高亮状态:
function highlightRegion(regionName) { myChart.dispatchAction({ type: 'geoRoam', geoIndex: 0, zoom: 1.8 }); myChart.dispatchAction({ type: 'highlight', seriesIndex: 0, name: regionName }); }注意geoRoam里的zoom是最终的缩放倍率,而不是在当前基础上再放大多少。所以如果想实现“点击两次从 1.8 变成 2.2”,需要先通过getOption()读当前zoom,再传入新值。highlight的name必须与当前区域名完全一致,否则高亮不生效。
如果区域名在 GeoJSON 里是英文,而界面上要显示中文,可以在geo组件里配置nameMap:
geo: { map: 'world', nameMap: { 'north-area': '北部区', 'south-area': '南部区' } }nameMap的 key 是 GeoJSON 中的原名,value 是展示名。配置之后,点击事件回调里的params.name也会变成中文,此时再拿去匹配后端明细接口,就要注意字段到底是英文还是中文。
4.3 返回上级:重置视野与清除高亮
下钻之后必须提供“返回上级”的操作。重置视野最简单的实现是把zoom设回 1,清空center,同时清除高亮状态:
function resetMapView() { myChart.dispatchAction({ type: 'downplay', seriesIndex: 0 }); myChart.setOption({ geo: { center: null, zoom: 1 } }); }这里有一个实际遇到过的问题:把center设为null不会生效,应该直接把它从配置里删除。改法是不传center字段,并且把zoom显式设为 1,这样 echarts 会自动回到默认视角。如果项目里同时存在多个 series,downplay时要把seriesIndex和name都传全,避免误清除其他系列的高亮。
5. 常见问题排查:四类地图显示异常的现象与修复
地图这块的坑比较集中,下面按我实际排查的顺序整理成“现象 → 原因 → 解决”的格式,遇到异常可以直接对照。
5.1 地图完全空白,只有坐标轴或只剩背景色
现象:图表区域有底色,网格或标题正常,但没有任何地图轮廓。
原因:最常见的是registerMap传入的注册名和series.map不一致,或者registerMap执行失败后代码没有提示。另一个高频原因是 GeoJSON 本身是普通 JSON 但不是地理数据,比如下载成了简化后的边界列表。
解决:先看控制台有没有报错。然后执行console.log(echarts.getMap('world')),如果返回空对象,说明注册没成功。再检查导入的文件内容,确认features[0].geometry.type是Polygon或MultiPolygon。我排查的顺序是:注册名 → 文件内容 → 是否重复注册。
5.2 地图渲染了,但区域位置明显偏移或形状错乱
现象:地图能显示,但某几个区域跑到海里,或者整体扭曲,和底图的经纬度对不上。
原因:世界地图 GeoJSON 存在多种投影坐标系。如果 JSON 里混入了不统一的坐标范围,echarts 默认按 GeoJSON 的coordinates直接绘制,投影不一致就会出现错位。
解决:替换 GeoJSON 数据源,选与 echarts 坐标系兼容的统一投影数据。不要用本地缓存里“能用但有点歪”的旧文件,这种问题后期很难修。另外检查文件里的bbox或crs字段,如果出现非 WGS84 的坐标系,优先先做一次坐标转换再注册。
5.3 区域填色全是默认色,visualMap 不生效
现象:地图轮廓正常,数据也传了,但所有区域都是同一个灰色或同一个基础色。
原因:data数组中的name和 GeoJSON 里的properties.name对不上。可能是中英文差异、带不带空格、全角半角符号,甚至编码问题。echarts 匹配不到时不会报错,只会默默跳过。
解决:先输出features[0].properties.name看看真实字段值。再对比data里的 name。如果两个都是正确的但数量很大,写一个脚本做集合对比,输出差集。差集往往是英文全名和缩写之间的差异,或数据里带了-1之类的额外字符。
5.4 滚轮缩放后视野被重置,或者点完一个区域回不到原状
现象:第一次 zoom 正常,但每次数据刷新后,地图突然回到全球视野;或者点击某个区域放大后,找不到方法还原。
原因:setOption合并模式不会保留没传入的zoom和center,图表内部会回到默认值。而还原时把center设为null又不会触发重置,导致操作看似无效。
解决:在每次更新数据时,如果产品要求保持视野,就把当前zoom和center从getOption()读出来,原样合并传回。重置时不要传center,只显式传zoom: 1。我习惯把它封装成一个方法,所有数据刷新都走这个方法,避免遗漏。
6. 进阶技巧:裁剪世界地图 JSON 并预生成中英文名映射
世界地图 GeoJSON 通常有几百 KB 到几 MB,对首屏加载很不友好。如果项目只用到十几个国家,可以提前裁剪掉多余的features,把体积缩到原来的十分之一。这个过程我在本地跑一个 Node 脚本完成,输出一份精简后的 JSON 和一个映射表。
const worldGeoJSON = require('./world.json'); // 这里放你业务实际需要的区域英文名,替换成你自己 GeoJSON 里的 properties.name const targetAreas = ['area-a', 'area-b', 'area-c']; const filterFeatures = worldGeoJSON.features.filter(f => targetAreas.includes(f.properties.name) ); const miniWorld = { ...worldGeoJSON, features: filterFeatures }; // 同步生成 nameMap,供前端渲染中文名 const nameMap = {}; filterFeatures.forEach(f => { nameMap[f.properties.name] = f.properties.name_cn || f.properties.name; }); const fs = require('fs'); fs.writeFileSync('./world-mini.json', JSON.stringify(miniWorld)); fs.writeFileSync('./name-map.json', JSON.stringify(nameMap));这里的关键是targetAreas必须和 GeoJSON 里实际的properties.name完全一致。我最早做裁剪时直接拿中文名去匹配,结果裁剪出来只有零个区域。现在有好几个项目都产出了自己的地区清单,我会统一放在一个配置文件里维护,避免每次搜索。
裁剪后的数据在前端使用没有任何区别,仍然是registerMap注册。加载体积变小后,首屏渲染速度明显改善。nameMap 文件随后在geo组件里展开使用:
import nameMap from './name-map.json'; geo: { map: 'world', nameMap: nameMap }从那以后,我每次接到地图需求,都会先强制走一遍校验和裁剪脚本:先用脚本确认features的结构,再检查name是否能对上后端接口,最后才把精简的文件交给前端页面。这个动作省掉了大量“看起来是 echarts 配置问题,实际是数据不匹配”的排查时间。希望帮到你。
本文还有配套的精品资源,点击获取