Last active
August 29, 2015 14:04
-
-
Save mpmckenna8/f43a842a8953e87f5b3c to your computer and use it in GitHub Desktop.
d3.geo.zoom.js plugin first use
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2013, Jason Davies, http://www.jasondavies.com | |
// See LICENSE.txt for details. | |
(function() { | |
var radians = Math.PI / 180, | |
degrees = 180 / Math.PI; | |
// TODO make incremental rotate optional | |
d3.geo.zoom = function() { | |
var projection, | |
zoomPoint, | |
event = d3.dispatch("zoomstart", "zoom", "zoomend"), | |
zoom = d3.behavior.zoom() | |
.on("zoomstart", function() { | |
var mouse0 = d3.mouse(this), | |
rotate = quaternionFromEuler(projection.rotate()), | |
point = position(projection, mouse0); | |
if (point) zoomPoint = point; | |
zoomOn.call(zoom, "zoom", function() { | |
projection.scale(d3.event.scale); | |
var mouse1 = d3.mouse(this), | |
between = rotateBetween(zoomPoint, position(projection, mouse1)); | |
projection.rotate(eulerFromQuaternion(rotate = between | |
? multiply(rotate, between) | |
: multiply(bank(projection, mouse0, mouse1), rotate))); | |
mouse0 = mouse1; | |
event.zoom.apply(this, arguments); | |
}); | |
event.zoomstart.apply(this, arguments); | |
}) | |
.on("zoomend", function() { | |
zoomOn.call(zoom, "zoom", null); | |
event.zoomend.apply(this, arguments); | |
}), | |
zoomOn = zoom.on; | |
zoom.projection = function(_) { | |
return arguments.length ? zoom.scale((projection = _).scale()) : projection; | |
}; | |
return d3.rebind(zoom, event, "on"); | |
}; | |
function bank(projection, p0, p1) { | |
var t = projection.translate(), | |
angle = Math.atan2(p0[1] - t[1], p0[0] - t[0]) - Math.atan2(p1[1] - t[1], p1[0] - t[0]); | |
return [Math.cos(angle / 2), 0, 0, Math.sin(angle / 2)]; | |
} | |
function position(projection, point) { | |
var t = projection.translate(), | |
spherical = projection.invert(point); | |
return spherical && isFinite(spherical[0]) && isFinite(spherical[1]) && cartesian(spherical); | |
} | |
function quaternionFromEuler(euler) { | |
var λ = .5 * euler[0] * radians, | |
φ = .5 * euler[1] * radians, | |
γ = .5 * euler[2] * radians, | |
sinλ = Math.sin(λ), cosλ = Math.cos(λ), | |
sinφ = Math.sin(φ), cosφ = Math.cos(φ), | |
sinγ = Math.sin(γ), cosγ = Math.cos(γ); | |
return [ | |
cosλ * cosφ * cosγ + sinλ * sinφ * sinγ, | |
sinλ * cosφ * cosγ - cosλ * sinφ * sinγ, | |
cosλ * sinφ * cosγ + sinλ * cosφ * sinγ, | |
cosλ * cosφ * sinγ - sinλ * sinφ * cosγ | |
]; | |
} | |
function multiply(a, b) { | |
var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], | |
b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3]; | |
return [ | |
a0 * b0 - a1 * b1 - a2 * b2 - a3 * b3, | |
a0 * b1 + a1 * b0 + a2 * b3 - a3 * b2, | |
a0 * b2 - a1 * b3 + a2 * b0 + a3 * b1, | |
a0 * b3 + a1 * b2 - a2 * b1 + a3 * b0 | |
]; | |
} | |
function rotateBetween(a, b) { | |
if (!a || !b) return; | |
var axis = cross(a, b), | |
norm = Math.sqrt(dot(axis, axis)), | |
halfγ = .5 * Math.acos(Math.max(-1, Math.min(1, dot(a, b)))), | |
k = Math.sin(halfγ) / norm; | |
return norm && [Math.cos(halfγ), axis[2] * k, -axis[1] * k, axis[0] * k]; | |
} | |
function eulerFromQuaternion(q) { | |
return [ | |
Math.atan2(2 * (q[0] * q[1] + q[2] * q[3]), 1 - 2 * (q[1] * q[1] + q[2] * q[2])) * degrees, | |
Math.asin(Math.max(-1, Math.min(1, 2 * (q[0] * q[2] - q[3] * q[1])))) * degrees, | |
Math.atan2(2 * (q[0] * q[3] + q[1] * q[2]), 1 - 2 * (q[2] * q[2] + q[3] * q[3])) * degrees | |
]; | |
} | |
function cartesian(spherical) { | |
var λ = spherical[0] * radians, | |
φ = spherical[1] * radians, | |
cosφ = Math.cos(φ); | |
return [ | |
cosφ * Math.cos(λ), | |
cosφ * Math.sin(λ), | |
Math.sin(φ) | |
]; | |
} | |
function dot(a, b) { | |
for (var i = 0, n = a.length, s = 0; i < n; ++i) s += a[i] * b[i]; | |
return s; | |
} | |
function cross(a, b) { | |
return [ | |
a[1] * b[2] - a[2] * b[1], | |
a[2] * b[0] - a[0] * b[2], | |
a[0] * b[1] - a[1] * b[0] | |
]; | |
} | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
</style> | |
<body> | |
<svg class='keyer'></svg> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src="http://d3js.org/topojson.v1.min.js"></script> | |
<script src="d3.geo.zoom.js"></script> | |
<script> | |
d3.json("../world110.json", function(err, world) { | |
// console.log("data", world) | |
var countries = topojson.feature(world, world.objects.land); | |
// console.log("countries", countries) | |
var width = 720 | |
var height = 400 | |
//var projection = d3.geo.orthographic() | |
var projection = d3.geo.albers() | |
//var projection = d3.geo.mercator() | |
.scale(170) | |
.rotate([100,0,0]) | |
.translate([width/2, height/2]) | |
.clipAngle(90); | |
var path = d3.geo.path() | |
.projection(projection); | |
var svg = d3.select('.keyer'); | |
svg.attr('height',height).attr('width',width); | |
svg.append("path") | |
.datum(countries) | |
.attr("d", path(countries)) | |
.classed("land", true) | |
.attr('fill','rgb(83, 207, 142)'); | |
var graticule = d3.geo.graticule(); | |
svg.append("path") | |
.datum(graticule) | |
.attr("class", "graticule") | |
.attr("d", path) | |
.attr("fill", "transparent") | |
.attr("stroke","grey"); | |
var zoom = d3.geo.zoom() | |
.projection(projection) | |
//.scaleExtent([projection.scale() *.7, projection.scale() *10]) | |
.on("zoom.redraw", function(){ | |
d3.event.sourceEvent.preventDefault(); | |
svg.selectAll("path").attr("d",path); | |
}) | |
d3.selectAll('path') | |
.call(zoom); | |
}) | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment