Skip to content

Instantly share code, notes, and snippets.

@PatrickStotz
Last active February 11, 2023 00:40
  • Star 7 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save PatrickStotz/1f19b3e4cb848100ffd7 to your computer and use it in GitHub Desktop.
Spinning globe with glowing city markers in D3

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.

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.
<!DOCTYPE html>
<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="http://fonts.googleapis.com/css?family=Lora:700italic" rel="stylesheet" type="text/css">
<link href="http://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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment