|
<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> |