Skip to content

Instantly share code, notes, and snippets.

@kevinwarne
Last active August 29, 2015 14:11
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 kevinwarne/e46e8a510013cec22b2e to your computer and use it in GitHub Desktop.
Save kevinwarne/e46e8a510013cec22b2e to your computer and use it in GitHub Desktop.
D3 Analog/Digital Clock
<!DOCTYPE html>
<meta charset="utf-8">
<title>D3 Digital/Analog Clock</title>
<style>
.clock-outline {
stroke-width: 3px;
stroke: #666;
fill: #f8f8f8;
}
.clock-center circle{
stroke-width: 3px;
stroke: #666;
/* fill: #666;*/
}
rect.tick{
fill:#666;
}
text{
fill:#666;
font-size:12px;
}
.digital-minutes text,.digital-hours text{
font-size:60px;
}
.digital-hours text{
text-anchor:middle;
}
.analog-hours,.analog-minutes,.analog-seconds{
stroke:#666;
}
.analog-hours{
stroke-width:9;
stroke-linecap:round;
}
.analog-minutes{
stroke-width:7;
stroke-linecap:round;
}
.analog-seconds{
stroke-width:3;
}
body{
font-family:"Lucida Sans Unicode";
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/d3.superformula.v0.min.js"></script>
<script>
var body = d3.select('body')
var size = 400;
//make radio buttons
body.append('label')
.attr('for','analog')
.text('Analog');
body.append('input')
.attr('type','radio')
.attr('name','clock-type')
.attr('value','analog')
.attr('id','analog')
.attr('checked','checked')
.html('Analog')
.on('change',radio_button_click);
body.append('br');
body.append('label')
.attr('for','digital')
.text('Digital');
body.append('input')
.attr('type','radio')
.attr('name','clock-type')
.attr('value','digital')
.attr('id','digital')
.html('Digital')
.on('change',radio_button_click);
body.append('br');
var duration = 500;
var svg = body.append("svg")
.attr("width", 400)
.attr("height", 400);
var small = d3.superformula()
.type(function(d) { return d; })
.size(size);
var sf = d3.superformula()
.type("circle")
.size(size * 150)
.segments(360);
var clockOutline = svg
.append("path")
.attr("class", "clock-outline")
.attr("transform", "translate(200,200)")
.attr("d", sf);
var digitalContent = svg
.append('g')
.attr('class','digital-content')
.attr('display','none')
.style('opacity',0)
.attr("transform", "translate(200,200)");
var digitalMinutes = digitalContent
.append('g')
.attr("transform", "translate(40,22)")
.attr('class','digital-minutes')
.append('text')
var digitalHours = digitalContent
.append('g')
.attr("transform", "translate(-75,22)")
.attr('class','digital-hours')
.append('text')
var analogContent = svg
.append('g')
.attr('class','analog-content')
.attr("transform", "translate(200,200)");
var hourScale = d3.scale.linear()
.range([0,330])
.domain([0,11]);
var minuteScale = secondScale = d3.scale.linear()
.range([0,354])
.domain([0,59]);
var handData = [
{'label':'hours', 'scale':hourScale, 'length': -100},
{'label':'minutes', 'scale':minuteScale, 'length': -150},
{'label':'seconds', 'scale':secondScale, 'length': -150}
]
var analogHands = analogContent.selectAll('.hand')
analogHands.data(handData).enter()
.append('g')
.attr('class',function(d){ return'hand analog-'+d.label;})
.append('line')
.attr('x1',0)
.attr('y1',0)
.attr('x2',0)
.attr('y2',function(d){return d.length})
var analogAxis = analogContent
.append('g')
.attr('class','analog-axis');
var clockContent = svg
.append('g')
.attr('class','clock-content');
var clockCenter = clockContent
.append('g')
.attr('class','clock-center')
.attr("transform", "translate(200,200)");
var centerCircle = clockCenter.selectAll('circle').data([{r:8,y:0}]);
centerCircle.enter()
.append('circle')
.attr("transform", function(d){return "translate(0,"+d.y+")"})
.attr('r',function(d){return d.r;})
.style('fill', '#f8f8f8');
analogAxis.selectAll("rect.tick")
.data(d3.range(60))
.enter().append("svg:rect")
.attr("class", "tick")
.attr("x", -2)
.attr("y", -173)
.attr("width", 4)
.attr("height", function(d, i){return (i%5) ? 5 : 15;})
.attr("transform", function(d, i){return "rotate("+(i*6)+")";});
analogAxis.selectAll("text.label")
.data(d3.range(60))
.enter().append("svg:text")
.attr("class", "label")
.attr("x", function(d, i){return (148)*Math.cos(i*0.1046-1.57)})
.attr("y", function(d, i){return (150)*Math.sin(i*0.1046-1.57)})
.attr("alignment-baseline", "middle")
.attr("text-anchor", "middle")
.attr("transform", function(d, i){return "translate(0,4.5)";})
.text(function(d, i){return (i%5) ?'':d;});
var timeUpdateInterval,
updateInterval = 1000;
toAnalog();
function updateCenter(data, fill){
centerCircle = clockCenter.selectAll('circle').data(data)
centerCircle.enter()
.append('circle')
.attr("r",8)
.style('fill',fill[0]);
centerCircle
.transition()
.duration(duration)
.attr("transform", function(d){return "translate(0,"+d.y+")"})
.attr("r",function(d){return d.r;})
.style('fill',fill[1]);
centerCircle.exit()
.transition()
.duration(duration)
.attr("transform", "translate(0,0)")
.attr("r", 8)
.style('fill',fill[1])
.remove();
}
function toDigital(){
centerCircle.style('opacity',1)
analogContent
.transition()
.duration(duration/2)
.style('opacity',0)
.each('end',function(){d3.select(this).style('display','none');});
digitalContent
.style('display','inline')
.transition()
.delay(duration/2)
.duration(duration/2)
.style('opacity',1);
updateCenter([{r:4,y:-20}, {r:4,y:20}], ['#f8f8f8','#666']);
//reset update interval and align with Date seconds
clearInterval(timeUpdateInterval);
updateDigitalTime();
millisecondOffset = 1000 - new Date(Date.now()).getMilliseconds();
setTimeout(function(){
updateDigitalTime();
timeUpdateInterval = setInterval(function(){
updateDigitalTime();
centerCircle.style('opacity',0)
setTimeout(function(){
centerCircle.style('opacity',1)
},350)
},updateInterval);
}, millisecondOffset);
}
function toAnalog(){
centerCircle.style('opacity',1)
analogContent
.style('display','inline')
.transition()
.delay(duration/2)
.duration(duration/2)
.style('opacity',1);
digitalContent
.transition()
.duration(duration/2)
.style('opacity',0)
.each('end',function(){d3.select(this).style('display','none');});
updateCenter([{r:8,y:0}], ['#666','#f8f8f8']);
//reset update interval and align with Date seconds
clearInterval(timeUpdateInterval);
updateAnalogTime();
millisecondOffset = 1000 - new Date().getMilliseconds();
setTimeout(function(){
updateAnalogTime();
timeUpdateInterval = setInterval(updateAnalogTime,updateInterval);
}, millisecondOffset);
}
function updateDigitalTime(){
var time = new Date();
digitalHours.text(('0' + (time.getHours() % 12)).slice(-2));
digitalMinutes.text(('0' + time.getMinutes()).slice(-2));
}
function updateAnalogTime(){
var time = new Date();
handData[0].value = (time.getHours() % 12) + time.getMinutes()/60 ;
handData[1].value = time.getMinutes();
handData[2].value = time.getSeconds();
d3.selectAll('.hand').data(handData)
.transition()
.attr('transform',function(d){return 'rotate('+ d.scale(d.value) +')';});
}
function radio_button_click(d){
var checked_value = d3.select(this).attr('value');
if(checked_value == 'digital'){
d3.select(".clock-outline").transition().duration(duration).attr("d", sf.type('rectangle'));
toDigital();
}else{
d3.select(".clock-outline").transition().duration(duration).attr("d", sf.type('circle'));
toAnalog();
}
}
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment