Updated for d3.v4!
Before we get started, let’s take a look at the final, minimally-styled clock we’re building in this tutorial. We’ll do some additional styling with Part II, so bear with the minimalist look for now:
Making the Data
Because we obviously have no precompiled “time” data to use, we must first generate our time data. Our goal is to create a function in which we can create a new
Date objects methods
getHours() to retrieve those units in numeric format.
So here we fetch a Date object that represents the current time, and then we call the appropriate methods of that date object to get our current minute, hour, and second. Notice how we add the minute fraction to the hour variable. Without this little feature, the hour hand will “jump” from hour to hour each time the minute hand hits 0, instead of smoothly rotating around the clock. We could do the same for the minute hand, but most analog clocks I’ve seen feature a minute hand that “clicks” from minute to minute instead of continuously rotating like the hour hand. Add the fraction of seconds elapsed if you prefer a smooth-sweeping minute hand (i.e.,
+ second / 60).
Keeping data values out of your keys is an absolute must for d3, and is analogous to good database design practices like keeping data values out of your column headers. This is something Mike Dewar stresses in Getting Started with D3: Creating Data-Driven Documents, his excellent – albeit brief – introduction to creating data visualizations with d3.
Understanding d3 Scales
Next, after defining the chart width and height, the radius of the clock, and the x and y offset of the clock, we define our scaling function, using d3’s built-in
Understanding scales is key to understanding d3 – they are the functions you see called “x” and “y” in so many d3 scripts. Whatever you chose to call them, understanding them is crucial to grasping what d3 is doing behind the scenes , so I’m going to take a moment here to describe what’s happening in the last three lines above.
d3.time.scale() method for our scales. This time scale comes in very handy when plotting out times series.
Next, we move to the scale’s domain. In any d3 scale you define (and there are several different types), it’s important to understand that the domain is the input – it’s the data you’re putting into the chart. The range, on the other hand, is the output – it’s the scale onto which your data will be plotted on your chart. Here, our input domain is the minimum and maximum value of the seconds we are putting in: 0 and 59 (yes, a robust clock would take into account leap seconds, but let’s not go there). If you’re plotting a time-series bar chart, your domain would be your first and last dates (for your x scale) and the minimum and maximum of your series data (for your y scale).
Finally, we define the scale’s range. As mentioned above, the range is the output onto which your data is mapped. Here’s that’s a perfect circle, so we define or range as the circle’s circumference (all the way around the circle). We use radians instead of degrees because d3’s arc function uses radians, and it’s easier to use radians here than to convert angles to radians further down in the script.
We then do the same thing for the hour scale, only the domain goes from 0 to 11, plus 59/60 to account for the minute fraction we are adding to each hour.
Phew! That was a lot of words to describe not much code. Congratulations for reading this far. We’re a little over halfway through.
Putting Down the SVG
Next we lay down our base svg element and a svg group and append to that group our outer svg circle, which allows us to create the outline of the clock face, and the inner svg circle, which creates a nice little visual origin for the clock hands. Does that part of a clock have a name? Anyway, here’s the code:
This is all basic SVG manipulation, so I’m not going to go into detail. If this portion of the script is confusing, I highly recommend going through Scott Murray’s d3 tutorials at aligned left, particularly tutorials no. 10 and 11 (in that order). By the way, Scott is said to be working on a d3 book for beginners, which to the best of my knowledge will only be the second d3 book in existence, after the book by Mike Dewey I mention above. Given the excellent quality of Scott’s tutorials, I’m looking forward to reading and recommending his book.
So at this point, our clock at this point should look like this:
But something seems to be missing…hmmm. Ah yes, the hands! So now we reach for a render function that we will be able to call again and again, once time every second. You will often see these types of functions that are called to re-plot data with names like
plot(). In our script, this is the function that ties everything together, and with the exception of one last little timer function to call this render function, it wraps up this tutorial. Let’s take a look:
OK, for a d3 beginner, that surely looks intimidating, but there are actually only two challenging ideas to grasp in the code above:
- path generators
- data joins and their use
Even though data joins are featured in the last code block above (starting on line 22), I’m going to discuss them first, since data joins lie at the core of d3’s power and flexibility. Plus the path generators won’t make sense unless we first go over the data joins.
Data joins are discussed at length in Mike Bostock’s piece Thinking in Joins, as well as in one of Scott Murray’s highly-recommended d3 tutorials, so I’m not going to go over the fundamentals again here. Suffice it to say that we have three objects in our data (seconds, minutes, and hours), and we want to create three corresponding SVG objects (the second, minute, and hour hands on the clock). So what do we do? We join them together by calling
data() followed by
enter(). At this point, our data is ready and available for binding, and we proceed to build the hands of the block using the
arc path generator.
SVG paths are one of the technologies most flexible features. Anytime you see a complex shape or object rendered with svg – think flags, countries, complex symbols, and the like – there is a mini-language called svg path behind that image. Basically, path data tells svg to start here, draw there, then there, then there. If you’ve ever used Turtle graphics to draw something, you know the basic idea. Not surprisingly, d3 makes frequent use of svg paths, primarily through a feature called path generators. Think of path generators as the mold needed to form paths. First you use a path generator to produce the path data you’ll need to draw the path, then you append svg paths like you would any other svg element (such as circles, rects, or lines). Only you include an extra
.attr method with a
d attribute, followed by a reference to your path generator.
And that’s exactly what we’ve done above with the d3.arc path generator, only we’ve done it three times: once for each of the clocks hands. So on line #7 above, we build our second hand arc path generator, setting its inner radius at 0 and outer radius at 70, which means that the hand will start at the center of the circle and measure some 70 pixels long. And now for the trickiest part of the whole program. In order to emulate the clock hand as it sweeps around the clock, we map the current second onto the arc’s angle value. We give the starting and ending angle the same value, which means that it has an angle of 0, but an angle of 0 at the part of the clock that corresponds to the second value. You’ll note that here is where we finally use our scaleSecs d3.scaleLinear function and change the decimal value of the current second to the appropriate angle value in radians.
We do the same thing for the minute and hour arcs, with the only difference being the scale used (scaleSecs for second and minute – since both are based on 60 – and scaleHours for the hour), as well as the length of the radius (I made the hour hand shorter for obvious reasons). We could have easily done this using one path generator instead of three and ample use of
if statements in the accessor functions, but for the sake of clarity I split it up into three.
Returning down to line #43, 45, and 47 in the code above, you’ll see that we then call these path generators as we’re defining the each svg path’s
d values. We then assign appropriate
stroke-width values to the three svg objects, which is how an angle of 0 can be seen at all.
One final thing to cover in the code above is the very first d3 operation in the
render() function, on line #5 above. Here, we remove the existing clock hands to make room for the clock hands about to be rendered. This should all make sense once we go over the final – and mercifully short – segment of the script, which is the timer that wraps it all together.
setInterval(). So you’ll see how we use this function to both fetch our data (generated dynamically) and pass it to our render function, both every 1 second (time intervals must be specified here in milliseconds).
And so it should all make sense. At this point, our clock looks somewhat realistic:
Addendum: I should add that this is not the only way a tool as flexible as d3 can make three hands go around a clock. Notably, it would also be easy to use d3 along with svg transforms to achieve the same effect. I chose the approach described above because I felt provided the best introduction to d3 for users who know the very basics but want to learn how to build visualizations.
- Mike Dewar’s Getting Started with D3: Creating Data-Driven Documents (Amazon affiliate link)