Public
Edited
Aug 10, 2022
Importers
14 stars
Hello, A5Chandrupatla’s root-finding methodSidi’s root-finding methodRegular numbersDruidJS workerNatural breaksDistance to a segmentRay out of a convex hullWord Tour: 40k words and their friendsHello, @thi.ng/grid-iteratorsHead/tail breaksPseudo-blue noise shaderHow fast does walk-on-spheres converge?AoC 12: shortest path under constraintsKDE estimationPlot: Correlation heatmapPoisson Finish 2Poisson disk sampling functionsWoS with transportSimple and surprising sortLocal medianTime series topological subsamplingUnion-FindLevel set experiment 1Mean value coordinates
Poisson potential
Middle-squareWorld of squares (spherical)World of squaresLargest Inscribed SquareHello, PyWaveletsGeothmetic meandianHello, Reorder.jsGeometric MedianImage FFTTransport to a mapDisc TransportTP3: Power Diagram and Semi-Discrete Optimal TransportThe blue waveHello, genetic-jsSliced Optimal TransportDruidJSSelf-Organizing Maps meet DelaunayHello, polygon-clippingseedrandom, minimalWalk on Spheres 2Walk on SpheresHello, AutoencoderKaprekar’s numberVoronoiMap2DHello, ccwt.jsPolygon TriangulationQuantile.invert?Linear congruential generatorHue blurNeedle in a haystackMoving average blurApollo 11 implementation of trigonometric functions, by Margaret H. Hamilton (march 1969)2D curves intersectionThe 2D approximate Newton-Raphson methodInverting Lee’s Tetrahedral projectionLinde–Buzo–Gray stipplingMean shift clustering with kd-tree2D point distributionsShortest pathKahan SummationHello, delatinDijkstra’s algorithm in gpu.jsLloyd’s relaxation on a graphManhattan DiameterManhattan VoronoiMobility landscapes — an introductionDijkstra’s shortest-path treeH3 odditiesProtein MatrixConvex Spectral WeightsSort stuff by similarityKrigingDelaunay.findTrianglen-dimensions binning?Travelling with a self-organizing mapUMAP-o-MaticMNIST & UMAP-jsHello UMAP-jsMean shift clusteringLevenshtein transitionRd quasi-random sequencesAutomated label placement (countries)Phyllotaxis explainedMotionrugsPlanar hull (Andrew’s monotone chain algorithm)South Africa’s medial axisTravelling salesperson approximation with t-SNEDistance to shoreWorkerngraph: pagerank, louvain…t-SNE VoronoiCloud ContoursCircular function drawingKruskal MazeMyceliumTravelling salesperson approximation on the globe, with t-SNEtsne.jstsne.js & worker
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function potential(
graph,
charges,
{ alpha = 0.3, precision = 1e-8, maxSteps = 20000 } = {}
) {
const n = charges.length;
const D = new Float64Array(n); // differences
const A = new Float64Array(n);
const q = d3.mean(charges);

// normalize the flow by the count of valid neighbors
const nneighbors = new Uint16Array(n);
for (let s = 0; s < graph.sources.length; s++) {
const i = graph.sources[s];
const j = graph.targets[s];
if (!isNaN(charges[j])) nneighbors[i]++;
if (!isNaN(charges[i])) nneighbors[j]++;
}

// impedance normalization
const conductance = graph.costs
? Float32Array.from(graph.costs, (d) => 1 / d)
: null;

const Q = charges.map(
(d, i) =>
alpha *
(d - q) *
(!nneighbors[i]
? 0 // special case isolated points
: 1 / nneighbors[i])
);
const E = precision * A.length;

let steps = 0,
error,
c;

const { sources, targets } = graph;

do {
// add charges
for (let i = 0; i < D.length; i++) D[i] = Q[i];

// diffuse
for (let s = 0; s < sources.length; s++) {
const i = sources[s];
const j = targets[s];

// let d = alpha * (A[j] - A[i]); // Jacobi
let d = alpha * (D[j] + A[j] - D[i] - A[i]); // SOR
if (conductance) d *= conductance[s];

if (!isNaN(d)) {
D[i] += d;
D[j] -= d;
}
}

for (let i = 0; i < D.length; i++) A[i] += D[i];

// measure change
error = d3.sum(D, (d) => d * d);
steps++;
} while (error > E && steps < maxSteps);

return Object.assign(A, { steps, error, E });
}
Insert cell
Insert cell
Insert cell
P = potential(graph, C)
Insert cell
Insert cell
Insert cell
points = [...pick2d(width, height, npoints, distributionType)]
Insert cell
C = {
const C = new Float32Array(points.length);

for (const i of pointIndex.slice(0, sources)) {
const v = 1 + (!sourcesOnly && i % 2 ? -3 : 1);
C[i] += v;
graph.sources.forEach((k, s) => {
let j = graph.targets[s];
if (j === i) (j = k), (k = i);
if (k === i) C[j] += v * 0.6;
});
}

return C;
}
Insert cell
C.filter((d) => d)
Insert cell
pointIndex = d3.shuffle(d3.range(points.length))
Insert cell
// the voronoi partition defines the graph *and* is useful to draw the chart
voronoi = d3.Delaunay.from(points).voronoi([0, 0, width, height])
Insert cell
graph = {
let links = d3
.range(points.length)
.flatMap((i) => Array.from(voronoi.neighbors(i), (j) => [i, j]));

// on the square grid, remove diagonals
if (distributionType === "grid") {
let stride;
points.find((d, i) => d[0] > 0 && (stride = i));
links = links.filter(([i, j]) => [1, stride].includes(Math.abs(i - j)));
}

return {
sources: Uint16Array.from(links, ([i]) => i),
targets: Uint16Array.from(links, ([, j]) => j)
// costs (defaults to ...1)
};
}
Insert cell
import { pick2d } from "@fil/2d-point-distributions"
Insert cell
Insert cell
function fade(context, alpha) {
const { globalAlpha, globalCompositeOperation } = context;
context.globalCompositeOperation = "destination-out";
context.fillStyle = "white";
context.globalAlpha = alpha || 1;
context.fillRect(0, 0, context.canvas.width, context.canvas.height);
context.globalAlpha = globalAlpha;
context.globalCompositeOperation = globalCompositeOperation;
}
Insert cell
nodes = {
const random = d3.randomNormal(0, 1000);
return Array.from({ length: Math.min(width * 10, npoints * 2) }, () => ({
x: width / 2 + random(),
y: height / 2 + random()
}));
}
Insert cell
Insert cell
viewof image0 = {
const V = new Float32Array(width * height).fill(NaN);

// fill all triangles, interpolate via barycentric coordinates
const delaunay = d3.Delaunay.from(points);
for (let i = 0; i < delaunay.triangles.length; i += 3) {
const [a, b, c] = delaunay.triangles.slice(i, i + 3);
const [pa, pb, pc] = Array.from([a, b, c], (i) => P[i]);
if (isNaN(pa) || isNaN(pb) || isNaN(pc)) continue;

const [Ax, Bx, Cx] = Array.from([a, b, c], (i) => points[i][0]);
const [Ay, By, Cy] = Array.from([a, b, c], (i) => points[i][1]);
const [x0, x1] = d3.extent([Ax, Bx, Cx]);
const [y0, y1] = d3.extent([Ay, By, Cy]);

const z = (By - Cy) * (Ax - Cx) + (Ay - Cy) * (Cx - Bx);
if (!z) continue;

for (let x = Math.floor(x0); x < x1; x++) {
for (let y = Math.floor(y0); y < y1; y++) {
if (x < 0 || x >= width || y < 0 || y >= height) continue;
const ga = ((By - Cy) * (x - Cx) + (y - Cy) * (Cx - Bx)) / z;
const gb = ((Cy - Ay) * (x - Cx) + (y - Cy) * (Ax - Cx)) / z;
const gc = 1 - ga - gb;
if (ga >= 0 && gb >= 0 && gc >= 0)
V[x + width * y] = ga * pa + gb * pb + gc * pc;
}
}
}

// fill the missing parts with the value of the closest dot
const values = points
.map((d, i) => [...d, P[i]])
.filter(([, , p]) => !isNaN(p));
const quad = d3.quadtree().addAll(values);
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
if (isNaN(V[x + width * y])) V[x + width * y] = quad.find(x, y)[2];
}
}

return heatmap(V, {
width,
color: sourcesOnly ? d3.interpolateInferno : d3.interpolateRdYlBu
});
}
Insert cell
import { heatmap } from "@fil/heatmap"
Insert cell
Insert cell
B = require("array-blur")
Insert cell
viewof image1 = heatmap(B.blur().radius(blurRadius).width(width)(image0), {
width,
color: sourcesOnly ? d3.interpolateInferno : d3.interpolateRdYlBu
})
Insert cell
contours = d3.contours().size([width, height])(image1)
Insert cell
// filtered contours
isolines = contours.map(({ value, coordinates }) => ({
type: "MultiLineString",
value,
coordinates: coordinates.flatMap((polygon) =>
polygon.flatMap((ring) => {
const lines = [];
let line = [];
let i;
for (const p of ring) {
i = voronoi.delaunay.find(...p, i);
if (isNaN(P[i])) {
if (line.length > 1) {
lines.push(line);
line = [];
}
} else line.push(p);
}
if (line.length > 1) lines.push(line);
return lines;
})
)
}))
Insert cell
function grad(p, image) {
let x = Math.floor(p[0]),
y = Math.floor(p[1]);
if (x < 0) x = 0;
if (y < 0) y = 0;
if (x === width - 1) x--;
if (y === height - 1) y--;
return [
image[x + width * y] - image[x + 1 + width * y],
image[x + width * y] - image[x + width * (y + 1)]
];
}
Insert cell
gradient = points.map((d, i) => (isNaN(P[i]) ? null : grad(d, image1)))
Insert cell
Insert cell
streaklines = () => {
const visited = new Uint8Array(points.length);

let maxstep = 200000;

const streaklines = [];
for (const i of d3.shuffle(d3.range(points.length))) {
// already visited? drop
if (visited[i]) continue;

const streakline = [[...points[i], 0]];
visited[i] = 1; // logically useless but clearer

let delta;
let x;
let y;
let j = i;
let vx, vy, dx, dy;

for (const direction of [-1, 1]) {
x = points[i][0];
y = points[i][1];
vx = 0;
vy = 0;
do {
dx = vx;
dy = vy;
j = voronoi.delaunay.find(x, y, j);
if (isNaN(P[j])) break;
visited[j] = 1;
[vx, vy] = grad([x, y], image1).map((d) => d * direction);
const delta = Math.hypot(vx, vy);
x += vx / delta;
y += vy / delta;
streakline.push([x, y]);
} while (
vx * dx + vy * dy >= 0 &&
//delta > 1e-12 &&
x > 0 &&
y > 0 &&
x < width &&
y < height &&
maxstep-- > 0
);
streakline.reverse();
}

streaklines.push(streakline);
}

return streaklines;
}

Insert cell
height = 600
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