Skip to content

Instantly share code, notes, and snippets.

@HarryStevens
Last active Dec 13, 2016
Embed
What would you like to do?
Self-Rotating Analog Clock
license: gpl-3.0

This block uses D3.js to draw a clock that tells the time by rotating itself. No need for hands! It also uses chroma.js to color the background according to the time of day.

I learned how to calculate the points around a circle's circumference from this stackoverflow thread. Mathematicians call it the parametric equation for a circle.

<html>
<head>
<style>
body {
margin: 0;
font-family: "Helvetica Neue", sans-serif;
}
.border {
fill: #fff;
stroke: #3a403d;
stroke-width: 20px;
}
.tick {
stroke: #3a403d;
}
.tick.hour {
stroke-width: 4px;
}
.tick-minute {
stroke-width: 1px;
}
.inner-circle {
fill: #fff;
}
.text {
fill: #fff;
font-size: 10px;
}
.track {
fill: #e74c3c;
}
.half {
fill: #3a403d;
}
</style>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.2.1/chroma.min.js"></script>
<script>
// declare a whole bunch of variable to draw with
var borderWidth = 20,
every = 144;
svg = d3.select("body").append("svg"),
g = d3.select("svg").append("g"),
circle = g.append("circle"), // lch mode creates more saturated colors
color = chroma.scale(["#281324", "#ffff00", "#281324"]).mode('lch'), // a color scale for the background
ticks = [],
text = [];
for (let i = 1; i <= every; i++){
ticks[i] = g.append("line");
text[i] = g.append("text").attr("text-anchor", "middle");
}
var innerCircle = g.append("circle"),
half = svg.append("text").attr("text-anchor", "middle"),
track = svg.append("polygon");
function draw(){
var width = window.innerWidth,
height = window.innerHeight,
radius = height < width ? height / 2 - borderWidth / 2 : width / 2 - borderWidth / 2; // the radius of the circle will change depending on the window orientation
// the big svg wrapper
svg
.attr("width", width)
.attr("height", height);
// a square-shaped g element that can be rotated around its center
g
.attr("width", width > height ? height : width)
.attr("height", width > height ? height : width);
// clock border
circle
.attr("class", "border")
.attr("cx", width / 2)
.attr("cy", height / 2)
.attr("r", radius);
// calculate ticks
for (let i = 1; i <= every; i++){
// The parametric equation for a circle is
// x = cx + r * cos(a)
// y = cy + r * sin(a)
// Where r is the radius, cx,cy the origin, and a the angle in radians.
// A radian is 57.2958 degrees
var x = radius * Math.cos((360 * (i / every)) / 57.2958) + width / 2,
y = radius * Math.sin((360 * (i / every)) / 57.2958) + height / 2;
// draw ticks, with thicker ticks for the hours
ticks[i]
.attr("class", "tick " + (i / 12 % 1 == 0 ? "hour" : "minute"))
.attr("x1", width / 2)
.attr("y1", height / 2)
.attr("x2", x)
.attr("y2", y);
// first, only select the hours, and then calculate the hours
// (knowing that the 12 defaults to where the 3 should be)
var t = i / 12 % 1 == 0 ? i / 12 >= 10 ? i / 12 - 9 : i / 12 + 3 : null;
text[i]
.attr("class", "text")
.attr("x", x)
.attr("y", y)
.attr("dy", borderWidth / 5)
.text(t);
}
// plop a white circle in the center to hide some of the tick lines
innerCircle
.attr("class", "inner-circle")
.attr("cx", width / 2)
.attr("cy", height / 2)
.attr("r", radius / 1.2);
// use d3 timer to handle the gradual rotation
d3.timer(function(elapsed){
// calculate rotation based on the current time
var date = new Date(),
hrs = date.getHours(),
hrs = hrs > 12 ? hrs - 12 : hrs,
min = date.getMinutes(),
sec = date.getSeconds(),
time = hrs * 3600 + min * 60 + sec,
tot = 12 * 3600,
rot = -((time * 360) / tot);
// AM or PM?
half
.attr("class", "half")
.attr("x", width / 2)
.attr("y", height / 2)
.attr("dy", radius / 12)
.style("font-size", radius / 3)
.text(new Date().getHours() > 11 ? "PM" : "AM"); // update the AM/PM situation
// calculate the background color (0 and 1 are beginning and end of day, .5 is noon)
var c = (date.getHours() * 3600 + min * 60 + sec) / (3600 * 24);
// svg background
svg
.style("background", color(c));
g.attr("transform", "rotate(" + rot + " " + width / 2 + " " + height / 2 + ")"); // rotate the clock
// rotate the text (backwards)
for (let i = 1; i <= every; i++){
var x = radius * Math.cos((360 * (i / every)) / 57.2958) + width / 2,
y = radius * Math.sin((360 * (i / every)) / 57.2958) + height / 2;
text[i].attr("transform", "rotate(" + (-rot) + " " + x + " " + y + ")")
};
});
// now draw the track on top of the whole thing
var topX = width / 2,
topY = height < width ? 0 : height / 2 - radius - borderWidth / 2,
a = (topX - 5) + "," + topY,
b = (topX + 5) + "," + topY,
c = topX + "," + (topY + radius / 6),
tri = a + " " + b + " " + c;
// draw the track triangle
track
.attr("class", "track")
.attr("points", tri);
} // end draw()
// allow for resizing
window.onload = draw, window.onresize = draw;
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment