Skip to content

Instantly share code, notes, and snippets.

@borgar
Last active April 20, 2017 19:38
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 borgar/0549b917908d83873c68 to your computer and use it in GitHub Desktop.
Save borgar/0549b917908d83873c68 to your computer and use it in GitHub Desktop.
William Playfair's Price of Wheat
license: gpl-3.0
height: 515
border: no

This beautiful chart by William Playfair (credited as the inventor of the bar chart) was published in 1822 in a letter to Parliament. While it is considered a milestone in the development of infographics it has issues which would make it considered a failure today.

It plots the wages of mechanics and the price of wheat of together and is intended to show that "never at any former period was wheat so cheap, in proportion to mechanical labour, as it is at the present time." Playfair was correct. However, the dominant visual message in the chart is that the price of wheat varies but wages are stable.

<!DOCTYPE html>
<meta charset='utf-8'>
<style>
svg {
font: oblique 10px baskerville;
background : #eed;
}
.axis {
shape-rendering: crispEdges;
}
.tick line {
stroke : #000;
stroke-opacity : .3;
}
.minor line {
stroke-opacity : .15;
}
.linetext,
.title {
font-size : 12px;
}
.centuries .title {
font: bold 11px baskerville;
}
.heading {
font: bold 18px baskerville;
}
.monarchs rect {
stroke : #000;
fill : #000;
}
.monarchs .empty {
fill : none;
}
.description .inner {
fill : #eed;
}
.frame {
fill: none;
stroke: black;
}
.wages .area {
fill: #7ad;
fill-opacity: .4;
}
.wages .line {
fill: none;
stroke: black;
stroke-width: 1.3;
}
.wages .line-em {
fill: none;
stroke: #f46;
stroke-width: 3;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/queue.v1.min.js"></script>
<script>
var margin = { top: 45, right: 65, bottom: 40, left: 30 },
width = 960 - margin.left - margin.right,
height = 515 - margin.top - margin.bottom,
x = d3.scale.linear()
.domain([ 1565, 1830 ])
.range([ 0, width ]),
y = d3.scale.linear()
.domain([ 100, 0 ])
.range([ 0, height ]);
var svg = d3.select( 'body' ).append( 'svg' )
.attr( 'width', width + margin.left + margin.right )
.attr( 'height', height + margin.top + margin.bottom )
.append( 'g' )
.attr( 'transform', `translate(${margin.left},${margin.top})` );
var gradient = svg.append( 'defs' )
.append( 'linearGradient' )
.attr( 'id', 'gradient' )
.attr( 'x2', '0%' )
.attr( 'y2', '100%' )
.attr( 'spreadMethod', 'pad' );
gradient.append( 'stop' )
.attr( 'offset', '0%' )
.attr( 'stop-color', '#444' )
.attr( 'stop-opacity', 1 );
gradient.append( 'stop' )
.attr( 'offset', '40%' )
.attr( 'stop-color', '#444' )
.attr( 'stop-opacity', .9 );
gradient.append( 'stop' )
.attr( 'offset', '100%' )
.attr( 'stop-color', '#444' )
.attr( 'stop-opacity', 0 );
function parseWheat ( r ) {
r.wages = r.wages ? +r.wages : null;
r.wheat = +r.wheat;
r.year = +r.year;
return r;
}
function parseMonarchs ( r ) {
r.start = +r.start;
r.end = +r.end;
r.commonwealth = ( r.commonwealth === 'true' );
return r;
}
queue()
.defer( d3.csv, 'wheat_and_wages.csv', parseWheat )
.defer( d3.csv, 'monarchs.csv', parseMonarchs )
.await( ready );
function ready ( err, data, monarchs ) {
// Heading
svg.append( 'text' )
.attr( 'class', 'heading' )
.attr( 'x', x( 1700 ) )
.attr( 'y', -15 )
.style( 'text-anchor', 'middle' )
.text( d => `№1.` );
// Centuries
var area = d3.svg.area()
.x( x )
.y0( d => -Math.sin( ( d % 100 ) / 31.73 ) * 35 )
.y1( d => -Math.sin( ( d % 100 ) / 31.73 ) * 28 );
var centuries = svg.append( 'g' )
.attr( 'class', 'centuries' );
centuries.append( 'path' )
.datum( x.ticks( 400 ) )
.attr( 'd', area );
centuries.selectAll( '.title' )
.data([ 1575, 1650, 1750, 1820 ]).enter()
.append( 'text' )
.attr( 'class', 'title' )
.attr( 'x', x )
.attr( 'y', -5 )
.style( 'text-anchor', 'middle' )
.text( d => `${Math.floor(d/100)+1}th Century` );
// Frame
svg.append( 'rect' )
.attr( 'class', 'frame' )
.attr( 'width', width )
.attr( 'height', height );
// X-axis
var x_axis = svg.append( 'g' )
.attr( 'class', 'x axis' );
var xticks = x_axis.selectAll( '.tick' )
.data( x.ticks( 40 ) ).enter()
.append( 'g' )
.attr( 'transform', d => `translate(${x(d)},${height})` )
.attr( 'class', d => 'tick ' + ( d % 50 ? 'minor' : '' ) );
xticks.append( 'line' )
.attr( 'y2', -height );
xticks.append( 'text' )
.attr( 'y', 12 )
.style( 'text-anchor', (d, i) => !i ? 'end' : 'middle' )
.text( (d, i) => {
return !( d % 50 && i ) ? d : // century
!( d % 10 ) ? d % 100 : ''; // decade
});
x_axis.selectAll( '.title' )
.data([ 1650, 1780 ]).enter()
.append( 'text' )
.attr( 'class', 'title' )
.attr( 'transform', d => `translate(${x(d)},${height+26})` )
.style( 'text-anchor', 'middle' )
.text( '5 Years each division.' );
// Y-axis
var y_axis = svg.append( "g" )
.attr( "class", "y axis" );
var yticks = y_axis.selectAll( '.tick' )
.data( y.ticks( 20 ) ).enter()
.append( 'g' )
.attr( 'transform', d => `translate(0,${y(d)})` )
.attr( 'class', d => 'tick ' + ( d % 10 ? 'minor' : '' ) );
yticks.append( 'line' )
.attr( 'x2', width );
yticks.append( 'text' )
.attr( 'x', -4 )
.attr( 'dy', '.32em' )
.style( 'text-anchor', 'end' )
.text( d => !d ? '' : !( d % 5 ) ? d : '' );
yticks.append( 'text' )
.attr( 'x', width + 4 )
.attr( 'dy', '.32em' )
.style( 'text-anchor', 'start' )
.text( d => {
if ( d === 100 || d === 5 ) return d + ' Shillings.';
return d && !( d % 5 ) ? d : '';
});
y_axis.append( 'text' )
.attr( 'class', 'title' )
.attr( 'transform', d => `translate(${width+35},${y(50)}) rotate(-90)` )
.style( 'text-anchor', 'middle' )
.text( "Price of The Quarter of Wheat in Shillings." );
// Description
var desctext = [
{ text: 'Chart,', y: -44,
font: 'small-caps bold 20px baskerville' },
{ text: 'Showing at One View', y: -26,
font: 'oblique 16px baskerville' },
{ text: 'The Price of The Quarter of Wheat,', y: -10 },
{ text: '& Wages of Labour by the Week,', y: 6 },
{ text: '–– from ––', y: 17,
font: 'oblique 11px baskerville' },
{ text: 'The Year 1565 to 1821,', y: 30 },
{ text: '–– by ––', y: 39,
font: 'oblique 11px baskerville' },
{ text: 'William Playfair', y: 52,
font: 'normal small-caps 16px baskerville' } ];
var desc = svg.append( 'g' )
.attr( 'class', 'description' )
.attr( 'transform', `translate(${x(1653)},${y(73)})` );
desc.append( 'ellipse' )
.attr( 'class', 'outer' )
.attr({ 'rx': 66*1.7, 'ry': 66 });
desc.append( 'ellipse' )
.attr( 'class', 'inner' )
.attr({ 'rx': 65.7*1.7, 'ry': 64 });
desc.selectAll( 'text' )
.data( desctext ).enter()
.append( 'text' )
.text( d => d.text )
.attr( 'y', d => d.y )
.style( 'font', d => d.font || 'oblique 15px baskerville' )
.style( 'text-anchor', 'middle' );
// Price of The Quarter of Wheat.
var whe_area = d3.svg.area()
.x( d => x( d.year ) )
.y0( d => y( d.wheat ) )
.y1( height )
.interpolate( 'step-after' );
svg.append( 'path' )
.datum( data )
.attr( 'd', whe_area )
.style( 'fill', 'url(#gradient)' );
// Weekly Wages of a Good Mechanic.
var mec_area = d3.svg.area()
.x( d => x( d.year ) )
.y0( d => y( d.wages ) )
.y1( height );
var mec_line = d3.svg.line()
.x( d => x( d.year ) )
.y( d => y( d.wages ) );
var data_filt = data.filter( d => d.wages != null );
var mechanic = svg.append( 'g' )
.attr( 'class', 'wages' );
mechanic.append( 'defs' )
.datum( data_filt )
.append( 'path' )
.attr( 'd', mec_line )
.attr( 'id', 'wagesPath' );
mechanic.append( 'path' )
.datum( data_filt )
.attr( 'd', mec_area )
.attr( 'class', 'area' );
mechanic.append( 'use' )
.attr( 'transform', 'translate(0,-1)' )
.attr( 'xlink:href', '#wagesPath' )
.attr( 'class', 'line-em' );
mechanic.append( 'use' )
.attr( 'xlink:href', '#wagesPath' )
.attr( 'class', 'line' );
mechanic.selectAll( '.linetext' )
.data([ 25, 75 ]).enter()
.append( 'text' )
.attr( 'class', 'linetext' )
.attr( 'transform', 'translate(0,-7)' )
.append( 'textPath' )
.attr( 'startOffset', d => d + '%' )
.attr( 'xlink:href', '#wagesPath' )
.text( 'Weekly Wages of a Good Mechanic' );
// Monarchs.
var mons = svg.append( 'g' )
.attr( 'class', 'monarchs' )
.selectAll( 'g' )
.data( monarchs ).enter()
.append( 'g' )
.attr( 'transform', (d,i) => `translate(${x(d.start)},
${!d.commonwealth && i % 2 ? 15 : 10})` );
mons.append( 'rect' )
.attr( 'height', 5 )
.attr( 'width', d => x( d.end ) - x( d.start ) )
.attr( 'class', d => d.commonwealth ? 'empty' : '' );
mons.append( 'text' )
.text( d => d.name )
.attr( 'x', d => ( x( d.end ) - x( d.start ) ) / 2 )
.attr( 'y', 15 )
.style( 'text-anchor', 'middle' );
};
</script>
name start end commonwealth
Elizabeth 1565 1603 false
James I 1603 1625 false
Charles I 1625 1649 false
Cromwell 1649 1660 true
Charles II 1660 1685 false
James II 1685 1689 false
W&M 1689 1702 false
Anne 1702 1714 false
George I 1714 1727 false
George II 1727 1760 false
George III 1760 1820 false
George IV 1820 1821 false
year wheat wages
1565 41 5
1570 45 5.05
1575 42 5.08
1580 49 5.12
1585 41.5 5.15
1590 47 5.25
1595 64 5.54
1600 27 5.61
1605 33 5.69
1610 32 5.78
1615 33 5.94
1620 35 6.01
1625 33 6.12
1630 45 6.22
1635 33 6.3
1640 39 6.37
1645 53 6.45
1650 42 6.5
1655 40.5 6.6
1660 46.5 6.75
1665 32 6.8
1670 37 6.9
1675 43 7
1680 35 7.3
1685 27 7.6
1690 40 8
1695 50 8.5
1700 30 9
1705 32 10
1710 44 11
1715 33 11.75
1720 29 12.5
1725 39 13
1730 26 13.3
1735 32 13.6
1740 27 14
1745 27.5 14.5
1750 31 15
1755 35.5 15.7
1760 31 16.5
1765 43 17.6
1770 47 18.5
1775 44 19.5
1780 46 21
1785 42 23
1790 47.5 25.5
1795 76 27.5
1800 79 28.5
1805 81 29.5
1810 99 30
1815 78
1820 54
1821 54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment