block by curran 01aa2685f083b6c1b9fb

Map & Globe

Full Screen

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

See also these awesome derivatives:

web counter

index.html

<!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>

chiasm-leaflet.js


// 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;
}

globe.js

// 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;
}