Public
Edited
Aug 21, 2024
Importers
36 stars
Also listed in…
Geo
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
g = grid(N)
Insert cell
Insert cell
Insert cell
Insert cell
points = g
.map(d3.geoRotation([36, 0, 0]))
.map(d3.geoRotation(d3.geoAirocean().rotate()).invert)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
coords = (radius) => (row, col) => [
(col - row / 2) * radius * Math.sqrt(3),
row * radius * (3 / 2)
]
Insert cell
function* triangleInner(N) {
const c = coords(radius(N));
for (let row = 1; row < N; row++)
for (let col = 1; col < row; col++) yield c(row, col);
}
Insert cell
function* triangleEdges(N) {
for (let row = 1; row <= N; row++) {
let start = 0,
end = row,
step = row;
if (row === N) {
start = 1;
end = N - 1;
step = 1;
}
for (let col = start; col <= end; col += step) yield coords(row, col);
}
}
Insert cell
function* triangleVertices(N) {
for (let [col, row] of [[0, 0], [0, N], [N, N]]) yield coords(row, col);
}
Insert cell
Insert cell
theta = atan(0.5) * degrees
Insert cell
// One of the icosahedron’s 20 equilateral triangles
triangle = ({
type: "Polygon",
coordinates: [[[0, theta], [36, -theta], [-36, -theta], [0, theta]]]
})
Insert cell
c = d3.geoCentroid(triangle)
Insert cell
tprojection = d3
.geoProjection(d3.geoGrayFullerRaw()) // d3.geoGnomonicRaw
.rotate([-c[0], -c[1]])
.center([0, triangle.coordinates[0][0][1] - c[1]])
.scale(1)
.translate([0, 0])
Insert cell
path = d3.geoPath(tprojection)
Insert cell
vertices = [[0, 90], [0, -90]].concat(
Array.from({ length: 10 }, function(_, i) {
var phi = ((i * 36 + 180) % 360) - 180;
return [phi, i & 1 ? -theta : theta];
})
)
Insert cell
bounds = path.bounds(triangle)
Insert cell
radius = (N) => (bounds[1][1] - bounds[0][1]) / N / 1.5
Insert cell
function grid(N) {
const _i = [],
_e = [],
_v = [];
const theta = atan(0.5) * degrees;
for (let h of triangleInner(N)) {
h = tprojection.invert(h);
for (let i = 0; i < 10; i++)
_i.push([h[0] + i * 36, (i % 2 ? -1 : 1) * h[1]]);
h = d3.geoRotation([0, 90 - theta, 180])(h);
for (let i = 0; i < 5; i++) _i.push([h[0] + i * 72, h[1]]);
for (let i = 0; i < 5; i++) _i.push([36 - h[0] + i * 72, -h[1]]);
}

for (let i = 0; i < vertices.length; i++) {
const h = vertices[i];
_v.push(h);
for (let j = i + 1; j < vertices.length; j++) {
const k = vertices[j];

// this test is a shortcut for listing the edges.
if (Math.abs(d3.geoDistance(h, k) - 1.1) < 0.01) {
const i = d3.geoInterpolate(h, k);
for (let n = 1; n < N; n++) _e.push(i(n / N));
}
}
}

return [_v, _e, _i].flat();
}
Insert cell
Insert cell
world = d3.json(
"https://unpkg.com/visionscarto-world-atlas@0.0.6/world/110m_land.geojson"
)
Insert cell
height = 520
Insert cell
import { atan, degrees } from "@fil/math"
Insert cell
d3 = require("d3@7", "d3-geo-projection@4", "d3-geo-polygon@1.8", "d3-geo-voronoi@2")
Insert cell
import { checkbox, select, slider } from "@jashkenas/inputs"
Insert cell
import { drag } from "@d3/versor-dragging"
Insert cell
// https://www.colourlovers.com/palette/932683
palette = d3.scaleOrdinal([0, 1, 2], ["#FF9E9D", "#3FB8AF", "#DAD8A7"])
Insert cell
projection = ({
Airocean: d3
.geoAirocean()
.rotate([-83.65929 + 1e-9, 25.44458, -87.45184]) // +1e-9 avoids a tiny bug
.fitExtent([[2, 2], [width - 2, height - 2]], { type: "Sphere" }),
"Gnomonic icosahedral": d3
.geoIcosahedral()
.faceProjection(face => {
const c = face.site.map(d => -d);
c[2] = Math.abs(c[1] - 52.62) < 1 || Math.abs(c[1] + 10.81) < 1 ? 60 : 0;
return d3
.geoProjection(d3.geoGnomonicRaw)
.rotate(c)
.translate([0, 0]);
})
.rotate([-83.65929, 25.44458, -87.45184])
.angle(60)
.fitExtent([[2, 2], [width - 2, height - 2]], { type: "Sphere" }),
"Gray-Fuller icosahedral": d3
.geoIcosahedral()
.faceProjection(face => {
var c = face.site.map(d => -d);
c[2] = Math.abs(c[1] - 52.62) < 1 || Math.abs(c[1] + 10.81) < 1 ? 60 : 0;
return d3
.geoProjection(d3.geoGrayFullerRaw())
.rotate(c)
.translate([0, 0]);
})
.rotate([-83.65929, 25.44458, -87.45184])
.angle(60)
.fitExtent([[2, 2], [width - 2, height - 2]], { type: "Sphere" }),
Orthographic: d3
.geoOrthographic()
.rotate([-110, -35])
.fitExtent([[2, 2], [width - 2, height - 2]], { type: "Sphere" }),
"Azimuthal equal-area": d3
.geoAzimuthalEqualArea()
.rotate([-110, -35])
.clipAngle(54)
.fitExtent([[2, 2], [width - 2, height - 2]], { type: "Sphere" }),
Mercator: d3
.geoMercator()
.fitExtent([[2, 2], [width - 2, height - 2]], { type: "Sphere" })
}[projname])
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