Time is tricky to work with in general. Everybody seems to have their own way of representing time as text: seconds since the unix epoch, 2 digit years, months before days and days before months. One person's data may have dates like "99-01-01" and other's may look like "Jan 1, 1970". Some dates may also have time, others will not. Timezones will be specified sometimes but not others.
var currTime = new Date(); d3.select("#ex1").append("p").text("Current time: " + currTime); var aWhileAgo = new Date(1969, 1, 1); d3.select("#ex1").append("p").text("Time a while ago: " + aWhileAgo); var isCurrTimeAfter = (currTime > aWhileAgo)?"Yes":"No"; d3.select("#ex1").append("p").text("Is current time after a while ago? " + isCurrTimeAfter);
D3 Time Formatting
d3.time.format(formatString) returns a function that will convert a Date object into a string whose format matches the formatString. Likewise to go from a string to a Date you use d3.time.format(formatString).parse(StringToParse). Below is an example that goes from a string date to a Date object to a string date that is formatted in a different way.
var fmt1 = d3.time.format("%Y-%m-%d"); var dateStr = "2001-01-01"; // parse function converts a string to a Date var date = fmt1.parse(dateStr); var fmt2 = d3.time.format("%A, %B %e, %Y"); // format function converts from Date to a string var dateStr2 = fmt2(date); var ex2 = d3.select("#ex2"); ex2.append("p").text("Original data date: " + dateStr); ex2.append("p").text("Converted to Date object: " + date); ex2.append("p").text("Date converted to formatted string: " + dateStr2);
D3 supports many different date and time format specifiers. Here is a table of them with descriptions from D3's API Reference Wiki.
D3 provides d3.time.scale which is a scale for time data. At first glance, it is the same as d3.scale.linear except that it takes Date objects as inputs to the domain. This makes it easy to translate dates to locations in a visualiation.
Beyond the basic scale functionality, the ticks function of d3.time.scale allows granular control of the format and spacing of the ticks. There are two ways you can call the ticks function. One way is to pass the normal hint of the number of ticks that you want. The other is a D3 time interval and a step amount. D3 time intervals are ways of specifying amounts of time to D3. D3 provides several standard intervals, such as d3.time.month, which is unsurprisingly an interval that represents one month. It is smart enough to change size based on the length of the month.
var currDate = new Date(); var oneDayMillis = 1000*60*60*24; var thirtyDaysLater = new Date(currDate.getTime() + (30*oneDayMillis)); var sixtyDaysLater = new Date(currDate.getTime() + (60*oneDayMillis)); var timeScale = d3.time.scale() .domain([currDate, sixtyDaysLater]) .range([0,100]); var timeScaleEx = d3.select("#timeScaleEx"); timeScaleEx.append("p").text("Current Time Scaled: " + timeScale(currDate)); timeScaleEx.append("p").text("30 days later Scaled: " + timeScale(thirtyDaysLater)); timeScaleEx.append("p").text("60 days later Scaled: " + timeScale(sixtyDaysLater)); var ticks = timeScale.ticks(d3.time.day, 10); // ticks are Date objects, so I format them into something easier to read var monthDayFormat = d3.time.format(" %B %d"); ticks = ticks.map(monthDayFormat); timeScaleEx.append("p").text("10 Day Ticks:" + ticks);
In the example above, I create a time scale with a range from 0 to 100. The domain is today to 60 days in the future. As expected today maps to 0, while 30 days from now maps to 50 and 60 days from now maps to 100. I also demonstrate how ticks can be used with time intervals to give more granular control over the ticks.
Date d3.time.format d3.time.scale
Things to do
- Change the x-axis ticks to use 1 year intervals.
- Format the x-axis tick text to use the format "month year". Use the abbreviated month instead of the full month name.