最近玩了玩图表的热力图, 因为公司有同事想做无线设备的信号强度可视化, 在不同频段 (x 轴: MHz) 的信号强度 ( y 轴: dbm)本身就是一个两维的数据, 加上随着时间的信号强度变化, 在二维空间中会累加出热区效果, 我们可以计算出热力值作为第三维数据.
刚开始, 信号强度密集的区域不是很明显
随着时间推移, 可以看出信号强度集中在 40-60dbm 的部分
思路
利用 chart.js 我们可以完成基本的坐标轴和信号强度线条的绘制, 但是热力图是 chart.js 本身不支持的, 所以需要二次开发.
热力图实质上可以认为是点密度图, 就是数据点在空间中的密集程度, 越密集值越高. 具体的算法可以根据自己的需求来定, 但是主流的做法还是点密度. 这种算法可以是截断的, 也就是搜索半径内有多少数据点, 就作为热力值. 也可以是随着距离衰减的, 比如 IDW.
距离越远的点对于当前单元格的热力值影响相对弱, 这也是地理学第一定律的典型应用.
- static computeDensity(heatSets: any[], lineSets: number[], maxValueY: number) {
- if (!heatSets) {
- mat = this.genMat(matY, matX, 0); // 初始化 Y*X 的矩阵
- } else {
- mat = heatSets; // 上一次累加后的热力值矩阵
- }
- //
- for (let x = 0; x < matX; x += 1) {
- try {
- // 把当前的信号强度点直接累加到原有的热力值矩阵上, 如果想要把信号的其他属性作为权重, 那么就把 1 替换成当前点的某属性值
- mat[lineSets[x] - 1][x] += 1;
- this.addBuffer(mat, lineSets, x, radius); // 搜索半径为 radius, 对于当前数据点, 我们要把 ta 累加到附近的热力矩阵单元格内.
- }
- }
- }
- // 根据热力矩阵的统计结果 (最大最小值) 来限定边界颜色, 从红色渐变到背景色
- static setColor(densityData: DensityData) {
- // 使得每个热力值都对应 不同的渐变色.
- }
性能
性能在实时性要求较高的热力图中很重要, 包括 https://github.com/pa7/heatmap.js 这种著名的热力图库是具有很高性能的, 因为 ta 直接在 canvas 的渲染函数里面 putImageDate, 利用渐变函数直接上色, 性能是非常高的, 毫秒级别.
而我最开始的热力计算函数是很笨的, 遍历整个矩阵 (假如 n * n) 去搜索要计算热力的数据点或者线的节点 (m 个点), 复杂度很高, 最多需要执行 n * n * m 次累加函数. 但是后来逆向思维了一下, 直接遍历数据点(m 个), 最多再加上遍历周边半径(rad) 内的单元格, 至多执行 m * rad 次累加函数. 这个复杂度大大降低, 性能提升不止 10 倍. 最开始的 gif 图中的例子, 160 * 100 的矩阵, 加上动态数据(160 个节点), 热力计算的耗时单次在 10ms 以内.
草图
关于点密度的计算还是挺有趣的, 后面整理后再把关键代码放到 Github 上. 对, 就是那个已经被微软收购的 Github..
来源: http://www.jianshu.com/p/69d381c5359c