zoukankan      html  css  js  c++  java
  • heatmap.js 一个用canvas画热力图的利器

    最近研究了一下百度 淘宝 的点击区域热力图显示效果!觉有很有趣,于是Google了一下,发现此文不错,就转了一下!
    转自:http://1.aisensiy.sinaapp.com/heatmapjs/

    以下原文:

      于是就想找一找热力图的算法。找到了很多生成热力图的工具,它们的算法几乎是一致的,都是首先用alpha透明度方式画发散的圆在页面上,然后利用一个调色板,把对应的透明度改成相应的颜色即可。

    一个很不错的中文的算法介绍在这里 浅谈Heatmap

    一个英文的在这里 How to Make Heat Map

    虽说我本来打算的是找到算法自己去实现一下的,但是一不小心我发现万能的google在搜索记录里面给了我一个heatmap.js的链接。我好奇的点进去发现这就是我所需要实现的东西…

    heatmap.js可以使用canvas画出来一张漂亮的heatmap。更重要的是它支持数据的动态添加。比如,上图的演示就是一个利用mousemove事件生成heatmap的例子。它会自动的刷新canvas,实时显示鼠标运动的heatmap。

    打开heatmap.js发现里面的代码并不多,但是真的很精悍。

    页面代码请点击这里[heatmap.js],下面我做一个code的分析吧,看了那么久了,写下来一方面是自己加深记忆,另一方面就是可以更好的理清思路吧。[写就是为了更好的思考]么。

    code中包含两个主要的对象,store heatmap。store是heatmap的数据部分,算是model吧。而heatmap则是真正绘制图像的对象。heatmap部分可以被配置,可以自定义很多的内容,尤其是配色也是可以配置的,那么我们除了做出来正真的heatmap的效果之外还可以做出来各种各样不错的效果的。

    首先看看存储部分吧,比较简单,注释也比较清楚。

    复制代码
     1 // store object constructor
     2 // a heatmap contains a store
     3 // the store has to know about the heatmap
     4 // in order to trigger heatmap updates when
     5 // datapoints get added
     6 function store(hmap){
     7     var _ = {
     8         // data is a two dimensional array
     9         // a datapoint gets saved as data[point-x-value][point-y-value]
    10         // the value at [point-x-value][point-y-value]
    11         // is the occurrence of the datapoint
    12         data: [],
    13         // tight coupling of the heatmap object
    14         heatmap: hmap
    15     };
    16     // the max occurrence - the heatmaps radial gradient
    17     // alpha transition is based on it
    18     this.max = 1;
    19     this.get = function(key){
    20         return _[key];
    21     },
    22     this.set = function(key, value){
    23         _[key] = value;
    24     };
    25 };
    复制代码

    在model里面,支持一次添加一个数据点。这也是heatmapjs支持实时绘制的关键。一旦max值有变化就会重新绘制整个canvas。

    复制代码
    addDataPoint: function(x, y){
        if(x < 0 || y < 0)
            return;
        var me = this,
            heatmap = me.get("heatmap"),
            data = me.get("data");
        if(!data[x])
            data[x] = [];
        if(!data[x][y])
            data[x][y] = 0;
        // if count parameter is set increment by count otherwise by 1
        data[x][y]+=(arguments.length       me.set("data", data);
        // do we have a new maximum?
        if(me.max < data[x][y]){
            me.max = data[x][y];
            // max changed, we need to redraw all existing(lower) datapoints
            heatmap.get("actx").clearRect(0,0,heatmap.get("width"),heatmap.get("height"));
            for(var one in data)
                for(var two in data[one])
                    heatmap.drawAlpha(one, two, data[one][two]);
            // @TODO
            // implement feature
            // heatmap.drawLegend(); ?
            return;
        }
        heatmap.drawAlpha(x, y, data[x][y]);
    },
    复制代码

    下面就是画的部分了。这里是最重要的两个方法,drawAlpha colorize

    复制代码
    drawAlpha: function(x, y, count){
        // storing the variables because they will be often used
        var me = this,
            r1 = me.get("radiusIn"),
            r2 = me.get("radiusOut"),
            ctx = me.get("actx"),
            max = me.get("max"),
            // create a radial gradient with the defined parameters.
            // we want to draw an alphamap
            rgr = ctx.createRadialGradient(x,y,r1,x,y,r2),
            xb = x-r2, yb = y-r2, mul = 2*r2;
        // the center of the radial gradient has .1 alpha value
        rgr.addColorStop(0, 'rgba(0,0,0,'+((count)?(count/me.store.max):'0.1')+')');
        // and it fades out to 0
        rgr.addColorStop(1, 'rgba(0,0,0,0)');
        // drawing the gradient
        ctx.fillStyle = rgr;
        ctx.fillRect(xb,yb,mul,mul);
        // finally colorize the area
        me.colorize(xb,yb);
    },
    复制代码

    策略很简单,

    rgr.addColorStop(0, 'rgba(0,0,0,'+((count)?(count/me.store.max):'0.1')+')');
    // and it fades out to 0
    rgr.addColorStop(1, 'rgba(0,0,0,0)');

    利用当前点的count除以最大的count获取的结果做为alpha值。然后做一个RadialGradient画出来这个图就可以了。那么由于多个相近的点aphla效果的叠加就可以获取想要的效果了。这里就是canvas的nb之处了,看别的语言实现都是采用将一个这样的png图片画到画板上,但是canvas就可以直接实现这个效果。

    有了这幅aphla版本的heatmap 我们利用一个配送版做着色就大功告成了。

    这里又用到了上面所说的canvas的nb之处,在通常需要一个图片作为配色板的时候canvas可以自己做出来一个缓存起来。

    复制代码
    initColorPalette: function(){
        var me = this,
            canvas = document.createElement("canvas");
        canvas.width = "1";
        canvas.height = "256";
        var ctx = canvas.getContext("2d"),
            grad = ctx.createLinearGradient(0,0,1,256),
        gradient = me.get("gradient");
        for(var x in gradient){
            grad.addColorStop(x, gradient[x]);
        }
        ctx.fillStyle = grad;
        ctx.fillRect(0,0,1,256);
        //这里太强大了,缓存了我的画板数据,然后删除了画板
        me.set("gradient", ctx.getImageData(0,0,1,256).data);
        delete canvas;
        delete grad;
        delete ctx;
    },
    复制代码

    这种方式也给我们实现各种各样的配色提供了方便,我们只需要改变那个 **gradient** 就可以了。

    复制代码
    for(var i=3; i < length; i+=4){         // [0] -> r, [1] -> g, [2] -> b, [3] -> alpha
        var alpha = imageData[i],
        offset = alpha*4;
        if(!offset)
            continue;
        // we ve started with i=3
        // set the new r, g and b values
        // 根据透明度选择配色板上的配色
        imageData[i-3]=palette[offset];
        imageData[i-2]=palette[offset+1];
        imageData[i-1]=palette[offset+2];
        // we want the heatmap to have a gradient from transparent to the colors
        // as long as alpha is lower than the defined opacity (maximum),
        // we'll use the alpha value
        imageData[i] = (alpha < opacity)?alpha:opacity;
    }
    复制代码

    还是很简练的吧,看到heatmap.js的风格,真的像是在看一个不错的艺术品一样。强烈推荐一看~

  • 相关阅读:
    FreeTextbox 中吃掉Server路径的问题
    .net 中发mail到hotmail中乱码问题的解决
    管理的技巧和沟通
    将近一个星期都没来园子里面了
    园子里面西安的朋友请进来
    又开始乱了
    google真是个小人
    周末的晴天
    pm2入门
    strlen与sizeof区别 冷夜
  • 原文地址:https://www.cnblogs.com/mfryf/p/3110914.html
Copyright © 2011-2022 走看看