浅谈Heatmap
在自然界之中,蛇的眼睛有夜视功能,即便是茫茫黑夜,它也能轻而易举的找到猎物,这是因为任何物体都会辐射热红外,且辐射的高低和温度成正比,由于生命体的体温会明显高于周围环境的温度,所以在蛇眼面前便无处遁形。热红外成像被广泛应用于军事领域,士兵带上能识别热红外的眼镜后能轻而易举的发现藏匿的敌人。
热红外成像
唠叨了半天,听上去似乎有点跑题了,其实不然,对互联网从业者而言,同样需要有火眼金睛,以便识别网友的喜好,此时的衡量标准是点击,点击越多则表示越喜欢,此技术被称作Heatmap,已经有网站提供此类服务,如:clickdensity,clicktale,crazyegg等等,甚至还有类似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的详细介绍,还可以参考
收工!Heatmap虽然不是很复杂的技术,但涉及的方面却很繁杂,希望本文能帮到大家。
浅谈Heatmap的更多相关文章
- 浅谈 Fragment 生命周期
版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Fragment 文中如有纰漏,欢迎大家留言指出. Fragment 是在 Android 3.0 中 ...
- 浅谈 LayoutInflater
浅谈 LayoutInflater 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/View 文中如有纰漏,欢迎大家留言指出. 在 Android 的 ...
- 浅谈Java的throw与throws
转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...
- 浅谈SQL注入风险 - 一个Login拿下Server
前两天,带着学生们学习了简单的ASP.NET MVC,通过ADO.NET方式连接数据库,实现增删改查. 可能有一部分学生提前预习过,在我写登录SQL的时候,他们鄙视我说:“老师你这SQL有注入,随便都 ...
- 浅谈WebService的版本兼容性设计
在现在大型的项目或者软件开发中,一般都会有很多种终端, PC端比如Winform.WebForm,移动端,比如各种Native客户端(iOS, Android, WP),Html5等,我们要满足以上所 ...
- 浅谈angular2+ionic2
浅谈angular2+ionic2 前言: 不要用angular的语法去写angular2,有人说二者就像Java和JavaScript的区别. 1. 项目所用:angular2+ionic2 ...
- iOS开发之浅谈MVVM的架构设计与团队协作
今天写这篇博客是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇博客的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...
- Linux特殊符号浅谈
Linux特殊字符浅谈 我们经常跟键盘上面那些特殊符号比如(?.!.~...)打交道,其实在Linux有其独特的含义,大致可以分为三类:Linux特殊符号.通配符.正则表达式. Linux特殊符号又可 ...
- 浅谈Angular的 $q, defer, promise
浅谈Angular的 $q, defer, promise 时间 2016-01-13 00:28:00 博客园-原创精华区 原文 http://www.cnblogs.com/big-snow/ ...
随机推荐
- Python基础-__main__
Python基础-_main_ 写在前面 如非特别说明,下文均基于Python3 一.__main__的官方解释 参考 _main_ -- Top-level script environment ' ...
- LeetCode(121) Best Time to Buy and Sell Stock
题目 Say you have an array for which the ith element is the price of a given stock on day i. If you we ...
- BZOJ 1587: 叶子合并leaves
题目大意:求n个数分成k段的最小代价. 题解:DP,没什么好说的. 代码: #include<cstdio> #include<algorithm> using namespa ...
- List<T> List<?> 区别用法
List<T>是泛型方法,List<?>是限制通配符 List<T>一般有两种用途:1.定义一个通用的泛型方法.伪代码: public interface Dao{ ...
- C# 导出Excel的示例
Excel知识点. 一.添加引用和命名空间 添加Microsoft.Office.Interop.Excel引用,它的默认路径是C:\Program Files\Microsoft Visual S ...
- 向php数组添加元素的方法哪种更高效
$arr = array(); // 第一种 array_push($arr, 'test'); // 第二种 $arr[] = 'test'; 参考PHP官方文档:http://php.net/ma ...
- TOJ 2419: Ferry Loading II
2419: Ferry Loading II Time Limit(Common/Java):1000MS/10000MS Memory Limit:65536KByteTotal Subm ...
- .NET重构(五):存储过程、触发器和函数的区别
导读:在触发器的学习过程中,师傅讲了它的耦合性高,建议我能用存储过程,那到底什么是存储过程呢,自己也不是特别了解,还有就是,触发器也算是一种特殊的存储过程,为什么就不建议多用呢?接下来,就谈谈触发器. ...
- BZOJ 1007 [HNOI2008]水平可见直线 ——半平面交 凸包
发现需要求一个下凸的半平面上有几个交点. 然后我们把它变成凸包的问题. 好写.好调.还没有精度误差. #include <map> #include <ctime> #incl ...
- .net面试题汇总一第一篇
1. 简述 private. protected. public. internal 修饰符的访问权限. private:私有成员,只能在类内部中才可以访问. protected:受保护的,只能在该类 ...