Skip to content

Latest commit

 

History

History
180 lines (141 loc) · 4.99 KB

d3graph.md

File metadata and controls

180 lines (141 loc) · 4.99 KB

#Visualizing graphs with D3

D3 is a general visualization framework, in which most of the visualizations follow a similar convention. To D3, a graph is just a force layout, and that's about it. So, forget about other layouts, graph algorithms or anything graph like.

##Getting started

All OS specific instructions assume a Unix-based system, and are written based on using Mac OS X.

To get started, we'll create a directory d3 as a sandbox, and do a local install of D3 using npm. At the command line run the commands

mkdir d3
cd d3
npm init
npm install d3 --save-dev

The npm install commands will place d3 in the local node_modules directory, with minified code in the d3 subdirectory – so running node, you can require d3.min.js to use it.

##A Basic Page

<!DOCTYPE html>
<meta charset="utf-8">
<style>

.node {
  stroke: #fff;
  stroke-width: 1.5px;
}

.link {
  stroke: #999;
  stroke-opacity: .6;
}

</style>
<body>
  <script src="node_modules/d3/d3.min.js"></script>
  <script src="examplegraph.js"></script>
</body>

##A graph

A graph is specified as an object with properties nodes and edges, which are arrays of node objects and edge objects. A node simply has a name, so could be given as an object like

{ name: 'ANK3' }

while an edge has an ID and a source and target. Unlike the other libraries the source and target are the index of the nodes in the nodes array. So an edge would look like

{ id: 'e1', source: 0, target: 1 }

A fragment of our graph then looks like

var graphdata = {
  nodes: [
    { name: 'ANK3' },
    { name: 'SCN2A' },
    { name: 'FADD' }
  ],
  edges: [
    { id: 'e1', source: 0, target: 1 },
    { id: 'e2', source: 0, target: 2 }
  ]
};

##The setup

Since D3 is a general visualization scheme we have a lot more control over how things are displayed than with the dedicated frameworks. This means we need to setup our canvas for drawing.

var w = 960;
var h = 500;

var colors = d3.scale.category20();

var svg = d3.select("body").append("svg")
    .attr("width", w)
    .attr("height", h);

The layout is handled by a force object that we modify to set the forces used in the layout.

var force = d3.layout.force()
              .charge(-300)
              .linkDistance(80)
              .chargeDistance(300)
              .gravity(0.05)
              .size([w,h]);

Then we attach the graph to the force object, initialize the locations, and then start the layout. We can bound the layout by forcing each tick of the iteraction as a function of the size of the graph. The number of steps depends on the graph, and requires experimentation to see what yields a reasonable result.

var n = graphdata.nodes.length;

force.nodes(graphdata.nodes).links(graphdata.edges);

graphdata.nodes.forEach(function(d, i) { d.x = d.y = w / n * i; });

force.start();
for (var i = 6*n; i > 0; --i) force.tick();
force.stop();

The result of the layout may be anywhere within the frame set for display, so we need to move the graph into the center.

// Center the nodes in the middle.
var ox = 0, oy = 0;
graphdata.nodes.forEach(function(d) { ox += d.x; oy += d.y; });
ox = ox / n - w / 2; oy = oy / n - h / 2;
graphdata.nodes.forEach(function(d) { d.x -= ox; d.y -= oy; });

The appearance of the graph is controlled by attaching visual objects to the components of the graph.

var edges = svg.selectAll('line')
               .data(graphdata.edges)
               .enter()
               .append('line')
               .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; })
               .style("stroke",'#ccc')
               .style('stroke-width',1);

var nodes = svg.selectAll('circle')
               .data(graphdata.nodes)
               .enter()
               .append('circle')
               .attr("cx", function(d) { return d.x; })
               .attr("cy", function(d) { return d.y; })
               .attr("r",5)
               .style("fill",function(d,i){
                    return 'grey'; //colors(i);
                  })
               .call(force.drag);

var labels = svg.selectAll('text')
                .data(graphdata.nodes)
                .enter().append('text')
                .attr('fill','black')
                .attr('transform', function(d) { return 'translate('+(d.x+5)+','+(d.y+5)+')'; })
                .text(function(d) { return d.name; });

Since we are not allowing the graph to float, we set the positions here as well. For text we do this with the transform attribute, while for the other objects we set the x- or y-coordinates. (When the components need to float, we do this with the force.on('tick',-)) function.)

We finally get a graph that looks like

Protein interaction graph visualized with D3 force layout

##Finding D3