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

We will create a boatload of examples over the next two chapters. We start with hierarchical layouts (located in the d3-hierarchy module) because they're the most consistent in terms of data structure, which looks something like this:

{ "name": "Tywin Lannister", 
"children": [{
"name": "Jamie Lannister",
"children": [
{ "name": "Joffery Baratheon" },
{ "name": "Myrcella Baratheon" },
{ "name": "Tommen Baratheon" }
]}, {
"name": "Tyrion Lannister",
"children": []
}]}

An hierarchy can include lots of other data, but the main thing is it needs to be a nested series of objects, with this implicit hierarchy defined via an attribute (by default, named children). We can also use a helper function in d3-hierarchy, d3.stratify(), to convert a flat hierarchy like the following to the structure needed by our hierarchical layouts:

[ 
{"name": "Westeros", "parent": ""},
{"name": "Tywin Lannister", "parent": "Westeros"},
{"name": "Jamie Lannister", "parent": "Tywin Lannister"},
{"name": "Tyrion Lannister", "parent": "Tywin Lannister"},
{"name": "Joffery Baratheon", "parent": "Jamie Lannister"},
]

Note that each of these need one (and only one) root node, or rather, a node that has no parent and everything stems from. If you don't have a root node (or multiple root nodes), D3 will throw an exception, and nothing will work. In this case, we call our parent node Westeros because that's where all these folks live.

Be forewarned that we're using a dataset containing a boatload of data from a particular epic fantasy television show, up to and including season 6. It's possible that you might totally spoil a few of the surprises in the show if you dig too deeply into the data, so I'd recommend that you get caught up beforehand if this concerns you.

If you find Game of Thrones annoying, I'm really, really sorry.

We will use a third-party library here to generate legends. Install it using the following command:

    npm install d3-svg-legend --save

We could make a bunch of legends ourselves and it'd be a fun exercise and stuff, but really you can probably figure out what's going on already -- our new library uses a scale to create a bunch of SVG squares and text labels. Yawn, right?

It's been a while since we added anything to lib/common/index.js -- we're going to put a lot of stuff in there because we share a lot of functionalities across charts; add the following:

export const colorScale = d3.scaleOrdinal()
.range(d3.schemeCategory20);
export const heightOrValueComparator =
(a, b) => b.height - a.height || b.value - a.value;
export const valueComparator = (a, b) => b.value - a.value;

export const fixateColors = () => {};
export const addRoot = () => {};
export const tooltip = () => {};
export const descendantsDarker = () => {};

We start by creating a color scale using the Category20 20-color scheme, as well as a few custom comparators we'll use over and over. We also create a bunch of empty functions, which we'll flesh out in just a second.

Time to start building charts! We will use chartFactory to create a base chart that we then will use throughout. Create a new folder in lib/ called chapter6/ and then create a new file in chapter6/ called index.js. Add the following to it:

import * as d3 from 'd3';
import * as legend from 'd3-svg-legend';
import chartFactory, {
fixateColors,
addRoot,
colorScale as color,
tooltip,
heightOrValueComparator,
valueComparator,
descendantsDarker,
} from '../common';

In the preceding code, we import D3, our 3rd-party legend tool, and a boatload of stuff from our common functionality module.

Next, add the following:

const westerosChart = chartFactory({ 
margin: { left: 50, right: 50, top: 50, bottom: 50 },
padding: { left: 10, right: 10, top: 10, bottom: 10 },
});

This adds a padding object to our chart's prototype, which we'll use to make things a bit easier. Next, we will add a data loading function, which differentiates between a CSV or JSON file:

westerosChart.loadData = async function loadData(uri) { 
if (uri.match(/.csv$/)) {
this.data = d3.csvParse(await (await fetch(uri)).text());
} else if (uri.match(/.json$/)) {
this.data = await (await fetch(uri)).json();
}
return this.data;
};

Yay, more async/await fun; nice and straightforward. Parsed as CSV or JSON, assign it to the local context, then return that value. We will add a function that we'll use to initialize our charts and pass arguments to them:

westerosChart.init = function initChart(chartType, dataUri, ...args) {
this.loadData(dataUri).then(data => this[chartType].call(this, data, ...args));
this.innerHeight = this.height - this.margin.top - this.margin.bottom - this.padding.top - this.padding.bottom;
this.innerWidth = this.width - this.margin.left - this.margin.right -
this.padding.left - this.padding.right;
};

As our loadData() function is marked as async, it always returns a promise. We could make init() an async function and just await the results from loadData(), but let's just keep it simple and use .then() to resolve the promise returned by loadData(). This way the rest of init() doesn't have to wait until the data's resolved.

The ...args bit in the constructor's arguments is a new feature in ES2015 called the rest parameter. It collects every argument after the ones specifically defined as an array. We then use another new ES2015 feature, the spread operator, in this[chartType].call(this, ...args) to destructure the array into its individual values. This lets us add as many arguments as we want to each chart method we're writing.

The rest of .init() just sets up two properties, innerHeight and innerWidth, that we'll use to make adding legends and axes easier and less verbose.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset