Shows a duration based on the polar clock style, based on a Flash screensaver by pixelbreaker and a d3 version by Mike Bostock.
-
-
Save umbrae/a0b9548d7e1bf295578efa66c23ac0d8 to your computer and use it in GitHub Desktop.
Polar Clock (Duration Mode)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: gpl-3.0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
body { | |
background: #FFF; | |
margin: auto; | |
width: 960px; | |
color: #EEE; | |
} | |
.arc-text { | |
font: 16px sans-serif; | |
} | |
.arc-center { | |
fill: none; | |
} | |
#credit { | |
display: none; | |
position: absolute; | |
font: 10px sans-serif; | |
right: 10px; | |
bottom: 10px; | |
color: #ddd; | |
} | |
#credit a { | |
color: inherit; | |
} | |
.attention { | |
margin-top: 120px; | |
font-size: 9px; | |
font-weight: bold; | |
width: 100%; | |
color: #999; | |
text-align: center; | |
font-family: sans-serif; | |
} | |
#num_viewers_display { | |
font-size: 120%; | |
} | |
#humanDescription { | |
display: block; | |
width: 100%; | |
margin-top: 60px; | |
font-family: sans-serif; | |
color: #444; | |
font-weight: 900; | |
font-size: 47px; | |
line-height: 1.4; | |
text-align: center; | |
} | |
</style> | |
<!-- | |
<input type="range" min="1" max="300" step="1" name="num_viewers" id="num_viewers" value="2" /> | |
<label for="num_viewers">Viewers</label> | |
--> | |
<div id="humanDescription"></div> | |
<div class="attention"><span id="num_viewers_display">0</span></div> | |
<script src="https://d3js.org/d3.v3.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/humanize-duration/3.20.1/humanize-duration.js"></script> | |
<script> | |
var width = 960, | |
height = 800, | |
radius = Math.min(width, height) / 1.9, | |
spacing = .09, | |
start = moment([2018, 1, 4, 14, 3, 22]), | |
cur_time = moment([2018, 1, 4, 14, 3, 22]), | |
last_update = moment(), | |
viewerCount = 0; | |
var color = d3.scale.linear() | |
// .range(["hsl(-180,60%,50%)", "hsl(180,60%,50%)"]) | |
.range(["hsl(0,0,100%)", "hsl(0,0,50%)"]) | |
.interpolate(function(a, b) { var i = d3.interpolateString(a, b); return function(t) { return d3.hsl(i(t)); }; }); | |
var arcBody = d3.svg.arc() | |
.startAngle(0) | |
.endAngle(function(d) { return d.value * 2 * Math.PI; }) | |
.innerRadius(function(d) { return d.index * radius; }) | |
.outerRadius(function(d) { return (d.index + spacing) * radius; }) | |
.cornerRadius(6); | |
var arcCenter = d3.svg.arc() | |
.startAngle(0) | |
.endAngle(function(d) { return d.value * 2 * Math.PI; }) | |
.innerRadius(function(d) { return (d.index + spacing / 2) * radius; }) | |
.outerRadius(function(d) { return (d.index + spacing / 2) * radius; }); | |
var svg = d3.select("body").append("svg") | |
.attr("width", width) | |
.attr("height", height) | |
.append("g") | |
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); | |
var field = svg.selectAll("g") | |
.data(fields) | |
.enter().append("g"); | |
field.append("path") | |
.attr("class", "arc-body"); | |
field.append("path") | |
.attr("id", function(d, i) { return "arc-center-" + i; }) | |
.attr("class", "arc-center"); | |
field.append("text") | |
.attr("dy", ".35em") | |
.attr("dx", ".75em") | |
.style("text-anchor", "start") | |
.append("textPath") | |
.attr("startOffset", "50%") | |
.attr("class", "arc-text") | |
.attr("xlink:href", function(d, i) { return "#arc-center-" + i; }); | |
setInterval(refreshViewerCount, 5000); | |
tick(); | |
d3.select(self.frameElement).style("height", height + "px"); | |
function refreshViewerCount() { | |
var API_CLIENT_ID = '9lknlv5t889ducax0lzyfxyarnynb9'; | |
var TWITCH_USER_ID = '57095087'; | |
fetch('https://api.twitch.tv/helix/streams?user_id=' + TWITCH_USER_ID, | |
{ | |
mode: 'cors', | |
headers: { | |
'Client-ID': API_CLIENT_ID | |
} | |
} | |
).then(response => { | |
return response.json(); | |
}).then(function(responseJson) { | |
viewerCount = responseJson['data'][0]['viewer_count']; | |
document.querySelector('#num_viewers_display').innerText = viewerCount; | |
console.log("Updated viewer count to " + viewerCount); | |
}).catch(error => console.error(error)); | |
} | |
function tick() { | |
/* | |
if (!document.hidden) field | |
.each(function(d) { this._value = d.value; }) | |
.data(fields) | |
.each(function(d) { d.previousValue = this._value; }) | |
.transition() | |
.duration(50) | |
.each(fieldTransition); | |
*/ | |
var now = moment(); | |
var num_viewers = viewerCount; // document.querySelector('#num_viewers').value; | |
var time_since_last_update = now.diff(last_update); | |
last_update = now; | |
cur_time = cur_time.add(time_since_last_update * num_viewers); | |
var delta = moment.duration(cur_time.diff(start)); | |
var humanSeconds = humanizeDuration(parseInt(delta.as('seconds')) * 1000); | |
// Hack for keeping seconds on there so it doesn't wrap every minute | |
// Hour will still wrap but whatever | |
if (humanSeconds.indexOf('second') == -1) { | |
humanSeconds = humanSeconds + ", 0 seconds" | |
} | |
document.querySelector('#humanDescription').innerHTML = | |
"Viewers have spent " + humanSeconds + " watching this stream."; | |
setTimeout(tick, 50); | |
} | |
function fieldTransition() { | |
var field = d3.select(this).transition(); | |
field.select(".arc-body") | |
.attrTween("d", arcTween(arcBody)) | |
.style("fill", function(d) { return color(d.value); }); | |
field.select(".arc-center") | |
.attrTween("d", arcTween(arcCenter)); | |
field.select(".arc-text") | |
.text(function(d) { return d.text; }); | |
} | |
function arcTween(arc) { | |
return function(d) { | |
var i = d3.interpolateNumber(d.previousValue, d.value); | |
return function(t) { | |
d.value = i(t); | |
return arc(d); | |
}; | |
}; | |
} | |
/** | |
* Returns a random integer between min (inclusive) and max (inclusive). | |
* The value is no lower than min (or the next integer greater than min | |
* if min isn't an integer) and no greater than max (or the next integer | |
* lower than max if max isn't an integer). | |
* Using Math.round() will give you a non-uniform distribution! | |
*/ | |
function getRandomInt(min, max) { | |
min = Math.ceil(min); | |
max = Math.floor(max); | |
return Math.floor(Math.random() * (max - min + 1)) + min; | |
} | |
function fields() { | |
var now = moment(); | |
var num_viewers = viewerCount; // document.querySelector('#num_viewers').value; | |
var time_since_last_update = now.diff(last_update); | |
last_update = now; | |
cur_time = cur_time.add(time_since_last_update * num_viewers); | |
var delta = moment.duration(cur_time.diff(start)); | |
document.querySelector('#num_viewers_display').value = num_viewers; | |
return [ | |
{index: .1, text: delta.get('seconds') + ' seconds', value: num_viewers >= 60 ? 1.0 : (delta.get('seconds') + (delta.get('milliseconds') / 1000)) / 60}, | |
{index: .2, text: delta.get('minutes') + ' minutes', value: num_viewers > 3600 ? 1.0 : (delta.get('minutes') + (delta.get('seconds') / 60)) / 60}, | |
{index: .3, text: delta.get('hours') + ' hours', value: delta.get('hours') / 24}, | |
{index: .4, text: delta.get('days') + ' days', value: delta.get('days') / 30}, | |
{index: .5, text: delta.get('months') + ' months', value: delta.get('months') / 12}, | |
{index: .6, text: delta.get('years') + ' years', value: delta.get('years') / 10} | |
]; | |
} | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment