Handling Dates


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.

Javascript provides native support for time with the Date class. Among other things it makes it easy to extract part of a time, such as for a given time what day of the week is it? It also makes it easy to compare two different times.

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 provides time formatting helper functions. These functions make it easy to go from a formatted string to a Date or from a Date to a formatted string. In a typical visualization the flow will be convert the data's time string into a Javascript Date object. Then convert the Date object into a formatted string to meet your specifications.

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.

Time Scale

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])

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.





Things to do

  1. Change the x-axis ticks to use 1 year intervals.
  2. Format the x-axis tick text to use the format "month year". Use the abbreviated month instead of the full month name.

Extra Credit

Change the code on the left. Once you've made a change, the page will render on the right.