A Spinning globe with glowing city markers in D3.
Note: This visualization constantly recalculates and reprojects the position of over 4000 markers and thus needs a lot of computation power. Older computer and mobile devices might struggle with the rendering process. Furthermore the visualization is only optimized for Chrome browsers and might look different than expected when opened in other browsers.
Inspired by Curran Kelleher's idea to plot a world map in D3 by using the GeoNames database as seen in his great Introdction to D3 video.
xxxxxxxxxx
<html>
<head>
<meta charset="utf-8">
<title>Spinning globe with glowing city markers in D3</title>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Lora:700italic" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Muli" rel="stylesheet" type="text/css">
<style>
body {background: black;}
h1 {
font: italic bold 2.2em Lora;
color: #BBB;
margin: 0px;
}
p {
margin-top: 1.2em;
margin-bottom: 0.3em;
font: 1em Muli;
color: #BBB;
}
controls {
width:220px;
position: absolute;
padding:10px;
}
</style>
</head>
<body>
<!-- initiate HTML5 sliders and color picker -->
<controls>
<h1> SPINNING GLOBE </h1>
<p> Rotation speed & direction</p>
<input id="rotation" type="range" min="-0.1" max="0.1" step="0.005" value="0.015" style="width: 160px"/>
<p> Glow opacity</p>
<input id="glow" type="range" min="0.05" max="0.5" step="0.01" value="0.3" style="width: 160px"/>
<p> Marker size</p>
<input id="marker_size" type="range" min="1" max="10" step="0.5" value="5" style="width: 160px"/>
<p> Color </p>
<input id="color" type="color" value="#ffba00">
</controls>
<svg width="820" height="620"></svg>
<script>
// Map configuration
var width = 820;
var height = 620;
var rScale = d3.scale.sqrt();
var peoplePerPixel = 50000;
var max_population = [];
// Configuration for the spinning effect
var time = Date.now();
var rotate = [0, 0];
var velocity = [.015, -0];
// set projection type and paremetes
var projection = d3.geo.orthographic()
.scale(300)
.translate([(width / 2) + 100, height / 2])
.clipAngle(90)
.precision(0.3);
// create path variable, empty svg element and group container
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("svg");
var g = svg.append("g");
// drawing dark grey spehere as landmass
g.append("path")
.datum({type: "Sphere"})
.attr("class", "sphere")
.attr("d", path)
.attr("fill", "#0D0D0D");
// loading city locations from geoJSON
d3.json("geonames_cities_100k.geojson", function(error, data) {
// Handle errors getting and parsing the data
if (error) { return error; }
// setting the circle size (not radius!) according to the number of inhabitants per city
population_array = [];
for (i = 0; i < data.features.length; i++) {
population_array.push(data.features[i].properties.population);
}
max_population = population_array.sort(d3.descending)[0]
var rMin = 0;
var rMax = Math.sqrt(max_population / (peoplePerPixel * Math.PI));
rScale.domain([0, max_population]);
rScale.range([rMin, rMax]);
path.pointRadius(function(d) {
return d.properties ? rScale(d.properties.population) : 1;
});
// Drawing transparent circle markers for cities
g.selectAll("path.cities").data(data.features)
.enter().append("path")
.attr("class", "cities")
.attr("d", path)
.attr("fill", "#ffba00")
.attr("fill-opacity", 0.3);
// start spinning!
spinning_globe();
});
function spinning_globe(){
d3.timer(function() {
// get current time
var dt = Date.now() - time;
// get the new position from modified projection function
projection.rotate([rotate[0] + velocity[0] * dt, rotate[1] + velocity[1] * dt]);
// update cities position = redraw
svg.selectAll("path.cities").attr("d", path);
});
}
// Events for sliders and button
document.getElementById("rotation").addEventListener("change", function() {
var new_speed = this.value;
velocity[0] = new_speed
});
document.getElementById("glow").addEventListener("change", function() {
var new_glow = this.value;
g.selectAll("path.cities")
.attr("fill-opacity", new_glow);
});
document.getElementById("marker_size").addEventListener("change", function() {
var new_marker_size = 1 / this.value ;
peoplePerPixel = new_marker_size * 100000;
var rMin = 0;
var rMax = Math.sqrt(max_population / (peoplePerPixel * Math.PI));
rScale.range([rMin, rMax]);
});
document.getElementById("color").addEventListener("change", function() {
var new_color = this.value;
g.selectAll("path.cities")
.attr("fill", new_color);
});
// hackish approach to get bl.ocks.org to display individual height
d3.select(self.frameElement).style("height", height + "px");
</script>
</body>
</html>
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js