Menu

Time of day plot

A scatter plot with an scaleBand for the y-scale to bucket them by day. The only real fancy part of this plot is modifying the input data to be in "seconds since start of day" and generating the yDomain as every day between the min and max values, not just days for which we have values. This lets us see days in between that have no data.

import { default as LayerCake, calcExtents } from 'layercake';
import { timeDay } from 'd3-time';
import days from './data/days.js';
import { csvParse } from 'd3-dsv';
import { scaleBand } from 'd3-scale';
import Scatter from './components/ScatterScaleBand.html';
import AxisX from './components/AxisX.html';
import AxisY from './components/AxisYScaleBand.html';

const r = 4;
const padding = 4;

const daysJson = csvParse(days, row => {
  const parts = row.timestring.split('T');
  const time = parts[1].replace('Z', '').split(':').map(d => +d);
  row.seconds = time[0] * 60 * 60 + time[1] * 60 + time[2];
  row.day = parts[0];
  return row;
});

/* --------------------------------------------
 * Generate a range of days in between the min and max
 * in case we are missing any in our data so we can show empty days for them
 */
const extents = calcExtents(daysJson, [
  { field: 'x', accessor: d => d.timestring }
]);

const minDate = extents.x[0].split('T')[0].split('-').map(d => +d);
const maxDate = extents.x[1].split('T')[0].split('-').map(d => +d);

const allDays = timeDay.range(new Date(Date.UTC(minDate[0], minDate[1] - 1, minDate[2])), new Date(Date.UTC(maxDate[0], maxDate[1] - 1, maxDate[2] + 1)))
  .map(d => d.toISOString().split('T')[0]).sort().reverse();

const myCake = new LayerCake({
  padding: { top: 0, right: 12, bottom: 20, left: 75 },
  x: 'seconds',
  y: 'day',
  xDomain: [0, 24 * 60 * 60],
  yDomain: allDays,
  yScale: scaleBand().paddingInner([0.05]).round(true),
  xPadding: [padding, padding],
  yPadding: [padding, padding],
  data: daysJson,
  target: document.getElementById('my-chart')
})
  .svgLayers([
    { component: AxisX,
      opts: {
        ticks: [0, 4, 8, 12, 16, 20, 24].map(d => d * 60 * 60),
        formatTick: d => `${Math.floor(d / 60 / 60)}:00`
      }
    },
    { component: AxisY, opts: { } }
  ])
  .canvasLayers([
    { component: Scatter, opts: { r, fill: 'rgba(255, 204, 0, 0.75)' } }
  ]);

myCake.render();