转自原文 数据可视化之热力图
最近看了一下百度的热力图,通过百度地图,确实是一个实时大数据渲染的一个形象表达形式,正好借这个机会学习一下,刚买的机械键盘,发现有两个好处:每天不写点代码(或调试),感觉对不起这价钱啊,估计我之前买的所有键盘+鼠标花费总和都不如这个键盘贵;其次就是控制自己不再吃零食了,怕掉进键盘里心疼啊。
好了,热力图还是相对比较容易,我们主要讨论如下3+1点吧,主要是前三部分,后面只是简单分析一下百度热力图和个人的简单看法。热点图的实现参考了SuperMap的热点图和百度Echarts的热点图实现。
-
原理
-
实现
-
优化
-
百度热力图简述
原理
如上是全国范围内的截图,一看就能了解当前中国人口密集度。每个区域的形状不规则,而且还五颜六色。直觉上,我们会觉得每个区域都应该有一个位置点,还应该有一个缓冲范围,然后对这个范围内进行一个渐变效果。这个思路应该还是比较理性的,只是还是无法解释区域的不规则,但抽象了位置点(XY)和渐变(五颜六色)的数据概念。那我们再结合数据,看看我们的推理是否准确。
这是一个示例数据,可见每一个HeatPoint由三部分组成(X,Y,Weight)。这个和我们之前分析的比较类似,每一个热点都有一个位置和权重,权重越大,则该点越显著,也就代表其渐变的一个衰变因素。不规则的区域又是如何形成的?看完代码后发现,是每个热点各管各的,然后相互叠加影响,形成了最终具有真实意义的奇形怪状的热点图。想想也是,不然好看不实用,那热点图的设计也就本末倒置了。另外还有一个半径属性,主要是缓冲区的半径,表示该热点的影响范围,通常我们认为所有热点的影响范围即半径都是一样的,只是权重不同,这也是为了处理的方便。
打个比方,下雨天的池塘,每一个雨滴都会引起一个涟漪,这就相当于一个热点,位置不同,雨滴的大小速度质量等并不完全一样,因此具有不同的权重,但在水面上,相互影响,形成了很多不同的形状,而水波的密集度可以用渐变来体现。这样,我们则把每一个个离散点,通过一个缓冲区,转变为连续的面,进而对其进行可视化展现。
当然,在数据上需要多提一点,实际中,因为热点数据量非常大,所以在不同级别下的数据会有优化,比如全国范围内(大)的数据比较粗略(小),而区域范围内(小)数据精细(大)。这里面有一个抽稀和聚类的处理(个人感觉应该是这个思路),这是数据处理层面的问题,我们下面还是专注可视化方面的问题。
实现
这些代码都比较简单,如果你对canvas有一定基础,相信也能明白,主要是看思路。首先就是对所有点进行一次筛选和统计,只保留屏幕范围内的热点,并根据权重获取当前最大和最小权重。这样我们获取了一个[minWeight,maxWeight]以及所有需要处理的drawPoints数组,为下面的渲染准备好数据。
渲染的过程其实就是对每一个热点权重范围内的点进行颜色的更新,颜色包括两个部分:RGB和Alpha。而渲染过程也分为两个过程:权重的计算和权重对应的颜色(RGB + Alpha)。
首先,权重的计算是一个插值的过程,根据权重值和范围值(距离热点的距离)的算法,你可以根据具体的需要选择合适的算法,而计算主要有两种情况,当前点还没有任何权重值;累加当前点的权重值。点越密,累加后的权重值就越高,则可以用权重值高的风格(红色)突出。其次就是根据权重值获取对应的颜色,这里会有一个颜色表,根据不同的权重值,采用具体的差值算法获取对应的颜色。
这样则完成了热点图的渲染,这里需要注意的是,两个for循环是对热点的行列的遍历,本身是一个矩阵范围,而热点本身的范围应该是一个圆,因此在for循环中需要判断是否在圆内。最终效果如下图:
优化
如上就是热点图的原理和实现,基本上实现都大同小异。不知道你看出来有什么问题了没有?性能!
这个实现有一个特点,假设有N个热点,权重假设是(0,1)之间的平均值0.5,半径假设为R(像素单位),那一共要有N * 0.5 * R * R * 4(4个象限),这个计算量是惊人的,和N以及R的平方是线性增长。另外一个特点,在for循环中的规则都是一致的。
这就让我们想到,如何能够进行优化,答案就是批次和模板。在开始前,我们先说两个技术点。
Canvas渐变填充
如上是伪代码,最终是在canvas上绘制了一个圆,但本身是从黑到白的渐变,同时阴影在x轴上偏移d个像素,这样,该代码生成了如下一张图片,我们称它为权重图,暂时不解释,只需要明白这段代码生成该图的过程即可。
Canvas色带
这里主要有两个函数:
-
createLinearGradient()创建线性的渐变对象
-
addColorStop() 方法规定不同的颜色,以及在 gradient对象中的何处定位颜色
这样,我们便创建了一个色带,就是如上这样一个效果,当然我们也可以根据自己的需要,定义一个彩色色带,就想我们平时所见的调色板中的颜色色带一样。这里比较宽,主要是给大家看一下效果,真正程序中,宽度只需要一个像素就可以。
实例化
不知道大家这时候是否发现了里面的原理和巧妙之处?
打个比方,我一直想买一个印章,这样每次买书的时候,就不用自己一笔一划的来写了,字写得比人还丑,这可以理解(不过最后还是没买,因为最后连书也买不起了)。假如我有了印章,这样每次就不用自己写了,直接盖章多省事啊,而且还一模一样。
这里,假设这个章是圆形的,它就是一个模板,对应的就是创建好的渐变填充的纹理。以前我们需要对热点缓冲区内的所有点都进行计算,计算出权重值,现在只需要以该热点为圆心盖一下,则把该热点范围内所有点的权重值都写上去了。
于是,逐个把所有热点都盖章,这样权重值不是都有了嘛,啪啪啪的声音就是爽。但这有一个问题,热点之间的相互影响怎么处理?这是需要叠加效果的,而不是后盖的章覆盖前者的效果。这里在盖章的时候增加一个透明度的属性,谁的权重大,谁的透明度就小,这样就可以叠加效果了,之前写的风向图的轨迹也是这个思路,还记得吗?下面是逐次盖章(贴图)的代码实现,大家过一遍,都是一个思路。
这样之后,我们就有了一个“热力图”了,不过略有遗憾,因为它是黑白相机拍出来的,都是0~255的灰度值,也就是权重信息。这样,我们就需要根据之前创建的色带来对它进行上色的过程了
可见,通过模板,我们可以极大的减少计算量,渲染也是批次的,而不是逐点赋值。这样我们可以根据不同的算法来创建对应的模板,实现不同的热点风格。下面是百度热力图采用这个方式实现的思路。
百度热力图&总结
不知不觉又写了这么多,就压缩一下篇幅吧。如下,是百度热力图八小时的请求队列,从v的属性可以看出来是小时单位,而xyz和地图行列号一致。如果想要叠加百度热力图的,就可以按照这个思路来加载热力图层了。
总体来说,个人觉得有两点收获:1功能的实现不是完结,而是开始,方能深入掌握其中,就好比一锅汤,精华都在下面,沉得住寂寞,肯定有所收获。2热力图的技术点都不难,一看便知,但不看还真不知道,这些技术点看起来没关系,但结合好了性能的提升非常明显。所以,见的多了经验就多。
今天圣诞节了,每年圣诞节我都会买一本书,然后写上”Merry Christmas to me”,然后送给自己,能看到这的人也都是真爱,再唠叨几句吧:昨天听了一个deliberate practice的概念,讲的是同等时间下,业余爱好者喜欢不断练习自己熟悉的动作,而专业选手则会专注在自己不熟悉但有机会掌握的动作,这一块在心理学中成为panic zone,属于你知道,但不精通的部分。个人觉得收获很大,如何更好的发挥时间价值,这也是我,作为一个老人家需要调整的地方。