Skip to content

Instantly share code, notes, and snippets.

@curran
Last active September 16, 2015 19:20
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save curran/6284affc05bdeb7dfc9e to your computer and use it in GitHub Desktop.
Save curran/6284affc05bdeb7dfc9e to your computer and use it in GitHub Desktop.
Mortality

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.

web counter
// 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));
}
}
};
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment