This example is a revived piece of old code from February 2014.
It shows causes of death in an interactive set of linked visualizations, a tree navigator and a stacked area chart.
// This module provides short names for causes of death, | |
// for use as labels for concise presentation. | |
// | |
// Curran Kelleher 2/18/2014 | |
define([], function () { | |
var shortNames = { | |
'Major cardiovascular diseases': 'Cardiovascular diseases', | |
'Symptoms, signs, and abnormal clinical and laboratory findings, not elsewhere classified': 'Unclassified conditions', | |
'Chronic lower respiratory diseases': 'Respiratory diseases', | |
'Pneumonitis due to solids and liquids': 'Pneumonitis', | |
'Chronic liver disease and cirrhosis': 'Liver disease', | |
'Complications of medical and surgical care': 'Complications of care', | |
'Benign/other neoplasms': 'Neoplasms' | |
}; | |
// Gets a short version of a given cause of disease | |
// for use as labels. | |
return function getShortName(name) { | |
var shortName = shortNames[name]; | |
return shortName ? shortName : name; | |
}; | |
}); |
<!-- This page is an example visualization of Cause of Death data | |
from the Centers for Disease Control. | |
Details of the data can be found here: | |
https://github.com/curran/data/tree/gh-pages/cdc/mortality | |
The visualization draws from this D3 example: | |
http://bl.ocks.org/mbostock/3885211 | |
Curran Kelleher | |
2/13/2014 | |
--> | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset=utf-8 /> | |
<title>Causes of Death</title> | |
<script src="http://d3js.org/d3.v3.js"></script> | |
<script src="http://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script> | |
<style> | |
body { | |
font: 12px sans-serif; | |
} | |
.title { | |
text-anchor: middle; | |
font-size: 2em; | |
} | |
/* Begin style for stacked area visualization. */ | |
.axis path, | |
.axis line { | |
fill: none; | |
stroke: #000; | |
shape-rendering: crispEdges; | |
} | |
/* End style for stacked area visualization. */ | |
/* Begin style for tree visualization. */ | |
.node circle { | |
stroke: #000; | |
stroke-width: 1.5px; | |
} | |
.node .with-children { | |
fill: #000; | |
} | |
.node .without-children { | |
fill: #FFF; | |
} | |
.node { | |
font: 12px sans-serif; | |
} | |
.link { | |
fill: none; | |
stroke: #ccc; | |
stroke-width: 1.5px; | |
} | |
/* End style for tree visualization. */ | |
</style> | |
</head> | |
<body> | |
<script data-main="main" src="http://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.10/require.min.js"></script> | |
</body> | |
</html> |
// A visualization of Cause of Death data from the Centers for Disease Control. | |
// | |
// Details of the data can be found here: | |
// https://github.com/curran/data/tree/gh-pages/cdc/mortality | |
// | |
// Curran Kelleher | |
// 2/18/2014 | |
require(['tree', 'stackedArea'], function (tree, stackedArea) { | |
// Fetch the data as an AMD module. | |
var tableURL = 'http://curran.github.io/data/cdc/mortality/mortality_full.js', | |
hierarchyURL = 'http://curran.github.io/data/cdc/mortality/hierarchy/hierarchy.js'; | |
require([tableURL, hierarchyURL], function(table, hierarchy){ | |
// The dimensionsions of the SVG shared by both visualizations. | |
var outerWidth = 960, | |
outerHeight = 500, | |
// The number of pixels from the left where the | |
// two visualizations meet. | |
horizontalSplit = 350, | |
// The margins for each visualization. | |
margins = { | |
tree: { | |
top: 20, | |
right: outerWidth - horizontalSplit + 160, | |
bottom: 27, | |
left: 8 | |
}, | |
stackedArea: { | |
top: 27, | |
right: 170, | |
bottom: 30, | |
left: horizontalSplit | |
} | |
}, | |
// Create the SVG element that will contain both visualizations. | |
svg = d3.select('body').append('svg') | |
.attr('width', outerWidth) | |
.attr('height', outerHeight), | |
// Add the title of the plot. | |
title = svg.append('text') | |
.attr('x', outerWidth / 2 ) | |
.attr('y', 20) | |
.attr('class', 'title'); | |
// Initialize the tree visualization. | |
tree.init(svg, outerWidth, outerHeight, margins.tree, hierarchy); | |
// Initialize the stacked area visualizaiton. | |
stackedArea.init(svg, outerWidth, outerHeight, margins.stackedArea, table); | |
// Set up the stacked area to respond to tree navigations. | |
// Called with the cause of death names to show. | |
tree.onNavigate(function (newRoot, names){ | |
stackedArea.update(names); | |
title.text('Causes of Death in the US: ' + newRoot.name); | |
}); | |
}, function(err){ | |
// If we are here, the data failed to load. | |
console.log(err); | |
}); | |
}); |
// This module implements the stacked area visualization. | |
// | |
// Curran Kelleher 2/18/2014 | |
define(['getShortName'], function (getShortName) { | |
var width, | |
height, | |
g, | |
data, | |
x = d3.time.scale(), | |
y = d3.scale.linear(), | |
color = d3.scale.ordinal() | |
// Colors hand-picked from http://www.w3schools.com/tags/ref_colorpicker.asp | |
.range(['#006699','#00CC99','#009933','#CC6699','#99CC00','#CC9900','#CC3300','#FFCC00','#FF0000','#990033','#FF6699','#CC3399','#9900FF','#6666FF','#3333CC','#66CCFF','#0000FF','#00FFFF','#99FFCC','#CCFF99','#FFCC99','#FF99CC','#CC99FF','#CCCCFF','#333300']), | |
xAxis = d3.svg.axis() | |
.scale(x) | |
.orient('bottom'), | |
xAxisG, | |
area = d3.svg.area() | |
.x(function(d) { return x(d.date); }) | |
.y0(function(d) { return y(d.y0); }) | |
.y1(function(d) { return y(d.y0 + d.y); }), | |
stack = d3.layout.stack() | |
.values(function(d) { return d.values; }) | |
.offset('expand'); | |
// This function should be called once to set up the visualization. | |
function init(svg, outerWidth, outerHeight, margin, _data){ | |
data = _data; | |
// Parse years into Date objects for use with D3 time scale. | |
data.forEach(function(d) { | |
d.date = new Date(d.year, 0); | |
}); | |
width = outerWidth - margin.left - margin.right; | |
height = outerHeight - margin.top - margin.bottom; | |
x.range([0, width]); | |
y.range([height, 0]); | |
g = svg.append('g') | |
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
xAxisG = g.append('g') | |
.attr('class', 'x axis') | |
.attr('transform', 'translate(0,' + height + ')'); | |
} | |
function update(names){ | |
var causes, | |
namesIndex = {}; | |
x.domain(d3.extent(data, function(d) { return d.date; })); | |
names.forEach(function(name){ | |
namesIndex[name] = true; | |
}); | |
// Transform the data for D3's stack layout. | |
// see https://github.com/mbostock/d3/wiki/Stack-Layout | |
causes = names.map(function(name) { | |
return { | |
name: name, | |
values: data.map(function(d) { | |
return {date: d.date, y: clean(d[name])}; | |
}) | |
}; | |
}) | |
// Sort the layers by the most recent value. | |
causes = _.sortBy(causes, function(cause) { | |
return cause.values[cause.values.length - 1].y; | |
}); | |
// Set the color domain so each color is a cause of death. | |
// Use sorted values. | |
color.domain(_.pluck(causes, 'name').reverse()); | |
// Add the stacked areas. | |
var cause = g.selectAll('.cause') | |
.data(stack(causes)); | |
cause.enter().append('path') | |
.attr('class', 'cause'); | |
cause | |
.attr('d', function(d) { return area(d.values); }) | |
.style('fill', function(d) { return color(d.name); }); | |
cause.exit().remove(); | |
// Add the legend. | |
// See http://bl.ocks.org/mbostock/3888852 | |
// Use sorted causes for legend. | |
var legend = g.selectAll('.legend') | |
.data(causes.map(function(d){ return d.name; }).reverse()); | |
var legendEnter = legend.enter().append('g') | |
.attr('class', 'legend'); | |
legend.attr('transform', function(d, i) { | |
return 'translate(' + (width + 3) + ',' + i * 20 + ')'; | |
}); | |
// TODO remove hard code size. | |
legendEnter.append('rect') | |
.attr('width', 18) | |
.attr('height', 18); | |
legend.select('rect') | |
.style('fill', color); | |
legendEnter.append('text') | |
.attr('x', 20) | |
.attr('y', 10) | |
.attr('dy', '.35em'); | |
legend.select('text') | |
.text(getShortName); | |
legend.exit().remove(); | |
// Add the X axis (years). | |
xAxisG.call(xAxis); | |
} | |
// Replace missing data with 0 and parse strings into numbers. | |
function clean(value){ | |
return value === '~' ? 0 : parseFloat(value); | |
} | |
return { | |
init: init, | |
update: update | |
}; | |
}); |
// A tree visualization of Cause of Death hierarchy from the Centers for Disease Control. | |
// This module implements the tree visualization. | |
// | |
// | |
// Details of the hierarchy can be found here: | |
// https://github.com/curran/hierarchy/tree/gh-pages/cdc/mortality | |
// | |
// Draws from: | |
// | |
// Radial Tree | |
// http://bl.ocks.org/mbostock/4063550 | |
// | |
// Linear Tree | |
// http://bl.ocks.org/mbostock/4063570 | |
// | |
// Margin Convention | |
// http://bl.ocks.org/mbostock/3019563 | |
// | |
// Collapsible Tree Layout | |
// http://mbostock.github.io/d3/talk/20111018/tree.html | |
// | |
// Curran Kelleher 2/18/2014 | |
define(['getShortName'], function (getShortName) { | |
// A function called when the user navigates in the tree. | |
var onNavigate = function(){}, | |
// Keeps track of the root of the visible tree. | |
root; | |
// This function should be called once to set up the visualization. | |
function init(svg, outerWidth, outerHeight, margin, hierarchy){ | |
var nodeRadius = 4, | |
labelOffset = 7, | |
width = outerWidth - margin.left - margin.right, | |
height = outerHeight - margin.top - margin.bottom, | |
tree = d3.layout.tree() | |
.size([height, width]) | |
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; }), | |
diagonal = d3.svg.diagonal() | |
.projection(function(d) { return [d.y, d.x]; }), | |
g = svg.append('g') | |
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') | |
// Use a separate group for links so they always appear behind nodes. | |
linkG = g.append('g'); | |
// Initialize the visualization to show the top of the tree. | |
navigate(hierarchy); | |
function navigate(newRoot) { | |
root = newRoot; | |
onNavigate(root, childNames(root)); | |
update(); | |
} | |
function update(){ | |
// Toggles nodes to show direct children only. | |
showSubtree(root); | |
var nodes = layoutNodes(root); | |
var links = tree.links(nodes); | |
var link = linkG.selectAll('.link') | |
.data(links); | |
link.enter().append('path') | |
.attr('class', 'link'); | |
link.attr('d', diagonal); | |
link.exit().remove(); | |
var node = g.selectAll('.node') | |
.data(nodes); | |
var nodeEnter = node.enter().append('g') | |
.attr('class', 'node'); | |
node.attr('transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; }); | |
nodeEnter.append('circle') | |
.attr('r', nodeRadius) | |
.on('click', handleClick); | |
node.select('circle') | |
.attr('class', function (d) { | |
return hasChildren(d) ? 'with-children' : 'without-children'; | |
}) | |
.call(setCursor); | |
nodeEnter.append('text') | |
.attr('dy', '.35em') | |
.attr('dx', labelOffset + 'px') | |
.attr('text-anchor', 'start') | |
.on('click', handleClick); | |
node.select('text') | |
.text(function(d) { return getShortName(d.name); }) | |
.call(setCursor); | |
node.exit().remove(); | |
} | |
// Handles the special case of a single child, | |
// which is not handled properly by D3's tree layout. | |
function layoutNodes(root){ | |
var nodes = tree.nodes(root); | |
if(nodes.length === 2) { | |
nodes.forEach(function(node){ | |
node.x = height / 2; | |
}); | |
} | |
return nodes; | |
} | |
// Handles clicking on a node in the tree (node or text). | |
function handleClick(d){ | |
// If the user clicks on the root node, navigate up the tree. | |
if(d.isRoot && d.parent) { | |
navigate(d.parent); | |
} else if(d._children) { | |
// Otherwise navigate down the tree. | |
navigate(d); | |
} | |
} | |
// Returns whether or not a node has more than one child, | |
// regardless of whether it is collapsed or not. | |
function hasChildren(d){ | |
var children = d.children || d._children; | |
return (children && children.length > 1) | |
} | |
// Sets the cursor of the given selection (circle or text) | |
// to be a pointer if the node has more than one child, | |
// as an affordance that it can be clicked. | |
function setCursor(selection){ | |
selection.style('cursor', function (d) { | |
return hasChildren(d) ? 'pointer' : 'auto'; | |
}); | |
} | |
} | |
// Expands and collapses nodes such that: | |
// `root` is expanded, and | |
// the children of `root` are collapsed. | |
function showSubtree(root){ | |
expand(root); | |
// The `isRoot` flag is used in the click event | |
// handler to determine which navigation direction | |
// is intended - clicking on the root moves up the tree. | |
root.isRoot = true; | |
if(root.children){ | |
root.children.forEach(function(node) { | |
collapse(node); | |
node.isRoot = false; | |
}); | |
} | |
} | |
function expand(d){ | |
if (d._children) { | |
d.children = d._children; | |
d._children = null; | |
} | |
} | |
function collapse(d) { | |
if (d.children) { | |
d._children = d.children; | |
d.children = null; | |
} | |
} | |
function childNames(d){ | |
var children = d.children || d._children; | |
return _.pluck(children, 'name'); | |
} | |
return { | |
init: init, | |
onNavigate: function(callback) { | |
onNavigate = callback; | |
// Call the callback once initially. | |
if(root){ | |
onNavigate(root, childNames(root)); | |
} | |
} | |
}; | |
}); |