Population of the countries of the world, encoded as circular areas as in the previous example. This time, countries are not depicted. Bubbles are placed onto a country's centroid, and the graticule is left to help locating faraway bubbles (e.g., the pacific islands) and give a sense of geographical displacement.
Last active
August 7, 2017 10:16
-
-
Save nitaku/95a31e8beb6fc937b42f43d6c6434614 to your computer and use it in GitHub Desktop.
World population - bubbles without map
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
svg = d3.select 'svg' | |
width = svg.node().getBoundingClientRect().width | |
height = 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) | |
# PROJECTION | |
projection = d3.geoWinkel3() | |
.rotate [0, 0] | |
.center [0, 0] | |
.scale (width - 3) / (2 * Math.PI) | |
.translate [width/2, height/2] | |
path = d3.geoPath projection | |
# GRATICULE and OUTLINE | |
graticule = d3.geoGraticule() | |
# POPULATION BUBBLES SCALE | |
radius = d3.scaleSqrt() | |
.range [0, 50] | |
# COLORS | |
color = d3.scaleOrdinal(d3.schemeCategory10) | |
.domain ['North America', 'Africa', 'South America', 'Asia', 'Europe', 'Oceania'] | |
zoomable_layer.append 'path' | |
.datum graticule.outline() | |
.attrs | |
class: 'sphere_fill' | |
d: path | |
contents = zoomable_layer.append 'g' | |
zoomable_layer.append 'path' | |
.datum graticule | |
.attrs | |
class: 'graticule' | |
d: path | |
zoomable_layer.append 'path' | |
.datum graticule.outline() | |
.attrs | |
class: 'sphere_stroke' | |
d: path | |
d3.json 'ne_50m_admin_0_countries.topo.json', (geo_data) -> | |
countries_data = topojson.feature(geo_data, geo_data.objects.countries).features | |
# subdivide multipolygons | |
countries_data.forEach (d) -> | |
if d.geometry.type is 'Polygon' | |
# compute area to aid label hiding | |
d.area = d3.geoArea(d) | |
d.main = d | |
else if d.geometry.type is 'MultiPolygon' | |
subpolys = [] | |
d.geometry.coordinates.forEach (p) -> | |
sp = { | |
coordinates: p | |
properties: d.properties | |
type: 'Polygon' | |
} | |
# compute area to aid label hiding | |
sp.area = d3.geoArea(sp) | |
subpolys.push sp | |
# store the biggest polygon as main | |
d.main = subpolys.reduce ((a, b) -> if a.area > b.area then a else b), subpolys[0] | |
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 { | |
country: d | |
value: +index[d.properties.iso_a3]['2016'] | |
} | |
radius | |
.domain [0, d3.max population_data, (d) -> d.value] | |
# sort by descending population to avoid covering a small bubble with a big one | |
population_data.sort (a,b) -> d3.descending(a.value, b.value) | |
# bubbles | |
bubbles = contents.selectAll '.bubble' | |
.data population_data | |
en_bubbles = bubbles.enter().append 'circle' | |
.attrs | |
class: 'bubble' | |
fill: (d) -> color d.country.properties.continent | |
r: (d) -> radius d.value | |
transform: (d) -> | |
[x,y] = projection d3.geoCentroid(d.country.main) | |
return "translate(#{x},#{y})" | |
en_bubbles.append 'title' | |
.text (d) -> "#{d.country.properties.name_long}\nPopulation: #{d3.format(',')(d.value)}" | |
# labels | |
labels = contents.selectAll '.label' | |
.data population_data | |
en_labels = labels.enter().append 'g' | |
.attrs | |
class: 'label' | |
transform: (d) -> | |
[x,y] = projection d3.geoCentroid(d.country.main) | |
return "translate(#{x},#{y})" | |
en_labels.append 'text' | |
.text (d) -> d.country.properties.name_long | |
.attrs | |
dy: '0.35em' | |
# lod | |
lod(1) | |
lod = (z) -> | |
zoomable_layer.selectAll '.label' | |
.classed 'hidden', (d) -> radius(d.value) < 18/z |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body, html { | |
padding: 0; | |
margin: 0; | |
height: 100%; | |
} | |
svg { | |
width: 100%; | |
height: 100%; | |
background: white; | |
} | |
.sphere_stroke { | |
fill: none; | |
stroke: black; | |
stroke-width: 2px; | |
vector-effect: non-scaling-stroke; | |
} | |
.sphere_fill { | |
fill: white; | |
} | |
.graticule { | |
fill: none; | |
stroke: #777; | |
stroke-width: 0.5px; | |
stroke-opacity: 0.5; | |
vector-effect: non-scaling-stroke; | |
pointer-events: none; | |
} | |
.country { | |
fill: #999; | |
fill-opacity: 0.3; | |
stroke: white; | |
stroke-width: 0.5; | |
vector-effect: non-scaling-stroke; | |
} | |
.label { | |
font-family: sans-serif; | |
font-size: 10px; | |
pointer-events: none; | |
text-anchor: middle; | |
} | |
.label.no_iso_code { | |
font-style: italic; | |
} | |
.label.hidden { | |
display: none; | |
} | |
.bubble { | |
fill-opacity: 0.2; | |
stroke: black; | |
stroke-width: 0.5; | |
vector-effect: non-scaling-stroke; | |
} | |
.bubble:hover { | |
fill-opacity: 0.4; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>World population - bubbles without map</title> | |
<link type="text/css" href="index.css" rel="stylesheet"/> | |
<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="https://d3js.org/d3-geo-projection.v2.min.js"></script> | |
<script src="//d3js.org/topojson.v2.min.js"></script> | |
</head> | |
<body> | |
<svg></svg> | |
<script src="index.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Generated by CoffeeScript 1.10.0 | |
(function() { | |
var color, contents, graticule, height, lod, path, projection, radius, svg, width, zoom, zoomable_layer; | |
svg = d3.select('svg'); | |
width = svg.node().getBoundingClientRect().width; | |
height = 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); | |
projection = d3.geoWinkel3().rotate([0, 0]).center([0, 0]).scale((width - 3) / (2 * Math.PI)).translate([width / 2, height / 2]); | |
path = d3.geoPath(projection); | |
graticule = d3.geoGraticule(); | |
radius = d3.scaleSqrt().range([0, 50]); | |
color = d3.scaleOrdinal(d3.schemeCategory10).domain(['North America', 'Africa', 'South America', 'Asia', 'Europe', 'Oceania']); | |
zoomable_layer.append('path').datum(graticule.outline()).attrs({ | |
"class": 'sphere_fill', | |
d: path | |
}); | |
contents = zoomable_layer.append('g'); | |
zoomable_layer.append('path').datum(graticule).attrs({ | |
"class": 'graticule', | |
d: path | |
}); | |
zoomable_layer.append('path').datum(graticule.outline()).attrs({ | |
"class": 'sphere_stroke', | |
d: path | |
}); | |
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; | |
countries_data.forEach(function(d) { | |
var subpolys; | |
if (d.geometry.type === 'Polygon') { | |
d.area = d3.geoArea(d); | |
return d.main = d; | |
} else if (d.geometry.type === 'MultiPolygon') { | |
subpolys = []; | |
d.geometry.coordinates.forEach(function(p) { | |
var sp; | |
sp = { | |
coordinates: p, | |
properties: d.properties, | |
type: 'Polygon' | |
}; | |
sp.area = d3.geoArea(sp); | |
return subpolys.push(sp); | |
}); | |
return d.main = subpolys.reduce((function(a, b) { | |
if (a.area > b.area) { | |
return a; | |
} else { | |
return b; | |
} | |
}), subpolys[0]); | |
} | |
}); | |
return d3.csv('population.csv', function(data) { | |
var bubbles, en_bubbles, en_labels, index, labels, population_data; | |
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({ | |
country: d, | |
value: +index[d.properties.iso_a3]['2016'] | |
}); | |
} | |
}); | |
radius.domain([ | |
0, d3.max(population_data, function(d) { | |
return d.value; | |
}) | |
]); | |
population_data.sort(function(a, b) { | |
return d3.descending(a.value, b.value); | |
}); | |
bubbles = contents.selectAll('.bubble').data(population_data); | |
en_bubbles = bubbles.enter().append('circle').attrs({ | |
"class": 'bubble', | |
fill: function(d) { | |
return color(d.country.properties.continent); | |
}, | |
r: function(d) { | |
return radius(d.value); | |
}, | |
transform: function(d) { | |
var ref, x, y; | |
ref = projection(d3.geoCentroid(d.country.main)), x = ref[0], y = ref[1]; | |
return "translate(" + x + "," + y + ")"; | |
} | |
}); | |
en_bubbles.append('title').text(function(d) { | |
return d.country.properties.name_long + "\nPopulation: " + (d3.format(',')(d.value)); | |
}); | |
labels = contents.selectAll('.label').data(population_data); | |
en_labels = labels.enter().append('g').attrs({ | |
"class": 'label', | |
transform: function(d) { | |
var ref, x, y; | |
ref = projection(d3.geoCentroid(d.country.main)), x = ref[0], y = ref[1]; | |
return "translate(" + x + "," + y + ")"; | |
} | |
}); | |
en_labels.append('text').text(function(d) { | |
return d.country.properties.name_long; | |
}).attrs({ | |
dy: '0.35em' | |
}); | |
return lod(1); | |
}); | |
}); | |
lod = function(z) { | |
return zoomable_layer.selectAll('.label').classed('hidden', function(d) { | |
return radius(d.value) < 18 / z; | |
}); | |
}; | |
}).call(this); |
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