ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

图书销售全国地图可视化大屏:从GeoJSON到ECharts实战教程

图书销售全国地图可视化大屏:从GeoJSON到ECharts实战教程 在做了前两期的大屏项目之后收到最多的私信就是问地图能不能搬上大屏。说实话最开始我是有点抗拒的因为地图可视化看起来简单但实际上要处理GeoJSON、坐标系、缩放适配、交互联动一整套东西比普通柱状图折线图麻烦不少。不过等真正做完这版《图书销售展示全国地图可视化》大屏我反而觉得它是整个系列里性价比最高的一期——视觉冲击力最强比赛现场最容易让评委眼前一亮代码量却完全在可控范围内。这篇文章就把我从0到1做这版大屏的完整思路写下来包含我自己踩过的坑适合正在准备大数据可视化比赛作品、期末作业或者单纯想给自己的项目里加一屏地图的同学参考。1. 为什么用地图展示图书销售“一屏看全国”的设计逻辑1.1 图书销售数据的天然地理属性图书销售这个场景选得很有意思。出版社看的是全国各区域的发行情况新华书店系统看的是渠道覆盖电商运营看的是爆款书的省份热度这些业务需求全都长在一张地图上。相比“华东区销售额同比增长12%”这样的文字描述地图上用颜色深浅直接把省份销量渲染出来哪一个省是深红色、哪一片区域颜色发白观众一眼就能形成判断不需要在脑子里做二次转换。我在这个项目里规划了三层数据展示维度第一层是省份维度的年度累计销量用地图区域色块呈现第二层是重点城市的实时订单脉冲用涟漪散点在地图上做动态扩散第三层是左右两侧数据面板用于展示总额、分类占比、地区排名等明细信息。这样一屏下来既有宏观的空间分布又有微观的数值细节信息层级非常清楚。1.2 地图大屏与普通Dashboard的区别先说个可能扎心的结论如果你只是把一张ECharts地图塞进普通网页中间那它叫“图表页”不叫“可视化大屏”。大屏和Dashboard的核心区别在于叙事逻辑。Dashboard默认是让人凑近了看数据的鼠标点来点去大屏则是让人站在两米外扫一眼就能抓住重点的。所以设计地图大屏时要主动放弃一些交互上的精细操作把精力放在视觉主次的构建上。我这一版的做法是让地图占据画面大概50%的面积放在中间偏左的位置右侧面板放辅助数据顶部是醒目的投影标题“全国图书销售数据实时监测平台”。左边和右边各留出四个上下排列的信息卡片区域用于放置关键指标和榜单。1.3 这个场景怎么在比赛和作业里讲出彩同样的技术会讲故事的队伍分数就是更高。图书销售地图大屏有一个天然的优势数据可解释性强。“广东销量最高”可以解释为人口基数大、阅读消费能力强“西藏颜色偏淡”可以解释为区域渠道覆盖不足下一步可以针对性做推广。在比赛答辩时评委最喜欢听到的就是对数据的解读能力而不是单纯背一句“使用了ECharts地图组件”。我建议你准备作品时提前编好两三个数据故事一是在地图上找出一匹黑马省份解释它为什么增长快二是对比相邻省份的差异给出业务建议。这会让你的作品从“作业”直接抬升到“作品”的层次。2. 技术选型HTMLCSSJS凭什么撑起一个可视化大屏2.1 为什么还要回归原生三件套现在可视化领域早就被Vue、React等框架占领了但我在做比赛型和作业型大屏时仍然坚持用原生HTMLCSSJS一个重要原因是现场可靠性。比赛现场的电脑是什么环境谁也说不准没有Node环境、装不了依赖、网络受限这些都是常见事故。原生三件套只要有一个浏览器就能跑双击index.html就能打开零部署成本。而且从学习价值来讲用原生技术完成一版复杂大屏你对CSS布局、JS数据处理、异步加载的理解会明显比套用框架更扎实。框架帮你藏起来的那些细节恰恰是大屏最容易出问题的位置。等原生版本跑通了再迁移到Vue里也就是CtrlC、CtrlV的事。2.2 ECharts引入方式与版本选择的建议地图渲染部分我选用了ECharts 5.4.3。选它的理由很直接生态成熟、中文文档齐全、开箱即用的地图支持。在国内做可视化大屏ECharts基本是事实标准评委和老师看到它不会觉得陌生也方便你在答辩时解释原理。引入方式建议用Script标签直接引CDN不要用npm。比赛现场很可能没有网络所以更稳妥的做法是提前把echarts.min.js下载到本地js目录里。引用版本要固定不要用“最新”这种不稳定的写法避免未来ECharts升级导致配置项语法变化项目放半年后再跑突然报错。script srcjs/echarts.min.js/script script srcjs/china.js/script script srcjs/index.js/script这里注意china.js就是地图GeoJSON数据文件它不是ECharts框架本身而是单独的一份地图数据脚本。引入顺序必须在echarts.min.js之后因为china.js内部会调用ECharts的注册接口。2.3 项目文件结构与源码组织大屏项目最忌讳把所有代码堆在一个HTML里那会让后期维护变成灾难。我建议的目录结构是这样的book-sales-dashboard/ ├── index.html # 主页面 ├── css/ │ ├── base.css # 全局样式与重置 │ ├── layout.css # 大屏布局与面板 │ └── animation.css # 动画和装饰效果 ├── js/ │ ├── echarts.min.js # ECharts核心库 │ ├── china.js # 中国地图GeoJSON │ ├── mockdata.js # 模拟数据生成器 │ ├── map.js # 地图核心渲染逻辑 │ └── panels.js # 侧边面板图表逻辑 ├── images/ │ └── bg.png # 背景纹理这样拆分的逻辑是数据、渲染、布局三者解耦。比赛现场如果需要临时改数据只需要编辑mockdata.js不需要去渲染代码里翻找。如果有同学问你“数据源在哪”你也可以直接指出这个文件体现你的工程意识。3. 大屏布局的搭建从设计稿到像素级还原3.1 1920x1080基准与自适应缩放方案大屏开发的第一步是确定基准分辨率。绝大多数大屏硬件是1080P或4K我习惯以1920x1080作为设计基准然后用一套等比缩放的逻辑去适配其他分辨率。这里最推荐的方式是在最外层容器上用transform: scale()做整体缩放。html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; background: #030b1a; } #screen { width: 1920px; height: 1080px; transform-origin: 0 0; overflow: hidden; position: relative; }配合一段JS在页面加载时计算缩放比例让整个大屏等比例适配到任意屏幕上function autoScale() { const screen document.getElementById(screen); const scaleX window.innerWidth / 1920; const scaleY window.innerHeight / 1080; const scale Math.min(scaleX, scaleY); screen.style.transform scale( scale ); } window.addEventListener(resize, autoScale); autoScale();这种方案的好处是整个1920x1080画布里的定位逻辑完全不用改写死像素坐标也不会乱。注意transform-origin: 0 0必须加否则缩放会以元素中心为基准左上角对不齐。3.2 CSS 3D变换rotateY与translateZ的实际效果标题和装饰动画是大屏氛围感的重要来源。很多同学看到CSS里面写transform: rotatey(60deg) translatez(300px)就懵了不知道出来是什么样子。我实际试过之后可以给你一个直观描述这个复合变换的含义是先让元素绕Y轴旋转60度然后沿着变换后自身坐标系的Z轴方向平移300px。结果就是元素看起来像一扇被推开一半的门并且相对于屏幕向外“飘”出了一段距离。如果你想让大屏在加载时产生“数据从远处飞来”的入场效果可以把它用在卡片或数字层的动画里。配合父容器设置perspective空间感才会出来.loader-wrapper { perspective: 1000px; } .rotate-tip { transform: rotateY(60deg) translateZ(300px); transition: all 1s ease; } .rotate-tip.active { transform: rotateY(0deg) translateZ(0px); }这里最容易被忽略的点是perspective尽可能加在父元素上而不是动画元素本身否则每个子元素会以自己为原点建立透视视觉上会非常奇怪。想让3D旋转看起来正常这是一个核心规则。3.3 装饰边框、标题栏与涟漪光圈大屏如果没有装饰元素再好的地图也会显得干瘪。我的做法是在面板边角加CSS绘制的发光角标同时做一条顶部流动光效的标题栏。标题栏可以拆成三段中间主标题、左右两侧的对称光影。这样视觉重心稳定观众视线不会被引导到旁边去。涟漪光圈是另一个强烈推荐的装饰效果。ECharts的effectScatter只能在地图上做涟漪如果面板上的某个重点数字也想要雷达扫描的效果可以完全用CSS实现.ripple { position: relative; width: 80px; height: 80px; border-radius: 50%; border: 2px solid rgba(64, 158, 255, 0.8); } .ripple::after { content: ; position: absolute; inset: 0; border-radius: 50%; border: 2px solid rgba(64, 158, 255, 0.5); animation: ripple-diffuse 1.5s ease-out infinite; } keyframes ripple-diffuse { 0% { transform: scale(1); opacity: 1; } 100% { transform: scale(2.5); opacity: 0; } }用::after伪元素配合scale放大和透明度衰减就做出来了雷达波纹效果不需要额外加载任何库。4. 地图可视化核心实现全国地图上的图书销售数据4.1 地图注册与数据来源ECharts本身不内置中国地图数据你需要先注册一份GeoJSON。我用的是ECharts官方配套的china.js加载完这个文件后ECharts全局环境中就已经存在名为china的地图注册项了。在JS里初始化项目时你可以二次确认注册是否成功const registered echarts.getMap(china); if (!registered) { console.error(中国地图未加载成功请检查 china.js 是否在 echarts 之后引入); }这个检查很有必要。很多同学的地图大屏为什么不出图十次里有八次都是因为china.js没加载成功或者顺序错了而浏览器控制台又不会给出明确的报错只有一句Component series.map not exists类似的信息排查起来很折磨人。4.2 用地图series渲染省级销售分布核心的地图配置项我放在一个函数里管理。这里要注意的是visualMap它决定了颜色深浅和数值范围的映射关系也是地图能表达数据分布的关键。function initMap() { const mapDom document.getElementById(mapContainer); mapChart echarts.init(mapDom); const option { tooltip: { trigger: item, formatter: function(params) { if (params.value) { return params.name br/销量 params.value.toLocaleString() 册; } return params.name br/暂无数据; } }, visualMap: { min: 0, max: 8000, left: 30, bottom: 30, text: [高, 低], calculable: true, inRange: { color: [#e0f3ff, #74b9ff, #0984e3, #6c1fbf] } }, series: [{ type: map, map: china, roam: false, zoom: 1.1, label: { show: true, fontSize: 10, color: #fff }, itemStyle: { borderColor: #00d4ff, borderWidth: 1.2, areaColor: #0a2b4b }, emphasis: { label: { color: #fff, fontSize: 14 }, itemStyle: { areaColor: #ffd700, shadowBlur: 20, shadowColor: rgba(255, 215, 0, 0.6) } }, data: salesData }] }; mapChart.setOption(option); }这段配置里有个值得注意的细节visualMap的max我没有设成一个固定的大数字而是根据数据最大值动态计算避免某一项的数值过高导致整个地图颜色过深、失掉层次。具体做法就是在生成模拟数据后取一次Math.max。4.3 数据格式和省份名称对齐ECharts地图的数据难点永远不是渲染而是数据里的省份名称与地图GeoJSON里的名称完全匹配。china.js里的省份名称是“广东”“北京”“新疆维吾尔自治区”这种全称如果你的数据源里写的是“广东省”“北京市”地图上就会对应不上表现为某省份一直显示“暂无数据”。所以在数据处理环节我专门写了一个名称归一化函数function normalizeProvinceName(name) { return name .replace(/省$/, ) .replace(/市$/, ) .replace(/壮族自治区$/, ) .replace(/维吾尔自治区$/, ) .replace(/回族自治区$/, ) .replace(/特别行政区$/, ); }用replace把常见后缀去掉保证数据能正确落在地图区域上。这个函数虽然小但没有它你的地图就永远是残缺的。4.4 涟漪散点图叠加重点城市销售脉冲只做区块填色画面会显得安静。我更推荐在地图上叠加一个effectScatter系列让几个重点城市的位置出现涟漪扩散效果模拟“正在发生”的销售脉冲。这个系列在视觉上给评委的感知非常直接像雷达波一圈一圈往外推。const cityData [ { name: 北京, value: [116.46, 39.92, 3200] }, { name: 上海, value: [121.48, 31.22, 2800] }, { name: 广州, value: [113.23, 23.16, 2600] }, { name: 成都, value: [104.06, 30.67, 2200] } ]; series.push({ type: effectScatter, coordinateSystem: geo, data: cityData, symbolSize: 10, showEffectOn: render, rippleEffect: { scale: 4, brushType: stroke }, itemStyle: { color: #00ffff, shadowBlur: 10, shadowColor: #00ffff } });坐标系的配置要在geo字段里先定义好。这里有个容易踩的点如果地图series里没有配geo而是直接类型为map那么effectScatter上面的coordinateSystem: geo就会报错。我在项目里的做法是在option里同时配置geo和series[map]geo专门负责坐标系map系列负责区域染色二者互不干扰。5. 数据层处理模拟数据、格式化与动态刷新5.1 用JS模拟数据的合理套路真实项目里地图数据来自后端接口但作业和比赛场景通常只能造数。造数也是个技术活不能让数据看起来太假。比如销量最高的省份不应该总是广东浙江相邻省份之间也不该出现极端跳变。我写模拟数据时先定一个基础量再叠加地区系数。地区系数可以反映人口和经济水平这样造出来的数据才有业务逻辑。function generateMockData() { const base { 广东: 7800, 江苏: 6900, 山东: 6100, 浙江: 5800, 河南: 5300, 四川: 4900, 湖北: 4300, 湖南: 3800, 北京: 7200, 上海: 6800, 安徽: 3500, 福建: 3300, 陕西: 2700, 重庆: 2800, 辽宁: 2500, 河北: 2400 }; // 其他省份的默认值统一做一个低基数随机 const national [天津,山西,内蒙古,吉林,黑龙江,江西,广西,海南,贵州,云南,西藏,甘肃,青海,宁夏,新疆]; const result []; national.forEach(function(item) { result.push({ name: item, value: Math.round(800 Math.random() * 1500) }); }); Object.keys(base).forEach(function(item) { result.push({ name: item, value: Math.round(base[item] * (0.85 Math.random() * 0.3)) }); }); return result; }这样生成的销量最大值不会超过8000太多地图颜色分布跨度可控每个省份又都有细微差异演示起来很自然。5.2 数据格式化与字符串判断技巧除了销量数字大屏上还会显示诸如“已覆盖省份”“热销品类”等文案。数据处理阶段经常会用到字符串判断和格式化比如从一段描述里判断省份列表是否包含某个地区。const coverStr 北京|上海|广东|浙江|江苏|四川; if (coverStr.includes(广东)) { console.log(华南区域已覆盖主销省份); }includes是判断字符串是否包含子串最直接的API比indexOf ! -1的可读性好很多。大屏演示时我还会把大数字转成带千分位分隔符的小写格式直接用toLocaleString()完成代码干净又不容易出错。5.3 模拟实时刷新让大屏“动”起来大屏最忌讳静态不动。评委站到屏幕前如果发现数字纹丝不动会立刻觉得是假数据。我加了两个维度的动态效果一是地图上的涟漪散点每5秒改变一次数值强度二是侧边排行榜每10秒做一次平滑动效展示变化。setInterval(function() { const randomIndex Math.floor(Math.random() * salesData.length); salesData[randomIndex].value Math.round(salesData[randomIndex].value * (0.9 Math.random() * 0.24)); mapChart.setOption({ series: [{ data: salesData }] }); }, 5000);这里的setOption只需要传入变化的部分ECharts内部会做增量更新不会因为频繁刷新导致闪烁或卡顿。有一点要注意visualMap的max不要随每次刷新都重新计算否则地图色板的颜色层次会不停跳变观感很差。6. 踩坑实录地图不显示、缩放糊、3D转不动6.1 地图不显示问题多半在加载顺序我调试这版大屏时最崩溃的就是地图区域一片空白。排查链路我建议按顺序走先在浏览器Network面板确认china.js有没有加载成功再看Console有没有报错最后看echarts.getMap(china)是否存在。一大半的空白问题都是资源没加载进来。还有一种是跨域导致的地图加载失败。如果你单独把GeoJSON放在一个.json文件里再用fetch读取在file://协议下会直接被浏览器拦截。我后来统一改成把GeoJSON封装成china.js脚本用全局变量暴露出来彻底绕开跨域问题。6.2 小屏笔记本上糊屏别让scale扛所有事大屏项目做完后在同事的小笔记本上演示经常发现字体和地图边缘发虚。原因是transform: scale()缩放时浏览器是按比例整体缩放的低于1的缩放值会让一些细线被采样成模糊的半色调。这是个纯CSS问题处理思路是不要指望一个等比缩放吃遍所有分辨率。更稳的适配方式是做两版布局断点分辨率大于1600px时用1920基准的缩放方案低于1280px时切换紧凑布局把右侧面板的宽度收窄地图缩小。比赛场地不固定这个提前量一定要留出来。6.3 容器隐藏状态下图表初始化宽度为0大屏经常会做分步加载或者等待动画地图容器初始状态可能被CSS设置为display:none或宽度为0等动画结束再显示出来。这时候你在隐藏状态下init地图ECharts会认为容器宽高是0渲染出一个空地图或尺寸错乱的地图。解决方案是在容器变为可见后主动调用一次resize()但我更推荐的做法是动画完成后延迟初始化地图setTimeout(function() { initMap(); }, 600);等入场动画跑完、容器已经拿到实际尺寸后再创建图表就完全不会出现这个问题。如果你已经初始化了补救手段是mapChart mapChart.resize();6.4 3D旋转不生效多半是透视属性缺失CSS的3D效果非常依赖perspective和transform-style: preserve-3d。我见过很多同学把rotateY(60deg)写在元素上但是父元素没有设置perspective导致旋转看起来只是一个扁平的伸缩变化完全没有空间深度。这个不是浏览器兼容问题而是CSS 3D坐标系没建立起来。另外要注意层叠上下文。如果动画元素被overflow: hidden的父容器裁剪或者z-index设置不当3D飞入效果会被后面的面板挡住。大屏的层级顺序建议统一为“背景层 - 地图层 - 面板层 - 顶层动效”按这个顺序分配z-index就不用反复试position了。7. 完整代码结构与比赛演示的加分技巧7.1 源码文件的核心分工文末提供的源码包括完整的index.html、css目录、js目录和images目录。你拿到后首先打开mockdata.js看数据结构再打开map.js看渲染逻辑最后回index.html里对照布局结构基本一个晚上就能吃透改完。源码里我把地图主文件map.js注释写得非常细每个配置项后面都标了作用。这样不仅方便你二次开发比赛答辩时也直接可以当成代码讲解的底稿让评委觉得你的工程习惯很好。7.2 比赛答辩时怎么把这个作品讲清楚做比赛项目光做得好不够讲得好同样重要。我建议答辩流程按“三句话”组织第一句话讲业务图书销售数据分布在各省要用一张大屏实时掌握全国市场第二句话讲技术前端基于HTMLCSSJS原生开发地图渲染采用ECharts的map系列加effectScatter联动数据层使用模拟动态刷新模拟实时接入第三句话讲亮点颜色深浅代表销量梯度、涟漪散点代表城市热卖、左右面板提供明细支撑。演示时一定要先让地图出场停留至少五秒不要操作让评委看清数据分布。然后再手动刷新一次数据或切换面板展示动态效果。节奏比内容更影响印象分。7.3 拿到源码后可以继续扩展的三件事一是接入真实接口把mockdata.js里的数据源替换成fetch请求接口返回同样的数组结构即可二是做省份下钻点击某个省跳转到该省的城市地图需要额外准备城市级GeoJSON核心逻辑和省级地图完全一致三是加入自动轮播高亮定时把某几个重点省份通过dispatchAction高亮播放增强大屏的“演示感”。我个人还是最推荐完成省份下钻这个扩展。它能把你的地图大屏从“展示作品”提升到“完整产品”也是比赛评委最喜欢追问的功能。技术上不复杂但需要你对地图数据联动有更系统的理解。最后分享一个我每次演示前必做的小事情检查浏览器缩放比例是不是100%。浏览器缩放只要是90%或者125%大屏的等比适配就会产生一圈白边或者裁掉边缘信息我在现场已经见过太多团队因为这个小问题翻车。代码写得好不如演示保护好这句话在比赛现场永远成立。
返回列表