简介:「echarts 世界地图js」是一套面向前端开发者与数据可视化学习者的世界地图示例资源,重点解决在网页中快速搭建可交互全球数据地图的问题,适合展示人口、GDP等跨国指标或做区域数据对比的场景。压缩包共77个文件,以39个js脚本和38个json地图/配置数据为主,整体约3.42MB,便于查看地图初始化、配置项组织与数据接入方式。已有1639人学习下载,说明其在实际项目与入门练习中具有一定参考价值。读者可从中获得世界地图渲染的完整素材与代码线索,包括地图数据、主题样式、ECharts实例配置及交互效果参考,能帮助理解tooltip、visualMap、缩放平移等常见能力的落地写法;资源中的脚本与数据文件可配合复现地图加载、数值映射和视觉编码流程,并据此改造成自己的可视化页面。
1. 先别急着 setOption:echarts 世界地图js 到底替你做了哪一步
这份 echarts 世界地图js 资源,最常见的形态不是一张图片,而是一段把 GeoJSON 注册进 ECharts 的脚本:它提供区域边界、区域名称,以及一个能被series.map引用的底图。很多人以为拿到 js 就能直接出图,真正卡住的地方往往是注册名、脚本顺序和名称匹配。它适合做离线大屏、内部报表和区域指标分布图,不适合街道级细节或实时轨迹。先把它当成一份“地图字典”,再谈样式和数据。
2. 把 world.js 接进页面:三种加载路径与一份最小可运行配置
地图资源接不进去,后面调色、加 tooltip、做钻取都是空谈。我一般先把问题拆成两层:world.js到底是“已经帮你注册好的脚本”,还是“只暴露了一份 GeoJSON 的数据文件”。这两种东西文件名可能都叫world.js,但接法完全不同。先判断结构,再选加载方式,能少走很多弯路。
2.1 先判断 world.js 是“注册脚本”还是“纯数据”
拿到文件后不要急着改代码,先用文本搜索看几个关键词:registerMap、features、export default、window.。如果文件里出现echarts.registerMap('world', ...),说明它是注册脚本,页面里只要保证echarts先加载、它后加载即可。如果只看到features数组和properties.name,那它更像纯 GeoJSON 包装,需要你自己调registerMap。如果是export default,说明它走模块导出,script标签直接引入不一定能用。
# 在资源目录里快速看结构,不要全文件打印 grep -n "registerMap\|features\|export default\|window\." world.js | head -20这段命令只做一件事:把可能影响接入方式的关键词找出来。registerMap出现,优先按注册脚本处理;features出现但没有registerMap,按纯数据处理;export default出现,优先走打包器。注意,压缩后的文件可能只有一行,grep的输出会很长,看到关键词即可,不要直接拿压缩文件手改。
注意:
world.js里的注册名不一定是world,以文件里registerMap的第一个参数为准。后面所有map: 'world'都要和它一致。
2.2 浏览器直连:script 顺序与最小初始化
最常见的落地方式是静态页面直接引。顺序必须是 ECharts 在前、world.js在后,否则world.js内部调echarts.registerMap时会直接报错。容器必须有明确高度,地图不是文本,父级高度为 0 时它不会自己撑开。下面这份最小示例只画底图,不接业务数据,用来确认资源本身可用。
<div id="worldMap" style="width: 100%; height: 600px;"></div> <script src="./echarts.min.js"></script> <script src="./vendor/world.js"></script> <script> // 1. 先确认 world.js 是否已经完成注册 if (!echarts.getMap('world')) { const geo = window.world || window.WORLD_GEOJSON || window.worldGeoJson; if (!geo) { throw new Error('world.js 未注册地图,也未暴露 GeoJSON 全局变量'); } echarts.registerMap('world', geo); } // 2. 容器必须有明确高度,否则地图高度为 0 const dom = document.getElementById('worldMap'); const chart = echarts.init(dom); // 3. 最小配置:先只画底图,确认资源可用 chart.setOption({ series: [{ type: 'map', map: 'world', // 必须和 registerMap 的第一个参数一致 roam: true }] }); // 4. 容器尺寸变化时重算,避免大屏缩放后留白 window.addEventListener('resize', () => chart.resize()); </script>这段代码的关键参数只有三个:map是注册名,不是文件名;roam控制拖拽和缩放,先设true方便检查地图是否完整;容器高度必须落在 CSS 上,height: 600px只是示例,实际按大屏格子给。echarts.getMap('world')用来判断是否已经注册,能避免重复注册,也能在控制台快速确认资源有没有被加载。
如果页面用file://直接打开,而world.js内部又用fetch拉 JSON,就会被浏览器的本地文件限制拦住。常见做法是起一个静态服务:
# 在项目根目录启动静态服务,避免 fetch/JSON 被 file:// 限制 python3 -m http.server 8080 # 浏览器访问 http://localhost:8080/index.html这不是地图资源本身的要求,而是前端加载本地 JSON 的通用习惯。world.js如果是纯脚本注册,直接双击 HTML 有时也能出图;但只要涉及fetch、模块导入或跨文件路径,静态服务更稳。
2.3 打包器接入:副作用导入与全局 shim
项目走构建工具时,最容易翻车的不是地图本身,而是world.js里的执行时机。很多世界地图脚本默认页面里有一个全局echarts,内部直接写echarts.registerMap(...)。打包后模块作用域变了,全局变量不一定存在,地图就会静默消失。我的处理方式是先挂全局,再副作用导入,让脚本按它原本的假设执行。
import * as echarts from 'echarts'; // 有些 world.js 内部直接调用全局 echarts.registerMap,需要先挂到 window window.echarts = echarts; // 副作用导入:只执行 world.js,不接收返回值 await import('./vendor/world.js'); // 如果 world.js 是纯数据模块,走这一条 // import worldGeo from './vendor/world.js'; // echarts.registerMap('world', worldGeo); const chart = echarts.init(document.getElementById('worldMap')); chart.setOption({ series: [{ type: 'map', map: 'world', roam: true }] });window.echarts = echarts是兼容层,不是必须长期保留的写法;等确认world.js是纯数据模块后,应该改成显式registerMap,减少全局污染。await import()适合在模块顶层或异步初始化函数里用,注意它要求运行环境支持顶层 await;如果不支持,就把导入放到async function boot()里。另一个坑是构建工具的 tree-shaking:只在代码里import './vendor/world.js'但不使用返回值时,某些配置会把它当成无副作用模块摇掉。常见做法是在构建配置里把该文件标记为有副作用,或者改成显式导入 GeoJSON 后手动注册。
{ "sideEffects": [ "./src/vendor/world.js" ] }这段配置的意思是:不要对这个文件做副作用裁剪。路径按项目实际结构调整,不要照抄文件名。做完这一步,再用echarts.getMap('world')在控制台确认一次,能返回对象就说明注册成功;返回undefined,继续回到脚本顺序和导入方式上查。
3. 让颜色说话:visualMap、tooltip 与 nameMap 的联动参数
底图能出来之后,下一步才是业务表达。世界地图的区域多、名称长,颜色映射和悬浮提示如果不克制,很容易变成“能看但读不懂”。我一般先把visualMap的范围从数据里算出来,再用tooltip补原始名称和数值,最后才调roam和布局。顺序反过来,调半天也不知道是数据没对上,还是样式覆盖了。
3.1 visualMap:min/max 不是拍脑袋,分段与连续怎么选
visualMap最容易犯的错是手写min: 0, max: 100。业务数据可能集中在 20 到 40 之间,也可能有一个极端值把色带拉满,手写范围会让大部分区域颜色几乎一样。连续型适合看整体高低分布,分段型适合汇报时明确分档。区域数量多时,色带控制在 3 到 5 段,不要做彩虹色。
| 参数 | 作用 | 常见设置 |
|---|---|---|
type | 连续或分段 | 指标跨度大用continuous,汇报分档用piecewise |
min/max | 色彩映射范围 | 从数据算,不要固定写 0 到 100 |
inRange.color | 区间内颜色 | 3 到 5 个颜色足够 |
outOfRange.color | 无数据或越界 | 用浅灰,避免和最低值同色 |
seriesIndex | 绑定哪个系列 | 多系列时必填,单系列也建议写 |
calculable | 可拖拽手柄 | 连续型可开,分段型一般不开 |
const rawData = [ { name: 'Region-A', value: 12 }, { name: 'Region-B', value: 87 }, { name: 'Region-C', value: null } ]; const nums = rawData .map(d => Number(d.value)) .filter(Number.isFinite); let min = Math.min(...nums); let max = Math.max(...nums); // 所有值相同时给一个最小跨度,避免色带退化 if (min === max) max = min + 1; chart.setOption({ tooltip: { trigger: 'item' }, visualMap: { type: 'continuous', min: Math.floor(min), max: Math.ceil(max), calculable: true, seriesIndex: 0, inRange: { color: ['#dbeafe', '#60a5fa', '#1e3a8a'] }, outOfRange: { color: '#e5e7eb' }, text: ['高', '低'], left: 16, bottom: 16 }, series: [{ type: 'map', map: 'world', data: rawData.map(d => ({ name: d.name, value: Number.isFinite(Number(d.value)) ? Number(d.value) : null })) }] });这段代码先过滤出有效数值,再向下取整、向上取整,避免色带边界出现小数。seriesIndex: 0把visualMap明确绑到第一个系列,后面加折线图或散点图时不会误伤。outOfRange给无数据区域一个浅灰,和最低值颜色拉开;如果两者太接近,读者会误以为“没有数据”也是低值。null值不要直接转成0,否则缺数据区域会被染成最低档。
3.2 tooltip 与 nameMap:显示名和原始名分离
世界地图的区域名通常是英文或拼音,直接悬浮出来对业务方不友好。nameMap可以做显示名映射,但它不是数据清洗工具:它不会自动帮你把业务表里的简称、别名、大小写差异全部对齐。稳妥做法是数据侧保留一套标准名,显示侧用formatter或nameMap转成可读名称,两边不要混着改。
const displayName = new Map([ ['Region-A', '区域 A'], ['Region-B', '区域 B'] ]); chart.setOption({ tooltip: { trigger: 'item', confine: true, formatter: (p) => { const label = displayName.get(p.name) || p.name || '未知区域'; const value = p.value == null || Number.isNaN(Number(p.value)) ? '无数据' : Number(p.value).toFixed(1); return `${label}<br/>指标值:${value}`; } }, series: [{ type: 'map', map: 'world', nameMap: { 'Region-A': '区域 A', 'Region-B': '区域 B' }, data: rawData }] });confine: true让提示框限制在图表容器内,移动端和大屏边缘区域不容易溢出。formatter里先取p.name,再用displayName转一次,转不到就回退原名称;p.value可能是null、空字符串或字符串数字,所以用Number.isNaN(Number(p.value))兜底。nameMap只影响显示层,数据到底用原始名还是映射名,建议先拿一个区域做单点验证:数据里只放一条记录,能点亮再批量。不要一半用原始名、一半用显示名,最后排查起来非常费劲。
3.3 roam、layoutCenter 与 layoutSize:视口和自适应
世界地图的长宽比和容器不一定匹配,默认铺满时容易出现上下留白或左右裁切。layoutCenter和layoutSize控制地图在容器里的布局中心与大小,roam控制用户交互,zoom控制初始缩放。大屏项目里我一般把layoutSize设成'95%'左右,留一点边给图例和visualMap,不要顶满。
chart.setOption({ series: [{ type: 'map', map: 'world', roam: true, zoom: 1.15, center: [0, 0], layoutCenter: ['50%', '50%'], layoutSize: '95%', aspectScale: 0.85, scaleLimit: { min: 1, max: 8 }, emphasis: { label: { show: true } } }] });roam: true是拖拽和缩放的快捷写法,需要限制缩放范围时改成对象,或者单独加scaleLimit。layoutCenter用百分比时相对于容器,layoutSize可以写百分比或像素;aspectScale会改变地图的纵向拉伸比例,不要为了“填满”把区域拉变形。center: [0, 0]只是初始中心点示例,实际按业务关注区域调整。容器尺寸变化后必须调chart.resize(),否则layoutSize还是按旧容器算的。
提示:先确认
echarts.getMap('world')有返回值,再调样式;底图没注册时,调色和 tooltip 都不会生效。
4. 数据落到区域上:从名称对账到 series.data 对齐
地图资源接好、样式调完,真正决定图表能不能用的,是series.data里的name能不能和 GeoJSON 里的区域名对上。世界地图尤其容易在这里翻车:业务表用简称,地图数据用全称;业务表有空格,地图数据有连字符;业务表是字符串数字,地图侧按数值排序。我的习惯是先导出一份“标准名称清单”,再做别名映射,最后统计未覆盖区域和多余数据。
4.1 从注册数据里导出名称清单
不要靠猜。地图注册成功后,直接通过echarts.getMap('world')拿到 GeoJSON,把features里的properties.name全部列出来。这份清单就是后续数据对齐的基准。不同资源的属性名可能不叫name,所以先打印一条样本看看。
const mapInfo = echarts.getMap('world'); if (!mapInfo) throw new Error('地图未注册'); const geo = mapInfo.geoJSON || mapInfo.geoJson; const firstFeature = geo.features[0]; console.log('第一条属性样本:', firstFeature.properties); const regionNames = geo.features.map(f => f.properties.name); console.log('区域数量:', regionNames.length); console.table(regionNames.slice(0, 20));geoJSON和geoJson做了一次兼容,避免不同构建产物属性名不一致。features是区域数组,properties.name是常见名称字段;如果样本里没有name,而是NAME、name_en之类的字段,就要在map里换字段名,不要继续硬套。regionNames既是排查工具,也是后面生成对照表的源数据。
4.2 数据清洗:别名、空值、字符串数值
业务数据进来后,先做一轮标准化:去空格、别名映射、数值转换、重复名称处理。别名表不要写得太宽,宁可少映射,也不要让两个不同区域误合并。重复名称要看业务含义:如果是同一区域的多条记录,应该先聚合;如果只是重复导入,保留一条并打日志。
function buildCanonicalSet(names) { return new Set(names.map(n => String(n).trim())); } function normalizeRows(rows, canonicalSet) { const alias = new Map([ ['Region A', 'Region-A'], ['Region_B', 'Region-B'] ]); const seen = new Set(); const clean = []; for (const row of rows) { let name = String(row.name ?? '').trim(); name = alias.get(name) || name; // 不在地图标准名称里的记录先丢弃,后面单独统计 if (!canonicalSet.has(name)) continue; // 同名记录只保留第一条,真实业务里应先聚合再进来 if (seen.has(name)) continue; const value = Number(row.value); if (!Number.isFinite(value)) continue; seen.add(name); clean.push({ name, value }); } return clean; }buildCanonicalSet把地图名称做成集合,查询成本低。alias只处理明确的别名,比如空格、下划线、缩写;不要用模糊匹配,否则容易把相似名称合并错。row.name ?? ''兼容null和undefined,Number(row.value)把字符串数字转成数值,Number.isFinite过滤掉空值和非法字符。重复名称这里简单保留第一条,真实业务里更常见的是按名称求和或取最新值,策略要写在数据清洗层,不要留到图表配置里。
4.3 series.data 对齐与缺失值排查
清洗完的数据不要直接塞进series.data,先做一次对账:哪些区域有数据、哪些区域缺失、哪些数据多余。缺失不一定是错误,可能业务上就没有;多余数据则通常意味着别名表漏了,或者名称格式不一致。把这两个数字打出来,排查效率会高很多。
const canonicalSet = buildCanonicalSet(regionNames); const cleanData = normalizeRows(rawRows, canonicalSet); const cleanSet = new Set(cleanData.map(d => d.name)); const missing = regionNames.filter(n => !cleanSet.has(n)); const extra = cleanData.filter(d => !canonicalSet.has(d.name)); console.log('未覆盖区域数:', missing.length); console.log('多余数据数:', extra.length); console.table(extra.slice(0, 10)); chart.setOption({ series: [{ type: 'map', map: 'world', data: cleanData, emphasis: { label: { show: true } } }] });missing用来决定无数据区域怎么显示,通常交给visualMap.outOfRange的浅灰;extra用来反查别名表,如果extra不为空但canonicalSet里没有,说明这些名称根本不在底图里。cleanData里只保留能匹配上的记录,避免无效数据干扰 tooltip。最后把cleanData传给series.data,不要再在setOption里做二次名称转换,转换逻辑越靠前越容易复用。
5. 空白、不上色、缩放漂移:五个高频翻车点的排查顺序
地图类问题最怕“看起来没报错”。控制台干净,页面空白;底图出来了,区域全灰;拖拽两下,地图跑偏。下面这五类是我在项目里反复遇到的,按“先资源、再数据、后交互”的顺序排查,基本能覆盖大多数场景。每条都按现象、原因、解决写,方便对照。
5.1 地图整块空白:先查脚本顺序,再查容器高度
现象:页面有容器,控制台没有明显报错,地图不出现。原因通常有三个:world.js在echarts.min.js之前加载,内部调registerMap时echarts还不存在;容器高度是 0,地图画布没有尺寸;注册名和series.map不一致。解决顺序是先看echarts.getMap('world')有没有返回值,再看容器getBoundingClientRect()的高度,最后核对注册名。
console.log('地图注册信息:', echarts.getMap('world')); console.log('容器尺寸:', document.getElementById('worldMap').getBoundingClientRect());如果第一行是undefined,问题在资源加载或注册;如果第一行有对象、第二行高度是 0,问题在 CSS。父级用了height: 100%但祖先没有高度,也会导致容器高度为 0。大屏项目里常见的是格子用了绝对定位,内部图表容器没有显式高度,初始化时还是 0。
5.2 只有底图没有颜色:名称和数值两头对不上
现象:地图轮廓正常,所有区域都是默认色,visualMap也显示了,但颜色不生效。原因多半是series.data的name和 GeoJSON 的区域名不一致,或者value是字符串、空值、重复名称导致匹配失败。另一个常见原因是visualMap的min/max被手写死,数据全落在范围外。解决方式是先打印第一条匹配记录,再打印未匹配数量。
console.log('第一条数据:', chart.getOption().series[0].data[0]); console.log('未匹配区域数:', missing.length); console.log('数据值样本:', cleanData.slice(0, 5));chart.getOption()拿到的是合并后的配置,能确认数据有没有真正进入系列。missing.length很大,先查别名和大小写;missing.length很小但颜色仍不对,查visualMap.min/max和outOfRange。如果所有值都是0,min和max相等,色带会退化,需要给一个最小跨度。
5.3 缩放后漂移或重复渲染:init 被调用两次
现象:拖拽或缩放后地图跳到左上角,或者每次切换页面回来,地图多一层、提示框重复出现。原因通常是同一个 DOM 上重复echarts.init,或者resize监听器重复绑定。单页应用里组件
本文还有配套的精品资源,点击获取