block by jczaplew 6798471

Adaptive Composite Map Projection (Canvas)

Full Screen

Same as http://bl.ocks.org/jczaplew/6603431 but using Canvas instead of SVG. Seems to be faster in some browsers, but still remains unusably slow in mobile browsers. For full code see https://github.com/jczaplew/d3-acmp-lite

index.html

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <title>Adaptive Composite Map Projection (Lite)</title>
  <style>
    body {
      background: #fcfcfa;
      height: 500px;
      position: relative;
      width: 960px;
    }
    .stroke {
      fill: none;
      stroke: #000;
      stroke-width: 3px;
    }
    .fill {
      fill: #000;
    }
    .graticule {
      fill: none;
      stroke: #fff;
      stroke-width: .5px;
      stroke-opacity: .5;
    }
    .land {
      fill: #fff;
      stroke:#777;
      stroke-width:.75px;
    }
    .boundary {
      fill: none;
      stroke: #fff;
      stroke-width: .5px;
    }
  </style>
</head>
<body>
  <script src="//d3js.org/d3.v3.min.js"></script>
  <script src="//d3js.org/d3.geo.projection.v0.min.js"></script>
  <script src="//d3js.org/topojson.v1.min.js"></script>
  <script>

  function pickProjection(scale, coords) {
      if (scale <= 1.5) {
        d3.select("#proj").text("Hammer");
        currentProj = "hammer";
        return d3.geo.hammer()
           .coefficient(2)
           .precision(0.3)
           .scale(scale * 100)
           .rotate([coords[0],0]);

      } else if (scale <= 2.0) {
        d3.select("#proj").text("Modified Hammer");
        currentProj = "hammer";
        return d3.geo.hammer()
           .coefficient(2.0 - (scale-1.5) * 2.0)
           .precision(0.3)
           .scale(scale * 100)
           .rotate([coords[0],0]);

      } else if (scale <= 6.0) {
        d3.select("#proj").text("Lambert azimuthal");
        currentProj = "lambert";
        return d3.geo.azimuthalEqualArea()
           .scale(scale * 100)
           .precision(0.3)
           .clipAngle(180 - 1e-3)
           .rotate(coords);

      } else if (scale <= 12) {
          d3.select("#proj").text("Albers");
          currentProj = "albers";
          coords = (coords[0] == 0) ? [0.01, 0.01] : coords;
          return d3.geo.albers()
            .rotate([coords[0], 0])
            .center([0, -coords[1]])
            .parallels([ -coords[1] - scale, -coords[1] + scale])
            .scale(scale * 100)
            .translate([width / 2, height / 2])
            .precision(.1);

      } else {
        d3.select("#proj").text("Mercator");
        return d3.geo.mercator()
           .scale(scale * 95)
           .translate([width / 2, height / 2])
           .rotate([coords[0],0])
           .center([0, -coords[1]]);
      }
    }

  var width = 960,
      height = 500,
      scale = 1,
      m0,
      o0,
      currentProj,
      rotate = [0.1,0.1],
      projection = pickProjection(1, [0,0]),
      graticule = d3.geo.graticule(),
      sphere = {type: "Sphere"};

  var canvas = d3.select("body").append("canvas")
    .attr("width", width)
    .attr("height", height);

  var context = canvas.node().getContext("2d");

  var path = d3.geo.path()
    .projection(projection)
    .context(context);

  var zoom = d3.behavior.zoom()
    .center([width / 2, height / 2])
    .on("zoomstart", function(){
      m0 = [d3.event.sourceEvent.pageX, d3.event.sourceEvent.pageY];
        if (currentProj == "albers") {
          var proj = projection.rotate(),
              cent = projection.center();
          o0 = [-proj[0], cent[1]];
        } else {
          var proj = projection.rotate();
          o0 = [-proj[0],-proj[1]];
        }
        d3.event.sourceEvent.stopPropagation();
    })
    .on("zoom", function() {
      if (m0) {
        var constant = (scale < 4) ? 4 : scale * 2;
        var m1 = [d3.event.sourceEvent.pageX, d3.event.sourceEvent.pageY],
            o1 = [o0[0] + (m0[0] - m1[0]) / constant, o0[1] + (m1[1] - m0[1]) / constant];
      }

      rotate = [-o1[0], -o1[1]];
      scale = (d3.event.scale >= 1) ? d3.event.scale : 1;
      projection = pickProjection(scale, rotate);
      path = d3.geo.path().projection(projection);
      
      context.clearRect(0, 0, width, height);
      
      // Update sphere styles
      context.beginPath();
      path.context(context)(sphere);
      context.fillStyle = "#000";
      context.fill();

      // Update graticule styles
      context.beginPath();
      path.context(context)(grid);
      context.lineWidth = 0.5;
      context.strokeStyle = "#ddd";
      context.stroke();

      // Update land styles
      context.beginPath();
      path.context(context)(land);
      context.fillStyle = "#fff";
      context.fill();
      context.strokeStyle = "#000";
      context.stroke();

    });

  d3.json("countries_1e5.json", function(error, data) {
    land = topojson.feature(data, data.objects.countries),
    grid = graticule();

    context.clearRect(0, 0, width, height);

    // Style sphere
    context.beginPath();
    path(sphere);
    context.fillStyle = "#000";
    context.fill();

    // Style graticule
    context.beginPath();
    path(grid);
    context.lineWidth = 0.5;
    context.strokeStyle = "#ddd";
    context.stroke();

    // Style land
    context.beginPath();
    path(land);
    context.fillStyle = "#fff";
    context.fill();
    context.strokeStyle = "#000";
    context.stroke();

    canvas.call(zoom);

    d3.select("body").append("text")
      .attr("id", "info");
    d3.select("body").append("text")
      .attr("id", "proj");
  });

  </script>
</body>
</html>