Skip to content

Instantly share code, notes, and snippets.

@jcdcodes
Last active March 1, 2016 15:25
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 jcdcodes/35b102254d8802a19644 to your computer and use it in GitHub Desktop.
Save jcdcodes/35b102254d8802a19644 to your computer and use it in GitHub Desktop.
Braunish Travel Alarm

CSS sure can turn into a lot of pixel tweaking if you let it. I called it quits when the need to tweak the horizontal spacing of the hour numerals from 10 to 2 became apparent.

<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="//d3js.org/d3.v3.min.js"></script>
<style type="text/css">
.border {
fill: #f8f8f8;
stroke: #aaa;
stroke-width: 1px;
}
.second {
fill: none;
stroke: #fd4;
stroke-width: 3.8px;
}
.hand {
stroke-linejoin: round;
stroke-linecap: round;
}
.hour {
stroke: white;
stroke-width: 16px;
}
.minute {
stroke: white;
stroke-width: 8.0px;
}
.hub {
stroke: none;
stroke-width: 4.2px;
fill: #fd4;
}
.bezel {
stroke: #444;
stroke-width: 10.5px;
fill: #111;
}
.hourlabels {
font-family: Helvetica, Arial, sans-serif;
font-weight: Light;
font-size: 26pt;
letter-spacing: -0.1em;
fill: #ccc;
}
.quartz {
font-family: Helvetica, Arial, sans-serif;
font-size: 10pt;
fill: #ddd;
}
.majortick {
stroke: white;
stroke-width: 2.5px;
}
.minortick {
stroke: white;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<script type="text/javascript">
var w = 960,
h = 500,
x = d3.scale.ordinal().domain(d3.range(3)).rangePoints([0, w], 2);
var fields = [
{name: "hour", value: 0, size: 12, handlength: 167},
{name: "minute", value: 0, size: 60, handlength: 196},
{name: "second", value: 0, size: 60, handlength: 198}
];
var handclasses = ["hour hand", "minute hand", "second hand"];
function angle(d) {
return (d.value / d.size) * 2 * Math.PI;
}
function clockAngle(d) {
return -Math.PI/2 + angle(d);
}
var hand = d3.svg.arc()
.innerRadius(10)
.outerRadius(function(d,i) { return d.handlength; })
.startAngle(angle)
.endAngle(angle);
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g");
//
// Border, dial, numerals, and hub. All fixed elements.
//
svg.append("rect").attr("height", h).attr("width", w).attr("class", "border");
var bezelradius = 230;
svg.append("rect")
.attr("class", "bezel")
.attr("x", w/2 - bezelradius)
.attr("y", h/2 - bezelradius)
.attr("width", 2 * bezelradius)
.attr("height", 2 * bezelradius)
.attr("rx", 30)
.attr("ry", 30)
;
svg.append("circle")
.attr("class", "bezel")
.attr("cx", w/2)
.attr("cy", h/2)
.attr("r", 210);
svg.selectAll("text")
//.data(["XII", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI"])
//.data(["12", "", "", "3", "", "", "6", "", "", "9", "", ""])
.data(["12", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"])
.enter().append("text")
.text(function(d) { return d; })
.attr("class", "hourlabels")
.attr("x", function(d, i) { return 170 * Math.cos(clockAngle({value: i, size: 12})); })
.attr("y", function(d, i) { return 167 * Math.sin(clockAngle({value: i, size: 12})); })
.attr("dy", 13)
.attr("text-anchor", "middle")
.attr("transform", "translate(" + (w/2) + "," + (h/2) + ")");
svg.selectAll("line.majortick")
.data(d3.range(12))
.enter().append("line")
.attr("x1", function(d, i) { return 187 * Math.cos(clockAngle({value: i, size: 12})); })
.attr("x2", function(d, i) { return 200 * Math.cos(clockAngle({value: i, size: 12})); })
.attr("y1", function(d, i) { return 187 * Math.sin(clockAngle({value: i, size: 12})); })
.attr("y2", function(d, i) { return 200 * Math.sin(clockAngle({value: i, size: 12})); })
.attr("class", "majortick")
.attr("transform", "translate(" + (w/2) + "," + (h/2) + ")");
svg.selectAll("line.minortick")
.data(d3.range(60))
.enter().append("line")
.attr("x1", function(d, i) { return 193 * Math.cos(clockAngle({value: i, size: 60})); })
.attr("x2", function(d, i) { return 200 * Math.cos(clockAngle({value: i, size: 60})); })
.attr("y1", function(d, i) { return 193 * Math.sin(clockAngle({value: i, size: 60})); })
.attr("y2", function(d, i) { return 200 * Math.sin(clockAngle({value: i, size: 60})); })
.attr("class", "minortick")
.attr("transform", "translate(" + (w/2) + "," + (h/2) + ")");
svg.append("text")
.text("quartz")
.attr("class", "quartz")
.attr("text-anchor", "middle")
.attr("transform", "translate(" + (w/2) + "," + (h/2 - 67) + ")");
setInterval(function() {
var now = new Date();
fields[2].previous = fields[2].value; fields[2].value = now.getSeconds();
fields[1].previous = fields[1].value; fields[1].value = now.getMinutes() + (fields[2].value / 60);
fields[0].previous = fields[0].value; fields[0].value = now.getHours() % 12 + (fields[1].value / 60);
// Smoothly animate hands between x:59 and x+1:00
if (fields[2].value == 0 && fields[2].previous >= 59) fields[2].previous = -1;
if (fields[1].value == 0 && fields[1].previous >= 59) fields[1].previous = -1;
// Here, we filter not for entries where d.value evaluates to true, but where
// there is a number. Otherwise we don't get to see all three hands when
// it's midnight, noon, or the top of the hour; zero evaluates to false in javascript.
var line = svg.selectAll("path.hand")
.data(fields.filter(function(d) { return d.value > -1; }), function(d) { return d.name; });
line.enter().append("svg:path")
.attr("transform", function(d) { return "translate(" + (w/2) + "," + (h/2) + ")"; })
.attr("class", function(d, i) { return handclasses[i]; })
;
line.transition()
.ease("bounce")
.duration(200)
.attrTween("d", tweenWith(hand))
;
svg.append("circle")
.attr("class", "hub")
.attr("cx", w/2)
.attr("cy", h/2)
.attr("r", 25);
}, 1000);
function tweenWith(f) {
return function arcTween(b) {
var i = d3.interpolate({value: b.previous}, b);
return function(t) {
return f(i(t));
};
};
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment