Published
Edited
Sep 1, 2019
Importers
7 stars
Insert cell
Insert cell
projection = geoAlbersUsaPr().scale(1300).translate([487.5, 305])
Insert cell
Insert cell
Insert cell
function geoAlbersUsa() {
return geoCompositeProjection(
// LOWER 48
d3.geoAlbers()
.scale(1000)
.translate([480, 250])
.clipExtent([[0, 0], [900, 500]]),
// ALASKA
d3.geoConicEqualArea()
.rotate([154, 0])
.center([-2, 58.5])
.parallels([55, 65])
.scale(350)
.translate([173, 451])
.clipExtent([[55, 370], [266, 484]]),
// HAWAII
d3.geoConicEqualArea()
.rotate([157, 0])
.center([-3, 19.9])
.parallels([8, 18])
.scale(1000)
.translate([275, 462])
.clipExtent([[266, 416], [365, 484]]),
)
}
Insert cell
function geoAlbersUsaPr() {
return geoCompositeProjection(
// LOWER 48
d3.geoAlbers()
.scale(1000)
.translate([480, 250])
.clipExtent([[0, 0], [900, 500]]),
// ALASKA
d3.geoConicEqualArea()
.rotate([154, 0])
.center([-2, 58.5])
.parallels([55, 65])
.scale(350)
.translate([173, 451])
.clipExtent([[55, 370], [266, 484]]),
// HAWAII
d3.geoConicEqualArea()
.rotate([157, 0])
.center([-3, 19.9])
.parallels([8, 18])
.scale(1000)
.translate([275, 462])
.clipExtent([[266, 416], [365, 484]]),
// PUERTO RICO
d3.geoConicEqualArea()
.rotate([66, 0])
.center([0, 18])
.parallels([8, 18])
.scale(1000)
.translate([830, 474])
.clipExtent([[800, 454], [860, 484]]),
)
}
Insert cell
Insert cell
function geoAlbersUsaPr2() {
return geoCompositeProjection(
// LOWER 48, ALASKA, HAWAII
geoAlbersUsa(),
// PUERTO RICO
d3.geoConicEqualArea()
.rotate([66, 0])
.center([0, 18])
.parallels([8, 18])
.scale(1000)
.translate([830, 474])
.clipExtent([[800, 454], [860, 484]]),
)
}
Insert cell
Insert cell
function geoCompositeProjection(...projections) {
var cache,
cacheStream,
points = Array(projections.length).fill(null),
point,
pointStream = { point: function(x, y) { point = [x, y] } };
// store the original translate(), clipExtent(), scale(), etc of each child, because
// we're going to update them later if the composite needs to be translated or scaled.
var translations = [],
clipExtents = [],
precisions = [],
scales = [];
for (var i = 0; i < projections.length; ++i) {
translations.push(projections[i].translate());
clipExtents.push(projections[i].clipExtent ? projections[i].clipExtent() : null);
precisions.push(projections[i].precision());
scales.push(projections[i].scale());
}
function projection(coordinates) {
var x = coordinates[0], y = coordinates[1];
point = null;
for (let p of points) {
p(x, y);
if (point) return point;
}
return point;
}
projection.stream = function(stream) {
if (cache && cacheStream === stream) {
return cache;
} else {
var streams = [];
for (var i = 0; i < projections.length; ++i) {
streams.push(projections[i].stream(stream));
}
cache = multiplex(streams);
cacheStream = stream;
return cache;
}
}
projection.precision = function(_) {
if (!arguments.length) return projections[0].precision();
var factor = _ / precisions[0];

for (var i = 0; i < projections.length; ++i) {
projections[i].precision(factor * precisions[i]);
}
return reset();
};

projection.scale = function(_) {
if (!arguments.length) return projections[0].scale();
var factor = _ / scales[0];
for (var i = 0; i < projections.length; ++i) {
projections[i].scale(factor * scales[i]);
}
return projection.translate(projections[0].translate());
};
projection.translate = function(_) {
if (!arguments.length) return projections[0].translate();
var x = +_[0],
y = +_[1],
k = projections[0].scale() / scales[0];
for (var i = 0; i < projections.length; ++i) {
function skew(point) {
// convert a point from the original coordinate system to the current screen-space coordinate system
// point: point in original coordinates needing to be transformed
// [x, y]: new root point
// translations[0]: old root point
// point - translations[0]: vector that needs scaling
// (point - translations[0]) * k: scaled vector
// scaled vector + [x, y] -> new screen-space coordinate
const dx = point[0] - translations[0][0],
dy = point[1] - translations[0][1];
return [x + dx * k, y + dy * k]
}
projections[i].translate(skew(translations[i]));
if (projections[i].clipExtent !== undefined && clipExtents[i] !== null) {
projections[i].clipExtent([skew(clipExtents[i][0]), skew(clipExtents[i][1])])
}
points[i] = projections[i].stream(pointStream);
}

return reset();
};
projection.clipExtent = function(_) {
if (!arguments.length) {
// compute the effective clip extent of the whole composite -- equivalent to the smallest bounding
// box that contains all the childrens' clip extents, or null if one or more children doesn't have
// a clip extent.
let bounded = true;
let x0 = +Infinity, y0 = +Infinity, x1 = -Infinity, y1 = -Infinity;

for (var i = 0; i < projections.length; ++i) {
const extent = projections[i].clipExtent ? projections[i].clipExtent() : null;

if (extent) {
x0 = Math.min(x0, extent[0][0]);
y0 = Math.min(y0, extent[0][1]);
x1 = Math.max(x1, extent[1][0]);
y1 = Math.max(y1, extent[1][1]);
} else {
bounded = false;
}
}

return bounded ? [[x0, y0], [x1, y1]] : null;
}
for (var i = 0; i < projections.length; ++i) {
// shrink each child's clip extent so that it lies within the input clip extent (_)
if (projections[i].clipExtent === undefined) continue;
let extent = projections[i].clipExtent();
if (extent === null) continue;
projections[i].clipExtent(
[[ Math.max(extent[0][0], _[0][0]), Math.max(extent[0][1], _[0][1]) ],
[ Math.min(extent[1][0], _[1][0]), Math.min(extent[1][1], _[1][1]) ]])
}
return reset();
}
function reset() {
console.log("RESET");
cache = cacheStream = null;
return projection;
}

return projection;
}
Insert cell
function multiplex(streams) {
const n = streams.length;
return {
point(x, y) { for (const s of streams) s.point(x, y); },
sphere() { for (const s of streams) s.sphere(); },
lineStart() { for (const s of streams) s.lineStart(); },
lineEnd() { for (const s of streams) s.lineEnd(); },
polygonStart() { for (const s of streams) s.polygonStart(); },
polygonEnd() { for (const s of streams) s.polygonEnd(); }
};
}
Insert cell
path = d3.geoPath(projection)
Insert cell
us = fetch("https://cdn.jsdelivr.net/npm/us-atlas@3/counties-10m.json").then(response => response.json())
Insert cell
topojson = require("topojson-client@3")
Insert cell
d3 = require("d3-geo@1")
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more