Public
Edited
Nov 12, 2022
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Object.keys(geojson.features[0].properties)
Insert cell
Insert cell
Insert cell
Insert cell
redraw = () => {
const ctx = canvas.getContext("2d")
ctx.clearRect(0, 0, width, height)
add_triangles

if (showing.indexOf("triangulation") > -1) {
wrap_timer( () => {
for (let feature of featureset.features) {
const { coords, vertices} = feature.triangles
for (let i = 0; i < vertices.length; i += 3) {
ctx.beginPath();
ctx.moveTo(...coords.slice(vertices[i]*2, vertices[i]*2+2))
ctx.lineTo(...coords.slice(vertices[i+1]*2, vertices[i+1]*2+2));
ctx.lineTo(...coords.slice(vertices[i+2]*2, vertices[i+2]*2+2));
ctx.stroke();
}
}
}, "Draw triangulation to canvas")
}
stats.number_of_points = generated_points.x_array.length
if (showing.indexOf("points") > -1) {
wrap_timer( () => {
for (let i = 0; i < generated_points.x_array.length; i++) {
ctx.fillRect(generated_points.x_array[i], generated_points.y_array[i], 1, 1)
}
}, "Draw points to canvas"
)
}
}
Insert cell
function multipolygon_to_triangles(projected) {
// When triangulated, everything is a multipolygon.
if (projected.type == "Polygon") {
return polygon_to_triangles(projected.coordinates)
}
let all_coords = []
let all_vertices = []
for (let polygon of projected.coordinates) {
const current_vertex = all_coords.length/2
const { coords, vertices } = polygon_to_triangles(polygon);
all_coords.push(...coords)
// If need to shift because we may be storing multiple triangle sets on a feature.
all_vertices.push(...vertices.map(d => d + current_vertex))
}
return {coords:all_coords, vertices:all_vertices}
}
Insert cell
geojson = d3.json(geo_url)
Insert cell
add_triangles = wrap_timer(() => {
featureset.features.map(d => d.triangles = multipolygon_to_triangles(d.geometry))
}, "Triangulate with Earcut")
Insert cell
featureset = projected_geojson
Insert cell
projected_geojson = wrap_timer(() => {
const proj = d3
.geoProjection(projection)
.fitExtent( [[10, 10], [width, height]],
geojson
)
return d3.geoProject(geojson, proj)}, "Project with d3-project")
Insert cell
stats = ({})
Insert cell
wrap_timer = (func, label) => {
const start = performance.now()
const v = func()
stats[label] = performance.now() - start
return v
}
Insert cell
Insert cell
import { earcut, polygon_to_triangles } from '@bmschmidt/a-binary-file-format-for-projected-triangulated-shapefile'
Insert cell
import {randround, random_point} from '@bmschmidt/dot-density-election-maps-with-webgl'
Insert cell
import {checkbox, slider, radio} from '@jashkenas/inputs'
Insert cell
generated_points = {
add_triangles;
const points = wrap_timer(
() => dot_density(featureset, ["Epton", "goo"], N_REPRESENTED),
"Generate random points"
);
yield points;

while (update_continuously == "yes") {
const points = wrap_timer(
() => dot_density(featureset, ["Epton"], N_REPRESENTED),
"Generate random points"
);
yield points;
}
}
Insert cell
md`## Random point code

Generates a triangulation for each feature if it isn't there already.

`
Insert cell
function dot_density(triangulated_geojson, fields, n_represented = 1) {
// Usually this can just be a number.
let targets = fields.map(f => [])
const { features } = triangulated_geojson;
// let counts_by_field = []
let total_counts = 0
let ix = 0;
for (let field of fields) {
let i = 0
for (let feature of features) {
const target = randround(feature.properties[field]/n_represented)
total_counts += target || 0
targets[ix][i++] = target || 0
}
ix++;
}
const x_array = new Float32Array(total_counts)
const y_array = new Float32Array(total_counts)
const field_array = new Array(total_counts).fill("")
const ix_array = d3.range(total_counts)
// We are going to place these points randomly.
// Important for overplotting.
d3.shuffle(ix_array)
let attempted = 0;
let overall_position = 0;
let fnum = 0
for (let feature of features) {
// Triangulate, if it hasn't been done yet.
if (!feature.triangles) {feature.triangles = multipolygon_to_triangles(feature.geometry)}
let local_targets = targets.map(d => d[fnum])
fnum += 1
const {coords, vertices} = feature.triangles;
const triangles = [];
// unpack the triangles.
for (let i = 0; i < vertices.length; i += 3) {
const a = coords.slice(vertices[i]*2, vertices[i]*2+2);
const b = coords.slice(vertices[i+1]*2, vertices[i+1]*2+2);
const c = coords.slice(vertices[i+2]*2, vertices[i+2]*2+2);
// Double area saves an op.
const double_area = Math.abs(
a[0] * (b[1] - c[1]) + b[0] * (c[1] - a[1]) + c[0] * (a[1] - b[1])
)
triangles.push({a, b, c, double_area})
}
let double_areas = d3.sum(triangles.map(d => d.double_area))
// earcut seems to always return triangles in a form where the absolute
// value isn't necessary.
for (let {a, b, c, double_area} of triangles) {
const share_of_remaining = double_area/double_areas
double_areas -= double_area
for (let f_num of d3.range(local_targets.length)) {
let how_many_points_do_i_get = randround(local_targets[f_num] * share_of_remaining)
attempted += how_many_points_do_i_get;
for (let i = 0; i < how_many_points_do_i_get; i++) {
const [x, y] = random_point(a, b, c)
const writing_to = ix_array[overall_position++]
x_array[writing_to] = x;
y_array[writing_to] = y;
field_array[writing_to] = f_num
local_targets[f_num] -= 1
}
}
}
}
return {x_array, y_array, field_array}
}
Insert cell
d3 = require("d3@v6", "d3-geo-projection")
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