Published
Edited
Apr 26, 2021
1 fork
3 stars
Insert cell
md`# Understanding d3-time`
Insert cell
todayMidnightLocal = d3.timeDay()
Insert cell
// See, it's local time
todayMidnightLocal.toISOString()
Insert cell
beginningOfThisHourLocal = d3.timeHour()
Insert cell
todayMidnightUtc = d3.utcDay()
Insert cell
// Woohoo!
todayMidnightUtc.toISOString()
Insert cell
dateStr = "2021-04-15T15:20:05.123Z"
Insert cell
date = new Date(dateStr)
Insert cell
// date internally represents that UTC point in time, in milliseconds
+date // coerce to number
Insert cell
// same as .getTime()
date.getTime()
Insert cell
dateStr2 = date.toISOString()
Insert cell
// conversion from an iso 8601 datetime string and back preserves the value
dateStr === dateStr2
Insert cell
md`## interval.floor

interval.round and interval.ceil are similar`
Insert cell
date
Insert cell
dateBeginningOfYear = d3.timeYear.floor(date)
Insert cell
dateBeginningOfMonth = d3.timeMonth.floor(date)
Insert cell
dateBeginningOfWeek = d3.timeWeek.floor(date)
Insert cell
// beginning of the day of the input date
dateBeginningOfDay = d3.timeDay.floor(date)
Insert cell
// it's the beginning of the day in local time!
dateBeginningOfDay.toISOString()
Insert cell
dateBeginningOfHour = d3.timeHour.floor(date)
Insert cell
dateBeginningOfMinute = d3.timeMinute.floor(date)
Insert cell
dateBeginningOfSecond = d3.timeSecond.floor(date)
Insert cell
md`## interval.offset (date math!)`
Insert cell
date
Insert cell
tomorrow = d3.timeDay.offset(date, 1)
Insert cell
yesterday = d3.timeDay.offset(date, -1)
Insert cell
oneHourAgo = d3.timeHour.offset(date, -1)
Insert cell
md`## interval.range`
Insert cell
// one week prior to the date
start = d3.timeWeek.offset(date, -1)
Insert cell
end = date
Insert cell
everyDayBetween = d3.timeDay.range(start, end)
Insert cell
// note dates are floored to the specified interval
everyDayBetween[0].toISOString()
Insert cell
everyOtherDayBetween = d3.timeDay.range(start, end, 2)
Insert cell
everyMinuteBetween = d3.timeMinute.range(start, end)
Insert cell
md`## interval.every

Syntactic sugar on top of interval.filter`
Insert cell
// returns a function
everyOtherYear = d3.timeYear.every(2)
Insert cell
// called by itself, will just use new Date()
everyOtherYear()
Insert cell
end2 = end
Insert cell
start2 = d3.timeYear.offset(end2, -10)
Insert cell
// used with range
everyOtherYear.range(start2, end2)
Insert cell
everyTenMinutes = d3.timeMinute.every(10)
Insert cell
date
Insert cell
// the interval function works the same as any other interval, e.g. timeDay
everyTenMinutes.floor(date)
Insert cell
everyTenMinutes.ceil(date)
Insert cell
// 2 units = 20 minutes in this case
everyTenMinutes.offset(date, 2)
Insert cell
md`## interval.count`
Insert cell
minutesIntoTheDay = d3.timeMinute.count(d3.timeDay(), new Date())
Insert cell
start
Insert cell
end
Insert cell
daysBetween = d3.timeDay.count(start, end)
Insert cell
minutesBetween = d3.timeMinute.count(start, end)
Insert cell
md`## timeInterval`
Insert cell
// minimally, floor + offset. seems like you have to mutate date.
customInterval = d3.timeInterval(
date => {
date.setSeconds(0, 0);
},
(date, step) => {
date.setMinute(date.getMinute() + step);
}
)
Insert cell
date
Insert cell
customInterval(date)
Insert cell
customInterval(date, 2)
Insert cell
md`## What if we want every 10 minutes but offset by 3 minutes?`
Insert cell
// I think we have to use a filter
everyTenMinutesOffset = d3.timeMinute.filter(
d => (d.getMinutes() - 3) % 10 === 0
)
Insert cell
[start, end]
Insert cell
everyTenMinutesOffset.range(start, end)
Insert cell
md`## How is it used in TimeChart?`
Insert cell
timeChartInterval = d3.utcMinute.every(10) // sample frequency
Insert cell
timeChartStop = timeChartInterval() // exclusive
// remember calling this just calls it with new Date(), so it's the nearest "every 10 minutes" mark
Insert cell
timeChartStart = timeChartInterval.offset(timeChartStop, -timeChartTicks) // inclusive
Insert cell
// why -928? gives a 26px margin/indent at the left
// go back 928 intervals, i.e. 928 * 10 minutes == 9,280 minutes == 6.44 days or about a week's worth of data
timeChartTicks = 928

// 1 minute intervals == 15.466 hrs or a bit over a day's worth of data
// 1 hour intervals = 38.66 days
Insert cell
timeChartIntervalRange = timeChartInterval.range(timeChartStart, timeChartStop)
Insert cell
// internally, the width of the timechart will be determined by:
timeChartIntervalRange.length
Insert cell
width
Insert cell
timeChartData = {
let data = [];
for (let i = 0; i < timeChartTicks; i++) {
data.push({
date: timeChartIntervalRange[i],
value: Math.random() * Math.sin(i * 0.02)
});
}
return data;
}
Insert cell
Insert cell
TimeChart(timeChartData, {
interval: timeChartInterval,
start: timeChartStart,
stop: timeChartStop,
title: "Random data",
max: 1,
// bands: 1,
scheme: "piyg",
marginTop: -16
})
Insert cell
md`## Some real data`
Insert cell
bitcoinHistory = fetch(
`https://api.coincap.io/v2/assets/bitcoin/history?interval=h12&start=${+tcbStart}&end=${+tcbStop}` // 1 day intervals
)
.then(response => response.json())
.then(data => data)
Insert cell
tcbData = bitcoinHistory.data.map(d => ({
value: +d.priceUsd,
date: tcbInterval.floor(new Date(d.date)) // good idea to floor the date with the interval especially with day interval, to avoid timezone offset which will zero out all the data in the timechart
}))
Insert cell
maxPrice = d3.max(tcbData.map(d => d.value))
Insert cell
tcbInterval = d3.timeHour.every(12)
Insert cell
tcbStart = d3.timeYear.offset(tcbStop, -1) // max range for h12 data is 1 year
Insert cell
tcbStop = tcbInterval()
Insert cell
Insert cell
TimeChart(tcbData, {
interval: tcbInterval,
start: tcbStart,
stop: tcbStop,
title: "Bitcoin",
max: maxPrice,
marginTop: -16,
scheme: "blues"
// bands: 1
})
Insert cell
md`## Imports`
Insert cell
import { TimeChart, TimeAxis, d3 } from "@observablehq/timechart"
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more