|
// This is an example Chiasm plugin based on this D3 Canvas example: |
|
// http://bl.ocks.org/mbostock/ba63c55dd2dbc3ab0127 |
|
function Globe (){ |
|
|
|
var my = ChiasmComponent({ |
|
backgroundColor: "black", |
|
foregroundColor: "white", |
|
center: [0, 0], |
|
zoom: 1, |
|
sens: 0.25 |
|
}); |
|
|
|
var canvas = document.createElement("canvas"); |
|
var context = canvas.getContext("2d"); |
|
|
|
var projection = d3.geo.orthographic() |
|
.clipAngle(90); |
|
|
|
var path = d3.geo.path() |
|
.projection(projection) |
|
.context(context); |
|
|
|
// Interaction that lets the user rotate the globe. |
|
// Draws from http://bl.ocks.org/KoGor/5994804 |
|
d3.select(canvas) |
|
.call(d3.behavior.drag() |
|
.origin(function() { |
|
var r = projection.rotate(); |
|
return { |
|
x: r[0] / my.sens, |
|
y: -r[1] / my.sens |
|
}; |
|
}) |
|
.on("drag", function() { |
|
var lng = -d3.event.x * my.sens; |
|
var lat = d3.event.y * my.sens; |
|
|
|
// Disallow rotation beyond the poles. |
|
lat = lat > 89 ? 89 : lat < -89 ? -89 : lat; |
|
|
|
my.center = [ lng, lat ]; |
|
})); |
|
|
|
|
|
// Hand off the DOM element to the Chiasm layout plugin, which will inject it |
|
// into the parent container for us when we specify the special property `el`. |
|
my.el = canvas; |
|
|
|
// The following will all change at runtime, but they are set to some value to |
|
// handle the case that the render function gets run before they are updated. |
|
my.box = { |
|
width: 960, |
|
height: 600 |
|
}; |
|
my.radius = my.box.height / 2 - 5; |
|
my.scale = my.radius; |
|
my.cosLat = 1; |
|
|
|
my.when("box", function (box){ |
|
canvas.width = box.width; |
|
canvas.height = box.height; |
|
projection.translate([box.width / 2, box.height / 2]); |
|
my.radius = box.height / 2 - 5; |
|
}); |
|
|
|
my.when("radius", function (radius){ |
|
my.scale = radius; |
|
}); |
|
|
|
my.when("scale", function (scale){ |
|
projection.scale(my.scale); |
|
}); |
|
|
|
my.when("center", function (center){ |
|
var lat = center[1]; |
|
my.cosLat = Math.cos(toRadians(lat)); |
|
|
|
my.rotate = [ -center[0], -center[1] ]; |
|
}); |
|
|
|
my.when("rotate", function (rotate){ |
|
projection.rotate(rotate); |
|
}); |
|
|
|
function toRadians(deg){ |
|
return deg / 180 * Math.PI; |
|
} |
|
|
|
d3.json("world-110m.json", function(error, world) { |
|
if (error) throw error; |
|
|
|
var land = topojson.feature(world, world.objects.land); |
|
|
|
d3.timer(function(elapsed) { |
|
context.fillStyle = my.backgroundColor; |
|
context.fillRect(0, 0, my.box.width, my.box.height); |
|
|
|
context.fillStyle = my.foregroundColor; |
|
context.strokeStyle = my.foregroundColor; |
|
|
|
context.beginPath(); |
|
path(land); |
|
context.fill(); |
|
|
|
// This constant was tweaked such that the circle on the globe |
|
// roughly matches the viewport in the Leaflet map. |
|
var constant = 800; |
|
|
|
// Compute the size of the current viewport on the globe. |
|
// Draws from formula found at |
|
// http://wiki.openstreetmap.org/wiki/Zoom_levels |
|
var currentViewRadius = constant * my.box.height * my.cosLat / (Math.pow(2, my.zoom + 8)); |
|
|
|
// Stop the code from crashing with transient states that happen on page load. |
|
currentViewRadius = currentViewRadius < 1 ? 1 : currentViewRadius; |
|
var radius = my.radius < 1 ? 1 : my.radius; |
|
|
|
context.beginPath(); |
|
context.arc(my.box.width / 2, my.box.height / 2, radius, 0, 2 * Math.PI, true); |
|
context.lineWidth = 2.5; |
|
context.stroke(); |
|
|
|
|
|
// This makes the circle invert whatever color is underneath it. |
|
context.save(); |
|
context.globalCompositeOperation = "difference"; |
|
context.strokeStyle = "white"; |
|
|
|
// Draw the circle that represents the current zoom and pan. |
|
context.beginPath(); |
|
context.arc(my.box.width / 2, my.box.height / 2, currentViewRadius, 0, 2 * Math.PI, true); |
|
context.lineWidth = 2.5; |
|
context.stroke(); |
|
|
|
// This is the inverse of context.save(), it pops off the context stack so |
|
// we get back whatever the previous value was for |
|
// globalCompositeOperation. |
|
context.restore(); |
|
}); |
|
}); |
|
|
|
return my; |
|
} |
This is awesome! Nice work. Thank you for sharing.