资讯动态

ECharts热力图实战:昌平区街镇地图数据可视化Demo

发布时间:2026/10/14 9:36:03 来源:尧图企业网站定制
简介本资源是一份面向Web前端开发者与数据可视化学习者的Echarts地图热力图实战示例以北京市昌平区各街镇为展示对象演示了如何将地理边界数据与业务热度数值结合直观呈现区域分布差异。示例完整覆盖了从GeoJson地图数据处理、Echarts地图series配置到tooltip悬浮交互的核心链路适合希望快速上手Echarts地图应用或开展地理信息可视化练习的初中级开发者参考。资源包共包含6个文件压缩后大小约406KB主要文件类型为js脚本、json数据与html页面js文件提供Echarts核心库及统计组件支持json文件存储昌平区街镇边界GeoJson数据html文件则用于承载图表页面与交互逻辑。目前已有411人学习浏览示例中展示了如何使用手工绘制地图转换得到的GeoJson数据渲染区域轮廓并通过自定义热量数据集填充颜色同时利用tooltip配置项实现鼠标悬停展示街道相关信息的交互体验整体代码结构清晰便于对照学习与二次修改。1. 昌平区街镇热力图 Demo把“看不见的密度”变成能拍板的地图做数据可视化的人应该都有同感一张表格放在面前你很难一眼说出“哪个街镇最集中”但换成地图上一片从蓝到红的色块马上就知道该把资源往哪投。这个 Demo 就是用 ECharts 把北京市昌平区下辖街镇的面状边界加载进来再通过热力图把业务数据映射到地理空间上解决“数据有、位置有、但看不出空间分布”的问题。适合那些刚接触地图可视化、想在本地快速跑通一版的前端同学也适合需要给运营或管理层做汇报原型的场景。整个方案只依赖静态 GeoJSON 加一个图表库不引入 GIS 服务部署成本低替换别的区县数据也能直接复用。2. 先备好昌平区街镇边界GeoJSON 从哪里来、怎么挂进 ECharts2.1 两种静态数据来源在线 GeoJSON 站点与业务侧自维护要做街镇级别的热力图第一步不是写图表配置而是拿到能用的昌平区街镇边界数据。市面上常见的做法有两种一种是从提供行政区划 GeoJSON 的在线数据站点下载现成文件这种文件通常已经按区县做好了切割一个区县对应一个 JSON里面包含该区县下所有街镇的边界坐标另一种是业务后端自己维护行政区划接口返回结构同样是 GeoJSON FeatureCollection。Demo 阶段我更推荐第一种因为不需要和后端联调文件直接落到本地就能跑。拿到文件后建议放到项目的public/map/目录下命名用changping.json。这样在开发环境里访问路径就是/map/changping.json部署时也能原样携带。需要注意这个文件的内部结构一个合格的 GeoJSON 必须是一个FeatureCollection每个feature的properties.name字段对应一个街镇名称。你在后续配置里写的系列数据最终就是靠这个name字段和地图边界关联起来的。我在本地经常先跑一段脚本把文件里的街镇名称全部打印出来确认没有乱码或多余空格再继续。2.2 用 fetch 加 registerMap 把地图注册进 EChartsECharts 本身不内置任何区县边界它只是渲染器。你要先通过registerMap把 GeoJSON 注册成一个可用的地图名之后再在geo组件或map系列里引用这个名称。下面是一个最小注册流程import * as echarts from echarts; async function loadChangpingMap() { // 静态文件放到 public/map 下相对路径能适配多数开发服务器 const response await fetch(/map/changping.json); const geoJson await response.json(); // 第二个参数必须是解析后的对象不是 JSON 字符串 echarts.registerMap(changping, geoJson); // 验证一下打印所有街镇 name方便后续对齐数据 const names geoJson.features.map((item) item.properties.name); console.log(names); }这段代码的逻辑很直接先请求文件再注册地图最后打印街镇名称列表。这里有几个容易踩的点。第一fetch拿到的响应必须调response.json()直接把字符串传给registerMap是无效的第二registerMap的调用时机必须在setOption之前否则图表初始化时找不到地图第三注册名要和后面配置里的map: changping保持完全一致大小写都不能差。2.3 坐标系统一为什么“看起来差不多”却对不上昌平区街镇热力图最常见的偏差问题几乎都出在坐标系上。国内地图平台的 GeoJSON 绝大多数基于 GCJ-02 坐标系也就是国内测绘部门加密后的坐标体系而很多业务系统里的点位数据来自 GPS 设备或国际通用数据源用的是 WGS-84。两者差距通常在几百米在街镇级别的地图上就会表现为点落到了地图外面或者整块热力区域和边界错开。我的建议很简单整个 Demo 从底图到业务数据统一使用同一种坐标系。如果底图是 GCJ-02数据点的经纬度也必须先转换成 GCJ-02 再传入。ECharts 不会帮你做任何坐标系转换它只是把你给它的数值原样映射到地图上。判断是否偏位的笨办法是在数据里找一两个明显的地标点比如某个大型商圈看热力点和实际位置是否吻合。这一步不做好后面所有调整都是白费。3. 用 heatmap 系列做点状热力坐标点数据直接铺到街镇底图上3.1 什么场景用 heatmap 而不是 map散点密度是另一种“热力”在开始写代码前先明确一个区别ECharts 里的map系列是把数据按行政区域聚合后填充颜色而heatmap系列是把带有经纬度的点画成一片渐变光晕表现的是点在空间上的密集程度。标题里的“街镇热力图”业务上通常需要两种形态都支持。如果你手里的是外卖订单、故障工单、用户打卡这类带坐标的明细数据用heatmap叠加到底图上最合适它能看出人群或事件在哪个位置聚集如果你手里的是已经按街镇汇总好的指标比如常住人口、企业数量那就该用第 4 章的map系列做面状染色。我一般先跑一版点状热力图确认数据分布没有明显异常再聚合到街镇层面看行政口径的结果。3.2 最小热力 option一个 series 同时呈现地图形状和热力强度下面这段配置是点状热力图的最小子集它把一个geo组件和一个heatmap系列组合在一起const chart echarts.init(document.getElementById(chart)); chart.setOption({ geo: { map: changping, roam: true, zoom: 10, itemStyle: { areaColor: #1e2a3a, // 底图区域颜色选深色更显热力 borderColor: #4a6785 // 街镇边界线颜色 } }, visualMap: { show: false, min: 0, max: 150, inRange: { color: [rgba(30, 30, 30, 0), #2e7eb6, #f7d13b, #e2342f] } }, series: [ { name: 点位密集度, type: heatmap, coordinateSystem: geo, data: [ { value: [116.253, 40.218, 95] }, // [经度, 纬度, 热力值] { value: [116.192, 40.225, 132] }, { value: [116.281, 40.196, 60] } ], pointSize: 18, blurSize: 25, opacity: 0.6 } ] });这段配置里最关键的是coordinateSystem: geo它告诉 ECharts这个热力系列不使用直角坐标轴而是把数据里的前两个数值当作经度和纬度映射到上面geo组件定义的地图上。visualMap控制热力值的颜色映射范围和渐变方向对heatmap系列来说它决定哪些值显示为冷色、哪些显示为暖色。data里每一项是一个数组前三项依次是经度、纬度、热力值这个顺序不能写反否则点会直接画到外海去。3.3 pointSize、blurSize、opacity把参数调到能看且不糊这三个参数直接决定热力图长什么样也是初学者最容易乱调的地方。pointSize是单个热力点的半径单位是像素调太大点与点会融成一片调太小则整个图显得稀疏blurSize是光晕扩散范围它比pointSize大时过渡更柔和热力中心不会突兀opacity是整体透明度要考虑底图的边界线是否还能看清。在普通笔记本电脑屏幕下我通常从pointSize: 16、blurSize: 22、opacity: 0.65起步然后根据数据点的密集程度微调。点越密集pointSize越要调小点越稀疏反而要加大blurSize让光晕连成片才能看出趋势。有一点容易被忽略这些数值的单位是屏幕像素不是地图上的地理距离。地图放大时点的相对位置会跟着变但热力光晕的半径不变所以会出现“图放大了但热力圈还是那么大”的现象这是正常行为。如果你希望热力范围随缩放联动那就只能通过监听georoam事件动态改参数这个后面再说。还有个小建议visualMap.show在纯展示场景可以设成false但在自查数据分布时最好打开免得热力值区间设置不合理所有点都显示成同一种颜色。4. 把数据聚合到街镇区域填充式热力图与 visualMap 联动4.1 数据结构对齐name 对应 properties.name别靠坐标硬拼点状热力图的优势是保留了真实位置但它没法直接回答“哪个街镇最多”。要做街镇口径的统计就得把明细数据按行政区域聚合然后让每个区域渲染出独立的颜色块。这种面状热力图在业务里更常见因为拿到的指标大多已经按部门、按片区统计好了。聚合后的数据格式非常简单一个街镇对应一个对象其中name必须和 GeoJSON 里properties.name完全一致value是要展示的数值。我习惯先从后端查询里直接输出这种结构避免在前端做匹配const regionData [ { name: 示范街道A, value: 320 }, { name: 示范街道B, value: 180 }, { name: 示范镇C, value: 245 } ];这里要特别强调name不是给人看的而是给 ECharts 做关联用的主键。GeoJSON 里叫“示范街道A”你的数据里就必须一字不差连空格和标点都不能有差异。很多“地图渲染了但区域没变色”的问题根因就是这里对不上。我建议把 2.2 节里打印出来的names数组当成对照表和regionData逐一比对而不是凭肉眼记忆。4.2 map 系列加 visualMap 的最小配置与配色区域填充式热力图的核心是用map系列配合visualMap实现连续色带。map系列负责按边界填充颜色visualMap负责把数值区间映射成颜色梯度。配置如下chart.setOption({ tooltip: { trigger: item, formatter: (params) { // params.name 是街镇名params.value 是当前数值 return ${params.name}br/热力值${params.value ?? 暂无数据}; } }, visualMap: { min: 0, max: 400, calculable: true, inRange: { color: [#2c7bb6, #ffffbf, #d7191c] }, text: [高, 低], orient: vertical, right: 20, bottom: 30 }, series: [ { name: 街镇聚合值, type: map, map: changping, roam: true, data: regionData, label: { show: true, fontSize: 10 }, emphasis: { label: { show: true, fontWeight: bold } } } ] });visualMap的min和max决定了数值到颜色的映射刻度。如果数据里最大值是 380max设成 400 没问题如果最大值只有 120max还写 400那所有区域都会显示成最冷的蓝色看起来像“没渲染”。我一般先把max设成业务阈值的合理上限再打开calculable: true让使用者能在界面上拖动选择区间同时也能方便你自查颜色分层是否合理。inRange.color里的颜色数组是有顺序的第一个对应最小值最后一个对应最大值蓝到红是默认直觉配色也可以换成公司品牌色。4.3 更接近产品的细节提示框、点击事件与图例开关在 Demo 跑通后别急着收工有三个细节值得补上。第一个是tooltip的formatter上面代码里已经写了关键点是params.value可能为undefined因为有些街镇没有数据要兜底显示占位文案否则提示框里会出现一行 “undefined”很不专业。第二个是点击事件业务方经常希望点某个街镇能看到下钻明细chart.on(click, (params) { if (params.componentType series params.name) { // 把选中的街镇名称抛给外部联动右侧明细表格 console.log(选中的街镇, params.name); } });第三个是图例开关。calculable: true时用户能拖动控制区间但如果你希望在图例上直接隐藏某个范围之外的区域可以再加一个visualMap的show配置并配合series里的selectedMode控制。多数场景下保留默认即可但别让图例在移动端占掉太多空间设置right和bottom时给手机屏幕留出余量。5. 避坑街镇热力图渲染的 5 个经典翻车现场5.1 地图整体漂移热力点落在边界外现象底图边界能显示但散点热力总是落在某个方向上离实际位置大概几百米。原因GeoJSON 底图和点位数据使用了不同坐标系最常见的是 GCJ-02 底图配了 WGS-84 的点。解决确认来源若是 GPS 设备采集的原始数据先做坐标转换再进热力图。没有统一坐标系的条件下任何配置调整都救不回来。5.2 页面白屏控制台报错说地图不存在现象图表容器是空的控制台提示Map changping not exists。原因setOption执行时registerMap还没完成或者fetch请求失败返回了 HTML 错误页JSON 解析报错后导致后续代码中断。解决把注册逻辑放进async函数里先await拿到 GeoJSON 再初始化图表同时给fetch加一个状态判断非 200 响应直接抛出提示不要拿错误内容去注册。5.3 区域不染色但边界和 label 都正常现象地图显示出来了街镇名称也有可所有区域都是同一种底色。原因series.data里的name和properties.name匹配不上或者visualMap.max远大于实际数据最大值。解决先打印对比确认名称没有多余空格再看数据里有没有null值空值会导致该区域跳过映射。也有一种情况是visualMap的inRange.color只配了一个颜色ECharts 无法构成梯度整体看起来就变成纯色了。5.4 街镇边界线过粗热力颜色被线压下去现象热力图效果还行但边界线黑乎乎的颜色看着很脏。原因geo.itemStyle.borderWidth默认值偏大或者被全局主题覆盖成了粗线。解决把borderWidth调到 0.5 到 1 之间borderColor选择和底色接近但有一定对比度的颜色比如深蓝底配浅蓝线。对于标注文字设置labelLayout: { hideOverlap: true }可以自动隐藏重叠名称避免小街道的标签互相挤压。5.5 本地双击 HTML 文件打开时热力图层不出现现象用 Vite 或 Webpack 开发服务器跑没问题直接双击index.html却无法加载地图。原因浏览器对file://协议的fetch请求有跨域限制GeoJSON 被拦截。解决本地调试时不要双击 HTML起一个简单的静态服务器比如npx serve或 Python 的http.server。如果一定要在纯静态环境演示就把 GeoJSON 转成一个 JS 文件用window.__CHANGPING_GEO__暴露出来直接同步引用。6. 把 Demo 变成可复用组件命名规范、数据口径和热力图三种变形先养成一个习惯把数据结构和图表配置分开。业务端统一输出{ name, value }前端只负责渲染。这样不管你以后是换地图数据还是换统计口径都不需要改动图表的渲染逻辑。第二个习惯是把visualMap的配色和max值抽成常量不同业务模块可能有不同阈值写死在setOption里后期维护成本很高。数值口径是另一个容易被忽视的地方。同样是“热力值”有的业务是绝对量有的是密度有的是百分比。用visualMap.formatter可以在图例上直接说明visualMap: { formatter: (value) (value 1000 ? (value / 1000).toFixed(1) k : value) }最后聊一下标题里“热力图”的三种变形。第一种就是本文第 3 章的点状热力适合有经纬度明细数据的场景第二种是第 4 章的面状区域填充适合按行政单位聚合好的统计指标第三种是散点气泡图用气泡大小代替光晕强度适合点数量不多又想精确体现每个位置数值的场景。三者可以共存比如用面状图打底点状热力叠加关注重点区域。我自己做第一版街镇热力图的时候就是因为数据里的 name 多了一个空格整个区域染色始终不出来排查了半个下午。后来把名称对比脚本固定成流程的一部分再没遇到过这种问题。希望这篇笔记能帮你绕过这些坑把昌平区街镇热力图顺利跑起来。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑