网页热力图绘制原理

热力图,是用来表现用户某种行为集中度的工具.

在这里我们只讲热力图的技术实现方法及原理,关于其他内容(比如canvas context2dAPI),篇幅限制,不在本文讨论范围之内.

MDN 将提供详细的相关资料.

现代浏览器为我们提供了多种显示图像方案,主要有:

方式 优点 缺点 可用度
img 标签 静态,兼容,交互方便,有css支持 无法进行自由绘制,无法嵌套 如果后端绘图,可以尝试
svg 标签 矢量,自由缩放,其他同上 本质是dom,开销较大,且api提供的功能有限 适合绘制各种报表
context2d API 可自由绘制,api简单,chrome有硬件加速 逐像素绘制,如果绘图面积大,性能会差一些 适合绘制热力图
WebGL API 可自由绘制,高性能 逐像素绘制,api复杂,而且兼容性差 主要看浏览器是否支持

综上所述不难看出,使用canvas元素的 context2d API作为绘制热力图的技术方案是最佳的.

绘制热力图前,我们需要的资料

数据源:

热力图是对数据的一种可视化展现形式,那么想要绘制热力图,首先需要数据,数据的格式可以自由定义, 只要在每一个点上,包含坐标点击量 这2个必要信息就行.

定义绘图参数:

就像显示器一般会提供 亮度,对比度,饱和度等可供用户自己调节的参数,热力图也应该有类似的调节参数,下面提供了一些我们常用的参数:

参数 说明
半径值(radius) 热力图中每一个点的半径.
最大透明度(maxOpacity) 热力图中每一个点可以拥有的最大透明度.
最小透明度(minOpacity) 热力图中每一个点可以拥有的最小透明度.
最大值(max) 热力图中每一个点可以拥有的最大值(超出这个值则和这个值相等).
最小值(min) 热力图中每一个点可以拥有的最小值(低于这个值则忽略改点).

半径值(radius):

热力图究其原理是由一个个数据点(圆形)累加在一起形成的, 数据点只有坐标信息,并没有规定这点应该被画多大,所以这个参数就是用来控制圆点大小的, 一般在数据量较大的场景里我们会给一个较小的半径值,这样热力图会显得更精确,同时也不失细腻.

但如果数据量较小,点和点之间分布的往往比较远 ,这样我们可以适当加大半径值,否则有些点就会被"孤立"起来

最大透明度(maxOpacity) 和 最小透明度(minOpacity):

热力图中的点,仔细观察会发现,它们不仅仅在颜色上有区别,而且在透明度上也有区别,却靠近中心的像素越红,透明度越低,越是在外围的点越蓝, 透明度越高. 针对这一特性 maxOpacity 和 minOpacity 规定了一个范围,让所有点的透明度都在这个范围里,让热力图会显得更加平滑.

观察下面两个点,就能看出差别

maxOpacity =1  minOpacity=1  //没有alpha渐变,边缘十分生硬.

maxOpacity = 0.75  minOpacity=0 //有alpha渐变,边缘平滑的过渡.

明显可以看到,有渐变的更柔和,更平滑.

最大值(max):

在热力图中,往往数据点的分布是十分不均匀的, 比如google.com 在搜索框的点击量肯定会比页面其他区域高得多,甚至不在一个量级上. 这样以来如果以页面中点击量最大的那个点作为最红点的标准,则其他区域很可能"没有露脸的机会了", 一个坐标上的点击量理论上可以无限大, 这样就需要设置一个最大标准量,如果某个点的点击量超过了这个最大值,就按照最大标准量处理,这样可以避免少数极值拉高整个色阶.

最小值(min):

设置最小值的作用是为了降低热力图中的"噪点", 因为在网页中任何区域都是可以点击的,在大数法则下,经常会有莫名其妙的"点"冒出来,这些点的点击量 非常小,往往只有1,如果这些点也参与绘图,不但会降低性能,而且还会使整个热力图出现 斑斑驳驳的噪点,对于这些量很小的数据往往根据情况可以考虑过滤掉.

具体该如何绘制

上文说到,热力图是由很多数据点叠加而成的,那么这个叠加是怎么完成的呢?

首先,我们来看看单个点是怎么绘制的.

var tplCanvas = document.createElement('canvas');
var tplCtx = tplCanvas.getContext('2d');
var x = radius;
var y = radius;
tplCanvas.width = tplCanvas.height = radius*2;
var gradient = tplCtx.createRadialGradient(x, y, radius, x, y, radius1);
gradient.addColorStop(0, 'rgba(0,0,0,1)');
gradient.addColorStop(1, 'rgba(0,0,0,0)');
tplCtx.fillStyle = gradient;
tplCtx.fillRect(0, 0, 2*radius, 2*radius);

//createRadialGradient(x0, y0, r0, x1, y1, r1)
//前三个参数代表以(x0,y0)为圆心,r0为半径的圆,后三个参数代表以(x1,y1)为圆心,
//r1为半径的另一个圆。渐变会在两个圆中间的区域出现。

这样我们便得到一个

在绘制图像的时候要注意,需要遵循前面的配置,比如最大点击量为300 最小是2 当前点的点击量是50 那么这个点的alpha值要按照比例来确定. (50-2)/(300-2) 结果是 0.161; 那么对于这个单点来说他的颜色是 (0,0,0,0.161);

最后将一个个临时的小点(tplCanvas)绘制到一幅大的canvas上,这样就得到了下图.

对! 他是黑白的,严格来说只是纯黑色,只不过不同部分的alpha值不同,所以显得灰度不同, alpha值可以线性累计, 这是一种比较简单且巧妙的方法 .

我来详细解释一下上面的话.

由于热力图是一个个数据点叠加上去的,那么在最后一个点画完以前是不能确定图上任意一点的颜色的.(你不知道下一个点要绘制的位置)

上图箭头指向黄色区域,为什么是黄色呢? 是因为 左侧圆的绿色部分 和 右侧圆的绿色部分 有些重合,重合后权重提升,从而变成了黄色,也就是说在右侧圆 完成绘制以前并不能确定箭头所指点的颜色. 即便他被左侧圆包含.

然而最困难的还不是这些,(绿+绿=黄? 好吧 有多黄? 浅黄 深黄?) 因为一个像素点的状态是由4种数据决定的 R G B A. 在热力图中是一个从红到蓝的渐变, 并不是简单的分别线性叠加关系.

但纯色图像却可以, 我们不关心它的颜色,只需要把他的Alpha分量线性叠加就行,也就是说你只需要不断往canvas上画小圆就行. 每画一个圆, 如果和前面的图片重合区域,那么这个区域的alpha值会自动积累在canvas上.

后续的处理工作:

黑白的热力图不是我们最后想要的,那么该如何给它上色呢?

1.首先我们要生成一个颜色字典,它规定数据从低到高该使用哪些颜色,然后把它拉成一个线性的结构(可以是数组),这样在这个范围中, 给出一个比值就可以得到一个对应颜色.(比如 可以生成一个 从蓝到红的颜色字典,在这个字典里 0对应蓝色 1对应红色)

2.遍历黑白图像的每一个像素,读取它的 alpha值(0-1), 按照前面的配置,查询颜色字典,填色.

3.从新绘图得到彩色图像

基于WebGL的热力图

除了context2dAPI 外,使用WebGL API 也可以达到相同的效果, 不过WebGL API有些复杂,还需要学习 GLSL语言,这里不再详述了

感兴趣的童鞋可以参考: https://github.com/wshxbqq/WebGL-HeatMap

但原理是类似的,都需要2次绘制,(先绘制黑白图像,然后着色)

留言:

称呼:*

邮件:

网站:

内容: