Jul 172012

The purpose of this series will be to illustrate the ways in which the javascript visualization library, d3js, is used to create force-directed graphs. Several of the methods used in this series have been illustrated in their examples, but I hope this will unify several of their concepts into a single series.

This post will introduce the basics. The second post will describe consuming gephx datafiles. The third post will describe how to draw various elements typically found in a graph.

A force directed graph in this library is composed of nodes and links which enter, update, and exit the graph as data is changed. These methods provide the developer a good way of controlling the behavior of the graph in the midst of dynamically updating data. Since nobody actually reads stuff, let’s just skip the crap and hop to the code:

Entering, Updating, and Exiting

var drawGraph = function(json)
	// Declare a spot for the graph
	var svg = d3.select("body").append("svg")
		.attr("width", '100%')
		.attr("height", '89%');
	// Create the graph
	var force = d3.layout.force()
		.on('tick', tick)
		.size([width, height]);
	// Add the data
	// Draw the links
	var link = svg.selectAll(".link").data(force.links());
	// Update the new links
	// Remove the old links
	// Draw the nodes
	var node = svg.selectAll(".node").data(force.nodes());
	// Update the new nodes 
	// Remove the old nodes
	// Create the tick function which animates the graph
	function tick()
		link.attr("x1", function(d) { return d.source.x; })
		    .attr("y1", function(d) { return d.source.y; })
		    .attr("x2", function(d) { return d.target.x; })
		    .attr("y2", function(d) { return d.target.y; });
		node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

That’s all we really need to get a very basic graph setup. What took me a long time to figure out is what goes into the nodes array and the links array. In my application, I mapped nodes to an object graph instead of an array for easy updating. Keep in mind, if one plans on taking this route, the object map must be converted into an array for d3js. Luckily, they have a helper function that does this for us:

// Add the data

Nodes need to have a unique id. These can be auto-generated, or mapped explicity. Since I am consuming a data file, unique ids have already been created (more on this in the second post).

// Populate the nodes
for (var node in json.graph.nodes.node)
	// Only add new stuff.  This provides correct behavior on data updates
	if (!(json.graph.nodes.node[node]['@'].id in nodes))
		nodes[json.graph.nodes.node[node]['@'].id] = {"name": json.graph.nodes.node[node]['@'].id, "data": json.graph.nodes.node[node]};
// Populate the links
for (var edge in json.graph.edges.edge)
	links[json.graph.edges.edge[edge]['@'].id] = {"source": nodes[json.graph.edges.edge[edge]['@'].source], "target": nodes[json.graph.edges.edge[edge]['@'].target], "data":json.graph.edges.edge[edge]};				

My “name” and “data” objects are entirely arbitrary and only used to store more data about my data for other purposes in the application – the only important part is the id in nodes, the source, and the target in links. Nodes require an id, and links require references to the entire node object. Simply providing the id to a node in either source or target for links is not enough! This is because d3js updates the node objects to include spatial data like x and y values.
Their documentation describes all the components of these objects – check it out – it will save a bunch of headache.

  • weezyF

    Nice tutorial! Any way you could post just the first few lines of your json file?
    Thanks! weezyF

  • http://protractileaigu.blogspot.fr/ Amirouche

    Thanks it was informative.

  • Pineapple

    2 words: Add. Demos.

  • Luigi Assom

    Could you please post an html + json as example?? it would be very very helpful to work it out.

  • CC

    Hi What are the chances you could give examples of how your json files are laid out?