Skip to content

Instantly share code, notes, and snippets.

@wiinci
Last active August 29, 2015 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wiinci/9218980 to your computer and use it in GitHub Desktop.
Save wiinci/9218980 to your computer and use it in GitHub Desktop.
D3 Clock

A simple animated clock icon made to delight MailChimp users (and folks like Adam). On load and refresh, the clock adjusts to the current time.

Thanks to all involved:

  • Thierry’s tweet started it all
  • Eric and Federico got the ball rolling
  • Guan gave a quick Dojo widget walk-through for use across MailChimp
  • Alvaro noticed that the hour-hand wasn’t right and scaleBetweenHours helped solve that
  • Caleb designed the previously static clock icon

Live on bl.ocks and Codepen.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
.clock { margin: 7% auto; display: block; }
</style>
<div class="d3clock"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
(function() {
// Clock look-n-feel: customize at will!
var width = 360,
height = 360,
strokeWidth = 6,
clockFillColor = "none",
clockBorderColor = "#B7B7B7",
clockHandColor = "#FEBE12",
clockCenterColor = "#FEBE12",
transitionEnabled = 1,
radius = width / 2,
vis, clock, hourPosition, minutePosition, clockhand, hourPositionOffset;
// Set up time
var now = new Date();
var data = [{
'unit': 'minutes',
'value': now.getMinutes()
}, {
'unit': 'hours',
'value': now.getHours()
}];
// Set up Scales
// Map 60 minutes onto a radial 360 degree range.
var scaleMins = d3.scale.linear()
.domain([0, 59 + 59 / 60])
.range([0, 2 * Math.PI]);
// Map 12 hours onto a radial 360 degree range.
var scaleHours = d3.scale.linear()
.domain([0, 11 + 59 / 60])
.range([0, 2 * Math.PI]);
// Every hour, the minute hand rotates 360 degrees and the hour hand rotates 30 degrees.
// To get the final, accurate hour hand position, the linear movement of the minute hand
// is mapped to a 30 degree radial angle and the resulting angular offset
// is added to the hour hand position (in scaleHours above).
var scaleBetweenHours = d3.scale.linear()
.domain([0, 59 + 59 / 60])
.range([0, Math.PI / 6]);
// Set up SVG
vis = d3.select("div.d3clock")
.append("svg:svg")
.attr("class", "clock")
.attr("width", width)
.attr("height", height);
clock = vis.append("svg:g")
.attr("transform", "translate(" + radius + "," + radius + ")");
// Clock face
clock.append("svg:circle")
.attr("class", "clockface")
.attr("r", radius - strokeWidth)
.attr("fill", clockFillColor)
.attr("stroke", clockBorderColor)
.attr("stroke-width", strokeWidth * 2);
// When animating, set 12 o’clock as the clockhand animation start position
minutePosition = d3.svg.arc()
.innerRadius(0)
.outerRadius((3 / 4) * radius)
.startAngle(0)
.endAngle(0);
hourPosition = d3.svg.arc()
.innerRadius(0)
.outerRadius((1 / 2) * radius)
.startAngle(0)
.endAngle(0);
// When not animating, set the clockhand positions based on time
minutePositionFinal = d3.svg.arc()
.innerRadius(0)
.outerRadius((2 / 3) * radius)
.startAngle(function (d) {
return scaleMins(+d.value);
})
.endAngle(function (d) {
return scaleMins(+d.value);
});
hourPositionFinal = d3.svg.arc()
.innerRadius(0)
.outerRadius((1 / 2) * radius)
.startAngle(function (d) {
return (scaleHours(+d.value % 12) + scaleBetweenHours(hourPositionOffset));
})
.endAngle(function (d) {
return (scaleHours(+d.value % 12) + scaleBetweenHours(hourPositionOffset));
});
// Add clockhands to the clockface
clockhand = clock.selectAll(".clockhand")
.data(data)
.enter()
.append("svg:path")
.attr("class", "clockhand")
.attr("stroke", clockHandColor)
.attr("stroke-width", strokeWidth + 4)
.attr("stroke-linecap", "round")
.attr("stroke-linejoin", "round")
.attr("fill", "none");
// Animate clockhands!
if (transitionEnabled) {
clockhand.attr("d", function (d) {
if (d.unit === "minutes") {
hourPositionOffset = +d.value;
return minutePosition();
} else if (d.unit === "hours") {
return hourPosition();
}
})
.transition()
.delay(333)
.duration(555)
.ease("elastic", 1, 4)
.attrTween("transform", tween);
} else {
clockhand.attr("d", function (d) {
if (d.unit === "minutes") {
hourPositionOffset = +d.value;
return minutePositionFinal(d);
} else if (d.unit === "hours") {
return hourPositionFinal(d);
}
});
}
function tween(d, i, a) {
if (d.unit === "minutes") {
return d3.interpolate("rotate(0)", "rotate(" + (scaleMins(+d.value) * (180 / Math.PI)) + ")");
} else if (d.unit === "hours") {
return d3.interpolate("rotate(0)", "rotate(" + ((scaleHours(+d.value % 12) + scaleBetweenHours(hourPositionOffset)) * (180 / Math.PI)) + ")");
}
}
// Add center dial
return clock.append("svg:circle")
.attr("class", "centerdot")
.attr("r", strokeWidth + 2)
.attr("fill", "#fff")
.attr("stroke", clockCenterColor)
.attr("stroke-width", strokeWidth + 2);
}());
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment