Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active June 1, 2020 04:23
Show Gist options
  • Save mbostock/5629120 to your computer and use it in GitHub Desktop.
Save mbostock/5629120 to your computer and use it in GitHub Desktop.
AlbersUSA + PR
license: gpl-3.0
redirect: https://observablehq.com/@d3/u-s-map-with-puerto-rico

A modified version of d3.geo.albersUsa that includes Puerto Rico.

// A modified d3.geo.albersUsa to include Puerto Rico.
function albersUsaPr() {
var ε = 1e-6;
var lower48 = d3.geo.albers();
// EPSG:3338
var alaska = d3.geo.conicEqualArea()
.rotate([154, 0])
.center([-2, 58.5])
.parallels([55, 65]);
// ESRI:102007
var hawaii = d3.geo.conicEqualArea()
.rotate([157, 0])
.center([-3, 19.9])
.parallels([8, 18]);
// XXX? You should check that this is a standard PR projection!
var puertoRico = d3.geo.conicEqualArea()
.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 + ε, y + .120 * k + ε], [x - .214 * k - ε, y + .234 * k - ε]])
.stream(pointStream).point;
hawaiiPoint = hawaii
.translate([x - .205 * k, y + .212 * k])
.clipExtent([[x - .214 * k + ε, y + .166 * k + ε], [x - .115 * k - ε, y + .234 * k - ε]])
.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);
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>
rect {
fill: none;
pointer-events: all;
}
.sphere {
fill: none;
stroke: #ccc;
shape-rendering: crispEdges;
}
.graticule {
fill: none;
stroke: #777;
stroke-opacity: .2;
}
.mesh {
fill: none;
stroke: #000;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script src="albers-usa-pr.js"></script>
<script>
var width = 960,
height = 500;
var projection = albersUsaPr()
.scale(1070)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var graticule = d3.geo.graticule()
.step([2, 2]);
svg.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("path")
.datum({type: "Sphere"})
.attr("class", "sphere")
.attr("d", path);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
svg.on("mousemove", function() {
console.log(projection.invert(d3.mouse(this)));
});
d3.json("/mbostock/raw/4090846/us.json", function(error, us) {
if (error) throw error;
svg.append("path")
.datum(topojson.mesh(us))
.attr("class", "mesh")
.attr("d", path);
});
</script>
@mikemellor11
Copy link

Is there a way to get the fitExtent && fitSize methods included in this implementation? I'm not quite sure how to go about binding those.

@CrHvG
Copy link

CrHvG commented Feb 11, 2020

will there be an R version?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment