fbpx

D3 Force Layout Basics

Force Layouts in D3 are a fun and engaging way to visualize data. Because they're so different from how other chart types work it can be difficult to comprehend. In this post I want to walk you through an basic way to learn how D3 Force Layouts work and how to build your first D3 Force Layout graph. Let's get started!

What is a Force Layout in D3?

First the formal, techie, definition: A Force Layout in D3 is a strategy for displaying data elements, visually, that position linked nodes using physical simulation.

That basically means it is a way to draw a collection of data elements (nodes) and how they relate to each other (links) and use some algorithms that represent how things may relate in the physical world. Think stars and planets in the solar system.

Setting up our Data

As is the toughest challenge in all of data visualization, how we structure our data is important. The simplest way is to have two different data sets; one for nodes and one for links.

Doing it that way isn't fun though and is extra work, so a better way I feel is to have one data set which represents both the nodes and links. Here is a simple example:

[code]

var data = [

{ source: ‘Baratheon', target:'Lannister' }
{ source: ‘Baratheon', target:'Stark' }
{ source: ‘Lannister', target:'Stark' }
{ source: ‘Stark', target:'Bolton' }

];

[/code]

Notice this is a flat data structure compared to a nested one. It's easier that way for Force Layouts in D3, trust me.

Parsing our Data

Since we're just using a single data set here which contains the links, we need to tease out the nodes in there by parsing our data with a forEach loop. This code reads in our data above, and for each “row” in our data (the stuff between each set of curly braces) it will add a node to our empty nodes data set.

[code]

    // create empty nodes array
    var nodes = {};
    
    // compute nodes from links data
    links.forEach(function(link) {
        link.source = nodes[link.source] ||
            (nodes[link.source] = {name: link.source});
        link.target = nodes[link.target] ||
            (nodes[link.target] = {name: link.target});        
    });

[/code]

Building the Layout

Winter has come! (Jon Snow Voice) It's time to build our force layout…

We start by adding a Scalable Vector Graphic (SVG) to our page which will allow us to then add our D3 Force Layout.

[code]

    // add a SVG to the body for our viz
    var svg=d3.select('body').append('svg')
        .attr('width', width)
        .attr('height', height);

[/code]

After adding the SVG we can build our force layout

[code]
    // use the force
    var force = d3.layout.force() //build the layout
        .size([width, height]) //specified earlier
        .nodes(d3.values(nodes)) //add nodes
        .links(links) //add links
        .on("tick", tick) //what to do
        .linkDistance(300) //set for proper svg size
        .start(); //kick the party off!
[/code]

With the layout built, it's time to add our links and nodes

[code]

    // add the links
    var link = svg.selectAll('.link')
        .data(links)
        .enter().append('line')
        .attr('class', 'link'); 
    // add the nodes
    var node = svg.selectAll('.node')
        .data(force.nodes()) //add
        .enter().append('circle')
        .attr('class', 'node')
        .attr('r', width * 0.03); //radius of circle

[/code]

Lastly, we need to tell the Force Layout what to do on each ‘tick', which is essentially everytime you mess with the display by dragging things around. It's more complicated than that, but it's nice to think about it this way.

[code]

    function tick(e) {
        
        node.attr('cx', function(d) { return d.x; })
            .attr('cy', function(d) { return d.y; })
            .call(force.drag);
            
        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; });
        
    }
[/code]

This function helps us because it tells D3 to use the force.drag function which makes things fun. Then it also updates the position of each node as well as the start (x1, y1) and end (x2,y2) of the links.

 

Revel in Your Accomplishment!

It's time to sit back, enjoy a good craft  brew (I'm biased towards San Diego breweries), and appreciate what you've done. If you want to take it to the next level, check out the following examples here:

 

Want to learn more? Get a Free Trial to Pluralsight here and check out my D3 Fundamentals course!

Final Working Result

2 Comments

  1. I enjoyed the webinar. There is only one thing I struggle with understanding;
    links.forEach(function(link) {
    link.source = nodes[link.source] ||
    (nodes[link.source] = {name: link.source});
    link.target = nodes[link.target] ||
    (nodes[link.target] = {name: link.target});
    });
    I would like the output to be {id: “name1”} instead of how it is now: name1: {name: “name1”}

    Appreciate a pointer. I can alway write this as a for loop.

    Thanks

  2. Hi.
    Its really is a great tutorial . but I want to ask that How to add image to every circle.
    I want to display image on every node inside circle

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.