I'm currently analysing my sleep data (with the help of d3), and I have an array of times/dates from my sleep tracking app representing when a sleep period started and ended.
[
{
"startDate": "2017-12-25 00:09:00 +0100",
"endDate": "2017-12-25 00:50:00 +0100"
},
{
"startDate": "2017-12-25 01:45:00 +0100",
"endDate": "2017-12-25 02:11:00 +0100"
},
{
"startDate": "2017-12-25 02:26:00 +0100",
"endDate": "2017-12-25 12:37:00 +0100"
},
...
]
From these entries, I want to create a histogram-type chart that shows how likely it is that I'll be asleep for a given hour and minute of the day. So along the X-axis will be 1,440 ticks representing each minute of a general day, and the height of the bar will be how likely it is (using a simple count) I'll be asleep at that given minute.
Currently, I'm creating an array of all the individual minutes between all of my intervals (i.e. in the first entry above, there would be 41 objects representing each minute between 00:09 and 00:50) and then matching them against all 1,440 minutes in a day. When they match, increase the count for that "minute of the day" by one.
So over time, I can see that I'm likely to be asleep at, say, 01:00, and awake at, say, 12:30.
I have around 3,100 sleep entries, and while my current code appears to work, it takes a long time to go through my entries (understandably as there's a map within a map). If I try to run it on just 1,000 entries in a Codesandbox, I don't get the results back for around a minute.
Also, it… feels inefficient. I'm sure there must be a more efficient way of doing this (I wondered if perhaps there was a d3 function to make it easier, but I couldn't find one).
Part of the problem I've had is knowing what to search for — is it a histogram? A frequency chart? Something else?
import * as d3 from 'd3';
import { add, format, getHours, getMinutes } from 'date-fns';
import { flatMap, map } from 'lodash';
// For each interval in the source data, create an array of the minutes
// in that interval
// e.g. For an interval time 00:10:00 to 00:18:00,
// return 9 dates (00:10:00, 00:11:00, 00:12:00, etc.)
const minutesFromIntervals = flatMap(data, (interval) =>
d3.timeMinute.range(
new Date(interval.startDate),
new Date(interval.endDate),
),
);
// Create an array of all the minutes in a day between midnight and
// midnight (1440 minutes)
const minutesOfDay = d3.timeMinute.range(
new Date(2020, 0, 1),
add(new Date(2020, 0, 1), { hours: 24 }),
);
// Map over the minutes of a day and see which interval minutes match
const results = map(minutesOfDay, (o) => {
let count = 0;
const outerDate = new Date(o);
const hour = getHours(outerDate);
const minute = getMinutes(outerDate);
map(minutesFromIntervals, (p) => {
const innerDate = new Date(p);
if (getHours(innerDate) === hour && getMinutes(innerDate) === minute) {
count += 1;
}
});
return {
key: format(o, 'HH:mm'),
value: count,
};
});
console.log({ results });
// results (example):
// [
// { key: "00:00", value: 0 },
// { key: "00:01", value: 1 },
// { key: "00:02", value: 1 },
// { key: "00:03", value: 1 },
// { key: "00:04", value: 2 },
// { key: "00:05", value: 2 },
// { key: "00:06", value: 1 },
// { key: "00:07", value: 3 },
// ...
// ]