This code creates an interactive heatmap visualization that displays time-series data, grouping it by day and coloring each day based on the total time spent. It uses D3.js to generate the SVG elements and apply the color scale.
npm run import -- "d3 calendar"
var D3Node = require('d3-node');
var moment = require('moment');
var d3n = new D3Node();
var d3 = d3n.d3;
function d3Heatmap(data) {
var margin = {top: 20, right: 50, bottom: 20, left: 20};
// input vars for getter setters
// TODO: turn these in to options
var startYear = d3.min(data.map(d => d.start)).getFullYear(),
endYear = d3.max(data.map(d => d.end)).getFullYear() + 1,
years = d3.range(startYear, endYear).reverse(),
colourRangeStart = '#666666',
colourRangeEnd = '#000000',
width = 950,
height = years.length * 150,
dayLength = 60 * 60 * 24,
sizeByYear = (height - margin.top - margin.bottom) / years.length,
sizeByDay = d3.min([
// divide by 8, 7 days in a week and 1 row for label
sizeByYear / 8,
// 54 weeks because every year has a partial week on both ends
(width - margin.left - margin.right) / 54]),
day = (d) => (d.getDay() + 6) % 7,
week = d3.timeFormat('%W'),
date = d3.timeFormat('%b %d');
// combine date data by day
var nestedData = d3.nest()
// round down to nearest day
.key((d) => Math.round(d.start.getTime() / dayLength / 1000) * dayLength)
// sum hours spent for the day to determine color range
.rollup(function (n) {
return d3.sum(n, function (d) {
return d.end.getTime() - d.start.getTime(); // key
});
})
.map(data)
var svg = d3n.createSVG(width, height)
.attr('class', 'chart')
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var year = svg.selectAll('.year')
.data(years)
.enter().append('g')
.attr('class', 'year')
.attr('transform', (d, i) => 'translate(30,' + i * sizeByYear + ')');
year.append('text')
.text((d) => d)
.attr('class', 'year-title')
.attr('transform', 'translate(-38,' + sizeByDay * 3.5 + ')rotate(-90)')
.attr('text-anchor', 'middle')
// apply the heatmap colours
var colour = d3.scaleLinear()
.range(['white', 'white', colourRangeStart, colourRangeEnd])
.domain([-1, 0, .000001, 1])
var rect = year.selectAll('.day')
.data((d) => d3.timeDays(new Date(d, 0, 1), new Date(d + 1, 0, 1)))
.enter().append('rect')
.attr('fill', (d) => {
const t = Math.round(d.getTime() / dayLength / 1000) * dayLength;
// 12 hours of work is too much!
const normalDay = nestedData['