Vue+ECharts-GL打造3D中国地图数据大屏:五层叠加实践指南

发布时间:2026/10/3 3:40:43
Vue+ECharts-GL打造3D中国地图数据大屏:五层叠加实践指南 1. 项目整体设计与技术选型思路1.1 为什么是 Vue ECharts-GL前两天接了一个数据可视化大屏的需求要求做一个 Vue ECharts-GL 的 3D 中国地图还要把 GDP、人口、交易流向等好几个维度的数据同时摆上去。我第一反应不是去翻 Three.js 文档而是直接锁定了 ECharts-GL 这一套方案。原因其实很简单在 3D 地图这块如果只是想快速做出一个能上线的、信息密度高的可视化页面ECharts-GL 的学习成本和开发效率是其他方案比不了的。拿 Cesium 和 Three.js 来对比Cesium 本身是个三维地理信息平台处理卫星影像、地形、坐标系那一套非常专业但它的定位偏向 GIS 系统不是给业务数据可视化准备的。Three.js 倒是够灵活什么都能画问题在于你从零开始搭一个带光照、投影、经纬度转换、区域拾取的地球或地图工程量相当可观而且后续交给你同事维护他也得啃很长一段时间的 Three.js 文档。ECharts-GL 则完全不同它是在 ECharts 配置体系上做的 WebGL 扩展所有的图形都通过 option 对象来描述做 3D 地图就像写 2D 图表一样简单数据映射、图例、tooltip、事件绑定全部复用 ECharts 的成熟机制工程效率高得多。Vue 在这套方案里的角色也很明确它主要负责组件拆分和数据驱动。地图容器我可以封装成一个独立组件父组件只需要把接口返回的数据丢进来组件内部通过 watch 监听数据变化再刷新 option不需要手动操作 DOM。大屏项目里这种数据更新频率还挺高的Vue 的响应式机制天然适合做这件事。再加上 Vite 的秒级热更新调试 3D 场景参数时体验相当舒服。1.2 “5层叠加”具体是哪5层每层表达什么信息很多人看到“5层叠加”第一反应是炫技实际上每一层都有它的数据表达任务。我这次做的 5 层结构每一层解决一类信息第一层是 3D 底图。这一层负责稳定输出全国各省的轮廓和区域颜色用颜色深浅表达整体的数值高低比如各省 GDP 总量分布让人一眼就能看出哪些省份是第一梯队。第二层是柱状数据层。底图只能表达“面”的信息柱状图则表达“点”上的精确数值。我用 bar3D 在每个重点城市坐标上方拉出一根柱子柱高对应具体指标值不同省份用颜色区分跟底图的区域色形成对照。第三层是散点与涟漪层。散点负责标记重点城市或热点节点比如一线城市、省会、口岸城市effectScatter3D 的涟漪效果让这些关键点位在地图上不断扩散视觉优先级最高适合强调“核心节点”。第四层是飞线关系层。飞线表达的是节点之间的联系比如城市之间的资金流向、货物流向、访问流量。lines3D 配合动画效果让一条条光带从起点流向终点动态感很强。第五层是标签信息层。地图上光有图形没有文字业务方根本看不懂。标签层负责在城市坐标旁显示城市名和核心数值同时也可以作为 tooltip 的补充把最关键的 1 到 2 个指标直接钉在地图上。这 5 层从下到上分别是“面、柱、点、线、文”正好覆盖了区域分布、精确数值、关键节点、关联关系、文本标注这 5 类表达需求。实际做的时候不能无脑堆层与层之间要通过透明度、颜色饱和度、光效强度来做视觉分层否则叠加出来的画面会糊成一团。这个我后面会详细说。2. 环境准备与地图数据获取2.1 创建Vue项目并安装依赖这一节内容不算难但版本坑比较多我直接给一套验证过的组合。项目脚手架我用的是 Vite创建命令和以前一样npm create vitelatest china-map-demo -- --template vue cd china-map-demo npm install安装依赖时注意版本我测试下来最稳的组合是 echarts 5.x echarts-gl 2.x。直接装最新版就行npm install echarts echarts-gl不建议再用 echarts-gl 1.x那是匹配 echarts 4 时代的老版本API 和 2.x 差了不少。装完之后在 main.js 里做全局引入import { createApp } from vue import App from ./App.vue import * as echarts from echarts import echarts-gl createApp(App).mount(#app)全局注册 echarts 只是为了方便调试实际项目里更推荐按需引入。不过 echarts-gl 整个包目前没有特别细的按需拆分图省事的话在组件里直接import * as echarts from echarts然后import echarts-gl也可以打包体积后面用动态加载去优化。另外一个容易忽略的点如果你用的 Vue 版本比较老比如 Vue 2.6 配 ViteECharts 5 里部分 API 的响应式追踪会有问题建议还是老老实实升级到 Vue 3。这个组合我踩过坑项目如果定了 Vue 2 且不方便升级可以考虑退到 echarts 4.9 加 echarts-gl 1.1.2但功能就少了。2.2 获取中国地图GeoJSON数据并注册ECharts 本身不带你国内地图的 GeoJSON 数据。以前网上一堆 china.js 文件很多是旧版阿里 DataV 导出的区域编码老、图形精度低现在不推荐用了。我的建议是直接去阿里云 DataV.GeoAtlas 拉取最新的 GeoJSON地址是https://datav.aliyun.com/portal/school/atlas/area_selector页面上选择中国然后下载 JSON 文件保存到项目的src/assets/geo/目录下。全国级别的 GeoJSON 文件体积一般在 1MB 到 3MB 之间对于大屏项目来说可以直接加载如果对体积敏感后续可以用 mapshaper 做简化压缩把精度从 0.01 降到 0.1 左右视觉上几乎看不出差别。地图数据准备好之后在组件里注册import * as echarts from echarts import echarts-gl import chinaJson from /assets/geo/china.json echarts.registerMap(china, chinaJson)注册完成之后chart 的 option 里凡是能用map: china的地方都可以引用这份数据。需要注意一点GeoJSON 的坐标系默认是 WGS84 经纬度ECharts-GL 内部会做投影转换我们只需要保证数据里的坐标是 [经度, 纬度] 的顺序就行了别的不用操心。3. 五层叠加的核心实现3.1 先把坐标系理清楚用 geo3D 而不是 map3D这是整个项目最关键的一个决策也是很多人踩坑的地方。一搜 ECharts-GL 3D 地图网上教程基本都会让你用series-map3D它确实能渲染出立体地图看起来很酷。但我建议在做多图层叠加时把地图底图用geo3D组件来承载而不是 map3D 系列。原因在于map3D 只是一个 series 类型它本身不提供可共享的坐标系。你想在同一个图表里再叠加柱状图、散点图、飞线这些 3D 系列需要一个统一的geo3D坐标系来定位。如果你用了 map3D 当底图你会发现其他系列根本没有 map3D 的坐标系可挂数据定位就成了大问题。而 geo3D 组件既负责画 3D 地图区域又天然是其他 3D 系列的坐标系一举两得。所以我的完整 option 结构是在geo3D组件里配置地图底图然后在series数组里按顺序叠加 bar3D、effectScatter3D、lines3D、scatter3D标签层每个系列都设置coordinateSystem: geo3D。这个结构从头到尾贯穿整个项目后面每一层的代码都会基于它来扩展。3.2 第一层geo3D 底图的区域着色与光照先看底图层的完整配置geo3D: { map: china, regionHeight: 2, shading: lambert, environment: rgba(0, 0, 0, 0), itemStyle: { color: #2a5caa, opacity: 0.9, borderWidth: 1, borderColor: #5ad8d8 }, light: { main: { intensity: 1.2, shadow: true, alpha: 30, beta: 40 }, ambient: { intensity: 0.4 } }, viewControl: { distance: 110, alpha: 40, beta: 0, autoRotate: false }, label: { show: false } }regionHeight是地图区域挤出成 3D 体的厚度单位是三维空间的抽象值不是经纬度。默认值是 3我觉得 2 比较合适太厚了会挡住后面叠加的柱子底部太薄又没有立体感。shading: lambert是兰伯特光照模型配合light.main可调整主光源的方向和强度我习惯把alpha设在 30 到 45 之间这样地图表面会有明暗渐变而不是一张死板的贴图。底图的itemStyle.color我选了深蓝色系因为后面的柱子和飞线都是红黄亮色系底图压暗一点才能把上层数据衬托出来。如果底图颜色太亮整张屏看起来就会像调色盘打架。这一层的 label 我先不显示因为全国三十多个省级行政区如果全部标上名字画面会非常拥挤标签的工作交给第五层来做只显示需要强调的城市。3.3 第二层bar3D 柱状数据层底图有了接下来就是 5 层叠加里视觉冲击力最强的柱状数据层。先看城市数据长什么样const cityData [ { name: 北京, value: [116.405285, 39.904989, 980] }, { name: 上海, value: [121.472644, 31.231706, 860] }, { name: 广州, value: [113.264385, 23.129112, 760] }, { name: 深圳, value: [114.057868, 22.543099, 890] }, { name: 成都, value: [104.066513, 30.572815, 690] } ]value 数组依次是经度、纬度、柱高数值。这里有个非常容易踩的坑柱子的高度不是直接用原始业务数值因为在 3D 空间的 Z 轴上原始值跨度可能很大不做归一化的话有的柱子能顶到天上去有的矮得看不见。我会先做一次线性缩放const maxVal Math.max(...cityData.map(d d.value[2])) const scaleFactor 80 / maxVal const barData cityData.map(d ({ name: d.name, value: [d.value[0], d.value[1], d.value[2] * scaleFactor] }))归一化的目标是把最大值对应的柱子高度控制在 80 个单位左右。这个 80 不是随便定的在 geo3D 的默认视角下柱子高度 80 差不多是regionHeight的 40 倍视觉上正好从地图表面明显隆起又不会戳破整个场景的天花板。你用默认视角看这个比例最协调。柱状系列的配置如下{ type: bar3D, coordinateSystem: geo3D, data: barData, barSize: 0.8, bevelSize: 0.2, bevelSmoothness: 2, minHeight: 0.2, shading: lambert, itemStyle: { color: #ff7c43 }, emphasis: { itemStyle: { color: #ffd166 } } }barSize是柱子的粗细单位是经纬度0.8 意味着柱子的底边接近一个经纬度的大小在省级粒度下看起来像一根细柱挺拔不臃肿。bevelSize和bevelSmoothness是柱体边缘的倒角加了之后柱子边缘不会出现明显锯齿灯光打上去有高光反射。柱子颜色我用橙色系因为橙色跟深蓝底图有很强的对比度视觉焦点会很自然地聚到柱子上。如果你负责的业务有品牌色可以替换itemStyle.color但原则是不能跟底图颜色一个色系。3.4 第三层effectScatter3D 散点与涟漪动效柱状图表达了数值散点层主要用来标注“重点节点”。这些节点不一定是数据最大的但一定是业务上需要强提醒的位置比如总部所在地、核心枢纽城市。我用的是 effectScatter3D它自带动画扩散效果可以让关键节点在 3D 场景里非常突出。const spotData [ { name: 北京, value: [116.405285, 39.904989, 10] }, { name: 上海, value: [121.472644, 31.231706, 10] }, { name: 深圳, value: [114.057868, 22.543099, 10] } ] { type: effectScatter3D, coordinateSystem: geo3D, data: spotData, symbolSize: 8, itemStyle: { color: #00ffff, opacity: 0.9 }, rippleEffect: { brushType: stroke, scale: 5, frequency: 4 } }symbolSize决定了中心实点的大小8 在 3D 场景里已经足够醒目。rippleEffect.scale是涟漪缩放半径我测下来 4 到 6 之间视觉最舒服太大会和旁边的柱子产生视觉干扰太小又感觉不到扩散效果。frequency是动画频率单位是秒数值越大扩散越快大屏展示时调到 4 左右比较有节奏感。需要特别说明的是effectScatter3D 的散点坐标同样要挂在 geo3D 坐标系下所以coordinateSystem: geo3D不能漏。散点的高度我这里固定为 10让它稍微悬浮在地图上空和柱状图形成空间层次但又不至于飘到半空跟飞线撞在一起。3.5 第四层lines3D 飞线关系层飞线层是这个项目里动画效果最复杂的部分也是业务方最买账的一层。它表达的是城市之间的“连接关系”我用它来展示核心城市与其他城市之间的业务流向。飞线数据这样组织const linkData [ { coords: [[116.405285, 39.904989], [121.472644, 31.231706]] }, { coords: [[116.405285, 39.904989], [113.264385, 23.129112]] }, { coords: [[121.472644, 31.231706], [114.057868, 22.543099]] }, { coords: [[116.405285, 39.904989], [104.066513, 30.572815]] } ]对应系列配置{ type: lines3D, coordinateSystem: geo3D, effect: { show: true, trailWidth: 3, trailLength: 0.3, trailOpacity: 0.8 }, data: linkData, lineStyle: { color: #ffd166, opacity: 0.5, width: 1.5 } }trailWidth是飞线尾巴的宽度我实测 3 左右在屏幕上最清晰太细则像一根细线缺乏动感太粗又会遮盖底图。trailLength是尾巴长度0.3 表示尾巴占整条线段的 30%这个值越高尾部越长拖尾效果越明显但也越容易显得杂乱。综合多个项目经验0.3 是个不错的平衡点。飞线的颜色我用了和柱状图接近的暖黄系这样飞线和柱子天然形成一组“同类数据”的心理暗示而散点的青色则代表另一类“重点标记”数据两套色系在场景里互不干扰。3.6 第五层标签与信息浮层最后这一层是决定项目完成度的关键。很多人的 3D 地图看着炫但缺少文字标注业务方根本不知道那些柱子代表着哪个城市。第五层我用了两种手段第一种是 scatter3D 的 label 标签第二种是自定义 DOM 浮层两种方式根据场景灵活选。先看 scatter3D 标签层它本质上是往地图上放了一堆透明散点把标签显示出来{ type: scatter3D, coordinateSystem: geo3D, data: cityData.map(d ({ name: d.name, value: [d.value[0], d.value[1], 85] })), symbolSize: 0.1, label: { show: true, formatter: (params) { return ${params.name}\n${params.value[2]} }, fontSize: 14, color: #ffffff, backgroundColor: rgba(0, 0, 0, 0.45), borderRadius: 4, padding: [4, 8], lineHeight: 20 }, emphasis: { label: { show: true } } }symbolSize设成 0.1是为了让散点本体几乎不可见只留下文字标签避免在地图上出现一堆圆点干扰视觉。标签的高度我设在 85比柱子最高点略高一点这样标签浮在柱子顶部上方不会被柱子遮挡。formatter 函数里params.value[2]取到的是原始数据里的业务数值所以标签上可以直接看到“城市名 具体数值”。除了图表自带的 label我还在大屏项目里做过 DOM 浮层的方案。思路很简单在 Vue 组件里维护一个绝对定位的 div 层用 ECharts 的chart.convertToPixel把经纬度坐标转换成屏幕像素坐标然后动态设置浮层的位置。不过这里有个限制——当用户开启了viewControl的旋转和缩放后3D 场景每帧都在变标签位置也必须跟着重新计算。这需要在chart.on(updateAxisPointer)或者动画帧里不断同步实现复杂度会高一些。如果你的需求只是静态展示建议直接用 scatter3D 的 label 就好省心且效果稳定。3.7 完整可运行的 option 参考到这里 5 层全部到位我贴一份简化后可以直接跑起来的完整 option。实际项目里拆到 Vue 组件中这份代码就是核心配置模块。const option { geo3D: { map: china, regionHeight: 2, shading: lambert, itemStyle: { color: #2a5caa, opacity: 0.9, borderWidth: 1, borderColor: #5ad8d8 }, light: { main: { intensity: 1.2, shadow: true, alpha: 30, beta: 40 }, ambient: { intensity: 0.4 } }, viewControl: { distance: 110, alpha: 40, beta: 0 } }, series: [ // 第二层柱状数据层 { type: bar3D, coordinateSystem: geo3D, data: barData, barSize: 0.8, bevelSize: 0.2, bevelSmoothness: 2, shading: lambert, itemStyle: { color: #ff7c43 } }, // 第三层散点涟漪层 { type: effectScatter3D, coordinateSystem: geo3D, data: spotData, symbolSize: 8, itemStyle: { color: #00ffff }, rippleEffect: { brushType: stroke, scale: 5, frequency: 4 } }, // 第四层飞线关系层 { type: lines3D, coordinateSystem: geo3D, effect: { show: true, trailWidth: 3, trailLength: 0.3, trailOpacity: 0.8 }, data: linkData, lineStyle: { color: #ffd166, opacity: 0.5, width: 1.5 } }, // 第五层标签信息层 { type: scatter3D, coordinateSystem: geo3D, data: labelData, symbolSize: 0.1, label: { show: true, formatter: (params) ${params.name}, fontSize: 12, color: #ffffff, backgroundColor: rgba(0, 0, 0, 0.3), padding: [2, 6] } } ] }注意这份 option 里的barData、spotData、linkData、labelData需要你自己根据业务数据做处理格式就是前面说的[经度, 纬度, 数值]结构。series 数组的顺序不要乱我是按照“柱子、散点、飞线、标签”的顺序排列的如果先画散点再画柱子柱子会把散点盖住视觉上就会出现奇怪的遮挡。4. 常见问题与排查技巧实录4.1 地图白屏或黑屏这个现象在第一次跑 ECharts-GL 项目时特别常见而且坑位很多。最典型的两个原因一个是 WebGL 上下文创建失败另一个是地图 GeoJSON 注册失败。先说 WebGL。ECharts-GL 是依赖 WebGL 的如果浏览器环境不支持 WebGL或者显卡驱动禁用硬件加速图表就会白屏。排查方法很简单打开浏览器的控制台看有没有报Error creating WebGL context之类的错误。如果有先检查浏览器设置里的“硬件加速”开关再更新显卡驱动。如果还不能解决就要在项目里做降级方案比如检测到 WebGL 不支持时自动切换到 2D 地图版本。另一个常见原因是地图数据没注册成功。echarts.registerMap(china, chinaJson)注册的 key 是字符串option 里的map: china必须和它完全一致大小写和空格都不能错。如果 GeoJSON 是从网络接口动态加载的还要确认异步时序确保注册完成后再调用setOption否则地图区域会是空白。4.2 柱子、散点定位偏移如果你发现柱子没有立在地图对应的省份或城市上而是偏出去一段距离十有八九是坐标系或数据顺序问题。GeoJSON 的经纬度顺序是 [经度, 纬度]业务方给的数据里经常是“纬度在前”比如维度表里习惯写“北纬 xx东经 xx”你不做转换直接塞进去柱子就会往西飞一大段。还有一种情况是数据里的坐标超出了中国范围。ECharts-GL 的 geo3D 坐标系对超出地图范围的坐标也会尝试渲染但位置会偏移到地图外面去看起来就非常诡异。排查时可以先用一个已知坐标的城市测试比如北京的 [116.4, 39.9]如果这根柱子位置正确再回头检查业务数据清洗逻辑。另外我要提醒一个经验geo3D 坐标系下的坐标是 WGS84 经纬度目前 ECharts-GL 不支持 GCJ-02 火星坐标。如果你们业务拿到的坐标本身经过了国测局偏移那就先做一次坐标转换否则所有点位都会系统性偏移几百米到几公里。4.3 多层叠加在同一个地理坐标系下显示混乱图层一多画面就乱这是必然的。三个最容易解决的问题点透明度、遮挡关系、配色冲突。透明度和遮挡是重点。geo3D 底图的opacity我建议不要超过 0.9给后面的柱子、飞线留出视觉空隙。柱子的barSize也不要一次性调得太大粗柱子虽然醒目但会挡住身后的底图区域和飞线。我通常先全部配置都用默认值跑一遍看看画面整体效果再逐层微调。不要一上来就追求每一层都“明显”正确的思路是让每一层都有它该有的存在感底图是背景柱子是主力散点是亮点飞线是动态辅助标签是解释说明。配色上我前面已经讲过用深蓝底图配暖橙柱子和亮黄飞线青色只用作关键散点。如果你们必须用一套完全不同的品牌色也一定要保证底图和上层数据之间有色相上的强对比而不是单纯靠明度区分。单纯明度区分在灯光照射下很容易失效因为 3D 场景里的光照会让深色区域变得更暗浅色区域产生高光层次反而更乱。4.4 打包部署后地图数据路径报错本地开发一切正常npm run build之后部署到服务器上地图区域就变成空白了。这个问题大多出在地图 GeoJSON 的加载方式上。如果用import chinaJson from /assets/geo/china.json这种方式Vite 默认会把 JSON 打进 JS 包里一般不会有问题。但如果你用的是fetch(/geo/china.json)相对路径请求打包后部署在子目录下路径就会变成/geo/china.json如果服务器上没有这个文件或者部署路径不对请求就会 404。我建议在项目中统一用import方式导入 GeoJSON省去路径问题。如果数据量太大必须走接口或 public 目录那就要用 Vite 的import.meta.env.BASE_URL拼路径或者放在 CDN 上避免相对路径带来的排查困难。另外一个跟打包相关的细节是 echarts-gl 的代码体积。全局引入 echarts 和 echarts-gl 打出来的包会超过 1MB在低带宽环境下首屏加载很慢。如果大屏对加载速度有要求可以在 main.js 里按需引入或者用import()动态加载地图组件让首屏先出其他内容等地图模块加载完成后再渲染。我一般会在路由层面给大屏组件单独做代码分割实测可以从首屏 JS 里砍掉将近 400KB 的体积。4.5 一些通用的调试技巧最后分享几个调试 3D 地图的技巧能帮你省不少时间。第一场景里某个对象位置不对不要反复改代码刷新看效果直接在option外面把相关数组console.log出来先确认数据结构是否符合预期。3D 图表出问题90% 的根因都在数据上而不在渲染配置上。第二调整光照或者视角参数时开启viewControl.autoRotate让地图自动旋转可以快速观察各个角度的效果。确定方向后记得关掉自动旋转否则演示时地图一直转业务方看着头晕。第三把 tooltip 打开。ECharts-GL 的 3D 系列同样支持 tooltip鼠标悬停在柱子上可以看到完整的value数组方便你确认每个点位的经纬度和数值有没有传对。对于多图层叠加的场景tooltip 还能帮你区分点击到的到底是哪个系列排查遮挡问题时特别好用。第四使用visualMap组件给柱状数据层加颜色渐变。比如数据从低到高对应蓝到红会自动生成图例这样比只用单一橙色更能表达数值梯度而且 ECharts-GL 对 visualMap 的支持是原生内置的配置方法和 2D 图表基本一致。这 5 层叠加方案我前后做过多轮迭代目前手上跑着的大屏版本就是通过这套组合实现。这套方案不局限于中国地图换一张 GeoJSON 就能快速适配省份、城市甚至特定园区的 3D 地图飞线换成地铁线路图、散点换成设备点位逻辑都完全通用。如果你正在做类似的数据可视化大屏照着这个思路先跑通基础底图再逐层往上加大概率不会翻车。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询