d3 is well known for its use in generating data-driven charts. But it’s also a great tool for building user-interface components that aren’t necessarily driven by external data sets. This post will explore constructing a digital clock by taking advantage of nested d3 selections and data. The full code is also available for download.
The finished product is a digital clock depicting the browser time in hours, minutes, and seconds:
We start by importing d3 dependencies and defining constants used in sizing and coloring the components:
Here “DIGIT” refers to a single digit in the clock; “BAR” refers to a bar within a digit; and “DOT” refers to the dots between hours and minutes and minutes and seconds. Note that all sizing constants derive from DIGIT_WIDTH, with the help from some magic numbers. Thus to change the clock size simply change the DIGIT_WIDTH value. The color constants correspond to a bar being “on” or “off”. Below is a visual representation of the sizing constants in action:
Each bar in a digit is an SVGPathElement with the same d attribute value. We use d3’s path module to generate the path attribute value, which is stored as barPath for later use:
While each bar shares the same path each has its own x-y positioning, rotation, and on-off state. We define a function barData that generates bar data for a single digit based on the digit’s current value (e.g., 0, 1, 2, 3, … 9). Later we use the function to bind data to digits in the clock.
The function returns an array of objects corresponding to individual bars: [top, top-left, top-right, middle, bottom-left, bottom-right, bottom]. Each object defines the bar’s position within the digit (the x and y properties), the bar’s rotation (the rot property), and its state (the on property):
All of the object properties are static save for on, which evaluates to true if the bar should be on for the passed digit value v. We now begin rendering the clock itself, starting with the containing svg element and black background:
Next we render the digits, binding the bar data defined earlier:
Line 6 There’s one digit for each component of the current time. Here we initialize the digits with dummy data representing the time “12:34:56”. We’ll update with the actual time in a bit.
Line 10 Specify the x position for each digit while leaving space for the dots that we’ll add later.
Line 13 Bind data to each bar, passing in the digit value.
Line 17 Use the barPath we created earlier.
Line 18 Set the fill to on or off according the bar’s state.
Line 19 Position the bar according to its configuration.
Next we add the dots that appear between the hour and minute and minute and second. We follow a similar approach to creating the bars, first defining the data to position each dot then binding those data to the SVG elements. dotData defines an [x, y] position for each dot:
At this point the code should render the clock, though the time will always be 12:34:56. The final step is to define an update function that uses the current time to update the digit and bar data:
Lines 8-15 Update the data for the digits. Initially we used [1, 2, 3, 4, 5, 6]; here we’re using the actual time in a 12-hour format.
Line 16 Update the bar data, passing in the owning digit value.
Line 17 Update the bar fill according to its on property value.
Line 20 Call the update function on a suitably short interval to capture the changing time.
That’s it, at long last you’ll be able to know what time it is! The full source file is available here for download. Feel free to comment or ask questions below.
Leave a comment