<?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; English</title>
	<atom:link href="http://blog.corunet.com/category/english/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>Twitter alerts: using twitter streaming API for fun and profit</title>
		<link>http://blog.corunet.com/twitter-alerts-using-twitter-streaming-api/</link>
		<comments>http://blog.corunet.com/twitter-alerts-using-twitter-streaming-api/#comments</comments>
		<pubDate>Fri, 23 Oct 2009 12:25:31 +0000</pubDate>
		<dc:creator>David Pardo</dc:creator>
				<category><![CDATA[English]]></category>
		<category><![CDATA[Programación]]></category>
		<category><![CDATA[Twitter]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[jquery]]></category>
		<category><![CDATA[json]]></category>
		<category><![CDATA[php]]></category>

		<guid isPermaLink="false">http://blog.corunet.com/?p=39</guid>
		<description><![CDATA[
Twitter is a wonderful service, but, until now, you have to subscribe to some websites to be alerted when a selected word (maybe your trademark) is tweeted. We&#8217;ll try to develop a service that filters the tweeter api, stores the interesting ones in our database, and show them in the browser in real time.
If you [...]]]></description>
			<content:encoded><![CDATA[<h2><img class="alignleft size-thumbnail wp-image-74" title="twitter_alerts" src="http://blog.corunet.com/wp-content/uploads/2009/10/twitter_alerts-150x150.png" alt="twitter_alerts" width="150" height="150" /></h2>
<p>Twitter is a wonderful service, but, until now, you have to subscribe to some websites to be alerted when a selected word (maybe your trademark) is tweeted. We&#8217;ll try to develop a service that filters the tweeter api, stores the interesting ones in our database, and show them in the browser in real time.</p>
<p>If you want to try it, <a href="twitter-alerts-using-twitter-streaming-api/#video">watch it in action</a>, grab<a href="/twitter-alerts-using-twitter-streaming-api/#thecode"> the code</a> or read on&#8230;</p>
<p><span id="more-39"></span></p>
<h2>What?</h2>
<p>We&#8217;ll take twitter real time results for a given word (or words) and visualize them in a browser window, like monitter.com, but on our own servers and a bit more automatic. This is going to be useful to get our own alerts and work with them.</p>
<h2>How?</h2>
<p>We are going to use the twitter stream API to get the comments containing a given word. Since the results are given in Json format, we&#8217;ll need to filter the data, take the interesting bits and store them. From the other side, we&#8217;re going to write a bit of javascript to read the data from the server into a web page using ajax. We could use other technologies, like comet, to push the data to the browser, but, while it would be a much cleaner implementation, I think I&#8217;m not ready to write about that yet (check this space <img src='http://blog.corunet.com/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> ). We need to decouple the reading of the stream from twitter and the serving to our clients because we can only have a single active  instance of the twitter streaming api at a given time, and we&#8217;re going to leave the listening proccess on for a long time using a daemon/service approach. On top of that, we want to store the tweets in a database for later perusal and data mining.</p>
<h2>The tools</h2>
<p>We are trying to make a useful system.  To do this, we&#8217;ll need some tools:</p>
<ul>
<li>HTTP server:I&#8217;m currently using <a href="http://apache.org" target="_blank" onclick="pageTracker._trackPageview('/outgoing/apache.org?referer=');">Apache</a>, but any one would be OK</li>
<li>Server side programming language: This time, PHP. While probably not as elegant or fast as Python or as cool as Ruby, gets the work done and it&#8217;s available in almost all web hosting plans. And the documentation is pretty extensive.</li>
<li>A database to store all the info</li>
<li>Client side programming: We are going to use HTML, javascript and the jQuery javascript library to simplify AJA(X) programming and for the effects.</li>
<li>A twitter acount: Yours own is OK but maybe you want to create a new one for this kind of tasks. Write down your user and password, we are going to need them soon.</li>
<li>The twitter API. Twitter people are so kind they have developed a restful API free for everyone to use. And it&#8217;s sub-zero cool.</li>
</ul>
<h2>The Twitter streaming API</h2>
<p>Twitter has published a Streaming API that&#8217;s described as “The Twitter Streaming API allows near-realtime access to various subsets of Twitter public statuses”. In fact, this is jus what we need. You can read all the documentation at <a href="https://twitterapi.pbworks.com/Streaming-API-Documentation" onclick="pageTracker._trackPageview('/outgoing/twitterapi.pbworks.com/Streaming-API-Documentation?referer=');">https://twitterapi.pbworks.com/Streaming-API-Documentation</a>, but I&#8217;ll try to take the interesting parts for this project so you don&#8217;t need to yet.</p>
<p>We are going to use just one method (status/filter) to get results including one or several words. This method can return a stream of data in xml or json formats, has to be called using POST and can get some parameters. You can use it from the command line if you have access to some kind of unix in the following way:</p>
<p><code>curl -d 'track=google' http://stream.twitter.com/1/statuses/filter.json -uuser:password</code></p>
<p>Where user and password are your twitter credentials.</p>
<p>It should return something like:</p>
<p class="toggle_code"><a href="/uploads/code/response.json" onclick="jQuery('#code_0').toggle('slow');return false">response.json</a></p>
<div  style="display:none" id="code_0">
<pre class="brush: js">{&quot;text&quot;:&quot;RT @gillesguillemin: Google FINALLY Releases AS3 Player for YouTube http://bit.ly/yKgaz&quot;,
&quot;favorited&quot;:false,&quot;in_reply_to_user_id&quot;:null,&quot;in_reply_to_screen_name&quot;:null,&quot;source&quot;:&quot;web&quot;,&quot;geo&quot;:null,
&quot;in_reply_to_status_id&quot;:null,&quot;user&quot;:{&quot;friends_count&quot;:52,&quot;screen_name&quot;:&quot;imrahil&quot;,&quot;verified&quot;:false,
&quot;profile_background_color&quot;:&quot;9ae4e8&quot;,&quot;favourites_count&quot;:8,&quot;notifications&quot;:null,&quot;profile_text_color&quot;:&quot;000000&quot;,
&quot;description&quot;:&quot;&quot;,&quot;location&quot;:&quot;Poland&quot;,&quot;time_zone&quot;:&quot;Warsaw&quot;,&quot;profile_link_color&quot;:&quot;0000ff&quot;,&quot;following&quot;:null,
&quot;profile_background_image_url&quot;:&quot;http://s.twimg.com/a/1255558003/images/themes/theme1/bg.png&quot;,
&quot;profile_sidebar_fill_color&quot;:&quot;e0ff92&quot;,&quot;protected&quot;:false,&quot;url&quot;:&quot;http://flex.imrahil.com&quot;,
&quot;geo_enabled&quot;:false,&quot;profile_background_tile&quot;:false,&quot;name&quot;:&quot;Jarek&quot;,&quot;profile_sidebar_border_color&quot;:&quot;87bc44&quot;,
&quot;profile_image_url&quot;:&quot;http://a3.twimg.com/profile_images/76229939/avatar5_normal.jpg&quot;,&quot;id&quot;:5393872,
&quot;statuses_count&quot;:48,&quot;utc_offset&quot;:3600,&quot;created_at&quot;:&quot;Sun Apr 22 07:02:54 +0000 2007&quot;,&quot;followers_count&quot;:31},
&quot;id&quot;:4887052323,&quot;truncated&quot;:false,&quot;created_at&quot;:&quot;Thu Oct 15 12:10:57 +0000 2009&quot;}

{&quot;text&quot;:&quot;Google Wave\u2019s Little Secret: It Already Works On The iPhone&quot;,&quot;favorited&quot;:false,
&quot;in_reply_to_user_id&quot;:null,&quot;in_reply_to_screen_name&quot;:null,
&quot;source&quot;:&quot;&lt;a href=\&quot;http://www.atebits.com/\&quot; rel=\&quot;nofollow\&quot;&gt;Tweetie&lt;/a&gt;&quot;,&quot;geo&quot;:null,
&quot;in_reply_to_status_id&quot;:null,&quot;user&quot;:{&quot;friends_count&quot;:52,&quot;screen_name&quot;:&quot;Jwizzman&quot;,
&quot;verified&quot;:false,&quot;profile_background_color&quot;:&quot;000000&quot;,&quot;favourites_count&quot;:1,&quot;notifications&quot;:null,
&quot;profile_text_color&quot;:&quot;663B12&quot;,&quot;description&quot;:&quot;&quot;,&quot;location&quot;:&quot;Amsterdam, The Netherlands&quot;,
&quot;time_zone&quot;:&quot;Amsterdam&quot;,&quot;profile_link_color&quot;:&quot;1F98C7&quot;,&quot;following&quot;:null,
&quot;profile_background_image_url&quot;:&quot;http://a3.twimg.com/profile_background_images/20711299/background.png&quot;,
&quot;profile_sidebar_fill_color&quot;:&quot;DAECF4&quot;,&quot;protected&quot;:false,&quot;url&quot;:&quot;http://www.julianprofas.com&quot;,
&quot;geo_enabled&quot;:false,&quot;profile_background_tile&quot;:false,&quot;name&quot;:&quot;Julian Profas \uf8ff&quot;,
&quot;profile_sidebar_border_color&quot;:&quot;C6E2EE&quot;,
&quot;profile_image_url&quot;:&quot;http://a3.twimg.com/profile_images/458268969/foto_avatar_2_normal.png&quot;,
&quot;id&quot;:15727906,&quot;statuses_count&quot;:645,&quot;utc_offset&quot;:3600,&quot;created_at&quot;:&quot;Mon Aug 04 21:08:08 +0000 2008&quot;,
&quot;followers_count&quot;:79},&quot;id&quot;:4887052409,&quot;truncated&quot;:false,&quot;created_at&quot;:&quot;Thu Oct 15 12:10:58 +0000 2009&quot;}
</pre>
</div>
<p>Until you exit it (with CTRL+C) . This is a Json stream and can be read and parsed by several means. In fact, it&#8217;s eval-uable javascript code that we could read from the browser. But right now, we&#8217;re going to use a server-side language to read it and work with the interesting parts.</p>
<h2>Reading Twitter stream</h2>
<p>As I told you in the tools section, we are going to use PHP as our server side language. For the first part, the reading of the twitter stream, we don&#8217;t even need a web server, since we can run it from the command line. And if we run it from the command line, we can convert it to a kind of daemon/service and leave it on for a long time. But first, some code:</p>
<p class="toggle_code"><a href="/uploads/code/basicstreaming.php" onclick="jQuery('#code_1').toggle('slow');return false">basicstreaming.php</a></p>
<div  style="display:none" id="code_1">
<pre class="brush: php">&lt;?php
	$opts = array(
		&#039;http&#039;=&gt;array(
			&#039;method&#039;	=&gt;	&quot;POST&quot;,
			&#039;content&#039;	=&gt;	&#039;track=flickr&#039;,
		)
	);
	$context = stream_context_create($opts);
	$instream = fopen(&#039;http://USERNAME:PASSWORD@stream.twitter.com/1/statuses/filter.json&#039;,&#039;r&#039; ,false, $context);
	while(! feof($instream)) {
		if(! ($line = stream_get_line($instream, 20000, &quot;\n&quot;))) {
			continue;
		}else{
			//print_r(json_decode($line));
			$tweet = json_decode($line);
			print $tweet-&gt;{&#039;text&#039;}.&quot;\n&quot;;
			flush();
		}
	}
?&gt;
</pre>
</div>
<p>This is (almost) the simplest code that delivers what we want. A php formatted stream of tweets that include our marked word. If you run it from your command line, it&#8217;ll show something like:</p>
<p class="toggle_code"><a href="/uploads/code/sample_tweets.php" onclick="jQuery('#code_2').toggle('slow');return false">sample_tweets.php</a></p>
<div  style="display:none" id="code_2">
<pre class="brush: php">stdClass Object
(
    [favorited] =&gt;
    [in_reply_to_user_id] =&gt;
    [text] =&gt; Multiplexing and switching - sci.electronics.design | Google Groups: Multiplexing and switc.. http://bit.ly/m4nIq http://bit.ly/o0gO4
    [created_at] =&gt; Tue Oct 20 09:32:08 +0000 2009
    [geo] =&gt;
    [in_reply_to_screen_name] =&gt;
    [source] =&gt; &lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;
    [in_reply_to_status_id] =&gt;
    [user] =&gt; stdClass Object
        (
            [notifications] =&gt;
            [profile_text_color] =&gt; 1c0a1c
            [url] =&gt; http://www.managedserviceproviders.biz
            [time_zone] =&gt; Arizona
            [created_at] =&gt; Thu Aug 20 20:54:47 +0000 2009
            [profile_link_color] =&gt; 29ccf5
            [profile_background_image_url] =&gt; http://s.twimg.com/a/1255997062/images/themes/theme9/bg.gif
            [description] =&gt; Telecom Professional who enjoys helping businesses set up and manage their communications including voice, data and social media.
            [profile_sidebar_fill_color] =&gt; 361da3
            [profile_background_tile] =&gt;
            [following] =&gt;
            [profile_sidebar_border_color] =&gt; 0d0f12
            [geo_enabled] =&gt;
            [statuses_count] =&gt; 737
            [followers_count] =&gt; 237
            [protected] =&gt;
            [profile_image_url] =&gt; http://a3.twimg.com/profile_images/424262573/istock_000008181854xsmall-150x150_1__normal.jpg
            [location] =&gt; Phoenix, AZ
            [friends_count] =&gt; 236
            [name] =&gt; Jennifer Springston
            [verified] =&gt;
            [profile_background_color] =&gt; 1A1B1F
            [screen_name] =&gt; inTELEgentMSP
            [id] =&gt; 67421923
            [utc_offset] =&gt; -25200
            [favourites_count] =&gt; 0
        )

    [truncated] =&gt;
    [id] =&gt; 5015099767
)
stdClass Object
(
    [text] =&gt; 逛了一下午的教务处、鼓浪听涛、豆瓣和Google，寻找厦大比较能说的老师。结果发现大部分老师都不在漳州校区开课。
    [created_at] =&gt; Tue Oct 20 09:32:12 +0000 2009
    [in_reply_to_status_id] =&gt;
    [source] =&gt; &lt;a href=&quot;http://www.hellotxt.com/&quot; rel=&quot;nofollow&quot;&gt;HelloTxt&lt;/a&gt;
    [truncated] =&gt;
    [favorited] =&gt;
    [in_reply_to_user_id] =&gt;
    [user] =&gt; stdClass Object
        (
            [profile_background_image_url] =&gt; http://s.twimg.com/a/1255724203/images/themes/theme3/bg.gif
            [url] =&gt; http://www.jiakon.com
            [created_at] =&gt; Sat Jun 02 07:59:20 +0000 2007
            [profile_sidebar_fill_color] =&gt; E3E2DE
            [profile_background_tile] =&gt;
            [profile_sidebar_border_color] =&gt; D3D2CF
            [description] =&gt; Education,Student,Mathematics,Edubuntu
            [geo_enabled] =&gt;
            [statuses_count] =&gt; 2379
            [followers_count] =&gt; 186
            [friends_count] =&gt; 168
            [following] =&gt;
            [verified] =&gt;
            [profile_background_color] =&gt; EDECE9
            [favourites_count] =&gt; 8
            [protected] =&gt;
            [profile_image_url] =&gt; http://a1.twimg.com/profile_images/26783462/IMG0017A_normal.jpg
            [location] =&gt; Xiamen China
            [notifications] =&gt;
            [profile_text_color] =&gt; 634047
            [name] =&gt;  Xu Tom
            [time_zone] =&gt; Beijing
            [screen_name] =&gt; jiakon
            [id] =&gt; 6518512
            [utc_offset] =&gt; 28800
            [profile_link_color] =&gt; 088253
        )

    [id] =&gt; 5015100624
    [geo] =&gt;
    [in_reply_to_screen_name] =&gt;
)
stdClass Object
(
    [favorited] =&gt;
    [in_reply_to_user_id] =&gt;
    [text] =&gt; Google Wave&#039;s Best Use Cases - Wave - Lifehacker: http://bit.ly/1nCqlM
    [created_at] =&gt; Tue Oct 20 09:32:12 +0000 2009
    [geo] =&gt;
    [in_reply_to_screen_name] =&gt;
    [source] =&gt; &lt;a href=&quot;http://apiwiki.twitter.com/&quot; rel=&quot;nofollow&quot;&gt;API&lt;/a&gt;
    [in_reply_to_status_id] =&gt;
    [user] =&gt; stdClass Object
        (
            [notifications] =&gt;
            [profile_text_color] =&gt; 3C3940
            [url] =&gt;
            [time_zone] =&gt; Alaska
            [created_at] =&gt; Sun Apr 19 05:22:52 +0000 2009
            [profile_link_color] =&gt; 0099B9
            [profile_background_image_url] =&gt; http://s.twimg.com/a/1255980505/images/themes/theme4/bg.gif
            [description] =&gt; Enrich you websurfing experience! Discover interesting and amazing websites now.
            [profile_sidebar_fill_color] =&gt; 95E8EC
            [profile_background_tile] =&gt;
            [following] =&gt;
            [profile_sidebar_border_color] =&gt; 5ED4DC
            [geo_enabled] =&gt;
            [statuses_count] =&gt; 4233
            [followers_count] =&gt; 5997
            [protected] =&gt;
            [profile_image_url] =&gt; http://a1.twimg.com/profile_images/146406726/webdiscover_normal.jpg
            [location] =&gt; Webverse
            [friends_count] =&gt; 5735
            [name] =&gt; Web Explorer
            [verified] =&gt;
            [profile_background_color] =&gt; 0099B9
            [screen_name] =&gt; Web2Discover
            [id] =&gt; 33139096
            [utc_offset] =&gt; -32400
            [favourites_count] =&gt; 0
        )

    [truncated] =&gt;
    [id] =&gt; 5015100766
)
</pre>
</div>
<p>Let&#8217;s look at the code:</p>
<p>We create an $opts array (in fact an array of arrays) that contain the parameters. In this particular case, we&#8217;re using two, the POST method and the search line (track=google). Then, we can treat the twitter stream as a file, using <em>stream_context_create</em> and <em>fopen</em> and just start reading lines. Each line is going to be a JSON encoded tweet, similar to what we&#8217;ve seen when we called the API from command line. Since we want to use the contents as easily as possible, we&#8217;ll need the <em>json_decode</em> function to parse it into PHP objects, print them and call <em>flush</em> just in case we&#8217;re calling the script from a browser.</p>
<h2>Storing the results</h2>
<p>The best way to store the results for later perusal is a database. I&#8217;m using MySQL but any other database should be OK. To be able to store the data, we need to create a database with a single table.</p>
<p>We are only going to store the following data:</p>
<ul>
<li>Text: This is the twetter status. 140 chars max.</li>
<li>User screen name: The screen name of the poster. This is needed to create the link to twitter</li>
<li>Id: A unique id for the tweet. It&#8217;s a sequential number, so, we can order the tweets acording to this, and use it along with the user screen name to built a link back to twitter, and use it as our primary key.</li>
<li>Followers count: The number of people that are going to receive the tweet in their inboxes. We are using it to style the real-time viewer. Since my primal intention is to watch a trademark, I care about the number of people that are watching the messages.</li>
<li>The time of the tweet: basically for filtering purposes. We are going to store our server time to avoid lengthy conversions.</li>
</ul>
<p>We could store several other fields, and a complete solution should probably take into account that you can have some different tweet types, but for the time, these four fields should suffice.</p>
<p>To create the table, we can run the following SQL script from the server:</p>
<p class="toggle_code"><a href="/uploads/code/createtable.sql" onclick="jQuery('#code_3').toggle('slow');return false">createtable.sql</a></p>
<div  style="display:none" id="code_3">
<pre class="brush: sql">CREATE DATABASE twitter_alerts;

CREATE TABLE `twitter_alerts`.`tweets` (
`id` BIGINT UNSIGNED NOT NULL ,
`text` VARCHAR( 150 ) NOT NULL ,
`screen_name` VARCHAR( 255 ) NOT NULL ,
`followers_count` INT NOT NULL ,
`created_at` DATETIME NOT NULL ,
PRIMARY KEY ( `id` )
) ENGINE = InnoDB;

GRANT ALL PRIVILEGES ON twitter_alerts.tweets TO &#039;twitter_alerts&#039;@&#039;localhost&#039; IDENTIFIED BY &#039;somepasword&#039;;
</pre>
</div>
<p>After that, we will have a single table database waiting for us to fill it with tweets&#8230; Let&#8217;s go:</p>
<p class="toggle_code"><a href="/uploads/code/storing_tweets_in_the_database.php" onclick="jQuery('#code_4').toggle('slow');return false">storing_tweets_in_the_database.php</a></p>
<div  style="display:none" id="code_4">
<pre class="brush: php">&lt;?php

	$opts = array(
		&#039;http&#039;=&gt;array(
			&#039;method&#039;	=&gt;	&quot;POST&quot;,
			&#039;content&#039;	=&gt;	&#039;track=twitter&#039;,
		)
	);
	//We&#039;re going to store the data in the database, so, let&#039;s open a connection:
	$db = mysql_connect(&#039;localhost&#039;, &#039;twitter_alerts&#039;, &#039;somepasword&#039;);
	mysql_select_db(&#039;twitter_alerts&#039;, $db);

	$context = stream_context_create($opts);
	while (1){
		$instream = fopen(&#039;http://USERNAME:PASSWORD@stream.twitter.com/1/statuses/filter.json&#039;,&#039;r&#039; ,false, $context);
		while(! feof($instream)) {
			if(! ($line = stream_get_line($instream, 20000, &quot;\n&quot;))) {
				continue;
			}else{
				$tweet = json_decode($line);
				//Clean the inputs before storing
				$id = mysql_real_escape_string($tweet-&gt;{&#039;id&#039;});
				$text = mysql_real_escape_string($tweet-&gt;{&#039;text&#039;});
				$screen_name = mysql_real_escape_string($tweet-&gt;{&#039;user&#039;}-&gt;{&#039;screen_name&#039;});
				$followers_count = mysql_real_escape_string($tweet-&gt;{&#039;user&#039;}-&gt;{&#039;followers_count&#039;});
				//We store the new post in the database, to be able to read it later
				$ok = mysql_query(&quot;INSERT INTO tweets (id ,text ,screen_name ,followers_count, created_at) VALUES (&#039;$id&#039;, &#039;$text&#039;, &#039;$screen_name&#039;, &#039;$followers_count&#039;, NOW())&quot;);
				if (!$ok) {echo &quot;Mysql Error: &quot;.mysql_error();}
				flush();
			}
		}
	}
?&gt;
</pre>
</div>
<p>It&#8217;s as ugly as sin but it works. If you run it from the command line, it should start storing tweets in your database and keep on until you stop it. So, our database is starting to fill with tweets concerning our desired word. Now we need to be able to navigate them&#8230;</p>
<h2>Creating the code from the server side</h2>
<p>Now we need to publish the tweets in our browser. To do that, we need a small PHP script that returns the tweets when called. If we call it with a parameter <em>start</em> it&#8217;ll return all the tweets with an id bigger than that. Otherwise, it&#8217;ll return the last ten tweets stored in our database. To do that, we will use two different queries, the first one to return the last ten results, and the second one to return all results since the given <em>id</em>. We use subqueries (SELECT from SELECT) to get the results in our wished order.</p>
<p class="toggle_code"><a href="/uploads/code/server.php" onclick="jQuery('#code_5').toggle('slow');return false">server.php</a></p>
<div  style="display:none" id="code_5">
<pre class="brush: php">&lt;?php
	//We are going to need a database connection:
	$db = mysql_connect(&#039;localhost&#039;, &#039;twitter_alerts&#039;, &#039;somepasword&#039;);
	mysql_select_db(&#039;twitter_alerts&#039;, $db);
	//Now, two possibilities: if we don&#039;t have a start parameter, we print the last ten tweets.
	//Otherwise, we print all the tweets with IDs bigger than start, if any
	$start = mysql_real_escape_string($_GET[&#039;start&#039;]);
	if(! $start){
		$query = &quot;SELECT * FROM (SELECT * FROM tweets ORDER BY id DESC LIMIT 0,10) AS last_ten ORDER BY id ASC&quot;;
	}else{
		$query = &quot;SELECT * FROM (SELECT * FROM tweets WHERE id&gt;&quot;.$start.&quot; ORDER BY id DESC LIMIT 0,10) AS new_tweets ORDER BY id ASC&quot;;
	}

	$result = mysql_query($query);
	$data = array(); //Initializing the results array

	while ($row = mysql_fetch_assoc($result)){
		array_push($data, $row);
	}
	$json = json_encode($data);
	print $json;
?&gt;</pre>
</div>
<p>We are going to poll this code every ten seconds and refresh the tweets list to show the most recent ones, using javascript, and the output format will be JSON.</p>
<h2>Writing a front-end</h2>
<p>Since, as <a href="http://wall.org" target="_blank" onclick="pageTracker._trackPageview('/outgoing/wall.org?referer=');">Larry Wall</a> said, one of the cardinal virtues of a programmer is lazyness, we are going to use jQuery  to construct the interface and the business logic. And we&#8217;re not even serving it, but linking from the Google CDN, as <a href="http://encosia.com/2009/10/11/do-you-know-about-this-undocumented-google-cdn-feature/" target="_blank" onclick="pageTracker._trackPageview('/outgoing/encosia.com/2009/10/11/do-you-know-about-this-undocumented-google-cdn-feature/?referer=');">Dave Ward posted</a> in his wondeful blog.</p>
<p>So, let&#8217;s start with the HTML:</p>
<p class="toggle_code"><a href="/uploads/code/barebones.html" onclick="jQuery('#code_6').toggle('slow');return false">barebones.html</a></p>
<div  style="display:none" id="code_6">
<pre class="brush: html">&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&gt;
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; xml:lang=&quot;gl&quot; lang=&quot;gl&quot;&gt;
&lt;head&gt;
		&lt;title&gt;Twitter alert viewer&lt;/title&gt;
		&lt;meta http-equiv=&quot;Content-type&quot; content=&quot;text/html;charset=UTF-8&quot; /&gt;
		&lt;script src=&quot;http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js&quot;&gt;&lt;/script&gt;
		&lt;style type=&quot;text/css&quot;&gt;
				body{
						background-color:#ccccff;
						font-family: georgia, serif;
				}
				.hidden{
						height:0px;
						overflow:hidden;
				}
				#tweets{
						margin:0 auto;
						width:400px;
				}
				#tweets div{
						border:1px solid silver;
						margin:5px;
						padding:5px;
						background-color:#fff;
				}
		&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
		&lt;div id=&quot;tweets&quot;&gt;
				&lt;h1&gt;Twitter alert viewer&lt;/h1&gt;
				&lt;a href=&quot;#&quot; onclick=&quot;clearTimeout(timeOut)&quot;&gt;Pause&lt;/a&gt;
				&lt;a href=&quot;#&quot; onclick=&quot;poll()&quot;&gt;Run&lt;/a&gt;
		&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
</div>
<p>Just some styles, one script link and a body containing one div, two links and a header. Simple, eh?</p>
<p>We need to add some javascript in the middle of the code to connect to the server:</p>
<p class="toggle_code"><a href="/uploads/code/logic.js" onclick="jQuery('#code_7').toggle('slow');return false">logic.js</a></p>
<div  style="display:none" id="code_7">
<pre class="brush: js">
var last = &#039;&#039;;
var timeOut;

function getTweets(id){
		$.getJSON(&quot;server.php?start=&quot;+id,
		function(data){
				$.each(data, function(count,item){
						addNew(item);
						last = item.id;
				});
		});
}

function addNew(item){
		if($(&#039;#tweets div.tweet&#039;).length&gt;9){ //If we have more than nine tweets
				$(&#039;#tweets div.tweet:first&#039;).toggle(300);//remove it form the screen
				$(&#039;#tweets div.tweet:first&#039;).removeClass(&#039;tweet&#039;);//and it&#039;s class
				$(&quot;#tweets div:hidden&quot;).remove(); //sweeps the already hidden elements
		}
		$(&#039;#tweets&#039;).append(renderTweet(item, &#039;hidden&#039;));
}

function renderTweet(item){
		importanceColor=getImportanceColor(item.followers_count);
		return &#039;&lt;div class=&quot;tweet&quot; id=&quot;&#039;+item.id+&#039;&quot;&gt;&#039;+
		&#039;&lt;strong&gt;&lt;a href=&quot;http://twitter.com/&#039;+item.screen_name+&#039;&quot; style=&quot;color:&#039;+importanceColor+&#039;&quot;&gt;&#039;+
		item.screen_name+&#039;&lt;/a&gt;&lt;/strong&gt;&lt;span class=&quot;text&quot;&gt;&#039;+
		item.text
		+&#039;&lt;/span&gt;&lt;span class=&quot;created_at&quot;&gt;&lt;br /&gt;&lt;a href=&quot;http://twitter.com/&#039;+
		item.screen_name+&#039;/status/&#039;+item.id+&#039;&quot;&gt;&#039;+
		item.created_at+&#039;&lt;/span&gt;&lt;/div&gt;&#039;;
}

function getImportanceColor(number){
		rgb = 255-Math.floor(16*(Math.log(number+1)+1)); //should return about 0 for 0 followers and 255 for 4million (Ashton Kutchner? Obama?)
		return &#039;rgb(&#039;+rgb+&#039;,0,0)&#039;;
}

function poll(){
		timeOut = setTimeout(&#039;poll()&#039;, 200);//It calls itself every 200ms
		getTweets(last);
}

$(document).ready(function() {
		poll();
});</pre>
</div>
<p>Let&#8217;s see&#8230; This is probably the most complex part of the article, so, I&#8217;ll try to go slow and explain every function:</p>
<h3>getTweets(id)</h3>
<p>This function calls the server using the getJSON jQuery method. Then, it takes each response line and calls addNew with it. If we call it with an <em>id</em> parameter, it&#8217;ll ask the server for all tweets with <em>id</em>s greater than that. Otherwise, it will grab the last ten tweets.</p>
<h3>addNew(item)</h3>
<p>It takes an item (a tweet) as input. It hides the first tweet, remove it&#8217;s &#8216;tweet&#8217; class, appends a new tweet at the bottom and shows it. It calls the renderTweet function to get the tweet in HTML format.</p>
<h3>renderTweet(item)</h3>
<p>Just one line to call getImportanceColor() and a return with the HTML code. It&#8217;s a bit long but that&#8217;s because we&#8217;re adding a couple of links to the tweet to be able to visit the original one.</p>
<h3>getImportanceColor(number)</h3>
<p>It takes a number of followers and returns a rgb color that will be between total black, for people without followers, and total red, for Ashton Kutchner. It uses logarithms to scale between the two extremes, because there are 6 orders of magnitude between the extremes. We will use it to paint (it) black the twitters with few followers and red the twitter stars.</p>
<h3>poll()</h3>
<p>This is the timeout function that calls itself every 200ms and gets the new tweets.</p>
<p>The last block just starts the polling as soon as the document is loaded.</p>
<h2>The Result</h2>
<p><a name="video" ></a><object type="application/x-shockwave-flash" width="425" height="344" data="http://www.youtube.com/v/96JEBQk8vig&fs=1&rel=0&hd=1&showinfo=0"><param name="movie" value="http://www.youtube.com/v/96JEBQk8vig&fs=1&rel=0&hd=1&showinfo=0"></param><param name="allowFullScreen" value="true"></param><param name="wmode" value="transparent" /></object></p>
<p>This is a small screen capture of my browser visiting the HTML/javascript page while running storing_tweets_in_the_database.php. It&#8217;s watching the word &#8216;twitter&#8217; and, as you can see, it&#8217;s running too fast for the human eye -at least mine -, but since we are keeping all the data in our database, it&#8217;s not lost forever  <img src='http://blog.corunet.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<h2>Limits</h2>
<p>Right now, because of the Twitter API limits, just one instance of the watching process can be run at once. Anyhow, you can write several words, separated by commas, and Twitter will return results for all of them.This code should not be used in production, since there are almost no security checks to avoid missuse. If you want to use it in a machine open to the public, you should check -twice- every input for missbehaviour.</p>
<h2>The code</h2>
<p><a name="thecode"></a></p>
<ol>
<li><a href="/uploads/twitter_watch/twitter_watch.zip">Download the code</a> and unzip it into a folder in your local webserver</li>
<li>Edit config.php to add your twitter login data and the words you want to watch</li>
<li>Create the database and the table with the SQL code above</li>
<li>Run watch.php and leave it running for as long as you wish.</li>
<li>Visit http://localhost/thefolderwhereyouunzippedthecode/ and watch the tweets coming.</li>
</ol>
<h2>Further work</h2>
<p>Obviously, this is just a sample. It can be made much better looking, and we could even analyze the tweets and tweet back a response to any questions concerning our keywords. The watch module should be daemonized or converted to a service to be left unatended. The HTML page could be able to filter between two dates and so on. Keep on watching. We&#8217;ll try to keep on posting this kind of contents.</p>
<h2>Shameless plug</h2>
<p>I&#8217;m part of <a href="http://coru.net/" onclick="pageTracker._trackPageview('/outgoing/coru.net/?referer=');">Corunet</a>, a web agency in Spain, that can deliver consistent good results in all kind of internet projects. You can visit our website <a href="http://coru.net/" onclick="pageTracker._trackPageview('/outgoing/coru.net/?referer=');">http://coru.net/</a> or contact me at david@corunet.com if you have any special needs <img src='http://blog.corunet.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>You can follow me on twitter as<a href="http://twitter.com/dei_biz/" onclick="pageTracker._trackPageview('/outgoing/twitter.com/dei_biz/?referer=');"> @dei_biz</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.corunet.com/twitter-alerts-using-twitter-streaming-api/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Automatic CSS: The stylizator</title>
		<link>http://blog.corunet.com/automatic-css-the-stylizator/</link>
		<comments>http://blog.corunet.com/automatic-css-the-stylizator/#comments</comments>
		<pubDate>Wed, 20 Jun 2007 20:15:15 +0000</pubDate>
		<dc:creator>David Pardo</dc:creator>
				<category><![CDATA[CSS and Javascript]]></category>
		<category><![CDATA[English]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[perl]]></category>

		<guid isPermaLink="false">http://blog.corunet.com/english/automatic-css-the-stylizator</guid>
		<description><![CDATA[Writing CSS is good fun, but analyzing an html document to find how the page is structured us, at the very least, tiring. If you have ever had to write CSS for a blog or CMS template, you already know how time-consuming is to find every ID and CLASS in a large document. We&#8217;re going [...]]]></description>
			<content:encoded><![CDATA[<p><img class="alignleft" src="/uploads/stylizator/stylizator_p.png" alt="The stylizator" width="150" height="120" />Writing CSS is good fun, but analyzing an html document to find how the page is structured us, at the very least, tiring. If you have ever had to write CSS for a blog or CMS template, you already know how time-consuming is to find every ID and CLASS in a large document. We&#8217;re going to write a simple script that takes an HTML file as input and gives us two things:</p>
<ul>
<li>First, some CSS and Javascript to generate a visual structure with all the names like the one that can be seen at the top image (and a javascript only version!!).</li>
<li> Second, a CSS file without any rules, but with every ID and class in the document.</li>
</ul>
<p>Let&#8217;s go:<br />
<span id="more-33"></span></p>
<h2>The tools</h2>
<p>We need to use any scripting language to read the HTML and act accordingly. This time we&#8217;re going to use perl, since it&#8217;s a simple and powerful language for text managing. It&#8217;s available in most operating systems and for windows users, it&#8217;s downloadable from <a href="http://activestate.com/" onclick="pageTracker._trackPageview('/outgoing/activestate.com/?referer=');">http://activestate.com/ </a>.</p>
<p>It should be possible to write in ruby, python, java or any other programming language. Take it as a guideline or, if you just want to use it, <a href="/uploads/stylizator/stylizator.zip">grab the code</a><br />
We&#8217;re going to use some CSS and Javascript too. Let&#8217;s get wet&#8230;</p>
<h2>Reading and parsing HTML</h2>
<p>First of all, we need an HTML document to be parsed. As I&#8217;ll recall later, I used HTML code from <a href="http://csszengarden.com" onclick="pageTracker._trackPageview('/outgoing/csszengarden.com?referer=');">csszengarden</a> to try the code.<br />
First thing to do is read the html from a file. The script we&#8217;re going starts the following way:</p>
<p class="toggle_code"><a href="/uploads/code/stylizator.1.pl" onclick="jQuery('#code_0').toggle('slow');return false">stylizator.1.pl</a></p>
<div  style="display:none" id="code_0">
<pre class="brush: pl">#!/usr/bin/perl
use strict;
my $file=shift;
die (&quot;HTML file missing&quot;) unless $file=~/\.html?$/;
my $filename=$file;
$filename=~s/\.html?$//;
print &quot;$filename\n&quot;;
open (HTML,$file) || die (&quot;HTML file can&#039;t be opened&quot;);
my @html=&lt;HTML&gt;;
close (HTML);
open(CSS,&quot;&gt;$filename.css&quot;);
open(HTML, &quot;&gt;$filename.structure.html&quot;);
my $html=join(&#039;&#039;,@html);</pre>
</div>
<p>This first part opens a file, reads it and assign the contents to a variable. Now, we need to find all the id&#8217;s present in the file. To do that we could use a full-fledged HTML parser, but since many documents in the real world are not correct and our necessities are quite easy, there&#8217;s a simpler solution. So, regular expressions to the rescue.<br />
Regular expressions find patterns in a file. We can tell perl to find things like /\w+/ and it will match alphanumeric characters. Probably to find named elements it should be enough to write:</p>
<p><code><br />
/id=\"?\w+\"?/i</code></p>
<p>Where &#8220;id=&#8221; means exactly that, \&#8221;? means a possible quotation mark, (\w+) is a group of letters or numbers, another possible quote and the last &#8220;i&#8221; tells us to ignore the case of the text. In this particular case, we&#8217;re going to use a slightly more complicated expression, to take into acount possible missing quotes and so on:</p>
<p><code><br />
/id\s*=\s*"?([^"\s&gt;]+)/<br />
</code></p>
<h2>Creating the CSS template</h2>
<p>We&#8217;re going to write an empty CSS file. I like to use a previously written template that includes links to my CSS-reset file and some more files I always use. Then, for every named element, we need a line:</p>
<p><code><br />
#namedelement{</code></p>
<p>}</p>
<p class="toggle_code"><a href="/uploads/code/stylizator.2.pl" onclick="jQuery('#code_1').toggle('slow');return false">stylizator.2.pl</a></p>
<div  style="display:none" id="code_1">
<pre class="brush: pl">print CSS &quot;\@import url(zero.css);\nbody{\n\n}\n&quot;;
my @id = $html =~ /id\s*=\s*&quot;?([^&quot;\s&gt;]+)/gis;
foreach my $id(@id){
	print &quot;id -&gt; $id\n&quot;;
	print CSS &quot;#&quot;.$id.&quot;{\n\n\t\n}\n&quot;;
}</pre>
</div>
<p>and every time we find a class, since they are not unique, we need to filter it, creating a hash (associative array) with its name. And then, the same thing we&#8217;ve done with the IDs. Some more code:</p>
<p class="toggle_code"><a href="/uploads/code/stylizator.3.pl" onclick="jQuery('#code_2').toggle('slow');return false">stylizator.3.pl</a></p>
<div  style="display:none" id="code_2">
<pre class="brush: pl">my %class;
my @classes = $html =~ /class\s*=\s*&quot;?([^&quot;\s&gt;]+)/gis;
foreach my $class(@classes){
	$class{$class}=&quot;class&quot;;
}
while (my($key, $value)=each(%class)){
	print &quot;$value -&gt; $key\n&quot;;
	print CSS &quot;.$key&quot;.&quot;{\n\n}\n&quot;
}</pre>
</div>
<p>With all that code, we already have a CSS template. You could start right now editing it to suit your needs. But we don&#8217;t know yet what the structure is. Why don&#8217;t we create a new HTML file that shows us how the document is laid? OK. Let&#8217;s write some more code</p>
<h2>Javascript: The magic</h2>
<p>Here is where the fun begins. Probably, I&#8217;ll need to wite another post about this piece of code, since there are a couple of sleights of hand, but, briefly, the following piece of code removes all the stylesheets and the inline CSS rules, then  takes all the elements in the dom and finds the ones that have an ID, and adds a P element with the ID name to each one.</p>
<p class="toggle_code"><a href="/uploads/code/stylizator.js" onclick="jQuery('#code_3').toggle('slow');return false">stylizator.js</a></p>
<div  style="display:none" id="code_3">
<pre class="brush: js">function removeSheets(){
  if(document.styleSheets){
    var c = document.styleSheets.length;
    for(var i=0;i&lt;c;i++){
		document.styleSheets[i].disabled=true;
    }
  }
  findNamedElements();
}
function findNamedElements(){
	allElements = document.getElementsByTagName(&#039;*&#039;);
	for(i=0;i&lt;allElements.length;i++){
		allElements[i].style.cssText = &#039;&#039;;
		if(allElements[i].id){
			allElements[i].style.position = &#039;relative&#039;;
			allElements[i].style.border = &#039;1px solid silver&#039;;
			allElements[i].style.padding = &#039;20px&#039;;
			allElements[i].style.margin = &#039;10px&#039;;
			p=document.createElement(&#039;p&#039;);
			sometext = document.createTextNode(allElements[i].id);
			p.appendChild(sometext);
			p.className = &#039;sign&#039;;
			allElements[i].appendChild(p);
		}
	}
	if (window.innerWidth){
		var styleText = &#039;.sign{border:1px dashed red;position:absolute;top:0;left:0;padding:2px;background-color:#fff}&#039;;
		var head=document.getElementsByTagName(&quot;head&quot;)[0];
		var styleNode = document.createElement(&quot;style&quot;);
		styleNode.appendChild(document.createTextNode(styleText));
		head.appendChild(styleNode);
	}else{
		var newStyle = document.createStyleSheet();
		newStyle.addRule(&#039;.sign&#039;,&#039;border:1px dashed red;position:absolute;top:0;left:0;padding:2px;background-color:#fff&#039;);
	}
}
removeSheets();
</pre>
</div>
<p>As you can see, there are only two functions. The first one, removeSheets(), disables every stylesheet included from the HTML, and then calls the other one, findNamedElements(), This function gets all the elements in the DOM (getElementsByTagName(&#8216;*&#8217;)) and iterates over them, doing a couple of things:</p>
<ul>
<li>First, remove inline style elements (allElements[i].style.cssText = &#8221;)</li>
<li>For the elements with an ID, some styles are added (a silver box around them)</li>
<li>A child P tag with the ID text is created too, giving it a class &#8217;sign&#8217;</li>
</ul>
<p>Then, we create a new stylesheet only for that class. We have to code a different way for Explorer and Firefox/Opera/Others because of compatibility issues.<br />
Last, the function is called with removesheets(). You can even save the code as a bookmarklet and apply it to any website. It&#8217;s truly portable.</p>
<p>Now we need some perl code to insert the javascript into the HTML file, just before the &lt;/body&gt;: tag.<br />
<strong>Update:</strong> My pal Félix has just tol me that I need to add an &#8220;s&#8221; in the javascript substitution, to cope with multi-line javascript. Thanks, Félix</p>
<p class="toggle_code"><a href="/uploads/code/stylizator.4.pl" onclick="jQuery('#code_4').toggle('slow');return false">stylizator.4.pl</a></p>
<div  style="display:none" id="code_4">
<pre class="brush: pl">$html=~s/&lt;script.*?&lt;\/script&gt;//sg;
open (JS,&quot;stylizator.js&quot;) || die(&quot;You also need the file stylizator.js in the same folder&quot;);
my @js=&lt;JS&gt;;
close (JS);
my $js=&#039;&lt;script type=&quot;text/javascript&quot;&gt;&#039;.join(&quot;&quot;,@js).&#039;&lt;/script&gt;&lt;/body&gt;&#039;;
$html=~s/&lt;\/body&gt;/$js/e;
print HTML $html;
close (CSS);
close (HTML);</pre>
</div>
<p>When you run the full script, another HTML file will be generated, that shows how the document is laid.</p>
<h2>Trying it out</h2>
<p>The best example I can think of is <a href="http://csszengarden.com/" onclick="pageTracker._trackPageview('/outgoing/csszengarden.com/?referer=');">CSSzengarden,</a> a site where a default layout is given to designers to skin it the better they can. I downloaded the HTML code (<acronym title="Creative commons, Attribution-NonCommercial-ShareAlike">CC license, by-nc-sa</acronym>) and called he script over it:</p>
<p><code><br />
$&gt; perl stylizator.pl csszengarden.html</code></p>
<p>And I got a csszengarden CSS file, with all the named tags, and a csszengarden.structure.html file with the visual structure of the site.</p>
<p class="toggle_code"><a href="/uploads/code/csszengarden.html" onclick="jQuery('#code_5').toggle('slow');return false">csszengarden.html</a></p>
<div  style="display:none" id="code_5">
<pre class="brush: html">&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Strict//EN&quot;
	&quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&quot;&gt;
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; xml:lang=&quot;en&quot; &gt;
&lt;head&gt;
	&lt;meta http-equiv=&quot;content-type&quot; content=&quot;text/html; charset=iso-8859-1&quot; /&gt;
	&lt;meta name=&quot;author&quot; content=&quot;Dave Shea&quot; /&gt;
	&lt;meta name=&quot;keywords&quot; content=&quot;design, css, cascading, style, sheets, xhtml, graphic design, w3c, web standards, visual, display&quot; /&gt;
	&lt;meta name=&quot;description&quot; content=&quot;A demonstration of what can be accomplished visually through CSS-based design.&quot; /&gt;
	&lt;meta name=&quot;robots&quot; content=&quot;all&quot; /&gt;
	&lt;title&gt;css Zen Garden: The Beauty in CSS Design&lt;/title&gt;

	&lt;!-- to correct the unsightly Flash of Unstyled Content. http://www.bluerobot.com/web/css/fouc.asp --&gt;
	&lt;script type=&quot;text/javascript&quot;&gt;&lt;/script&gt;

	&lt;style type=&quot;text/css&quot; title=&quot;currentStyle&quot; media=&quot;screen&quot;&gt;
		@import &quot;/001/001.css&quot;;
	&lt;/style&gt;
	&lt;link rel=&quot;Shortcut Icon&quot; type=&quot;image/x-icon&quot; href=&quot;http://www.csszengarden.com/favicon.ico&quot; /&gt;
	&lt;link rel=&quot;alternate&quot; type=&quot;application/rss+xml&quot; title=&quot;RSS&quot; href=&quot;http://www.csszengarden.com/zengarden.xml&quot; /&gt;
&lt;/head&gt;

&lt;!--

	This xhtml document is marked up to provide the designer with the maximum possible flexibility.
	There are more classes and extraneous tags than needed, and in a real world situation, it&#039;s more
	likely that it would be much leaner.

	However, I think we can all agree that even given that, we&#039;re still better off than if this had been
	built with tables.

--&gt;

&lt;body id=&quot;css-zen-garden&quot;&gt;
&lt;div id=&quot;container&quot;&gt;
	&lt;div id=&quot;intro&quot;&gt;
		&lt;div id=&quot;pageHeader&quot;&gt;
			&lt;h1&gt;&lt;span&gt;css Zen Garden&lt;/span&gt;&lt;/h1&gt;
			&lt;h2&gt;&lt;span&gt;The Beauty of &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; Design&lt;/span&gt;&lt;/h2&gt;
		&lt;/div&gt;

		&lt;div id=&quot;quickSummary&quot;&gt;
			&lt;p class=&quot;p1&quot;&gt;&lt;span&gt;A demonstration of what can be accomplished visually through &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt;-based design. Select any style sheet from the list to load it into this page.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p2&quot;&gt;&lt;span&gt;Download the sample &lt;a href=&quot;/zengarden-sample.html&quot; title=&quot;This page&#039;s source HTML code, not to be modified.&quot;&gt;html file&lt;/a&gt; and &lt;a href=&quot;/zengarden-sample.css&quot; title=&quot;This page&#039;s sample CSS, the file you may modify.&quot;&gt;css file&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
		&lt;/div&gt;

		&lt;div id=&quot;preamble&quot;&gt;
			&lt;h3&gt;&lt;span&gt;The Road to Enlightenment&lt;/span&gt;&lt;/h3&gt;
			&lt;p class=&quot;p1&quot;&gt;&lt;span&gt;Littering a dark and dreary road lay the past relics of browser-specific tags, incompatible &lt;acronym title=&quot;Document Object Model&quot;&gt;DOM&lt;/acronym&gt;s, and broken &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; support.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p2&quot;&gt;&lt;span&gt;Today, we must clear the mind of past practices. Web enlightenment has been achieved thanks to the tireless efforts of folk like the &lt;acronym title=&quot;World Wide Web Consortium&quot;&gt;W3C&lt;/acronym&gt;, &lt;acronym title=&quot;Web Standards Project&quot;&gt;WaSP&lt;/acronym&gt; and the major browser creators.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p3&quot;&gt;&lt;span&gt;The css Zen Garden invites you to relax and meditate on the important lessons of the masters. Begin to see with clarity. Learn to use the (yet to be) time-honored techniques in new and invigorating fashion. Become one with the web.&lt;/span&gt;&lt;/p&gt;
		&lt;/div&gt;
	&lt;/div&gt;

	&lt;div id=&quot;supportingText&quot;&gt;
		&lt;div id=&quot;explanation&quot;&gt;
			&lt;h3&gt;&lt;span&gt;So What is This About?&lt;/span&gt;&lt;/h3&gt;
			&lt;p class=&quot;p1&quot;&gt;&lt;span&gt;There is clearly a need for &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; to be taken seriously by graphic artists. The Zen Garden aims to excite, inspire, and encourage participation. To begin, view some of the existing designs in the list. Clicking on any one will load the style sheet into this very page. The code remains the same, the only thing that has changed is the external .css file. Yes, really.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p2&quot;&gt;&lt;span&gt;&lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; allows complete and total control over the style of a hypertext document. The only way this can be illustrated in a way that gets people excited is by demonstrating what it can truly be, once the reins are placed in the hands of those able to create beauty from structure. To date, most examples of neat tricks and hacks have been demonstrated by structurists and coders. Designers have yet to make their mark. This needs to change.&lt;/span&gt;&lt;/p&gt;
		&lt;/div&gt;

		&lt;div id=&quot;participation&quot;&gt;
			&lt;h3&gt;&lt;span&gt;Participation&lt;/span&gt;&lt;/h3&gt;
			&lt;p class=&quot;p1&quot;&gt;&lt;span&gt;Graphic artists only please. You are modifying this page, so strong &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; skills are necessary, but the example files are commented well enough that even &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; novices can use them as starting points. Please see the &lt;a href=&quot;http://www.mezzoblue.com/zengarden/resources/&quot; title=&quot;A listing of CSS-related resources&quot;&gt;&lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; Resource Guide&lt;/a&gt; for advanced tutorials and tips on working with &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt;.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p2&quot;&gt;&lt;span&gt;You may modify the style sheet in any way you wish, but not the &lt;acronym title=&quot;HyperText Markup Language&quot;&gt;HTML&lt;/acronym&gt;. This may seem daunting at first if you&amp;#8217;ve never worked this way before, but follow the listed links to learn more, and use the sample files as a guide.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p3&quot;&gt;&lt;span&gt;Download the sample &lt;a href=&quot;/zengarden-sample.html&quot; title=&quot;This page&#039;s source HTML code, not to be modified.&quot;&gt;html file&lt;/a&gt; and &lt;a href=&quot;/zengarden-sample.css&quot; title=&quot;This page&#039;s sample CSS, the file you may modify.&quot;&gt;css file&lt;/a&gt; to work on a copy locally. Once you have completed your masterpiece (and please, don&amp;#8217;t submit half-finished work) upload your .css file to a web server under your control. &lt;a href=&quot;http://www.mezzoblue.com/zengarden/submit/&quot; title=&quot;Use the contact form to send us your CSS file&quot;&gt;Send us a link&lt;/a&gt; to the file and if we choose to use it, we will spider the associated images. Final submissions will be placed on our server.&lt;/span&gt;&lt;/p&gt;
					&lt;/div&gt;

		&lt;div id=&quot;benefits&quot;&gt;
			&lt;h3&gt;&lt;span&gt;Benefits&lt;/span&gt;&lt;/h3&gt;
			&lt;p class=&quot;p1&quot;&gt;&lt;span&gt;Why participate? For recognition, inspiration, and a resource we can all refer to when making the case for &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt;-based design. This is sorely needed, even today. More and more major sites are taking the leap, but not enough have. One day this gallery will be a historical curiosity; that day is not today.&lt;/span&gt;&lt;/p&gt;
		&lt;/div&gt;

		&lt;div id=&quot;requirements&quot;&gt;
			&lt;h3&gt;&lt;span&gt;Requirements&lt;/span&gt;&lt;/h3&gt;
			&lt;p class=&quot;p1&quot;&gt;&lt;span&gt;We would like to see as much &lt;acronym title=&quot;Cascading Style Sheets, version 1&quot;&gt;CSS1&lt;/acronym&gt; as possible. &lt;acronym title=&quot;Cascading Style Sheets, version 2&quot;&gt;CSS2&lt;/acronym&gt; should be limited to widely-supported elements only. The css Zen Garden is about functional, practical &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; and not the latest bleeding-edge tricks viewable by 2% of the browsing public. The only real requirement we have is that your &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; validates.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p2&quot;&gt;&lt;span&gt;Unfortunately, designing this way highlights the flaws in the various implementations of &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt;. Different browsers display differently, even completely valid &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; at times, and this becomes maddening when a fix for one leads to breakage in another. View the &lt;a href=&quot;http://www.mezzoblue.com/zengarden/resources/&quot; title=&quot;A listing of CSS-related resources&quot;&gt;Resources&lt;/a&gt; page for information on some of the fixes available. Full browser compliance is still sometimes a pipe dream, and we do not expect you to come up with pixel-perfect code across every platform. But do test in as many as you can. If your design doesn&amp;#8217;t work in at least IE5+/Win and Mozilla (run by over 90% of the population), chances are we won&amp;#8217;t accept it.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p3&quot;&gt;&lt;span&gt;We ask that you submit original artwork. Please respect copyright laws. Please keep objectionable material to a minimum; tasteful nudity is acceptable, outright pornography will be rejected.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p4&quot;&gt;&lt;span&gt;This is a learning exercise as well as a demonstration. You retain full copyright on your graphics (with limited exceptions, see &lt;a href=&quot;http://www.mezzoblue.com/zengarden/submit/guidelines/&quot;&gt;submission guidelines&lt;/a&gt;), but we ask you release your &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; under a Creative Commons license identical to the &lt;a href=&quot;http://creativecommons.org/licenses/by-nc-sa/1.0/&quot; title=&quot;View the Zen Garden&#039;s license information.&quot;&gt;one on this site&lt;/a&gt; so that others may learn from your work.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p5&quot;&gt;&lt;span&gt;Bandwidth graciously donated by &lt;a href=&quot;http://www.dreamfirestudios.com/&quot;&gt;DreamFire Studios&lt;/a&gt;. Now available: &lt;a href=&quot;http://www.amazon.com/exec/obidos/ASIN/0321303474/mezzoblue-20/&quot;&gt;Zen Garden, the book&lt;/a&gt;.&lt;/span&gt;&amp;nbsp;&lt;/p&gt;
		&lt;/div&gt;

		&lt;div id=&quot;footer&quot;&gt;
			&lt;a href=&quot;http://validator.w3.org/check/referer&quot; title=&quot;Check the validity of this site&amp;#8217;s XHTML&quot;&gt;xhtml&lt;/a&gt; &amp;nbsp;
			&lt;a href=&quot;http://jigsaw.w3.org/css-validator/check/referer&quot; title=&quot;Check the validity of this site&amp;#8217;s CSS&quot;&gt;css&lt;/a&gt; &amp;nbsp;
			&lt;a href=&quot;http://creativecommons.org/licenses/by-nc-sa/1.0/&quot; title=&quot;View details of the license of this site, courtesy of Creative Commons.&quot;&gt;cc&lt;/a&gt; &amp;nbsp;
			&lt;a href=&quot;http://www.contentquality.com/mynewtester/cynthia.exe?Url1=http:%2F%2Fcsszengarden.com%2F&quot; title=&quot;Check the accessibility of this site according to U.S. Section 508&quot;&gt;508&lt;/a&gt; &amp;nbsp;
			&lt;a href=&quot;http://mezzoblue.com/zengarden/faq/#aaa&quot; title=&quot;Check the accessibility of this site according to Web Content Accessibility Guidelines 1.0&quot;&gt;aaa&lt;/a&gt;
		&lt;/div&gt;

	&lt;/div&gt;

	&lt;div id=&quot;linkList&quot;&gt;
		&lt;div id=&quot;linkList2&quot;&gt;
			&lt;div id=&quot;lselect&quot;&gt;
				&lt;h3 class=&quot;select&quot;&gt;&lt;span&gt;Select a Design:&lt;/span&gt;&lt;/h3&gt;
				&lt;ul&gt;
					&lt;li&gt;&lt;a href=&quot;?cssfile=/202/202.css&amp;amp;page=0&quot; title=&quot;AccessKey: a&quot; accesskey=&quot;a&quot;&gt;Retro Theater&lt;/a&gt; by &lt;a href=&quot;http://space-sheeps.info/&quot; class=&quot;c&quot;&gt;Eric Rog&amp;eacute;&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;?cssfile=/201/201.css&amp;amp;page=0&quot; title=&quot;AccessKey: b&quot; accesskey=&quot;b&quot;&gt;Lily Pond&lt;/a&gt; by &lt;a href=&quot;http://www.tulips4rose.com&quot; class=&quot;c&quot;&gt;Rose Thorogood&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;?cssfile=/200/200.css&amp;amp;page=0&quot; title=&quot;AccessKey: c&quot; accesskey=&quot;c&quot;&gt;Icicle Outback&lt;/a&gt; by &lt;a href=&quot;http://www.timovirtanen.com/&quot; class=&quot;c&quot;&gt;Timo Virtanen&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;?cssfile=/199/199.css&amp;amp;page=0&quot; title=&quot;AccessKey: d&quot; accesskey=&quot;d&quot;&gt;Zen Army&lt;/a&gt; by &lt;a href=&quot;http://www.niceguy.com/&quot; class=&quot;c&quot;&gt;Carl Desmond&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;?cssfile=/198/198.css&amp;amp;page=0&quot; title=&quot;AccessKey: e&quot; accesskey=&quot;e&quot;&gt;The Original&lt;/a&gt; by &lt;a href=&quot;http://www.bluejam.com/&quot; class=&quot;c&quot;&gt;Joachim Shotter&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;?cssfile=/197/197.css&amp;amp;page=0&quot; title=&quot;AccessKey: f&quot; accesskey=&quot;f&quot;&gt;Floral Touch&lt;/a&gt; by &lt;a href=&quot;http://www.jahmasta.com/&quot; class=&quot;c&quot;&gt;Jadas Jimmy&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;?cssfile=/196/196.css&amp;amp;page=0&quot; title=&quot;AccessKey: g&quot; accesskey=&quot;g&quot;&gt;Elegance in Simplicity&lt;/a&gt; by &lt;a href=&quot;http://www.manisheriar.com/blog/&quot; class=&quot;c&quot;&gt;Mani Sheriar&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;?cssfile=/195/195.css&amp;amp;page=0&quot; title=&quot;AccessKey: h&quot; accesskey=&quot;h&quot;&gt;Dazzling Beauty&lt;/a&gt; by &lt;a href=&quot;http://blog.denysri.com/&quot; class=&quot;c&quot;&gt;Deny Sri Supriyono&lt;/a&gt;&lt;/li&gt;
				&lt;/ul&gt;
			&lt;/div&gt;

			&lt;div id=&quot;larchives&quot;&gt;
				&lt;h3 class=&quot;archives&quot;&gt;&lt;span&gt;Archives:&lt;/span&gt;&lt;/h3&gt;
				&lt;ul&gt;
					&lt;li&gt;&lt;a href=&quot;/?cssfile=/001/001.css&amp;amp;page=1&quot; title=&quot;View next set of designs. AccessKey: n&quot; accesskey=&quot;n&quot;&gt;&lt;span class=&quot;accesskey&quot;&gt;n&lt;/span&gt;ext designs &amp;raquo;&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;http://www.mezzoblue.com/zengarden/alldesigns/&quot; title=&quot;View every submission to the Zen Garden. AccessKey: w&quot; accesskey=&quot;w&quot;&gt;Vie&lt;span class=&quot;accesskey&quot;&gt;w&lt;/span&gt; All Designs&lt;/a&gt;&lt;/li&gt;
				&lt;/ul&gt;
			&lt;/div&gt;

			&lt;div id=&quot;lresources&quot;&gt;
				&lt;h3 class=&quot;resources&quot;&gt;&lt;span&gt;Resources:&lt;/span&gt;&lt;/h3&gt;
				&lt;ul&gt;
					&lt;li&gt;&lt;a href=&quot;/001/001.css&quot; title=&quot;View the source CSS file for the currently-viewed design, AccessKey: v&quot; accesskey=&quot;v&quot;&gt;&lt;span class=&quot;accesskey&quot;&gt;V&lt;/span&gt;iew This Design&amp;#8217;s &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt;&lt;/a&gt;&lt;/li&gt;					&lt;li&gt;&lt;a href=&quot;http://www.mezzoblue.com/zengarden/resources/&quot; title=&quot;Links to great sites with information on using CSS. AccessKey: r&quot; accesskey=&quot;r&quot;&gt;&lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; &lt;span class=&quot;accesskey&quot;&gt;R&lt;/span&gt;esources&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;http://www.mezzoblue.com/zengarden/faq/&quot; title=&quot;A list of Frequently Asked Questions about the Zen Garden. AccessKey: q&quot; accesskey=&quot;q&quot;&gt;&lt;acronym title=&quot;Frequently Asked Questions&quot;&gt;FA&lt;span class=&quot;accesskey&quot;&gt;Q&lt;/span&gt;&lt;/acronym&gt;&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;http://www.mezzoblue.com/zengarden/submit/&quot; title=&quot;Send in your own CSS file. AccessKey: s&quot; accesskey=&quot;s&quot;&gt;&lt;span class=&quot;accesskey&quot;&gt;S&lt;/span&gt;ubmit a Design&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;http://www.mezzoblue.com/zengarden/translations/&quot; title=&quot;View translated versions of this page. AccessKey: t&quot; accesskey=&quot;t&quot;&gt;&lt;span class=&quot;accesskey&quot;&gt;T&lt;/span&gt;ranslations&lt;/a&gt;&lt;/li&gt;
				&lt;/ul&gt;
			&lt;/div&gt;
		&lt;/div&gt;
	&lt;/div&gt;

&lt;/div&gt;

&lt;!-- These extra divs/spans may be used as catch-alls to add extra imagery. --&gt;
&lt;div id=&quot;extraDiv1&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div id=&quot;extraDiv2&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div id=&quot;extraDiv3&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
XXXX
&lt;/body&gt;
&lt;/html&gt;</pre>
</div>
<p class="toggle_code"><a href="/uploads/code/csszengarden.structure.html" onclick="jQuery('#code_6').toggle('slow');return false">csszengarden.structure.html</a></p>
<div  style="display:none" id="code_6">
<pre class="brush: html">&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Strict//EN&quot;
	&quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&quot;&gt;
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; xml:lang=&quot;en&quot; &gt;
&lt;head&gt;
	&lt;meta http-equiv=&quot;content-type&quot; content=&quot;text/html; charset=iso-8859-1&quot; /&gt;
	&lt;meta name=&quot;author&quot; content=&quot;Dave Shea&quot; /&gt;
	&lt;meta name=&quot;keywords&quot; content=&quot;design, css, cascading, style, sheets, xhtml, graphic design, w3c, web standards, visual, display&quot; /&gt;
	&lt;meta name=&quot;description&quot; content=&quot;A demonstration of what can be accomplished visually through CSS-based design.&quot; /&gt;
	&lt;meta name=&quot;robots&quot; content=&quot;all&quot; /&gt;
	&lt;title&gt;css Zen Garden: The Beauty in CSS Design&lt;/title&gt;

	&lt;!-- to correct the unsightly Flash of Unstyled Content. http://www.bluerobot.com/web/css/fouc.asp --&gt;

	&lt;style type=&quot;text/css&quot; title=&quot;currentStyle&quot; media=&quot;screen&quot;&gt;
		@import &quot;/001/001.css&quot;;
	&lt;/style&gt;
	&lt;link rel=&quot;Shortcut Icon&quot; type=&quot;image/x-icon&quot; href=&quot;http://www.csszengarden.com/favicon.ico&quot; /&gt;
	&lt;link rel=&quot;alternate&quot; type=&quot;application/rss+xml&quot; title=&quot;RSS&quot; href=&quot;http://www.csszengarden.com/zengarden.xml&quot; /&gt;
&lt;/head&gt;

&lt;!--

	This xhtml document is marked up to provide the designer with the maximum possible flexibility.
	There are more classes and extraneous tags than needed, and in a real world situation, it&#039;s more
	likely that it would be much leaner.

	However, I think we can all agree that even given that, we&#039;re still better off than if this had been
	built with tables.

--&gt;

&lt;body id=&quot;css-zen-garden&quot;&gt;
&lt;div id=&quot;container&quot;&gt;
	&lt;div id=&quot;intro&quot;&gt;
		&lt;div id=&quot;pageHeader&quot;&gt;
			&lt;h1&gt;&lt;span&gt;css Zen Garden&lt;/span&gt;&lt;/h1&gt;
			&lt;h2&gt;&lt;span&gt;The Beauty of &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; Design&lt;/span&gt;&lt;/h2&gt;
		&lt;/div&gt;

		&lt;div id=&quot;quickSummary&quot;&gt;
			&lt;p class=&quot;p1&quot;&gt;&lt;span&gt;A demonstration of what can be accomplished visually through &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt;-based design. Select any style sheet from the list to load it into this page.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p2&quot;&gt;&lt;span&gt;Download the sample &lt;a href=&quot;/zengarden-sample.html&quot; title=&quot;This page&#039;s source HTML code, not to be modified.&quot;&gt;html file&lt;/a&gt; and &lt;a href=&quot;/zengarden-sample.css&quot; title=&quot;This page&#039;s sample CSS, the file you may modify.&quot;&gt;css file&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
		&lt;/div&gt;

		&lt;div id=&quot;preamble&quot;&gt;
			&lt;h3&gt;&lt;span&gt;The Road to Enlightenment&lt;/span&gt;&lt;/h3&gt;
			&lt;p class=&quot;p1&quot;&gt;&lt;span&gt;Littering a dark and dreary road lay the past relics of browser-specific tags, incompatible &lt;acronym title=&quot;Document Object Model&quot;&gt;DOM&lt;/acronym&gt;s, and broken &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; support.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p2&quot;&gt;&lt;span&gt;Today, we must clear the mind of past practices. Web enlightenment has been achieved thanks to the tireless efforts of folk like the &lt;acronym title=&quot;World Wide Web Consortium&quot;&gt;W3C&lt;/acronym&gt;, &lt;acronym title=&quot;Web Standards Project&quot;&gt;WaSP&lt;/acronym&gt; and the major browser creators.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p3&quot;&gt;&lt;span&gt;The css Zen Garden invites you to relax and meditate on the important lessons of the masters. Begin to see with clarity. Learn to use the (yet to be) time-honored techniques in new and invigorating fashion. Become one with the web.&lt;/span&gt;&lt;/p&gt;
		&lt;/div&gt;
	&lt;/div&gt;

	&lt;div id=&quot;supportingText&quot;&gt;
		&lt;div id=&quot;explanation&quot;&gt;
			&lt;h3&gt;&lt;span&gt;So What is This About?&lt;/span&gt;&lt;/h3&gt;
			&lt;p class=&quot;p1&quot;&gt;&lt;span&gt;There is clearly a need for &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; to be taken seriously by graphic artists. The Zen Garden aims to excite, inspire, and encourage participation. To begin, view some of the existing designs in the list. Clicking on any one will load the style sheet into this very page. The code remains the same, the only thing that has changed is the external .css file. Yes, really.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p2&quot;&gt;&lt;span&gt;&lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; allows complete and total control over the style of a hypertext document. The only way this can be illustrated in a way that gets people excited is by demonstrating what it can truly be, once the reins are placed in the hands of those able to create beauty from structure. To date, most examples of neat tricks and hacks have been demonstrated by structurists and coders. Designers have yet to make their mark. This needs to change.&lt;/span&gt;&lt;/p&gt;
		&lt;/div&gt;

		&lt;div id=&quot;participation&quot;&gt;
			&lt;h3&gt;&lt;span&gt;Participation&lt;/span&gt;&lt;/h3&gt;
			&lt;p class=&quot;p1&quot;&gt;&lt;span&gt;Graphic artists only please. You are modifying this page, so strong &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; skills are necessary, but the example files are commented well enough that even &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; novices can use them as starting points. Please see the &lt;a href=&quot;http://www.mezzoblue.com/zengarden/resources/&quot; title=&quot;A listing of CSS-related resources&quot;&gt;&lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; Resource Guide&lt;/a&gt; for advanced tutorials and tips on working with &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt;.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p2&quot;&gt;&lt;span&gt;You may modify the style sheet in any way you wish, but not the &lt;acronym title=&quot;HyperText Markup Language&quot;&gt;HTML&lt;/acronym&gt;. This may seem daunting at first if you&amp;#8217;ve never worked this way before, but follow the listed links to learn more, and use the sample files as a guide.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p3&quot;&gt;&lt;span&gt;Download the sample &lt;a href=&quot;/zengarden-sample.html&quot; title=&quot;This page&#039;s source HTML code, not to be modified.&quot;&gt;html file&lt;/a&gt; and &lt;a href=&quot;/zengarden-sample.css&quot; title=&quot;This page&#039;s sample CSS, the file you may modify.&quot;&gt;css file&lt;/a&gt; to work on a copy locally. Once you have completed your masterpiece (and please, don&amp;#8217;t submit half-finished work) upload your .css file to a web server under your control. &lt;a href=&quot;http://www.mezzoblue.com/zengarden/submit/&quot; title=&quot;Use the contact form to send us your CSS file&quot;&gt;Send us a link&lt;/a&gt; to the file and if we choose to use it, we will spider the associated images. Final submissions will be placed on our server.&lt;/span&gt;&lt;/p&gt;
					&lt;/div&gt;

		&lt;div id=&quot;benefits&quot;&gt;
			&lt;h3&gt;&lt;span&gt;Benefits&lt;/span&gt;&lt;/h3&gt;
			&lt;p class=&quot;p1&quot;&gt;&lt;span&gt;Why participate? For recognition, inspiration, and a resource we can all refer to when making the case for &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt;-based design. This is sorely needed, even today. More and more major sites are taking the leap, but not enough have. One day this gallery will be a historical curiosity; that day is not today.&lt;/span&gt;&lt;/p&gt;
		&lt;/div&gt;

		&lt;div id=&quot;requirements&quot;&gt;
			&lt;h3&gt;&lt;span&gt;Requirements&lt;/span&gt;&lt;/h3&gt;
			&lt;p class=&quot;p1&quot;&gt;&lt;span&gt;We would like to see as much &lt;acronym title=&quot;Cascading Style Sheets, version 1&quot;&gt;CSS1&lt;/acronym&gt; as possible. &lt;acronym title=&quot;Cascading Style Sheets, version 2&quot;&gt;CSS2&lt;/acronym&gt; should be limited to widely-supported elements only. The css Zen Garden is about functional, practical &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; and not the latest bleeding-edge tricks viewable by 2% of the browsing public. The only real requirement we have is that your &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; validates.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p2&quot;&gt;&lt;span&gt;Unfortunately, designing this way highlights the flaws in the various implementations of &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt;. Different browsers display differently, even completely valid &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; at times, and this becomes maddening when a fix for one leads to breakage in another. View the &lt;a href=&quot;http://www.mezzoblue.com/zengarden/resources/&quot; title=&quot;A listing of CSS-related resources&quot;&gt;Resources&lt;/a&gt; page for information on some of the fixes available. Full browser compliance is still sometimes a pipe dream, and we do not expect you to come up with pixel-perfect code across every platform. But do test in as many as you can. If your design doesn&amp;#8217;t work in at least IE5+/Win and Mozilla (run by over 90% of the population), chances are we won&amp;#8217;t accept it.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p3&quot;&gt;&lt;span&gt;We ask that you submit original artwork. Please respect copyright laws. Please keep objectionable material to a minimum; tasteful nudity is acceptable, outright pornography will be rejected.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p4&quot;&gt;&lt;span&gt;This is a learning exercise as well as a demonstration. You retain full copyright on your graphics (with limited exceptions, see &lt;a href=&quot;http://www.mezzoblue.com/zengarden/submit/guidelines/&quot;&gt;submission guidelines&lt;/a&gt;), but we ask you release your &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; under a Creative Commons license identical to the &lt;a href=&quot;http://creativecommons.org/licenses/by-nc-sa/1.0/&quot; title=&quot;View the Zen Garden&#039;s license information.&quot;&gt;one on this site&lt;/a&gt; so that others may learn from your work.&lt;/span&gt;&lt;/p&gt;
			&lt;p class=&quot;p5&quot;&gt;&lt;span&gt;Bandwidth graciously donated by &lt;a href=&quot;http://www.dreamfirestudios.com/&quot;&gt;DreamFire Studios&lt;/a&gt;. Now available: &lt;a href=&quot;http://www.amazon.com/exec/obidos/ASIN/0321303474/mezzoblue-20/&quot;&gt;Zen Garden, the book&lt;/a&gt;.&lt;/span&gt;&amp;nbsp;&lt;/p&gt;
		&lt;/div&gt;

		&lt;div id=&quot;footer&quot;&gt;
			&lt;a href=&quot;http://validator.w3.org/check/referer&quot; title=&quot;Check the validity of this site&amp;#8217;s XHTML&quot;&gt;xhtml&lt;/a&gt; &amp;nbsp;
			&lt;a href=&quot;http://jigsaw.w3.org/css-validator/check/referer&quot; title=&quot;Check the validity of this site&amp;#8217;s CSS&quot;&gt;css&lt;/a&gt; &amp;nbsp;
			&lt;a href=&quot;http://creativecommons.org/licenses/by-nc-sa/1.0/&quot; title=&quot;View details of the license of this site, courtesy of Creative Commons.&quot;&gt;cc&lt;/a&gt; &amp;nbsp;
			&lt;a href=&quot;http://www.contentquality.com/mynewtester/cynthia.exe?Url1=http:%2F%2Fcsszengarden.com%2F&quot; title=&quot;Check the accessibility of this site according to U.S. Section 508&quot;&gt;508&lt;/a&gt; &amp;nbsp;
			&lt;a href=&quot;http://mezzoblue.com/zengarden/faq/#aaa&quot; title=&quot;Check the accessibility of this site according to Web Content Accessibility Guidelines 1.0&quot;&gt;aaa&lt;/a&gt;
		&lt;/div&gt;

	&lt;/div&gt;

	&lt;div id=&quot;linkList&quot;&gt;
		&lt;div id=&quot;linkList2&quot;&gt;
			&lt;div id=&quot;lselect&quot;&gt;
				&lt;h3 class=&quot;select&quot;&gt;&lt;span&gt;Select a Design:&lt;/span&gt;&lt;/h3&gt;
				&lt;ul&gt;
					&lt;li&gt;&lt;a href=&quot;?cssfile=/202/202.css&amp;amp;page=0&quot; title=&quot;AccessKey: a&quot; accesskey=&quot;a&quot;&gt;Retro Theater&lt;/a&gt; by &lt;a href=&quot;http://space-sheeps.info/&quot; class=&quot;c&quot;&gt;Eric Rog&amp;eacute;&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;?cssfile=/201/201.css&amp;amp;page=0&quot; title=&quot;AccessKey: b&quot; accesskey=&quot;b&quot;&gt;Lily Pond&lt;/a&gt; by &lt;a href=&quot;http://www.tulips4rose.com&quot; class=&quot;c&quot;&gt;Rose Thorogood&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;?cssfile=/200/200.css&amp;amp;page=0&quot; title=&quot;AccessKey: c&quot; accesskey=&quot;c&quot;&gt;Icicle Outback&lt;/a&gt; by &lt;a href=&quot;http://www.timovirtanen.com/&quot; class=&quot;c&quot;&gt;Timo Virtanen&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;?cssfile=/199/199.css&amp;amp;page=0&quot; title=&quot;AccessKey: d&quot; accesskey=&quot;d&quot;&gt;Zen Army&lt;/a&gt; by &lt;a href=&quot;http://www.niceguy.com/&quot; class=&quot;c&quot;&gt;Carl Desmond&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;?cssfile=/198/198.css&amp;amp;page=0&quot; title=&quot;AccessKey: e&quot; accesskey=&quot;e&quot;&gt;The Original&lt;/a&gt; by &lt;a href=&quot;http://www.bluejam.com/&quot; class=&quot;c&quot;&gt;Joachim Shotter&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;?cssfile=/197/197.css&amp;amp;page=0&quot; title=&quot;AccessKey: f&quot; accesskey=&quot;f&quot;&gt;Floral Touch&lt;/a&gt; by &lt;a href=&quot;http://www.jahmasta.com/&quot; class=&quot;c&quot;&gt;Jadas Jimmy&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;?cssfile=/196/196.css&amp;amp;page=0&quot; title=&quot;AccessKey: g&quot; accesskey=&quot;g&quot;&gt;Elegance in Simplicity&lt;/a&gt; by &lt;a href=&quot;http://www.manisheriar.com/blog/&quot; class=&quot;c&quot;&gt;Mani Sheriar&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;?cssfile=/195/195.css&amp;amp;page=0&quot; title=&quot;AccessKey: h&quot; accesskey=&quot;h&quot;&gt;Dazzling Beauty&lt;/a&gt; by &lt;a href=&quot;http://blog.denysri.com/&quot; class=&quot;c&quot;&gt;Deny Sri Supriyono&lt;/a&gt;&lt;/li&gt;
				&lt;/ul&gt;
			&lt;/div&gt;

			&lt;div id=&quot;larchives&quot;&gt;
				&lt;h3 class=&quot;archives&quot;&gt;&lt;span&gt;Archives:&lt;/span&gt;&lt;/h3&gt;
				&lt;ul&gt;
					&lt;li&gt;&lt;a href=&quot;/?cssfile=/001/001.css&amp;amp;page=1&quot; title=&quot;View next set of designs. AccessKey: n&quot; accesskey=&quot;n&quot;&gt;&lt;span class=&quot;accesskey&quot;&gt;n&lt;/span&gt;ext designs &amp;raquo;&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;http://www.mezzoblue.com/zengarden/alldesigns/&quot; title=&quot;View every submission to the Zen Garden. AccessKey: w&quot; accesskey=&quot;w&quot;&gt;Vie&lt;span class=&quot;accesskey&quot;&gt;w&lt;/span&gt; All Designs&lt;/a&gt;&lt;/li&gt;
				&lt;/ul&gt;
			&lt;/div&gt;

			&lt;div id=&quot;lresources&quot;&gt;
				&lt;h3 class=&quot;resources&quot;&gt;&lt;span&gt;Resources:&lt;/span&gt;&lt;/h3&gt;
				&lt;ul&gt;
					&lt;li&gt;&lt;a href=&quot;/001/001.css&quot; title=&quot;View the source CSS file for the currently-viewed design, AccessKey: v&quot; accesskey=&quot;v&quot;&gt;&lt;span class=&quot;accesskey&quot;&gt;V&lt;/span&gt;iew This Design&amp;#8217;s &lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt;&lt;/a&gt;&lt;/li&gt;					&lt;li&gt;&lt;a href=&quot;http://www.mezzoblue.com/zengarden/resources/&quot; title=&quot;Links to great sites with information on using CSS. AccessKey: r&quot; accesskey=&quot;r&quot;&gt;&lt;acronym title=&quot;Cascading Style Sheets&quot;&gt;CSS&lt;/acronym&gt; &lt;span class=&quot;accesskey&quot;&gt;R&lt;/span&gt;esources&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;http://www.mezzoblue.com/zengarden/faq/&quot; title=&quot;A list of Frequently Asked Questions about the Zen Garden. AccessKey: q&quot; accesskey=&quot;q&quot;&gt;&lt;acronym title=&quot;Frequently Asked Questions&quot;&gt;FA&lt;span class=&quot;accesskey&quot;&gt;Q&lt;/span&gt;&lt;/acronym&gt;&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;http://www.mezzoblue.com/zengarden/submit/&quot; title=&quot;Send in your own CSS file. AccessKey: s&quot; accesskey=&quot;s&quot;&gt;&lt;span class=&quot;accesskey&quot;&gt;S&lt;/span&gt;ubmit a Design&lt;/a&gt;&lt;/li&gt;
					&lt;li&gt;&lt;a href=&quot;http://www.mezzoblue.com/zengarden/translations/&quot; title=&quot;View translated versions of this page. AccessKey: t&quot; accesskey=&quot;t&quot;&gt;&lt;span class=&quot;accesskey&quot;&gt;T&lt;/span&gt;ranslations&lt;/a&gt;&lt;/li&gt;
				&lt;/ul&gt;
			&lt;/div&gt;
		&lt;/div&gt;
	&lt;/div&gt;

&lt;/div&gt;

&lt;!-- These extra divs/spans may be used as catch-alls to add extra imagery. --&gt;
&lt;div id=&quot;extraDiv1&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div id=&quot;extraDiv2&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div id=&quot;extraDiv3&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;script type=&quot;text/javascript&quot;&gt;
function removeSheets(){
  if(document.styleSheets){
    var c = document.styleSheets.length;
    for(var i=0;i&lt;c;i++){
		document.styleSheets[i].disabled=true;
    }
  }
  findNamedElements();
}
function findNamedElements(){
	allElements = document.getElementsByTagName(&#039;*&#039;);
	for(i=0;i&lt;allElements.length;i++){
		allElements[i].style.cssText = &#039;&#039;;
		if(allElements[i].id){
			allElements[i].style.position = &#039;relative&#039;;
			allElements[i].style.border = &#039;1px solid silver&#039;;
			allElements[i].style.padding = &#039;20px&#039;;
			allElements[i].style.margin = &#039;10px&#039;;
			p=document.createElement(&#039;p&#039;);
			sometext = document.createTextNode(allElements[i].id);
			p.appendChild(sometext);
			p.className = &#039;cartel&#039;;
			allElements[i].appendChild(p);
		}
	}
	if (window.innerWidth){
		var styleText = &#039;.cartel{border:1px dashed red;position:absolute;top:0;left:0;padding:2px;background-color:#fff}&#039;;
		var head=document.getElementsByTagName(&quot;head&quot;)[0];
		var styleNode = document.createElement(&quot;style&quot;);
		styleNode.appendChild(document.createTextNode(styleText));
		head.appendChild(styleNode);
	}else{
		var newStyle = document.createStyleSheet();
		newStyle.addRule(&#039;.cartel&#039;,&#039;border:1px dashed red;position:absolute;top:0;left:0;padding:2px;background-color:#fff&#039;);
	}
}
removeSheets();
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
</div>
<p class="toggle_code"><a href="/uploads/code/csszengarden.css" onclick="jQuery('#code_7').toggle('slow');return false">csszengarden.css</a></p>
<div  style="display:none" id="code_7">
<pre class="brush: css">@import url(zero.css);body{text-align:center;}#css-zen-garden{

}
#container{

}
#intro{

}
#pageHeader{

}
#quickSummary{

}
#preamble{

}
#supportingText{

}
#explanation{

}
#participation{

}
#benefits{

}
#requirements{

}
#footer{

}
#linkList{

}
#linkList2{

}
#lselect{

}
#larchives{

}
#lresources{

}
#extraDiv1{

}
#extraDiv2{

}
#extraDiv3{

}
.supportingText{

}
.benefits{

}
.extraDiv3{

}
.extraDiv2{

}
.lselect{

}
.footer{

}
.larchives{

}
.lresources{

}
.linkList{

}
.intro{

}
.preamble{

}
.container{

}
.requirements{

}
.css-zen-garden{

}
.quickSummary{

}
.explanation{

}
.pageHeader{

}
.extraDiv1{

}
.linkList2{

}
.participation{

}
</pre>
</div>
<p>Something like this. Click to expand(PNG 239KB):<br />
<a href="/uploads/stylizator/stylizator.large.png"><img src="/uploads/stylizator/stylizator.large_p.png" alt="" /></a></p>
<h2>The code</h2>
<p>Download <a href="/uploads/stylizator/stylizator.zip">this file</a>, unzip it in the same folder and call it via &gt;perl stylizator.pl yourhtmlfile.html<br />
It&#8217;ll create a file named yourhtmlfile.css and another one called yourhtmlfile.structure.html</p>
<h2>Further work</h2>
<p>There&#8217;s so much to be done. It could be possible to create a firefox extension that gives us the same visual result at the press of a button, or a system that keeps only structural CSS, discarding type and colors. The regular expressions could be better, too, to allow not-so-well-defined documents.  If you have any further questions, drop me a line to <a href="mailto:david@corunet.com">david@corunet.com</a>.</p>
<p>Have fun.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.corunet.com/automatic-css-the-stylizator/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Three column layout with full page height</title>
		<link>http://blog.corunet.com/three-column-layout-with-full-page-height/</link>
		<comments>http://blog.corunet.com/three-column-layout-with-full-page-height/#comments</comments>
		<pubDate>Thu, 14 Jun 2007 19:36:56 +0000</pubDate>
		<dc:creator>David Pardo</dc:creator>
				<category><![CDATA[CSS and Javascript]]></category>
		<category><![CDATA[English]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[javascript]]></category>

		<guid isPermaLink="false">http://blog.corunet.com/english/three-column-layout-with-full-page-height</guid>
		<description><![CDATA[
Sometimes I wish I could do things that were easily done with table-based layouts but quite hard using just CSS.
Following a couple of posts in a CSS related Spanish mailing list (Ovillo),  a guy called Zafonic showed me how to use negative margins and positive paddings to make equal sized columns. With a couple [...]]]></description>
			<content:encoded><![CDATA[<p><img class="size-thumbnail wp-image-90 alignleft" title="3" src="http://blog.corunet.com/wp-content/uploads/2007/06/3-150x150.png" alt="The final result" width="150" height="150" /></p>
<p>Sometimes I wish I could do things that were easily done with table-based layouts but quite hard using just CSS.<br />
Following a couple of posts in a CSS related Spanish mailing list (Ovillo),  a guy called Zafonic showed me how to use negative margins and positive paddings to make equal sized columns. With a couple javascript functions, I patched it to fill the full browser window. Let&#8217;s see how&#8230;<br />
<strong>Update:</strong> Error in code solved. Thanks to the testers<span id="more-64"></span></p>
<h2>The problem</h2>
<p>We&#8217;re going to write a three-column plus footer layout that covers all the browser space. The requisites are the following:</p>
<ul>
<li>The three columns will have different background colors</li>
<li>We&#8217;re not going to use background images (so, no <a href="http://alistapart.com/articles/fauxcolumns/" onclick="pageTracker._trackPageview('/outgoing/alistapart.com/articles/fauxcolumns/?referer=');">faux-columns</a>)</li>
<li>We want the page to cover the full height of the browser. So, the footer will be at the bottom of the screen.</li>
<li>The layout has to degrade gracefully for javascript disabled browsers</li>
<li>All the code has to be valid and accesible</li>
</ul>
<h2>The beginning</h2>
<p>The first part is simple. We need some html that defines three columns and a footer. Lets wrap the three columns within a div (called, by the way, container) and call the three columns col1, col2 and col3. The html code will be something like:</p>
<p class="toggle_code"><a href="/uploads/code/body.html" onclick="jQuery('#code_0').toggle('slow');return false">body.html</a></p>
<div  style="display:none" id="code_0">
<pre class="brush: html">&lt;body&gt;
	&lt;div id=&quot;container&quot;&gt;
		&lt;div id=&quot;col1&quot;&gt;
			first column&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;
		&lt;/div&gt;
		&lt;div id=&quot;col2&quot;&gt;
			second column&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-
		&lt;/div&gt;
		&lt;div id=&quot;col3&quot;&gt;
			third column&lt;br /&gt;-&lt;br /&gt;-
		&lt;/div&gt;
	&lt;/div&gt;
	 &lt;div id=&quot;footer&quot;&gt;footer&lt;br /&gt;-&lt;/div&gt;
&lt;/body&gt;
</pre>
</div>
<p>We need to give the columns a width and float them to the left. The css would be:</p>
<p class="toggle_code"><a href="/uploads/code/first.css" onclick="jQuery('#code_1').toggle('slow');return false">first.css</a></p>
<div  style="display:none" id="code_1">
<pre class="brush: css">body{
	margin:0 auto;
}
#col1{
	background-color:red;
	width:20%;
	float:left;
}
#col2{
	background-color:blue;
	width:60%;
	float:left;
}
#col3{
	background-color:yellow;
	width:20%;
	float:left;
}
#footer{
	height:40px;
	clear:both;
	background-color:black;
	color:white;
	width:100%;
}
</pre>
</div>
<p>So, we already have a three column layout, but each column has a diferent height and the footer is far from the bottom:</p>
<p><img src="/uploads/threecolumns/1.png" alt="first version" /></p>
<h2>Negative margins to the rescue</h2>
<p>As I already told you, I had&#8217;t heard about creating layouts using negative margins (I should have read <a href="http://alistapart.com/articles/negativemargins" onclick="pageTracker._trackPageview('/outgoing/alistapart.com/articles/negativemargins?referer=');">this article from A List Apart</a> some time ago). In a nutshell, it says that you can use a large negative margin and the opposite positive padding to &#8220;make room for the columns&#8221;. I recommend to read it if you haven&#8217;t, it&#8217;s very useful.<br />
So, let&#8217;s go on the code. We&#8217;re going to add a -5000 pixel margin-bottom and a 5000 pixel padding-bottom to each column:</p>
<p class="toggle_code"><a href="/uploads/code/second.css" onclick="jQuery('#code_2').toggle('slow');return false">second.css</a></p>
<div  style="display:none" id="code_2">
<pre class="brush: css">body{
	margin:0 auto;
}
#col1{
	background-color:red;
	width:20%;
	float:left;
	margin-bottom:-5000px;
 	padding-bottom:5000px;
}
#col2{
	background-color:blue;
	width:60%;
	float:left;
	margin-bottom:-5000px;
 	padding-bottom:5000px;
}
#col3{
	background-color:yellow;
	width:20%;
	float:left;
	margin-bottom:-5000px;
 	padding-bottom:5000px;
}
#footer{
	height:40px;
	clear:both;
	background-color:black;
	color:white;
	width:100%;
	margin-bottom:-5000px;
 	padding-bottom:5000px;
}
</pre>
</div>
<p>This way we get same height columns. This looks much better:</p>
<p><img src="/uploads/threecolumns/2.png" alt="second version" /></p>
<p>but we don&#8217;t have 100% height yet. Internet explorer supports 100% height but all the other browsers don&#8217;t. So, we&#8217;ll need to use javascript to solve it:</p>
<h2>Filling the screen &#8211; Here comes Javascript</h2>
<p>The first thing we need is to find the height of the browser window. I use a small javascript function that takes into account the diferent behaviours of the different browsers:</p>
<p class="toggle_code"><a href="/uploads/code/height.js" onclick="jQuery('#code_3').toggle('slow');return false">height.js</a></p>
<div  style="display:none" id="code_3">
<pre class="brush: js">function windowHeight(){
	var alto= 0;
	if( typeof( window.innerWidth ) == &#039;number&#039; ) {
		alto= window.innerHeight;
	} else if( document.documentElement &amp;&amp; ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
		alto= document.documentElement.clientHeight;
	} else if( document.body &amp;&amp; ( document.body.clientWidth || document.body.clientHeight ) ) {
		alto= document.body.clientHeight;
	}
	return alto;
}
</pre>
</div>
<p>This function returns the height of the browser window. It does one thing and does it well&#8230; Using this new knokledge, we can set the height of the columns, making them the browser&#8217;s window height minus the footer, to fill the screen completely. The other function we&#8217;ll need is:</p>
<p class="toggle_code"><a href="/uploads/code/fill.js" onclick="jQuery('#code_4').toggle('slow');return false">fill.js</a></p>
<div  style="display:none" id="code_4">
<pre class="brush: js">function fillthescreen(){
	winH = windowHeight(); //This returns the screen heigth
	heightNeeded=winH-40; //We need to substract the footer height
	if( typeof( window.innerWidth ) != &#039;number&#039; ) { //Explorer doesn&#039;t recognize minHeight
		document.getElementById(&#039;co11&#039;).style.height=heightNeeded+&#039;px&#039;; //So, we use height (and explroer bug)
	}
	document.getElementById(&#039;col1&#039;).style.minHeight=heightNeeded+&#039;px&#039;; //For every other browser, we use minHeight
}
</pre>
</div>
<p>This function works by calling the last one (windowHeight) and substracting the footer size. If we&#8217;re in Explorer, we use style.height to modify the height of the columns and in all other browsers, minHeight, since Explorer doesn&#8217;t support that attribute. We&#8217;re going to call that function via onload.<br />
And we get this:</p>
<p><img src="/uploads/threecolumns/3.png" alt="third version" /></p>
<p>If the window is not tall enough to fit the column height, the page behaves normally:</p>
<p><img src="/uploads/threecolumns/4.png" alt="small screen" /></p>
<h2>The full code</h2>
<p>Putting all together, this is what we get:</p>
<p class="toggle_code"><a href="/uploads/code/3.html" onclick="jQuery('#code_5').toggle('slow');return false">3.html</a></p>
<div  style="display:none" id="code_5">
<pre class="brush: html">&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Strict//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&quot;&gt;
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; xml:lang=&quot;en&quot;&gt;
&lt;head&gt;
&lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=utf-8&quot; /&gt;
&lt;title&gt;Demo three columns&lt;/title&gt;
&lt;script type=&quot;text/javascript&quot;&gt;
	function fillthescreen(){
		winH = windowHeight(); //This returns the screen heigth
		heightNeeded=winH-40; //We need to substract the footer height
		if( typeof( window.innerWidth ) != &#039;number&#039; ) { //Explorer doesn&#039;t recognize minHeight
			document.getElementById(&#039;col1&#039;).style.height=heightNeeded+&#039;px&#039;; //So, we use height (and explroer bug)
		}
		document.getElementById(&#039;col1&#039;).style.minHeight=heightNeeded+&#039;px&#039;; //For every other browser, we use minHeight
	}

	function windowHeight(){
		var alto= 0;
		if( typeof( window.innerWidth ) == &#039;number&#039; ) {
			alto= window.innerHeight;
		} else if( document.documentElement &amp;&amp; ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
			alto= document.documentElement.clientHeight;
		} else if( document.body &amp;&amp; ( document.body.clientWidth || document.body.clientHeight ) ) {
			alto= document.body.clientHeight;
		}
		return alto;
	}
&lt;/script&gt;
&lt;style type=&quot;text/css&quot;&gt;
body{
	margin:0 auto;
}
#container{
 overflow:hidden;
}
#col1{
 background-color:red;
 width:20%;
 float:left;
margin-bottom:-5000px;
 padding-bottom:5000px;
 }
#col2{
 background-color:blue;
 width:60%;
 float:left;
margin-bottom:-5000px;
 padding-bottom:5000px;
 }
#col3{
 background-color:yellow;
 width:20%;
 float:left;
margin-bottom:-5000px;
 padding-bottom:5000px;
 }
#footer{
	height:40px;
 clear:both;
 float:none;
 background-color:black;
 color:white;
 width:100%;
}

&lt;/style&gt;
&lt;/head&gt;

&lt;body onload=&quot;fillthescreen()&quot;&gt;
	&lt;div id=&quot;container&quot;&gt;
		&lt;div id=&quot;col1&quot;&gt;
			first column&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;
		&lt;/div&gt;
		&lt;div id=&quot;col2&quot;&gt;
			second column&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-&lt;br /&gt;-
		&lt;/div&gt;
		&lt;div id=&quot;col3&quot;&gt;
			third column&lt;br /&gt;-&lt;br /&gt;-
		&lt;/div&gt;
	&lt;/div&gt;
	 &lt;div id=&quot;footer&quot;&gt;footer&lt;br /&gt;-&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
</div>
<p>If you use it in a production environment, it&#8217;d be wise to split the javascript and the CSS into their own files, and review all the code, since it&#8217;s done in a quick and dirty way.</p>
<p>You can download the code using <a href="/uploads/threecolumns/3.zip">this link</a>. People without javascript don&#8217;t get the full height but the site is equally usable and visually pleasant. I you have any further questions or just want to talk, drop me a line to <a href="maito:david@corunet.com">david@corunet.com</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.corunet.com/three-column-layout-with-full-page-height/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<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>
