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.
// 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);
<!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>
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;
}