Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active August 3, 2017 18:50
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/5875c370da8852fd925783eca2fb58cc to your computer and use it in GitHub Desktop.
Save nitaku/5875c370da8852fd925783eca2fb58cc to your computer and use it in GitHub Desktop.
World map II

Following the previous example, this is an attempt to label the countries of the world with the corresponding ISO 3166-1 alpha-2 code.

This turned out to be more difficult than expected, because some countries in disputed areas have no ISO code (represented in this map as red labels). Data from Natural Earth's datasets assign an arbitrary ID to these cases (the string '-99'). That makes it impossible to merge the topoJSON and the TSV provided by Mike Bostock's world atlas back into one, since some entries carry the same identifier.

Moreover, using the country shape's centroid to place the label is less than ideal in many cases (e.g., France).

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})"
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()
svg.append 'defs'
.append 'path'
.datum graticule.outline()
.attrs
id: 'sphere'
d: path
zoomable_layer.append 'use'
.attrs
class: 'sphere_fill'
'xlink:href': '#sphere'
contents = zoomable_layer.append 'g'
zoomable_layer.append 'path'
.datum graticule
.attrs
class: 'graticule'
d: path
zoomable_layer.append 'use'
.attrs
class: 'sphere_stroke'
'xlink:href': '#sphere'
d3.json 'https://unpkg.com/world-atlas@1/world/50m.json', (geo_data) ->
d3.tsv 'https://unpkg.com/world-atlas@1/world/50m.tsv', (data) ->
# index data by ISO n3 ID
index = {}
data.forEach (d) -> index[d.iso_n3] = d
countries_data = topojson.feature(geo_data, geo_data.objects.countries).features
# countries
countries = contents.selectAll '.country'
.data countries_data
en_countries = countries.enter().append 'path'
.attrs
class: 'country'
d: path
en_countries.append 'title'
.text (d) -> if d.id is '-99' then 'invalid ISO code' else index[d.id].formal_en
# labels
labels = contents.selectAll '.label'
.data countries_data
en_labels = labels.enter().append 'g'
.attrs
class: 'label'
transform: (d) ->
[x,y] = projection d3.geoCentroid(d)
return "translate(#{x},#{y})"
.classed 'error', (d) -> d.id is '-99'
en_labels.append 'text'
.text (d) -> index[d.id].iso_a2
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: #DDD;
stroke: white;
stroke-width: 0.5;
vector-effect: non-scaling-stroke;
}
.country:hover {
fill: #CCC;
}
.label {
font-family: sans-serif;
font-size: 10px;
pointer-events: none;
}
.label.error {
fill: red;
font-weight: bold;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>World map II</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>
// Generated by CoffeeScript 1.10.0
(function() {
var contents, graticule, height, path, projection, 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
});
return zoomable_layer.selectAll('.label > text').attrs({
transform: "scale(" + (1 / 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();
svg.append('defs').append('path').datum(graticule.outline()).attrs({
id: 'sphere',
d: path
});
zoomable_layer.append('use').attrs({
"class": 'sphere_fill',
'xlink:href': '#sphere'
});
contents = zoomable_layer.append('g');
zoomable_layer.append('path').datum(graticule).attrs({
"class": 'graticule',
d: path
});
zoomable_layer.append('use').attrs({
"class": 'sphere_stroke',
'xlink:href': '#sphere'
});
d3.json('https://unpkg.com/world-atlas@1/world/50m.json', function(geo_data) {
return d3.tsv('https://unpkg.com/world-atlas@1/world/50m.tsv', function(data) {
var countries, countries_data, en_countries, en_labels, index, labels;
index = {};
data.forEach(function(d) {
return index[d.iso_n3] = d;
});
countries_data = topojson.feature(geo_data, geo_data.objects.countries).features;
countries = contents.selectAll('.country').data(countries_data);
en_countries = countries.enter().append('path').attrs({
"class": 'country',
d: path
});
en_countries.append('title').text(function(d) {
if (d.id === '-99') {
return 'invalid ISO code';
} else {
return index[d.id].formal_en;
}
});
labels = contents.selectAll('.label').data(countries_data);
en_labels = labels.enter().append('g').attrs({
"class": 'label',
transform: function(d) {
var ref, x, y;
ref = projection(d3.geoCentroid(d)), x = ref[0], y = ref[1];
return "translate(" + x + "," + y + ")";
}
}).classed('error', function(d) {
return d.id === '-99';
});
return en_labels.append('text').text(function(d) {
return index[d.id].iso_a2;
});
});
});
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment