ARTICLE DETAIL

资讯详情

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

腾讯位置服务热力图实战:坐标聚合、分位数与性能调优

腾讯位置服务热力图实战:坐标聚合、分位数与性能调优 做地图可视化的人大概率都遇到过这种场景业务方丢过来一张几十万行的设备上报记录或者订单表就问一句能不能看出人都在哪儿扎堆。绕来绕去你最终要交付的核心其实就是一张读得懂的热力图。腾讯位置服务在这件事上给了一套相对完整的能力配合它的数据可视化组件不用自己写 WebGL、不用啃着色器几十行代码就能把一堆经纬度变成一张有层次、能缩放、能交互的分布图。我把这篇内容按实际项目里的推进顺序来写从需求判断、坐标系处理、参数调参一直到上线跑起来之后踩的那些坑尽量把每一步背后的为什么都说明白。不管你是刚接触地图可视化、只会复制官方示例的新手还是已经用别家方案做过大屏、想换一套更省事的老手下面这些内容应该都能直接用上。如果你现在正卡在热力图颜色不对劲点多了卡成幻灯片数据打上去一片空白这几个具体问题上可以直接跳到最后一章的问题排查表。1. 动手之前先想清楚这张热力图要回答什么问题1.1 三种典型场景数据形态完全不一样热力图不是一种图而是一类图的统称。我在项目里最常遇到的其实是三种完全不同的需求用同一套参数去套结果一定会有一种不好看。第一种是密度型比如共享单车停放点、门店客流、设备分布。这类数据特点是点多、权重大致相等你关心的是哪里密哪里疏。这种场景下radius要给大一点让相邻点能够自然融合颜色的分布才会平滑。第二种是强度型比如某个区域内的订单金额、信号强度、温度。这类数据点可能不多但每个点的数值差异巨大你关心的是哪里峰值高。这时候权重的归一化方式就成了成败关键直接拿原始数值喂进去往往整张图只有一两个红点其余全是蓝的。第三种是时序型比如一天内的车流变化、一周内的活动签到。它的核心不是空间分布而是空间加时间的双维度需要靠时间轴回放来呈现热力图只是其中一个图层。先判断清楚自己属于哪一类再去调参数能省掉一半的返工。我见过太多人拿着强度型数据套密度型的参数然后抱怨颜色怎么调都不对。1.2 自己糊 Canvas 还是用现成组件自建方案我认真做过两轮。第一轮是用 Canvas 在屏幕坐标上画径向渐变再叠色思路很朴素每个点画一个圆形渐变所有点的 alpha 值累加最后按阈值映射颜色。这套东西做出来大概两百行效果勉强能看但它有几个绕不过去的问题。缩放和拖拽的时候必须重算几十万点的情况下每次交互都要几百毫秒体验直接崩掉。原因是 Canvas 的逐像素累加是 O(点数 × 半径面积)点一多就是灾难。第二轮我换成了 WebGL 着色器性能问题解决了但坐标系转换、图层叠加顺序、移动端的精度问题又得自己啃一遍。前前后后花了大概两周才稳定下来。用腾讯位置服务的数据可视化组件本质上是把这部分工作交给了一套已经被大量项目验证过的实现。它的热力图图层跑在 GL 渲染管线上几万个点也能保持流畅的缩放而且和地图的底图、点标记、线图层共享同一套坐标系和事件系统叠加逻辑不用你操心。代价是灵活性受限。比如你想做一个非标准的热力效果像六边形网格热力或者等值面它未必支持。所以我的判断标准很简单如果你的需求是标准的热力分布用现成组件如果你要表现的是某种特殊的分析模型自建。大部分业务场景都落在前者。1.3 成本核算免费额度到底够不够用这一点必须提前算很多项目在开发阶段跑得好好的一上量就被账单教育。腾讯位置服务对个人开发者和小规模项目有免费配额JS API 的调用量计算方式是按地图初始化次数和部分服务调用次数来的。热力图图层本身是渲染层的能力不额外产生按点计费的费用这点比某些按数据点收费的方案友好很多。我在一个日活两万左右的小程序里实测过地图初始化的调用量每天大概在两万到三万次之间用户打开页面算一次页面切换回来如果没做缓存会再算一次。这个量级在免费额度内是能覆盖的但如果你做了多页面共享地图实例、或者用户频繁切换 tab数字会涨得很快。注意地图实例一定要做复用。同一个页面里反复new地图对象不仅调用量翻倍内存也会持续增长移动端会很明显地卡。企业级的场景比如给客户做数据可视化大屏通常是固定几个大屏终端长时间挂着。这种量级反而很小一天可能就几十次初始化。真正吃量的是 C 端高频交互的产品。所以在立项阶段先把每天大概多少次地图打开这个数字估出来比写完代码再优化要省事得多。2. 开工前的四件准备工作少一件都要返工2.1 Key 申请与安全配置别把裸 Key 丢到前端申请流程本身不复杂在控制台建应用、建 Key、绑定服务类型就行。真正容易出事的是安全配置这一步。我见过最危险的做法是把没有任何限制的 Key 直接写在打包好的 JS 里。这种 Key 一旦被扒出来别人可以拿着它去调你的额度你的账单会莫名其妙地涨。正确做法有两层保险一是域名白名单把允许调用这个 Key 的域名都填进去浏览器侧的非授权来源会被直接拒绝二是如果涉及服务端调用给 Key 配置Secret Key 做签名校验让每次请求带一个短时效的签名。前端用的 JS API Key 只能靠白名单因为浏览器环境藏不住任何密钥。这个要接受现实不要试图在前端做签名那只是把问题挪了个地方。还有一个容易忽略的点Key 要按环境分开。开发、测试、生产各建一个好处是排查问题的时候能一眼看出流量来自哪里出了问题也能单独下线某一个而不影响其他环境。多人协作的项目尤其要注意一个人的本地调试把生产额度刷爆的事情我见过不止一次。2.2 坐标系热力图最常见的整体偏移元凶这一节我建议反复读两遍。地图可视化的坐标系问题是所有问题里最隐蔽也最致命的。简单说GPS 设备原始输出的是 WGS84 坐标而国内主流地图服务使用的是 GCJ02 坐标。这两个坐标系之间不是简单的平移而是有一套非线性偏移算法位置差异通常在几十米到几百米之间。你在城区做热力图如果坐标系搞错了整张图会朝一个方向偏出去看起来好像有分布实际上落点全是错的。更麻烦的是这个错误不会报错。地图该渲染渲染热力图该显示显示只有当你把图叠在卫星底图上看才会发现热点全跑到了马路对面甚至河里。处理方式我一般分三种情况数据来自自家 App 收集的 GPS 定位是 WGS84需要转成 GCJ02 再喂给地图。数据来自用户的地址文本先用地理编码接口转成坐标拿到的通常已经是地图坐标系直接用。数据来自第三方系统先问清楚它输出的是什么坐标系别猜。转换这一步我建议放在数据入库的时候就做掉而不是每次渲染时在浏览器里转。前端转换虽然有现成的算法实现但每次几万点算一遍也是白白浪费性能。落库时统一存储地图坐标系前端直接用是最省心的做法。注意不要用看起来对齐了来判断坐标系对不对。用几个已知地标的坐标去验证比如把某个明确的路口坐标打到图上放大到 18 级看它是不是压在路口中心。2.3 数据清洗与网格聚合把十万点压到一万点先说一个经验数字热力图里单个图层的点数超过五千就开始有明显感知超过五万在很多中低端安卓机上会掉帧。但业务给的数据往往远超这个量级。这个时候不要去优化渲染而是先做聚合。热力图本质上展示的是密度分布相邻几十米内的很多点对最终的视觉结果几乎没有贡献提前合并掉完全不影响观感。我的做法是按经纬度网格聚合。因为热力图半径是按像素算的地图缩放级别不同一个像素对应的实际距离也不一样。为了不让聚合结果在不同缩放级别下失真我一般取一个比较细的网格大概是 0.001 度左右在纬度 30 度附近大约是 100 米然后把落在同一个格子里的点合并成一条记录权重累加。// 按 0.001 度网格聚合原始点位 function gridAggregate(points, precision 3) { const grid new Map(); const factor Math.pow(10, precision); for (const p of points) { // 用固定的小数位做格子 keyconsecutive 的点会落进同一个桶 const gx Math.round(p.lng * factor); const gy Math.round(p.lat * factor); const key gx _ gy; const cell grid.get(key); if (cell) { cell.count p.weight || 1; } else { // 用格子中心点作为落点避免所有点都吸附到边界 grid.set(key, { lng: gx / factor, lat: gy / factor, count: p.weight || 1 }); } } return Array.from(grid.values()); }这段代码没有任何外部依赖十万点跑下来大概几十毫秒完全可以在数据加载阶段同步执行。聚合完之后通常会得到几千到两万条记录正好落在比较舒服的区间。如果你希望更精确一点可以在格子内用平均值而不是中心点但在热力图这种模糊表达的场景下差别肉眼基本看不出来。2.4 个性化地图样式底色决定热力图好不好看这一点属于提前做能省事、事后改很难受的准备工作。热力图的颜色是从冷色到暖色的渐变如果底图也是高饱和度的彩色两者会打架。我的习惯是把底图调成以灰、白、深蓝为主的低饱和度样式让热力图的颜色成为画面上唯一的强调色。腾讯位置服务提供了个性化地图的样式配置能力可以在控制台里调整道路、水系、绿地、文字等要素的颜色和显隐。做暗色大屏的时候把底图整体调成深色、只保留主要道路的浅灰描边热力图叠上去会非常干净。这一步我建议放在项目最早期做因为样式配置出图之后你在调试热力图颜色的时候才有准确的参照。如果等热力图调好了再换底图很可能颜色又要重调一遍。3. 从零跑通第一张热力图3.1 页面骨架与脚本加载顺序先给一个能直接跑的最小结构。这里我用原生 HTML 加脚本的方式框架无关你迁到 Vue 或 React 里也是同样的顺序。!DOCTYPE html html head meta charsetutf-8 / meta nameviewport contentwidthdevice-width, initial-scale1, maximum-scale1, user-scalableno / title分布热力图/title style html, body { margin: 0; padding: 0; height: 100%; } #mapContainer { width: 100%; height: 100%; } /style /head body div idmapContainer/div script srchttps://map.qq.com/api/gljs?v1.expkey你的KEY/script script // 地图必须在 SDK 脚本加载完成后才能初始化 window.onload function () { initMap(); }; function initMap() { const center new TMap.LatLng(39.98412, 116.30756); const map new TMap.Map(document.getElementById(mapContainer), { center: center, zoom: 12, pitch: 0, viewMode: 2D }); // 后续所有可视化图层都挂在 map 实例上 window.__map map; } /script /body /html有几个细节值得强调。容器必须有明确的高度height: 100%要求父级链上每一层都有高度否则地图会显示成一个零高度的空条控制台还不会有任何报错。这是新手最常见的地图不显示原因占我遇到的情况里大概三分之一。viewMode我习惯显式写成 2D。默认值虽然是 2D但写出来能让后面接手的人一眼看清意图如果你后面要做 3D 的城市楼块效果这里改成 3D 并配合 pitch 和 rotation 参数。移动端要加上user-scalableno不然用户在热力图区域双指缩放的时候会先触发浏览器的页面缩放地图反而没反应体验很割裂。3.2 热力图图层的构造参数逐项拆解下面这段是核心。参数名以你实际引入的 SDK 版本为准但每个参数的意图是通用的即使字段名有出入你也能对上号。function createHeatLayer(map, points) { const heat new TMap.visualization.Heat({ radius: 25, // 每个数据点的影响半径单位像素 height: 0, // 贴地高度做 3D 抬升时可以调大 opacity: 0.85, // 图层整体不透明度 gradientColor: { // 颜色带key 为 0~1 的归一化值 0.0: #2b3a8f, 0.3: #2f8ed6, 0.55: #4ec36b, 0.75: #f2c744, 1.0: #e2452b } }); heat.setData({ data: points, // [{ lng, lat, count }] max: computeMax(points) }); heat.addTo(map); return heat; }radius 是最影响观感的参数。它决定了单个点的影响范围。半径小的时候每个点都像一个孤立的亮点密集区域会形成明显的纹理感半径大的时候点与点之间充分融合整张图会变得非常平滑但小规模的热点会被淹没。我的经验值是这样数据规模地图缩放级别建议 radius1000 点以内10 到 1215 到 251000 到 1 万点12 到 1420 到 351 万点以上14 以上30 到 50全国视角4 到 640 到 60这个表只是起步参考真正要调的时候盯着屏幕看哪种半径下热点的数量和位置最符合业务预期就以那个为准。gradientColor 的 key 必须是 0 到 1 之间递增的数值。很多人会写成百分比字符串或者写成不递增的顺序结果就是颜色不生效但不报错。另外色带里最好有一个明显的过渡色直接从蓝跳到红画面上会出现明显的色阶断层。height 参数是做 3D 效果用的。把它调大热力面会从地面抬起来配合 3D 视角和倾斜的相机角度能做出那种悬浮在空中的热力云效果做大屏的时候视觉冲击力很强。但要注意抬升之后它和底图的道路就对不上了只适合宏观表达。3.3 max 阈值怎么定这一步决定颜色对不对这是整篇文章里我最想讲透的一点。热力图的分色逻辑是把每个点的权重除以上限值得到一个 0 到 1 的归一化结果再按这个结果去取色带上的颜色。这个上限值设错了整张图就废了。假设你的数据里绝大多数的权重在 1 到 20 之间但有一个异常点权重是 5000。如果你直接取最大值 5000 作为上限那么其余的 99% 的点归一化之后都在 0.004 以下全部挤在色带最冷的那一小段里整张图看起来就是一片深蓝什么都看不出来。反过来如果你把上限设得太低比如设成 10那大部分点都会满格整张图一片红同样没有信息量。正确的做法是用分位数来定上限而不是用极值。我一般取 P90 到 P99 之间的值。// 用分位数确定 max避免极端值压垮色阶 function computeMax(points, percentile 0.95) { if (!points.length) return 1; const counts points.map(p p.count || 1).sort((a, b) a - b); const idx Math.min( counts.length - 1, Math.floor(counts.length * percentile) ); // 兜底分位数算出来是 0 的话给个 1避免除零 return Math.max(counts[idx], 1); }举个具体的例子。假设你有 20 个聚合后的格子权重分别是1, 1, 2, 2, 3, 3, 4, 5, 5, 6, 7, 8, 9, 12, 15, 18, 22, 30, 45, 5000按 P95 计算位置在下标floor(20 * 0.95) 19也就是 5000 那个值。这说明百分位取 0.95 在这个小样本里还是被极端值带跑了。改取 P90下标是 18得到 45此时上限是 45那个 5000 的点会满格显示为最红其余点的分布层次就出来了。所以我在实际项目里的取值习惯是数据量小几百条以内取 P85 到 P90数据量大数千条以上取 P95 到 P99。数据量越大分位数越稳定越接近 P99。这个上限还可以做成动态的。比如用户放大到某个区域之后只对该视野内的点重新计算分位数这样局部细节会看得更清楚。我的做法是监听地图的缩放结束事件如果缩放级别变化超过两级就用当前视野内的点重算一次 max 并刷新图层。注意每次重算 max 都会导致全局配色变化用户会感觉颜色在跳。所以要么加一个防抖比如 300 毫秒要么只在缩放级别跨过整级的时候才重算别让它在连续缩放过程中反复触发。3.4 数据分批与图层销毁动态刷新的正确姿势真实项目里的热力图基本都不是一次性的要么定时刷新要么跟着筛选条件变化。这里有两个坑。第一个坑是重复 addTo。每次都new一个新的热力图图层再加到地图上旧图层不会自动消失叠了几十层之后渲染压力巨大还会出现颜色越叠越深的现象。正确做法是图层实例只创建一次后续只调setData。let heatLayer null; function updateHeat(map, points) { const payload { data: points, max: computeMax(points) }; if (!heatLayer) { heatLayer new TMap.visualization.Heat({ radius: 25, opacity: 0.85, gradientColor: { 0: #2b3a8f, 0.5: #4ec36b, 1: #e2452b } }); heatLayer.addTo(map); } heatLayer.setData(payload); } // 页面卸载或组件销毁时一定要清掉否则会内存泄漏 function destroyHeat() { if (heatLayer) { heatLayer.remove(); heatLayer null; } }第二个坑是大数据量的同步阻塞。如果一次setData传入五万条记录主线程会被卡住几百毫秒表现出来就是页面点一下就白屏一下。解决办法除了前面说的网格聚合之外还可以分批渲染先渲染权重最高的一批比如前 20%让用户立刻看到主要热点然后在下一个空闲帧再把剩下的补上。分批的时候有个小技巧就是第一批不要按时间顺序取而要按权重降序取。这样用户第一眼看到的就是最有信息量的部分剩余的补充数据即使晚几百毫秒到达感知上也不明显。4. 进阶组合让热力图不只热力图4.1 热力图叠加点标记与聚合单独一张热力图业务方往往会问那具体是哪些点。这时候需要叠加标记。但直接叠几万个标记肯定不行。我的做法是热力图打底 聚合标记点顶在上层。聚合逻辑是把临近的点合并成一个带数字的圆点缩放到一定级别才展开成独立标记。两层叠加时要注意视觉层级热力图的不透明度建议降到 0.6 到 0.7聚合点用纯色实心加白描边这样即使落在热力图最红的区域标记依然清晰可辨。如果热力图太实标记会被淹没用户会觉得看着糊。还有一个细节是点击事件的处理顺序。热力图图层默认是不拦截点击的点击会穿透到地图底层这会带来一个问题用户点了一个红点你没法知道点的是哪个数据。解决办法是在热力图上层再放一个透明的点图层或者在热力图的点击回调里用距离就近匹配最近的原始点。// 点击热力图时就近匹配最近的原始点位 map.on(click, function (evt) { const clickLng evt.latLng.getLng(); const clickLat evt.latLng.getLat(); let nearest null; let minDist Infinity; for (const p of rawPoints) { // 用经纬度差的平方做近似距离避免开方运算 const dlng p.lng - clickLng; const dlat p.lat - clickLat; const d dlng * dlng dlat * dlat; if (d minDist) { minDist d; nearest p; } } // 阈值控制太远就不认为点中了 if (nearest minDist 0.0001) { showInfoWindow(nearest); } });这种近似距离在纬度 40 度左右误差可以接受用来做点到哪个点的判断足够了。如果你需要精确的米级距离用球面距离公式但注意别在大数据集上循环调用那会很慢。4.2 时间轴回放让热力图动起来时序型的场景我一般会做一个时间轴控件把一天或者一周切成 24 或者 168 个时间片每个时间片预先把数据聚合好播放的时候定时切换setData。性能上的关键点是预先算好每一帧的数据和 max不要在播放过程中现算。如果数据量不大可以把所有帧都放在内存里如果很大就按需预加载相邻的几帧播放到哪加载到哪。播放帧率我建议控制在每秒 2 到 4 帧之间也就是每 250 到 500 毫秒切一次。太快了人眼跟不上看到的只是一团颜色在闪太慢了节奏又拖沓。根据经验一整天 24 帧每帧 500 毫秒整体播放下来 12 秒左右节奏比较舒服。还有一个提升观感的小技巧帧与帧之间不要硬切可以用setData的渐变动画或者自己在两帧之间做一次权重插值。不过这个要看你用的 SDK 版本是否支持过渡动画不支持的话硬切也能接受毕竟热力图本身就是模糊的硬切的突兀感远小于折线图。4.3 做成数据可视化大屏的适配要点大屏场景和手机端完全是两套思路这里单独讲。分辨率与缩放。大屏经常是 1920×1080 或者 3840×2160 的物理分辨率浏览器可能还开着系统缩放。地图容器要设置成跟随视口不要写死像素值。同时地图的zoom要在大屏上适当增大因为同样的 zoom 在大屏上看起来内容更稀疏你可能会觉得怎么点这么散其实是因为可视范围变大了。底图与配色。前面说过大屏一定要用暗色或者低饱和底图。热力图在深色背景上的发光感是很强的那种视觉效果是浅色底图给不了的。文字标注统一用浅灰或白色避免多色混战。图例与数值标签。热力图最大的问题是看不清具体数值。大屏上通常会在角落放一个色带图例标明每个颜色区间对应的权重范围。这个图例要和你实际的 max 联动max 变了图例的刻度也要跟着变不然就是错的。长时间运行的稳定性。大屏可能连续挂几天不关内存泄漏会被放大。所有定时器都要在组件销毁时清掉所有图层都要有remove的调用路径地图实例也要确保只有一个。我在一个挂机大屏项目里就是因为忘了清定时器跑了两天之后内存涨到了 1.5G浏览器直接崩掉。5. 常见问题与排查技巧实录5.1 图层完全不显示先排除这五种可能按我的排查顺序从最可能到最不可能容器高度为零。打开控制台看地图容器元素的实际高度是 0 就往上找父级逐层检查有没有高度缺失。数据为空或者格式不对。检查传入的data数组长度以及每条记录的字段名是不是 SDK 要求的名字。字段名写错是最隐蔽的因为不报错。坐标数值超范围。经度应该在 -180 到 180 之间纬度在 -90 到 90 之间。如果传进来的是经度 × 10 的 6 次方这种整数形式的坐标热力图会被画到地球外面看起来就是不显示。max 值设置过大。前面说过如果 max 远大于实际权重所有点都归一化成接近 0颜色全部落在色带最冷端如果色带最冷端又是个接近透明的浅色视觉上就是没有。图层加到了错误的地图实例上。页面里如果创建了两个地图对象比如热更新时重复初始化图层可能加到了被隐藏的那个上。提示排查时最有效的一招是先把radius调到 50、颜色改成纯红色试一下。如果红色出现了说明坐标系和数据都没问题问题在配色和参数如果还是不显示那就是容器或者数据的问题。5.2 颜色不生效或者渐变断层颜色类的排查相对简单基本就是三种情况。色带的 key 不是数值类型。有的写法会把 key 写成带引号的字符串0.5在某些版本里会解析失败导致整个色带回退成默认配色。统一写成数值。色带的 key 没有从 0 开始或者没有到 1。如果只有 0.5 和 1 两个节点0 到 0.5 之间的部分会怎么取色是不确定的实际效果可能就是从第一个节点直接填色。稳妥的做法是至少给 0、0.5、1 三个节点需要细腻过渡就给到 5 到 7 个节点。颜色格式不被支持。十六进制和 rgb 通常都支持rgba 里带 alpha 的写法有的版本会把透明度忽略掉。要控透明度就用图层级的 opacity 参数别在色带里塞。排查完还不生效就把色带改成两极色比如纯蓝到纯红先确认通道是通的再逐步加节点调回来。5.3 卡顿与内存问题的定位思路卡顿优先看数据量次优先看更新频率最后才怀疑渲染本身。先统计一下每帧实际传给setData的点数。超过五万就想办法聚合。再检查是不是每次数据变化都重建了图层实例如果是改成复用。最后看是不是有多个图层同时存在比如切换指标时旧的图层没有 remove。内存问题的定位我用 Chrome 的内存快照对比。在页面稳定运行的时候打一次快照等十分钟再打一次对比这两次之间的对象增量。如果地图实例、图层实例、定时器这类对象在持续增长就是有地方没释放。一个特别常见的泄漏点是事件监听。地图的on注册的回调在组件卸载时如果没有对应的off回调会一直挂在事件系统上闭包里引用的数据也就释放不掉。用框架开发的时候一定要在生命周期结束的钩子里做清理。5.4 速查表现象最可能原因排查动作地图容器空白无报错容器高度为 0逐层检查父元素高度热力图整体偏移WGS84 与 GCJ02 混用用已知地标验证落点整图一片冷色max 取值被极端值拉高改用 P90 到 P99 分位数整图一片暖色max 取值过低提高分位数或检查权重归一化颜色与配置不符色带 key 非数值或越界改成 0 到 1 的数值 key越刷越深图层重复创建未销毁复用实例只调 setData缩放卡顿单图层点数过多网格聚合后重传挂机几小时后崩溃定时器与监听未清理卸载时清定时器、解绑事件移动端缩放失效页面级手势拦截加 user-scalableno5.5 几个我自己踩出来的小技巧最后分享几个文档里基本不会写、但实际很管用的经验。深色底图配暖色热力浅色底图配冷色热力。这个听起来像废话但我见过太多项目把红橙黄的热力图叠在暖黄色的底图上整个画面糊成一团。判断标准非常简单截图之后转成灰度如果热力图区域和背景的灰度差很小这个配色就是失败的。做全国视角的时候先把数据按省份聚一次再渲染而不是直接把几百万个点丢给热力图。全国尺度下一个像素对应的是好几公里原始点位的信息完全是冗余的。max 做成随缩放级别变化的动态值但要用防抖。这个在 3.3 节讲过了是让局部细节能被看见的关键。没有它全国视角下调好的配色放大到街道级别就完全没法看。给热力图加一个仅显示 Top N的开关。业务方经常只关心最热的几十个区域与其让他在一片渐变色里眯着眼睛找不如直接给他一个列表或者只看前 50 个热点的模式这个功能加一次能被夸很久。数据时间戳一定要带上并且在前端做好时区处理。我在一个跨时区的项目里因为服务端返回的是 UTC 时间戳、前端直接当成北京时间用导致时间轴回放整体错位了 8 个小时排查了半天才定位到。这个坑和数据可视化本身没关系但一旦踩上浪费的时间比调热力图参数多得多。
返回列表