Skip to content

Instantly share code, notes, and snippets.

@jwilber
Last active October 8, 2018 05:55
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 jwilber/7efe98bd384a95a466b8975a930408e4 to your computer and use it in GitHub Desktop.
Save jwilber/7efe98bd384a95a466b8975a930408e4 to your computer and use it in GitHub Desktop.
one hundred people
license: mit
<!DOCTYPE html>
<html lang="en">
<head>
<link href="https://fonts.googleapis.com/css?family=PT+Sans|PT+Serif|Roboto" rel="stylesheet">
<link href="../codecss/prism.css" rel="stylesheet" />
<meta charset="UTF-8">
<title>One Hundred People</title>
<style type="text/css">
body {
background-color: white;
font-family: 'consolas', serif;
}
#container {
max-width: 1000px;
margin-left: auto;
margin-right: auto;
margin-top: 5px;
padding: 1px 10px 10px 10px;
background-color: white;
box-shadow: 1px 1px 1px 1px #fff;
}
h1 {
font-weight: 300;
color: darkslategray;
font-family: "consolas", sans-serif;
font-size: 56px;
margin-top: 10px;
margin-bottom: 0px;
}
h2 {
font-weight: 300;
color: darkslategray;
font-family: "PT Sans", sans-serif;
font-size: 32px;
margin-bottom: 20px;
margin-top: -10px;
}
p {
font-size: 18px;
margin-top: 4px;
margin-bottom: 8px;
color: #333;
}
a {
color: darkslategray;
}
a:hover {
color: darkslategray;
}
.footer {
font-size: 14px;
margin-top: 0px;
}
#svganchor {
margin: auto;
}
.criterionList {
font-size: 12px;
color: #222;
text-transform: uppercase;
cursor: pointer;
}
.chartTitle {
font-family: "PT Sans", sans-serif;
font-weight: 700;
font-size: 22px;
color: #444;
}
.chartSubTitle {
font-family: "PT Sans", sans-serif;
font-size: 18px;
color: #444;
}
.nodesData {
font-family: 'PT Serif', serif;
font-size: 14px;
}
.nodesDataSpan {
font-family: 'PT Serif', serif;
font-size: 18px;
font-weight: 700;
}
.preciseNumberTitle {
font-family: "PT Sans", sans-serif;
font-size: 12px;
color: #444;
}
.nodesDataPrecise {
font-family: 'PT Serif', serif;
font-size: 12px;
color: #444;
}
svg {
background-color: white;
}
</style>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<div id="container">
<h1>One Hundred People</h1>
<div id="svganchor"></div>
<br>
</div>
<script type="text/javascript">
var totalData = [{
criterion: "Sex",
description: "Distribution of males and females",
groups: [{
name: "Male",
value: 50.43
}, {
name: "Female",
value: 49.57
}]
}, {
criterion: "Body weight",
description: "People above a weight considered healthy",
groups: [{
name: "Overweight",
value: 21.88
}, {
name: "Not overweight",
value: 78.12
}]
},{
criterion: "Undernourishment",
description: "People with sufficient quantity or quality of nourishment",
groups: [{
name: "Undernourished",
value: 10.00
}, {
name: "Not Undernourished",
value: 90.00
}]
},{
criterion: "Age",
description: "Distribution by age",
groups: [{
name: "Below 14 years of age",
value: 26.30
}, {
name: "Between 15 and 64 years",
value: 65.90
}, {
name: "65 years and over",
value: 7.80
}]
},{
criterion: "Internet access",
description: "People with an internet connection",
groups: [{
name: "With connection",
value: 47.31
}, {
name: "Without connection",
value: 52.69
}]
},{
criterion: "Homosexuality",
description: "Distribution by sexual orientation",
groups: [{
name: "Heterosexual",
value: 97.4
}, {
name: "Homosexual or Bisexual",
value: 2.6
}]
},{
criterion: "Mobile phone",
description: "People using mobile phones",
groups: [{
name: "Using a mobile",
value: 63.90
}, {
name: "Not using a mobile",
value: 36.10
}]
},{
criterion: "Electricity",
description: "People with access to electricity",
groups: [{
name: "Access",
value: 84.58
}, {
name: "No access",
value: 15.42
}]
},{
criterion: "Water",
description: "People with access to clean water",
groups: [{
name: "No access",
value: 8.39
}, {
name: "Access",
value: 91.61
}]
},{
criterion: "Unemployment",
description: "Unemployment rate",
groups: [{
name: "Unemployed",
value: 5.93
}, {
name: "Employed",
value: 94.07
}]
},{
criterion: "Rule of the road",
description: "Side of the road in bidirectional traffic",
groups: [{
name: "Drive on the right side",
value: 66.00
}, {
name: "Drive on the left side",
value: 34.00
}]
},{
criterion: "Income",
description: "Dollars per day",
groups: [{
name: "Less than $2",
value: 71.00
}, {
name: "Between $10 and $20",
value: 13.00
}, {
name: "More than $20",
value: 16.00
}]
},{
criterion: "Religion",
description: "Distribution by religion",
groups: [{
name: "Christian",
value: 31.50
}, {
name: "Muslim",
value: 22.32
}, {
name: "Atheist",
value: 15.35
}, {
name: "Hindu",
value: 13.95
}, {
name: "Other",
value: 16.88
}]
},{
criterion: "Continent",
description: "People from different continents",
groups: [{
name: "Africa",
value: 14.5
}, {
name: "Europe",
value: 11.4
}, {
name: "Americas",
value: 13.2
},{
name: "Asia",
value: 60.3
}, {
name: "Oceania",
value: 0.6
}]
},{
criterion: "Literacy",
description: "People able to read and write",
groups: [{
name: "Literate",
value: 83.7
}, {
name: "Illiterate",
value: 16.3
}]
},{
criterion: "Handedness",
description: "Preference to use one hand rather than the other",
groups: [{
name: "Left-handedness",
value: 8.00
}, {
name: "Right-handedness",
value: 92.00
}]
},{
criterion: "Blood type",
description: "ABO blood group system",
groups: [{
name: "A",
value: 33.90
}, {
name: "B",
value: 16.20
}, {
name: "AB",
value: 5.10
}, {
name: "O",
value: 44.80
}]
},{
criterion: "Sanitation",
description: "People with access to basic sanitation, like flush toilets and treated sewage",
groups: [{
name: "Access",
value: 67.52
}, {
name: "No access",
value: 32.48
}]
}];
var headPath = "M251.249,127.907c17.7,0,32.781-6.232,45.254-18.7c12.467-12.467,18.699-27.554,18.699-45.253 c0-17.705-6.232-32.783-18.699-45.255C284.029,6.233,268.948,0,251.249,0c-17.705,0-32.79,6.23-45.254,18.699 c-12.465,12.469-18.699,27.55-18.699,45.255c0,17.703,6.23,32.789,18.699,45.253C218.462,121.671,233.549,127.907,251.249,127.907 z";
var bodyPath = "M381.438,153.029c-10.663-10.657-23.599-15.987-38.827-15.987H159.889c-15.23,0-28.171,5.327-38.831,15.987 c-10.657,10.66-15.987,23.604-15.987,38.831v118.776c0,7.611,2.663,14.079,7.993,19.407s11.803,7.994,19.414,7.994 c7.614,0,14.087-2.666,19.417-7.994c5.327-5.328,7.994-11.796,7.994-19.407V210.134h18.271v260.379 c0,8.754,3.144,16.275,9.423,22.559c6.28,6.276,13.796,9.418,22.554,9.418s16.278-3.142,22.557-9.418 c6.28-6.283,9.42-13.802,9.42-22.559V338.038h18.27V470.52c0,8.75,3.141,16.275,9.421,22.552 c6.279,6.283,13.802,9.425,22.556,9.425c8.76,0,16.279-3.142,22.559-9.425c6.283-6.283,9.418-13.798,9.418-22.552V210.134h18.274 v100.495c0,7.618,2.665,14.086,7.994,19.411c5.328,5.331,11.799,7.994,19.41,7.994c7.61,0,14.093-2.663,19.417-7.994 c5.328-5.325,7.994-11.793,7.994-19.411V191.86C397.43,176.63,392.095,163.689,381.438,153.029z";
var criterionListData = totalData.map(d => d.criterion);
var divList = d3.select("#controler");
var criterionList = divList.selectAll(".criterionList")
.data(criterionListData)
.enter()
.append("p")
.attr("class", "criterionList");
criterionList.text(d => d);
var width = 850, height = 500;
var svg = d3.select("#svganchor")
.append("svg")
.attr("width", width)
.attr("height", height);
var padding = [10, 10, 10, 10];
var xScale = d3.scalePoint()
.range([padding[3], width - padding[1]])
.padding(0.5);
var barScale = d3.scaleLinear()
.range([padding[3], width - padding[1]])
.domain([0,100]);
var colorScale = d3.scaleOrdinal()
.range(['#1b9e77','#d95f02','#7570b3','#e7298a','#66a61e']);
var dataNodes = d3.range(100).map(() => ({id: "", xPosition:""}));
var simulation = d3.forceSimulation()
.force("collide", d3.forceCollide(12))
.force("x", d3.forceX(d => d.xPosition).strength(0.2))
.force("y", d3.forceY(height / 2))
.velocityDecay(0.7);
var chartTitle = svg.append("text")
.attr("class", "chartTitle")
.attr("x", width / 2)
.attr("y", 20)
.attr("text-anchor", "middle");
var chartSubTitle = svg.append("text")
.attr("class", "chartSubTitle")
.attr("x", width / 2)
.attr("y", 44)
.attr("text-anchor", "middle");
var preciseNumberTitle = svg.append("text")
.attr("class", "preciseNumberTitle")
.attr("y", 424)
.attr("x", padding[3])
.text("Precise numbers:");
draw("Sex");
function draw(criterion){
chartTitle.text(criterion);
var filteredData = totalData.filter(d => d.criterion === criterion);
chartSubTitle.text(filteredData[0].description);
xScale.domain(filteredData[0].groups.map(d => d.name));
colorScale.domain(filteredData[0].groups.map(d => d.name));
var counterData = 0;
filteredData[0].groups.forEach(d=>{
var rounded = Math.round(d.value);
for(var i = counterData; i < rounded + counterData; i++){
dataNodes[i].id = d.name;
dataNodes[i].xPosition = xScale(d.name);
};
counterData += rounded;
});
var nodes = svg.selectAll(".g")
.data(dataNodes)
.enter()
.append("g");
nodes.attr("fill", d => colorScale(d.id));
nodes.append("path")
.attr("d", headPath)
.attr("transform", "translate(-12,0) scale(0.045,0.045)");
nodes.append("path")
.attr("d", bodyPath)
.attr("transform", "translate(-12,0) scale(0.045,0.045)");
var nodesData = svg.selectAll(".nodesData")
.data(filteredData[0].groups, d => d.name)
.enter()
.append("text")
.attr("class", "nodesData")
.attr("text-anchor", "middle")
.attr("y", 100)
.attr("x", d => xScale(d.name))
.text(d => d.name + ": ")
.append("tspan")
.attr("class", "nodesDataSpan")
.text(d => Math.round(d.value));
var nodesDataPrecise = svg.selectAll(".nodesDataPrecise")
.data(filteredData[0].groups, d => d.name)
.enter()
.append("text")
.attr("class", "nodesDataPrecise")
.attr("text-anchor", "middle")
.attr("y", 440)
.attr("x", d => xScale(d.name))
.text(d => d.name + ": " + d.value + "%");
dataNodes.forEach(d =>{
d.x = width / 2;
d.y = height / 2;
});
simulation.nodes(dataNodes)
.on("tick", tick);
var bars = svg.selectAll(".bars")
.data(filteredData[0].groups);
var barsEnter = bars.enter()
.append("rect")
.attr("class", "bars")
.attr("y", 470)
.attr("x", (d,i) => i ? barScale(filteredData[0].groups[i-1].value) : barScale(0))
.attr("height", 20)
.attr("width", d => barScale(d.value) - padding[3])
.attr("fill", d => colorScale(d.name));
var polylines = svg.selectAll(".polylines")
.data(filteredData[0].groups)
.enter()
.append("polyline")
.attr("class", "polylines")
.attr("points", d => "" + xScale(d.name) + ",470 "
+ xScale(d.name) + ",446")
.attr("stroke-width", 1)
.attr("stroke", "darkslategray");
function tick(){
nodes.attr('transform', (d) => {
if (d.y < 120) {
d.y = 120
};
if (d.y > 380) {
d.y = 380
};
return 'translate(' + (d.x) + ',' + (d.y) + ')';
});
};
function redraw(criterion){
chartTitle.text(criterion);
var filteredData = totalData.filter(d => d.criterion === criterion);
chartSubTitle.text(filteredData[0].description);
xScale.domain(filteredData[0].groups.map(d => d.name));
colorScale.domain(filteredData[0].groups.map(d => d.name));
var counterData = 0;
filteredData[0].groups.forEach(d=>{
var rounded = Math.round(d.value);
for(var i = counterData; i < rounded + counterData; i++){
dataNodes[i].id = d.name;
dataNodes[i].xPosition = xScale(d.name);
};
counterData += rounded;
});
nodes.transition()
.duration(500)
.attr("fill", d => colorScale(d.id));
var newNodesData = svg.selectAll(".nodesData")
.data(filteredData[0].groups, d => d.name + d.value);
var newNodesDataExit = newNodesData.exit()
.remove();
var newNodesDataEnter = newNodesData.enter()
.append("text")
.attr("class", "nodesData")
.attr("text-anchor", "middle")
.attr("y", 100)
.attr("x", d => xScale(d.name))
.text(d => d.name + ": ")
.append("tspan")
.attr("class", "nodesDataSpan")
.text(d => Math.round(d.value));
var newNodesDataPrecise = svg.selectAll(".nodesDataPrecise")
.data(filteredData[0].groups, d => d.name + d.value);
newNodesDataPrecise.exit()
.transition()
.delay(750)
.duration(10)
.remove();
newNodesDataPrecise.enter()
.append("text")
.attr("class", "nodesDataPrecise")
.attr("text-anchor", "middle")
.attr("y", 440)
.attr("x", d => xScale(d.name))
.transition()
.delay(750)
.duration(10)
.text(d => d.name + ": " + d.value + "%");
var bars = svg.selectAll(".bars")
.data(filteredData[0].groups);
var barExit = bars.exit()
.transition()
.delay(750)
.duration(500)
.attr("x", width - padding[1])
.attr("width", 0)
.remove();
var barsEnter = bars.enter()
.append("rect")
.attr("class", "bars")
.attr("x", width - padding[1])
.merge(bars)
.attr("y", 470)
.attr("height", 20);
barsEnter.transition()
.delay(750)
.duration(500)
.attr("x", (d,i) =>{
if(i === 0){
return barScale(0);
} else {
var counter = 0;
for(var j = 0; j < i; j++){
counter += filteredData[0].groups[j].value;
};
return barScale(counter);
}
})
.attr("width", d => barScale(d.value) - padding[3])
.attr("fill", d => colorScale(d.name));
var polylines = svg.selectAll(".polylines")
.data(filteredData[0].groups);
var polylinesExit = polylines.exit()
.transition()
.delay(750)
.duration(500)
.attr("points", "" + width + ",470 "
+ width + ",458 " + width + ",458 "
+ width + ",446")
.remove();
var polylinesEnter = polylines.enter()
.append("polyline")
.attr("class", "polylines")
.attr("points", "" + width + ",470 "
+ width + ",458 " + width + ",458 "
+ width + ",446")
.merge(polylines)
.transition()
.delay(750)
.duration(500)
.attr("points", (d,i) =>{
if(i === 0){
return "" + (barScale(0) + (barScale(filteredData[0].groups[0].value))/2) + ",470 " + (barScale(0) + (barScale(filteredData[0].groups[0].value))/2) + ",458 "
+ xScale(d.name) + ",458 " + xScale(d.name) + ",446";
} else {
var counter = 0;
for(var j = 0; j < i; j++){
counter += filteredData[0].groups[j].value;
};
var thisWidth = barScale(counter);
return "" + (thisWidth + (barScale(d.value) - padding[3])/2) + ",470 " + (thisWidth + (barScale(d.value) - padding[3])/2) + ",458 " + xScale(d.name) + ",458 " + xScale(d.name) + ",446";
}
})
.attr("stroke-width", 1)
.attr("fill", "none")
.attr("stroke", "darkslategray");
setTimeout(function(){
simulation.nodes(dataNodes);
simulation.alpha(0.8).restart();
}, 750)
//end of redraw
};
d3.selectAll(".criterionList").on("click", function(d){
redraw(d);
});
//end of draw
};
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment