首页 / 资讯中心 / 文章详情

ECharts实现四川地图物质流可视化与个性化标注实战

ECharts实现四川地图物质流可视化与个性化标注实战 ★ FEATURED ARTICLE
把散落各市的物料流向、产量数据和重点园区位置揉进一张四川省地图是很多工业、物流、环保类项目都会遇到的需求。ECharts 里地图 物质流 个性化标注的组合听起来像技术大杂烩实际拆开并不复杂核心就是“地图怎么铺、流向怎么画、标签怎么放”三件事。这篇文章会把我自己在类似项目里的完整实现思路、配置文件、踩坑点全部过一遍适合那些已经有 ECharts 基础、想快速做出带业务含义区域可视化的人。你不需要懂复杂的 WebGIS一个 page 一个组件就能跑起来。1. 需求拆解地图、物质流、标注分别是什么1.1 从标题反推业务场景“四川省地图物质流个性化标注”这个标题背后对应的通常是一类区域物质流动分析系统。比如某公司有几条生产线原材料从上游矿区进到中游加工厂半成品再流转到下游园区最终产品运到各个消费地。这类项目要展示的不是简单的“某个城市某个指标大”而是“哪个点产出、哪个点消耗、东西往哪流动、流量有多大”。信息密度高单一柱状图或折线图根本装不下。这种情况下地图承担的是空间底图角色物质流承担的是“动态关系表达”角色个性化标注则负责把地图上看不出来的业务属性补全——比如某个节点的产能、某个标志性建筑、某个运输通道的容量上限。三种信息叠在同一个二维平面上必须有主次否则会糊成一团。1.2 方案选型为什么选 ECharts 而不是 WebGIS 引擎在动手之前我先对比过几个方向。用 WebGIS 类引擎做最大的问题是地图底图、投影、坐标转换等概念太多项目周期短的时候根本来不及调。另一个常见方案是用 SVG 手绘路径说实话如果你是资深美工效果会很好但执行成本偏高而且后续想接真实数据还得自己维护拓扑关系。ECharts 的好处在于它已经把“地图渲染”和“数据可视化”两件事揉在了一套配置体系里。你只要拿到四川省的 GeoJSON注册成地图然后按需求往 series 里塞数据就行。地图样式基于 series 的视觉通道控制流向用 lines 或 scatter 都能处理自定义标注可以用 markPoint、graphic 甚至自定义系列的 label 全套搞定。单页就能完成也不需要额外引入地图服务离线可用。1.3 环境准备与注意事项我这次的实现环境是 Vite Vue 3 ECharts 5但核心代码不依赖框架拿原生 HTML 也完全能跑。版本上建议 ECharts 5.x 以上早期版本对地图注册方式和 label 的配置项有区别旧项目升级时记得看迁移文档。有个比较容易忽略的细节四川省级地图里包含了很多市县边界GeoJSON 文件体积通常不小。如果项目对首屏速度有要求建议用压缩后的拓扑 JSON不要直接塞原始的未合并边界数据。还要注意如果前端会做按市下钻省 JSON 和市 JSON 的坐标系必须一致最好都从同一来源获取避免不同数据源之间边界对不上。2. 数据准备三套数据的建模与关联2.1 四川省地图 GeoJSON 的获取与裁剪先在公开地图数据仓库找一份四川省省界 GeoJSON。以我的经验这一步的关键不是找不找得到而是拿到的文件合不合规。常见问题是要素结构里只有 polygon 没有 center 坐标或者属性字段名全是英文缩写你根本不知道哪个字段对应地市名。为了后面做 map 注册和 label 匹配最好先跑一个脚本清洗一遍// 将 GeoJSON 里 properties 字段统一为 name const geojson fetchedJson; geojson.features geojson.features.map(f ({ ...f, properties: { name: f.properties.name || f.properties.NAME || f.properties.cityName } }));清洗后一定要在 ECharts 里先空载跑一次确认各市没有缺失、没有飞地、没有重叠多边形。尤其要注意甘孜、阿坝这类范围较大的地区边界精细度对物质流路径的效果有直接影响。如果发现某个边界明显变形尽量回到数据源重新取不要自己在线上拉点省下后面一大堆看错位置的时间。2.2 物质流数据建模从 Excel 到 JSON物质流数据的来源五花八门有数据库导出、有 Excel 整理、甚至还有一堆纸质台账。我最常用的是把这些数据统一整理成下面的 JSON 结构{ nodes: [ { id: site_A, name: 某原料基地, city: 某市A, category: 供应, value: 120 }, { id: site_B, name: 某加工厂, city: 某市B, category: 中转, value: 80 } ], links: [ { from: site_A, to: site_B, value: 45 }, { from: site_B, to: site_C, value: 30 } ] }这里有三个细节要提前想好。第一id 不要用中文尽量用稳定编码后面做流量映射时会方便很多。第二site 的城市字段要跟地图 GeoJSON 的 name 完全一致比如“某市A”如果地图里叫“A市”你就要么改数据要么改地图属性统一成一份映射表。第三value 字段可以单纯指物质重量也可以拆成“物资量 运输车次”我会尽量保留两个维度方便后面同时用粗细和颜色表达。2.3 个性化标注数据的字段设计标注这块最容易在后期返工因为需求方往往在第一版上线后才说“我还想标一下这个厂房的坐标”。所以我给标注数据的字段留得比较宽松{ id: mark_01, lat: 30.67, lng: 104.06, name: 核心枢纽A, type: major, summary: 年处理能力200万吨辐射周边三市 }lat 和 lng 用来定位type 控制样式summary 用来做 hover 或侧边栏。不要想着把所有展示性文字塞进 nameECharts 的地图标签在字多的时候会互相遮挡尽量把它拆成“主标题 详情”两层。另外标注点不要太多通常一张全屏地图上超过 20 个高亮标注就已经接近视觉极限了宁可做“点击加载更多”也不要一次铺满。3. 地图页面的核心实现3.1 ECharts 地图注册的两种方式和差别ECharts 里注册地图有两种常见路径。老写法是echarts.registerMap(sichuan, geoJson)然后series-map里直接指定 map。新写法还有一种是直接把 GeoJSON 塞给geo的map属性。两者用起来看起来差不多但要注意geo组件是专门做地理坐标系底图的它自己不能直接展示数据系列必须配合series-map或者scatter把数据放在 geo 坐标之上。我这次的方案是用geo做底图控制整体视觉各种物质流 series 通过coordinateSystem: geo隐射到底图上。这个组合灵活度高既能保留区域边界又能在上方铺线、铺点、铺自定义图形。如果用series-map单独做区域样式和 series 数据会耦合得更紧适合更简单的地图填色场景。const chart echarts.init(document.getElementById(map)); echarts.registerMap(sichuan, sichuanGeoJson); chart.setOption({ geo: { map: sichuan, roam: true, zoom: 1.2, itemStyle: { areaColor: #e8e8e8, borderColor: #999 } }, series: [] });3.2 geo 配置里的关键参数roam设成 true 之后地图就可以缩放和平移这在看物质流细节时几乎是必须的。但缩放之后标注和飞线的粗细很容易显得比例失调所以后面我会专门讲 scale 联动问题。zoom初始值取决于地图数据最后的显示范围不同来源的 GeoJSON 宽高比不一样有的从全国裁出来的四川会显得很扁有的专门处理过比例接近视觉预期。我一般先把 zoom 调成 1看着不行再整 0.1 步进微调不要一次给太多。itemStyle里areaColor是底图区域色borderColor是市界颜色borderWidth默认 1。如果你想强调某个重点区域可以在regions配置里单独对某个市设置样式。这个功能很有用比如把重点产业园区所在市高亮为浅蓝色其他市保持灰色视觉上立刻有了层级。3.3 series 里 map 与 geo 的联动坑很多第一次写 geo series 的朋友会遇到一个问题series 的 type 明明写了 map配置也放好了图却渲染不出来了。多半是没搞懂坐标系。series-map默认是独立坐标系它跟geo是两条路。要让区域数据和 geo 联动更稳妥的实际是直接用geo配合series的geoIndex但对于地图填色需求最直接的做法还是把区域数据做成一个 series-map 并设置geoIndex: 0这样能利用 geo 的地图边界去展示数据。series: [{ type: map, geoIndex: 0, data: mapData.map(d ({ name: d.city, value: d.value })), label: { show: true } }]但需要注意如果同时存在一个 series-map 和一堆散点、线series 的层级顺序、zlevel 都需要安排好。我通常把区域填色放在最底层飞线和散点放中间层标注放最上层这样才不会出现散点被区域遮盖的问题。4. 物质流可视化的排版与呈现4.1 物质流的“节点连线”表达物质流在地图上的表达我分成两个部分节点和连线。节点是物质产生、中转、消纳的地点用 scatter 加不同颜色的 symbol 表达连线是节点之间的物质传输用 lines 系列表达。这要比单用一个桑基图直观因为地图本身提供了空间参照。节点数据先转换成经纬度坐标。怎么转如果你的数据只有城市名可以先用一个映射表查城市中心点坐标再把偏置量加到对应节点上。比如多个节点都在同市为防止重叠我会在中心点基础上做小范围随机偏移并保证偏移范围不超过市级行政边界否则节点会飘到隔壁市去。连线部分核心配置如下series.push({ name: 物质流, type: lines, coordinateSystem: geo, zlevel: 5, effect: { show: true, period: 6, trailLength: 0.4, symbol: arrow, symbolSize: 6 }, lineStyle: { color: #ff9800, width: 2, opacity: 0.8, curveness: 0.3 }, data: linkData });4.2 折线曲率、流向箭头与动画节奏的调校curveness是控制线条弯曲程度的关键参数。0 是直线1 是接近半圆0.2 到 0.4 是相对自然的弧线。如果两个节点距离很近curveness 设太大线条容易反向绕一圈如果跨市距离很远curveness 设太小线条偏直又不够优美。动态看物质流还要靠effect。把 effect.symbol 设为 arrow效果上会有一个小箭头沿路径流动material movement 方向很直观。period影响动画周期数值越小流得越快但太快会让人觉得烦躁我常用 5 到 8 秒一轮。arrow 效果还有一个细节它沿着路径跑但不会改变路径本身粗细。如果想表达“流量越大线条越粗”就要在 data item 里单独指定 lineStyle.width。我建议按 value 的映射来基础宽度 1最大宽度 6不要无上限放大否则地区之间会出现夸张粗线把地图都遮住。4.3 利用颜色和宽度表达物质类别与流量需求里如果同时有“煤炭流向”和“水泥流向”两种物质同一个 lines 系列会很难分清。更清晰的做法是每种物质一个 lines series用lineStyle.color区分同时配合图例legend实现显隐。每种物质内部再按流量映射宽度。这里我踩过一个坑如果一类物质有 20 条线每条的 value 差异很大直接线性映射会导致小流量线条看不见。我一般先做对数变换或分位数分段。简单做法是把 value 分成 3 到 4 个区间每个区间给一个固定宽度代码大约是这样const widthMap v { if (v 10) return 1; if (v 30) return 2.5; if (v 60) return 4; return 6; };这样视觉上更容易对比也不会因为极端值而让主流线条淹没其他信息。4.4 物质流线视角下地图缩放怎么保持协调roam开启后用户会缩放地图。问题是缩放时线条宽度和箭头大小也会跟着 geo 缩。许多项目第一版上线后用户反馈“我放大到某市线条怎么变成乱麻了”。这是因为缩放后局部线条密集区被放大原先隐藏的交错全部暴露出来。我提供的方案是监听georoam事件在缩放结束后重新设置 arrow 的 symbolSize 和 lineStyle.width。缩放越大线条相对越细箭头相对越小才不至于把区域盖住。这个逻辑用代码写起来不复杂但需要配合节流函数否则缩放动画过程中会频繁 setOption。chart.on(georoam, () { // 用 requestAnimationFrame 做节流 requestAnimationFrame(() { chart.setOption({ series: [{ effect: { symbolSize: currentZoom 2 ? 3 : 6 } }] }); }); });5. 个性化标注的技巧与玩法5.1 用 markPoint 做高亮标注还是用 graphic个性化标注有两种主流实现markPoint 和 graphic。markPoint 属于 series 的一部分可以基于数据坐标在地图上打点、标文字、标 icon优点是和 series 数据绑定紧密适合“从业务数据中挑出重点节点”的场景。graphic 是更底层的图形绘制自由度极高可以摆放任意文字、矩形、图片但不会随数据自动点位适合做“完全定制化的水印、标题、角落横幅”。我通常优先用 markPoint因为它天然支持coord坐标、label文字、symbol图形、以及 tooltip。单独用 graphic 做十几个动态点位配置量会大很多。series.push({ type: effectScatter, coordinateSystem: geo, data: majorSites.map(s ({ name: s.name, value: [s.lng, s.lat], itemStyle: { color: #d33 }, label: { show: true, formatter: s.name, position: right } })) });5.2 标签避让多标注重叠的三种解法标注一多文字重叠就是必然的。比较原始的方案是缩小字号但缩到 10px 以下后根本看不清。我实验过几种方案各有适用范围。固定偏移让所有 label 统一在 marker 右侧上下交错排列适合标注数量较少、分布比较散的情况。分组展示把同一市内的多个标注点合并成一个“簇标点”点击后再展开适合城市内节点密集的情况。层级过滤地图 zoom 小于某个阈值时只显示关键标注放大后再显示次级标注适合省级看宏观流向、市级看细节点位的情况。第三种在实际业务中最实用。ECharts 里没有内置层级显示逻辑但可以通过事件监听手动控制 series data 的长度和可见性。比如监听georoam事件后按照当前 zoom 来重新设置 series 的数据。5.3 tooltip 与信息面板的联动怎么让标注不白做标注不只是视觉上打一个点用户点击后需要看到详情。我用过的两种交互方式第一种是 tooltip。把标注点的tooltip.formatter写成 HTML 模板做成带表格的悬浮卡片。优点是零额外代码缺点是信息一多悬浮框会盖住地图移动端体验也一般。第二种是旁边加信息面板。点击标注点时将一个对象传给 Vue 或 React 的组件状态右侧滑出一个详情栏。这里有良好的扩展性可以显示图片、关联数据、跳转链接也可快速集成业务流程。这个方案我更推荐因为业务标注最终都会希望落到具体台账。选中态的处理要注意ECharts 的点击事件拿到的参数里有dataIndex你可以把该 dataIndex 对应的数据置为高亮再重新 setOption。如果不清除上一次的高亮每次点击都会留下一块残影看着很乱。6. 常见问题与调优实录6.1 地图不显示、数据空白、线对不上坐标新手最常遇到的就是地图白屏。先别急着怀疑代码按这个顺序排查检查 GeoJSON 是否成功加载console 里有没有 registerMap 后的报错。检查 series 或 geo 的 map 名称是否跟注册名称完全一致包括大小写和空格。检查 series 数据里的经纬度是否反向。很多数据源用的是“纬度、经度”存坐标而 ECharts 地图要求的是“经度纬度”。这一条我至少见了四五次。检查数据中是否混入了 null 和 NaN。ECharts 遇到无效坐标不会报错但会悄悄不画。如果 resized 之后地图被截断记得调用chart.resize()。如果是在 Tab 页里渲染需要在页面可见时再初始化否则宽度检测会是 0。6.2 性能优化大物质流数据怎么推流不卡当链路数据超过 300 条时ECharts 渲染依然能撑住但动态动画会明显掉帧。我有几个实测有效的降载措施减少effect的数量只给流量前 N 的关键路径加飞线箭头。关闭animation或把animationDuration调短避免缩放时大量重绘。利用large模式的 lines 配置ECharts 的大数据模式会走画布优化路径牺牲部分自定义效果换取流畅度。数据聚合先把同一方向的重复链路合并成一条聚合线再在 tooltip 里显示详细子链路数量。6.3 字体、配色与地图风格统一地图风格和业务大屏的视觉体系最好提前定清楚。我用过比较稳的方案是底图区域色控制在低饱和度的浅灰或浅蓝绿边界线用中等灰度物质流线条用高饱和的主题色比如橙红或青色标注点用对比色比如红色或黄色。三种视觉层级一目了然。如果背景是大屏深色模式底图areaColor用深蓝黑边界色用白色透明度 0.4。如果背景是汇报 PPT 式的白色底图用浅灰边界色用深灰。文字标签的颜色一定要跟底图区色有足够对比度否则一放大就迷失。还有一点地图上的市名 label 默认有点大我一般会设成 10 或 11 号字并在 zoom 变小或变大时动态改变。省内城市名密集的区域比如川中一带如果不调整字号挤在一起很难看。可以把 label 的show根据 zoom 层级开关同时开启avoidLabelOverlap: true来自动避让。7. 扩展方向从静态图到可交互分析工具前面写的这套其实已经是一个可交付的地图分析模块。但做成产品的话还能往两个方向扩展。第一个方向是时间轴。很多物质流数据是按月度或季度更新的可以在 ECharts 外面包一层时间滑块切换时间维度后更新 nodes 和 links。注意切换时需要整体更新 series 的 data不要让旧数据和新增数据混合。我通常会重写数据数组而不是在原数组上 push。第二个方向是联动下钻。省级地图点某一个市跳转到该市的区县地图并展示该市内部的物质流网络。实现上只需要额外注册对应市的 GeoJSON点击事件里替换地图和 data 即可。但要注意GeoJSON 的注册名不能重复下钻后要重新 registerMap并处理返回上一级的交互按钮。这个扩展我目前在模拟项目X里做过一版用户体验提升非常大。地图从“展示页”变成了“分析工具”用户会主动去点、去筛、去比。对做工业管理、环保调度或者供应链规划的朋友这套思路很值得一试。8. 写在最后的实操心得我自己在做过几套区域物质流全是洞之后的一个体会ECharts 的 map 其实不难真正吃掉大量时间的是数据对齐和样式微调。数据对齐靠前期规范和映射表样式微调只能靠反复审看。这里分享一个很实用的小技巧在开发阶段给地图容器加上一条淡色网格背景并用 console 把每个标注点的经纬度实时打印出来。这样当某个点位置不对时你能很快判断是经纬度反了、还是 GeoJSON 问题还是偏移量没算对。可视化层级的 bug很多时候用调试台比用肉眼看更高效。另外每次 setOption 前尽量把 option 对象完整构建好不要零零散散地往原对象里塞字段。尤其是 geo、series 这种嵌套层级很深的配置直接改原对象很容易改到一半变成 undefined。我比较推荐在函数里重新组装一个 option再用 setOption 整体覆盖省心也不容易留隐藏问题。这套四川省地图物质流个性化标注的组合改改 geoJSON 和 series data完全可以平移到任何省级区域。数据梳理清楚之后真正写配置的时间其实只需要一两天。如果你正好要做类似的区域可视化按这篇文章的顺序先把数据和事件模型定好后面实现会顺畅很多。
阅读完成 · 觉得有帮助?
咨询建站