Public
Edited
Mar 31, 2024
Importers
26 stars
Insert cell
Insert cell
Insert cell
dimensions = ["r", "g", "b"]
Insert cell
randomDatum = () =>
Object.fromEntries(dimensions.map(d => [d, Math.random() * 255]))
Insert cell
data = Array.from({ length: 3000 }, randomDatum)
Insert cell
distance = (a, b) => Math.hypot(...dimensions.map(d => a[d] - b[d]))
Insert cell
Insert cell
neurons = Array.from({ length: 308 }, randomDatum, replay)
Insert cell
Insert cell
positions = neurons.map(randomPoint) // see implementation below
Insert cell
voronoi = d3.Delaunay.from(positions).voronoi([0, 0, width, height])
Insert cell
// Any function or generator that lists the neighbors of a node is good to use
// see https://observablehq.com/@d3/voronoi-neighbors
neighbors = i => voronoi.neighbors(i)
Insert cell
Insert cell
bmu = d => d3.leastIndex(neurons, u => distance(u, d))
Insert cell
Insert cell
learning_rate = .1
Insert cell
function step(i) {
const d = data[i];

// find the BMU and update it
const u = bmu(d);
update_neuron(u, d, learning_rate);

// update its neighbors too, more gently
for (const n of neighbors(u)) update_neuron(n, d, .25 * learning_rate);

return u;
}
Insert cell
function update_neuron(u, d, alpha) {
const a = neurons[u];
for (const i of dimensions) a[i] += alpha * (d[i] - a[i]);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function* run() {
const t = 1 / 200, // sampling for change%
target = 0.05; // target change%
let change = 1,
i = 0;
do {
// select a data point to update
const n = i++ % data.length;
const u = step(n);

// log the number of unhappy data points
change = (change + t * (u !== bmus[n])) / (1 + t);
bmus[n] = u;

// draw this step (progressive time)
if (Math.sqrt(i) % 1 === 0) {
draw();
yield `step ${i}, change rate: ${(change * 100).toFixed(1)}%`;
}

// stop when less than 5% of the recently examined data points are unhappy
} while (change > target && i < 100000);

draw();
yield `stopped after ${i} steps, change: ${(change * 100).toFixed(1)}%`;
}
Insert cell
Insert cell
Insert cell
Insert cell
randomPoint = {
const positions = [...pick2d(width + 30, height, neurons.length, topology)];
return (_, i) => [
positions[i % positions.length][0],
positions[i % positions.length][1] + Math.random()
];
}
Insert cell
bmus = (neurons, Int32Array.from(data).fill(-1))
Insert cell
mutable replay = -1 // ensure neurons, positions, bmus are reset
Insert cell
(show, draw()) // redraw the map when the color selector is clicked
Insert cell
height = 600
Insert cell
import { pick2d } from "@fil/2d-point-distributions"
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