Simple D3.js Force Layout Example in less than 100 lines of code

Force Layout ExampleThis post is the follow through on the commitment I made in this one. Namely, a simple D3.js example (in the style of a Tut) aimed at Healthcare Analysts.

I’ve chosen one of the most simple and flexible diagram styles – the Force Layout Diagram.

Following my own advice in this post, I’ll start with the data. I’ve chose JSON for readability over csv. However, health care analysts (not being programmers) are pretty unfamiliar with JSON. So if this is too inaccessible, I’ll do a csv or SQL version.

I created two files containing my JSON data. The first (nodes.json) contains a list of my force layout nodes. Each object/node in the list has an implicit index number starting at position zero. Creighton Hospital would occupy the zero position in this example.

You can cut and paste this into a text editor and save as nodes.json into the working folder on your webserver.

[
    {
        "name": "Creighton Hospital"
    },
    {
        "name": "Heart Hospital"
    },
    {
        "name": "Marshall Regional Medical Center"
    },
    {
        "name": "McKennan Hospital & University Health Center"
    },
    {
        "name": "Queen of Peace Hospital"
    },
    {
        "name": "Sacred Heart Hospital"
    },
    {
        "name": "St. Luke's Hospital"
    },
    {
        "name": "St. Mary's Hospital"
    },
    {
        "name": "Milbank Area Hospital"
    },
    {
        "name": "Pipestone County Medical Center"
    },
    {
        "name": "St. Michael's Hospital"
    },
    {
        "name": "Wagner Community Memorial Hospital"
    }
]

The second file (links.json) identifies the start and end points for the connections between the nodes. Nodes are identified by the implied index position identified earlier. So, “source”:1 in links.json would be a reference to “Heart Hospital” in nodes.json. Note that some nodes intentionally have no connections but appear to connect to themselves.

You can cut and paste this into a text editor and save as links.json into the working folder on your webserver, alongside your nodes.json file.

[
    {
        "source": 0,
        "target": 0
    },
    {
        "source": 1,
        "target": 1
    },
    {
        "source": 1,
        "target": 7
    },
    {
        "source": 2,
        "target": 2
    },
    {
        "source": 3,
        "target": 1
    },
    {
        "source": 3,
        "target": 3
    },
    {
        "source": 3,
        "target": 9
    },
    {
        "source": 4,
        "target": 4
    },
    {
        "source": 6,
        "target": 5
    },
    {
        "source": 6,
        "target": 3
    },
    {
        "source": 8,
        "target": 8
    },
    {
        "source": 10,
        "target": 3
    },
    {
        "source": 11,
        "target": 3
    },
    {
        "source": 11,
        "target": 11
    }
]

I’ve chosen to put the node and link data in two separate files to make the mental separation between the two types required for a Force Layout diagram.

Getting used to identifying nodes this way (by index number) and separating them from their links, will help make the leap to Sankey diagrams a little less challenging.

This pattern also helps if you’re going to load this data dynamically from a database as each requires different SQL queries to generate them. Trying to concatenate SQL results into one JSON file on the fly can be an unnecessary PITA.

I’ve chosen to load my two data files with the queue library (lines 42-45). I like this approach as it avoids async page vs data loading issues (lots of questions at StackOverflow on this).

You can copy this combined JavaScript and HTML code and paste it into your index.html file.

I apologize in advance for the crappy code syntax highlighting and formatting offered by wordpress.com.


<!DOCTYPE html>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<html lang="en">
<html>
  <head>
  <script 
      type="text/javascript" i
      src="http://d3js.org/d3.v3.min.js">
  </script>
  <script 
      type="text/javascript" 
      src="http://d3js.org/queue.v1.min.js"> 
  </script>
</head>
<body>
<script type="text/javascript">

/* Set the diagrams Height & Width */
    var h = 500, w = 950;
/* Set the color scale we want to use */
    var color = d3.scale.category20();
/* Establish/instantiate an SVG container object */
    var svg = d3.select("body")
                    .append("svg")
                    .attr("height",h)
                    .attr("width",w);
/* Build the directional arrows for the links/edges */
        svg.append("svg:defs")
                    .selectAll("marker")
                    .data(["end"]) 
                    .enter().append("svg:marker")
                    .attr("id", String)
                    .attr("viewBox", "0 -5 10 10")
                    .attr("refX", 15)
                    .attr("refY", -1.5)
                    .attr("markerWidth", 6)
                    .attr("markerHeight", 6)
                    .attr("orient", "auto")
                    .append("svg:path")
                    .attr("d", "M0,-5L10,0L0,5");
/* Pre-Load the json data using the queue library */
queue()
    .defer(d3.json, "nodes.json")
    .defer(d3.json, "links.json")
    .await(makeDiag); 
/* Define the main worker or execution function */
function makeDiag(error, nodes, links, table) {
    /* Draw the node labels first */
   var texts = svg.selectAll("text")
                    .data(nodes)
                    .enter()
                    .append("text")
                    .attr("fill", "black")
                    .attr("font-family", "sans-serif")
                    .attr("font-size", "10px")
                    .text(function(d) { return d.name; }); 
    /* Establish the dynamic force behavor of the nodes */
    var force = d3.layout.force()
                    .nodes(nodes)
                    .links(links)
                    .size([w,h])
                    .linkDistance([250])
                    .charge([-1500])
                    .gravity(0.3)
                    .start();
    /* Draw the edges/links between the nodes */
    var edges = svg.selectAll("line")
                    .data(links)
                    .enter()
                    .append("line")
                    .style("stroke", "#ccc")
                    .style("stroke-width", 1)
                    .attr("marker-end", "url(#end)");
    /* Draw the nodes themselves */                
    var nodes = svg.selectAll("circle")
                    .data(nodes)
                    .enter()
                    .append("circle")
                    .attr("r", 20)
                    .attr("opacity", 0.5)
                    .style("fill", function(d,i) { return color(i); })
                    .call(force.drag);
    /* Run the Force effect */
    force.on("tick", function() {
               edges.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; });
               nodes.attr("cx", function(d) { return d.x; })
                    .attr("cy", function(d) { return d.y; })
               texts.attr("transform", function(d) {
                        return "translate(" + d.x + "," + d.y + ")";
                        });
               }); // End tick func
}; // End makeDiag worker func
</script>
</body>
</html>

Much of the “.style” that’s going on can be moved into a css file. For newcomers to all this, I’ve chosen to leave it here to make tweaking simpler. So now you should have three files in the folder on your webserver.

 nodes.json
 links.json
 index.html

You can now point your web browser at the folder on your webserver and behold your force layout diagram.

Advertisements

4 thoughts on “Simple D3.js Force Layout Example in less than 100 lines of code

  1. Hey, excellent write up thank you for the tutorial.

    I have a quick question : why the nodes get interfered to each other I mean why the node are not independent, I want them to stay away but not get mixed, I believe that is because of the gravity or the charge ? Could you show me an example please.

  2. I am trying to create dynamically changing diagram. The json files could be kept changing, I’d like to see the graph change when json changes, and disable the animation. Is it doable? Thanks.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s