Published
Edited
Sep 15, 2022
4 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
tracts = Array.from(points, (p) => {
for (let i = 0; i < features.features.length; i++) {
if (d3.geoContains(features.features[i], p)) return i;
}

// try and repair the few holes that were in the original geojson file
for (let i = 0; i < features.features.length; i++) {
if (d3.geoContains(features.features[i], [p[0] + 0.001, p[1]])) return i;
}
for (let i = 0; i < features.features.length; i++) {
if (d3.geoContains(features.features[i], [p[0], p[1] + 0.001])) return i;
}
})
Insert cell
classes = d3.group(d3.range(points.length), (i) => tracts[i])
Insert cell
Insert cell
voronoi = d3.geoVoronoi(points)
Insert cell
Insert cell
total = features.features.map((d) => d.properties.CY7001)
Insert cell
Insert cell
counts = d3.rollup(
points,
(v) => v.length,
(p, i) => tracts[i]
)
Insert cell
Insert cell
Insert cell
function pycnoStep(values0) {
const values1 = Float64Array.from(values0);

const neighbors = voronoi.delaunay.neighbors;

for (const [tract, I] of classes) {
if (tract === undefined) continue;

// smooth by making our value closer to the mean of our neighbors
for (const i of I) {
values1[i] +=
(d3.mean(neighbors[i], (j) => values0[j]) - values0[i]) * 0.7;
}

// enforce sums
const sum = d3.sum(I, (i) => values1[i]);
for (const i of I) {
values1[i] += ((total[tract] - sum) / I.length) * 1;
}

// clamp each point above 0 and reallocate to others in the same class
const neg = d3.group(I, (i) => values1[i] < 0);
if (neg.has(true)) {
const reallocate =
-d3.sum(neg.get(true), (i) => values1[i]) / neg.get(false).length;
for (const i of neg.get(true)) values1[i] = 0;
for (const i of neg.get(false)) values1[i] += reallocate;
}
}

return values1;
}
Insert cell
Insert cell
Insert cell
Insert cell
// applies the reallocation *n* times, and updates the map
reallocated = {
let v = init;
for (let i = 0; i < steps; i++) {
v = pycnoStep(v);
}
pycnoMap.update(v);
return v;
}
Insert cell
error = Math.round(
d3.sum(
d3.rollup(
reallocated,
(v) => d3.sum(v),
(d, i) => tracts[i]
),
([tract, sum]) => Math.abs(total[tract] - sum)
)
)
Insert cell
Insert cell
points = Array.from(pick2d(width, height, 10000, gridtype), projection.invert)
Insert cell
color = d3
.scaleSequential(d3.interpolateCividis)
.domain(d3.extent(total.map((d, i) => d / classes.get(i).length)))
Insert cell
d3 = require("d3@7", "d3-geo-voronoi@1.6")
Insert cell
features = FileAttachment("annarbor.geojson").json()
Insert cell
projection = d3
.geoMercator()
.fitExtent([[20, 20], [width - 20, height - 20]], features)
Insert cell
height = 600
Insert cell
import { pick2d } from "@fil/2d-point-distributions"
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