<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Corunet. El Blog &#187; Usability</title>
	<atom:link href="http://blog.corunet.com/category/usability/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.corunet.com</link>
	<description>Web development, usability and more</description>
	<lastBuildDate>Fri, 23 Oct 2009 15:33:27 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Clickmaps in sourceforge</title>
		<link>http://blog.corunet.com/clickmaps-in-sourceforge/</link>
		<comments>http://blog.corunet.com/clickmaps-in-sourceforge/#comments</comments>
		<pubDate>Mon, 21 Aug 2006 16:55:49 +0000</pubDate>
		<dc:creator>David Pardo</dc:creator>
				<category><![CDATA[English]]></category>
		<category><![CDATA[Usability]]></category>

		<guid isPermaLink="false">http://blog.corunet.com/english/clickmaps-in-sourceforge</guid>
		<description><![CDATA[Today, all the code for creating clickmaps has been uploaded to sourceforge and made public under a GPL license. You can find it at http://sourceforge.net/projects/clickmaps
Thanks to jerret, the code now uses RMagick calls and it&#8217;s usable with logs sporting more than ten thousand clicks per page. 
]]></description>
			<content:encoded><![CDATA[<p>Today, all the code for creating clickmaps has been uploaded to sourceforge and made public under a GPL license. You can find it at <a href="http://sourceforge.net/projects/clickmaps" onclick="pageTracker._trackPageview('/outgoing/sourceforge.net/projects/clickmaps?referer=');">http://sourceforge.net/projects/clickmaps</a></p>
<p>Thanks to jerret, the code now uses RMagick calls and it&#8217;s usable with logs sporting more than ten thousand clicks per page. </p>
]]></content:encoded>
			<wfw:commentRss>http://blog.corunet.com/clickmaps-in-sourceforge/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>The definitive heatmap</title>
		<link>http://blog.corunet.com/the-definitive-heatmap/</link>
		<comments>http://blog.corunet.com/the-definitive-heatmap/#comments</comments>
		<pubDate>Wed, 16 Aug 2006 18:39:21 +0000</pubDate>
		<dc:creator>David Pardo</dc:creator>
				<category><![CDATA[English]]></category>
		<category><![CDATA[Usability]]></category>
		<category><![CDATA[heatmaps]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://blog.corunet.com/english/the-definitive-heatmap</guid>
		<description><![CDATA[After the interest shown about the clickmaps / heatmaps articles, I&#8217;ve decided to gather all the information into an easy to use system. What we are going to make is a complete solution that allows collecting, analyzing and showing the click information our users give us. Now, it works in web pages not center aligned [...]]]></description>
			<content:encoded><![CDATA[<p><img class="alignleft" title="The final heatmap" src="/uploads/heatmaps/thumbnail.png" border="0" alt="The final Heatmap" hspace="3" vspace="3" width="150" height="104" align="left" />After the interest shown about the clickmaps / heatmaps articles, I&#8217;ve decided to gather all the information into an easy to use system. What we are going to make is a complete solution that allows collecting, analyzing and showing the click information our users give us. Now, it works in web pages not center aligned and is quite a bit more robust. Read on&#8230;<span id="more-11"></span></p>
<h2>What?</h2>
<p>If you are a webmaster, you had probably thought about what do users do in your website. Beyond usual statistics, clickmaps allow you to find where your users are clicking. This is quite useful to find areas in needing of change, layouts that don&#8217;t work as intended or anchors that aren&#8217;t being understood as you would like.</p>
<p>You&#8217;re going to be able to find every single click your users make in your website, being over a link or even in blank areas. We are going to do it the following way:</p>
<h2>The proccess</h2>
<p>We need to divide the full proccess into some manageable steps that use some open source tools. Since I work both in windows and linux systems, I&#8217;ll be <strong>OS agnostic </strong>and use only tools available in most systems, including Mac OSX.<br />
The main steps and the tools they use are the following:</p>
<ol>
<li>The collecting (javascript and apache)</li>
<li>The processing (ruby and imageMagick)</li>
<li>The showing (javascript)</li>
</ol>
<h2>The collecting</h2>
<p>We are going to use  a small snippet of unobtrusive javascript to allow the client to tell our server the click positions. Just place this small javascript file at the very end of your template, right before the closing &lt;body&gt; tag:</p>
<p class="toggle_code"><a href="/uploads/code/registerclicks.js" onclick="jQuery('#code_0').toggle('slow');return false">registerclicks.js</a></p>
<div  style="display:none" id="code_0">
<pre class="brush: js">/**
 * @author David Pardo: Corunet
 * Run after loading
 */

var xOffset,yOffset;
var tempX = 0;
var tempY = 0;

//detect browser
var IE = document.all?true:false
if (!IE) {
	document.captureEvents(Event.MOUSEMOVE)
}
//find the position of the first item on screen and store offsets
	//find the first item on screen (after body)
	var firstElement=document.getElementsByTagName(&#039;body&#039;)[0].childNodes[1];
	//find the offset coordinates
	xOffset=findPosX(firstElement);
	yOffset=findPosY(firstElement);
	if (IE){ // In IE there&#039;s a default margin in the page body. If margin&#039;s not defined, use defaults
		var marginLeftExplorer  = parseInt(document.getElementsByTagName(&#039;body&#039;)[0].style.marginLeft);
		var marginTopExplorer   = parseInt(document.getElementsByTagName(&#039;body&#039;)[0].style.marginTop);
		/*assume default 10px/15px margin in explorer*/
		if (isNaN(marginLeftExplorer)) {marginLeftExplorer=10;}
		if (isNaN(marginTopExplorer)) {marginTopExplorer=15;}
		xOffset=xOffset+marginLeftExplorer;
		yOffset=yOffset+marginTopExplorer;
	}
/*attach a handler to the onmousedown event that calls a function to store the values*/
document.onmousedown = getMouseXY;

/*Functions*/
/*Find positions*/
function findPosX(obj){
	var curleft = 0;
	if (obj.offsetParent){
		while (obj.offsetParent){
			curleft += obj.offsetLeft
			obj = obj.offsetParent;
		}
	}else if (obj.x){
		curleft += obj.x;
	}
	return curleft;
}

function findPosY(obj){
	var curtop = 0;
	if (obj.offsetParent){
		while (obj.offsetParent){
			curtop += obj.offsetTop
			obj = obj.offsetParent;
		}
	}else if (obj.y){
		curtop += obj.y;
	}
	return curtop;
}
function getMouseXY(e) {
	if (IE) {
		tempX = event.clientX + document.body.scrollLeft
		tempY = event.clientY + document.body.scrollTop
	} else {
		tempX = e.pageX
		tempY = e.pageY
	}
	tempX-=xOffset;
	tempY-=yOffset;
	var url=&#039;guardacoordenadas.pl?x=&#039;+tempX+&#039;&amp;y=&#039;+tempY;
	guardar(url);
	return true;
}
function guardar(url){
	var xmlDoc = null ;
	if (typeof window.ActiveXObject != &#039;undefined&#039; ) {
		xmlDoc = new ActiveXObject(&#039;Microsoft.XMLHTTP&#039;);
	}else {
		xmlDoc = new XMLHttpRequest();
	}
	xmlDoc.open( &#039;GET&#039;, url, true );
	xmlDoc.send( null );
}</pre>
</div>
<p>The code adds a onMouseDown handler to the document, executes a function for every click and returns true, since we want the user to follow the normal navigation. Then, when the user clicks any part of the page, a tiny request is going to get sent on the background to our server. The script has to calculate the offsett of the first element inside the &lt;body&gt; tag, because most pages arent aligned to the top-right corner. In <strong>liquid layouts </strong>the system is <strong>not going to work </strong>at all</p>
<p>The request is sent via a HttpRequest object that calls a file in the server. In last version, I used a small GCI written in perl to log the request and return an empty document, but since we want to serve so many request, there&#8217;s a better method to apply. Using a perl CGI, in a modern server, we get the following results benchmarking with apache bench (100 requests, 10 concurrent ones):</p>
<p><code><br />
Concurrency Level:      10<br />
Time taken for tests:   6.537187 seconds<br />
Complete requests:      100<br />
Failed requests:        0<br />
Write errors:           0<br />
Total transferred:      17100 bytes<br />
HTML transferred:       0 bytes<br />
Requests per second:    15.30 [#/sec] (mean)<br />
Time per request:       653.719 [ms] (mean)<br />
Time per request:       65.372 [ms] (mean, across ...)<br />
Transfer rate:          2.45 [Kbytes/sec] received<br />
</code></p>
<h2>Mod_imap</h2>
<p>Apache has some <em>modules</em> that work the following way:<br />
You define a handler and what you want to do with it. Some of them are well known, like mod_perl or mod_cgi, but a lesser known one, called mod_imap, does exactly what we want. It&#8217;s a module meant to return server-side image maps, but if we use an empty map file, all we get is a 204 status (no data) and a logged transaction. The difference is quite significative. Using Apache Bench with the same configuration, this is what we get:</p>
<p><code><br />
Concurrency Level:      10<br />
Time taken for tests:   0.106316 seconds<br />
Complete requests:      100<br />
Failed requests:        0<br />
Write errors:           0<br />
Total transferred:      36464 bytes<br />
HTML transferred:       20246 bytes<br />
Requests per second:    940.59 [#/sec] (mean)<br />
Time per request:       10.632 [ms] (mean)<br />
Time per request:       1.063 [ms] (mean, across ...)<br />
Transfer rate:          329.21 [Kbytes/sec] received<br />
</code></p>
<p>That&#8217;s 950 requests per second vs 15 with the CGI method!!! <strong>We are almost a hundred times faster with this approach!</strong>The only thing we have to do to use this mod_imap is to touch a little bit the apache configuration file. Do it carefully because it can hurt your entire server. In the relevant section add the following lines:</p>
<p><code><br />
AddHandler mod_imap .map<br />
CustomLog /tmp/clicklog clicklog #or modify according to your system<br />
</code></p>
<p>And define a custom log in the same file adding this:<br />
<code><br />
LogFormat "%q,%{Referer}i" clicklog<br />
</code></p>
<p>This way, everything ending in .map is going to be treated as a server-side map, and since the map is empty, it&#8217;s not taking your user anywhere. But it logs it, in file /tmp/clicklog (YMMV).</p>
<h2>The log analysis</h2>
<p>Since we used a logFormat apache directive to write our log, the format should be easy to parse. The query string is written in the log as it comes, and the full lines should be in the following format:</p>
<p><code><br />
?x=483&amp;y=32&amp;dx10&amp;dy15,http://demo.html<br />
?x=461&amp;y=177&amp;dx10&amp;dy15,http://demo.html<br />
?x=408&amp;y=40&amp;dx10&amp;dy15,http://demo.html<br />
(...)<br />
</code></p>
<p>I decided to write a Ruby script to parse the file and generate the final images, because I hadn&#8217;t used ruby before and thought it would be a good way to approach the problem. Last time I had written an structured perl script, but I think that object-oriented is the way to go in this particular situation, since the objects should be well-defined and dividing the program among several coders should be easier too.</p>
<p><em><strong>Update:</strong>Thanks to Jerret, this part has been enhanced using RMagick. Part of the code below can be updated and works some 50 times faster. On top of that, a new sourceforge project has been started at http://sourceforge.net/projects/clickmaps/ under a GPL license. Of course, if you don&#8217;t want to install/use RMagick you can still download the original version at the end of this post.</em></p>
<p>I´ll try to explain the model. It uses five classes:</p>
<dl>
<dt>Conf: </dt>
<dd>Sets some configuration variables and returns them as a hash. This way, every configuration variable is set in this class and it&#8217;s easy to get them later on</dd>
</dl>
<p class="toggle_code"><a href="/uploads/code/conf.rb" onclick="jQuery('#code_1').toggle('slow');return false">conf.rb</a></p>
<div  style="display:none" id="code_1">
<pre class="brush: rb">class Conf
	def initialize
	@data = {
		&#039;logfile&#039; =&gt; &#039;log.txt&#039;,
		&#039;dotimage&#039; =&gt; &#039;bolilla.png&#039;,
		&#039;format&#039; =&gt; &#039;png&#039;,
		&#039;colorimage&#039; =&gt; &#039;colors.png&#039;,
		&#039;opacity&#039; =&gt; &quot;0.50&quot;,
		&#039;dotwidth&#039; =&gt; 64
	}
	raise &quot;log file not found&quot; \
	unless File.exist?(@data[&#039;logfile&#039;])
	raise &quot;dot image not found&quot; \
	unless File.exist?(@data[&#039;dotimage&#039;])
	raise &quot;color image not found&quot; \
	unless File.exist?(@data[&#039;colorimage&#039;])
	end
	attr_reader :data
end</pre>
</div>
<dl>
<dt>Readparsefile</dt>
<dd>Reads and parses the file defined as logfile in the conf object. For each log line, it stores it into a click object and append it to an array. There are two methods that return all the URLs in the log file (geturls) and all the information for a single URL as a Log object</dd>
</dl>
<p class="toggle_code"><a href="/uploads/code/readparsefile.rb" onclick="jQuery('#code_2').toggle('slow');return false">readparsefile.rb</a></p>
<div  style="display:none" id="code_2">
<pre class="brush: rb">class Readparsefile
  def initialize(name)
    @name = name
    @data = Array.new
    lines = IO.readlines(@name).collect { |l| l.chomp }
    for line in lines
        line.gsub!(/\?x\=/,&#039;&#039;)
        line.gsub!(/\&amp;y\=/,&#039;,&#039;)
        line.gsub!(/\//,&#039;_&#039;)
        x,y,url = line.split(/,/)
        if (x and y and url)
            @data.push(Click.new(url, x, y))
        else
            $stderr.puts &quot;Warning: Bogus line &quot;&lt;&lt; line
        end
    end
    @urls = Array.new
    @data.each do |line|
        @urls.push(line.url)
    end
    raise &quot;no clicks found&quot; unless lines.length &gt; 0
    @urls.uniq!
  end
  def geturls
      return @urls
  end
  def coordsurl(url)
    @url=url
    xMax=0
    yMax=0
    coords = Array.new
    @data.each do |line|
        coords.push(line)
        xMax=line.x if line.x&gt;xMax
        yMax=line.y if line.y&gt;yMax
    end
    return Log.new(xMax,yMax,coords,@url)
  end
end</pre>
</div>
<dl>
<dt>Click</dt>
<dd> Stores the data in each log line, including X, Y and URL. Provides a method (xy) that returns an string like &#8220;x100y200&#8243; to compare the exact coordinates, useful to extract the maximum number of times a single click is repeated </dd>
</dl>
<p class="toggle_code"><a href="/uploads/code/click.rb" onclick="jQuery('#code_3').toggle('slow');return false">click.rb</a></p>
<div  style="display:none" id="code_3">
<pre class="brush: rb">class Click
    def initialize(url,x,y)
        @url=url
        @x=x
        @y=y
    end
    def url
        return @url
    end
    def x
        return @x.to_i
    end
    def y
        return @y.to_i
    end
    def xy
        return &quot;x&quot;+@x+&quot;y&quot;+@y
    end
end</pre>
</div>
<dl>
<dt>Log</dt>
<dd> Stores all the values pertinent to a single URL and gives accesors to them. There&#8217;s also a &#8220;next&#8221; method that returns next click within the same URL </dd>
</dl>
<p class="toggle_code"><a href="/uploads/code/log.rb" onclick="jQuery('#code_4').toggle('slow');return false">log.rb</a></p>
<div  style="display:none" id="code_4">
<pre class="brush: rb">class Log
    def initialize (x,y,list,url)
        @line = 0
        @x=x
        @y=y
        @url=url
        @list=list
        @points = Hash.new(0)
        @list.each do |point|
            @points[point.xy] +=1
        end
        @reps = @points.values.max
    end
    attr_reader <img src='http://blog.corunet.com/wp-includes/images/smilies/icon_mad.gif' alt=':x' class='wp-smiley' /> , :y, :list, :reps, :url
    def next
        coord = @list[@line]
        @line += 1
        return coord
    end
end</pre>
</div>
<dl>
<dt>Image</dt>
<dd>Receives a log object and the conf object. There are three methods to normalize the spot we&#8217;re going to use as a click indicator (normalizespot), compose every click as a dot (iterate) and colorize the final image (colorize)</dd>
</dl>
<p class="toggle_code"><a href="/uploads/code/image.rb" onclick="jQuery('#code_5').toggle('slow');return false">image.rb</a></p>
<div  style="display:none" id="code_5">
<pre class="brush: rb">class Image
    def initialize(data,conf)
        @data = data
        @name = data.url.gsub!(/\W/,&#039;&#039;).gsub!(/\_/,&#039;&#039;).to_s
        @conf=conf.data
    end
    def normalizespot
        #divide spot.png intensity by max. position reps (@data.reps)
        intensity = (100-(100/@data.reps).ceil).to_s
        normalize = &quot;convert &quot;&lt;&lt;@conf[&#039;dotimage&#039;]&lt;&lt;&quot; -fill white -colorize &quot;&lt;&lt;intensity&lt;&lt;&quot;% &quot;&lt;&lt;@name&lt;&lt;&quot;.bol.png&quot;
        system(normalize)
    end
    def iterate
        halfwidth=@conf[&#039;dotwidth&#039;]/2
        compose = &quot;convert -page &quot;&lt;&lt;(@data.x+halfwidth).to_s&lt;&lt;&quot;x&quot;&lt;&lt;(@data.y+halfwidth).to_s&lt;&lt;&quot; pattern:gray100 &quot;
        #iterate spots
        @data.list.each do |dot|
            compose &lt;&lt; &quot;-page +&quot;&lt;&lt;((dot.x)-halfwidth).to_s&lt;&lt;&quot;+&quot;&lt;&lt;((dot.y)-halfwidth).to_s&lt;&lt;&quot; &quot;&lt;&lt;@name&lt;&lt;&quot;.bol.png &quot;
        end
        compose &lt;&lt; &quot;-background white -compose multiply -flatten &quot;&lt;&lt;@name&lt;&lt;&quot;.empty.png&quot;
        system(compose)
    end
    def colorize
        #invert image...
        invert = &quot;convert &quot;&lt;&lt;@name&lt;&lt;&quot;.empty.png -negate &quot;&lt;&lt;@name&lt;&lt;&quot;.full.png&quot;
        system(invert)
        #colorize it...
        colorize = &quot;convert &quot;&lt;&lt;@name&lt;&lt;&#039;.full.png -type TruecolorMatte &#039;&lt;&lt;@conf[&#039;colorimage&#039;]&lt;&lt;&#039; -fx &quot;v.p{0,u*v.h}&quot; &#039;&lt;&lt;@name&lt;&lt;&quot;.colorized.png&quot;
        system(colorize)
        #and apply transparency...
        transparency = &quot;convert &quot;&lt;&lt;@name&lt;&lt;&#039;.colorized.png -channel A -fx &quot;A*&#039;&lt;&lt;@conf[&#039;opacity&#039;]&lt;&lt;&#039;&quot;  &#039;&lt;&lt;@name&lt;&lt;&#039;.final.&#039;&lt;&lt;@conf[&#039;format&#039;]
        system(transparency)
    end
end</pre>
</div>
<p>Then, the main program is only eight lines long. It leverages the objects&#8217; methods to be as compact as possible. In fact, the only thing it does is to iterate over each url to create a different image.<br />
<code> </code></p>
<pre>conf = Conf.new
file = Readparsefile.new(conf.data['logfile'])
file.geturls.each do |url|
    image = Image.new(file.coordsurl(url),conf)
    image.normalizespot
    image.iterate
    image.colorize
end</pre>
<h2>Is it better?</h2>
<p>You can find another program (this time written in perl) in an older post that does a similar job of making heatmaps. But there has been some modifications that makes this an usable system instead of a proof of concept:</p>
<dl>
<dt>Flexible configuration</dt>
<dd>Over the harcoded last version, in this one is quite simple to modify the images used in the heatmap generation, or the log name. <strong>You only have to modify the Conf definition</strong>. It would be so easy to use an external conf file, but doing it this way is quicker for me</dd>
<dt>Multiple URL support</dt>
<dd>While last version only let you extract one image, this one <strong>makes a heatmap for every URL in your log</strong>. </dd>
<dt>Much faster execution time</dt>
<dd>Instead of composing the full image everytime, now we create a single ImageMagick sentence to do al the composition for us. That gives us a couple of orders speed advantage. <strong>Last version lasted about fifteen minutes for a couple hundred clicks, and now it&#8217;s about five seconds</strong>. Please note that, for many clicks, the program uses quite a bit of memory. Probably for a production environment it would be neccesary to divide the <em>compose</em> sentence into manageable chuncks, and iterate at the end with them to create the final heatmap.</dd>
<dt>Manual capture is not needed anymore</dt>
<dd>Since the last step is to decrement the opacity of the map, we can use a little bit of javascript to overlay the PNG image over the original page. So, the stakeholders can review it without someone manually capturing the screen. This way we don&#8217;t need to set an XServer in the production environment</dd>
<dt>Easier to maintain and extend</dt>
<dd>The object oriented paradigm doesn&#8217;t give us faster code, but much more manageable one. You can extend it as you want</dd>
</dl>
<h2>What you get</h2>
<p>Now, you&#8217;ll have several images. Most of them are OK to delete, but there&#8217;s one ending in final.png that&#8217;s your heatmap. We&#8217;re going to overlay it on top of your web page. That image should be a semi-transparent PNG like this one:</p>
<p><img src="/uploads/final2_p.png" alt="" /></p>
<h2>The overlay</h2>
<p>This is the final part of the proccess. We already have the overlay image and all we need is a <strong>javascript snippet </strong>that can be called anytime and that creates a layer on top of your website with the click information. Just like the first step, we&#8217;re going to position it over the very first item in the page.<br />
The best way to do that is via a <em><strong>bookmarklet</strong></em>, that is, an small javascript snippet saved as a bookmark. This way, you can have it in your browser and ask for the overlay image when you feel like. The javascript recalculates the offsets of the first element inside the &lt;body&gt; tag and writes the heatmap image on top of it.</p>
<p class="toggle_code"><a href="/uploads/code/overlay.js" onclick="jQuery('#code_6').toggle('slow');return false">overlay.js</a></p>
<div  style="display:none" id="code_6">
<pre class="brush: js">/**
 * @author DavidPardo
 */
//find the position of the first item on screen and store offsets
//find the first item on screen (after body)
var firstElement=document.getElementsByTagName(&#039;body&#039;)[0].childNodes[1];
//find the offset coordinates
xOffset=findPosX(firstElement);
yOffset=findPosY(firstElement);
if (IE){ // In IE there&#039;s a default margin in the page body. If margin&#039;s not defined, use defaults
	var marginLeftExplorer  = parseInt(document.getElementsByTagName(&#039;body&#039;)[0].style.marginLeft);
	var marginTopExplorer   = parseInt(document.getElementsByTagName(&#039;body&#039;)[0].style.marginTop);
	/*assume default 10px/15px margin in explorer*/
	if (isNaN(marginLeftExplorer)) {marginLeftExplorer=10;}
	if (isNaN(marginTopExplorer)) {marginTopExplorer=15;}
	xOffset=xOffset+marginLeftExplorer;
	yOffset=yOffset+marginTopExplorer;
}
// add the image element to the dom, absolutely positioned
var style=&quot;z-index:100;position:absolute;left:&quot;+xOffset+&quot;px;top:&quot;+yOffset+&quot;px&quot;;
var bodytag=document.getElementsByTagName(&#039;body&#039;)[0]
var newdiv = document.createElement(&#039;div&#039;);
var currentpage=location.href;
var escapedpage=currentpage.replace(/\W/g,&#039;&#039;);
newdiv.setAttribute(&#039;id&#039;,&#039;heatmap&#039;);
newdiv.innerHTML = &#039;&lt;img src=&quot;&#039;+escapedpage+&#039;.final.png&quot; style=&quot;&#039;+style+&#039;&quot; &gt;&#039;;
bodytag.appendChild(newdiv);
</pre>
</div>
<h2>The result</h2>
<p>We got a beautiful heatmap on top of our web page. We can call the overlay from wherever we want and show it to the project stakeholders. Look at the result:</p>
<p><img src="/uploads/final.png" alt="" /></p>
<h2>The code</h2>
<p>I made a <strong>ready to download package with all the code</strong>. It&#8217;s released under a MIT license that means that you can do whatever you want with it. Probably in the future it&#8217;ll be part of an open source release; if you feel like, start it yourself or contact me for more information.</p>
<p><a href="/uploads/clickmaps.net.tar.gz">Download code. Tar.gz file </a></p>
<h2>What else?</h2>
<p>The sky is the limit. If you want a hosted service, contact us. <a href="http://www.corunet.com" onclick="pageTracker._trackPageview('/outgoing/www.corunet.com?referer=');">Our company </a>can give you bespoke solutions to all your web intelligence needs, being it log analysis, path tracking and so on. If you&#8217;re a developer, feel free to use all the code as you wish, and please <a href="mailto:david@corunet.com">write me </a>to tell your experiences. Stay tuned!</p>
<p>By the way, there has been a post in <a href="http://remysharp.com/2007/01/18/how-i-achieved-cross-site-scripting/" onclick="pageTracker._trackPageview('/outgoing/remysharp.com/2007/01/18/how-i-achieved-cross-site-scripting/?referer=');"> remysharp blog</a> explaining how to record the clicks in a different server. Thanks.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.corunet.com/the-definitive-heatmap/feed/</wfw:commentRss>
		<slash:comments>81</slash:comments>
		</item>
		<item>
		<title>How to make heat maps</title>
		<link>http://blog.corunet.com/how-to-make-heat-maps/</link>
		<comments>http://blog.corunet.com/how-to-make-heat-maps/#comments</comments>
		<pubDate>Sun, 06 Aug 2006 16:30:37 +0000</pubDate>
		<dc:creator>David Pardo</dc:creator>
				<category><![CDATA[English]]></category>
		<category><![CDATA[Usability]]></category>

		<guid isPermaLink="false">http://blog.corunet.com/english/how-to-make-heat-maps</guid>
		<description><![CDATA[NOTE: This post has been improved at The definitive heatmap
There is not much documentation about creating heat maps. I haven&#8217;t been able to find an open source solution that, giving the coordinates, creates a heat map like those shown in Etre blog
In the last post (part 1, part 2) we got a list of click [...]]]></description>
			<content:encoded><![CDATA[<p>NOTE: This post has been improved at <a href="http://blog.corunet.com/english/the-definitive-heatmap">The definitive heatmap</a><br />
There is not much documentation about creating heat maps. I haven&#8217;t been able to find an open source solution that, giving the coordinates, creates a heat map like those shown in <a href="http://www.etre.com/blog/2006/05/five_days_five_heatmaps/" title="Five days, five heatmaps" target="_blank" onclick="pageTracker._trackPageview('/outgoing/www.etre.com/blog/2006/05/five_days_five_heatmaps/?referer=');">Etre blog</a><br />
In the last post (<a href="/english/zero-budget-eye-tracking-clickmaps">part 1</a>, <a href="/english/zero-budget-eye-tracking-clickmaps-part-2">part 2</a>) we got a list of click positions in a web page, but the result is easy to improve.</p>
<p><span id="more-10"></span><br />
We have the tools. Using a bit of scripting and ImageMagick, it shouldn&#8217;t be difficult to make a useful heat map. </p>
<h2>The steps</h2>
<ol>
<li>Normalizing the data</li>
<li>Plotting the dots</li>
<li>Capturing the screen</li>
<li>Making the overlay</li>
</ol>
<h2>Normalizing the data<br />
</h2>
<p>Lets assume that the data comes in the following format:<br />
<code><br />
x332y288 http://corunet.com/ 192.168.0.10<br />
x399y288 http://corunet.com/ 192.168.0.10<br />
x489y294 http://corunet.com/ 192.168.0.10<br />
x655y346 http://corunet.com/ 192.168.0.10<br />
x709y351 http://corunet.com/ 192.168.0.10<br />
x350y348 http://corunet.com/ 192.168.0.10<br />
x384y305 http://corunet.com/ 192.168.0.10<br />
&#8230;<br />
</code><br />
We need to pick the coordinates and find the boundary values (maximum) and the maximum number of clicks in the same point. This way, we will use that number as the maximum intensity in our heat map. The point most reclicked will have an intensity of 255 and the dots with zero clicks will have zero value. This way, we will probably exceed the maximum value for dots close to each other, but we&#8217;ll try to solve it later..</p>
<p>A better way to do it would be to define a matrix as big as the maximum values, add the coordinates and normalyze it, making the biggest value 255 and the smallest one zero, but that will have to wait, at least, until the second version.</p>
<p>The first part of the code (again, in perl) will be like this, marking split lines with #//:</p>
<p><code><br />
<span style="color:#399;font-style:italic;">#!/usr/bin/perl</span><br />
<span style="color:#000;">use</span> <span style="color:#900;">strict</span><span style="color:#000;">;</span><br />
<span style="color:#000;">my</span> <span style="color:#080;">$imgWidth</span><span style="color:#000;">=</span><span style="color:#f0f;">64</span><span style="color:#000;">;</span><br />
<span style="color:#001;">open</span> <span style="color:#000;">(</span><span style="color:#3A3;">LOG</span><span style="color:#000;">,</span><span style="color:#00a;">"</span><span style="color:#00a;">log.txt</span><span style="color:#00a;">"</span><span style="color:#000;">)</span> <span style="color:#000;">||</span> <span style="color:#300;">die</span><span style="color:#000;">;</span><br />
<span style="color:#000;">my</span> <span style="color:#f70;">@log</span><span style="color:#000;">=</span><span style="color:#000;">&lt;</span><span style="color:#3A3;">LOG</span><span style="color:#000;">&gt;</span><span style="color:#000;">;</span><br />
<span style="color:#001;">close</span> <span style="color:#000;">(</span><span style="color:#3A3;">LOG</span><span style="color:#000;">)</span><span style="color:#000;">;</span><br />
<span style="color:#000;">my</span> <span style="color:#000;">(</span><span style="color:#f70;">@x</span><span style="color:#000;">,</span><span style="color:#f70;">@y</span><span style="color:#000;">)</span><span style="color:#000;">;</span><br />
<span style="color:#000;">my</span> <span style="color:#000;">(</span><span style="color:#080;">$xMax</span><span style="color:#000;">,</span><span style="color:#080;">$yMax</span><span style="color:#000;">,</span><span style="color:#080;">$repetitionsMax</span><span style="color:#000;">,</span><span style="color:#080;">$i</span><span style="color:#000;">,</span><span style="color:#080;">$j</span><span style="color:#000;">)</span><span style="color:#000;">;</span><br />
<span style="color:#000;">my</span> <span style="color:#80f;">%repetitions</span><span style="color:#000;">;</span><br />
<span style="color:#000;">foreach</span> <span style="color:#000;">my</span> <span style="color:#080;">$line</span><span style="color:#000;">(</span><span style="color:#f70;">@log</span><span style="color:#000;">)</span><span style="color:#000;">{</span><br />
	<span style="color:#000;">(</span><span style="color:#000;">my</span> <span style="color:#080;">$coords</span><span style="color:#000;">,</span><span style="color:#300;">undef</span><span style="color:#000;">,</span><span style="color:#300;">undef</span><span style="color:#000;">)</span><span style="color:#000;">=</span><span style="color:#001;">split</span><span style="color:#000;">(</span><span style="color:#00a;">"</span><span style="color:#00a;"><span style="color:#800;">\t</span></span><span style="color:#00a;">"</span><span style="color:#000;">,</span><span style="color:#080;">$line</span><span style="color:#000;">)</span><span style="color:#000;">;</span><br />
	<span style="color:#80f;">$repetitions</span><span style="color:#000;">{</span><span style="color:#080;">$coords</span><span style="color:#000;">}</span><span style="color:#000;">++</span><span style="color:#000;">;</span><br />
	<span style="color:#080;">$coords</span><span style="color:#000;">=~</span><span style="color:#00a;">s/</span><span style="color:#00a;">x</span><span style="color:#00a;">/</span><span style="color:#00a;"></span><span style="color:#00a;">/</span><span style="color:#000;">;</span><br />
	<span style="color:#000;">(</span><span style="color:#f70;">$x</span><span style="color:#000;">[</span><span style="color:#080;">$i</span><span style="color:#000;">]</span><span style="color:#000;">,</span><span style="color:#f70;">$y</span><span style="color:#000;">[</span><span style="color:#080;">$i</span><span style="color:#000;">]</span><span style="color:#000;">)</span><span style="color:#000;">=</span><span style="color:#001;">split</span><span style="color:#000;">(</span><span style="color:#00a;">"</span><span style="color:#00a;">y</span><span style="color:#00a;">"</span><span style="color:#000;">,</span><span style="color:#080;">$coords</span><span style="color:#000;">)</span><span style="color:#000;">;</span><br />
	<span style="color:#080;">$i</span><span style="color:#000;">++</span><span style="color:#000;">;</span><br />
<span style="color:#000;">}</span><br />
<span style="color:#000;">while</span> <span style="color:#000;">(</span><span style="color:#000;">(</span><span style="color:#000;">my</span> <span style="color:#080;">$key</span><span style="color:#000;">,</span> <span style="color:#000;">my</span> <span style="color:#080;">$value</span><span style="color:#000;">)</span><span style="color:#000;">=</span><span style="color:#001;">each</span><span style="color:#000;">(</span><span style="color:#80f;">%repetitions</span><span style="color:#000;">)</span><span style="color:#000;">)</span><span style="color:#000;">{</span><br />
	<span style="color:#080;">$repetitionsMax</span><span style="color:#000;">=</span><span style="color:#080;">$value</span> <span style="color:#000;">if</span> <span style="color:#080;">$repetitionsMax</span><span style="color:#000;">&lt;</span><span style="color:#080;">$value</span><span style="color:#000;">;</span><br />
<span style="color:#000;">}</span><br />
<span style="color:#000;">for</span> <span style="color:#000;">(</span><span style="color:#080;">$j</span><span style="color:#000;">=</span><span style="color:#f0f;">0</span><span style="color:#000;">;</span><span style="color:#080;">$j</span><span style="color:#000;">&lt;</span><span style="color:#080;">$i</span><span style="color:#000;">;</span><span style="color:#080;">$j</span><span style="color:#000;">++</span><span style="color:#000;">)</span><span style="color:#000;">{</span><br />
	<span style="color:#080;">$xMax</span><span style="color:#000;">=</span><span style="color:#f70;">$x</span><span style="color:#000;">[</span><span style="color:#080;">$j</span><span style="color:#000;">]</span> <span style="color:#000;">if</span> <span style="color:#080;">$xMax</span><span style="color:#000;">&lt;</span><span style="color:#f70;">$x</span><span style="color:#000;">[</span><span style="color:#080;">$j</span><span style="color:#000;">]</span><span style="color:#000;">;</span><br />
	<span style="color:#080;">$yMax</span><span style="color:#000;">=</span><span style="color:#f70;">$y</span><span style="color:#000;">[</span><span style="color:#080;">$j</span><span style="color:#000;">]</span> <span style="color:#000;">if</span> <span style="color:#080;">$yMax</span><span style="color:#000;">&lt;</span><span style="color:#f70;">$y</span><span style="color:#000;">[</span><span style="color:#080;">$j</span><span style="color:#000;">]</span><span style="color:#000;">;</span><br />
<span style="color:#000;">}</span><br />
<span style="color:#300;">print</span> <span style="color:#080;">$repetitionsMax</span><span style="color:#000;">,</span><span style="color:#00a;">"</span><span style="color:#00a;"><span style="color:#800;">\n</span><span style="color:#080;">$xMax</span>;<span style="color:#080;">$yMax</span><span style="color:#800;">\n</span></span><span style="color:#00a;">"</span><span style="color:#000;">;</span><br />
</code></p>
<p>Now, we have  three interesting values: xMax, yMax and repetitionsMax. The first two are going to be the dimensions of the canvas, and the third one, our maximum value. For me, those are 848 pixels, 2641 pixels and 3 repetitions. So, lets create the canvas:</p>
<p><code><br />
<span style="color:#000;">my</span> <span style="color:#080;">$xCanvas</span><span style="color:#000;">=</span><span style="color:#080;">$xMax</span><span style="color:#000;">+</span><span style="color:#001;">int</span><span style="color:#000;">(</span><span style="color:#080;">$imgWidth</span><span style="color:#000;">/</span><span style="color:#f0f;">2</span><span style="color:#000;">)</span><span style="color:#000;">;</span><br />
<span style="color:#000;">my</span> <span style="color:#080;">$yCanvas</span><span style="color:#000;">=</span><span style="color:#080;">$yMax</span><span style="color:#000;">+</span><span style="color:#001;">int</span><span style="color:#000;">(</span><span style="color:#080;">$imgWidth</span><span style="color:#000;">/</span><span style="color:#f0f;">2</span><span style="color:#000;">)</span><span style="color:#000;">;</span><br />
<span style="color:#000;">my</span> <span style="color:#080;">$createcanvas</span><span style="color:#000;">=</span><span style="color:#00a;">"</span><span style="color:#00a;">convert -size </span><span style="color:#00a;">"</span><span style="color:#000;">.</span> <span style="color:#080;">$xCanvas</span><span style="color:#000;">.#//<br /></span><span style="color:#00a;">"</span><span style="color:#00a;">x</span><span style="color:#00a;">"</span><span style="color:#000;">.</span><span style="color:#080;">$yCanvas</span><span style="color:#000;">.</span><span style="color:#00a;">"</span><span style="color:#00a;"> pattern:gray100 empty.png</span><span style="color:#00a;">"</span><span style="color:#000;">;</span><br />
<span style="color:#001;">system</span> <span style="color:#000;">(</span><span style="color:#080;">$createcanvas</span><span style="color:#000;">)</span><span style="color:#000;">;</span><br />
</code></p>
<h2>Plotting the dots</h2>
<p>We are going to compound our recently created canvas with an existing image for every line in the log. The image, <a href="/uploads/heatmaps/bolilla.png" title="Png image to mark the dots" target="_blank">bolilla.png</a>, is a PNG file with transparency that allows us to paint a region instead of a single dot for each click. As the maximum opacity value is 1 (the center of the image is completely opaque) we will use a simple formula to determine the percent of overlay we are going to use: Percent=100/repetitionsMax:</p>
<p><code><br />
<span style="color:#000;">my</span> <span style="color:#080;">$createNormalizedSpot</span><span style="color:#000;">=</span><span style="color:#00a;">"</span><span style="color:#00a;">convert bolilla.png -fill #//<br /> white -colorize </span><span style="color:#00a;">"</span><span style="color:#000;">.</span><span style="color:#001;">int</span><span style="color:#000;">(</span><span style="color:#f0f;">100</span><span style="color:#000;">/</span><span style="color:#080;">$repetitionsMax</span><span style="color:#000;">)</span><span style="color:#000;">.</span><span style="color:#00a;">"</span><span style="color:#00a;">% bol.png</span><span style="color:#00a;">"</span><span style="color:#000;">;</span><br />
<span style="color:#001;">system</span> <span style="color:#000;">(</span><span style="color:#080;">$createNormalizedSpot</span><span style="color:#000;">)</span><span style="color:#000;">;</span><br />
</code></p>
<p>So, for each log line, I&#8217;ll compound the image again over the canvas:</p>
<p><code><br />
<span style="color:#000;">for</span> <span style="color:#000;">(</span><span style="color:#080;">$j</span><span style="color:#000;">=</span><span style="color:#f0f;">0</span><span style="color:#000;">;</span><span style="color:#080;">$j</span><span style="color:#000;">&lt;</span><span style="color:#080;">$i</span><span style="color:#000;">;</span><span style="color:#080;">$j</span><span style="color:#000;">++</span><span style="color:#000;">)</span><span style="color:#000;">{</span><br />
	<span style="color:#000;">my</span> <span style="color:#000;">(</span><span style="color:#080;">$x</span><span style="color:#000;">,</span><span style="color:#080;">$y</span><span style="color:#000;">)</span><span style="color:#000;">;</span><br />
	<span style="color:#080;">$x</span><span style="color:#000;">=</span><span style="color:#f70;">$x</span><span style="color:#000;">[</span><span style="color:#080;">$j</span><span style="color:#000;">]</span><span style="color:#000;">-</span><span style="color:#f0f;">32</span><span style="color:#000;">;</span><br />
	<span style="color:#080;">$y</span><span style="color:#000;">=</span><span style="color:#f70;">$y</span><span style="color:#000;">[</span><span style="color:#080;">$j</span><span style="color:#000;">]</span><span style="color:#000;">-</span><span style="color:#f0f;">32</span><span style="color:#000;">;</span><br />
	<span style="color:#001;">system</span> <span style="color:#000;">(</span><span style="color:#00a;">"</span><span style="color:#00a;">composite -compose multiply -geometry #//<br /> +<span style="color:#080;">$x</span>+<span style="color:#080;">$y</span> bol.png empty.png empty.png<span style="color:#800;">\n</span></span><span style="color:#00a;">"</span><span style="color:#000;">)</span><span style="color:#000;">;</span><br />
	<span style="color:#300;">print</span> <span style="color:#00a;">"</span><span style="color:#00a;">used <span style="color:#080;">$j</span> of <span style="color:#080;">$i</span> clicks<span style="color:#800;">\n</span></span><span style="color:#00a;">"</span><span style="color:#000;">;</span><br />
<span style="color:#000;">}</span><br />
</code></p>
<p>And I got something like this:</p>
<p class="fotocentro">
<img src="/uploads/heatmaps/empty_p.png" title="Dots painted" >
</p>
<h2>Making the final image</h2>
<p>We need to remap empty.png to a false color colormap to get the typical heat map appearance. The first thing that come to my mind was to use photoshop, but it&#8217;s much more interesting to do it in Imagemagick too. Version 6 is needed to apply colors from one image to another, but once I discovered that, worked like a charm. First of all, we need to invert the image we got:</p>
<p><code><br />
convert empty.png -negate full.png<br />
</code></p>
<p>And then, replace the colors with </p>
<p><code><br />
convert full.png colors.png -fx "v.p{0,u*v.h}" final.png<br />
</code></p>
<p class="fotocentro">
<img src="/uploads/heatmaps/map_p.png" alt="Final map image">
</p>
<p>Where <a href="/uploads/heatmaps/colors.png" title="Colors image" target="_blank">colors.png</a> is a gradient image i made first. <a href="/uploads/heatmaps/colors.png" title="Colors image" target="_blank">You can download and use it as you want.</a></p>
<h2>Capturing the screen</h2>
<p>The cheapest and easiest way to capture the original web screen is by using <a href="https://addons.mozilla.org/firefox/1146/" title="Firefox add-on: Screen Grab" target="_blank" onclick="pageTracker._trackPageview('/outgoing/addons.mozilla.org/firefox/1146/?referer=');">Firefox&#8217; Screen Grab. </a> It allows us to capture the complete web page, without having to capture it screen by screen and stitching by hand. What we get is something like this:</p>
<p class="fotocentro">
<img src="/uploads/heatmaps/blog_p.png" alt="screen capture">
</p>
<p>I saved the result as <i>blog.png</i></p>
<h2>Compounding the final image</h2>
<p>Now, all we have to do is to compound the final image with the screen capture, again from the command line:</p>
<p><code><br />
composite -blend 40% final.png blog.png heatmap.png<br />
</code></p>
<h2>The result</h2>
<p>What we got is a heat map of the data in the log. It&#8217;s a bit more visual than the last version, and easier to understand when we have lots of dots.</p>
<p class="fotocentro">
<img src="/uploads/heatmaps/final_p.png" alt="Final heatmap">
</p>
<h2>The code</h2>
<p>You can download the perl script that does all the work. You will need a working instance of perl (5.6 or better) and ImageMagick V6. Both of them should come as standard in moder linux distributions, or you can install each in a windows system from <a href="http://activestate.com" target="_blank" onclick="pageTracker._trackPageview('/outgoing/activestate.com?referer=');">Activestate (perl)</a> and <a href="http://imagemagick.org" target="_blank" onclick="pageTracker._trackPageview('/outgoing/imagemagick.org?referer=');">ImageMagick.org (ImageMagick)</a>.</p>
<p><a href="/uploads/heatmaps/heatmapsmaker.tar.gz">Download code. tgz file, 258KB.</a></p>
<p>Drop me a line if you use it!</p>
<p><a href="mailto:david@corunet.com" target="_blank">david@corunet.com</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.corunet.com/how-to-make-heat-maps/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Zero budget eye tracking. Clickmaps part 2</title>
		<link>http://blog.corunet.com/zero-budget-eye-tracking-clickmaps-part-2/</link>
		<comments>http://blog.corunet.com/zero-budget-eye-tracking-clickmaps-part-2/#comments</comments>
		<pubDate>Sun, 30 Jul 2006 16:01:08 +0000</pubDate>
		<dc:creator>David Pardo</dc:creator>
				<category><![CDATA[English]]></category>
		<category><![CDATA[Usability]]></category>
		<category><![CDATA[heatmaps]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[perl]]></category>

		<guid isPermaLink="false">http://blog.corunet.com/english/zero-budget-eye-tracking-clickmaps-part-2</guid>
		<description><![CDATA[In the first part of Zero budget eye tracking, we wrote a small script to keep track of the clicks in a web page. A couple of days after, we already have some information to analyze. Keep on reading to know how&#8230;
NOTE: This post has been improved at The definitive heatmap
I added the scripts to [...]]]></description>
			<content:encoded><![CDATA[<p><a href="/english/zero-budget-eye-tracking-clickmaps">In the first part of Zero budget eye tracking</a>, we wrote a small script to keep track of the clicks in a web page. A couple of days after, we already have some information to analyze. Keep on reading to know how&#8230;<span id="more-9"></span><br />
NOTE: This post has been improved at <a href="http://blog.corunet.com/english/the-definitive-heatmap">The definitive heatmap</a><br />
I added the scripts to our website (<a href="http://www.corunet.com" onclick="pageTracker._trackPageview('/outgoing/www.corunet.com?referer=');">www.corunet.com</a>) and used a resolution of 1280&#215;1024 px. After 65 clicks, I have a log like this one:</p>
<p><code><br />
x332y288	http://corunet.com/	192.168.0.10<br />
x399y288	http://corunet.com/	192.168.0.10<br />
x489y294	http://corunet.com/	192.168.0.10<br />
x655y346	http://corunet.com/	192.168.0.10<br />
x709y351	http://corunet.com/	192.168.0.10<br />
x350y348	http://corunet.com/	192.168.0.10<br />
x384y305	http://corunet.com/	192.168.0.10<br />
...<br />
</code></p>
<p>We would need to filter the x and y coordinates to draw some dots over the page. I will use the following code (written in perl):</p>
<p><code><br />
<span style="color:#399;font-style:italic;">#!/usr/bin/perl</span><br />
<span style="color:#001;">open</span> <span style="color:#000;">(</span><span style="color:#3A3;">DATOS</span><span style="color:#000;">,</span><span style="color:#00a;">"</span><span style="color:#00a;">log.txt</span><span style="color:#00a;">"</span><span style="color:#000;">)</span><span style="color:#000;">;</span><br />
<span style="color:#000;">my</span> <span style="color:#f70;">@datos</span><span style="color:#000;">=</span><span style="color:#000;">&lt;</span><span style="color:#3A3;">DATOS</span><span style="color:#000;">&gt;</span><span style="color:#000;">;</span><br />
<span style="color:#001;">close</span> <span style="color:#000;">(</span><span style="color:#3A3;">DATOS</span><span style="color:#000;">)</span><span style="color:#000;">;</span><br />
<span style="color:#000;">my</span> <span style="color:#080;">$i</span><span style="color:#000;">;</span><br />
<span style="color:#000;">foreach</span> <span style="color:#000;">my</span> <span style="color:#080;">$dato</span><span style="color:#000;">(</span><span style="color:#f70;">@datos</span><span style="color:#000;">)</span><span style="color:#000;">{</span><br />
<span style="color:#000;">if</span> <span style="color:#000;">(</span><span style="color:#080;">$dato</span><span style="color:#000;">=~</span><span style="color:#00a;">/</span><span style="color:#00a;"><span style="color:#800;">\.</span>com<span style="color:#800;">\/</span><span style="color:#800;">\t</span></span><span style="color:#00a;">/</span><span style="color:#000;">)</span><span style="color:#000;">{</span><br />
<span style="color:#000;">my</span> <span style="color:#f70;">@posicion</span><span style="color:#000;">=</span><span style="color:#001;">split</span><span style="color:#000;">(</span><span style="color:#00a;">"</span><span style="color:#00a;"><span style="color:#800;">\t</span></span><span style="color:#00a;">"</span><span style="color:#000;">,</span><span style="color:#080;">$dato</span><span style="color:#000;">)</span><span style="color:#000;">;</span><br />
<span style="color:#f70;">$posicion</span><span style="color:#000;">[</span><span style="color:#f0f;">0</span><span style="color:#000;">]</span><span style="color:#000;">=~</span><span style="color:#00a;">s/</span><span style="color:#00a;">x</span><span style="color:#00a;">/</span><span style="color:#00a;">/</span><span style="color:#000;">;</span><br />
<span style="color:#000;">my</span> <span style="color:#000;">(</span><span style="color:#080;">$x</span><span style="color:#000;">,</span><span style="color:#080;">$y</span><span style="color:#000;">)</span><span style="color:#000;">=</span><span style="color:#001;">split</span><span style="color:#000;">(</span><span style="color:#00a;">"</span><span style="color:#00a;">y</span><span style="color:#00a;">"</span><span style="color:#000;">,</span><span style="color:#f70;">$posicion</span><span style="color:#000;">[</span><span style="color:#f0f;">0</span><span style="color:#000;">]</span><span style="color:#000;">)</span><span style="color:#000;">;</span><br />
<span style="color:#080;">$i</span><span style="color:#000;">++</span><span style="color:#000;">;</span><br />
<span style="color:#300;">print</span> <span style="color:#00a;">'</span><span style="color:#00a;">&lt;img src="/i/pelotilla.png" </span><br />
<span style="color:#00a;"> style="z-index:</span><span style="color:#00a;">'</span><span style="color:#000;">.</span><span style="color:#080;">$i</span><span style="color:#000;">.</span><span style="color:#00a;">'</span><span style="color:#00a;">;position:absolute;</span><br />
<span style="color:#00a;"> left:</span><span style="color:#00a;">'</span><span style="color:#000;">.</span><span style="color:#080;">$x</span><span style="color:#000;">.</span><span style="color:#00a;">'</span><span style="color:#00a;">px;top:</span><span style="color:#00a;">'</span><span style="color:#000;">.</span><span style="color:#080;">$y</span><span style="color:#000;">.</span><span style="color:#00a;">'</span><span style="color:#00a;">px;</span><br />
<span style="color:#00a;"> height:16px;width:16px;"&gt;</span><span style="color:#00a;">'</span><span style="color:#000;">.</span><span style="color:#00a;">"</span><span style="color:#00a;"><span style="color:#800;">\n</span></span><span style="color:#00a;">"</span><span style="color:#000;">;</span><br />
<span style="color:#000;">}</span><br />
<span style="color:#000;">}</span><br />
</code></p>
<p>This script writes out an IMG tag for every log line that asked for the main page (ends in .com). This way, we would only need to paste this html code into a copy of the original page. We also will need a small image to mark the spots. I used a 16&#215;16px</p>
<h2>The click map</h2>
<p>The former script prints out some html code like this one:</p>
<p><code><br />
...<br />
&lt;img src="/i/pelotilla.png"<br />
style="z-index:51;position:absolute;<br />
left:605px;top:773px;<br />
height:16px;width:16px;"&gt;<br />
&lt;img src="/i/pelotilla.png"<br />
style="z-index:52;position:absolute;<br />
left:778px;top:792px;<br />
height:16px;width:16px;"&gt;<br />
&lt;img src="/i/pelotilla.png"<br />
style="z-index:53;position:absolute;<br />
left:799px;top:810px;<br />
height:16px;width:16px;"&gt;<br />
...<br />
</code></p>
<p>Pasting the code into the page copy, this is what we get:</p>
<p><img src="/uploads/captura.jpg" alt="Click map" /></p>
<p>I used the system posted in <a href="http://webfx.eae.net/dhtml/pngbehavior/pngbehavior.html" onclick="pageTracker._trackPageview('/outgoing/webfx.eae.net/dhtml/pngbehavior/pngbehavior.html?referer=');">webfx </a> to use transparent PNGs in internet explorer. We wouldn&#8217;t need it if we had done our research using Opera or Firefox browsers.<br />
Each dot in the above image  refers to a single click in our web site. Studying it we would be able to identify areas where the users are having trouble to find the links, and where they think should be a link but isn&#8217;t. In a real world analysis it would be better to have some more dots to work on.</p>
<h2>Shortcomings</h2>
<li>The system&#8217;s not able to support resolution changes. We would need some more javascript to refer every click to a known point of the screen. And it would be pretty difficult, if not impossible, to use it in liquid layouts.</li>
<li>In flash movies, we are going to have some trouble because the onclick event is not supported. As you can see in the screen capture above, there is not a single click detected in the top area, where the flash menu is.</li>
<p>Using the same technique, we could be able to store every mouse position in some milliseconds interval to replay our users movements, and there are many other possibilities.</p>
<h2>Acknowledgements</h2>
<p>I got the inspiration for this post from the <a href="http://blog.outer-court.com/click/" onclick="pageTracker._trackPageview('/outgoing/blog.outer-court.com/click/?referer=');">&#8220;Click Survey&#8221;</a> that lets you click randomly in any position of a picture.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.corunet.com/zero-budget-eye-tracking-clickmaps-part-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Zero budget eye tracking. Clickmaps.</title>
		<link>http://blog.corunet.com/zero-budget-eye-tracking-clickmaps/</link>
		<comments>http://blog.corunet.com/zero-budget-eye-tracking-clickmaps/#comments</comments>
		<pubDate>Fri, 28 Jul 2006 12:50:18 +0000</pubDate>
		<dc:creator>David Pardo</dc:creator>
				<category><![CDATA[English]]></category>
		<category><![CDATA[Usability]]></category>

		<guid isPermaLink="false">http://blog.corunet.com/english/zero-budget-eye-tracking-clickmaps</guid>
		<description><![CDATA[Eye tracking is quite expensive. The hardware and software have astounding capabilities, but sometimes it&#8217;s hard to justify the expenditure. But there is a technique that can imitate it until certain point. You can find where your users are clicking using just a bit of javascript and server side programming. Find out how&#8230;
NOTE: This post [...]]]></description>
			<content:encoded><![CDATA[<p>Eye tracking is quite expensive. The hardware and software have astounding capabilities, but sometimes it&#8217;s hard to justify the expenditure. But there is a technique that can imitate it until certain point. You can find where your users are clicking using just a bit of javascript and server side programming. Find out how&#8230;<span id="more-8"></span><br />
NOTE: This post has been improved at <a href="http://blog.corunet.com/english/the-definitive-heatmap">The definitive heatmap</a></p>
<p class="fotocentro"><img src="/uploads/566532_eye.jpg" alt="Ojo de usuario" /></p>
<p>We need to follow three simple steps:</p>
<ol>
<li>Create a function that follows the mouse movement and calls an external script in the server for every click</li>
<li>Call the server using an asynchronous method like HttpRequest</li>
<li>Use a logging program to write a log in the server</li>
</ol>
<h2>Capturing the mouse position</h2>
<p>The first thing to do is to find out the mouse position. The best way to approach this is to link a javascript file from the end of your html document. The file should be something like this:</p>
<p><code>/*detect browser*/<br />
var IE = document.all?true:false<br />
if (!IE) document.captureEvents(Event.MOUSEMOVE)<br />
var tempX = 0<br />
var tempY = 0<br />
/*call the function*/<br />
document.onclick = getMouseXY;<br />
function getMouseXY(e) {<br />
  if (IE) {<br />
    tempX = event.clientX + document.body.scrollLeft<br />
    tempY = event.clientY + document.body.scrollTop<br />
  } else {<br />
    tempX = e.pageX<br />
    tempY = e.pageY<br />
  }<br />
  var url="/perl/guardacoordenadas.pl?x="+tempX+"&#038;y="+tempY;<br />
  guardar(url);<br />
  return true<br />
}<br />
if( document.getElementsByTagName ){<br />
 var links = document.getElementsByTagName( 'a' );<br />
 for( var i=0; i < links.length; i++ ){<br />
  links[i].onclick = function(){<br />
   return getMouseXY(this);<br />
  }<br />
 }<br />
}<br />
</code></p>
<p> This script filters the browser, inits the click controller and calls an external function <em>guardar </em> that will use the HttpRequest object to call the server.</p>
<h2>HttpRequest</h2>
<p>The HttpRequest object allows us to call the server outside the normal navigation flow. What we want now is to store the mouse position every time the user clicks in the page. We are going to use the following javascript code:</p>
<p><code>var xmlDoc = null ;<br />
if (typeof window.ActiveXObject != 'undefined' ) {<br />
 xmlDoc = new ActiveXObject("Microsoft.XMLHTTP");<br />
}else {<br />
 xmlDoc = new XMLHttpRequest();<br />
}<br />
function guardar(url){<br />
 xmlDoc.open( "GET", url, true );<br />
 xmlDoc.send( null );<br />
}</code></p>
<p>Just like before, we have to use a different approach for internet explorer and all other browsers, since explorer uses its own implementation, to create an asynchronous call object (xmlDoc).  Then, we use this object to call the server in the background using a GET method to send the mouse position.</p>
<h2>The server side</h2>
<p>From the server side, there's no need to send anything to the user. We only need to store the variables sent by the query string in a file (or database), mainly the coordinates, the referer and the IP address. The following perl/CGI code should work as a proof of concept</p>
<p><code>#!/usr/bin/perl<br />
use CGI;<br />
use strict;<br />
my $q=new CGI; #create a new GCI instance<br />
print $q->header(); #print headers<br />
open (LOG,">>log.txt") ||die; #open log file to append<br />
print LOG $q->Vars; #and prints all the data<br />
print LOG "\t";<br />
print LOG $ENV{'HTTP_REFERER'};<br />
print LOG "\t";<br />
print LOG $ENV{'REMOTE_ADDR'};<br />
print LOG "\n";<br />
close (LOG);</code></p>
<p>This script willl write a line for every click in our page. </p>
<h2>To do</h2>
<p>Now it's time to analyze the data we got. We would need to filter the log and make a clickmap. The English version of the second part is still unavailable, but  <a href="/usabilidad/eye-tracking-para-pobres-parte-2">you can read the Spanish version if you want</a>. I'll try to translate it as soon as possible</p>
<p>There are many other things that can be done with this approach. You can, for example, write a log with every mouse movement, to keep track of the users' navigation. If you find some creative uses for this, please comment.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.corunet.com/zero-budget-eye-tracking-clickmaps/feed/</wfw:commentRss>
		<slash:comments>12</slash:comments>
		</item>
	</channel>
</rss>
