|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<style> |
|
|
|
body { |
|
font-family: sans-serif; |
|
} |
|
|
|
.label--year { |
|
font-size: 12px; |
|
} |
|
|
|
.day { |
|
fill: #fff; |
|
stroke: #ccc; |
|
} |
|
|
|
.month { |
|
fill: none; |
|
stroke: #000; |
|
stroke-width: 2px; |
|
} |
|
|
|
.highlight { |
|
fill: none; |
|
stroke: #404040; |
|
stroke-width: 2px; |
|
pointer-events: none; |
|
} |
|
|
|
.highlight--underlying { |
|
fill: #ccc; |
|
fill-opacity: 0.1; |
|
stroke: #fff; |
|
stroke-width: 4px; |
|
pointer-events: none; |
|
} |
|
|
|
.q0 { fill: #a50026; } |
|
.q1 { fill: #d73027; } |
|
.q2 { fill: #f46d43; } |
|
.q3 { fill: #fdae61; } |
|
.q4 { fill: #fee090; } |
|
.q5 { fill: #ffffbf; } |
|
.q6 { fill: #e0f3f8; } |
|
.q7 { fill: #abd9e9; } |
|
.q8 { fill: #74add1; } |
|
.q9 { fill: #4575b4; } |
|
.q10 { fill: #313695; } |
|
|
|
</style> |
|
</head> |
|
<body> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script src="date-range-path.js"></script> |
|
<script> |
|
|
|
var cellSize = 14; |
|
|
|
var width = 114; |
|
var height = 800; |
|
|
|
var formatPercent = d3.format('.1%'); |
|
var formatDate = d3.timeFormat('%Y-%m-%d'); |
|
|
|
var parseDate = d3.timeParse('%Y-%m-%d'); |
|
|
|
var quantize = d3.scaleQuantize() |
|
.domain([-.05, .05]) |
|
.range(d3.range(11).map(function(d) { return 'q' + d; })); |
|
|
|
function daysInYear(year) { |
|
var t0 = new Date(year, 0, 1); |
|
var t1 = new Date(year + 1, 0, 1); |
|
return d3.timeDay |
|
.every(1) |
|
.range(t0, t1); |
|
} |
|
|
|
function monthsInYear(year) { |
|
var t0 = new Date(year, 0, 1); |
|
var t1 = new Date(year + 1, 0, 1); |
|
return d3.timeMonth |
|
.every(1) |
|
.range(t0, t1); |
|
} |
|
|
|
function weekOfYear(date) { |
|
return d3.timeWeek |
|
.count(d3.timeYear(date), date); |
|
} |
|
|
|
function parseRow(d) { |
|
return { |
|
date: parseDate(d.date), |
|
pct_change: parseFloat(d.pct_change) |
|
}; |
|
} |
|
|
|
function monthOffset(t0) { |
|
return new Date(t0.getFullYear(), t0.getMonth() + 1, 0); |
|
} |
|
|
|
function weekOffset(t0) { |
|
return new Date(t0.getFullYear(), t0.getDate() + 7, 0); |
|
} |
|
|
|
function ready(error, data) { |
|
if (error) throw error; |
|
|
|
var dataByDate = d3.map(data, function(d) { return formatDate(d.date); }); |
|
|
|
function getPctChange(date) { |
|
var t = formatDate(date); |
|
if (dataByDate.has(t)) return dataByDate.get(t).pct_change; |
|
return null; |
|
} |
|
|
|
var yearExtent = d3.extent(data, function(d) { return d.date.getFullYear(); }); |
|
var startYear = yearExtent[0]; |
|
var endYear = yearExtent[1]; |
|
|
|
var yearRange = d3.range(startYear, endYear); |
|
|
|
var tx = (width - cellSize * 7) - 1; |
|
var ty = ((height - cellSize * 53) / 2); |
|
|
|
var svg = d3.select('body').selectAll('svg') |
|
.data(yearRange) |
|
.enter().append('svg') |
|
.attr('width', width) |
|
.attr('height', height) |
|
.attr('class', 'calendar') |
|
.append('g') |
|
.attr('transform', 'translate(' + tx + ',' + ty + ')'); |
|
|
|
svg.append('text') |
|
.attr('class', 'label label--year') |
|
.attr('transform', 'translate(' + cellSize * 3.5 + ', -6)') |
|
.style('text-anchor', 'middle') |
|
.text(function(year) { return year; }); |
|
|
|
function dayClass(date) { |
|
var pct_change = getPctChange(date); |
|
var quantile = pct_change !== null ? quantize(pct_change) : ''; |
|
return 'day ' + quantile; |
|
} |
|
|
|
var day = svg.append('g').attr('class', 'days') |
|
.selectAll('.day').data(daysInYear) |
|
.enter().append('rect') |
|
.attr('class', dayClass) |
|
.attr('width', cellSize) |
|
.attr('height', cellSize) |
|
.attr('x', function(date) { return date.getDay() * cellSize; }) |
|
.attr('y', function(date) { return weekOfYear(date) * cellSize; }); |
|
|
|
function titleText(date) { |
|
var pct_change = getPctChange(date); |
|
return formatDate(date) + ': ' + formatPercent(pct_change); |
|
} |
|
|
|
day.append('title').text(titleText); |
|
|
|
// Separate months |
|
var monthPath = dateRangePath() |
|
.cellSize(cellSize) |
|
.offset(monthOffset) |
|
.orientation('vertical') |
|
.closed(false); |
|
|
|
var month = svg.append('g').attr('class', 'months') |
|
.selectAll('.month').data(monthsInYear) |
|
.enter().append('path') |
|
.attr('class', 'month') |
|
.attr('d', monthPath); |
|
|
|
// Highlight time when there were the biggest declines |
|
var highlightPath = dateRangePath() |
|
.cellSize(cellSize) |
|
.orientation('vertical') |
|
.closed(true); |
|
|
|
var t0 = new Date(2008, 8, 8); |
|
var t1 = new Date(2008, 11, 19); |
|
|
|
const highlight = svg.filter(function(d) { return d === 2008; }).append('g'); |
|
|
|
highlight.append('path') |
|
.attr('class', 'highlight--underlying') |
|
.attr('d', highlightPath(t0, t1)); |
|
|
|
highlight.append('path') |
|
.attr('class', 'highlight') |
|
.attr('d', highlightPath(t0, t1)); |
|
} |
|
|
|
d3.tsv('dow-jones.tsv', parseRow, ready); |
|
|
|
|
|
</script> |
|
</body> |
|
</html> |