Skip to content

Instantly share code, notes, and snippets.

@steveharoz
Forked from eagereyes/index.html
Last active August 29, 2015 14:07
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 steveharoz/67a134a02253d483851a to your computer and use it in GitHub Desktop.
Save steveharoz/67a134a02253d483851a to your computer and use it in GitHub Desktop.
<!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