Tag Archive for 'treemap'

DOM vs. Canvas TreeMaps

As you may have seen I’m back from vacations and trying to organize a little bit the code for the next version of the JavaScript InfoVis Toolkit, which is not there yet; although I did push the new visualizations over to GitHub so you can play with them :)

I also created a new project at GitHub that’s called DOMTreeMap. DOMTreeMap is a robust, library agnostic, JavaScript/HTML/CSS TreeMap library that allows you to easily create Squarified, Strip and SliceAndDice TreeMap layouts by using JSON input data. The only global object created by this library is TM and it consists on four main sub-objects/classes:

  • TM.Util contains useful JSON Tree manipulation methods.
  • TM.Squarified creates Squarified TreeMap tilings.
  • TM.Strip creates Strip like tilings.
  • TM.SliceAndDice creates Slice and Dice TreeMap tilings.

Each tiling algorithm has its pros and cons. The main criteria for determining if a tiling algorithm is good is to check for the aspect ratio of nodes, check if the result is ordered or not and check for stability (i.e whether the nodes maintain their relative positions when the TreeMap is being navigated).

Here’s a picture describing these points for the Strip, Squarified and SliceAndDice tiling algorithms:

As you may have noticed, DOMTreeMap is a standalone version of the JIT TreeMap component.

So why do this, then?

Well, I decided that for the next version of the library TreeMaps are going to be Canvas based. I’m already adapting the TreeMap tiling algorithms to render on Canvas and also plugging the rest of the TreeMap visualizations to an abstract Graph class that is also used by all other visualizations so I can use a lot of library code related to Tree Operations, Tree Animations, etc.
Also, not all TreeMap visualizations render nodes as rectangles. Some TreeMap implementations are based on Voronoi Tessellations for example:

So in order to maintain a more “abstract” notion of TreeMaps I think that having Canvas based TreeMaps would be a good idea. Anyway, now I’m working on adapting the code to be able to render TreeMaps on canvas, I must say that performance-wise things are looking promising:

You are welcome to participate on the DOMTreeMap project which will grow as a standalone solution for drawing TreeMaps solely based on HTML, CSS and JavaScript.

Don’t forget that if you want to know more about this project or the JavaScript InfoVis Toolkit you can follow me on Twitter or GitHub!

Cushion Treemaps

Remember Treemaps?

There was a thread at the Google Group for the JIT asking for Gradients in Treemaps.

Actually they’re called cushion treemaps, and they have been created by Ph.Ds Jarke J. van Wijk and Huub van de Wetering. Cushion Treemaps have been used in successful applications like SequoiaView and companies like MagnaView are building very interesting visualizations with cushion treemaps.

The paper is quite interesting: cushion treemaps were created by using shading to show a tree’s structure:

“How can we use shading to show the tree structure? The human visual system is trained to interpret variations in shade as illuminated surfaces [6]. Hence, we can answer the question by constructing a surface which shape encodes the tree structure.”

Shadowing is created by adding bumps to rectangles.

In the JavaScript InfoVis Toolkit, this can be done by overriding the leafBox method of the Treemap class. This method renders a leaf node (nodes which are generally colored in the Treemap).
By adding an image of a radial gradient inside that div we can emulate cushion treemaps.

Instead of creating a new class that extends TM and overrides that method, we can take advantage of JavaScript’s object mutability feature and re-implement the method in the same class.


    TM.Squarified.implement({
       leafBox: function(json, coord) {
        var config = this.config;
        var backgroundColor = config.Color.allow && this.setColor(json),
        offst = config.offset,
        width = coord.width - offst,
        height = coord.height - offst;
        var c = {
         'top': (offst / 2) + "px",
         'height':height + "px",
         'width': width + "px",
         'left': (offst / 2) + "px"
        };
        if(backgroundColor) c['background-color'] = backgroundColor;
       //Add an image to our leaf node to create a cushion treemap.
        var img = "<img src='gradient.png' style='position:absolute; z-index:2; top:0; left:0; width:" + c.width + "; height:"+ c.height +"'; />"; 

        return "<div class='leaf' style=" + this.toStyle(c) + ">" + img + json.name + "</div>";
       }
    });

And that’s it, now we have Squarified Cushion Treemaps. I must say that I love cushion treemaps, they look a lot more cool than simple treemaps.

I made a small POC where you can enable/disable the cushion feature, among other interesting things:

cushion treemap

Treemap quick tutorial

This tutorial requires you to have read Feeding JSON tree structures to the JIT and on controllers first.

Hi, this is going to be a quick tutorial on how to set the treemap up and running.

Note: You could change all calls to TM.Squarified to TM.SliceAndDice or TM.Strip and it should also work.

We are going to work with this tree JSON structure:

var json = {"id":"node02",
 "name":"0.2",
 "data":[
    {"key":"key1",
     "value":195},
    {"key":"key2",
     "value":5}],
 "children":[
    {"id":"node13",
     "name":"1.3",
     "data":[
        {"key":"key1",
         "value":23},
        {"key":"key2",
         "value":8}],
     "children":[
        {"id":"node24",
         "name":"2.4",
         "data":[
            {"key":"key1",
             "value":6},
            {"key":"key2",
             "value":-75}],
         "children":[]},
        {"id":"node25",
         "name":"2.5",
         "data":[
            {"key":"key1",
             "value":9},
            {"key":"key2",
             "value":-48}],
         "children":[]},
        {"id":"node26",
         "name":"2.6",
         "data":[
            {"key":"key1",
             "value":1},
            {"key":"key2",
             "value":-1}],
         "children":[]},
        {"id":"node27",
         "name":"2.7",
         "data":[
            {"key":"key1",
             "value":7},
            {"key":"key2",
             "value":25}],
         "children":[]}]},
    {"id":"node18",
     "name":"1.8",
     "data":[
        {"key":"key1",
         "value":17},
        {"key":"key2",
         "value":28}],
     "children":[
        {"id":"node29",
         "name":"2.9",
         "data":[
            {"key":"key1",
             "value":8},
            {"key":"key2",
             "value":-28}],
         "children":[]},
        {"id":"node210",
         "name":"2.10",
         "data":[
            {"key":"key1",
             "value":9},
            {"key":"key2",
             "value":-83}],
         "children":[]}]},
    {"id":"node111",
     "name":"1.11",
     "data":[
        {"key":"key1",
         "value":25},
        {"key":"key2",
         "value":-82}],
     "children":[
        {"id":"node212",
         "name":"2.12",
         "data":[
            {"key":"key1",
             "value":8},
            {"key":"key2",
             "value":-27}],
         "children":[]},
        {"id":"node213",
         "name":"2.13",
         "data":[
            {"key":"key1",
             "value":3},
            {"key":"key2",
             "value":-80}],
         "children":[]},
        {"id":"node214",
         "name":"2.14",
         "data":[
            {"key":"key1",
             "value":7},
            {"key":"key2",
             "value":-73}],
         "children":[]},
        {"id":"node215",
         "name":"2.15",
         "data":[
            {"key":"key1",
             "value":7},
            {"key":"key2",
             "value":26}],
         "children":[]}]},
    {"id":"node116",
     "name":"1.16",
     "data":[
        {"key":"key1",
         "value":17},
        {"key":"key2",
         "value":91}],
     "children":[
        {"id":"node217",
         "name":"2.17",
         "data":[
            {"key":"key1",
             "value":7},
            {"key":"key2",
             "value":48}],
         "children":[]},
        {"id":"node218",
         "name":"2.18",
         "data":[
            {"key":"key1",
             "value":10},
            {"key":"key2",
             "value":-86}],
         "children":[]}]},
    {"id":"node119",
     "name":"1.19",
     "data":[
        {"key":"key1",
         "value":52},
        {"key":"key2",
         "value":-77}],
     "children":[
        {"id":"node220",
         "name":"2.20",
         "data":[
            {"key":"key1",
             "value":8},
            {"key":"key2",
             "value":64}],
         "children":[]},
        {"id":"node221",
         "name":"2.21",
         "data":[
            {"key":"key1",
             "value":5},
            {"key":"key2",
             "value":84}],
         "children":[]},
        {"id":"node222",
         "name":"2.22",
         "data":[
            {"key":"key1",
             "value":6},
            {"key":"key2",
             "value":81}],
         "children":[]},
        {"id":"node223",
         "name":"2.23",
         "data":[
            {"key":"key1",
             "value":1},
            {"key":"key2",
             "value":25}],
         "children":[]},
        {"id":"node224",
         "name":"2.24",
         "data":[
            {"key":"key1",
             "value":4},
            {"key":"key2",
             "value":18}],
         "children":[]},
        {"id":"node225",
         "name":"2.25",
         "data":[
            {"key":"key1",
             "value":10},
            {"key":"key2",
             "value":37}],
         "children":[]},
        {"id":"node226",
         "name":"2.26",
         "data":[
            {"key":"key1",
             "value":8},
            {"key":"key2",
             "value":83}],
         "children":[]},
        {"id":"node227",
         "name":"2.27",
         "data":[
            {"key":"key1",
             "value":10},
            {"key":"key2",
             "value":-62}],
         "children":[]}]},
    {"id":"node128",
     "name":"1.28",
     "data":[
        {"key":"key1",
         "value":37},
        {"key":"key2",
         "value":-40}],
     "children":[
        {"id":"node229",
         "name":"2.29",
         "data":[
            {"key":"key1",
             "value":8},
            {"key":"key2",
             "value":-67}],
         "children":[]},
        {"id":"node230",
         "name":"2.30",
         "data":[
            {"key":"key1",
             "value":8},
            {"key":"key2",
             "value":46}],
         "children":[]},
        {"id":"node231",
         "name":"2.31",
         "data":[
            {"key":"key1",
             "value":4},
            {"key":"key2",
             "value":-99}],
         "children":[]},
        {"id":"node232",
         "name":"2.32",
         "data":[
            {"key":"key1",
             "value":8},
            {"key":"key2",
             "value":-38}],
         "children":[]},
        {"id":"node233",
         "name":"2.33",
         "data":[
            {"key":"key1",
             "value":1},
            {"key":"key2",
             "value":-3}],
         "children":[]},
        {"id":"node234",
         "name":"2.34",
         "data":[
            {"key":"key1",
             "value":8},
            {"key":"key2",
             "value":82}],
         "children":[]}]},
    {"id":"node135",
     "name":"1.35",
     "data":[
        {"key":"key1",
         "value":24},
        {"key":"key2",
         "value":63}],
     "children":[
        {"id":"node236",
         "name":"2.36",
         "data":[
            {"key":"key1",
             "value":10},
            {"key":"key2",
             "value":8}],
         "children":[]},
        {"id":"node237",
         "name":"2.37",
         "data":[
            {"key":"key1",
             "value":8},
            {"key":"key2",
             "value":63}],
         "children":[]},
        {"id":"node238",
         "name":"2.38",
         "data":[
            {"key":"key1",
             "value":6},
            {"key":"key2",
             "value":46}],
         "children":[]}]}]};

Note: remember that the Treemap takes the first dataset object value to calculate the rectangles dimensions. That means that the values you setted on the first dataset object for all nodes must be coherent. Coherent means that, for example, if node A has nodes B and C as children, B having 3 as its first dataset object value and C having 2 as its first dataset value, then A must have 5 as its first dataset object value.

Put this HTML in your page:

<html>
	<head>

	<link type="text/css" rel="stylesheet" href="/static/css/example-treemap.css" />

	<script type="text/javascript" src="/static/js/mootools-1.2.js"></script>
	<script type="text/javascript" src="/static/js/treemap/Treemap.js" ></script>
	<script type="text/javascript" src="/static/js/example/example-treemap.js" ></script>

	</head>

	<body onload="init();">

<div id="infovis" />

	</body>
</html>

Note: You’ll probably have to change the paths to the css and javascript files.
Note2: This is the only visualization that requires Mootools 1.2+ to run. I use version 1.2 which comes packaged with the library (in the extras folder). You can also download it from here.

Now, create a treemap-example.css and put this code in it:

html, body {
	width:100%;
	height:100%;
	background-color:#222;
	text-align:center;
	overflow:hidden;
	font-size:10px;
	font-family:Verdana, Geneva, Arial, Helvetica, sans-serif;
}

#infovis {
	position:relative;
	width:900px;
	height:500px;
	margin:auto;
	background-color:#1D1D20;
}

#infovis div {
	position:absolute;
	overflow:hidden;
}

#infovis .content {
	background-color:#333;
	border:0px solid #111;
}

#infovis .head {
	height:12px;
	color:white;
	background-color:#444;
}

#infovis .head.in-path {
	background-color:#655;
}

#infovis .body {
	background-color:black;
}

#infovis .leaf {
	color:white;
	background-color:#111;
	display:table-cell;
	vertical-align:middle;
}

#infovis .over-leaf {
	border:1px solid #9FD4FF;
}

#infovis .over-content {
	background-color: #9FD4FF;
}

#infovis .over-head { /* ...boy i'm funny */
	background-color:#A4D9FF;
	color:black;
}

/*TOOLTIPS*/
.tool-tip {
	color: #fff;
	width: 139px;
	z-index: 13000;
	background-color: black;
}

.tip-title {
	font-weight: bold;
	font-size: 11px;
	margin: 0;
	color: #9FD4FF;
	padding: 8px 8px 4px;
	background-color: black;
}

.tip-text {
	font-size: 11px;
	padding: 4px 8px 8px;
	background-color: black;
}

Note: I won’t explain all CSS classes, they are pretty much self explainable. However, you must put a position:relative; in the infovis container (the visualization container), just as specified on the CSS stylesheet.

Finally, create an example-treemap.js file and put this code in it:

function init() {
	var json = //json data specified above...
	var tm = new TM.Squarified();
	tm.loadFromJSON(json);

}

You should see a treemap up and running.

Some notes:

  • It’s mandatory to put the width and height style properties on the main div container, just as specified on the CSS stylesheet.
  • You could change the main container id by setting the rootId property to whatever id you like in the controller object passed to the TM constructor.

Customizing the Treemap

Let’s add some colors!
In order to add colors you must know three things:

  • Colors are calculated by taking the second dataset object value. You must also know the range of this value. In this example the second value ranges between [-100, 100]. You can change these values by setting the Color.minValue and Color.maxValue parameters for the treemap controller.
  • You can also choose the color range for your visualization, this is done by setting the Color.minColorValue and Color.maxColorValue parameters. These properties take an array of 3 integers each describing an RGB value.
  • Finally, to enable treemap coloring you must set the property Color.allow to true.

So the init javascript function would now look like:

function init() {
  var json = //..json data
  var tm = new TM.Squarified({
    //main container id.
    rootId: 'infovis',
    //orientation
    orientation: "v",

     Color: {
        //Allow coloring
        allow: true,
        //Set min value and max value for
        //the second *dataset* object values.
        //Default's to -100 and 100.
        minValue: -100,
        maxValue: 100,
        //Set color range. Default's to reddish and
        //greenish. It takes an array of three
        //integers as R, G and B values.
        maxColorValue: [0, 255, 50],
        minColorValue: [255, 0, 50]
     }
  });
  tm.loadFromJSON(json);
}

You should see a colored treemap now.

Finally, let’s put nice tooltips! We’ll do this by using the Tip Mootools class.
First, we’ll define an onAfterCompute controller method to query for all nodes that will be displaying information (like .head and .leaf nodes) and assign the information to be displayed. To know more about this please check the docs for the Tips Mootools plug in
I also define some extra methods that convert the dataset to HTML.
I also allow tooltips by setting tips to true.

So the JavaScript file should now look like this:

function init() {
  var json = //...same json data as before...

  var tm = new TM.Squarified({
    //main container id.
    rootId: 'infovis',
    //orientation
    orientation: "v",
    //Allow tips for treemap
     tips: true,

     Color: {
        //Allow coloring
        allow: true,
        //Set min value and max value for
        //the second *dataset* object values.
        //Default's to -100 and 100.
        minValue: -100,
        maxValue: 100,
        //Set color range. Default's to reddish and
        //greenish. It takes an array of three
        //integers as R, G and B values.
        maxColorValue: [0, 255, 50],
        minColorValue: [255, 0, 50]
    },

    onAfterCompute: function() {
      var that = this, parent;
      $$('#infovis .leaf', '#infovis .head').each(function(elem, i) {
        //get the JSON tree node element having the same id
        //as the dom element queried and makeTip.
        if(p = elem.getParent()) {
          var sTree = TreeUtil.getSubtree(tm.tree, p.id);
          if(sTree) that.makeTip(elem, sTree);
        }
      });
    },
//Tooltip content is setted by setting the *title* of the element to be *tooltiped*.
//Read the mootools docs for further understanding.
    makeTip: function(elem, json) {
      var title = json.name;
      var html = this.makeHTMLFromData(json.data);
      elem.store('tip:title', title).store('tip:text', html);
    },
//Take each dataset object key and value and make an HTML from it.
    makeHTMLFromData: function(data) {
      var html = '';
      for(var i=0; i<data.length; i++) {
        html += data[i].key + ': ' + data[i].value + '<br />';
      }
      return html;
    }
  });

  tm.loadFromJSON(json);
}

You should see some tooltips now!

Remember that you have more controller methods to customize your visualization just as you want! Hope it was helpful.