【Unity3D】基于Canvas的热力图绘制办法

一、前言

本文主要讲的是如何根据数据值,在Canvas上重新绘制RGBA的值,完成热力图的绘制

二、原文

原文地址:http://www.blueidea.com/tech/web/2010/7933.asp
原文作者:佚名
原文出处:蓝色理想

三、正文

1、介绍

最近参与的一个项目Marmot中需要根据点坐标绘制热力图。

热力图

以特殊高亮的形式显示访客热衷的页面区域或访客所在的地理区域
特点为:

  1. 可以显示不可点击区域发生的事情。你将发现用户经常会点击那些不是链接的地方,也许你应该在那个地方放置一个资源链接。比如:如果你发现人们总是在点击某个产品图片,你能想到的是,他们也许想看大图,或者是想了解该产品的更多信息。 同样,他们可能会错误地认为特别的图片就是导航链接。

  2. 热力图同时还能告诉你,页面的哪些部分吸引了大多数用户的注意。这对那些对web分析数据没有很多经验的产品人员非常有用。

  3. 如果你在一个页面上有多个链接指向同一个URL,例如:如果有不同位置的3个链接指到同一个特定的产品页面 ,那么热力图将会显示你的访客最喜欢点击哪一个链接,这将帮助你提升网页的设计并让它对用户更加友好,不过实现这个功能需要一些设置。

实例如下:

在这里插入图片描述
需要注意的是上图实例粒度粗,梯度小,容差大。反映了热力图的一个属性:趋势相关。不过,热力图也可以做到粒度细,梯度大,容差小。这完全是依据采样数据的精确性以及分析需求来做的。给个例子(Google的眼动分析[焦点梯度]图):
在这里插入图片描述
下面介绍热力图绘制的方法,注意,以下代码并没有检测数据有效性,也没有对数据进行过滤,剔除脏数据,同时没有处理异常。实际使用时请不要忽略此类情况,否则会对最终结果造成干扰……

3、绘制

问题描述

假设有一块画布,1200px2000px尺寸,一组坐标数据,格式为[x,y]二维数组,量级为10000~100000,采样粒度为77。依据点坐标的分布密度绘制热力图

方法一

思路:使用canvas元素标签将所有点绘制到画布上,每个点给予较低的透明度。然后获取画布每个点的位数据,根据其alpha值(alpha ∈ [0, 255])的大小计算每一位的r,g,b的值,得出所有新的位数据之后,重新绘制。使之呈现为红色↔蓝色渐变。

代码

 /*假设点坐标为aXY,二维数组*/
var aXY = [[x1, y1], [x2, y2], [x3, y3], [x4, y4]...];
//获取canvas的context
var context = canvas.getContext('2d');
var pi2 = Math.PI * 2;
//设置填充样式,透明度为0.1
context.fillStyle = 'rgba(255,30,0,0.1)';
for (var i = 0, len = aXY.length; i < len; i++) {
    var x = aXY[i][0], y = aXY[i][1];
    context.beginPath();
    //绘制圆点
    context.arc(x, y, 6, 0, pi2, true);
    context.closePath();
    context.fill();
}
//获取这个画布的位数据
var imgd = context.getImageData(0, 0, 1200, 2000);
var pix = imgd.data;
// 循环计算rgb,使之根据alpha值映射到红蓝渐变
for (var i = 0, n = pix.length; i < n; i += 4) {
    //位数据的格式为[rgbargbargba……],每个rgba代表了每个点的rgba四个通道的值
    var a = pix[i+3]; //alpha
    //red
    pix[i  ] = 128 * Math.sin((1 / 256 * a - 0.5 ) * Math.PI ) + 200;
    //green
    pix[i+1] = 128 * Math.sin((1 / 128 * a - 0.5 ) * Math.PI ) + 127;
    //blue,128之后直接衰减为0
    pix[i+2] = 256 * Math.sin((1 / 256 * a + 0.5 ) * Math.PI );
    pix[i+3] = pix[i+3] * 0.8;
}
context.putImageData(imgd, 0, 0);

上面的代码将会呈现:

在这里插入图片描述
显而易见,这并不是热力图,但是可以精确反映每个点的分布密度,红色表示在该区域的点数据较多,浅,蓝色表示密度小。那么如何改进?
使用径向渐变代替圆点的绘制,用以表示每一个点向周围的点的辐射,渐变色的叠加可以展现梯度变换的效果。

方法二

思路:

对所有点数据进行计算,得出每个点的密度值,然后依据密度值由低到高,绘制点数据。

代码

var points = [[x1, y1], [x2, y2], [x3, y3], [x4, y4]...];
var cache = {};
//计算每个点的密度
for (var i = 0, len = points.length; i < len; i++) {
    for (var j = 0, len2 = points[i].length; j < len2; j++) {
        var key = points[i][j][0] + '*' + points[i][j][1];
        if (cache[key]) {
            cache[key] ++;
        } else {
            cache[key] = 1;
        }
    }
}
//点数据还原
var oData = [];
for (var m in cache) {
    if (m == '0*0') continue;
    var x = parseInt(m.split('*')[0], 10);
    var y = parseInt(m.split('*')[1], 0);
    oData.push([x, y, cache[m]]);
}
//简单排序,使用数组内建的sort
oData.sort(function(a, b){
    return a[2] - b[2];
});
var max = oData[oData.length - 1][2];
var pi2 = Math.PI * 2;
//设置阈值,可以过滤掉密度极小的点
var threshold = this._points_min_threshold * max;
//alpha增强参数
var pr = (Math.log(245)-1)/245;
for (var i = 0, len = oData.length; i < len; i++) {
    if (oData[i][2]  0 ? 0 : 1);
    //q参数用于平衡梯度差,使之符合人的感知曲线log2N,如需要精确梯度,去掉log计算
    var q = parseInt(Math.log(oData[i][2]) / Math.log(max) * 255);
    var r = parseInt(128 * Math.sin((1 / 256 * q - 0.5 ) * Math.PI ) + 200);
    var g = parseInt(128 * Math.sin((1 / 128 * q - 0.5 ) * Math.PI ) + 127);
    var b = parseInt(256 * Math.sin((1 / 256 * q + 0.5 ) * Math.PI ));
    var alp = (0.92 * q + 20) / 255;
    //如果需要灰度增强,则取消此行注释
    //var alp = (Math.exp(pr * q + 1) + 10) / 255
    var radgrad = this.context.createRadialGradient(oData[i][0], oData[i][1], 1, oData[i][0], oData[i][1], 8);
    radgrad.addColorStop( 0, 'rgba(' + r + ',' + g + ','+ b + ',' + alp + ')');
    radgrad.addColorStop( 1, 'rgba(' + r + ',' + g + ','+ b + ',0)');
    this.context.fillStyle = radgrad;
    this.context.fillRect( oData[i][0] - 8, oData[i][1] - 8, 16, 16);
}

以上代码结果如下:

在这里插入图片描述
大约处理了25000个点,用时大约700ms。属于可接受范围内。

方案度量:此方案性能比方案一有明显优势。

猜你喜欢

转载自blog.csdn.net/q764424567/article/details/84749251
今日推荐