Updated version of Mike Bostock's Versor Dragging to include zoom interaction.
This combo behavior is also available for convenience as the d3-geo-zoom plugin module.
license: gpl-3.0 | |
height: 600 | |
border: no |
Updated version of Mike Bostock's Versor Dragging to include zoom interaction.
This combo behavior is also available for convenience as the d3-geo-zoom plugin module.
<!DOCTYPE html> | |
<canvas width="960" height="600"></cavnas> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="https://unpkg.com/topojson-client@2"></script> | |
<script src="versor.js"></script> | |
<script> | |
var canvas = d3.select("canvas"), | |
width = canvas.property("width"), | |
height = canvas.property("height"), | |
context = canvas.node().getContext("2d"); | |
var projection = d3.geoOrthographic() | |
.scale((height - 10) / 2) | |
.translate([width / 2, height / 2]) | |
.precision(0.1); | |
var path = d3.geoPath() | |
.projection(projection) | |
.context(context); | |
canvas.call(d3.zoom() | |
.on("start", zoomstarted) | |
.on('zoom', zoomed)); | |
var render = function() {}, | |
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. | |
function zoomstarted() { | |
v0 = versor.cartesian(projection.invert(d3.mouse(this))); | |
r0 = projection.rotate(); | |
q0 = versor(r0); | |
} | |
function zoomed() { | |
projection.scale(d3.event.transform.k * (height - 10) / 2); | |
var v1 = versor.cartesian(projection.rotate(r0).invert(d3.mouse(this))), | |
q1 = versor.multiply(q0, versor.delta(v0, v1)), | |
r1 = versor.rotation(q1); | |
projection.rotate(r1); | |
render(); | |
} | |
d3.json("https://unpkg.com/world-atlas@1/world/110m.json", function(error, world) { | |
if (error) throw error; | |
var sphere = {type: "Sphere"}, | |
land = topojson.feature(world, world.objects.land); | |
render = function() { | |
context.clearRect(0, 0, width, height); | |
context.beginPath(), path(sphere), context.fillStyle = "#fff", context.fill(); | |
context.beginPath(), path(land), context.fillStyle = "#000", context.fill(); | |
context.beginPath(), path(sphere), context.stroke(); | |
}; | |
render(); | |
}); | |
</script> |
// Version 0.0.0. Copyright 2017 Mike Bostock. | |
(function(global, factory) { | |
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : | |
typeof define === 'function' && define.amd ? define(factory) : | |
(global.versor = factory()); | |
}(this, (function() {'use strict'; | |
var acos = Math.acos, | |
asin = Math.asin, | |
atan2 = Math.atan2, | |
cos = Math.cos, | |
max = Math.max, | |
min = Math.min, | |
PI = Math.PI, | |
sin = Math.sin, | |
sqrt = Math.sqrt, | |
radians = PI / 180, | |
degrees = 180 / PI; | |
// Returns the unit quaternion for the given Euler rotation angles [λ, φ, γ]. | |
function versor(e) { | |
var l = e[0] / 2 * radians, sl = sin(l), cl = cos(l), // λ / 2 | |
p = e[1] / 2 * radians, sp = sin(p), cp = cos(p), // φ / 2 | |
g = e[2] / 2 * radians, sg = sin(g), cg = cos(g); // γ / 2 | |
return [ | |
cl * cp * cg + sl * sp * sg, | |
sl * cp * cg - cl * sp * sg, | |
cl * sp * cg + sl * cp * sg, | |
cl * cp * sg - sl * sp * cg | |
]; | |
} | |
// Returns Cartesian coordinates [x, y, z] given spherical coordinates [λ, φ]. | |
versor.cartesian = function(e) { | |
var l = e[0] * radians, p = e[1] * radians, cp = cos(p); | |
return [cp * cos(l), cp * sin(l), sin(p)]; | |
}; | |
// Returns the Euler rotation angles [λ, φ, γ] for the given quaternion. | |
versor.rotation = function(q) { | |
return [ | |
atan2(2 * (q[0] * q[1] + q[2] * q[3]), 1 - 2 * (q[1] * q[1] + q[2] * q[2])) * degrees, | |
asin(max(-1, min(1, 2 * (q[0] * q[2] - q[3] * q[1])))) * degrees, | |
atan2(2 * (q[0] * q[3] + q[1] * q[2]), 1 - 2 * (q[2] * q[2] + q[3] * q[3])) * degrees | |
]; | |
}; | |
// Returns the quaternion to rotate between two cartesian points on the sphere. | |
versor.delta = function(v0, v1) { | |
var w = cross(v0, v1), l = sqrt(dot(w, w)); | |
if (!l) return [1, 0, 0, 0]; | |
var t = acos(max(-1, min(1, dot(v0, v1)))) / 2, s = sin(t); // t = θ / 2 | |
return [cos(t), w[2] / l * s, -w[1] / l * s, w[0] / l * s]; | |
}; | |
// Returns the quaternion that represents q0 * q1. | |
versor.multiply = function(q0, q1) { | |
return [ | |
q0[0] * q1[0] - q0[1] * q1[1] - q0[2] * q1[2] - q0[3] * q1[3], | |
q0[0] * q1[1] + q0[1] * q1[0] + q0[2] * q1[3] - q0[3] * q1[2], | |
q0[0] * q1[2] - q0[1] * q1[3] + q0[2] * q1[0] + q0[3] * q1[1], | |
q0[0] * q1[3] + q0[1] * q1[2] - q0[2] * q1[1] + q0[3] * q1[0] | |
]; | |
}; | |
function cross(v0, v1) { | |
return [ | |
v0[1] * v1[2] - v0[2] * v1[1], | |
v0[2] * v1[0] - v0[0] * v1[2], | |
v0[0] * v1[1] - v0[1] * v1[0] | |
]; | |
} | |
function dot(v0, v1) { | |
return v0[0] * v1[0] + v0[1] * v1[1] + v0[2] * v1[2]; | |
} | |
return versor; | |
}))); |