Skip to content

Instantly share code, notes, and snippets.

Last active Jan 13, 2018
What would you like to do?
Dynamic Globe Rotation
license: gpl-3.0

Rotate the globe by dragging it or by adjusting the sliders. When you rotate, you are adjusting the Orthographic projection's three Euler angles.

See also:

  • Turn the Earth My first crack at letting the user set the rotation. This example does not allow you to drag the globe – there is no direct manipulation.
  • Versor Dragging Fil put the module on GitHub, but I believe Mike Bostock and Jason Davies figured out the math and code. In Mike Bostock's block, you cannot use sliders to adjust the rotation – there is no abstract manipulation.
  • Up and Down the Ladder of Abstraction Bret Victor's wonderful essay on using visualization to understand dynamic systems.

The world countries polygons were downloaded from ArcGIS.

Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
<!DOCTYPE html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
body {
margin: 0;
.point-mouse {
fill: steelblue;
#rotation {
position: absolute;
font-family: monospace;
padding: 10px;
background: rgba(255, 255, 255, .5);
#rotation input {
width: 300px;
<div id="rotation"></div>
<script src=""></script>
<script src=""></script>
<script src=""></script>
var angles = ["λ", "φ", "γ"];
angles.forEach(function(angle, index){"#rotation").append("div")
.attr("class", "angle-label angle-label-" + index)
.html(angle + ": <span>0</span>")"#rotation").append("input")
.attr("type", "range")
.attr("class", "angle angle-" + index)
.attr("min", "-180")
.attr("max", "180")
.attr("step", "1")
.attr("value", "0");
var width = window.innerWidth, height = window.innerHeight;
var svg ="body").append("svg")
.attr("width", width)
.attr("height", height);
var projection = d3.geoOrthographic()
.scale(d3.min([width / 2, height / 2]))
.translate([width / 2, height / 2])
var path = d3.geoPath()
var graticule = d3.geoGraticule()
.step([10, 10]);
var v0, // Mouse position in Cartesian coordinates at start of drag gesture.
r0, // Projection rotation as Euler angles at start.
q0; // Projection rotation as versor at start.
.attr("class", "graticule")
.attr("d", path)
.style("fill", "none")
.style("stroke", "#ccc");
var drag = d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);;
function dragstarted(){
var mouse_pos = d3.mouse(this);
v0 = versor.cartesian(projection.invert(mouse_pos));
r0 = projection.rotate();
q0 = versor(r0);
.datum({type: "Point", coordinates: projection.invert(mouse_pos)})
.attr("class", "point point-mouse")
.attr("d", path);
function dragged(){
var mouse_pos = d3.mouse(this);
var v1 = versor.cartesian(projection.rotate(r0).invert(mouse_pos)),
q1 = versor.multiply(q0,, v1)),
r1 = versor.rotation(q1);
if (r1){
svg.selectAll("path").attr("d", path);
.datum({type: "Point", coordinates: projection.invert(mouse_pos)});
function dragended(){
d3.selectAll("input").on("input", function(){
// get all values
var nums = [];
d3.selectAll("input").each(function(d, i){
svg.selectAll("path").attr("d", path);
function update(eulerAngles){
angles.forEach(function(angle, index){".angle-label-" + index + " span").html(Math.round(eulerAngles[index]))".angle-" + index).property("value", eulerAngles[index])
d3.json("countries.json", function (error, countries){
if (error) throw error;
.data(topojson.feature(countries, countries.objects.polygons).features)
.attr("d", path)
.style("stroke", "#fff")
.style("stroke-width", "1px")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment