D3.js visualizations of data from Florence Nightingale's "A contribution to the sanitary history of the British army during the late war with Russia".
Several works served as inspiration:
Note that the data used by Bostock is not correct, as documented elsewhere.
xxxxxxxxxx
<html>
<head>
<meta charset="utf-8">
<title>Nightingale and the Crimean War</title>
<!-- Stylesheets -->
<link href='https://fonts.googleapis.com/css?family=Cabin:500' rel='stylesheet' type='text/css'>
<link rel="stylesheet" type="text/css" href="reset.css">
<link rel="stylesheet" type="text/css" href="style.css">
<!-- Libraries -->
<script type="text/javascript" src="d3.min.js"></script>
<!-- Scripts -->
<script type="text/javascript" src="script.js"></script>
</head>
<body class="clearfix">
<!--<h1>Nightingale and the Crimean War</h1>-->
<script type="text/javascript">
var line = Chart.line(),
stackedArea = Chart.stackedArea(),
stackedBar = Chart.stackedBar(),
groupedBar = Chart.groupedBar(),
multiplePie = Chart.multiplePie(),
multipleDonut = Chart.multipleDonut(),
height = 400,
format = d3.time.format('%m/%Y'),
causes = ['other', 'wounds', 'disease'],
xLabel = 'April 1854 - March 1856',
yLabel = 'Annual Rate of Mortality per 1000',
yTicks = 5,
dataLayers;
// Add a title:
d3.select('body').append('h2')
.attr('class', 'title')
.html( 'Diagrams <span class="small">of the</span> Causes <span class="small">of</span> Mortality <span class="small">in the</span> Army <span class="small">of the</span> East');
// Load the JSON data:
d3.json( 'data.json', function( data ) {
// Data from: https://ocp.hul.harvard.edu/dl/contagion/010164675
// Format the date and rework the data:
var scalar;
data.forEach( function(d) {
d.date = format.parse(d.date);
// Calculate the average annual mortality, as done by Nightingale:
// https://understandinguncertainty.org/node/214
scalar = 1000*12 / d.army_size;
d.disease = d.disease * scalar;
d.wounds = d.wounds * scalar;
d.other = d.other * scalar;
} );
// Format the data in terms of layers (by cause):
dataLayers = causes.map( function(cause) {
return data.map( function(d) {
return {
'x': d.date,
'y': d[cause],
'label': cause
};
});
});
// STACKED BAR CHART //
// Append a new figure to the DOM:
figure = d3.select( 'body' )
.append( 'figure' )
.attr('class', 'chart half');
// Get the figure width:
width = parseInt( figure.style( 'width' ), 10 );
// Update the chart generator settings:
stackedBar.legend( causes )
.width( width )
.height( height )
.xDomain( dataLayers[0].map( function(d) { return d.x; } ) )
.xScale( d3.scale.ordinal() )
.xLabel( xLabel )
.yTicks( yTicks )
.yLabel( yLabel );
// Bind the data and generate a new chart:
figure.datum( dataLayers )
.call( stackedBar );
// GROUPED BAR CHART //
// Append a new figure to the DOM:
figure = d3.select( 'body' )
.append( 'figure' )
.attr('class', 'chart half');
// Get the figure width:
width = parseInt( figure.style( 'width' ), 10 );
// Update the chart generator settings:
groupedBar.legend( causes )
.width( width )
.height( height )
.xDomain( dataLayers[0].map( function(d) { return d.x; } ) )
.xScale( d3.scale.ordinal() )
.xLabel( xLabel )
.yTicks( yTicks )
.yLabel( yLabel );
// Bind the data and generate a new chart:
figure.datum( dataLayers )
.call( groupedBar );
// STACKED AREA CHART //
// Append a new figure to the DOM:
figure = d3.select( 'body' )
.append( 'figure' )
.attr('class', 'chart half');
// Get the figure width:
width = parseInt( figure.style( 'width' ), 10 );
// Update the chart generator settings:
stackedArea.legend( causes )
.width( width )
.height( height )
.xScale( d3.time.scale() )
.xLabel( xLabel )
.yTicks( yTicks )
.yLabel( yLabel );
// Bind the data and generate a new chart:
figure.datum( dataLayers )
.call( stackedArea );
// LINE CHART //
// Append a new figure to the DOM:
figure = d3.select( 'body' )
.append( 'figure' )
.attr('class', 'chart half');
// Get the figure width:
width = parseInt( figure.style( 'width' ), 10 );
// Update the chart generator settings:
line.legend( causes )
.width( width )
.height( height )
.xScale( d3.time.scale() )
.xLabel( xLabel )
.yTicks( yTicks )
.yLabel( yLabel );
// Bind the data and generate a new chart:
figure.datum( dataLayers )
.call( line );
// MULTIPLE PIES //
// Prepare the data:
var _data = data.map( function(d) {
var sum = d.disease + d.wounds + d.other,
radius = Math.sqrt(sum / Math.PI);
return {
'labels': ['disease', 'wounds', 'other'],
'r': radius,
'x': d3.time.format('%b')(d.date),
'values': [d.disease, d.wounds, d.other]
};
});
// Append a new figure to the DOM:
figure = d3.select( 'body' )
.append( 'figure' )
.attr('class', 'chart half');
// Get the figure width:
width = parseInt( figure.style( 'width' ), 10 );
// Update the chart generator settings:
multiplePie.legend( ['disease', 'wounds', 'other'] )
.width( width )
.height( 600 )
.xLabel( xLabel )
.rDomain( [0, d3.max( _data, function(d) { return d.r; })] );
// Bind the data and generate a new chart:
figure.datum( _data )
.call( multiplePie );
// MULTIPLE DONUTS //
// Append a new figure to the DOM:
figure = d3.select( 'body' )
.append( 'figure' )
.attr('class', 'chart half');
// Get the figure width:
width = parseInt( figure.style( 'width' ), 10 );
// Update the chart generator settings:
multipleDonut.legend( ['disease', 'wounds', 'other'] )
.width( width )
.height( 600 )
.xLabel( xLabel )
.rDomain( [0, d3.max( _data, function(d) { return d.r; })] );
// Bind the data and generate a new chart:
figure.datum( _data )
.call( multipleDonut );
});
</script>
</body>
</html>