block by micahstubbs 6e82f440f7316dec01ccf951de7ff61d

Albers USA with Puerto Rico

Full Screen

forked from Albers USA with Puerto Rico by @Harry_Stevens

Code style changes to improve legibility.


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.js

const feature = topojson.feature(states, states.objects.states_20m_2017)
const projection = d3.geoAlbersUsaPr()
const path = d3.geoPath().projection(projection)

const container = d3.select('body')
const aspect_ratio = 0.582
let width
let height

const svg = container.append('svg')

const 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) {
  const width = size[0]
  const height = size[1]

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

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

  projection.scale(s).translate(t)
}

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 src="index.js"></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 >= 0.12 && y < 0.234 && x >= -0.425 && x < -0.214
        ? alaska
        : y >= 0.166 && y < 0.234 && x >= -0.214 && x < -0.115
        ? hawaii
        : y >= 0.204 && y < 0.234 && x >= 0.32 && x < 0.38
        ? 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(_ * 0.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 - 0.455 * k, y - 0.238 * k],
        [x + 0.455 * k, y + 0.238 * k],
      ])
      .stream(pointStream).point;

    alaskaPoint = alaska
      .translate([x - 0.307 * k, y + 0.201 * k])
      .clipExtent([
        [x - 0.425 * k + epsilon, y + 0.12 * k + epsilon],
        [x - 0.214 * k - epsilon, y + 0.234 * k - epsilon],
      ])
      .stream(pointStream).point;

    hawaiiPoint = hawaii
      .translate([x - 0.205 * k, y + 0.212 * k])
      .clipExtent([
        [x - 0.214 * k + epsilon, y + 0.166 * k + epsilon],
        [x - 0.115 * k - epsilon, y + 0.234 * k - epsilon],
      ])
      .stream(pointStream).point;

    puertoRicoPoint = puertoRico
      .translate([x + 0.35 * k, y + 0.224 * k])
      .clipExtent([
        [x + 0.32 * k, y + 0.204 * k],
        [x + 0.38 * k, y + 0.234 * k],
      ])
      .stream(pointStream).point;

    return albersUsa;
  };

  return albersUsa.scale(1070);
};

prettier.config.js

module.exports = {
  semi: false,
  singleQuote: true,
};