Skip to content

Instantly share code, notes, and snippets.

@blech
Forked from tomgp/LICENSE.txt
Last active December 26, 2015 21:59
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 blech/7219737 to your computer and use it in GitHub Desktop.
Save blech/7219737 to your computer and use it in GitHub Desktop.
DST Transitions

Using some animated clocks from d3 to show the difference between how US and EU timezone transitions work.

The top row shows three clocks in the three main European Union timezones on the last Sunday in October. As London passes 0200 BST it resets to 0100 GMT; simultaneously, the clocks change in Berlin and Helsinki.

The bottom row shows four clocks for the US timezones, on the first Sunday in November. In contrast to the EU clocks, the DST ripples across the country at 0200 local time.

(Thanks to bl.ocks.org/tomgp/6475678 for the starting point and to @mbostock for help with heights.)

<!DOCTYPE html>
<meta charset="utf-8">
<html>
<head>
<title>DST Transitions</title>
<style>
body {
background: #fff;
}
svg{
stroke: #000;
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
}
h1 {
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
font-weight: 200;
font-size: 24px;
}
div.clocks {
height: 215px;
}
#rim {
fill: none;
stroke: #999;
stroke-width: 1.5px;
}
.minute-hand{
stroke-width:2;
stroke-linecap:round;
}
.hour-hand{
stroke-width:3;
stroke-linecap:round;
}
.hands-cover{
stroke-width:.75;
fill:#fff;
}
.minute-tick{
stroke-width:.75;
fill:#000;
}
.hour-tick{
stroke-width:2; //same as the minute hand
}
.minute-label{
font-size: 14px;
stroke: #666;
}
.hour-label{
font-size: 24px;
}
</style>
</head>
<body>
<h1>Europe (Synchronised)</h1>
<div id="europe" class="clocks"></div>
<h1>USA (Local)</h1>
<div id="usa" class="clocks"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var radians = 0.0174532925,
clockRadius = 100,
margin = 5,
width = (clockRadius+margin)*2,
height = (clockRadius+margin)*4+150,
hourHandLength = 2*clockRadius/3,
minuteHandLength = clockRadius,
minuteTickStart = clockRadius;
minuteTickLength = -10,
hourTickStart = clockRadius,
hourTickLength = -18
minuteLabelRadius = clockRadius - 40;
minuteLabelYOffset = 5
hourLabelRadius = clockRadius - 40
hourLabelYOffset = 7;
var hourScale = d3.scale.linear()
.range([0,330])
.domain([0,11]);
var minuteScale = d3.scale.linear()
.range([0,354])
.domain([0,59]);
var london = {
'handData': [
{ type:'hour', length:-hourHandLength, scale:hourScale, value:0 },
{ type:'minute', value:0, length:-minuteHandLength, scale:minuteScale }
],
'dst': 2,
'changed': false,
'name': 'London',
'id': 'lhr',
'where': 'europe'
};
var berlin = {
'handData': [
{ type:'hour', length:-hourHandLength, scale:hourScale, value:1 },
{ type:'minute', value:0, length:-minuteHandLength, scale:minuteScale }
],
'dst': 3,
'changed': false,
'name': 'Berlin',
'id': 'txl',
'where': 'europe'
};
var helsinki = {
'handData': [
{ type:'hour', length:-hourHandLength, scale:hourScale, value:2 },
{ type:'minute', value:0, length:-minuteHandLength, scale:minuteScale }
],
'dst': 4,
'changed': false,
'name': 'Helsinki',
'id': 'hel',
'where': 'europe'
};
// note that these are correct relative to each other but not to Europe
// I didn't think waiting for four clock rotations was a good idea
var la = {
'handData': [
{ type:'hour', length:-hourHandLength, scale:hourScale, value:8 },
{ type:'minute', value:0, length:-minuteHandLength, scale:minuteScale }
],
'dst': 2,
'changed': false,
'name': 'Los Angeles',
'id': 'lax',
'where': 'usa'
};
var denver = {
'handData': [
{ type:'hour', length:-hourHandLength, scale:hourScale, value:9 },
{ type:'minute', value:0, length:-minuteHandLength, scale:minuteScale }
],
'dst': 2,
'changed': false,
'name': 'Denver',
'id': 'den',
'where': 'usa'
};
var chicago = {
'handData': [
{ type:'hour', length:-hourHandLength, scale:hourScale, value:10 },
{ type:'minute', value:0, length:-minuteHandLength, scale:minuteScale }
],
'dst': 2,
'changed': false,
'name': 'Chicago',
'id': 'ord',
'where': 'usa'
};
var nyc = {
'handData': [
{ type:'hour', length:-hourHandLength, scale:hourScale, value:11 },
{ type:'minute', value:0, length:-minuteHandLength, scale:minuteScale }
],
'dst': 2,
'changed': false,
'name': 'New York',
'id': 'jfk',
'where': 'usa'
};
clocks = [london, berlin, helsinki, la, denver, chicago, nyc];
function drawClock(clock){ //create all the clock elements
var svg = d3.select("div#"+clock['where']).append("svg")
.attr("width", width)
.attr("height", height);
var face = svg.append('g')
.attr('id','clock-face-'+clock['id'])
.attr('transform','translate(' + (clockRadius + margin) + ',' + (clockRadius + margin) + ')');
//add marks for minutes
face.selectAll('.minute-tick')
.data(d3.range(0,60)).enter()
.append('line')
.attr('class', 'minute-tick')
.attr('x1',0)
.attr('x2',0)
.attr('y1',minuteTickStart)
.attr('y2',minuteTickStart + minuteTickLength)
.attr('transform',function(d){
return 'rotate(' + minuteScale(d) + ')';
});
face.selectAll('.minute-label')
.data(d3.range(5,61,5))
.enter()
.append('text')
.attr('class', 'minute-label')
.attr('text-anchor','middle')
.attr('x',function(d){
return minuteLabelRadius*Math.sin(minuteScale(d)*radians);
})
.attr('y',function(d){
return -minuteLabelRadius*Math.cos(minuteScale(d)*radians) + minuteLabelYOffset;
})
.text(function(d){
if (d==30) {
return clock['name'];
}
return '';
});
//... and hours
face.selectAll('.hour-tick')
.data(d3.range(0,12)).enter()
.append('line')
.attr('class', 'hour-tick')
.attr('x1',0)
.attr('x2',0)
.attr('y1',hourTickStart)
.attr('y2',hourTickStart + hourTickLength)
.attr('transform',function(d){
return 'rotate(' + hourScale(d) + ')';
});
var hands = face.append('g').attr('id','clock-hands-'+clock['id']);
face.append('g').attr('id','face-overlay')
.append('circle').attr('class','hands-cover')
.attr('x',0)
.attr('y',0)
.attr('r',clockRadius/20);
hands.selectAll('line')
.data(clock['handData'])
.enter()
.append('line')
.attr('class', function(d){
return d.type + '-hand';
})
.attr('x1',0)
.attr('y1',function(d){
return d.balance ? d.balance : 0;
})
.attr('x2',0)
.attr('y2',function(d){
return d.length;
})
.attr('transform',function(d){
return 'rotate('+ d.scale(d.value) +')';
});
}
function moveHands(clock){
d3.select('#clock-hands-'+clock['id']).selectAll('line')
.data(clock['handData'])
.transition()
.ease('linear')
.attr('transform',function(d){
return 'rotate('+ d.scale(d.value) +')';
});
}
function updateData(clock){
var inc = 75;
clock['handData'][0].value += inc/3600;
clock['handData'][1].value += inc/60;
if (clock['dst']-1 < clock['handData'][0].value &&
clock['handData'][0].value < clock['dst'] &&
!clock['changed']) {
d3.select('#clock-face-'+clock['id']).style('stroke', '#f00')
}
if (clock['dst'] < clock['handData'][0].value &&
clock['handData'][0].value < clock['dst']+1 &&
!clock['changed']) {
clock['handData'][0].value = clock['dst']-1;
clock['changed'] = true;
d3.select('#clock-face-'+clock['id']).style('stroke', '#000')
}
if (12 < clock['handData'][0].value) {
clock['handData'][0].value -= 12;
clock['changed'] = false;
}
}
for (var i = 0; i < clocks.length; i++) {
drawClock(clocks[i]);
}
setInterval(function(){
for (var i = 0; i < clocks.length; i++) {
updateData(clocks[i]);
moveHands(clocks[i]);
};
}, 100);
d3.select(self.frameElement).style("height", height + "px");
</script>
</body>
</html>

Suggestions:

  • Flash the clock red when the time zone changes (or just the hands, or the name?)
  • Suppress transition for setting clock back - hand should snap back
  • Allow switch to show entering DST instead of leaving it
  • Figure out how to reduce CPU load (linear transitions that run for longer?)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment