Published
Edited
Feb 4, 2022
1 fork
29 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
z = FileAttachment("gpw-v4-population-density-rev11_2020_15_min_asc.zip").zip()
Insert cell
grid = (await z.file("gpw_v4_population_density_rev11_2020_15_min.asc").text())
.split(/\r\n/)
.slice(6)
.map((d) => d.split(/ /).map(Number))
Insert cell
value = (lon, lat, val) => val
Insert cell
Insert cell
points = d3
.hexbin()
.extent([[0, 0], [width, height]])
.radius(1.8)
.centers()
.map(c => [
c[0] + 0.05 * (Math.random() - .5),
c[1] + 0.05 * (Math.random() - .5)
])
.map(c => {
const p = projection.invert(c),
c1 = projection(p);
if (Math.hypot(c1[0] - c[0], c1[1] - c[1]) < 3) return p;
})
.filter(p => p)
Insert cell
// an alternative using "better" spherical points results… doesn't look as nice
// import { points } with { N } from "@fil/gray-fuller-grid"
Insert cell
Insert cell
dots = {
for (const p of points) p[2] = 0;
const v = d3.geoDelaunay(points);
let p = 0;
for (let i = 0; i < grid.length; i++) {
const line = grid[i];
for (let j = 0; j < line.length; j++) {
const val = line[j];
if (val <= 0) continue;
p = v.find(
((j - 0.5 * 1440) * 360) / 1441,
((i - 0.5 * 720) * -180) / 721,
p
);
points[p][2] += val;
}
}
return points.filter(d => d[2] > 10);
}
Insert cell
height = Math.min(600, width)
Insert cell
function svgMap() {
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]),
path = d3.geoPath(projection);

projection.fitExtent([[2, 2], [width - 2, height - 2]], { type: "Sphere" });
function render() {
svg
.append("g")
.attr("id", "sphere")
.append("path")
.attr("fill", "none")
.attr("stroke", "#000")
.attr("stroke-width", 0.125)
.datum({ type: "Sphere" })
.attr("d", path);

svg
.append("g")
.attr("id", "world")
.append("path")
.attr("fill", "#fff")
.attr("stroke", "#000")
.attr("stroke-width", 0.125)
.datum(world)
.attr("d", path);

const contrast = .7,
max = d3.max(points, d => d[2]),
mmax = 1 + Math.floor(contrast * Math.log(max));

const groups = d3
.groups(dots, d => 1 + Math.floor(contrast * Math.log(value(...d))))
.sort((a, b) => d3.ascending(a[0], b[0]));

const g = svg.append("g").attr("id", "population");
for (const [b, pts] of groups) {
path.pointRadius(Math.pow((1.2 / mmax) * b, exp));
g.append("g")
.attr("id", `points${b}`)
.append("path")
.attr("fill", "#000")
.datum({ type: "MultiPoint", coordinates: pts })
.attr("d", path);
}
}

return svg.call(render).node();
}
Insert cell
Insert cell
count = 20000
Insert cell
N = Math.floor(Math.sqrt((count / .29 - 2) / 10))
Insert cell
projection = d3.geoBertin1953()
Insert cell
world = d3.json(
"https://unpkg.com/visionscarto-world-atlas@0.0.6/world/110m_land.geojson"
)
Insert cell
d3 = require("d3@5", "d3-array@2", "d3-geo-projection@2", "d3-geo-voronoi@1", "d3-hexbin@0")
Insert cell
import { slider } from "@jashkenas/inputs"
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more