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() .gravity(.2) .distance(250) .charge(-1000) .on('tick', tick) .size([width, height]); // Add the data force.nodes(nodes) .links(links) .start(); // Draw the links var link = svg.selectAll(".link").data(force.links()); // Update the new links link.enter().append("line"); // Remove the old links link.exit().remove(); // Draw the nodes var node = svg.selectAll(".node").data(force.nodes()); // Update the new nodes node.enter().append("svg:g"); // Remove the old nodes node.exit().remove(); // 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 force.nodes(d3.values(nodes)) .links(dr.values(links)) .start(); |
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
-
http://protractileaigu.blogspot.fr/ Amirouche
-
Pineapple
-
Luigi Assom