Tag Archive for 'JIT'

Sunburst Visualization

In a previous post I showed the ForceDirected visualization I’m working on for version 1.2 of the JavaScript InfoVis Toolkit, today I’d like to talk about another visualization I’ve been working on, the Sunburst Visualization.

What’s the Sunburst Visualization?

I guess an example could help here:

A Sunburst visualization is a radial space-filling visualization technique for displaying tree like structures. There are other space-filling visualization methods that use other visual encodings for describing hierarchies. For example, the Treemap is a space-filling visualization that uses “containment” to show “parent-child” relationships.

There are a couple of subtle changes that can improve the way the information is communicated by this visualization.

  • The “classic” Sunburst visualization uses horizontal labels for node names. One problem with this is that as I mentioned in a previous post labels can be occluded. One solution for this is to rotate labels in a way that they’re not occluded.
  • A simple yet important thing to do when rotating labels is to rotate the labels in a way that they’re always facing up.

    In this example some labels are upside-down:

    A simple check could make labels more readable:

  • Another interesting thing that can be used with the Canvas Text API is the maxWidth parameter. This is an optional parameter that can be used to try to force the text to fit some width. I use this parameter when plotting the Sunburst visualization:

Node Styling and Behavior

The visualization also implements events for hovering and clicking nodes. You can also provide any number of styles to be smoothly updated when hovering and clicking nodes. There’s also onClick and onHover callbacks that are called when a node is clicked or hovered respectively.
For example, this is the configuration I used in the previous example:

        NodeStyles: {
          attachToDOM: false,
          attachToCanvas: true,
          stylesHover: {
            'color': '#d33'
          },
          stylesClick: {
            'color': '#3dd'
          },
          onClick: function(node) {
            //build right column relations list
            buildRightColumnRelationsList(node);

            //hide tip
            sb.tips.tip.style.display = 'none';

            //rotate
            sb.rotate(node, 'animate', {
              'duration': 1500,
              'transition': Trans.Quart.easeInOut
            });
          }
        },

You can also add tool-tips as I did in the example. The configuration I used was:

        Tips: {
          allow: true,
          attachToDOM: false,
          attachToCanvas: true,
          onShow: function(tip, node, elem) {
            tip.innerHTML = node.name;
          }
        },

Node styling and tool-tips can be attached to DOM elements (like HTML or SVG labels) or they can also be attached to the Canvas. The latter method uses an internal MouseEventManager to calculate the position of the mouse event and determine which node of the graph is being hovered or clicked.

Collapsing and Expanding Subtrees

I’ve been also implementing animations for collpasing/expanding subtrees:

The collapsing process reduces the angle span occupied by a parent node and sets its children alpha value to zero. There’s also a visual mark set for collapsed nodes.

I hope you like this visualization. There’s still much work to do, mostly regarding browser compatibility. I’ll keep you up to date with the progress of this visualization and the next visualization I’ll be implementing in the next post :)

Force Directed Layouts

I’m currently working on three new visualizations for the JavaScript InfoVis Toolkit that will be added in version 1.2.

I started the development of these new visualizations a couple of weeks ago, and while trying to incorporate these new visualizations harmoniously into the Toolkit I’ve found myself considering new design and code abstractions I haven’t thought about before. That’s good for the Toolkit, and also good for my brain :)

One of the visualizations I’ll be incorporating in the next major release is a Force-Directed visualization. I first want to thank Marcus Cobden for donating a lot of code related to this algorithm that you can find here.

My code is based in his donation and also in a nice overview of Force-Directed layout algorithms that I’ve found here.

So what are Force-Directed Layout Algorithms?

Force-Directed Layout algorithms are graph drawing algorithms based only on information contained within the structure of the graph itself rather than relying on contextual information. The most straightforward Force-Directed algorithm uses repulsive forces between nodes and attractive forces between adjacent nodes. This physical model produces some local minima in which the graph, well, gets to a stable configuration and is drawn in an aesthetically pleasing way.

The main algorithm consists on a main loop that simulates the system for some iterations and then plots the graph. The system will consist on repulsive forces between all graph nodes and attractive forces between adjacent nodes. The force models considered correspond to Hooke’s law and Coulomb’s law.

Here’s an example with some JSON data I also use for other examples at the demos page:

Force Directed Image

There are lots of refinements and different methods for making Force-Directed Layouts. Some are just based on the model I described before, others are based on what’s called Graph Theoretic Distances: If for two adjacent nodes their ideal distance is set as d, then for nodes with shortest path distance of n their ideal layout distance should be n * d. Attractive and repulsive forces are used to make a graph system with random nodes positions get to a local minima based on these rules.

I implemented the first model I described. For each iteration the computePositionStep method is called to update all nodes positions based on attractive and repulsive forces. The formal parameters passed to the method are property which is an array of node properties which contain positions to be updated and opt which is an object containing layout options like generic repulsive and attractive force functions. $C creates a new Complex Number.

  computePositionStep: function(property, opt) {
    var graph = this.graph, GUtil = Graph.Util;
    var min = Math.min, max = Math.max;
    var dpos = $C(0, 0);
    //calculate repulsive forces
    GUtil.eachNode(graph, function(v) {
      //initialize disp
      $each(property, function(p) {
        v.disp[p].x = 0; v.disp[p].y = 0;
      });
      GUtil.eachNode(graph, function(u) {
        if(u.id != v.id) {
          $each(property, function(p) {
            var vp = v.getPos(p), up = u.getPos(p);
            dpos.x = vp.x - up.x;
            dpos.y = vp.y - up.y;
            var norm = dpos.norm() || 1;
            v.disp[p].$add(dpos
                .$scale(opt.nodef(norm) / norm));
          });
        }
      });
    });
    //calculate attractive forces
    var T = !!graph.getNode(this.root).visited;
    GUtil.eachNode(graph, function(node) {
      GUtil.eachAdjacency(node, function(adj) {
        var nodeTo = adj.nodeTo;
        if(!!nodeTo.visited === T) {
          $each(property, function(p) {
            var vp = node.getPos(p), up = nodeTo.getPos(p);
            dpos.x = vp.x - up.x;
            dpos.y = vp.y - up.y;
            var norm = dpos.norm() || 1;
            node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
            nodeTo.disp[p].$add(dpos.$scale(-1));
          });
        }
      });
      node.visited = !T;
    });
    //arrange positions to fit the canvas
    var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
    GUtil.eachNode(graph, function(u) {
      $each(property, function(p) {
        var disp = u.disp[p];
        var norm = disp.norm() || 1;
        var p = u.getPos(p);
        p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm,
            disp.y * min(Math.abs(disp.y), t) / norm));
        p.x = min(w2, max(-w2, p.x));
        p.y = min(h2, max(-h2, p.y));
      });
    });
  }

You can find more information about this algorithm in this overview of Force-Directed algorithms.
This method is called multiple times to provide a final layout, for example like this:

      for(var i=0; i < times; i++) {
        opt.t = opt.tstart * (1 - i/(times -1));
        this.computePositionStep(property, opt);
      }

Where times corresponds to the number of iterations of the simulation, and is generally > 50 and t can be thought as the global temperature of a system that gets cooler with each iteration.

Performance

Although there are a couple of methods to reduce the complexity of this algorithm, this implementation runs like O(V^3), which can be considered as really bad performance.

This is not desirable for real-time interactive visualizations, in which everlasting computation algorithms can lead to blocking browser popups like “This Script is Taking too long…” or things like that.

One way you can avoid these popups is by making incremental computations. This can be done by splitting the main algorithm that has for example 100 iterations into smaller pieces of 20 iterations each. The main iteration loop could then be changed into something like this:

  computePositions: function(property, opt, incremental) {
    var times = this.config.iterations, i = 0, that = this;
    if(incremental) {
      (function iter() {
        for(var total=incremental.iter, j=0; j<total; j++) {
          opt.t = opt.tstart * (1 - i++/(times -1));
          that.computePositionStep(property, opt);
          if(i >= times) {
            incremental.onComplete();
            return;
          }
        }
        incremental.onStep(Math.round(i / (times -1) * 100));
        setTimeout(iter, 1);
      })();
    } else {
      for(; i < times; i++) {
        opt.t = opt.tstart * (1 - i/(times -1));
        this.computePositionStep(property, opt);
      }
    }
  }

This method could be called for example like this:

    //compute positions incrementally and animate.
    fd.computePositions(property, opt, {
      iter: 20,
      onStep: function(perc) {
        Log.write(perc + '% loaded...');
      },
      onComplete: function() {
        Log.write('done');
       fd.animate();
      }
    });

And the main computation would be split into smaller parts calling on each step of the computation the onStep callback.

Graph Operations

For graph operations (Adding/Removing nodes and edges, Graph Sum and Graph Morphing) I make all iterations in the first layout and then only apply 20 iterations after some operation has been completed. Here's a video:

When (*not*) to use Force-Directed Layouts

Force-Directed Layouts can be a good choice when you're drawing general graphs and you don't have domain specific (or any other topological) information about them.
Tree structures however are a special case of graphs: this means that tree structures have more constrains and therefore there's more contextual information for drawing a Tree than for drawing a general graph.
Also, Trees are easier to grasp for the human eye than "general graphs". There's that intrinsic notion of hierarchy that can be used to draw aesthetically pleasing graph layouts. Think about the "general graph" layouts that start with the drawing of a spanning tree and then add the "extra edges" to complete the graph structure (for example here and here). The Multitrees method I mentioned a couple of posts ago was also developed to try to extract more contextual information about a graph and draw it based on a notion of partial hierarchy.

Because of this, in my opinion, it wouldn't be wise to plot Trees that have many nodes with this Force-Directed algorithm: this extra-information about trees isn't used in this layout. However, if you're planning on drawing general graphs and you don't have any other information about them, then this algorithm can detect interesting symmetries and make aesthetically pleasing drawings.

I'll keep you updated with the other visualizations I'm working on in the next posts.

Version 1.1.3

I just tagged the JavaScript InfoVis Toolkit with version 1.1.3. It’s been some time since the last release, and I wanted to use this post to make a summary of the changes and to describe some of the new features that have been added to the library.

I’ll start with the new features:

SpaceTree: SwitchAlignment

I added some new global configuration properties to the SpaceTree: align and indent.

Align sets the alignment of the tree to center, left and right:



The indent parameter sets an offset between a parent and its children when the alignment is left or right. You can also use the switchAlignment method for changing the alignment of the tree with an animation.

SpaceTree: Multiple nodes in path

I added two new methods to the SpaceTree: addNodeInPath and clearNodesInPath.
These two methods allow you to add a node to the “selected-nodes” path. When a node belongs to the “selected-nodes” path it remains always visible (as in always expanded).

I made this small video to show the feature:

SpaceTree: MultiTree

I added a SpaceTree configuration property called multitree.
If multitree=true, the visualization will search for the $orn data property in each node and display the subtrees according to their orientation.

In this example I set multitree=true and set $orn=’left’ for some nodes and $orn=’right’ for others. This way I create a partition of the tree:

I also use the setRoot method to set the clicked node as root for the visualization. This way the clicked node is centered and a centrifugal view from that node is drawn.

Bug Fixes

I’ve been fixing a couple of bugs also, most of them have to do with Treemaps:

I also want to thank Guido Schmidt for his interest in the library and work on the GWT JIT component.

There’s also been some JIT development for Grails done by Bertrand Goetzmann.

Anyway, things are looking good! :)

The JavaScript InfoVis Toolkit 1.1 is Out!

After several months of hard work I can finally announce version 1.1 of the JavaScript InfoVis Toolkit.

What’s the JavaScript InfoVis Toolkit?

The JavaScript InfoVis Toolkit provides tools for creating Interactive Data Visualizations for the Web.

What’s new in this version?

Code-Related

  • The library has been split into modules for code reuse.
  • All visualizations are packaged in the same file. You can create multiple instances of any visualization. Moreover, you can combine and compose visualizations. If you want to know more take a look at the Advanced Demos.
  • This Toolkit is library agnostic. This means that you can combine this toolkit with your favorite DOM/Events/Ajax framework such as Prototype, MooTools, ExtJS, YUI, JQuery, etc.
  • You can extend this library in many ways by adding or overriding class methods. The JavaScript InfoVis Toolkit has a robust (and private) class system, heavily inspired by MooTools’, that allows you to implement new methods in the same class without having to define any new Class extension. By creating mutable classes you can add new custom Node and Edge rendering functions pretty easily.
  • Custom visualizations are created by adding or changing Node/Edge colors, shapes, rendering functions, etc. You can also implement many controller methods that are triggered at different stages of the animation, like onBefore/AfterPlotLine, onBefore/AfterCompute, onBefore/AfterPlotNode, request, etc.
    You can also add new Animation transitions like Elastic or Back with easeIn/Out transitions.
    If you want to know more about these features please take a look at the Demos code.

As you can see, this new version has been built with four concepts/goals in mind: Modularity, Customization, Composition and Extensibility. I already explained some of these things in the previous post.

Hope you enjoy it.

More about the JavaScript InfoVis Toolkit 1.1

I’ve been putting a lot of effort in the upcoming version of the JavaScript InfoVis Toolkit lately, and I though it would be a good idea to show some of the new features I came up with.

The new version isn’t finished yet, but I’ve come pretty far and wanted to make a sort of checkpoint for the things I’ve done, the things I’ll be doing and the things I’m thinking about doing.

So what have I been working on?

  • A new project page design.
  • A complete documentation. I made an API documentation that is also mixed with some narrative documentation for each Class, so you can learn how the visualizations are implemented and how to use them.
  • Packaging All visualizations will be packaged in the same file, and you’ll be able to make your own build based on what you’re going to use (Treemaps, Hypertrees, RGraphs, Spacetrees or any combination of those). This is a cool aspect of making modular code.
    The other good thing about modular code is that the size of the full package will drop in ~30% compared to version 1.0.8a
  • Examples I’ve been coding some new visualization examples that will be packaged with the library. Some of them are very similar to the ones found in 1.0.8a, but adapted for version 1.1. Other examples show some of the new features of the library, and others try to expose some features of version 1.0.8a that were not properly documented.

Library features

I’ve been building this library with four things in mind:

  • Extensibility The library has multiple access points where it can be extended in different ways. For example, all main Classes are mutable objects, so you can extend or implement any method of any class in-place, like for example re-implement the nodes coloring method in the Squarified treemap:

       //TM.Strip, TM.SliceAndDice also work
       TM.Squarified.implement({
         'setColor': function(json) {
           return json.data.$color;
         }
       });
    

    …or adding new node/edge plotting methods in the Hypertree, ST or RGraph:

    Hypertree.Plot.NodeTypes.implement({
      'my-node-rendering-method': function(node, canvas) {
        //implement node rendering here
      }
    });
    

    etc.

  • Customization The library provides many ways for customizing the visualizations. There are controller methods that determine the behavior of the visualization, and configuration parameters like node and edge types, color and dimensions. Node shapes can be square, rectangle, circle, ellipse, etc. and edge shapes: line, hyperline and arrow. I also added transition effects like Quart, Bounce, Elastic, Back, etc. for the animations.
  • Modularity As explained above, the code has been divided into modules, providing a way for making custom builds of the library. Modularity also takes care of namespacing: I only add Classes that are meant to be accessed by the user and I don’t pollute the window object with unnecessary global objects.
  • Composition A major improvement in this version is that all visualizations can co-exist in the same namespace. That means that multiple instances of different visualizations can be used and composed to make new visualizations. I haven’t explored this feature of the library yet, but this would mean that for example I can make a Treemap that has Hypertrees rendered as leaves, or a Spacetree that has Treemaps as nodes, or… well, any other combination of things.

Examples

As you might know, I don’t have the most suited computer for making screencasts, so sorry if you see some performance problems.

This is a short video I made of a RGraph example.

The main idea behind this example is Customization.
That can be seen for example in the different node types, edge types and colors used, as well as in the Elastic transition effect for the animation.

This is just an example to expose as much features as I can in one visualization, so don’t take this as a “useful” visualization example please.

Here is another short video: it illustrates how Graph Operations can be made with the Hypertree visualization.

You’ll see 4 consecutive operations:

  1. Removing a subtree The bottom right subtree will be removed with an animation.
  2. Removing edges Edges from the top left subtree will be removed with an animation.
  3. Adding a graph A graph will be added with an animation
  4. Morphing The graph will transform into another graph -with an animation

Enjoy.

JavaScript InfoVis Toolkit 1.1 Preview

In case you’re wondering what I’m up to…

I’ve been adding more features to the JavaScript InfoVis Toolkit, to be released I-don’t-know-when-yet (still a lot of work to do regarding documentation, hosting, scripts, etc.).

Anyway, this video shows only some of the features to be included:

  • Custom nodes: built-in shapes are none, circle, square, rectangle, ellipse, among others
  • Custom edges: built-in shapes are none, line, quadratic, bezier, arrow, among others
  • Custom Animations: linear, Quart, Bounce, Elastic, Back, etc.
  • Change tree orientation: already possible in 1.0.8a.

Unfortunately my video card isn’t very good, so the video quality and fps aren’t as good as I’d wanted.
Animations are pretty smooth though, as you can see for yourself, so don’t blame the library, blame my computer!

Anyway, here’s the video:

Another cool thing is that you can also create custom node and edge rendering functions :)

Stay tuned, there are more features to come!

Visualizing Linux package dependencies

I’ve been building a Linux package dependency visualizer with Python and the JavaScript Infovis Toolkit that gathers all dependencies for a linux package and displays them in an interactive tree visualization.

So, let’s say your query is wine and you want to see dependencies for that package. The visualization will display wine as the centered node, laying its dependencies on outer concentric circles like this:

rg1

By clicking on xbase-clients you’ll set this node as root:

rg2

Then, the visualization will query for xbase-clients dependencies, morphing its state into the new node’s perspective:

rg3

You can play with the example here.

I’ll explain how to build this in case you want your own at home.
I guess this is going to be also a nice tutorial on how to configure the RGraph visualization to run advanced examples, including the new morphing animations in version 1.0.7a.

Server Side

Server side we need to build a service that can transform the apt-rdepends output for package dependencies into a JSON tree structure.

The apt-rdepends is a linux tool (which you can install with apt-get install apt-rdepends) that displays a hierarchy of package dependencies for a given package. Here’s an example when querying for erlang:

cmd1

You can either use popen2 or commands.getoutput to fetch the output for a system call in Python, I’ll do the latter.
The main function that makes the system call and returns the answer could be something like this:

def get_dependency_tree(package=''):
    out = commands.getoutput("apt-rdepends " + package).split("\n")
    ans = []
    #if dependencies were found for this package.
    if len(out) > 3 and out[3].strip() == package:
        ans = out[3:]
    else:
        ans = [package]
    return make_tree(package=ans[0].strip(), source=ans, level=2)

The make_tree function will create the tree structure that will then be serialized into JSON to be processed client side.

We will first need a make_tree_node function that creates a tree node structure from a package’s name:

#returns a tree node
def make_tree_node(id, node_name):
    node_name = node_name.strip()
    return {
            'id': id,
            'name': node_name,
            'children': [],
            'data': []
    }

As you can see, this is the same tree node as the JSON tree structure defined for the JIT:

var json = {
	"id": "aUniqueIdentifier",
	"name": "usually a nodes name",
	"data": [
	    {key:"some key",       value: "some value"},
		{key:"some other key", value: "some other value"}
	],
	children: [/* other nodes or empty */]
};

Our make_tree function will receive as formal parameters the root package, the response from the apt-rdepends call, an integer that will specify the max depth for the tree (in case we want to prune it to some level) and an id prefix that will be set for each node:

def make_tree(package='', source=[], level=1, prefix=''):
    node = make_tree_node(package + '_' + prefix, package)
    if level > 0:
        deps = get_package_deps(package, source)
        [node['children'].append(make_tree(elem, source, level -1, package)) for elem in deps]
    return node

As you can see, make_tree recursively creates nodes and appends them to their parent children property.

Finally, I also made a get_package_deps function that retrieves all children for a given package, parsing source:

def get_package_deps(package_name='', source=[]):
    ans, found_package_name = [], False
    #test if is a dependency line
    dependency = lambda package: package.strip().startswith('Depends:')
    for line in source:
        #package name line
        if not found_package_name and package_name == line.strip():
            found_package_name = True
        #it's a package dependency, add its name to the answer
        elif found_package_name and dependency(line):
            ans.append(line.split("Depends: ")[1].split("(")[0].strip())
        #end of dependency lines
        elif found_package_name and not dependency(line):
            return ans
    return ans

If you used Django, then you could expose your service in the views.py file like this:

def apt_dependencies(request, mode, package):
    json = aptdependencies.get_dependency_tree(package)
    json_string = simplejson.dumps(json)
    return render_to_response('raw.html', { 'json' : json_string })

Client Side

All the JavaScript Infovis Toolkit visualizations are customizable via controller methods.
If this is the first time you use this library, perhaps it would be better to start with the RGraph quick tutorial first.

First we define a simple Log object, that will write the current state of the graph to a label (like loading… or stuff like that).

I’ll use Mootools, but you can use whatever you want.

var Log = {
	elem: false,
	getElem: function() {
		return this.elem? this.elem : this.elem = $('log');
	},

	write: function(text) {
		var elem = this.getElem();
		elem.set('html', text);
	}
};

Then we can define an init function, that instanciates the RGraph object and returns it.
We will pass a controller to this object, that implements the onBeforeCompute, onAfterCompute, onPlaceLabel and onCreateLabel methods.
I’ll also define some utility methods, like requestGraph and preprocessTree:

function init() {
  //Set node radius to 3 pixels.
  Config.nodeRadius = 3;

  //Create a canvas object.
  var canvas= new Canvas('infovis', '#ccddee', '#772277');

  //Instanciate the RGraph
  var rgraph= new RGraph(canvas,  {
	//Here will be stored the
	//clicked node name and id
  	nodeId: "",
  	nodeName: "",

  	//Refresh the clicked node name
	//and id values before computing
	//an animation.
	onBeforeCompute: function(node) {
  		Log.write("centering " + node.name + "...");
		this.nodeId = node.id;
  		this.nodeName = node.name;
  	},

  //Add a controller to assign the node's name
  //and some extra events to the created label.
  	onCreateLabel: function(domElement, node) {
  		var d = $(domElement);
  		d.setOpacity(0.6).set('html', node.name).addEvents({
  			'mouseenter': function() {
  				d.setOpacity(1);
  			},
  			'mouseleave': function() {
  				d.setOpacity(0.6);
  			},
  			'click': function() {
				if(Log.elem.innerHTML == "done") rgraph.onClick(d.id);
  			}
  		});
  	},

	//Once the label is placed we slightly
	//change the positioning values in order
	//to center or hide the label
  	onPlaceLabel: function(domElement, node) {
		var d = $(domElement);
		d.setStyle('display', 'none');
		 if(node._depth <= 1) {
			d.set('html', node.name).setStyles({
				'width': '',
				'height': '',
				'display':''
			}).setStyle('left', (d.getStyle('left').toInt()
				- domElement.offsetWidth / 2) + 'px');
		}
	},

	//Once the node is centered we
	//can request for the new dependency
	//graph.
	onAfterCompute: function() {
		Log.write("done");
		this.requestGraph();
	},

	//We make our call to the service in order
	//to fetch the new dependency tree for
	//this package.
   	requestGraph: function() {
  		var that = this, id = this.nodeId, name = this.nodeName;
  		Log.write("requesting info...");
  		var jsonRequest = new Request.JSON({
  			'url': '/service/apt-dependencies/tree/'
					+ encodeURIComponent(name) + '/',

  			onSuccess: function(json) {
  				Log.write("morphing...");
				//Once me received the data
				//we preprocess the ids of the nodes
				//received to match existing nodes
				//in the graph and perform a morphing
				//operation.
  				that.preprocessTree(json);
				GraphOp.morph(rgraph, json, {
  					'id': id,
  					'type': 'fade',
  					'duration':2000,
  					hideLabels:true,
  					onComplete: function() {
						Log.write('done');
					},
  					onAfterCompute: $empty,
  					onBeforeCompute: $empty
  				});
  			},

  			onFailure: function() {
  				Log.write("sorry, the request failed");
  			}
  		}).get();
  	},

	//This method searches for nodes that already
	//existed in the visualization and sets the new node's
	//id to the previous one. That way, all existing nodes
	//that exist also in the new data won't be deleted.
 	preprocessTree: function(json) {
  		var ch = json.children;
  		var getNode = function(nodeName) {
  			for(var i=0; i<ch.length; i++) {
  				if(ch[i].name == nodeName) return ch[i];
  			}
  			return false;
  		};
  		json.id = rgraph.root;
		var root = rgraph.graph.getNode(rgraph.root);
  		GraphUtil.eachAdjacency(root, function(elem) {
  			var nodeTo = elem.nodeTo, jsonNode = getNode(nodeTo.name);
  			if(jsonNode) jsonNode.id = nodeTo.id;
  		});
  	}

  });

  return rgraph;
}

I did say advanced example.
You can always go to a simpler example to begin here.

Finally we have to initialize the visualization when the page loads, so we’ll attach an initialization function like this:

window.addEvent('domready', function() {
	var rgraph = init();
	new Request.JSON({
	  	'url':'/service/apt-dependencies/tree/wine/',
	  	onSuccess: function(json) {
			  //load wine dependency tree.
			 rgraph.loadTreeFromJSON(json);
			  //compute positions
			  rgraph.compute();
			  //make first plot
			  rgraph.plot();
			  Log.write("done");
			  rgraph.controller.nodeName = name;
	  	},

	  	onFailure: function() {
	  		Log.write("failed!");
	  	}
	}).get();

HTML and CSS

These are the HTML and CSS files I used to make this example/tutorial.
The HTML:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>

Linux package dependency visualizer

</title>
<link type="text/css" href="/static/css/style.css" rel="stylesheet" />
<script type="text/javascript" src="/static/js/mootools-1.2.js"></script>

<!--[if IE]>
<script language="javascript" type="text/javascript" src="/static/js/excanvas.js"></script>
<![endif]-->
<script language="javascript" type="text/javascript" src="/static/js/core/RGraph.js"></script>
<script language="javascript" type="text/javascript" src="/static/js/example/example-rgraph.js"></script>

</head>

<body onload="">

<canvas id="infovis" width="900" height="500"></canvas>
<div id="label_container"></div>

</body>
</html>
<div id="log"></div>

Note: You’ll probably have to change the path to the CSS and JavaScript files.

and the CSS file:

html,body {
	width:100%;
	height:100%;
	margin:0;padding:0;
	background-color:#333;
	text-align:center;
	font-size:0.94em;
	font-family:"Trebuchet MS",Verdana,sans-serif;
}

#infovis {
	width:900px;
	height:500px;
	background-color:#222;

}

.node {
	color: #fff;
	background-color:#222;
	font-weight:bold;
	padding:1px;
	cursor:pointer;
	font-size:0.8em;
}

.hidden {
	display:none;
}

Remarks

Although still in alpha, the JavaScript Infovis Toolkit can be used to perform advanced animations, customizing your visualization via a controller and not messing with the code.
This example also shows that it can be used to do more advanced things that only plotting static animations, interacting with services and handling pretty well visualizations where the dataset changes over time.
You can download the library here, latest version is 1.0.7a.
You can also go to the main project page to know more.

Hope it was useful.
Feel free to post any comment or questions.
Bye!

Feeding JSON graph structures to the JIT

Version 1.0.3a of the JIT allows you to load graph structures to the RGraph and Hypertree objects. I chose a different JSON structure for graphs, since JSON tree structures don’t seem conceptually suitable for this task.
Hypertree and RGraph objects have a new method called loadGraphFromJSON(json [,i]) that takes a graph structure (described below) and optionally an index to set a particular node as root for the visualization. Please refer to the documentation for more information.

The graph structure

The JSON graph structure is an array of nodes, each having as properties:

  • id a unique identifier for the node.
  • name a node’s name.
  • data The data property contains a dataset. That is, an array of key-value objects defined by the user. Roughly speaking, this is where you can put some extra information about your node. You’ll be able to access this information at different stages of the computation of the JIT visualizations by using a controller.
  • adjacencies An array of strings each representing a nodes id.

For example,

var json = [
{
	"id": "aUniqueIdentifier",
	"name": "usually a nodes name",
	"data": [
	    {key:"some key",       value: "some value"},
		{key:"some other key", value: "some other value"}
	],
	"adjacencies": ["anotherUniqueIdentifier", "yetAnotherUniqueIdentifier" /* ... */]
} /* ... more nodes here ... */ ];

I did a small example of a K6 rendered with a RGraph. The JSON graph structure used for this example is:

var json= [
    {"id":"node0",
     "name":"node0 name",
     "data":[
        {"key":"some key",
         "value":"some value"},
        {"key":"some other key",
         "value":"some other value"}],
     "adjacencies":["node1","node2","node3","node4","node5"]},
    {"id":"node1",
     "name":"node1 name",
     "data":[
        {"key":"some key",
         "value":"some value"},
        {"key":"some other key",
         "value":"some other value"}],
     "adjacencies":["node0","node2","node3","node4","node5"]},
    {"id":"node2",
     "name":"node2 name",
     "data":[
        {"key":"some key",
         "value":"some value"},
        {"key":"some other key",
         "value":"some other value"}],
     "adjacencies":["node0","node1","node3","node4","node5"]},
    {"id":"node3",
     "name":"node3 name",
     "data":[
        {"key":"some key",
         "value":"some value"},
        {"key":"some other key",
         "value":"some other value"}],
     "adjacencies":["node0","node1","node2","node4","node5"]},
    {"id":"node4",
     "name":"node4 name",
     "data":[
        {"key":"some key",
         "value":"some value"},
        {"key":"some other key",
         "value":"some other value"}],
     "adjacencies":["node0","node1","node2","node3","node5"]},
    {"id":"node5",
     "name":"node5 name",
     "data":[
        {"key":"some key",
         "value":"some value"},
        {"key":"some other key",
         "value":"some other value"}],
     "adjacencies":["node0","node1","node2","node3","node4"]}];

You can post any question at the google group for this project.
Enjoy!

Further work on JIT

I’m very happy with this first JavaScript Infovis Toolkit release, and I’m already working on fixing some known bugs and adding features for the next release of this library.
For the next step I’ll be focusing on fixing bugs, refactoring code, adding some features and improving the documentation.
Some of the things you’ll find in my TODO list are:

  • For RGraphs:
    • Add polar interpolation in animations.
    • Mantain Child ordering during transitions.
    • Change node diameters based on the first dataset object value.
    • Graph support (trees with cycles…)
  • For the ST:
    • Code refactoring.
    • Performance improvements (bind with the Animation object).
    • Support for IE.
  • For Hypertrees:
    • Bug fix (node diameters, multilevel display).
    • Graph support, (although it might be supporting Graph structures already).
  • Treemaps
    • Bug fix (mostly on computing rectangles dimensions).

Before adding some radical features (binding visualizations with real time mutable data, etc.) I really want to have a strong API, and in order to do that, I must do some research on my own.
Don’t forget you have some quick tutorials on the visualizations. Also, the Hypertree, ST, RGraph and Treemap have some object references. Finally, there’s a Google Group about the JIT here.

RGraph quick tutorial

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

Note: I’ll be using Mootools for this tutorial. However, this visualization can be used without the Mootools library. You can find a Mootools build in the extras folder of the library.

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

We are going to work with this tree JSON structure:

var json = {"id":"node02",
 "name":"0.2",
 "data":[
    {"key":"key1",
     "value":8},
    {"key":"key2",
     "value":-88}],
 "children":[
    {"id":"node13",
     "name":"1.3",
     "data":[
        {"key":"key1",
         "value":8},
        {"key":"key2",
         "value":74}],
     "children":[
        {"id":"node24",
         "name":"2.4",
         "data":[
            {"key":"key1",
             "value":10},
            {"key":"key2",
             "value":55}],
         "children":[]},
        {"id":"node25",
         "name":"2.5",
         "data":[
            {"key":"key1",
             "value":8},
            {"key":"key2",
             "value":67}],
         "children":[]},
        {"id":"node26",
         "name":"2.6",
         "data":[
            {"key":"key1",
             "value":5},
            {"key":"key2",
             "value":-50}],
         "children":[]},
        {"id":"node27",
         "name":"2.7",
         "data":[
            {"key":"key1",
             "value":8},
            {"key":"key2",
             "value":10}],
         "children":[]},
        {"id":"node28",
         "name":"2.8",
         "data":[
            {"key":"key1",
             "value":2},
            {"key":"key2",
             "value":-69}],
         "children":[]},
        {"id":"node29",
         "name":"2.9",
         "data":[
            {"key":"key1",
             "value":3},
            {"key":"key2",
             "value":98}],
         "children":[]},
        {"id":"node210",
         "name":"2.10",
         "data":[
            {"key":"key1",
             "value":6},
            {"key":"key2",
             "value":12}],
         "children":[]},
        {"id":"node211",
         "name":"2.11",
         "data":[
            {"key":"key1",
             "value":2},
            {"key":"key2",
             "value":-95}],
         "children":[]}]},
    {"id":"node112",
     "name":"1.12",
     "data":[
        {"key":"key1",
         "value":1},
        {"key":"key2",
         "value":96}],
     "children":[
        {"id":"node213",
         "name":"2.13",
         "data":[
            {"key":"key1",
             "value":6},
            {"key":"key2",
             "value":-58}],
         "children":[]},
        {"id":"node214",
         "name":"2.14",
         "data":[
            {"key":"key1",
             "value":9},
            {"key":"key2",
             "value":-42}],
         "children":[]},
        {"id":"node215",
         "name":"2.15",
         "data":[
            {"key":"key1",
             "value":10},
            {"key":"key2",
             "value":92}],
         "children":[]},
        {"id":"node216",
         "name":"2.16",
         "data":[
            {"key":"key1",
             "value":7},
            {"key":"key2",
             "value":-15}],
         "children":[]},
        {"id":"node217",
         "name":"2.17",
         "data":[
            {"key":"key1",
             "value":3},
            {"key":"key2",
             "value":29}],
         "children":[]},
        {"id":"node218",
         "name":"2.18",
         "data":[
            {"key":"key1",
             "value":8},
            {"key":"key2",
             "value":-59}],
         "children":[]},
        {"id":"node219",
         "name":"2.19",
         "data":[
            {"key":"key1",
             "value":3},
            {"key":"key2",
             "value":21}],
         "children":[]},
        {"id":"node220",
         "name":"2.20",
         "data":[
            {"key":"key1",
             "value":2},
            {"key":"key2",
             "value":78}],
         "children":[]}]},
    {"id":"node121",
     "name":"1.21",
     "data":[
        {"key":"key1",
         "value":3},
        {"key":"key2",
         "value":53}],
     "children":[
        {"id":"node222",
         "name":"2.22",
         "data":[
            {"key":"key1",
             "value":5},
            {"key":"key2",
             "value":10}],
         "children":[]},
        {"id":"node223",
         "name":"2.23",
         "data":[
            {"key":"key1",
             "value":10},
            {"key":"key2",
             "value":21}],
         "children":[]},
        {"id":"node224",
         "name":"2.24",
         "data":[
            {"key":"key1",
             "value":6},
            {"key":"key2",
             "value":-32}],
         "children":[]},
        {"id":"node225",
         "name":"2.25",
         "data":[
            {"key":"key1",
             "value":5},
            {"key":"key2",
             "value":-42}],
         "children":[]},
        {"id":"node226",
         "name":"2.26",
         "data":[
            {"key":"key1",
             "value":2},
            {"key":"key2",
             "value":75}],
         "children":[]},
        {"id":"node227",
         "name":"2.27",
         "data":[
            {"key":"key1",
             "value":1},
            {"key":"key2",
             "value":-74}],
         "children":[]},
        {"id":"node228",
         "name":"2.28",
         "data":[
            {"key":"key1",
             "value":2},
            {"key":"key2",
             "value":52}],
         "children":[]},
        {"id":"node229",
         "name":"2.29",
         "data":[
            {"key":"key1",
             "value":10},
            {"key":"key2",
             "value":-49}],
         "children":[]}]},
    {"id":"node130",
     "name":"1.30",
     "data":[
        {"key":"key1",
         "value":9},
        {"key":"key2",
         "value":-29}],
     "children":[
        {"id":"node231",
         "name":"2.31",
         "data":[
            {"key":"key1",
             "value":6},
            {"key":"key2",
             "value":-23}],
         "children":[]},
        {"id":"node232",
         "name":"2.32",
         "data":[
            {"key":"key1",
             "value":10},
            {"key":"key2",
             "value":19}],
         "children":[]},
        {"id":"node233",
         "name":"2.33",
         "data":[
            {"key":"key1",
             "value":1},
            {"key":"key2",
             "value":92}],
         "children":[]}]},
    {"id":"node134",
     "name":"1.34",
     "data":[
        {"key":"key1",
         "value":9},
        {"key":"key2",
         "value":71}],
     "children":[
        {"id":"node235",
         "name":"2.35",
         "data":[
            {"key":"key1",
             "value":5},
            {"key":"key2",
             "value":-65}],
         "children":[]}]},
    {"id":"node136",
     "name":"1.36",
     "data":[
        {"key":"key1",
         "value":3},
        {"key":"key2",
         "value":-11}],
     "children":[
        {"id":"node237",
         "name":"2.37",
         "data":[
            {"key":"key1",
             "value":6},
            {"key":"key2",
             "value":-85}],
         "children":[]},
        {"id":"node238",
         "name":"2.38",
         "data":[
            {"key":"key1",
             "value":3},
            {"key":"key2",
             "value":-13}],
         "children":[]},
        {"id":"node239",
         "name":"2.39",
         "data":[
            {"key":"key1",
             "value":1},
            {"key":"key2",
             "value":80}],
         "children":[]},
        {"id":"node240",
         "name":"2.40",
         "data":[
            {"key":"key1",
             "value":10},
            {"key":"key2",
             "value":-69}],
         "children":[]}]},
    {"id":"node141",
     "name":"1.41",
     "data":[
        {"key":"key1",
         "value":10},
        {"key":"key2",
         "value":-4}],
     "children":[
        {"id":"node242",
         "name":"2.42",
         "data":[
            {"key":"key1",
             "value":8},
            {"key":"key2",
             "value":-27}],
         "children":[]},
        {"id":"node243",
         "name":"2.43",
         "data":[
            {"key":"key1",
             "value":9},
            {"key":"key2",
             "value":-44}],
         "children":[]},
        {"id":"node244",
         "name":"2.44",
         "data":[
            {"key":"key1",
             "value":9},
            {"key":"key2",
             "value":24}],
         "children":[]},
        {"id":"node245",
         "name":"2.45",
         "data":[
            {"key":"key1",
             "value":8},
            {"key":"key2",
             "value":-66}],
         "children":[]}]}]};

Put this HTML in your page:

<html>
	<head>

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

	<!--[if IE]>
		<script type="text/javascript" src="/static/js/excanvas.js"></script>
	<![endif]-->
	<script type="text/javascript" src="/static/js/mootools-1.2.js" ></script>
	<script type="text/javascript" src="/static/js/rgraph/RGraph.js" ></script>
	<script type="text/javascript" src="/static/js/example/example-rgraph.js" ></script>

	</head>
	<body onload="init();">

         <div id="infovis"></div>

	</body>
</html>

Note: You’ll probably have to change the paths to the css and javascript files.

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

html,body {
	width:100%;
	height:100%;
	overflow:hidden;
	margin:0;padding:0;
	background-color:#333;
	text-align:center;
}

#infovis {
	width:900px;
	height:500px;
	background-color:#222;
}

.node {
	color: white;
	background-color:transparent;
	cursor:pointer;
	font-weight:bold;
	opacity:0.9;
	border:1px solid red;
}

.node:hover {
	cursor:pointer;
	color: #222;
	background-color:white;
	font-weight:bold;
	opacity:1;
}

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

function init() {
  //Set node interpolation to linear (can also be 'polar')
  Config.interpolation = "linear";
  //Set distance for concentric circles
  Config.levelDistance = 100;

  var json= //data defined previously

  //Create a new canvas instance.
  var canvas = new Canvas('mycanvas', {
    //Where to inject the canvas. Any div container will do.
    'injectInto':'infovis',
    //width and height for canvas. Default's to 200.
    'width': 900,
    'height':500,
    //Canvas styles
    'styles': {
        'fillStyle': '#ccddee',
        'strokeStyle': '#772277'
    },
    //Add a background canvas for plotting
    //concentric circles.
    'backgroundCanvas': {
        //Add Canvas styles for the bck canvas.
      'styles': {
            'fillStyle': '#444',
            'strokeStyle': '#444'
        },
        //Add the initialization and plotting functions.
        'impl': {
            'init': $empty,
            'plot': function(canvas, ctx) {
                var times = 6, d = Config.levelDistance;
                var pi2 = Math.PI*2;
                for(var i=1; i<=times; i++) {
                    ctx.beginPath();
                    ctx.arc(0, 0, i * d, 0, pi2, true);
                    ctx.stroke();
                    ctx.closePath();
                }
            }
        }
    }
 });

  var rgraph= new RGraph(canvas,  {
    //Add a controller to make the tree move on click.
    onCreateLabel: function(domElement, node) {
      var d = $(domElement);
      d.addEvents({
        'click': function() {
          rgraph.onClick(d.id);
        }
      });
    }
   });

    //load tree from tree data.
    rgraph.loadTreeFromJSON(json);
    //compute positions
    rgraph.compute();
    //make first plot
    rgraph.plot();

}

You should see a RGraph up and running. Click on the labels and the tree should move.

Some notes:

  • If you want to know more about how the Canvas class is implemented and what other canvas customizations can be done please check this post and the canvas object reference. To know more about what canvas styles can be configured please check this post.
  • By assigning false to the backgroundCanvas property you can strip off the concentric circles. The backgroundCanvas functions and styles can also be customized to plot whatever background you like.

Customizing the Graph

Let's add some labels!

First, strip off the border: 1px solid red; line from the .node class in your CSS file.
It should look like this:

.node {
	color: white;
	background-color:transparent;
	cursor:pointer;
	font-weight:bold;
	opacity:0.9;
}

Now we are going to add a javascript controller in order to put the name of the nodes into the labels. Since we only need to do this once, we'll use the onCreateLabel method. If you don't know what I'm talking about you should probably read the on controllers post first.

So the JavaScript file should look like this now:

function init() {
  //Set node interpolation to linear (can also be 'polar')
  Config.interpolation = "linear";
  //Set distance for concentric circles
  Config.levelDistance = 100;

  var json= //data defined previously

  //Create a new canvas instance.
  var canvas = new Canvas('mycanvas', {
    //Where to inject the canvas. Any div container will do.
    'injectInto':'infovis',
    //width and height for canvas. Default's to 200.
    'width': 900,
    'height':500,
    //Canvas styles
    'styles': {
        'fillStyle': '#ccddee',
        'strokeStyle': '#772277'
    },
    //Add a background canvas for plotting
    //concentric circles.
    'backgroundCanvas': {
        //Add Canvas styles for the bck canvas.
      'styles': {
            'fillStyle': '#444',
            'strokeStyle': '#444'
        },
        //Add the initialization and plotting functions.
        'impl': {
            'init': $empty,
            'plot': function(canvas, ctx) {
                var times = 6, d = Config.levelDistance;
                var pi2 = Math.PI*2;
                for(var i=1; i<=times; i++) {
                    ctx.beginPath();
                    ctx.arc(0, 0, i * d, 0, pi2, true);
                    ctx.stroke();
                    ctx.closePath();
                }
            }
        }
    }
 });

  var rgraph= new RGraph(canvas,  {
    //Add a controller to make the tree move on click.
    onCreateLabel: function(domElement, node) {
      var d = $(domElement);
      d.set('html', node.name).addEvents({
        'click': function() {
          rgraph.onClick(d.id);
        }
      });
    }
   });

    //load tree from tree data.
    rgraph.loadTreeFromJSON(json);
    //compute positions
    rgraph.compute();
    //make first plot
    rgraph.plot();
}

You should see some labels now.
The thing is that... well, they are not centered. So we'll just add an onPlaceLabel method to the controller in order to do that, since the onPlaceLabel method is called after labels have been placed.

So the js code should now look like:

function init() {
  //Set node interpolation to linear (can also be 'polar')
  Config.interpolation = "linear";
  //Set distance for concentric circles
  Config.levelDistance = 100;

  var json= //data defined previously

  //Create a new canvas instance.
  var canvas = new Canvas('mycanvas', {
    //Where to inject the canvas. Any div container will do.
    'injectInto':'infovis',
    //width and height for canvas. Default's to 200.
    'width': 900,
    'height':500,
    //Canvas styles
    'styles': {
        'fillStyle': '#ccddee',
        'strokeStyle': '#772277'
    },
    //Add a background canvas for plotting
    //concentric circles.
    'backgroundCanvas': {
        //Add Canvas styles for the bck canvas.
      'styles': {
            'fillStyle': '#444',
            'strokeStyle': '#444'
        },
        //Add the initialization and plotting functions.
        'impl': {
            'init': $empty,
            'plot': function(canvas, ctx) {
                var times = 6, d = Config.levelDistance;
                var pi2 = Math.PI*2;
                for(var i=1; i<=times; i++) {
                    ctx.beginPath();
                    ctx.arc(0, 0, i * d, 0, pi2, true);
                    ctx.stroke();
                    ctx.closePath();
                }
            }
        }
    }
 });

  var rgraph= new RGraph(canvas,  {
    //Add a controller to make the tree move on click.
    onCreateLabel: function(domElement, node) {
      var d = $(domElement);
      d.set('html', node.name).addEvents({
        'click': function() {
          rgraph.onClick(d.id);
        }
      });
    },

  //Take off previous width and height styles and
  //add half of the *actual* label width to the left position
  // That will center your label (do the math man).
    onPlaceLabel: function(domElement, node) {
      domElement.innerHTML = '';
      domElement.innerHTML = node.name;
      var left = parseInt(domElement.style.left);
      domElement.style.width = '';
      domElement.style.height = '';
      var w = domElement.offsetWidth;
      domElement.style.left = (left - w /2) + 'px';
    }

   });

    //load tree from tree data.
    rgraph.loadTreeFromJSON(json);
    //compute positions
    rgraph.compute();
    //make first plot
    rgraph.plot();
}

You should see some centered labels now!

Labels already have the onclick event handler to move the graph. You set that onCreateLabel.

Remember there are lots of other controller methods!

Remember also that you can change the animation time and the frames per second in the animation with Config.animationTime and Config.fps.
Hope it was helpful.