Pan and zoom in the map on the left to rotate the globe.
Click and drag the globe to pan on the map.
An example that shows a Chiasm plugin based on Leaflet.js alongside a Chiasm globe plugin based on the D3 example This is a Globe.
The Chiasm plugins demonstrated here are
layout
A plugin for nested box layout of arbitrary components.
links
A plugin for data binding. This links the pan and zoom between the globe and the map.
leaflet
A Leaflet-based geographic map component.
globe
A D3 black and white globe renderer that uses HTML5 Canvas.
See also these awesome derivatives:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Map & Globe</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<!-- Leaflet.js, a geographic mapping library. -->
<link rel="stylesheet" href="//cdn.leafletjs.com/leaflet-0.7.5/leaflet.css" />
<script src="//cdn.leafletjs.com/leaflet-0.7.5/leaflet.js"></script>
<!-- A functional reactive model library. github.com/curran/model -->
<script src="//curran.github.io/model/cdn/model-v0.2.4.js"></script>
<!-- Chiasm core and plugins. github.com/chiasm-project -->
<script src="//chiasm-project.github.io/chiasm/chiasm-v0.2.0.js"></script>
<script src="//chiasm-project.github.io/chiasm-component/chiasm-component-v0.2.0.js"></script>
<script src="//chiasm-project.github.io/chiasm-layout/chiasm-layout-v0.2.1.js"></script>
<script src="//chiasm-project.github.io/chiasm-links/chiasm-links-v0.2.1.js"></script>
<!-- Custom Chiasm plugins for this example. -->
<script src="globe.js"></script>
<script src="chiasm-leaflet.js"></script>
<style>
body {
background-color: black;
}
/* Make the container fill the page using CSS. */
#chiasm-container {
position: fixed;
left: 20px;
right: 20px;
top: 20px;
bottom: 20px;
}
</style>
</head>
<body>
<div id="chiasm-container"></div>
<script>
var chiasm = Chiasm();
chiasm.plugins.layout = ChiasmLayout;
chiasm.plugins.links = ChiasmLinks;
chiasm.plugins.globe = Globe;
chiasm.plugins.leaflet = ChiasmLeaflet;
chiasm.setConfig({
"layout": {
"plugin": "layout",
"state": {
"containerSelector": "#chiasm-container",
"layout": {
"orientation": "horizontal",
"children": [
"leafletMap",
"d3Globe",
]
}
}
},
"leafletMap": {
"plugin": "leaflet",
"state": {
"center": [-4.592, 39.859],
"zoom": 5
}
},
"d3Globe": {
"plugin": "globe"
},
"links": {
"plugin": "links",
"state": {
"bindings": [
"leafletMap.zoom -> d3Globe.zoom",
"leafletMap.center <-> d3Globe.center"
]
}
}
});
</script>
</body>
</html>
// This is an example Chaism plugin that uses Leaflet.js.
function ChiasmLeaflet() {
var my = ChiasmComponent({
center: [0, 0],
zoom: 2
});
// This line of code lets you see what the center value is when you pan in the map.
//my.when("center", console.log, console);
// Expose a div element that will be added to the Chiasm container.
// This is a special property that Chiasm looks for after components are constructed.
my.el = document.createElement("div");
// When you zoom out all the way, this line makes the background black
// (by default it is gray).
d3.select(my.el).style("background-color", "black");
// Instantiate the Leaflet map, see docs at
// http://leafletjs.com/reference.html#map-constructor
my.map = L.map(my.el, {
// Turn off the "Leaflet" link in the lower right corner.
// Leaflet is properly attributed in the README.
attributionControl: false
}).setView(my.center, my.zoom);
// Add the black & white style map layer.
// Found by browsing http://leaflet-extras.github.io/leaflet-providers/preview/
// TODO move this to configuration.
L.tileLayer("http://stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}.png").addTo(my.map);
// Also try this http://{s}.tiles.earthatlas.info/natural-earth/{z}/{x}/{y}.png
// Returns the current Leaflet map center
// in a format that D3 understands: [longitude, latitude]
function getCenter(){
var center = my.map.getCenter();
return [center.lng, center.lat];
}
// Sets the Leaflet map center to be the given center.
// Note that Leaflet will immediately trigger a "move"
// event
function setCenter(center){
my.map.off("move", onMove);
my.map.panTo(L.latLng(center[1], center[0]), {
animate: false
});
my.map.on("move", onMove);
}
my.map.on("move", onMove);
function onMove(){
my.center = getCenter();
my.zoom = my.map.getZoom();
}
// If the center or zoom was set externally, update the map accordingly.
my.when("center", setCenter);
my.when("zoom", my.map.setZoom, my.map);
my.when("box", function (box) {
// Move to chiasm-layout?
d3.select(my.el)
.style("width", box.width + "px")
.style("height", box.height + "px");
// Tell Leaflet that the size has changed so it updates.
my.map.invalidateSize();
});
return my;
}
// 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;
}