Public
Edited
Nov 13, 2023
2 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
dots = genDots(data)
Insert cell
dots2 = genDots2(data, peoplePerDot)
Insert cell
Insert cell
getProjectedSegments = function (polygon) {
return turf
.lineSegment(polygon)
.features.map((f) => f.geometry.coordinates.map(projection));
}
Insert cell
function genDots(geo) {
// This function is based on code by Ahmad Barclay
let points = { type: "FeatureCollection", features: [] };
for (let polygon of geo.features) {
let bbox = turf.bbox(polygon);
let count = Math.round(polygon.properties.pop / peoplePerDot);
let i = 1;
while (i <= count) {
let point = randomPointInPolygon(polygon, bbox);
point.properties.id =
polygon.properties.item + i.toString().padStart(3, "0");
points.features.push(point);
i++;
}
}
// Shuffle points to avoid plotting one colour on top of the others where there are overlaps
d3.shuffle(points.features);
return points;
}
Insert cell
function genDots2(geo, peoplePerDot) {
let points = { type: "FeatureCollection", features: [] };
for (let polygon of geo.features) {
let bbox = turf.bbox(polygon);
let lineSegments = getProjectedSegments(polygon).map(
([[x0, y0], [x1, y1]]) => ({
coords: [
[x0, y0],
[x1, y1]
],
xmin: Math.min(x0, x1),
xmax: Math.max(x0, x1),
ymin: Math.min(y0, y1),
ymax: Math.max(y0, y1)
})
);
let count = Math.round(polygon.properties.pop / peoplePerDot);
let randomPoints = [];
for (let i = 0, end = count * effort * 2; i < end; i++) {
let unprojectedP = randomPointInPolygon(polygon, bbox);
let p = projectPoint(unprojectedP);
randomPoints.push({
unprojectedP,
p,
d: distanceToNearestSegment(p, lineSegments)
});
}
let i = 1;
while (i <= count) {
let bestIndex = indexOfBest(randomPoints);
let point = randomPoints[bestIndex];

// remove point from randomPoints
randomPoints[bestIndex] = randomPoints[randomPoints.length - 1];
randomPoints.pop();

updateDistances(randomPoints, point.p);

point.unprojectedP.properties.id =
polygon.properties.item + i.toString().padStart(3, "0");
points.features.push(point.unprojectedP);
i++;
}
}
// Shuffle points to avoid plotting one colour on top of the others where there are overlaps
d3.shuffle(points.features);
return points;
}
Insert cell
function indexOfBest(points) {
let bestIndex = 0;
let bestDist = points[0].d;
for (let i = 1; i < points.length; i++) {
if (points[i].d > bestDist) {
bestDist = points[i].d;
bestIndex = i;
}
}
return bestIndex;
}
Insert cell
// Based on https://stackoverflow.com/a/1501725/3347737
function pointToLineDistance(p, [v, w]) {
function dist2(v, w) {
let dx = v[0] - w[0];
let dy = v[1] - w[1];
return dx * dx + dy * dy;
}
function distToSegmentSquared(p, v, w) {
var l2 = dist2(v, w);
if (l2 == 0) return dist2(p, v);
var t =
((p[0] - v[0]) * (w[0] - v[0]) + (p[1] - v[1]) * (w[1] - v[1])) / l2;
t = Math.max(0, Math.min(1, t));
return dist2(p, [v[0] + t * (w[0] - v[0]), v[1] + t * (w[1] - v[1])]);
}
return Math.sqrt(distToSegmentSquared(p, v, w));
}
Insert cell
function distanceToNearestSegment(point, segments) {
let nearest = Infinity;
for (let segment of segments) {
// Do a few quick checks to try to avoid an expensive pointToLineDistance() call
if (point[0] >= segment.xmax + nearest) continue;
if (point[1] >= segment.ymax + nearest) continue;
if (point[0] <= segment.xmin - nearest) continue;
if (point[1] <= segment.ymin - nearest) continue;
let d = pointToLineDistance(point, segment.coords) * edgeSpacing;
if (d < nearest) nearest = d;
}
return nearest;
}
Insert cell
function updateDistances(points, point) {
for (let p of points) {
let dx = point[0] - p.p[0];
let dy = point[1] - p.p[1];
if (Math.abs(dx) >= p.d || Math.abs(dy) >= p.d) continue; // a little optimisation
let d = Math.hypot(dx, dy);
if (d < p.d) p.d = d;
}
}
Insert cell
function randomPointInPolygon(polygon, bbox) {
let point;
do {
point = turf.randomPoint(1, { bbox: bbox }).features[0];
} while (!turf.booleanPointInPolygon(point.geometry.coordinates, polygon));
return point;
}
Insert cell
function idToColourIndex(id, numberOfColours) {
// This is just a made-up function to assign some pretty colours
if (numberOfColours === 1) return 0;
let result = +id[id.length - 4] % numberOfColours;
if (id[id.length - 1] > 6) {
result = (result + +id[id.length - 1]) % numberOfColours;
}
return result;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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