All examples By author By category About

lbrucel

static study for clock

Concentric Clock Using D3.js

https://d3js.org -- for an overview, examples, documentation and the source code https://github.com/d3/d3/wiki/Tutorials -- tons of samples from the

I set out to learn the basics of D3.js. I decided to build a clock made strictly of circles. In this article I'll explain the idea, my development process, and step through my code that brings the idea to life.

The Idea -- A Minimalist Clock Made Strictly of Circles

https://clock.lbrucel.com -- the finished version

Development Plan

http://blockbuilder.org/ is an excellent web-based tool for developing and deploying your D3 projects.

First and foremost I needed to see that the idea made sense to me. I needed to simply draw a static version of the clock, then manually change the time to see that everything rendered as I expected. I opened a new project on blockbuild.org and began to layout out the page. Once I got the foundation in place, I moved on to make the clock dynamic -- to change appropriately over time.

Stepping Through The Code (Static Version)

http://blockbuilder.org/lbrucel/fbab9e0af6f9594a0bc7bd7f93741f09

Add a script tag to the D3.js library of your choice. Hop over to https://d3js.org to grab the latest version.

  <script src="https://d3js.org/d3.v4.min.js"></script>

Next, lets set up some variables we'll reference later in the code. Notice I've commented out getting of the actual date so that I'll can set hours, minutes and seconds manually until I'm ready for real time.

const margin = { top: 0, right: 0, bottom: 0, left: 0 }
const width = window.innerWidth - margin.left - margin.right
const height = window.innerHeight - margin.top - margin.bottom

// let date = new Date();  hours = date.getHours() % 12; minutes = date.getMinutes(); seconds = date.getSeconds()
// set hours, minutes and seconds manually - play with these values to see that the circles layout appropriately
let hours = 3   //range from 0 - 12
let minutes = 30 //range from 0 - 60
let seconds = 45 //range from 0 - 60

Now we'll set a few more constants for each of the circles. This may look a little tedious but it allows us to play with the size of the circles and have everything from here on out render nicely.

// set constants to define the radius of each circle
// faceRadius is 95% of smaller(width, height)
const outterRadius = (Math.min(width, height) / 2) * 0.95
const faceR = outterRadius
const hourR = faceR * 0.75
const minR = hourR * 0.75
const secR = minR * 0.75

// set constants to define the distance the center of the clock face
const hRadiusDiff = faceR - hourR
const mRadiusDiff = hourR - minR
const sRadiusDiff = minR - secR
//clock face is the maximum circle in the viewport, centered, set the coordinates for its center here
const faceCenter = [width / 2, height / 2]

Add the function xyFromBase to do the magic of transforming time to radians and return the coordindates of the center of the circle.

//xyFromBase is a function that returns the center of the circle
xyFromBase = (baseTime, radius, base) => {
  //baseTime is current minutes, seconds or hours
  //radius is distance from the current circle to the next outter circle
  //base is 60 for minutes and seconds; 12 for hours
  let radians = (baseTime * 360) / base
  radians = (radians * Math.PI) / 180
  radians = radians - Math.PI / 2
  return [Math.cos(radians) * radius, Math.sin(radians) * radius]
}

Finally getting into some D3.js here! We'll add an SVG element to the body of the DOM, then append the four circles: clockFaceCircle, hourCircle, minuteCircle and secondCircle. Note the call to the function xyFromBase before setting up hours, minutes and seconds.

//append an svg element to the body
let svg = d3
  .select('body')
  .append('svg')
  .attr('width', width)
  .attr('height', height)

let clockFaceCircle = svg
  .append('circle')
  .style('fill', 'lightblue')  //using lightblue for illustration purposes
  .attr('cx', faceCenter[0])
  .attr('cy', faceCenter[1])
  .attr('r', faceR)

//draw the hour circle
let hCenter = xyFromBase(hours, hRadiusDiff, 12)
hCenter[0] += faceCenter[0]
hCenter[1] += faceCenter[1]
let hourCircle = svg
  .append('circle')
  .style('fill', 'none')
  .style('stroke', '#990000')
  .style('stroke-width', 6)
  .attr('cx', hCenter[0])
  .attr('cy', hCenter[1])
  .attr('r', hourR)

//draw the minute circle
let mCenter = xyFromBase(minutes, mRadiusDiff, 60)
mCenter[0] += hCenter[0]
mCenter[1] += hCenter[1]
let minuteCircle = svg
  .append('circle')
  .style('fill', 'none')
  .style('stroke', '#20b2aa')
  .style('stroke-width', 6)
  .attr('cx', mCenter[0])
  .attr('cy', mCenter[1])
  .attr('r', minR)

//draw the seconds circle
let sCenter = xyFromBase(seconds, sRadiusDiff, 60)
sCenter[0] += mCenter[0]
sCenter[1] += mCenter[1]
let secondCircle = svg
  .append('circle')
  .style('fill', 'none')
  .style('stroke', '#001be5')
  .style('stroke-width', 6)
  .attr('cx', sCenter[0])
  .attr('cy', sCenter[1])
  .attr('r', secR)

Last but not least, we'll add some code to display the actual time.

//add the time in text -- and make it clean!
addZeroBefore = time => {
  return (time < 10 ? '0' : '') + time
}
const timeText = [
  {
    value: `${addZeroBefore(hours)}:${addZeroBefore(
        minutes
      )}:${addZeroBefore(seconds)}`,
    size: 0,
    label: ''
  },
]

let field = svg
  .selectAll('.field')
  .data(timeText)
  .enter()
  .append('g')
  .attr('class', 'field')

let label = field
  .append('text')
  .attr('x', 40)
  .attr('y', height - 40)
  .attr('font-size', '20px')
  .attr('font-family', 'sans-serif')
  .style('fill', 'white')
  .attr('class', 'label')

label.text(function(d) {
    return d.value + d.label
})