Published
Edited
Feb 2, 2020
24 stars
Insert cell
Insert cell
Insert cell
Insert cell
function* render(options = {}) {
const {
size: s = 600, // Canvas width and height
pad = -.1, // Padding factor
seed = 'b', // PRNG seed
count = 500, // Number of random points
duration = 4000, // Loop duration in ms
gif = false,
invalidation: invalidate = invalidation, // Render as GIF
} = options;
const p = s * pad, sp = s - (p * 2);
const rng = seedrandom(seed);
// Add corner points.
const coords = [p,p, s-p,p, p,s-p, s-p,s-p];
for(let n = 0; n < count; n++) coords.push(p+sp*rng(), p+sp*rng());
const delaunay = new Delaunator(new Float64Array(coords));
// Inflate triangle indices to coordinates to avoid lookups later on.
const offsets = new Float64Array(delaunay.triangles.length * 2);
for(let i = 0; i < delaunay.triangles.length; i++) {
offsets[i*2 ] = delaunay.coords[2*delaunay.triangles[i]];
offsets[i*2+1] = delaunay.coords[2*delaunay.triangles[i]+1];
}
const ctx = DOM.context2d(s, s, 1);
ctx.canvas.style.maxWidth = '100%';
if(gif) return yield* renderGif(invalidate, drawFrame, duration, {
// Prepares canvas and returns a reference.
preview: drawFrame(0),
// We could include other parameters here (e.g. some options).
filename: `delaunator-shards-${Date.now()/1000|0}`,
fps: 40,
});

// Live loop.
const to = Date.now();
while(true) {
yield drawFrame(((Date.now()-to)/duration) % 1);
}
// Render callback, draws animation at timestep t [0..1].
function drawFrame(t) {
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, s, s);
// Change direction.
t = 1-t;
// Loop over all triangles in the order in which they were
// produced by Delaunator.
const o = offsets;
for(let i = 0; i < o.length; i += 6) {
const
// Local easing offset.
ti = (((i / o.length) ** .1) * 2 + t) % 1,
// Scale easing.
_t = clamp(0, 1, 1.5 * easeCos(ti ** 4 + .4)),
// Triangle points.
x0 = o[i ], y0 = o[i+1],
x1 = o[i+2], y1 = o[i+3],
x2 = o[i+4], y2 = o[i+5],
// Triangle clones with transform origins and lightness.
origins = [
[x0, y0, .3],
[x1, y1, .5],
[x2, y2, 1],
];
// Draw one triangle for each origin.
for(let [xc, yc, l] of origins) {
ctx.fillStyle = `hsl(0,0%,${_t * l * 100}%)`;
ctx.beginPath();
// Scale towards origin.
ctx.moveTo(mix(xc, x0, _t), mix(yc, y0, _t));
ctx.lineTo(mix(xc, x1, _t), mix(yc, y1, _t));
ctx.lineTo(mix(xc, x2, _t), mix(yc, y2, _t));
ctx.closePath();
ctx.fill();
}
}
return ctx.canvas;
}
}
Insert cell
Insert cell
function mix(a, b, t) {
return a + t * (b - a);
}
Insert cell
function clamp(a, b, v) {
return v < a ? a : v > b ? b : v;
}
Insert cell
function easeCos(t) {
return Math.cos(t * Math.PI * 2) * .5 + .5;
}
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