Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active November 8, 2019 08:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nitaku/be2316fc1b32862ede5388b646694393 to your computer and use it in GitHub Desktop.
Save nitaku/be2316fc1b32862ede5388b646694393 to your computer and use it in GitHub Desktop.
World map VII - continents

Merging Natural Earth's countries dataset to create a map of continents yields a map that does not correspond to any of the alternatives listed by Wikipedia for subdividing the world into continents.

This map is similar to the 7 continents convention, but it portrays the Russian Federation and French Guyana (plus many small islands) as part of Europe. A curious continent called "Seven seas (open ocean)", grouping a few islands, can also be found in the dataset.

svg = d3.select 'svg'
width = svg.node().getBoundingClientRect().width
height = svg.node().getBoundingClientRect().height
CONTINENTS = ['North America', 'Africa', 'South America', 'Asia', 'Europe', 'Oceania', 'Seven seas (open ocean)', 'Antarctica']
# 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()
# COLORS
color = d3.scaleOrdinal(d3.schemeCategory10)
.domain CONTINENTS
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) ->
continents_data = CONTINENTS.map (d) ->
geometry = topojson.merge(geo_data, geo_data.objects.countries.geometries.filter (c) -> c.properties.continent is d)
return {
geometry: geometry
centroid: projection d3.geoCentroid(geometry)
id: d
}
# label each polygon instead of each multipolygon (to help with islands etc.)
labels_data = []
continents_data.forEach (d) ->
if d.geometry.type is 'Polygon'
labels_data.push d
else if d.geometry.type is 'MultiPolygon'
d.geometry.coordinates.forEach (p) ->
labels_data.push {
geometry: {
coordinates: p
properties: d.properties
type: 'Polygon'
}
id: d.id
}
# compute area to aid label hiding
labels_data.forEach (d) -> d.area = d3.geoArea(d.geometry)
# continents
continents = contents.selectAll '.continent'
.data continents_data
en_continents = continents.enter().append 'path'
.attrs
class: 'continent'
d: (d) -> path d.geometry
fill: (d) -> color d.id
en_continents.append 'title'
.text (d) -> d.id
# labels
labels = contents.selectAll '.label'
.data labels_data
en_labels = labels.enter().append 'g'
.attrs
class: 'label'
transform: (d) ->
[x,y] = projection d3.geoCentroid(d.geometry)
return "translate(#{x},#{y})"
en_labels.append 'text'
.text (d) -> d.id
# lod
lod(1)
lod = (z) ->
zoomable_layer.selectAll '.label'
.classed 'hidden', (d) -> d.area < Math.pow(0.2/z,2)
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;
}
.continent {
fill-opacity: 0.3;
stroke: white;
stroke-width: 0.5;
vector-effect: non-scaling-stroke;
}
.continent:hover {
fill-opacity: 0.6;
}
.label {
font-family: sans-serif;
font-size: 10px;
pointer-events: none;
text-anchor: middle;
}
.label.hidden {
display: none;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>World map VII - continents</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 CONTINENTS, color, contents, graticule, height, lod, path, projection, svg, width, zoom, zoomable_layer;
svg = d3.select('svg');
width = svg.node().getBoundingClientRect().width;
height = svg.node().getBoundingClientRect().height;
CONTINENTS = ['North America', 'Africa', 'South America', 'Asia', 'Europe', 'Oceania', 'Seven seas (open ocean)', 'Antarctica'];
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();
color = d3.scaleOrdinal(d3.schemeCategory10).domain(CONTINENTS);
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 continents, continents_data, en_continents, en_labels, labels, labels_data;
continents_data = CONTINENTS.map(function(d) {
var geometry;
geometry = topojson.merge(geo_data, geo_data.objects.countries.geometries.filter(function(c) {
return c.properties.continent === d;
}));
return {
geometry: geometry,
centroid: projection(d3.geoCentroid(geometry)),
id: d
};
});
labels_data = [];
continents_data.forEach(function(d) {
if (d.geometry.type === 'Polygon') {
return labels_data.push(d);
} else if (d.geometry.type === 'MultiPolygon') {
return d.geometry.coordinates.forEach(function(p) {
return labels_data.push({
geometry: {
coordinates: p,
properties: d.properties,
type: 'Polygon'
},
id: d.id
});
});
}
});
labels_data.forEach(function(d) {
return d.area = d3.geoArea(d.geometry);
});
continents = contents.selectAll('.continent').data(continents_data);
en_continents = continents.enter().append('path').attrs({
"class": 'continent',
d: function(d) {
return path(d.geometry);
},
fill: function(d) {
return color(d.id);
}
});
en_continents.append('title').text(function(d) {
return d.id;
});
labels = contents.selectAll('.label').data(labels_data);
en_labels = labels.enter().append('g').attrs({
"class": 'label',
transform: function(d) {
var ref, x, y;
ref = projection(d3.geoCentroid(d.geometry)), x = ref[0], y = ref[1];
return "translate(" + x + "," + y + ")";
}
});
en_labels.append('text').text(function(d) {
return d.id;
});
return lod(1);
});
lod = function(z) {
return zoomable_layer.selectAll('.label').classed('hidden', function(d) {
return d.area < Math.pow(0.2 / z, 2);
});
};
}).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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment