|
var mapWidth = 960, |
|
mapHeight = 500, |
|
focused = false, |
|
ortho = true, |
|
sens = 0.25; |
|
|
|
var projectionGlobe = d3.geo.orthographic() |
|
.scale(240) |
|
.center([0, 0]) |
|
.translate([mapWidth / 2, mapHeight / 2]) |
|
.clipAngle(90); |
|
|
|
var projectionMap = d3.geo.equirectangular() |
|
.scale(145) |
|
.center([0, 0]) |
|
.translate([mapWidth / 2, mapHeight / 2]) |
|
|
|
var projection = projectionGlobe; |
|
|
|
var path = d3.geo.path() |
|
.projection(projection); |
|
|
|
var globe2map = interpolatedProjection(projectionGlobe, projectionMap), |
|
map2globe = interpolatedProjection(projectionMap, projectionGlobe); |
|
|
|
var svgMap = d3.select("div#map").append("svg") |
|
.attr("overflow", "hidden") |
|
.attr("width", mapWidth) |
|
.attr("height", mapHeight); |
|
|
|
var zoneTooltip = d3.select("div#map").append("div").attr("class", "zoneTooltip"), |
|
pointLlabel = d3.select("div#map").append("div").attr("class", "pointLabel"), |
|
infoLabel = d3.select("div#map").append("div").attr("class", "infoLabel"); |
|
|
|
var g = svgMap.append("g"); |
|
|
|
//Unreelling transformation |
|
|
|
function animation(interProj) { |
|
defaultRotate(); |
|
g.transition() |
|
.duration(7500) |
|
.tween("projection", function() { |
|
return function(_) { |
|
interProj.alpha(_); |
|
g.selectAll("path").attr("d", path); |
|
}; |
|
}) |
|
} |
|
|
|
function interpolatedProjection(a, b) { |
|
var projection = d3.geo.projection(raw).scale(1), |
|
center = projection.center, |
|
translate = projection.translate, |
|
clip = projection.clipAngle, |
|
α; |
|
|
|
function raw(λ, φ) { |
|
var pa = a([λ *= 180 / Math.PI, φ *= 180 / Math.PI]), pb = b([λ, φ]); |
|
return [(1 - α) * pa[0] + α * pb[0], (α - 1) * pa[1] - α * pb[1]]; |
|
} |
|
|
|
projection.alpha = function(_) { |
|
if (!arguments.length) return α; |
|
α = +_; |
|
var ca = a.center(), cb = b.center(), |
|
ta = a.translate(), tb = b.translate(); |
|
center([(1 - α) * ca[0] + α * cb[0], (1 - α) * ca[1] + α * cb[1]]); |
|
translate([(1 - α) * ta[0] + α * tb[0], (1 - α) * ta[1] + α * tb[1]]); |
|
if (ortho === true) {clip(180 - α * 90);} |
|
return projection; |
|
}; |
|
|
|
delete projection.scale; |
|
delete projection.translate; |
|
delete projection.center; |
|
return projection.alpha(0); |
|
} |
|
|
|
//Rotate to default before animation |
|
|
|
function defaultRotate() { |
|
d3.transition() |
|
.duration(1500) |
|
.tween("rotate", function() { |
|
var r = d3.interpolate(projection.rotate(), [0, 0]); |
|
return function(t) { |
|
projection.rotate(r(t)); |
|
g.selectAll("path").attr("d", path); |
|
}; |
|
}) |
|
}; |
|
|
|
//Starter for function AFTER All transitions |
|
|
|
function endall(transition, callback) { |
|
var n = 0; |
|
transition |
|
.each(function() { ++n; }) |
|
.each("end", function() { if (!--n) callback.apply(this, arguments); }); |
|
} |
|
|
|
//Loading data |
|
|
|
queue() |
|
.defer(d3.json, "/d/5685937/world-110m.json") |
|
.defer(d3.tsv, "/d/5685937/world-110m-country-names.tsv") |
|
.await(ready); |
|
|
|
|
|
function ready(error, world, countryData) { |
|
|
|
var countryById = {}, |
|
countries = topojson.feature(world, world.objects.countries).features; |
|
|
|
//Adding countries by name |
|
|
|
countryData.forEach(function(d) { |
|
countryById[d.id] = d.name; |
|
}); |
|
|
|
//Drawing countries on the globe |
|
|
|
var world = g.selectAll("path").data(countries); |
|
world.enter().append("path") |
|
.attr("class", "mapData") |
|
.attr("d", path) |
|
.classed("ortho", ortho = true); |
|
|
|
//Drag event |
|
|
|
world.call(d3.behavior.drag() |
|
.origin(function() { var r = projection.rotate(); return {x: r[0] / sens, y: -r[1] / sens}; }) |
|
.on("drag", function() { |
|
var λ = d3.event.x * sens, |
|
φ = -d3.event.y * sens, |
|
rotate = projection.rotate(); |
|
//Restriction for rotating upside-down |
|
φ = φ > 30 ? 30 : |
|
φ < -30 ? -30 : |
|
φ; |
|
projection.rotate([λ, φ]); |
|
g.selectAll("path.ortho").attr("d", path); |
|
g.selectAll(".focused").classed("focused", focused = false); |
|
})) |
|
|
|
//Events processing |
|
|
|
world.on("mouseover", function(d) { |
|
if (ortho === true) { |
|
infoLabel.text(countryById[d.id]) |
|
.style("display", "inline"); |
|
} else { |
|
zoneTooltip.text(countryById[d.id]) |
|
.style("left", (d3.event.pageX + 7) + "px") |
|
.style("top", (d3.event.pageY - 15) + "px") |
|
.style("display", "block"); |
|
} |
|
}) |
|
.on("mouseout", function(d) { |
|
if (ortho === true) { |
|
infoLabel.style("display", "none"); |
|
} else { |
|
zoneTooltip.style("display", "none"); |
|
} |
|
}) |
|
.on("mousemove", function() { |
|
if (ortho === false) { |
|
zoneTooltip.style("left", (d3.event.pageX + 7) + "px") |
|
.style("top", (d3.event.pageY - 15) + "px"); |
|
} |
|
}) |
|
.on("click", function(d) { |
|
if (focused === d) return reset(); |
|
g.selectAll(".focused").classed("focused", false); |
|
d3.select(this).classed("focused", focused = d); |
|
infoLabel.text(countryById[d.id]) |
|
.style("display", "inline"); |
|
|
|
//Transforming Globe to Map |
|
|
|
if (ortho === true) { |
|
defaultRotate(); |
|
setTimeout(function() { |
|
projection = globe2map; |
|
path.projection(projection); |
|
animation(projection); |
|
g.selectAll(".ortho").classed("ortho", ortho = false); |
|
} |
|
, 1600); |
|
} |
|
}); |
|
|
|
//Adding extra data when focused |
|
|
|
function focus(d) { |
|
if (focused === d) return reset(); |
|
g.selectAll(".focused").classed("focused", false); |
|
d3.select(this).classed("focused", focused = d); |
|
} |
|
|
|
//Reset projection |
|
|
|
function reset() { |
|
g.selectAll(".focused").classed("focused", focused = false); |
|
infoLabel.style("display", "none"); |
|
zoneTooltip.style("display", "none"); |
|
|
|
//Transforming Map to Globe |
|
|
|
projection = map2globe; |
|
path.projection(projection); |
|
animation(projection); |
|
g.selectAll("path").classed("ortho", ortho = true); |
|
} |
|
}; |
have you manged to solve the country spreading issue?
as far as i can see the country's that get spread are the ones that are connected at the edges of the flat map to the left side.
wish i could help with code fix but i'm new to D3 and still learning to understand this amazing thing.