block by HarryStevens 0e440b73fbd88df7c6538417481c9065

Albers USA with Puerto Rico

Full Screen

Create a resizable map with an Albers USA projection that includes Puerto Rico. Adapted from this block but compatiable with D3 v4 & v5 and including a fitSize() function to help with resizing.

index.html

<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      margin: 0;
    }
    .state {
      fill: #ccc;
      stroke: #fff;
    }
  </style>
</head>
<body>
  <script src="https://d3js.org/d3-selection.v1.min.js"></script>
  <script src="https://d3js.org/d3-array.v1.min.js"></script>
  <script src="https://d3js.org/d3-geo.v1.min.js"></script>
  <script src="https://unpkg.com/topojson@3.0.2/dist/topojson.min.js"></script>
  <script src="albers-usa-pr.js"></script>
  <script src="states.js"></script>
  <script>
    var feature = topojson.feature(states, states.objects.states_20m_2017);
    var projection = d3.geoAlbersUsaPr(),
        path = d3.geoPath().projection(projection);

    var container = d3.select("body");
    var aspect_ratio = 0.582,
        width,
        height;
    
    var svg = container.append("svg");

    var paths_states = svg.selectAll(".state")
        .data(feature.features)
      .enter().append("path")
        .attr("class", "state");

    draw();

    window.addEventListener("resize", draw);

    function draw(){
      width = container.node().getBoundingClientRect().width;
      height = width * aspect_ratio > window.innerHeight ? window.innerHeight : width * aspect_ratio;
      svg
          .attr("width", width)
          .attr("height", height);
      fitSize([width, height], feature);
      paths_states.attr("d", path);
    }

    function fitSize(size, object){
      var width = size[0],
          height = size[1];

      projection
          .scale(1)
          .translate([0, 0]);

      var b = path.bounds(object),
          s = 1 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
          t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];

      projection
          .scale(s)
          .translate(t);
    }
  </script>
</body>
</html>

albers-usa-pr.js

d3.geoAlbersUsaPr = function() {
  var epsilon = 1e-6;

  var lower48 = d3.geoAlbers();

  // EPSG:3338
  var alaska = d3.geoConicEqualArea()
      .rotate([154, 0])
      .center([-2, 58.5])
      .parallels([55, 65]);

  // ESRI:102007
  var hawaii = d3.geoConicEqualArea()
      .rotate([157, 0])
      .center([-3, 19.9])
      .parallels([8, 18]);

  // XXX? You should check that this is a standard PR projection!
  var puertoRico = d3.geoConicEqualArea()
      .rotate([66, 0])
      .center([0, 18])
      .parallels([8, 18]);

  var point,
      pointStream = {point: function(x, y) { point = [x, y]; }},
      lower48Point,
      alaskaPoint,
      hawaiiPoint,
      puertoRicoPoint;

  function albersUsa(coordinates) {
    var x = coordinates[0], y = coordinates[1];
    point = null;
    (lower48Point(x, y), point)
        || (alaskaPoint(x, y), point)
        || (hawaiiPoint(x, y), point)
        || (puertoRicoPoint(x, y), point);
    return point;
  }

  albersUsa.invert = function(coordinates) {
    var k = lower48.scale(),
        t = lower48.translate(),
        x = (coordinates[0] - t[0]) / k,
        y = (coordinates[1] - t[1]) / k;
    return (y >= .120 && y < .234 && x >= -.425 && x < -.214 ? alaska
        : y >= .166 && y < .234 && x >= -.214 && x < -.115 ? hawaii
        : y >= .204 && y < .234 && x >= .320 && x < .380 ? puertoRico
        : lower48).invert(coordinates);
  };

  // A naïve multi-projection stream.
  // The projections must have mutually exclusive clip regions on the sphere,
  // as this will avoid emitting interleaving lines and polygons.
  albersUsa.stream = function(stream) {
    var lower48Stream = lower48.stream(stream),
        alaskaStream = alaska.stream(stream),
        hawaiiStream = hawaii.stream(stream),
        puertoRicoStream = puertoRico.stream(stream);
    return {
      point: function(x, y) {
        lower48Stream.point(x, y);
        alaskaStream.point(x, y);
        hawaiiStream.point(x, y);
        puertoRicoStream.point(x, y);
      },
      sphere: function() {
        lower48Stream.sphere();
        alaskaStream.sphere();
        hawaiiStream.sphere();
        puertoRicoStream.sphere();
      },
      lineStart: function() {
        lower48Stream.lineStart();
        alaskaStream.lineStart();
        hawaiiStream.lineStart();
        puertoRicoStream.lineStart();
      },
      lineEnd: function() {
        lower48Stream.lineEnd();
        alaskaStream.lineEnd();
        hawaiiStream.lineEnd();
        puertoRicoStream.lineEnd();
      },
      polygonStart: function() {
        lower48Stream.polygonStart();
        alaskaStream.polygonStart();
        hawaiiStream.polygonStart();
        puertoRicoStream.polygonStart();
      },
      polygonEnd: function() {
        lower48Stream.polygonEnd();
        alaskaStream.polygonEnd();
        hawaiiStream.polygonEnd();
        puertoRicoStream.polygonEnd();
      }
    };
  };

  albersUsa.precision = function(_) {
    if (!arguments.length) return lower48.precision();
    lower48.precision(_);
    alaska.precision(_);
    hawaii.precision(_);
    puertoRico.precision(_);
    return albersUsa;
  };

  albersUsa.scale = function(_) {
    if (!arguments.length) return lower48.scale();
    lower48.scale(_);
    alaska.scale(_ * .35);
    hawaii.scale(_);
    puertoRico.scale(_);
    return albersUsa.translate(lower48.translate());
  };

  albersUsa.translate = function(_) {
    if (!arguments.length) return lower48.translate();
    var k = lower48.scale(), x = +_[0], y = +_[1];

    lower48Point = lower48
        .translate(_)
        .clipExtent([[x - .455 * k, y - .238 * k], [x + .455 * k, y + .238 * k]])
        .stream(pointStream).point;

    alaskaPoint = alaska
        .translate([x - .307 * k, y + .201 * k])
        .clipExtent([[x - .425 * k + epsilon, y + .120 * k + epsilon], [x - .214 * k - epsilon, y + .234 * k - epsilon]])
        .stream(pointStream).point;

    hawaiiPoint = hawaii
        .translate([x - .205 * k, y + .212 * k])
        .clipExtent([[x - .214 * k + epsilon, y + .166 * k + epsilon], [x - .115 * k - epsilon, y + .234 * k - epsilon]])
        .stream(pointStream).point;

    puertoRicoPoint = puertoRico
        .translate([x + .350 * k, y + .224 * k])
        .clipExtent([[x + .320 * k, y + .204 * k], [x + .380 * k, y + .234 * k]])
        .stream(pointStream).point;

    return albersUsa;
  };

  return albersUsa.scale(1070);
}