Data-Drive Your Site with D3!

luzid vizualizes live data that changes over time. Therefore it is essential for us to generate and change visual artifacts based on a series of data points. This article shows how to create a simple HTML based tile visualization that is bound to live data using the popular d3 library.

A Simplistic Approach to Visualize Live Data

On the screenshot you can see a simple luzid visualization. It displays a tile for each of your dashboards with a recent dashboard screenshot as background image.

The data series (dashboards) to be visualized is modeled as a list of dashboard objects:

[{ id: 1, title: "Demo 1", screenshot: "http://luzid.com/screenshot1.png" },
 { id: 2, title: "Demo 2", screenshot: "http://luzid.com/screenshot2.png" }]

We could implement a simple Javascript that creates a div element for each item in the list. This approach, while easy to get started, gets more and more complex once we start to update, add or remove tiles whenever our data changes. And it gets even more complex when we start to add some fancy animations to our visualization. Let’s call this approach simplisticĀ since it’s actually straight forward to get started, but becomes too simple pretty soon.

Making the Simplistic Approach Simple using D3

Fortunately, d3 provides a good way to bind a series of data points to arbitrary DOM elements, which is very powerful and stays simple even if you start to add new stuff (more to come in future posts). The basic principle is to select a set of DOM elements (e.g. all div elements of a container) and assign a single data point to each of the elements. If there is no DOM element bound to a data point, then create a new element (enter). If there is a DOM element bound to an element which is not in our data series anymore, remove the element (exit). And finally, if there is an element bound to a data point, update the element:

function renderTiles(data) {

 // the return value of this function is used to uniquely identify a datum in
 // a data series. d is a single datum taken from the data series.
 var id = function(d) { return d.id };

// step 1: bind our data series
var tiles = d3.select("#container").selectAll(".tile").data(data, id);

// step 2: create a new tile for each datum
tiles.enter().append("div").classed("tile", true);

// step 3: remove tiles that are not in our data series anymore
tiles.exit().remove();

// step 4: update all tiles (existing and new ones)
tiles.style("background-image", function(d) { return "url(" + d.screenshot + ")" });

// render tiles for a given data series
renderTiles(getData());
// and update the tiles each time the data series changes
onDataChanged(function() { renderTiles(getData()) });

Before you continue, look at thisĀ fiddle and see this example in action (you can also experiment with the code there later on).

Behind the Curtain

So, what’s happening up here? Actually it’s just the simple set logic as explained above. In step 1, we select the set of DOM elements that we want to bind our data to (d3.select("#container").selectAll(".tile") is equal to the CSS Selector #container .tile). When we call this function for the first time, the set returned by selectAll will be empty. This will, however, change in step 2, which identifies all data points of our data series that have not been bound to an element in our set of DOM elements. For each data point a new div element will be created and appended to the container element. Additionally, each created div element will be given the class tile. For the data series shown above, the DOM looks like this, once we’ve executed step 2:

<div id="container">
  <div class="tile"></div> <!-- bound datum is: { id: 1, title: "Demo 1", ... } -->
  <div class="tile"></div> <!-- bound datum is: { id: 2, title: "Demo 2", ... } -->
</div>

I guess, by now it should be pretty clear to you what happens in step 3. You’re right – it identifies all DOM elements (selected in step 1), which are bound to a datum that is not in our data series anymore (e.g. imagine a datum { id: 3, … }) and removes those elements from the DOM. Finally, we want to execute some actions that must be done for all remaining (new and existing) DOM elements. In our case we will set the background-image CSS property to the URL provided by each datum. The given function is executed for each bound datum and the return value is applied to the corresponding DOM element. The DOM looks like this once we’ve executed step 4:

<div id="container">
  <div class="tile" style="background-image: url(http://luzid.com/screenshot1.png)"></div>
  <div class="tile" style="background-image: url(http://luzid.com/screenshot2.png)"></div>
</div>

Done! You can now execute the renderTile function as often as you want, with a data series of arbitrary size.

There’s more to come …

D3 and luzid can do much more for you (the fiddle e.g. uses ordering by title and adds a title span to each tile)! Next time we will enhance this simple example in order to look and behave a bit fancier by adding animations. See you in a while!

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>