Add visual flair with HTML5 heatmaps

Heat maps are an effective method for showing geographical or temporal data in a very visual and easily-grasped manner – and now there’s an easy-to-use JavaScript implementation.

Heatmap.js is the brainchild of Patrick Wied (patrick-wied.at), which began life as a JS1K competition entry and is now open source and available from GitHub (github.com/pa7/heatmap.js). It has since been used by Rockstar Games (of Grand Theft Auto fame) and a number of visualisation projects featured at bit.ly/171zlVal as well as gaining the interest of over 1,100 GitHub users.

But heat maps don’t have to be limited to traditional maps. In this tutorial we’ll use heatmap.js in two ways. First, to create a user experience tool which will track where the user interacts with the page with their mouse. When they navigate to a different page we’ll save an image to a server that could then layer together all heat maps of a single page and see which elements are interacted with the most.

We’ll also create a heat map visualisation with Leaflet (leafletjs.com), which will overlay a dataset over the UK to demonstrate exactly how versatile heatmap.js is.

Heat map Factory

Heatmap.js exposes itself as h337 (as in 1337, but heat) to the window. To make a new heat map out of the entire page we’ll attach it to the body element and make it invisible by default (although for debugging it’s useful to set this to true). The radius is how big a single point is in pixels.

Set up variables

Next we’ll set up a few variables that will help us keep track of the user’s movements. lastCoords will contain the X and Y co-ordinates when the mouse moves and when it’s still. mouseMove and Over are flags to make sure we don’t store results when the mouse is idle (e.g. if the user is hovering over something).

Click event

We’ll start by adding a new point when the user clicks. This is probably the most common thing that you’ll do with Heatmap.js and so it makes it very easy to accomplish. Simply use its utility method for getting the mouse’s position from the event and then use its addDataPoint method to add the X and Y co-ordinates.

Simulating click

Next we’ll write a method that will simulate the click event and be used when the user hovers over something. Again, it takes up to three parameters: X, Y, and a value, but for this we’re only interested in the X and Y co-ordinates but we’ll read the last known co-ordinates of the mouse from the lastCoords array.

Being anti idle

To call this simulateEv method we’ll check for when a user hovers over a single point, if the mouse triggers the mouseover event and it’s not moving then we push a new data point to the heat map every second that this occurs – this builds a stronger point, turning it from blue to red.
001 var antiIdle = function() {
002 if (mouseOver && !mouseMove && lastCoords && !timer) {
003 timer = setInterval(simulateEv, 1000);
004 }
005 };
006 (function(fn) {
007 setInterval(fn, 1000);
008 }(antiIdle));

Mouse-out event

When the user’s mouse no longer hovers on that element we clear the timer, if there is one, and set the mouseOver flag to false so that the antiIdle function will stop adding data points to the last co-ordinate every second. By using addEventListener we don’t conflict with other functions listening to the same event.

Mouse-move event

We now have heat map data points being added every time the user clicks, as well as when they hover. Next we’ll add an event for when the user moves their mouse. This adds a data point with the mousePosition and addDataPoints methods we used earlier and sets the lastCoords array which antiIdle uses.

Save function

That’s all it takes to make a functional UX heat map and see where users are interacting with the page. To increase its usefulness we can then export the heat map to an image and POST that to a server to be saved. We’ll pass the site’s title so that it can be identified later on.

Adding buttons

When user testing you could include some administrator buttons on the page to see the heat map being produced and manually save it if the user does something noteworthy. It is probably better to do this remotely though, to avoid disrupting the user’s flow. Still, for the purposes of this tutorial let’s include some.

Layout CSS

We’ll fix the admin buttons to the bottom-right of the page so that they don’t interrupt the page’s layout and make them semi-transparent, only appearing fully when they’re hovered over. As we have our CSS open we’ll also set the height of the map that we’ll make shortly.

Adding events

Now that we’ve written the save function we can trigger it when the page unloads, ie when the user goes to another page. Also we’ll add a manual save button as well as a toggle button for the heat map’s visibility. This is done using another helper method in Heatmap.js, toggleDisplay.

Decode image data

We’re POSTing to the server but not actually handling it, so the following short snippet of PHP gets the POST data, decodes the Base64 encoded string and then converts it to a PNG file. You could then use another tool (for example, something like FFmpeg) to layer images from the same page to see which elements users are interacting with most often on the page.

Save image file

With our image file ready to save, we’ll give it a name. We POSTed the site’s title so we’ll use that to identify it and a timestamp (time()) so that we can them proceed to list them by name and still see them chronologically. file_put_contents is an easy way to open a file and write its contents – in this case, the Base64 string.

Map HTML

We’ve learned how we can use Heatmap.js to track user’s movements in real-time and save that data as an image. Next, we’ll visualise static data on a map, this could be anything from how many stores you have in separate locations, to dry statistical data. The ID on the map <div> will help Leaflet initialise it.

Script includes

Heatmap.js has built–in support for Leaflet, it’s a great alternative to Google Maps and its implementation seems to work better than the Google Maps integration that comes with Heatmap.js. We also include QuadTree.js, which is a way of subdividing datasets to make it efficient at each zoom level.

New tile layer

Leaflet’s layers are fully customisable (like Google Maps satellite and road in hybrid mode) so we define which we’ll use as the default base layer and build on top of that. It gets its map data from the OpenStreetMap project and we’ll specify a maximum zoom level as it can be unresponsive with lots of overlapping points in some cases.

Heat map layer

We then add our second layer: the heat map. This time we’ll specify a much larger radius and set the absolute property to true. This means that the data points stay relative to the maximum value in the dataset while opacity determines how much of the map beneath is visible.

Gradient object

The gradient object contains key/value pairs that relate to how much weight an area has (from 0 to 1) and a CSS colour value. We’ll customise this to be greyscale (also known as the blackbody spectrum) as colours can lead to perception of gradients that aren’t actually present to avoid confusion with the UX tool.

Fetch and set

We’re going to visualise a traffic dataset from data.gov.uk – you can find the JSON file (data.json) on the resource disc included with this issue. The heat map layer that we created makes it extremely simple to set the map’s dataset simply by calling the setData method. This computes where the data relates to on the map.

Expected JSON

The dataset is simple, our example one has an array of objects that have a longitude, latitude, and value. If your dataset doesn’t have a value, don’t worry as it defaults to 1. The maximum value in our dataset is nine so we’ll set that. Thanks to QuadTree, heatmap.js is able to deftly handle large datasets – ours has 7,981 points.

Init Leaflet map

Putting it all together we initialise a new Leaflet map and centre it on the middle of the UK – we’ll also set a zoom level that fits mainland UK in view and then apply the two layers (base and then the heat map). ‘Map’ is the ID of the element that it’ll render itself inside.

Conclusion

We’ve shown how you can use heatmap.js as a useful UX tool and in order to visualise a dataset. It does all of the heavy lifting for you while being versatile enough to be customised and applied to a variety of scenarios.