Public
Edited
Sep 15, 2024
Importers
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Object.keys(geojson.features[0].properties)
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 '346dde0b66c2cf3b'
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, ["POP_EST", "goo"], N_REPRESENTED),
"Generate random points"
)
yield points
while (update_continuously == "yes") {
const points = wrap_timer(() => dot_density(featureset, ["POP_EST"], 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
Insert cell
d3 = require("d3@v6", "d3-geo-projection")
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