Skip to content

Instantly share code, notes, and snippets.

@Libbum
Last active January 1, 2016 10:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Libbum/284dc1ed9400f7627d92 to your computer and use it in GitHub Desktop.
Save Libbum/284dc1ed9400f7627d92 to your computer and use it in GitHub Desktop.
Problematic mouse zoom after globe scale
<!DOCTYPE html>
<meta charset="utf-8">
<style>
#map {
width: 600px;
margin: 0 auto;
}
.foreground {
fill: none;
stroke: #000;
stroke-width: 1px;
pointer-events: all;
cursor: -webkit-grab;
cursor: -moz-grab;
}
.foreground.zooming {
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
}
.graticule {
fill: none;
stroke: #979C9C;
stroke-width: .5px;
stroke-dasharray: 2,2;
}
.countries {
fill: #343642;
stroke: #979C9C;
stroke-width: .5px;
}
.cities {
fill: #348899;
}
.route {
fill: none;
stroke: #962D3E;
stroke-width: 1.5px;
}
.ocean {
fill: #F2EBC7;
}
.point {
fill: #962D3E;
}
</style>
<select id="Trips">
<option> J07 </option>
<option> E12 </option>
<option> E14 </option>
<option> A15 </option>
</select>
<input type="button" id="sub" value="OK"/>
<div id="map"></div>
<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js"></script>
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script>
! function() {
function t(t, n, e) {
var a = t.translate(),
o = Math.atan2(n[1] - a[1], n[0] - a[0]) - Math.atan2(e[1] - a[1], e[0] - a[0]);
return [Math.cos(o / 2), 0, 0, Math.sin(o / 2)]
}
function n(t, n) {
var e = t.invert(n);
return e && isFinite(e[0]) && isFinite(e[1]) && i(e)
}
function e(t) {
var n = .5 * t[0] * d,
e = .5 * t[1] * d,
a = .5 * t[2] * d,
o = Math.sin(n),
r = Math.cos(n),
c = Math.sin(e),
i = Math.cos(e),
s = Math.sin(a),
l = Math.cos(a);
return [r * i * l + o * c * s, o * i * l - r * c * s, r * c * l + o * i * s, r * i * s - o * c * l]
}
function a(t, n) {
var e = t[0],
a = t[1],
o = t[2],
r = t[3],
c = n[0],
i = n[1],
s = n[2],
l = n[3];
return [e * c - a * i - o * s - r * l, e * i + a * c + o * l - r * s, e * s - a * l + o * c + r * i, e * l + a * s - o * i + r * c]
}
function o(t, n) {
if (t && n) {
var e = l(t, n),
a = Math.sqrt(s(e, e)),
o = .5 * Math.acos(Math.max(-1, Math.min(1, s(t, n)))),
r = Math.sin(o) / a;
return a && [Math.cos(o), e[2] * r, -e[1] * r, e[0] * r]
}
}
function r(t, n) {
var e = Math.max(-1, Math.min(1, s(t, n))),
a = 0 > e ? -1 : 1,
o = Math.acos(a * e),
r = Math.sin(o);
return r ? function(e) {
var c = a * Math.sin((1 - e) * o) / r,
i = Math.sin(e * o) / r;
return [t[0] * c + n[0] * i, t[1] * c + n[1] * i, t[2] * c + n[2] * i, t[3] * c + n[3] * i]
} : function() {
return t
}
}
function c(t) {
return [Math.atan2(2 * (t[0] * t[1] + t[2] * t[3]), 1 - 2 * (t[1] * t[1] + t[2] * t[2])) * h, Math.asin(Math.max(-1, Math.min(1, 2 * (t[0] * t[2] - t[3] * t[1])))) * h, Math.atan2(2 * (t[0] * t[3] + t[1] * t[2]), 1 - 2 * (t[2] * t[2] + t[3] * t[3])) * h]
}
function i(t) {
var n = t[0] * d,
e = t[1] * d,
a = Math.cos(e);
return [a * Math.cos(n), a * Math.sin(n), Math.sin(e)]
}
function s(t, n) {
for (var e = 0, a = t.length, o = 0; a > e; ++e) o += t[e] * n[e];
return o
}
function l(t, n) {
return [t[1] * n[2] - t[2] * n[1], t[2] * n[0] - t[0] * n[2], t[0] * n[1] - t[1] * n[0]]
}
function u(t) {
for (var n = 0, e = arguments.length, a = []; ++n < e;) a.push(arguments[n]);
var o = d3.dispatch.apply(null, a);
return o.of = function(n, e) {
return function(a) {
try {
var r = a.sourceEvent = d3.event;
a.target = t, d3.event = a, o[a.type].apply(n, e)
} finally {
d3.event = r
}
}
}, o
}
var d = Math.PI / 180,
h = 180 / Math.PI;
d3.geo.zoom = function() {
function s(t) { v++ || t({ type: "zoomstart" }) }
function l(t) { t({ type: "zoom" }) }
function d(t) { --v || t({ type: "zoomend" }) }
var h, f, p, v = 0,
g = u(m, "zoomstart", "zoom", "zoomend"),
m = d3.behavior.zoom().on("zoomstart", function() {
var r = d3.mouse(this),
i = e(h.rotate()),
u = n(h, r);
u && (p = u), M.call(m, "zoom", function() {
h.scale(z.k = d3.event.scale);
var e = d3.mouse(this),
s = o(p, n(h, e));
h.rotate(z.r = c(i = s ? a(i, s) : a(t(h, r, e), i))), r = e, l(g.of(this, arguments))
}), s(g.of(this, arguments))
}).on("zoomend", function() {
M.call(m, "zoom", null), d(g.of(this, arguments))
}),
M = m.on,
z = {
r: [0, 0, 0],
k: 1
};
return m.rotateTo = function(t) {
var n = o(i(t), i([-z.r[0], -z.r[1]]));
return c(a(e(z.r), n))
}, m.projection = function(t) {
return arguments.length ? (h = t, z = {
r: h.rotate(),
k: h.scale()
}, m.scale(z.k)) : h
}, m.duration = function(t) {
return arguments.length ? (f = t, m) : f
}, m.event = function(t) {
t.each(function() {
var t = d3.select(this),
n = g.of(this, arguments),
a = z,
o = d3.transition(t);
if (o !== t) {
o.each("start.zoom", function() {
this.__chart__ && (z = this.__chart__), h.rotate(z.r).scale(z.k), s(n)
}).tween("zoom:zoom", function() {
var t = m.size()[0],
i = r(e(z.r), e(a.r)),
s = d3.geo.distance(z.r, a.r),
u = d3.interpolateZoom([0, 0, t / z.k], [s, 0, t / a.k]);
return f && o.duration(f(.001 * u.duration)),
function(e) {
var a = u(e);
this.__chart__ = z = {
r: c(i(a[0] / s)),
k: t / a[2]
}, h.rotate(z.r).scale(z.k), m.scale(z.k), l(n)
}
}).each("end.zoom", function() {
d(n)
});
try {
o.each("interrupt.zoom", function() {
d(n)
})
} catch (i) {}
} else this.__chart__ = z, s(n), l(n), d(n)
})
}, d3.rebind(m, g, "on")
}
}(),
function() {
function t(t, n, e) {
var a = n.projection();
t.append("path").datum(d3.geo.graticule()).attr("class", "graticule").attr("d", n),
t.append("path").datum({type: "Sphere"}).attr("class", "foreground").attr("d", n).on("mousedown.grab", function() {
var n;
e && (n = t.insert("path", ".foreground").datum({type: "Point", coordinates: a.invert(d3.mouse(this))}).attr("class", "point").attr("d", o));
var o = d3.select(this).classed("zooming", !0),
r = d3.select(window).on("mouseup.grab", function() {
o.classed("zooming", !1), r.on("mouseup.grab", null), e && n.remove()
})
})
}
function n(t, n) {
return d3.geo.orthographic().precision(.5).clipAngle(90).clipExtent([ [-1, -1], [t + 1, n + 1] ]).translate([t / 2, n / 2]).scale(t / 2 - 10)
}
function sphereRotate() {
var x0, y0, cy0, sy0, kx0, ky0,
x1, y1, cy1, sy1, kx1, ky1,
d,
k;
function interpolate(t) {
var B = Math.sin(t *= d) * k,
A = Math.sin(d - t) * k,
x = A * kx0 + B * kx1,
y = A * ky0 + B * ky1,
z = A * sy0 + B * sy1;
return [ Math.atan2(y, x) / d3_radians, Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_radians ];
}
interpolate.distance = function() {
if (d == null) k = 1 / Math.sin(d = Math.acos(Math.max(-1, Math.min(1, sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0)))));
return d;
};
interpolate.source = function(_) {
var cx0 = Math.cos(x0 = _[0] * d3_radians),
sx0 = Math.sin(x0);
cy0 = Math.cos(y0 = _[1] * d3_radians);
sy0 = Math.sin(y0);
kx0 = cy0 * cx0;
ky0 = cy0 * sx0;
d = null;
return interpolate;
};
interpolate.target = function(_) {
var cx1 = Math.cos(x1 = _[0] * d3_radians),
sx1 = Math.sin(x1);
cy1 = Math.cos(y1 = _[1] * d3_radians);
sy1 = Math.sin(y1);
kx1 = cy1 * cx1;
ky1 = cy1 * sx1;
d = null;
return interpolate;
};
return interpolate;
}
function getRotation(coords) {
var lat = 0,
long = 0;
for (var i = 0, len = coords.length; i < len; i++) {
lat += coords[i][0];
long += coords[i][1];
};
lat /= coords.length;
long /= coords.length;
return [-lat,-long];
}
var a = 600, //width
o = 600, //height
tripName = "A15",
proj = n(a, o),
r = d3.dispatch("world"),
c = -1,
d3_radians = Math.PI / 180;
d3.selectAll("#map").data([proj]).append("svg").attr("width", a).attr("height", o).each(function(p) {
var e = d3.geo.path().projection(p),
a = d3.select(this).call(t, e, !0);
a.selectAll(".foreground").call(d3.geo.zoom().projection(p).scaleExtent([.7 * p.scale(), 10 * p.scale()]).on("zoom.redraw", function() {
d3.event.sourceEvent.preventDefault && d3.event.sourceEvent.preventDefault(),
a.selectAll("path").attr("d", e)
})),
r.on("world." + ++c, function() {
a.selectAll("path").attr("d", e)
})
}),
$("#sub").on("click",function(){
var coords = [],
selected = $("#Trips").val(),
interp = sphereRotate();
if (selected !== tripName) {
d3.selectAll(".route").each( function(d, i){
if(d.properties.name == selected){
d3.select(this).attr("visibility", "visible");
coords = getRotation(d.geometry.coordinates);
} else {
d3.select(this).attr("visibility", "hidden");
}
});
d3.transition().delay(250).duration(2250)
.tween("rotate", function() {
interp.source(proj.rotate()).target(coords).distance();
var sc = d3.interpolate(proj.scale(), a / 2 - 10);
return function(i) {
proj.rotate(interp(i)).scale(sc(i));
d3.select("#map").selectAll("path").attr("d", d3.geo.path().projection(proj));
//r.world();
};
});
tripName = selected;
}
}),
d3.json("world.json", function(t, n) {
d3.selectAll("svg").insert("path", ".graticule").datum({type: "Sphere"}).attr("class", "ocean"),
d3.selectAll("svg").insert("path", ".foreground").datum(topojson.feature(n, n.objects.countries)).attr("class", "countries"),
d3.selectAll("svg").insert("path", ".foreground").datum(topojson.feature(n, n.objects.cities)).attr("class", "cities").selectAll("LineString").attr("class", "route"),
d3.selectAll("svg").insert("g", ".cities").attr("id", "routes"),
d3.selectAll("#routes").selectAll("path").data(topojson.feature(n, n.objects.trips).features).enter()
.append("path").attr("id", function(d) { return d.properties.name; }).attr("class", "route")
.attr("visibility", function(d) {
if (d.properties.name == tripName) {
proj.rotate(getRotation(d.geometry.coordinates));
return "visible";
} else {
return "hidden";
}
}),
r.world()
})
}();
</script>
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment