-
-
Save steveharoz/67a134a02253d483851a to your computer and use it in GitHub Desktop.
This file contains 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> | |
<html> | |
<head> | |
<title>Large Multiples</title> | |
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> | |
<style> | |
rect { | |
fill: #ddd; | |
stroke: none; | |
} | |
rect.active, circle.unit, circle.worker { | |
fill: steelblue; | |
} | |
text { | |
font-family: Helvetica, Arial, sans-serif; | |
fill: lightgray; | |
} | |
text.barlabel { | |
fill: white; | |
pointer-events: none; | |
} | |
text.label, #sentence, .activestep { | |
fill: darkgray; | |
} | |
.step:hover { | |
fill: #ccc; | |
} | |
h1 { | |
font-family: Helvetica, Arial, sans-serif; | |
margin-bottom: 0px; | |
margin-left: 5px; | |
} | |
button { | |
margin: 1em 0 0 0.5em; | |
} | |
input { | |
margin-left: 2em; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>It pays to be a CEO in the U.S.</h1> | |
<div id="chart"> | |
</div> | |
<div id="animations"> | |
<button onclick="makeSquares(currentCountry.multiple);">Repeat animation</button> | |
<input type="radio" id="InstantOption" name="rate" value="instant" onchange="intervalType=this.value; makeSquares(currentCountry.multiple);"><label for="InstantOption" title="All dots appear instantly">Instant</label> | |
<input type="radio" id="ConstantOption" name="rate" value="constantRate" onchange="intervalType=this.value; makeSquares(currentCountry.multiple);" checked="checked"><label for="ConstantOption" title="Every country's dots appear at the same rate">Constant rate</label> | |
<input type="radio" id="VariableOption" name="rate" value="constantTime" onchange="intervalType=this.value; makeSquares(currentCountry.multiple);"><label for="VariableOption" title="Every country's dots appear over the same duration">Constant time</label> | |
<input type="radio" id="AcceleratingOption" name="rate" value="accelerating" onchange="intervalType=this.value; makeSquares(currentCountry.multiple);"><label for="AcceleratingOption" title="Dots appearances speed up over time">Accelerating</label> | |
</div> | |
<script> | |
var data = [ | |
{country: "Poland", multiple: 28, article: ""}, | |
{country: "Austria", multiple: 36, article: ""}, | |
{country: "Denmark", multiple: 48, article: ""}, | |
{country: "Portugal", multiple: 53, article: ""}, | |
{country: "Norway", multiple: 58, article: ""}, | |
{country: "Japan", multiple: 67, article: ""}, | |
{country: "Israel", multiple: 76, article: ""}, | |
{country: "United Kingdom", multiple: 84, article: "the "}, | |
{country: "Sweden", multiple: 89, article: ""}, | |
{country: "Australia", multiple: 93, article: ""}, | |
{country: "France", multiple: 104, article: ""}, | |
{country: "Czech Republic", multiple: 110, article: "the "}, | |
{country: "Spain", multiple: 127, article: ""}, | |
{country: "Germany", multiple: 147, article: ""}, | |
{country: "Switzerland", multiple: 148, article: ""}, | |
{country: "United States", multiple: 354, article: "the "} | |
]; | |
var intervalID = -1; | |
var TOTAL_TIME = 3000; | |
var TIMEOUT = 20; | |
var IntervalTypes = { | |
instant : "instant", | |
constantRate : "constantRate", | |
constantTime : "constantTime", | |
accelerating : "accelerating" | |
}; | |
var intervalType = IntervalTypes.constantRate; | |
var currentCountry = null; | |
function makeSquares(numUnits) { | |
svg.selectAll('.unit').remove(); | |
var timeout = TIMEOUT; | |
if (intervalType == IntervalTypes.constantTime) | |
timeout = TOTAL_TIME / numUnits; | |
if (intervalType == IntervalTypes.accelerating) | |
timeout *= 2; | |
var a = Math.floor(Math.sqrt(numUnits)) // numUnits/.75 for 4:3 ratio of units | |
var b = Math.floor(numUnits/a); | |
var rest = numUnits-a*b; | |
if (rest > a) { | |
a += 1; | |
rest = numUnits-a*b; | |
} | |
if (b > a) { | |
var temp = a; | |
a = b; | |
b = temp; | |
} | |
var circles = []; | |
for (var y = 0; y < b; y += 1) { | |
for (var x = 0; x < a; x += 1) { | |
var c = svg.append('circle') | |
.attr('cx', unitsOffset+x*(unitsize+unitpadding)) | |
.attr('cy', unitsHeight-y*(unitsize+unitpadding)) | |
.attr('r', unitsize/2) | |
.style('visibility', 'hidden') | |
.attr('class', 'unit'); | |
circles.push(c); | |
} | |
} | |
for (var x = 0; x < rest; x += 1) { | |
var c = svg.append('circle') | |
.attr('cx', unitsOffset+x*(unitsize+unitpadding)) | |
.attr('cy', unitsHeight-b*(unitsize+unitpadding)) | |
.attr('r', unitsize/2) | |
.style('visibility', 'hidden') | |
.attr('class', 'unit'); | |
circles.push(c); | |
} | |
svg.select('#ceolabel').transition().duration(100).attr('y', unitsHeight-b*(unitsize+unitpadding)-16); | |
if (intervalID != -1) { | |
window.clearInterval(intervalID); | |
} | |
var circleIndex = 0; | |
function drawNext() { | |
circles[circleIndex].style('visibility', 'visible'); | |
circleIndex += 1; | |
if (circleIndex == circles.length) { | |
window.clearTimeout(intervalID); | |
intervalID = -1; | |
} | |
if (intervalType == IntervalTypes.accelerating) | |
timeout *= 0.99; | |
var nextInterval = timeout; | |
intervalID = window.setTimeout(drawNext, nextInterval); | |
} | |
if (intervalType == IntervalTypes.instant) | |
circles.map(function(c){c.style('visibility', 'visible')}); | |
else | |
drawNext(); | |
} | |
var svg = d3.select('#chart').append('svg').attr('width', 694).attr('height', 470); | |
var barheight = 24; | |
var barpad = 3; | |
var unitsize = 17; | |
var unitpadding = 3; | |
var topOffset = 30; | |
var unitsOffset = 320; | |
var unitsHeight = topOffset+16*barheight+15*barpad-unitsize/2; | |
var selected = data[0]; | |
function select(d) { | |
currentCountry = d; | |
svg.selectAll('.'+selected.country.replace(' ', '-')).classed('active', false); | |
svg.select('.barlabel.'+selected.country.replace(' ', '-')).text(selected.country); | |
svg.selectAll('.'+d.country.replace(' ', '-')).classed('active', true); | |
makeSquares(d.multiple); | |
svg.select('.barlabel.'+d.country.replace(' ', '-')).text(d.country+': '+d.multiple+'x'); | |
svg.select('#sentence').text('In '+d.article+d.country+', the average CEO makes '+d.multiple+' times the salary of the average worker.') | |
selected = d; | |
svg.select('.activestep').classed('activestep', false); | |
activeStep = -1; | |
for (var i = 0; i < steps.length; i++) { | |
if (data[steps[i]] == d) { | |
svg.select('.step-'+i).classed('activestep', true); | |
activeStep = i; | |
} | |
} | |
} | |
svg.selectAll('.bar').data(data).enter() | |
.append('rect') | |
.attr('width', 170) | |
.attr('height', barheight) | |
.attr('x', 5) | |
.attr('y', function(d, i) { return topOffset+i*(barheight+barpad); }) | |
.attr('class', function(d) { return 'bar '+d.country.replace(' ', '-'); }) | |
.on('mouseenter', function(d) { | |
select(d); | |
}); | |
svg.selectAll('.barlabel').data(data).enter() | |
.append('text') | |
.attr('x', barpad*2) | |
.attr('y', function(d, i) { return topOffset+i*(barheight+barpad)+barheight-barpad-4; }) | |
.attr('class', function(d) { return 'barlabel '+d.country.replace(' ', '-'); }) | |
.text(function(d) { return d.country; }); | |
svg.append('circle') | |
.attr('class', 'worker') | |
.attr('cx', unitsOffset-4*(unitsize+unitpadding)) | |
.attr('cy', unitsHeight) | |
.attr('r', unitsize/2); | |
svg.append('text') | |
.attr('class', 'label') | |
.attr('x', unitsOffset-4*(unitsize+unitpadding)-unitsize/2) | |
.attr('y', unitsHeight-16) | |
.text('Worker'); | |
svg.append('text') | |
.attr('class', 'label') | |
.attr('x', unitsOffset-unitsize/2) | |
.attr('y', unitsHeight-16) | |
.attr('id', 'ceolabel') | |
.text('CEO'); | |
svg.append('text') | |
.attr('id', 'sentence') | |
.attr('x', 5) | |
.attr('y', 18); | |
var steps = [0, 3, 7, 13, 15]; | |
var activeStep = 0; | |
var stepsLeft = 493; | |
svg.selectAll('.step').data(steps).enter() | |
.append('rect') | |
.attr('class', function(d, i) { return 'step step-'+i+(i==0?' activestep':''); }) | |
.attr('x', function(d, i) { return stepsLeft+i*(barheight+barpad); }) | |
.attr('y', topOffset) | |
.attr('width', barheight) | |
.attr('height', barheight) | |
.on('click', function(d, i) { | |
svg.select('.activestep').classed('activestep', false); | |
svg.select('.step-'+i).classed('activestep', true); | |
activeStep = i; | |
select(data[d]); | |
}); | |
svg.selectAll('.steplabel').data(steps).enter() | |
.append('text') | |
.attr('class', 'barlabel') | |
.attr('x', function(d, i) { return stepsLeft+7+i*(barheight+barpad); }) | |
.attr('y', topOffset+17) | |
.text(function(d, i) { return ''+(i+1); }); | |
svg.append('rect') | |
.attr('class', 'step') | |
.attr('x', stepsLeft+steps.length*(barheight+barpad)) | |
.attr('y', topOffset) | |
.attr('width', barheight*2.5) | |
.attr('height', barheight) | |
.on('click', function() { | |
if (activeStep < steps.length-1) { | |
svg.select('.activestep').classed('activestep', false); | |
activeStep += 1; | |
svg.select('.step-'+activeStep).classed('activestep', true); | |
select(data[steps[activeStep]]); | |
} | |
}); | |
svg.append('text') | |
.attr('class', 'barlabel') | |
.attr('x', stepsLeft+7+steps.length*(barheight+barpad)) | |
.attr('y', topOffset+17) | |
.text('Next >'); | |
select(data[0]); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment