Published
Edited
Nov 27, 2020
4 forks
13 stars
Insert cell
Insert cell
function stretch(xyOut, x, y) {
// This function gets points in [0, 0.5] x [0, 1] and returns (x, y) pairs in [0, 1] x [0, 0.5]
xyOut[0] = x * 2;
xyOut[1] = y * 0.5;
}
Insert cell
function fold(xyOut, x, y) {
// This function gets points in [0, 1] x [0, 0.5], and returns (x, y) pairs in [0, 0.5] x [0, 1]
if (x < 0.5) {
// Try other mappings like (y, x)!
xyOut[0] = x;
xyOut[1] = y;
} else {
xyOut[0] = x - 0.5;
xyOut[1] = y + 0.5;
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
createREGL = require('regl')
Insert cell
drawLoop = {
let prevTime = null;
const frame = regl.frame(({ time }) => {
if (prevTime !== null) {
points.t += (time - prevTime) * speed;

// Trigger a new transition when enough time has elapsed
if (points.t > 1.0) points.next();
}
drawPoints(points);
prevTime = time;
});

// Stop the current animation when we re-evaluate this cell
invalidation.then(frame.cancel);

return frame;
}
Insert cell
points = {
// Reevaluate this cell when the restart button is clicked
restart;

// An array for positions. We'll hold onto a buffer with these positions
// unmodified so that we can determine which class (red or blue) a point
// belongs to.
const xy = new Float32Array(n * 2);

// Initialize points in the rectangle [0, 0.5] x [0, 1]
for (var i = 0; i < n; i++) {
let x = (xy[2 * i] = Math.random() * 0.5);
xy[2 * i + 1] = Math.random();
}

// Create WebGL buffers
let buffers = [0, 1, 2].map(() => regl.buffer(xy));
const output = { buffers, next, t: 0 };

// We maintain two buffers and always transition from one to the next. The transition
// has two phases, which we denote with either state = 0 or state = 1.
let state = 0;

function next() {
// Loop t in [0, 1]
output.t = output.t % 1.0;

// Swap buffer references so that we're always interpolating from 0 -> 1
let tmp = buffers[0];
buffers[0] = buffers[1];
buffers[1] = tmp;

// We insert the output into xyOut instead of a new array so that we
// don't allocate millions of new two-element Arrays every time we
// transition.
const xyOut = [0, 0];

// Update the array of data
if (state === 0) {
// In the first transition, stretch horizontally and squash vertically
for (let i = 0; i < n; i++) {
stretch(xyOut, xy[2 * i], xy[2 * i + 1]);
xy[2 * i] = xyOut[0];
xy[2 * i + 1] = xyOut[1];
}
state = 1;
} else {
for (let i = 0; i < n; i++) {
fold(xyOut, xy[2 * i], xy[2 * i + 1]);
xy[2 * i] = xyOut[0];
xy[2 * i + 1] = xyOut[1];
}
state = 0;
}

// Update the target buffer accordingly
buffers[1].subdata(xy);
}

// Delete old buffers when this cell is reevaluated
invalidation.then(() => {
buffers[0].destroy();
buffers[1].destroy();
buffers[2].destroy();
});

return output;
}
Insert cell
drawPoints = regl({
vert: `
precision highp float;
attribute vec2 xyA, xyB, xyInitial;
uniform float t, pointSize, pixelRatio;
varying vec2 vXY0;
void main () {
// Pass the initial (x, y) coordiantes to the fragment shader
vXY0 = xyInitial;

// Interpolate and transform to the range [-1, 1] x [-1, 1]:
gl_Position = vec4(mix(xyA, xyB, t) * 2.0 - 1.0, 0, 1);

gl_PointSize = pointSize * pixelRatio;
}`,
frag: `
precision highp float;
varying vec2 vXY0;
void main () {
const vec3 c1 = vec3(0.9, 0.1, 0.5);
const vec3 c2 = vec3(0.2, 0.5, 1);

// Color based on the *initial* position, vXY0
gl_FragColor = vec4(vXY0.x < 0.25 ? c1 : c2, 1);
}`,
attributes: {
xyInitial: regl.prop('buffers[2]'),
xyA: regl.prop('buffers[0]'),
xyB: regl.prop('buffers[1]')
},
uniforms: {
t: (ctx, props) => cubicInOut(props.t),
pointSize,
pixelRatio: regl.context('pixelRatio')
},
primitive: 'points',
count: n,
depth: { enable: false }
})
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