Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active August 7, 2017 12:47
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 nitaku/8de69327a7587770f7efd70f6054d5f6 to your computer and use it in GitHub Desktop.
Save nitaku/8de69327a7587770f7efd70f6054d5f6 to your computer and use it in GitHub Desktop.
World population - circle packing

Another take on world population as bubble areas, this time with almost no geography at all. Countries are grouped and colored according to their continent.

Data from Natural Earth & data.worldbank.org.

svg = d3.select 'body'
.append 'svg'
width = d3.select('svg').node().getBoundingClientRect().width
height = d3.select('svg').node().getBoundingClientRect().height
# ZOOM
zoomable_layer = svg.append 'g'
zoom = d3.zoom()
.scaleExtent([-Infinity,Infinity])
.on 'zoom', () ->
zoomable_layer
.attrs
transform: d3.event.transform
# SEMANTIC ZOOM
# scale back all objects that have to be semantically zoomed
zoomable_layer.selectAll '.label > text'
.attrs
transform: "scale(#{1/d3.event.transform.k})"
# LOD & OVERLAPPING
lod(d3.event.transform.k)
svg.call zoom
# PACK
pack = d3.pack()
.size([width - 2, height - 2])
.padding(3)
# COLORS
color = d3.scaleOrdinal(d3.schemeCategory10)
.domain ['North America', 'Africa', 'South America', 'Asia', 'Europe', 'Oceania', 'Seven seas (open ocean)']
d3.json 'ne_50m_admin_0_countries.topo.json', (geo_data) ->
countries_data = topojson.feature(geo_data, geo_data.objects.countries).features
d3.csv 'population.csv', (data) ->
# use ISO a3 code as ID
# WARNING some records do not match
index = {}
data.forEach (d) ->
index[d['Country Code']] = d
population_data = []
countries_data.forEach (d) ->
if d.properties.iso_a3 of index
population_data.push {
id: d.properties.iso_a3
parent: d.properties.continent
country: d
value: +index[d.properties.iso_a3]['2016']
}
# adding dummy root since d3 stratify does not handle multiple roots
population_data.push {id: "root", parent: ""}
# also add continents
population_data.push {id: "North America", parent: "root"}
population_data.push {id: "Africa", parent: "root"}
population_data.push {id: "South America", parent: "root"}
population_data.push {id: "Asia", parent: "root"}
population_data.push {id: "Europe", parent: "root"}
population_data.push {id: "Oceania", parent: "root"}
population_data.push {id: "Seven seas (open ocean)", parent: "root"}
# tree construction
root = (d3.stratify()
.id((d) -> d.id)
.parentId((d) -> d.parent)
)(population_data)
root
.sum (d) -> d.value
.sort (a, b) -> b.value - a.value
pack(root)
# bubbles
bubbles = zoomable_layer.selectAll '.bubble'
.data root.leaves()
en = bubbles.enter().append 'circle'
.attrs
class: 'bubble'
cx: (d) -> d.x
cy: (d) -> d.y
r: (d) -> d.r
fill: (d) -> color d.parent.id
en.append 'title'
.text (d) -> "#{d.data.country.properties.name_long}\nPopulation: #{d3.format(',')(d.value)}"
# labels
labels = zoomable_layer.selectAll '.label'
.data root.leaves()
en_labels = labels.enter().append 'g'
.attrs
class: 'label'
transform: (d) -> "translate(#{d.x},#{d.y})"
en_labels.append 'text'
.text (d) -> d.data.country.properties.name_long
.attrs
dy: '0.35em'
# lod
lod(1)
lod = (z) ->
zoomable_layer.selectAll '.label'
.classed 'hidden', (d) -> d.r < 18/z
body, html {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
svg {
width: 100%;
height: 100%;
}
.bubble {
fill-opacity: 0.3;
stroke: black;
stroke-width: 0.5;
vector-effect: non-scaling-stroke;
}
.bubble:hover {
fill-opacity: 0.5;
}
.label {
font-family: sans-serif;
font-size: 10px;
pointer-events: none;
text-anchor: middle;
}
.label.hidden {
display: none;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>World population - circle Packing</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v0.4.min.js"></script>
<script src="//d3js.org/topojson.v2.min.js"></script>
<link rel="stylesheet" href="index.css">
</head>
<body>
<script src="index.js"></script>
</body>
</html>
// Generated by CoffeeScript 1.10.0
(function() {
var color, height, lod, pack, svg, width, zoom, zoomable_layer;
svg = d3.select('body').append('svg');
width = d3.select('svg').node().getBoundingClientRect().width;
height = d3.select('svg').node().getBoundingClientRect().height;
zoomable_layer = svg.append('g');
zoom = d3.zoom().scaleExtent([-Infinity, Infinity]).on('zoom', function() {
zoomable_layer.attrs({
transform: d3.event.transform
});
zoomable_layer.selectAll('.label > text').attrs({
transform: "scale(" + (1 / d3.event.transform.k) + ")"
});
return lod(d3.event.transform.k);
});
svg.call(zoom);
pack = d3.pack().size([width - 2, height - 2]).padding(3);
color = d3.scaleOrdinal(d3.schemeCategory10).domain(['North America', 'Africa', 'South America', 'Asia', 'Europe', 'Oceania', 'Seven seas (open ocean)']);
d3.json('ne_50m_admin_0_countries.topo.json', function(geo_data) {
var countries_data;
countries_data = topojson.feature(geo_data, geo_data.objects.countries).features;
return d3.csv('population.csv', function(data) {
var bubbles, en, en_labels, index, labels, population_data, root;
index = {};
data.forEach(function(d) {
return index[d['Country Code']] = d;
});
population_data = [];
countries_data.forEach(function(d) {
if (d.properties.iso_a3 in index) {
return population_data.push({
id: d.properties.iso_a3,
parent: d.properties.continent,
country: d,
value: +index[d.properties.iso_a3]['2016']
});
}
});
population_data.push({
id: "root",
parent: ""
});
population_data.push({
id: "North America",
parent: "root"
});
population_data.push({
id: "Africa",
parent: "root"
});
population_data.push({
id: "South America",
parent: "root"
});
population_data.push({
id: "Asia",
parent: "root"
});
population_data.push({
id: "Europe",
parent: "root"
});
population_data.push({
id: "Oceania",
parent: "root"
});
population_data.push({
id: "Seven seas (open ocean)",
parent: "root"
});
root = (d3.stratify().id(function(d) {
return d.id;
}).parentId(function(d) {
return d.parent;
}))(population_data);
root.sum(function(d) {
return d.value;
}).sort(function(a, b) {
return b.value - a.value;
});
pack(root);
bubbles = zoomable_layer.selectAll('.bubble').data(root.leaves());
en = bubbles.enter().append('circle').attrs({
"class": 'bubble',
cx: function(d) {
return d.x;
},
cy: function(d) {
return d.y;
},
r: function(d) {
return d.r;
},
fill: function(d) {
return color(d.parent.id);
}
});
en.append('title').text(function(d) {
return d.data.country.properties.name_long + "\nPopulation: " + (d3.format(',')(d.value));
});
labels = zoomable_layer.selectAll('.label').data(root.leaves());
en_labels = labels.enter().append('g').attrs({
"class": 'label',
transform: function(d) {
return "translate(" + d.x + "," + d.y + ")";
}
});
en_labels.append('text').text(function(d) {
return d.data.country.properties.name_long;
}).attrs({
dy: '0.35em'
});
return lod(1);
});
});
lod = function(z) {
return zoomable_layer.selectAll('.label').classed('hidden', function(d) {
return d.r < 18 / z;
});
};
}).call(this);
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment