Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active August 4, 2017 09:16
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/7544a6a31fe7d12466b57495208956fc to your computer and use it in GitHub Desktop.
Save nitaku/7544a6a31fe7d12466b57495208956fc to your computer and use it in GitHub Desktop.
World map IV

This example tries to improve the previous one by introducing a way to show more labels as the zoom level increases. The "algorithm" is a poor man's level of detail computation that uses empirically found magic numbers with an inverse square root law. It probably would not work reasonably in other cases.

Also, a minor bug is solved: Apparently, vector-effect: non-scaling-stroke does not work with <use>, so the world's outline was not scale independent. This is solved by creating two different paths without relying on <use>.

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()
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
# compute area to aid label hiding
countries_data.forEach (d) -> d.area = d3.geoArea(d)
# countries_data.sort (a,b) -> a.area - b.area
# countries
countries = contents.selectAll '.country'
.data countries_data
countries.enter().append 'path'
.attrs
class: 'country'
d: path
# 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})"
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: #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;
text-anchor: middle;
}
.label.no_iso_code {
font-style: italic;
}
.label.hidden {
display: none;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>World map IV</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, 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();
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_labels, labels;
countries_data = topojson.feature(geo_data, geo_data.objects.countries).features;
countries_data.forEach(function(d) {
return d.area = d3.geoArea(d);
});
countries = contents.selectAll('.country').data(countries_data);
countries.enter().append('path').attrs({
"class": 'country',
d: path
});
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 + ")";
}
});
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);
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