D3.js Layouts

by Peter Cook / @animateddata

D3

A library of components for building data visualisations

(It's not a 'charting library' like Highcharts)

For example:

DOM manipulation

scale functions

axes

projections

and...

Layouts

to help create non-trivial charts such as trees, networks etc.

For example

Tree

Circle packing

Circular partition

Stacked area

Force

Layouts

A D3 layout is a function that transforms and augments your data

(Typically your data is the input and array(s) are output)

For example:


var data = [10, 20, 30];

var pieLayout = d3.layout.pie();

pieLayout(data);

data[0];
-> {data: 10, endAngle: 6.28, startAngle: 5.24, value: 10}
            

(The pie layout is one of the simplest layouts)

Adding the path elements...


  var arcGenerator = d3.svg.arc()
    .innerRadius(50)
    .outerRadius(100);

  d3.select('svg g.pie')
    .selectAll('path')
    .data(data)
    .enter()
    .append('path')
    .attr('d', arcGenerator);
            

Creating and using a layout


// pie layout
var pieLayout = d3.layout.pie();  // returns a function
var mySegments = pieLayout(data);  // returns an array of pie segments

// tree layout
var treeLayout = d3.layout.tree();  // returns a function
var myNodes = treeLayout(data);  // returns an array of nodes

Configuration

D3 layouts are configurable e.g.


var forceLayout = d3.layout.force()
  .charge(-2000)
  .linkDistance(200)
  .gravity(0.5)
  .size([300, 300])
  .on('tick', forceTick);

Built-in layouts

Hierarchies

Force

Other

Hierarchies

Hierarchy layouts

Tree

Partition (v)

Circle packing (v)

Treemap (v)

v = leaf nodes have an associated value

Data structure

nested object

{
  children: [
    {
    },
    {
      children: [
        {
        },
        {
        }        
      ]
    }
  ]
}

With names and values

{
  name: 'Top of the tree',
  children: [
    {
      name: 'A leaf node',
      value: 10
    },
    {
      name: 'In the middle',
      children: [
        {
          name: 'Another leaf',
          value: 20
        },
        {
          name: 'One more leaf',
          value: 15
        }        
      ]
    }
  ]
}

Tree layout


var treeLayout = d3.layout.tree()
  .size([600, 400]);

var nodes = treeLayout(data);
var links = treeLayout.links(nodes);

d3.select('svg')
  .selectAll('circle')
  .data(nodes)
  .enter()
  .append('circle')
  .attr('cx', function(d) {return d.x;})
  .attr('cy', function(d) {return d.y;});

View

Partition


var partitionLayout = d3.layout.partition()
  .size([200, 200]);

var nodes = partitionLayout(data);

d3.select('svg')
  .selectAll('rect')
  .data(nodes)
  .enter()
  .append('rect')
  .attr('x', function(d) {return d.x;})
  .attr('y', function(d) {return d.y;})
  .attr('width', function(d) {return d.dx;})
  .attr('height', function(d) {return d.dy;});

View (circular)

Circle packing


var packLayout = d3.layout.pack()
  .size([400, 400])
  .padding(4);

var nodes = packLayout(data);

d3.select('svg')
  .selectAll('circle')
  .data(nodes)
  .enter()
  .append('circle')
  .attr('cx', function(d) {return d.x;})
  .attr('cy', function(d) {return d.y;})
  .attr('r', function(d) {return d.r;});

View

Force

Force layouts

Force

Custom forces

Data structures

Nodes and links


var nodes = [
  {},
  {},
  ...
];

var links = [
  {source: 0, target: 1},
  {source: 1, target: 5},
  ...
];

Force layout

var forceLayout = d3.layout.force()
    .size([500, 500])
    .on('tick', forceTick);

forceLayout
  .nodes(nodes)
  .links(links)
  .start();

function forceTick() {
  d3.selectAll('line.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;});

  d3.selectAll('circle.node')
    .attr('cx', function(d) {return d.x;})
    .attr('cy', function(d) {return d.y;});
}

View

Force with custom forces

var forceLayout = d3.layout.force()
    .size([500, 500])
    .on('tick', forceTick);

forceLayout
  .nodes(nodes)
  .start();

function forceTick(e) {
  var k = 0.5 * e.alpha;

  d3.selectAll('circle.node')
    .attr('cx', function(d) {
      d.x += (foci[d.group].x - d.x) * k;
      return d.x;
    })
    .attr('cy', function(d) {
      d.y += (foci[d.group].y - d.y) * k;
      return d.y;
    });
}

View

Other

Other layouts

Pie

Stack

Histogram

Chord

Pie layout

var data = [
  {name: 'Apples', percent: 40},
  {name: 'Oranges', percent: 30},
  {name: 'Bananas', percent: 10},
  {name: 'Grapes', percent: 20}
];

var pieLayout = d3.layout.pie()
  .value(function(d) {return d.percent;});

data = pieLayout(data);

var arcGenerator = d3.svg.arc()
  .innerRadius(50)
  .outerRadius(100);

var colorScale = d3.scale.category10();

d3.select('svg g.pie')
  .selectAll('path')
  .data(data)
  .enter()
  .append('path')
  .attr('d', arcGenerator)
  .style('fill', function(d, i) {return colorScale(i);});

View

Histogram layout

var histogram = d3.layout.histogram().bins(15);
var data = [3.4, 4.2, 1.2, 1.6, 7.6, ...];
var bins = histogram(data);

d3.select('svg')
  .selectAll('rect')
  .data(bins)
  .enter()
  .append('rect')
  .attr('y', function(d, i) {
    return i * barWidth;
  })
  .attr('height', barWidth)
  .attr('x', 0)
  .attr('width', function(d) {
    return xScale(d.y);
  });

View

Data visualisation design

I always recommend following a design process such as:


What problem are we trying to solve?

What data do we have?

How to map the data to graphical marks to solve the problem?


...rather than: 'which chart type should I use?'

Takeaways

D3 provides fine-grained components for building visualisations

Layouts transform your data, usually into arrays

Force layout not just for networks

Further D3 Workshop (Brighton)

Layouts, geo, resuable components and more!

March 11th 2015

See animatedata.co.uk/training

By Peter Cook / animateddata.co.uk