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.
<!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>
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);
}