• 浅谈Heatmap
    时间:2014-05-19   作者:火丁笔记   出处:huoding.com

    在自然界之中,蛇的眼睛有夜视功能,即便是茫茫黑夜,它也能轻而易举的找到猎物,这是因为任何物体都会辐射热红外,且辐射的高低和温度成正比,由于生命体的体温会明显高于周围环境的温度,所以在蛇眼面前便无处遁形。热红外成像被广泛应用于军事领域,士兵带上能识别热红外的眼镜后能轻而易举的发现藏匿的敌人。

    热红外成像

    唠叨了半天,听上去似乎有点跑题了,其实不然,对互联网从业者而言,同样需要有火眼金睛,以便识别网友的喜好,此时的衡量标准是点击,点击越多则表示越喜欢,此技术被称作Heatmap,已经有网站提供此类服务,如:clickdensityclicktalecrazyegg等等,甚至还有类似clickheat项目提供源代码供你直接使用。

    不过最灵活的方案莫过于自己搞定,下面大概说说Heatmap的实现:

    捕捉点击

    当然,这需要Javascript来实现。为了不陷入浏览器兼容的泥潭,我们选择JQuery:

    <script>
    jQuery(document).ready(function() {
        $(document).mousedown(function(e) {
            if (e.clientX >= $(window).width() || e.clientY >= $(window).height()) {
                return;
            }

            $.get("/path/to/a/empty/html/file", {
                page_x       : e.pageX,
                page_y       : e.pageY,
                screen_width : screen.width,
                screen_height: screen.height
            });
        });
    });
    </script>

    客户端使用Ajax通过GET方法触发一个空HTML页面,当然,还可以更简单点:

    <script>

    var image = new Image();
    image.src = "...";

    </script>
    之所以要记录屏幕分辨率是因为有的情况下需要修正点击坐标。比如说,一个居中显示的定宽的页面,其同一个位置在不同分辨率下的坐标是不同的,当渲染图片的时候,坐标需要以一个分辨率为准进行修正。

    另外,如果用户正在拖动滚动条,是不应该记录的。

    分析日志

    客户端使用Ajax通过GET方法触发一个空HTML页面,如此就会在服务端留下日志:

    page_x=...&page_y=...&screen_width=...&screen_height=...
    不同的日志格式,结果会有所不同,这里仅仅以此为例来说明问题,本文采用AWK来解析日志,当然你也可以使用Perl或别的你熟悉的语言:

    #!/usr/bin/awk -f

    BEGIN {
        FS="&";
    }

    NF == 4 {
        param["page_x"]        = "0";
        param["page_y"]        = "0";
        param["screen_width"]  = "0";
        param["screen_height"] = "0";

        split($0, query, "&");

        for (key in query) {
            split(query[key], item, "=");
            if (item[1] in param) {
                    param[item[1]] = item[2];
            }
        }

        print "page_x:"       , param["page_x"];
        print "page_y:"       , param["page_y"];
        print "screen_width:" , param["screen_width"];
        print "screen_height:", param["screen_height"];

        print "\n";
    }
    至于数据的持久化,是使用MongoDB或者别的,自己定夺,这里就不多说了。

    渲染图片

    出于演示方便的考虑,我使用了一些随机生成的数据,以Imagick为例,代码如下:

    <?php
    $coordinates = array();
    for ($i = 0; $i < 1000; $i++) {
        $coordinates[] = array(rand($i, 1000), rand($i, 1000));
    }
    $max_repeat = max(
        array_count_values(
            array_map(function($v) { return "{$v[0]}x{$v[1]}"; }, $coordinates)
        )
    );

    $opacity = 1 - 1 / $max_repeat;

    $heatmap_image = new Imagick();

    $heatmap_image->newImage(1000, 1000, new ImagickPixel('white'));
    $heatmap_image->setImageFormat('png');

    $plot_image = new Imagick('plot.png');

    $iterator = $plot_image->getPixelIterator();
    foreach($iterator as $row) {
        foreach ($row as $pixel) {
            $colors = $pixel->getColor();
            foreach (array('r', 'g', 'b') as $channel) {
                $color = $colors[$channel];
                if ($color !== 255) {
                    $colors[$channel] = $color + ((255 - $color) * $opacity);
                }
            }

            $pixel->setColor("rgb({$colors['r']},{$colors['g']},{$colors['b']})");
        }

        $iterator->syncIterator();
    }

    $plot_size = $plot_image->getImageGeometry();

    foreach ($coordinates as $pair) {
        $heatmap_image->compositeImage(
            $plot_image,
            Imagick::COMPOSITE_MULTIPLY,
            $pair[0] - $plot_size['width'] / 2,
            $pair[1] - $plot_size['height'] / 2
        );
    }

    $color_image = new Imagick('clut.png');
    $heatmap_image->clutImage($color_image);
    $heatmap_image->writeImage('heatmap.png');
    ?>

    代码虽然很多,但并不复杂,其中用到了两个图片,分别是:plot.png和clut.png。实际应用时,有时候点击量会非常大,此时没有必要把所有的点击都渲染出来,而应该采取随机取样的策略,如果采用MongoDB持久化的话,可以参考:The Random Attribute

    备注:代码参考image-tempest

    最终展示

    形象一点来说,其实就是通过CSS+Javascript把生成的图片盖在网页上,并调节图片透明度来达到合二为一的效果,篇幅所限,具体代码留给大家自己实现,例子效果可参考下图:

    Heatmap

    BTW:热点可能会随着时间改变,为了能对照某个时间的网页,可以使用CutyCapt截屏。顺手再贴一个相关的项目:smt2(simple mouse tracking)。

    有关Heatmap的详细介绍,还可以参考

    How to make heat maps
    The definitive heatmap

    收工!Heatmap虽然不是很复杂的技术,但涉及的方面却很繁杂,希望本文能帮到大家。

    网友留言/评论

    我要留言/评论

    相关文章

    GEOS介绍和安装:GEOS是“Geometry Engine, Open Source”的含义,提供了OGC规范中简单几何要素对象操作的C++语言的实现。在地理信息系统领域,拓扑模型是重要的,其计算方法简单但是难得以实现。使得GEOS不同于其他项目的也正是“空间谓词”与“空间操作”。空间谓词是比较两个空间对象并返回一个布尔变量值作为结果,它表明了存在于两个空间对象之间特殊的关系。比如典型的空间谓词有Contains(), Intersects(), Touches(), and Crosses()函数等。GEOS项目中对该些函数的实现是异常强壮的,即使是奇异几何对象或是临时的坐标系统运算也不能使其运算不正常或计算错误。目前绝大多数的商业软件仍然在最基础的空间谓词处理上相对成熟,这正是GEOS项目的重要意义。“空间操作”则主要是对两个几何对象进行计算并且返回一个新的几何实体。比较典型的操作函数如Difference(), Union()以及Buffer()等。GEOS中的操作算法已经被广泛的经过了测试。GEOS类库被各类开源空间信息软件项目广泛应用,使用GEOS,它们可以基于最新的规范的几何实体来完成,同时也拥有了复杂空间方法的实现。
    有关技术管理经理的一些思考:这些天里工作的环境发生了一些微小的变化,可能以后对基层开发的程序员也会有更加具体的影响。上周参加 Open Party 时,重点听了《那些失败的项目们》,分析了一个项目的提出、实施,直到最后失败的过程。我也在想一个技术团队究竟应该用怎样的一种管理方式,才能让技术团队的效率达到更优。
    如何成为一位优秀的创业CEO:做创业公司的 CEO 可以说是世界上最有挑战性的事情之一。你得让客户喜欢你的产品,得组建团队,还要想办法从客户、合作者和投资者那里拿到资金;并且要指导整个工作流程的优化。
    关于独立游戏开发5个过程的相关建议:作为一名独立游戏开发者,在制作游戏过程中尽量多学些东西这一点极为重要。我认为这一过程包含以下几个步骤:1.想法 2.原型 3.迭代 4.测试 5.完工。我希望针对这个过程的每个阶段提供一些对你们有所帮助的建议,以便你们加快开发速度,提升游戏质量。
    代码审查:好事?坏事?:在软件开发领域,代码审查看起来是一个少有争议、相当平和的话题。
    如何避免重构带来的危险:重构代码很危险,它会给测试工作增加巨大的负担。除非你的程序需要重构,一定不要轻易重构代码。我这里所说的并不是把一个for循环改成while循环,或把一个StringBuffer改成StringBuilder,我说的是大动作,例如重写一个方法,一个函数,甚至整个类或包。如果你缺乏对一个方法或一个类的了解,那你重构它的条件就不充分。即使你有一个天才的计划,你也需要和团队一起设计其中重大的修改。
    Facebook如何提高软件质量?:刘彪是微软测试技术团队的一名软件设计工程师,他在自己的博客上分享了Facebook如何提高软件质量的原则、手段和背后的原因。
    程序员如何保持优秀:优秀便接近成功,金钱和名望比较难以控制。
    关于实施有效站会(Stand-Up Meeting)的三个技巧:通常的站会(tand-Up meetings)的形式总是让我感觉有点怪怪的。它有时会造成一些并不期望的效果。这篇文章里,我将向大家介绍一些在我们blossom公司里经过修改后的站会措施。
    软件项目管理中的“免坑”指南:“谁也无法改变现状,唯有无数程序员血洒大地,才能使项目重建天日。”这一点也不夸张,软件项目做烂了就是个坑,参与者也不过是填坑的。就像是在魔兽世界战场遇到国家队一样,你赢也赢不了,出也出不去。