overlay.js
var width = 300,
height = 300,
largeWidth = 600,
largeHeight = 600;
var margin = {
top: 0
}
comparisons = {
"Sweden,Madagascar": {
scale: 1120,
latLon: [{
lat: 62.6,
lon: 16.3
}, {
lat: -19,
lon: 46.7
}]
},
"Australia,Antarctica": {
scale: 470,
latLon: [{
lat: -27,
lon: 130
}, {
lat: -90,
lon: 0
}]
},
"Europe,Brazil": {
scale: 527,
latLon: [{
lat: 56.44,
lon: 17.1
}, {
lat: -6.07833,
lon: -53.2052
}]
},
"US,Australia": {
scale: 640,
latLon: [{
lat: 35.65,
lon: -97.24
}, {
lat: -27,
lon: 130
}]
},
"South_America,Greenland": {
scale: 458,
latLon: [{
lat: -15.97,
lon: -52.87
}, {
lat: 65.88,
lon: -42.21
}]
},
"Brazil,US": {
scale: 360,
latLon: [{
lat: -16.7833,
lon: -53.2052
}, {
lat: 35.65,
lon: -97.24
}]
},
"Africa,North_America": {
scale: 350,
latLon: [{
lat: 6.52865,
lon: 20.3586336
}, {
lat: 48.2392291,
lon: -98.9443219
}]
},
"North_Africa,Russia": {
scale: 470,
latLon: [{
lat: 15.0,
lon: 18.82
}, {
lat: 60.65,
lon: 95.995
}]
},
"Saudi_Arabia,Alaska": {
scale: 713,
latLon: [{
lat: 22.389,
lon: 46.59
}, {
lat: 64.23,
lon: -149.862
}]
},
"Europe,Antarctica": {
scale: 454,
latLon: [{
lat: 56.44,
lon: 17.1
}, {
lat: -90,
lon: 0
}]
}
}
var state = {
scale: 450,
latLon: [{
lat: 6.52865,
lon: 20.3586336
}, {
lat: 48.2392291,
lon: -98.9443219
}]
}
if (window.location.hash.split("&").length != 0) {
var windowState = window.location.hash.split("&");
for (var i = 0; i < windowState.length; i++) {
var k = windowState[i].replace('#', '').split('=');
if (k[0] == "scale") {
state.scale = +k[1];
} else if (k[0] == "center0") {
state.latLon[0] = {
lat: k[1].split(",")[0],
lon: k[1].split(",")[1]
};
} else if (k[0] == "center1") {
state.latLon[1] = {
lat: k[1].split(",")[0],
lon: k[1].split(",")[1]
};
}
}
}
var mobile = false;
if (document.getElementById("testMobile").offsetWidth <= 0) {
mobile = true;
}
var padding = 5;
var transitionDuration = 1000;
var mainSVG = d3.select("#tool").append("svg")
.attr("width", 905)
.attr("height", 600)
.attr("class", "mainSVG");
var mapObjects = []
var zoomRange = [220, 1600]
var zoomToBoxScale = d3.scale.linear().domain([470, 2000]).range([80, 20]);
var slider = d3.select("#tool")
.append("p")
.append("input")
.attr("type", "range")
.style("margin-left", "750px")
.attr("min", zoomRange[0])
.attr("max", zoomRange[1])
.attr("step", (zoomRange[1] - zoomRange[0]) / 400)
.on("input", slided);
slider.property("value", state.scale)
function slided(d) {
var duration = 0;
updateCenterBoxSize(zoomToBoxScale(state.scale), zoomToBoxScale(d3.select(this).property("value")), duration)
state.scale = d3.select(this).property("value");
updateLargeScale(largeMapObjects, state.scale, duration);
updateHash()
}
for (var i = 0; i < 2; i++) {
mapObjects[i] = setUpSmallMaps(state.latLon[i].lat, state.latLon[i].lon, i)
}
function setUpSmallMaps(lat, lon, name) {
var center = [-lon, -lat]
var projectionSmall = d3.geo.orthographic()
.translate([width / 2, height / 2])
.scale(width / 2 * .9)
.center([0, 0])
.rotate(center)
.clipAngle(90)
.precision(.7);
var path = d3.geo.path()
.projection(projectionSmall);
var graticule = d3.geo.graticule();
var svg = mainSVG.append("g")
.attr('transform', 'translate(' + padding + ',' + (height * name + margin.top) + ')')
.classed("g_" + name, true);
svg.append("defs").append("path")
.datum({
type: "Sphere"
})
.attr("id", "sphere")
.attr("d", path);
svg.append("use")
.attr("class", "stroke")
.attr("xlink:href", "#sphere");
svg.append("use")
.attr("class", "fill")
.attr("xlink:href", "#sphere");
svg.append("path")
.datum(graticule)
.attr("class", "graticule graticule_" + name)
.attr("d", path);
svg.call(dragSetupSmall(name));
return {
"svg": svg,
"projection": projectionSmall,
"path": path,
"graticule": graticule,
"lat": lat,
"lon": lon
}
}
var largeSvg = mainSVG.append("g").attr("class", "largeSvg")
.attr('transform', 'translate(' + (2 * padding + width) + ',' + (margin.top) + ')')
var largeMapObjects = []
for (var i = 0; i < 2; i++) {
largeMapObjects[i] = setUpLargeMaps(state.latLon[i].lat, state.latLon[i].lon, i, largeSvg)
}
function setUpLargeMaps(lat, lon, name, svg) {
var center = [-lon, -lat]
var projectionLarge = d3.geo.azimuthalEqualArea()
.translate([largeWidth / 2, largeHeight / 2])
.scale(state.scale)
.center([0, 0])
.clipAngle(180 - 1e-3)
.clipExtent([
[2 * padding, 2 * padding],
[largeWidth - 2 * padding, largeHeight - 2 * padding]
])
.rotate(center)
.precision(.7);
var path = d3.geo.path()
.projection(projectionLarge);
return {
"svg": svg,
"projection": projectionLarge,
"path": path,
"lat": lat,
"lon": lon
}
}
d3.json("world-110m.json", function(error, world) {
largeMapObjects.data = world;
for (var i = 0; i < mapObjects.length; i++) {
svg = mapObjects[i].svg;
svg
.insert("path", ".graticule" + i)
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land land_" + i)
.attr("d", mapObjects[i].path);
addCenterBox(svg, [mapObjects[i].lon, mapObjects[i].lat], mapObjects[i].projection)
}
for (var i = 0; i < 2; i++) {
svg = largeMapObjects[i].svg.append('g').attr("class", "large_" + i);
svg.insert("path", ".large_graticule_" + i)
.datum(topojson.feature(world, world.objects.land))
.attr("class", "large_land_" + i)
.attr("d", largeMapObjects[i].path);
svg.insert("path", ".large_graticule" + i)
.datum(topojson.mesh(world, world.objects.countries, function(a, b) {
return a !== b;
}))
.attr("class", "boundary boundary_" + i)
.attr("d", largeMapObjects[i].path);
}
largeSvg.append('rect')
.attr("height", largeHeight - 4 * padding)
.attr("x", 2 * padding)
.attr("y", 2 * padding)
.attr("width", largeWidth - 4 * padding)
.attr("class", "largeBackgroundRect")
if (!mobile) {
d3.select(".largeSvg").call(dragSetupLarge())
}
updateLargeRotation(largeMapObjects, 0, largeMapObjects[0].projection.rotate(), 0)
updateLargeRotation(largeMapObjects, 1, largeMapObjects[1].projection.rotate(), 0)
});
function addCenterBox(svg) {
var distance = zoomToBoxScale(state.scale);
svg.append("rect").attr("x", width / 2 - distance)
.attr("y", height / 2 - distance)
.attr("width", distance * 2)
.attr("height", distance * 2)
.attr("stroke", "black")
.attr("fill", "none")
.attr("class", "littleBox")
}
function dragSetupSmall(name) {
function resetDrag() {
dragDistance = {
x: 0,
y: 0
};
}
var dragDistance = {
x: 0,
y: 0
};
return d3.behavior.drag()
.on("dragstart", function() {
d3.event.sourceEvent.preventDefault();
})
.on("drag", function() {
dragDistance.x = dragDistance.x + d3.event.dx;
dragDistance.y = dragDistance.y + d3.event.dy;
updateRotateBasedOnSmallMapPan(dragDistance, name, 'drag')
resetDrag()
})
.on("dragend", function() {
updateRotateBasedOnSmallMapPan(dragDistance, name, 'dragend')
resetDrag()
})
}
var count = 0;
function dragSetupLarge() {
function resetDrag() {
dragDistance = {
x: 0,
y: 0
};
}
var dragDistance = {
x: 0,
y: 0
};
return d3.behavior.drag()
.on("drag", function() {
dragDistance.x = dragDistance.x + d3.event.dx;
dragDistance.y = dragDistance.y + d3.event.dy;
updateRotateBasedOnLargeMapPan(dragDistance, 0, 'na')
updateRotateBasedOnLargeMapPan(dragDistance, 1, 'na')
resetDrag()
})
.on("dragend", function() {
updateRotateBasedOnLargeMapPan(dragDistance, 0, 'na')
updateRotateBasedOnLargeMapPan(dragDistance, 1, 'na')
resetDrag()
});
}
var updateHash = function() {
window.location.hash = "scale=" + state.scale + "¢er0=" + state.latLon[0].lat + "," + state.latLon[0].lon + "¢er1=" + state.latLon[1].lat + "," + state.latLon[1].lon;
slider.property("value", state.scale)
}
function pixelDiff_to_rotation_large(projection, pxDiff) {
var k = projection.invert([largeWidth / 2 - pxDiff.x, largeHeight / 2 - pxDiff.y])
return [-k[0], -k[1], 0]
}
function pixelDiff_to_rotation_small(projection, pxDiff) {
var k = projection.rotate()
return ([k[0] + pxDiff.x / 136 * 90, k[1] - pxDiff.y, k[2]])
}
function updateStateRotation(rotateCoord, name) {
state.latLon[name] = {
lon: rotateCoord[0],
lat: rotateCoord[1]
}
}
function updateLargeRotationDuringDrag(map, name, newRotate, transitionDuration) {
d3.selectAll(".large_land_" + name)
.transition().duration([transitionDuration])
.attrTween("d", rotationTween(map[name].path, map[name].projection, newRotate));
d3.selectAll(".boundary_" + name).transition().duration([transitionDuration]).remove();
}
function updateLargeRotationEndDrag(map, name, newRotate, transitionDuration) {
map[name].projection.rotate(newRotate)
d3.select(".large_" + name).insert("path", ".large_graticule" + name)
.datum(topojson.mesh(map.data, map.data.objects.countries, function(a, b) {
return a !== b;
}))
.attr("class", "boundary boundary_" + name)
.transition().duration([500])
.attr("d", map[name].path);
}
function updateLargeRotation(map, name, newRotate, transitionDuration) {
d3.selectAll(".large_land_" + name)
.transition().duration([transitionDuration])
.attrTween("d", rotationTween(map[name].path, map[name].projection, newRotate));
d3.selectAll(".boundary_" + name)
.transition().duration([transitionDuration])
.attrTween("d", rotationTween(map[name].path, map[name].projection, newRotate));
}
function updateLargeRotationAndScale(map, name, newRotate, newScale, transitionDuration) {
d3.selectAll(".large_land_" + name)
.transition().duration([transitionDuration])
.attrTween("d", rotationAndScaleTween(map[name].path, map[name].projection, newRotate, newScale))
d3.selectAll(".boundary_" + name)
.transition().duration([transitionDuration])
.attrTween("d", rotationAndScaleTween(map[name].path, map[name].projection, newRotate, newScale))
}
function updateSmallRotation(mapObject, name, newRotate, transitionDuration) {
if (transitionDuration == 0) {
mapObject[name].projection.rotate(newRotate)
d3.selectAll(".graticule_" + name).attr("d", mapObject[name].path);
d3.selectAll(".land_" + name).attr("d", mapObject[name].path);
} else {
d3.selectAll(".graticule_" + name)
.transition()
.duration([transitionDuration])
.attrTween("d", rotationTween(mapObject[name].path, mapObject[name].projection, newRotate));
d3.selectAll(".land_" + name)
.transition()
.duration([transitionDuration])
.attrTween("d", rotationTween(mapObject[name].path, mapObject[name].projection, newRotate));
}
}
function updateLargeScale(maps, newScale, duration) {
if (duration > 0) {
for (var i = 0; i < 2; i++) {
d3.selectAll(".large_land_" + i)
.transition().duration([duration])
.attrTween("d", scaleTween(maps[i].path, maps[i].projection, newScale));
d3.selectAll(".boundary_" + i)
.transition().duration([duration])
.attrTween("d", scaleTween(maps[i].path, maps[i].projection, newScale));
}
} else {
for (var i = 0; i < 2; i++) {
maps[i].projection.scale(newScale)
d3.selectAll(".large_land_" + i).attr("d", maps[i].path);
d3.selectAll(".boundary_" + i).attr("d", maps[i].path);
}
}
}
function updateCenterBoxSize(oldDistance, newDistance, duration) {
if (duration == 0) {
d3.selectAll(".littleBox")
.attr("x", width / 2 - newDistance)
.attr("y", height / 2 - newDistance)
.attr("width", newDistance * 2)
.attr("height", newDistance * 2)
} else {
d3.selectAll(".littleBox")
.transition().duration([duration])
.attrTween("x", distanceTween(width / 2 - oldDistance, width / 2 - newDistance))
.attrTween("y", distanceTween(height / 2 - oldDistance, height / 2 - newDistance))
.attrTween("width", distanceTween(2 * oldDistance, 2 * newDistance))
.attrTween("height", distanceTween(2 * oldDistance, 2 * newDistance))
}
}
function updateRotateBasedOnSmallMapPan(pixelDifference, name, stage) {
var projection = mapObjects[name].projection;
projection.rotate(pixelDiff_to_rotation_small(projection, pixelDifference))
updateView(projection.rotate(), "na", name, 0, 'na')
}
function updateRotateBasedOnLargeMapPan(pixelDifference, name, stage) {
var projection = largeMapObjects[name].projection;
var newRotate = pixelDiff_to_rotation_large(projection, pixelDifference)
updateView(newRotate, "na", name, 0, stage)
}
function updateView(newRotate, newScale, name, duration, stage) {
updateStateRotation(newRotate, name)
updateSmallRotation(mapObjects, name, newRotate, duration)
if (newScale == 'na') {
if (stage == 'drag') {
updateLargeRotationDuringDrag(largeMapObjects, name, newRotate, duration)
} else if (stage == 'dragend') {
updateLargeRotationEndDrag(largeMapObjects, name, newRotate, duration)
} else {
updateLargeRotation(largeMapObjects, name, newRotate, duration)
}
} else {
updateLargeRotationAndScale(largeMapObjects, name, newRotate, newScale, duration)
if (name == 0) {
updateCenterBoxSize(zoomToBoxScale(state.scale), zoomToBoxScale(newScale), duration)
state.scale = newScale
}
}
updateHash()
}
d3.selectAll("button").on("click", function() {
state.latLon[0] = comparisons[this.name].latLon[0];
state.latLon[1] = comparisons[this.name].latLon[1];
updateView([-state.latLon[0].lon, -state.latLon[0].lat, 0], comparisons[this.name].scale, 0, transitionDuration)
updateView([-state.latLon[1].lon, -state.latLon[1].lat, 0], comparisons[this.name].scale, 1, transitionDuration)
})
function rotationTween(path, projection, new3Rotation) {
return function(d, i, a) {
var interpolate = d3.interpolate(projection.rotate(), new3Rotation);
return function(t) {
projection.rotate(interpolate(t));
return path(d);
}
}
}
function scaleTween(path, projection, newScale) {
return function(d, i, a) {
var interpolate = d3.interpolate(projection.scale(), newScale);
return function(t) {
projection.scale(interpolate(t));
return path(d);
}
}
}
function distanceTween(oldDist, newDist) {
return function(d, i, a) {
var interpolate = d3.interpolate(oldDist, newDist);
return function(t) {
return interpolate(t);
}
}
}
function rotationAndScaleTween(path, projection, new3Rotation, newScale) {
return function(d, i, a) {
var interpolateScale = d3.interpolate(projection.scale(), newScale);
var interpolateRotate = d3.interpolate(projection.rotate(), new3Rotation);
return function(t) {
projection.scale(interpolateScale(t));
projection.rotate(interpolateRotate(t));
return path(d);
}
}
}