Reader small image

You're reading from  D3.js 4.x Data Visualization - Third Edition

Product typeBook
Published inApr 2017
Reading LevelIntermediate
PublisherPackt
ISBN-139781787120358
Edition3rd Edition
Languages
Tools
Right arrow
Authors (2):
Aendrew Rininsland
Aendrew Rininsland
author image
Aendrew Rininsland

<p>Aendrew Rininsland is a developer and journalist who has spent much of the last half a decade building interactive content for newspapers such as The Financial Times, The Times, Sunday Times, The Economist, and The Guardian. During his 3 years at The Times and Sunday Times, he worked on all kinds of editorial projects, ranging from obituaries of figures such as Nelson Mandela to high-profile, data-driven investigations such as The Doping Scandal the largest leak of sporting blood test data in history. He is currently a senior developer with the interactive graphics team at the Financial Times.</p>
Read more about Aendrew Rininsland

Swizec Teller
Swizec Teller
author image
Swizec Teller

Swizec Teller is a geek with a hat. Founding his first startup at 21, he is now looking for the next big idea as a full-stack web generalist focusing on freelancing for early-stage startup companies. When he isn't coding, he's usually blogging, writing books, or giving talks at various non-conference events in Slovenia and nearby countries. He is still looking for a chance to speak at a big international conference. In November 2012, he started writing Why Programmers Work at Night, and set out on a quest to improve the lives of developers everywhere.
Read more about Swizec Teller

View More author details
Right arrow

Chapter 6. Hierarchical Layouts of D3

Part of the process of learning to do cool things with D3 is looking at examples on bl.ocks.org. This is great, as you have a living, forkable code base that you can modify into something specific to your use case, but part of what makes learning D3 difficult is that quite often, these rely on layouts, which are effectively algorithms that restructure data in a certain way. These can seem really opaque if you don't know how they work.

Note

D3 v4 alert! Everything in this and the next chapters has changed significantly with D3 v4. Now more than ever, make sure that you pay attention to which version of D3 an example uses when looking at them online.

Over the course of the next two chapters, we'll be diving into layouts and building a ludicrous number of quick charts. First, we start with hierarchical layouts, which assumes a data structure with parent and child nodes; in the next chapter, we'll look at some of the other layouts, which include things such...

What are layouts and why should you care?


D3 layouts are modules that transform data into drawing rules. The simplest layout might only transform an array of objects into coordinates, like a scale.

However, we usually use layouts for more complex visualizations, such as drawing a force-directed graph or a tree, for instance. In these cases, layouts help us to separate calculating coordinates from putting pixels on the screen. This not only makes our code cleaner, but it also lets us reuse the same layouts for vastly different visualizations.

Built-in layouts

By default, D3 comes with around a dozen built-in layouts that cover most common visualizations. They can be split roughly into normal and hierarchical layouts. Normal layouts represent data in a flat hierarchy, whereas hierarchical layouts generally present data in a tree-like structure. 

The normal (non-hierarchical) layouts are as follows:

  • Histogram
  • Pie
  • Stack
  • Chord
  • Force

The hierarchical layouts are as follows:

  • Tree
  • Cluster
  • Tree map
  • Partition
  • Pack...

Hierarchical layouts


All hierarchical layouts are based on an abstract hierarchy layout designed for representing hierarchical data: a recursive parent-child structure. As mentioned earlier, imagine a tree or an organization chart.

All the code for the partition, tree, cluster, pack, and treemap layouts are defined in the d3-hierarchy module, and they all follow similar design patterns. The layouts are very similar and share lots of common aspects; so, to avoid repeating ourselves a whole bunch, we'll look at the common stuff first, and then focus on the differences.

First of all, we need some hierarchical data. In the book repository, I've provided a file called GoT-lineages-screentimes.json that contains all the character lineages (by father), the amount of time spent on screen, and the number of episodes a character is in. We'll use genealogies of each character as a way of creating a hierarchy, then time on screen to size various page elements in layouts that necessarily need them (I don...

Tree the whales!


Let's start with the most basic of hierarchical charts -- a tree! Create a new function and fill it with the following:

westerosChart.tree = function Tree(_data) { 
  const data = getMajorHouses(_data); 
  const chart = this.container; 
  const stratify = d3.stratify() 
    .parentId(d => d.fatherLabel) 
    .id(d => d.itemLabel); 
  const root = stratify(data); 
  const layout = d3.tree() 
    .size([ 
      this.innerWidth, 
      this.innerHeight, 
    ]); 
}

We use our next-to-be-written getMajorHouses() function to filter out characters who don't have a fatherLabel property and whose itemLabel isn't set as anybody's fatherLabel property. We then create a new stratify object and set its parentId() accessor function to each item's fatherLabel and the id() accessor to each item's itemLabel. We're able to do the latter with this dataset because we know that each itemLabel is distinct; if this was not the case (for instance, if you had a dataset where there were a few...

Muster the cluster!


Another type of similar diagram is a dendrogram, which uses D3's cluster layout and puts all leaf nodes of a tree at the same depth. Let's create that now. Comment out the westerosChart.init() line in main.js and add this beneath it:

westerosChart.init('cluster', 'data/GoT-lineages-screentimes.json');

Go back to chapter6/index and add the following:

westerosChart.cluster = function Cluster(_data) { 
  const data = getMajorHouses(_data); 
  const stratify = d3.stratify() 
    .parentId(d => d.fatherLabel) 
    .id(d => d.itemLabel); 

  const root = stratify(data); 

  fixateColors(houseNames(root), 'id'); 

  const layout = d3.cluster() 
    .size([ 
      this.innerWidth - 150, 
      this.innerHeight, 
    ]); 

  const links = layout(root) 
    .descendants() 
    .slice(1); 
}

This should look familiar already--we get our data, create a stratify generator, then use it on our data. We then create a cluster layout, give it a size (though, here we subtract 150 pixels...

Money for nothing, treemaps for free (maps)


Despite the similar name, treemaps bear little visual resemblance to the tree layout we used earlier; instead, they divide a tree into rectangular regions. This requires us to have a dimension to our data; in this case, we'll size the treemap regions based on screen time, nesting each region into its parent. As such, the size of each parent will be the sum of its children, plus its own value.

This is all going to start looking really similar, so we will start writing all of our common functions now. In common/index, add this function, which will give us increasingly deeper gradiations based on a hierarchy:

 export const descendantsDarker =  (d, color, invert = false, dk = 5) => 
   d3.color(
     color(
       d.ancestors()[d.ancestors().length - 2].id.split(' ').pop()
     ) )[invert ? 'brighter' : 'darker'](d.depth / dk);

This takes a datum, a color scale, and then three options: a Boolean to make the scale go brighter instead of darker, a numerical...

Smitten with partition


These next few charts are just going to fly by; they're really similar and we don't have anything else in terms of common functions to write.

Adjacency diagrams are similar to treemaps, but are intended to fill the available space. They tend to be useful for visualizing things such as disk usage. They are created by the partition layout.

Here's our example code in full:

westerosChart.partition = function Partition(_data) { 
  const data = getMajorHouses(_data); 
  const stratify = d3.stratify() 
    .parentId(d => d.fatherLabel) 
    .id(d => d.itemLabel); 

  const root = stratify(data) 
    .sum(d => d.screentime) 
    .sort(heightOrValueComparator); 

  const layout = d3.partition() 
    .size([ 
      this.innerWidth - 100, 
      this.innerHeight, 
    ]) 
    .padding(2) 
    .round(true); 

  layout(root); 

  const nodes = this.container.selectAll('.node') 
    .data(root.descendants().slice(1)) 
    .enter() 
    .append('g') 
      .attr('class', 'node...

Pack it up, pack it in, let me begin...


The pack layout produces charts similar to treemaps, but using round nodes. This is probably the best of the last three charts in this section for representing this hierarchy -- remember how we needed to use a ludicrous amount of padding in our treemap to make the parent nodes more visible? Since circle packing diagrams use more space, the parent nodes are more visible and that relationship is more pronounced.

Like before, comment out the other westerosChart.init() lines in main.js and add this:

westerosChart.init('partition', 'data/GoT-lineages-screentimes.json');

Next, add the following to chapter6/index.js:

westerosChart.pack = function Pack(_data) { 
  const data = getMajorHouses(_data); 

  const stratify = d3.stratify() 
    .parentId(d => d.fatherLabel) 
    .id(d => d.itemLabel); 

  const root = stratify(data) 
    .sum(d => d.screentime) 
    .sort(valueComparator); 

  const houseColors = color.copy().domain(houseNames(root)); 
  fixateColors...

Bonus chart! Sunburst radial partition joy!


Oh, you thought we were done? Let's squeeze one more chart out of d3-hierarchy before moving on.

If you remember, the partition chart was a bit funky, largely because it's mainly used in datasets where only the leaf nodes (that is, the outermost nodes without children) have a value. Since some of the parents in our dataset have screentime values, this sort of distorts it and makes it look odd. We will re-render that, but make it all cool and circular this time.

You know the drill. main.js:

westerosChart.init('radialPartition', 'data/GoT-lineages-screentimes.json');

In chapter6/index.js:

westerosChart.radialPartition = function RadialPartition(_data) { 
  const data = getMajorHouses(_data)
     .map((d, i, a) => Object.assign(d, {
       screentime: a.filter(v =>
         v.fatherLabel === d.itemLabel).length ? 0 : d.screentime,
     })   ); 
  const radius = Math.min(this.innerWidth, this.innerHeight) / 2; 
};

We start by creating a radius that...

Summary


Despite the near mythical power of D3 layouts, they turn out to be nothing more than helpers that turn your data into a collection of coordinates. We've used the hierarchical layouts to create a boatload of different charts, without much more than a few lines of code differing between implementations.

These charts are so simple, we could have really extended our base object a lot to be far more abstracted; so by doing things such as fixating colors and adding the legend during the init() method -- between chapter6/index and common/index -- we've added probably around 600 lines, and we could probably reduce that by a couple of hundred. However, it's also useful to create each chart separately to reinforce the workflow needed by the hierarchical layouts, as I've done in this chapter.

In the next chapter, we'll look at a few more layouts. They'll look pretty familiar to the hierarchical ones in terms of how we write them, but we'll be flipping around the data a fair bit more to accommodate...

lock icon
The rest of the chapter is locked
You have been reading a chapter from
D3.js 4.x Data Visualization - Third Edition
Published in: Apr 2017Publisher: PacktISBN-13: 9781787120358
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $15.99/month. Cancel anytime

Authors (2)

author image
Aendrew Rininsland

<p>Aendrew Rininsland is a developer and journalist who has spent much of the last half a decade building interactive content for newspapers such as The Financial Times, The Times, Sunday Times, The Economist, and The Guardian. During his 3 years at The Times and Sunday Times, he worked on all kinds of editorial projects, ranging from obituaries of figures such as Nelson Mandela to high-profile, data-driven investigations such as The Doping Scandal the largest leak of sporting blood test data in history. He is currently a senior developer with the interactive graphics team at the Financial Times.</p>
Read more about Aendrew Rininsland

author image
Swizec Teller

Swizec Teller is a geek with a hat. Founding his first startup at 21, he is now looking for the next big idea as a full-stack web generalist focusing on freelancing for early-stage startup companies. When he isn't coding, he's usually blogging, writing books, or giving talks at various non-conference events in Slovenia and nearby countries. He is still looking for a chance to speak at a big international conference. In November 2012, he started writing Why Programmers Work at Night, and set out on a quest to improve the lives of developers everywhere.
Read more about Swizec Teller