Tag Archive for 'javascript'

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!

Graph Operations

I’ve been working on the JavaScript Infovis Toolkit lately, fixing bugs, working on performance improvements and adding a new feature for Spacetrees, Hyperbolic Trees and RGraphs that I’ve found very interesting.

Mutable data

The JavaScript Infovis Toolkit is a JS Information Visualization library that includes radial layout of trees with animations, Treemaps, Hyperbolic Trees and Spacetrees.

Not so long ago I worked on adapting these tree layouts to support graph layouts as well, also including weighted nodes and edges, as you can see in this example.

However, one of the most challenging features I wanted to add to these visualizations was the possibility of dealing with mutable data. This way visualizations would also be useful to show how data changes over time, and updates to this data would be translated into smooth animations from one state of the graph to another.

The user could also interact at a deeper level with the visualizations, not only exploring the data, but also altering it, making updates to the information and seeing the results in real time.

The GraphOp object

The first thing that came into my mind when thinking on adding support for mutable data was prototyping the addSubtree and removeSubtree methods.
These operations seem suitable for the Spacetree, (and have been implemented for this visualization), but what about applying transformations to graphs?
Since the JIT adds support for graphs and trees in the RGraph and Hyperbolic Tree visualizations, both use cases should be well covered: adding and deleting subtrees as well as adding/deleting nodes, edges, and performing more general binary graph operations, such as graph sum and the one which I’m most proud of, morphing.

So this new release of the library comes with the addSubtree and removeSubtree methods for the Spacetree, and also with the GraphOp object for the Hypertree and RGraph visualizations, which includes unary and binary operations such as:

  • removeNode Multiple nodes can be removed from the visualization. You can choose up to four different animations for doing that.
  • removeEdge Multiple edges can be removed from the visualization. Supports many animations also.
  • sum Performs a sum of two graphs, morphing the result with sequential or concurrent animations of movement and fading.
  • morph A very useful operation in which you specify the resulting graph, and the visualization morphs the current graph state into that one.

Examples

I chose to make two real life examples. With real life I mean small apps that not only show the potential of these graph operations, but can also be actually useful to explore.

1.- Linux module dependency visualizer
It uses the RGraph visualization with the morphing operation to show dependencies between different modules you might find with the apt-get tool.
When clicking on a node you’ll set this node as root. Then the graph will perform a second animation, updating the dependencies for the new centered module.
Many details about the package are also provided under the Details toggler. You can also go to previous visited modules by using the History toggler.

These examples load data dynamically, so please be patient when loading the data.

rgraph

2.- Visualizing relations between artists and bands dinamically
Just as the old demos I made an app that relates bands and artists by common performances on bands, discs, songs, etc.
Clicking on a label will set the node as root. Then a second animation will take place, morphing the tree into the new node’s perspective.

Just as the previous example, you can find some information about the artists in the Details toggler. You can also browse previous visited nodes by clicking in the History toggler.

hypertree

All tutorials and posts have been updated for this new release. You can find more information in the project page and in the google group.

Although this library is still in alpha, some companies and products are already using it, such as OpenCRX and Platform Computing.

I’ll be writing a more technical overview of these features in further posts.

Hope you liked it :)

Memoization in JavaScript

While reading Jason Hickey’s Introduction to Objectve Caml I ran into some memoization examples that I found pretty interesting, and I wondered how memoization could be used/implemented in JavaScript.

What is memoization?
Memoization is a technique that uses side-effects to improve -at runtime- the performance of a function without altering its behavior. Roughly speaking, you can do memoization by storing function values that were returned in previous calls, and returning these values in further calls to the function instead of actually calling the function.

Where can I use memoization?
Not all functions can be memoized. In particular, pure functions can be memoized.
A function is said to be pure when it behaves in a predictable way, in the sense that for each x, the function will always return the same associated y value (i.e a single-valued map).

An example of a function that can be memoized is:

function square(x) {
     return x * x;
}

An example of a function that can’t be memoized could be:

var index = 1;
function not_mem(x) {
     index = index + 1;
     return x + index;
}

By introducing side-effects we alter the inner state of the function, having different return values for the same input. In this particular case, one could say that not_mem(0) != not_mem(0) is always true!

A somewhat generic way to do memoization
In OCaml we can define a higher order function memo that takes a function f as parameter and returns a memoized function that is perhaps faster than the input function.

   let memo f =
      let table = ref [] in
      let rec find_or_apply entries x =
         match entries with
            (x’, y) :: _ when x’ = x -> y
          | _ :: entries -> find_or_apply entries x
          | [] ->
             let y = f x in
             table := (x, y) :: !table;
             y
      in
      (fun x -> find_or_apply !table x)

We can see that the memo function has a table structure where it stores the f function results as (x, y) pairs. For each call to the memoized f function, memo will search in the table structure for an (x, y) pair that matches in x the input value. If found, it will return the associated y value:

         match entries with
            (x’, y) :: _ when x’ = x -> y

If not found, it will partially apply x to the original f function, and store the result in table as an (x, y) pair, to be used on further calls. It will finally return the computed value just as the orginial f function would have done:

             let y = f x in
             table := (x, y) :: !table;
             y

A simple timing shows the performance improvements on further calls for the memoized fibonacci memo_fib function:

  time memo_fib 40;;
Elapsed time: 14.581937 seconds
- : int = 102334155
# time memo_fib 40;;
Elapsed time: 0.000009 seconds
- : int = 102334155

Achieving the same thing in JavaScript
For unary functions a simple definition of memo could be:

function memo(f) {
  return function (x) {
      f.memo = f.memo || {};
      return (x in f.memo)? f.memo[x] : f.memo[x] = f(x);
  };
}

A couple of differences to notice for this version and the OCaml version are:

  • I’m not using a list of (x, y) pairs. Instead, I’m using a hashtable (or object) {}. This way, response time for the memoized function will not be proportional to the f function domain (as opposed to the OCaml memo function implementation).

  • I’m not using a local table variable to store previous calls. Instead, I’m using a property of the f function, f.memo.
  • In this particular case, the JS memo function behaves like the OCaml memo function for unary functions, since let y = f x for unary functions will evaluate f as opposed to currying the function. However, for (n > 1)-ary functions the OCaml version will return a curried function when called with less formal parameters than “expected”.

Lets do some profiling. For this I’ll be using the console.time and console.timeEnd firebug methods:

function memo(f) {
  return function (x) {
      f.memo = f.memo || {};
      return (x in f.memo)? f.memo[x] : f.memo[x] = f(x);
  };
}

function fib(x) {
    if(x < 2) return 1; else return fib(x-1) + fib(x-2);
}

var memo_fib = memo(fib);
//first call
console.time("first call");
console.log(memo_fib(30));
console.timeEnd("first call");

//console will output:
//first call: 17264ms
//1346269

//second call (memoized)
console.time("memoized call");
console.log(memo_fib(30));
console.timeEnd("memoized call");

//console will output:
//memoized call: 4ms
//1346269

Beyond unary functions memoization in JavaScript
The Ocaml and JavaScript versions of memo lack support for (n>1)-ary functions.
If the OCaml memo function is applied to a binary function, it will only memoize the first partial application for this function.

Lets define a function sum_fib and memo_sum_fib:

# let sum_fib a b = (fib a) + (fib b);;
val sum_fib : int -> int -> int = <fun>
# let memo_sum_fib = memo sum_fib;;
val memo_sum_fib : int -> int -> int = <fun>

Timing the memoized function might lead to some unexpected results:

# time memo_sum_fib 30 40;;
Elapsed time: 18.753172 seconds
- : int = 166926410
# time memo_sum_fib 30 40;;
Elapsed time: 18.753172 seconds
- : int = 166926410

This problem happens because the memoization happens at the first partial application level. That means that the table structure will hold (int, int -> int) elements, as opposed of a mapping from a pair of formal parameters to the returned value: (int * int, int).

In JavaScript we have a bigger problem. Since partial application is not a “natural” feature of the language and the memo function is not designed to handle n-ary functions, this will lead to an error or unexpected results. At least the OCaml version returned the expected values :P.

In JavaScript we can solve this problem by using the arguments object as the key to our f.memo hashtable. Our new memo function would now look like this:

function memo(f) {
  return function () {
      var args = Array.prototype.slice.call(arguments);
      f.memo = f.memo || {};
      return (args in f.memo)? f.memo[args] :
                     f.memo[args] = f.apply(this, args);
  };
}

Not only this function supports n-ary functions to be memoized, but also it performs a correct memoization, in the sense that it will store all formal parameters in f.memo, as the key to the value returned by this function.

Lets do some profiling!

function memo(f) {
  return function () {
      var args = Array.prototype.slice.call(arguments);
      f.memo = f.memo || {};
      return (args in f.memo)? f.memo[args] :
                     f.memo[args] = f.apply(this, args);
  };
}

function fib(x) {
    if(x < 2) return 1; else return fib(x-1) + fib(x-2);
}

function sum_fib(a, b) {
    return fib(a) + fib(b);
}

var memo_sum_fib = memo(sum_fib);
console.time("first call");
console.log(memo_sum_fib(20, 30));
console.timeEnd("first call");

//console will output:
//first call: 17165ms
//1357215

console.time("memoized call");
console.log(memo_sum_fib(20, 30));
console.timeEnd("memoized call");

//console will output:
//memoized call: 5ms
//1357215

Finally, a nice trick you can do is to call the memoized function the same a the function passed in as a formal parameter. Repeating the last example will show a lot of improvements!

function memo(f) {
  return function (x) {
      f.memo = f.memo || {};
      return (x in f.memo)? f.memo[x] : f.memo[x] = f(x);
  };
}

function fib(x) {
    if(x < 2) return 1; else return fib(x-1) + fib(x-2);
}

var fib = memo(fib);
//first call
console.time("first call");
console.log(fib(30));
console.timeEnd("first call");

//console will output:
//first call: 7ms
//1346269

//second call (memoized)
console.time("memoized call");
console.log(fib(30));
console.timeEnd("memoized call");

//console will output:
//memoized call: 5ms
//1346269

Any critique or comment will be well appreciated.
Bye!.

Pattern matching in OCaml (and JavaScript?)

I’ve been trying to learn OCaml lately.
I want to try information visualization for the “dektop” (as opposed to the web), and although I know C++ or Java would be recommended languages for this kind of thing (they have lots of libraries and pretty large communities regarding visualization in general -think about games, OpenGL, etc), I wouldn’t feel very comfortable doing geometric computation in C++ or Java.
Although geometric computation often handles objects, there’s also the “functional side” of it that doesn’t seem to be fulfilled by C++ or Java. Ocaml felt like the right mix in of programming paradigms to do this kind of thing, so I began to read Introduction to Objective Caml by Jason Hickey.

One of the chapters I really enjoyed about this book is Basic pattern maching. I was pretty amazed about the predominance of pattern matching in OCaml. Pattern matching can be applied to (almost) any OCaml primitive type (string, Boolean, char…), it also applies to a variety of syntactic structures (like assignment, function definition, etc) and finally there’s a lot of “syntactic sugar” going on in pattern matching.

The basic syntax for doing pattern matching in Ocaml is:

match expression with
 | pattern1 -> expression1
 | pattern2 -> expression2
 .
 .
 .
  | patternN  -> expressionN

This way you could define a fibonacci fib function like this:

let rec fib i =
   match i with
      0 -> 0
    | 1 -> 1
    | j -> fib (j - 2) + fib (j - 1);;

Since it’s quite common to define functions with match expressions as their body, Ocaml reserved a special keyword function that defines a function with a single argument treated as pattern match. For example the fib function could be re-defined like this:

  let rec fib = function
     0 -> 0
   | 1 -> 1
   | i -> fib (i - 1) + fib (i - 2);;

The pattern match argument is now implicit in the function definition. That’s quite minimalistic.
There’s also the as keyword used to bind a match to a variable. For example:

let rec fib = function
   (0 | 1) as i -> i
 | i -> fib (i - 1) + fib (i - 2);;

The when keyword evaluates a condition after the pattern match. The fib function could now look like this:

let rec fib = function
   i when i < 2 -> i
 | i -> fib (i - 1) + fib (i - 2);;

Pattern matching can also be applied to other Ocaml primitive types, like strings, chars, Booleans. Let’s take an example of the function is_uppercase:

  let is_uppercase = function
   ’A’ | ’B’ | ’C’ | ’D’ | ’E’ | ’F’ | ’G’ | ’H’
 | ’I’ | ’J’ | ’K’ | ’L’ | ’M’ | ’N’ | ’O’ | ’P’
 | ’Q’ | ’R’ | ’S’ | ’T’ | ’U’ | ’V’ | ’W’ | ’X’
 | ’Y’ | ’Z’ ->
    true
 | c ->
    false;;

But that’s not minimalistic. It would be if we used pattern ranges to specify the list of letters like this:

let is_uppercase = function
   ’A’ .. ’Z’ -> true
 | c -> false;;

The c1..c2 notation specifies the letters range.
The c pattern variable acts here like a wildcard to match all non-uppercase symbols. Since this is another commonly occurring structure, Ocaml reserves the _ symbol to match anything:

 let is_uppercase = function
    ’A’ .. ’Z’ -> true
  | _ -> false;;

Pattern matching everywhere
What’s really interesting about pattern matching in Ocaml, is that not only pattern matching can be applied to (almost) any primitive type, but that pattern matching constructions are also allowed in a variety of places:

let    pattern = expression

let    identifier pattern . . . pattern =  expression

fun    pattern -> expression

So, for example, if we define a tuple like this:

let p = 1, "Hello";;

We could access its components using pattern matching:

let x, y = p;;

Another example with lists could be:

let aList = [1; 2; 3];;
let hd :: tl = aList in
  Printf.printf "%d" hd;;

The latter code will print “1″.

What can’t be done…
We can’t do pattern matching with one type in Ocaml, and that’s functions:

  match (fun i -> i + 1) with
     (fun i -> i + 1) -> true;;
      ^^^
Syntax error

What about JavaScript?
Unfortunately JavaScript doesn’t have pattern matching. The closest thing to pattern matching that JavaScript has is, well, the Regexp object. In some sense that can be regarded as string pattern matching.
The funniest thing about that fact, is that “string pattern matching” can be used as “function pattern matching” in JavaScript.

JavaScript functions have a method called toSource, that returns a string of the source code of the function. For example:

(function (formalParam1, formalParam2)
     { return formalParam1; }).toSource();

should return (at least in Firefox) the String:

"(function (formalParam1, formalParam2) {return formalParam1;})"

However, applying this method to the native Function class returns the following:

"function Function() {[native code]}"

You can find function pattern matching examples in JavaScript when browsing the “Class” object implementations for any JS framework. Most frameworks that provide a “Class” object to wrap prototypal inheritance in something more OO have to check all methods for calls on “super” in order to effectively call the superclass method. However, we first have to verify that the browser supports a toSource method that actually returns a string of the entire function body. That check is done with simple function pattern matching:

  /xyz/.test(function(){xyz;});

This code will return true if the “xyz” pattern is found in the anonymous function. If true, then we can “search” for super calls in all body functions in order to replace them with the proper superclass method call. A nice example of simple JavaScript inheritance implementation can be seen in John Resig’s post

Although I know this is still a regexp match, It can also be seen as comparing a function’s body to a given pattern and thus I believe it can be called function pattern matching.

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.

On controllers

Controllers are a straightforward way to customize the JavaScript infovis toolkit (JIT) visualizations.
A controller is a JavaScript object that “implements” an interface. The interface methods will then be called at different stages of the visualization, allowing you to, for example, place labels and add event handlers to them, performing actions before and after the animation, making ajax - calls to load data dynamically to the tree, etc.

The controller interface is defined as:

 var ControllerInterface = {

       onCreateLabel: function (domElement, node) {},

       onPlaceLabel: function (domElement, node) {},

       onBeforePlotLine: function(adj) {},

       onAfterPlotLine: function(adj) {},

       onBeforeCompute: function (node) {},

       onAfterCompute: function () {}

       request: function(nodeId, level, onComplete) {},

 };
 

where:

  • onCreateLabel(domElement, node) is a method that receives the label dom element as first parameter, and the homologue JSON tree node as second parameter. This method will only be called on label creation. Note that a JSON tree node is a node from the input tree you provided to the visualization. If you don’t know what kind of JSON tree format is used to feed the visualizations, you should take a look at my other post first, feeding JSON tree structures to the JIT.
    This method proves useful when adding events to the labels used by the JIT.
  • onPlaceLabel(domElement, node) is a method that receives the label dom element as first parameter and the homologue JSON tree node as second parameter. This method is called each time a label has been placed on the visualization, and thus it allows you to update the labels properties, such as size or position. Note that onPlaceLabel will be triggered after updating positions to the labels. That means that, for example, the left and top css properties are allready updated to match the nodes positions.
  • onBeforePlotLine(adj) is called right before plotting an edge. It provides an adjacence object adj. This object contains two important properties, adj.nodeFrom and adj.nodeTo which contain the graph nodes connected by this edge. You can also assign extra information in an adjacency object, by using the data property. This can be done when assigning weighted graph JSON structures to the visualizations. To know more about weighted nodes and edges please check this post.
  • onAfterPlotLine(adj) behaves exactly like onBeforePlotLine except that this method is called right after plotting the adj edge. This method can be useful to restore a lineWidth state you’ve previously changed onBeforePlotLine.
  • onBeforeCompute(node) is a method called right before performing all computation and animations to the JIT visualization. In the case of treemaps this method will be called after entering or exiting a tree level. In the case of Hyperbolic trees, RGraphs and Spacetrees, this method will be triggered right before perfoming an animation.
    For Treemap visualizations, the node parameter corresponds to the root node of the subtree which will be laid out.
    For the Spacetree, Hypertree and RGraph visualizations, the node parameter corresponds to the actual node being clicked or centered on the canvas.
  • onAfterCompute() is a method triggered right after all animations or computations for the JIT visualizations ended.
  • request(nodeId, level, onComplete) is a method only used by the Treemap and Spacetree visualizations. You can use this method to “bufferize” information, so that not all the JSON tree structure is loaded at once. The nodeId parameter is the node actually being requested, the level parameter is the level of the subtree being requested, and the onComplete handler is a function you must call after performing your request. A common structure for the request method could be
    	request: function(nodeId, level, onComplete) {
    		var data;
                    //make a request call to fill the data object and on complete do:
    		onComplete(nodeId, data);
    	},
    

Note that you should not declare any of these methods on your controller object if you are not going to use them.
Note also that is not mandatory to provide a controller object to the main classes.
You can find some example uses for the controller object at the spacetree, hypertree, treemap and rgraph tutorials.
Be sure to know what JSON structure feeds the JIT visualizations before you read the tutorials.
Hope it was helpful.

Feeding JSON tree structures to the JIT

The tree structure that feeds the spacetree, hypertree, treemap and rgraph visualizations is always the same.
Roughly speaking, the JSON tree structure consists of tree 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.
  • children An array of children nodes, or an empty array if the node is a leaf node.

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"}
	],
	children: [/* other nodes or empty */]
};

About datasets:

Sometimes some dataset objects are read by the JIT classes to perform proper layouts.
For example, the treemap class reads the first object’s value for each node’s dataset to perform calculations about the dimensions of the rectangles laid.
Also, if you enable the Config.Color.allow property, the treemap will add colors on the layout, and these colors will be based on your second dataset object value.
RGraphs and Hyperbolic Trees also read the first dataset object value in order to compute node diameters and angular widths, when setting Config.allowVariableNodeDiameters = true.

That’s all for now. I recommend you to read the On controllers section and then some spacetree, hypertree, treemap or RGraph tutorials.