Skip to content

Instantly share code, notes, and snippets.

@curran
Last active July 16, 2018 19:19
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save curran/01aa2685f083b6c1b9fb to your computer and use it in GitHub Desktop.
Save curran/01aa2685f083b6c1b9fb to your computer and use it in GitHub Desktop.
Map & Globe

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:

web counter
// 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;
}
<!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="http://cdn.leafletjs.com/leaflet-0.7.5/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7.5/leaflet.js"></script>
<!-- A functional reactive model library. github.com/curran/model -->
<script src="http://curran.github.io/model/cdn/model-v0.2.4.js"></script>
<!-- Chiasm core and plugins. github.com/chiasm-project -->
<script src="http://chiasm-project.github.io/chiasm/chiasm-v0.2.0.js"></script>
<script src="http://chiasm-project.github.io/chiasm-component/chiasm-component-v0.2.0.js"></script>
<script src="http://chiasm-project.github.io/chiasm-layout/chiasm-layout-v0.2.1.js"></script>
<script src="http://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>
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.
@tyrasd
Copy link

tyrasd commented Sep 9, 2015

Very nice! Btw: Here's a modified version that shows the map's real bounding box on the globe instead of the approximate circle: http://bl.ocks.org/tyrasd/e2744ee563f7cab80350

@curran
Copy link
Author

curran commented Sep 9, 2015

This is awesome! Nice work. Thank you for sharing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment