Vertical layout for a calendar heatmap.
The month boundaries and the enclosed highlighted area are created using the
dateRangePath()
function defined in the date-range-path.js
. You can draw a path around any date range within a year by providing it a beginning and end date or a beginning date and a day-based offset, like a week or a month.
Fork from Mike Bostock's Calendar View
xxxxxxxxxx
<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>
https://d3js.org/d3.v4.min.js