Skip to content

Instantly share code, notes, and snippets.

@pirhoo
Last active April 4, 2016 09:30
Show Gist options
  • Save pirhoo/1a734463cae06cd5ede829e403ee7d41 to your computer and use it in GitHub Desktop.
Save pirhoo/1a734463cae06cd5ede829e403ee7d41 to your computer and use it in GitHub Desktop.
OSF visualization
license: gpl-3.0
border: no
height: 500

Silence is golden

var W = 960, H = 500;
var NODE_RADIUS = 10, CLUSTER_PADDING = 15;
var GENDER_FILL = {
'male': '#036F73',
'female': '#EF504F',
'other': '#FEF2D8'
};
var KEYS_MAP = {
"How would you describe yourself?": "gender",
"What year were you born in?": "born_year",
"Where are you based?": "based_place",
"Which one is your favorite?": "favorite",
"We will crowdsource the number of power cuts in Douala, Cameroon, using SMS and we will sell the data to corporations thinking of settling in.": "power_cuts_interest",
"We will create a database of all payments made by Big Pharma to doctors in the United States, write articles on the topic and sell the resulting tables.": "big_pharma_interest",
"We will create an agency that makes infographics for corporations and media outlets.": "agency_interest",
"We'll measure dialects in a country and do a web-app that tells people where they're from according to their speaking habits.": "speaking_interest"
};
var STEP_COLUMN = {
1: 'gender',
2: 'born_year',
3: 'based_place',
4: 'favorite',
5: 'power_cuts_interest',
6: 'big_pharma_interest',
7: 'agency_interest',
8: 'speaking_interest'
};
var CLUSTERS = {
// Gender
"female": { x: W * 1/3 - W * 1/6, y: H/2, radius: W/6},
"male": { x: W * 2/3 - W * 1/6, y: H/2, radius: W/6},
"other": { x: W * 3/3 - W * 1/6, y: H/2, radius: W/6},
// Baby animal
"cat": { x: W * 1/3 - W * 1/6, y: H/2, radius: W/6},
"pup": { x: W * 2/3 - W * 1/6, y: H/2, radius: W/6},
"prairie dog": { x: W * 3/3 - W * 1/6, y: H/2, radius: W/6},
// Interest
"1": { x: W * 1/5 - W * 1/10, y: H/2, radius: W/6},
"2": { x: W * 2/5 - W * 1/10, y: H/2, radius: W/6},
"3": { x: W * 3/5 - W * 1/10, y: H/2, radius: W/6},
"4": { x: W * 4/5 - W * 1/10, y: H/2, radius: W/6},
"5": { x: W * 5/5 - W * 1/10, y: H/2, radius: W/6}
};
var HIDDEN_CLUSTER = {
x: W/2,
y: H * 2,
radius: 0
}
# How would you describe yourself? What year were you born in? Where are you based? Which one is your favorite? We will crowdsource the number of power cuts in Douala, Cameroon, using SMS and we will sell the data to corporations thinking of settling in. We will create a database of all payments made by Big Pharma to doctors in the United States, write articles on the topic and sell the resulting tables. We will create an agency that makes infographics for corporations and media outlets. We'll measure dialects in a country and do a web-app that tells people where they're from according to their speaking habits. latitude longitude
77dda7b22612474e10ac9b3f06ead13d Female 1985 Beijing Prairie dog 1 1 1 3 39.904211 116.407395
baec107d44d5768d63c43a278ea9db58 Male 1988 Lagos Cat 4 5 2 4 6.524379 3.379206
a62630638d155d436d1200e515a7187b Male 1990 Delhi Cat 3 4 2 3 28.613939 77.209021
5e2b572f5b5350091771ed4352bea835 Other 1960 Karachi Pup 1 1 1 24.861462 67.009939
66fa48c89077092ad267315ae52b311d Female 1955 Istanbul Prairie dog 4 3 2 41.008238 28.978359
93594e1935a5d953df969fe04530d2b5 Female 1974 Tokyo Cat 3 4 2 35.689488 139.691706
e84348a4722eb707a361804e8d1964b8 Female 1945 Mumbai Cat 1 2 1 19.075984 72.877656
025ca0ea392f1c8b585cde76f9eeeec5 Male 1985 Moscow Prairie dog 4 1 3 55.755826 37.6173
08da7e45884adfdb603b37dc1b236cca Female 1969 São Paulo Pup 3 4 4 -23.55052 -46.633309
77dda7b22612474e10ac9b3f06ead13d Female 1988 Shenzhen Prairie dog 1 1 1 2 22.543096 114.057865
baec107d44d5768d63c43a278ea9db58 Male 1986 Seoul Cat 4 5 2 1 37.566535 126.977969
a62630638d155d436d1200e515a7187b Male 1990 Jakarta Cat 3 4 1 4 -6.208763 106.845599
5e2b572f5b5350091771ed4352bea835 Female 1960 Guangzhou Pup 1 2 1 23.12911 113.264385
66fa48c89077092ad267315ae52b311d Female 1945 Kinshasa Prairie dog 4 2 2 -4.441931 15.266293
93594e1935a5d953df969fe04530d2b5 Female 1979 Cairo Cat 3 1 3 30.04442 31.235712
e84348a4722eb707a361804e8d1964b8 Female 1945 Mexico City Cat 1 3 4 19.432608 -99.133208
025ca0ea392f1c8b585cde76f9eeeec5 Male 1975 Lima Prairie dog 4 4 2 -12.046374 -77.042793
08da7e45884adfdb603b37dc1b236cca Female 1976 London Pup 3 2 3 51.507351 -0.127758
025ca0ea392f1c8b585cde76f9eeeec5 Male 1956 New York City Cat 1 1 4 40.712784 -74.005941
// Move d to be adjacent to the cluster node.
function cluster(alpha, nodes) {
return function(d) {
var cluster = clusters[d.cluster];
if (cluster === d) return;
var x = d.x - cluster.x,
y = d.y - cluster.y,
l = Math.sqrt(x * x + y * y),
r = NODE_RADIUS + cluster.radius;
if (l != r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
}
};
}
// Resolves collisions between d and all other circles.
function collide(alpha, nodes) {
var padding = 2;
var quadtree = d3.geom.quadtree(nodes);
return function(d) {
var r = 10,
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== d)) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = NODE_RADIUS + quad.point.radius + (d.cluster === quad.point.cluster ? padding : CLUSTER_PADDING);
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
<!DOCTYPE html>
<meta charset="utf-8">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" media="screen" charset="utf-8">
<link rel="stylesheet" href="./style.css" media="screen" charset="utf-8">
<body>
<div class="workspace">
<div class="workspace__legend workspace__legend--1 row text-center">
<div class="col col-xs-4">Male</div>
<div class="col col-xs-4">Female</div>
<div class="col col-xs-4">Other</div>
</div>
<div class="workspace__legend workspace__legend--2 row text-center">
<div class="col col-xs-3">1930</div><!-- Years start at 1920 -->
<div class="col col-xs-3">1950</div>
<div class="col col-xs-3">1970</div>
<div class="col col-xs-3">1990</div><!-- Years end at 2000 -->
<div class="workspace__legend__axis"></div>
</div>
<div class="workspace__legend workspace__legend--4 row text-center">
<div class="col col-xs-4">
<img src="//i.imgur.com/anjmG4T.jpg" alt="cat" width="100" class="img-thumbnail img-responsive img-circle" />
</div>
<div class="col col-xs-4">
<img src="//i.imgur.com/IinAQ0k.jpg" alt="dog" width="100" class="img-thumbnail img-responsive img-circle" />
</div>
<div class="col col-xs-4">
<img src="//i.imgur.com/psHq30V.jpg" alt="prairie dog" width="100" class="img-thumbnail img-responsive img-circle" />
</div>
</div>
<div class="workspace__legend workspace__legend--interest workspace__legend--5 workspace__legend--6 workspace__legend--7 workspace__legend--8 text-center">
<div class="col">
<img src="//imgur.com/xqBQbQ1.jpg" alt="star" width="20" />
</div>
<div class="col">
<img src="//imgur.com/xqBQbQ1.jpg" alt="star" width="20" />
<img src="//imgur.com/xqBQbQ1.jpg" alt="star" width="20" />
</div>
<div class="col">
<img src="//imgur.com/xqBQbQ1.jpg" alt="star" width="20" />
<img src="//imgur.com/xqBQbQ1.jpg" alt="star" width="20" />
<img src="//imgur.com/xqBQbQ1.jpg" alt="star" width="20" />
</div>
<div class="col">
<img src="//imgur.com/xqBQbQ1.jpg" alt="star" width="20" />
<img src="//imgur.com/xqBQbQ1.jpg" alt="star" width="20" />
<img src="//imgur.com/xqBQbQ1.jpg" alt="star" width="20" />
<img src="//imgur.com/xqBQbQ1.jpg" alt="star" width="20" />
</div>
<div class="col">
<img src="//imgur.com/xqBQbQ1.jpg" alt="star" width="20" />
<img src="//imgur.com/xqBQbQ1.jpg" alt="star" width="20" />
<img src="//imgur.com/xqBQbQ1.jpg" alt="star" width="20" />
<img src="//imgur.com/xqBQbQ1.jpg" alt="star" width="20" />
<img src="//imgur.com/xqBQbQ1.jpg" alt="star" width="20" />
</div>
</div>
<div class="workspace__next workspace__next--0">
<a class="btn btn-default">See the result of the poll</a>
</div>
<div class="workspace__next workspace__next--1">
<h3>What is your gender?</h3>
<a class="btn btn-default">Continue</a>
</div>
<div class="workspace__next workspace__next--2">
<h3>What year were you born in?</h3>
<a class="btn btn-default">Continue</a>
</div>
<div class="workspace__next workspace__next--3">
<h3>Where are you based?</h3>
<a class="btn btn-default">Continue</a>
</div>
<div class="workspace__next workspace__next--4">
<h3>Which one is your favorite?</h3>
<a class="btn btn-default">Continue</a>
</div>
<div class="workspace__next workspace__next--5">
<h3>We will crowdsource the number of power cuts in Douala, Cameroon, using SMS and we will sell the data to corporations thinking of settling in.</h3>
<a class="btn btn-default">Continue</a>
</div>
<div class="workspace__next workspace__next--6">
<h3>We will create a database of all payments made by Big Pharma to doctors in the United States, write articles on the topic and sell the resulting tables.</h3>
<a class="btn btn-default">Continue</a>
</div>
<div class="workspace__next workspace__next--7">
<h3>We will create an agency that makes infographics for corporations and media outlets.</h3>
<a class="btn btn-default">Continue</a>
</div>
<div class="workspace__next workspace__next--8">
<h3>We'll measure dialects in a country and do a web-app that tells people where they're from according to their speaking habits.</h3>
<a class="btn btn-default">Replay !</a>
</div>
</div>
</body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/d3.geo.projection.v0.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script src="./constants.js"></script>
<script src="./force.js"></script>
<script>
// Creates SVG
var svg = d3.select(".workspace").append("svg").attr("width", W).attr("height", H);
var force = d3.layout.force();
var people = null;
var yearClusters = [];
// Create a cluster for every year
d3.range(80).forEach(function(y) {
var year = 1920 + y;
yearClusters[year] = {
radius: 10,
x: (20 + y) * W/100,
y: H/2
};
})
// Map conf
var map = {
projection: d3.geo.naturalEarth().scale(200).translate([W/2, H/1.8]).precision(.1),
path: d3.geo.path(),
g: svg.append("g").attr("class", "world")
};
function randomFunc(start, end){
return function() {
return start + Math.random() * (end - start);
}
}
function init(data){
// We have to edit the data
data.forEach(function(d) {
d.x = randomFunc(10, W - 10)();
d.y = randomFunc(10, H - 10)();
d.radius = 10;
// We map column names
Object.keys(d).forEach(function(key) {
if(KEYS_MAP.hasOwnProperty(key)) {
// Copy mapped key and cast numeric values
d[ KEYS_MAP[key] ] = isNaN(d[key]) ? d[key].toLowerCase() : 1*d[key];
delete d[key];
}
});
});
// Create peoples shapes
people = svg.selectAll('.people')
.data(data).enter()
.append('circle')
.attr('class', 'people')
.attr('cx', function(d) { return W/2 })
.attr('cy', function(d) { return H/2 })
.style('fill', function(d) { return GENDER_FILL[d.gender]; })
.style('stroke', function(d) { return d3.rgb(GENDER_FILL[d.gender]).darker(1); })
.attr('r', 0);
// Prepare force layout
force.nodes(data)
.friction(.2)
.size([W, H])
.on("tick", function(e) {
// Push different nodes in different directions for clustering.
data.forEach(function(d, i) {
d.y += (d.cluster.y - d.y) * e.alpha;
d.x += (d.cluster.x - d.x) * e.alpha;
});
people.each(collide(.1, data))
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
// Setup step 0
stepFunc(0, data)();
// Bind click event on the start button
for(var i = 0; i <= 8; i++) {
var next = (i % 8) + 1;
d3.select(".workspace__next--" + i + " .btn").on("click", stepFunc(next, data));
}
}
function createMap(error, world) {
if(error) throw error;
map.path.projection(map.projection);
map.g.append("path")
.datum(topojson.merge(world, world.objects.countries.geometries))
.attr("class", "world__land")
.attr("d", map.path);
}
function stepFunc(n, data) {
return function() {
// Change body class to the current step
d3.select('body').attr('class', 'step--' + n);
// Activate the right button
d3.selectAll('.workspace__next').classed('workspace__next--active', false);
d3.select('.workspace__next--' + n).classed('workspace__next--active', true);
// Activate the right legend
d3.selectAll('.workspace__legend').classed('workspace__legend--active', false);
d3.select('.workspace__legend--' + n).classed('workspace__legend--active', true);
// Trigger a different function according to the step
switch(n) {
case 0:
// Animate appearance
people.transition()
.delay(function(d, i) { return i * 150 })
.attr('cx', function(d) { return d.x })
.attr('cy', function(d) { return d.y })
.attr('r', function(d, i) { return d.radius });
break;
case 2:
// Ensure every element is visible
people.transition().attr('r', function(d, i) { return d.radius = 5 });
// Change the cluster attribute to use the gender
data.forEach(function(d) { d.cluster = yearClusters[d.born_year]; });
// Start layout
force.start();
break;
case 3:
// Ensure every element is visible
people.transition().attr('r', function(d, i) { return d.radius = 5 });
// Change the cluster attribute to use the gender
data.forEach(function(d) {
d.cluster = {
radius: 0,
x: map.projection([d.longitude, d.latitude])[0],
y: map.projection([d.longitude, d.latitude])[1]
}
});
// Start layout
force.start();
break;
default:
// Ensure every element is visible
people.transition().attr('r', function(d, i) { return d.radius = 10 });
// Change the cluster attribute to use the gender
data.forEach(function(d) {
// Each step use a different column
d.cluster = CLUSTERS[ d[ STEP_COLUMN[n] ] ] || HIDDEN_CLUSTER;
});
// Start layout
force.start();
break;
}
}
}
d3.csv("data.csv", init);
d3.json("world-50m.json", createMap);
</script>
.workspace {
width: 960px;
height:500px;
position: relative;
background:#fafafa;
overflow: hidden;
margin:auto;
box-shadow: 0 0 1px 2px #DCD2C7;
position: fixed;
top:50%;
left:50%;
margin-left: -480px;
margin-top: -250px;
}
@media(max-height:500px) {
.workspace {
position: static;
margin:0;
}
}
.workspace__next {
position:absolute;
bottom:20px;
left:20px;
right:20px;
text-align: right;
display: inline-block;
transition: 500ms;
opacity:0;
pointer-events: none;
min-height:34px;
z-index:20;
}
.workspace__next.workspace__next--active {
opacity:1;
pointer-events: all;
}
.workspace__next h3 {
font-weight:100;
font-size:1.2em;
position: absolute;
bottom:50%;
left:0;
right:0;
transform: translate(0, 50%);
padding:0 150px;
margin:0;
text-align: center;
pointer-events: none;
}
.workspace__next .btn {
border-width:3px;
border-color:#CCC2B8;
position: relative;
overflow: visible;
color:#BE1525;
background:#DCD2C7;
font-weight: bold;
}
.workspace__legend {
position: absolute;
top:0;
bottom:0;
left:0;
right:0;
opacity:0;
color:#CCC2B8;
pointer-events: none;
z-index:1;
transition: opacity 500ms;
}
.workspace__legend--active {
opacity:1;
}
.workspace__legend .col {
height:100%;
padding-top:20px;
}
.workspace__legend .col:nth-child(odd) {
background:#f0f0f0;
}
.workspace__legend--2 .workspace__legend__axis {
position: absolute;
top:50%;
left:0;
right:0;
height:1px;
background:#CCC2B8;
opacity:.2;
}
.workspace__legend--4 img {
border:0;
background:#CCC2B8;
}
.workspace__legend--interest {
white-space: nowrap;
}
.workspace__legend--interest .col {
width:20%;
display: inline-block;
margin:0;
}
.workspace__legend--interest .col img {
opacity:.7;
}
.people {
stroke-width:1.5;
}
svg {
position: relative;
z-index:10;
}
.world {
opacity:0;
transition: 500ms;
}
.world .world__land {
fill: #ddd;
}
.step--3 .world {
opacity:1;
}
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment