This iteration of a general purpose world map (see the previous attempt) adds coloring according to the color classes defined in Natural Earth‘s dataset, making it more like a political map.
The mapcolor7
property is used in conjunction with a desaturated version of d3’s schemeCategory10
scale.
// Generated by CoffeeScript 1.10.0
(function() {
var color, contents, desaturate, graticule, height, lod, 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
});
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();
desaturate = function(color) {
var hcl;
hcl = d3.hcl(color);
hcl.c -= 10;
return hcl;
};
color = d3.scaleOrdinal(d3.schemeCategory10.map(desaturate));
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, countries_data, en_countries, en_labels, labels, labels_data;
countries_data = topojson.feature(geo_data, geo_data.objects.countries).features;
labels_data = [];
countries_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({
coordinates: p,
properties: d.properties,
type: 'Polygon'
});
});
}
});
labels_data.forEach(function(d) {
return d.area = d3.geoArea(d);
});
countries = contents.selectAll('.country').data(countries_data);
en_countries = countries.enter().append('path').attrs({
"class": 'country',
d: path,
fill: function(d) {
return color(d.properties.mapcolor7);
}
});
en_countries.append('title').text(function(d) {
return d.properties.name_long;
});
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)), x = ref[0], y = ref[1];
return "translate(" + x + "," + y + ")";
}
});
en_labels.classed('no_iso_code', function(d) {
return d.properties.iso_a2 === '-99';
});
en_labels.append('text').text(function(d) {
return d.properties.name_long;
});
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);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>World map VI</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>
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()
# COLORS
desaturate = (color) ->
hcl = d3.hcl color
hcl.c -= 10
return hcl
color = d3.scaleOrdinal(d3.schemeCategory10.map desaturate)
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
# label each polygon instead of each multipolygon (to help with islands etc.)
labels_data = []
countries_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 {
coordinates: p
properties: d.properties
type: 'Polygon'
}
# compute area to aid label hiding
labels_data.forEach (d) -> d.area = d3.geoArea(d)
# labels_data.sort (a,b) -> a.area - b.area
# countries
countries = contents.selectAll '.country'
.data countries_data
en_countries = countries.enter().append 'path'
.attrs
class: 'country'
d: path
fill: (d) -> color(d.properties.mapcolor7)
en_countries.append 'title'
.text (d) -> d.properties.name_long
# 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)
return "translate(#{x},#{y})"
en_labels
.classed 'no_iso_code', (d) -> d.properties.iso_a2 is '-99'
en_labels.append 'text'
.text (d) -> d.properties.name_long
# 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;
}
.country {
fill-opacity: 0.3;
stroke: white;
stroke-width: 0.5;
vector-effect: non-scaling-stroke;
}
.country:hover {
fill-opacity: 0.6;
}
.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;
}