Skip to content

Instantly share code, notes, and snippets.

@elidupuis
Last active January 2, 2016 10: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 elidupuis/8294043 to your computer and use it in GitHub Desktop.
Save elidupuis/8294043 to your computer and use it in GitHub Desktop.
The Circles of Life

Dev setup

python -m SimpleHTTPServer 8080
sass --watch style.scss:style.css
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>How your perception of time changes as you age</title>
<link href='http://fonts.googleapis.com/css?family=Source+Sans+Pro:300,600,700,900|Sacramento' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="l-wrap">
<div class="l-side">
<p class="text">
<span class="text-when hilite">When</span>
<span class="text-ur hilite">you are</span>
<input class="fill-hilite" type="number" id="currentAge" value="30" min="1" max="150" required>
<span class="text-years">years old</span>
<span class="text-fancy"><span class="stars">every</span></span>
<input type="number" id="referenceAge" value="1" min="1" max="150">
<select id="referenceRatio"> &#8595;
<option value="365">days</option>
<option value="52">weeks</option>
<option value="12">months</option>
<option value="1" selected>years</option>
</select>
<span class="text-represent">represents</span>
<span class="text-result hilite" id="relativeTime"></span>
<span class="text-fancy"><span class="slashes">of your</span></span>
<span class="text-life">life</span>
</p>
</div>
<div class="l-main">
<div id="vis"></div>
</div>
</div>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="vis.js"></script>
</body>
</html>
$hilite: #8cdc0d;
$font: 'Source Sans Pro', helvetica, sans-serif;
$font-alt: 'Sacramento', cursive;
body {
font-family: $font;
padding: 0;
margin: 0;
min-height: 500px;
}
form {
position: relative;
z-index: 2;
}
.l-wrap {
width: 860px;
margin: auto;
position: relative;
}
.l-side {
width: 170px;
margin: 50px 0 0;
}
.l-main {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 230px;
}
.hilite {
color: $hilite;
}
.fill-hilite {
background: $hilite;
}
/* ==========================================================================
Text
========================================================================== */
.text {
font-size: 38px;
text-transform: uppercase;
position: relative;
font-weight: 300;
}
.text-when {
font-size: 50px;
font-weight: 600;
}
.text-ur {
font-size: 21px;
font-weight: 600;
position: absolute;
width: 50px;
line-height: 0.9;
top: 0.65em;
right: -0.15em;
text-align: right;
}
.text-years {
font-size: 38px;
font-weight: 300;
}
.text-represent {
font-size: 32px;
}
.text-result {
font-size: 62px;
font-weight: 700;
text-align: center;
display: block;
margin-top: -0.25em;
> span {
font-size: 30px;
vertical-align: super;
}
}
.text-fancy {
font-family: $font-alt;
font-weight: 400;
text-transform: lowercase;
font-size: 34px;
text-align: center;
display: block;
width: 100%;
margin: -0.35em 0 0;
}
.text-life {
font-size: 96px;
font-weight: 900;
letter-spacing: -4px;
display: block;
margin-top: -0.25em;
}
.stars {
position: relative;
&:before,
&:after {
content: url(http://f.cl.ly/items/3n340c381j2d2D0x3W1r/stars.png);
position: absolute;
top: 0;
}
&:before {
left: 110%;
}
&:after {
right: 110%;
}
}
.slashes {
position: relative;
&:before,
&:after {
content: url(http://f.cl.ly/items/0B3f1Y0V2w0n3f0F120m/slashes.png);
position: absolute;
top: 0;
}
&:before {
left: 110%;
}
&:after {
right: 95%;
}
}
/*
inputs, selects
========================================================================== */
input {
background: #000;
color: #fff;
display: block;
width: 100%;
text-align: center;
border: 0;
font-size: 70px;
}
select {
background: #000;
color: #fff;
width: 100%;
border-radius: 0;
padding: 0.25em 1em;
font-size: 25px;
font-family: $font-alt;
/* http://uplifted.net/programming/change-default-select-dropdown-style-just-css/ */
border: 0 !important;
-webkit-appearance: none;
-mox-appearance: none;
}
/* ==========================================================================
Vis
========================================================================== */
#vis {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
}
.axis path, .axis line {
fill: none;
stroke: $hilite;
shape-rendering: crispEdges;;
}
.tick text {
font-size: 12px;
fill: $hilite;
}
.outer {
fill: black;
}
.inner {
fill: $hilite;
}
line {
stroke: $hilite;
stroke-width: 1px;
}
var doit = (function(){
var d = document;
var currentAge = d.getElementById('currentAge');
var referenceAge = d.getElementById('referenceAge');
var referenceRatio = d.getElementById('referenceRatio');
var relativeTime = d.getElementById('relativeTime');
var output = d.getElementById('output')
// d3 varibales
var margin = {top: 0, right: 30, bottom: 20, left: 30}
var svg, x, xAxis, radius, width, height, outerCircle, innerCircle, refLine
function calculate () {
var age = +currentAge.value;
var reference = +referenceAge.value;
var conversion = 1/(+referenceRatio.value);
var ratio, percentage
// make sure reference age is equal or less than actual age
if (reference > age) {
reference = age;
referenceAge.value = age;
}
// calulate the proportion of current age
ratio = (reference * conversion) / age;
percentage = ratio * 100;
// convert to percentage and display
relativeTime.innerHTML = displayPercentage(percentage)
// update visualization
buildVis(age, (reference * conversion))
}
// format percentage decimal places
function displayPercentage (p) {
if (p < 1) {
return p.toFixed(3) + '<span>%</span>'
}
return p.toFixed(1) + '<span>%</span>'
}
// attach event listeners
[currentAge, referenceAge, referenceRatio].forEach(function(el) {
el.addEventListener('change', handleChange)
el.addEventListener('keyup', handleChange)
})
function handleChange (e) {
e.preventDefault()
calculate()
}
// first run vis setup
initVis(+currentAge.value)
// kick it off for the first time
calculate()
////////////////////////////////////////////////////////////////////
// d3...
////////////////////////////////////////////////////////////////////
function getSize () {
// var el = d.getElementById('vis')
// var w = el.offsetWidth || 600
// var h = el.offsetHeight || 600
// return d3.min([w, h])
return 660
}
// set up the D3 elements and inital state.
function initVis (max) {
width = getSize() - margin.left - margin.right;
height = getSize() - margin.top - margin.bottom;
radius = d3.scale.sqrt()
.domain([0, max])
.range([0, width/2]);
svg = d3.select("#vis").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
xAxis = d3.svg.axis()
.scale(radius)
.orient("bottom");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + width/2 + "," + height + ")")
.call(xAxis);
outerCircle = svg.append('circle')
.attr('class', "circle outer")
.attr('cx', width/2)
.attr('cy', height/2)
.attr('r', width/2)
innerCircle = svg.append('circle')
.attr('class', "circle inner")
.attr('cx', width/2)
.attr('cy', height/2)
svg.append('line')
.attr('class', 'line')
.attr('x1', width/2)
.attr('y1', height/2)
.attr('x2', width/2)
.attr('y2', height)
refLine = svg.append('line')
.attr('class', 'line')
.attr('y1', height/2)
.attr('y2', height)
}
// update the visualization
function buildVis (max, relative) {
width = getSize() - margin.left - margin.right;
height = getSize() - margin.top - margin.bottom;
radius.domain([0, max])
.range([0, width/2]);
svg
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
svg.select(".x.axis")
.transition()
.call(xAxis);
innerCircle
.transition()
.attr('r', radius(relative))
refLine
.transition()
.attr('x1', radius(relative) + (width/2)-0.5)
.attr('x2', radius(relative) + (width/2)-0.5)
}
})
document.addEventListener("DOMContentLoaded", function(event) {
doit()
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment