Public
Edited
Aug 25, 2023
8 forks
89 stars
Insert cell
Insert cell
Insert cell
viewof regl = reglCanvas(this, {
width,
height: Math.max(600, width * 0.6),
extensions: ["ANGLE_instanced_arrays"],
attributes: { antialias: false, depth: false }
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
loop = {
while (true) {
regl.poll();
regl.clear({ color: [0.05, 0.05, 0.05, 1] });
draw();
yield;
}
}
Insert cell
Insert cell
circleInstanceGeometry = Array.from(Array(numCircleDivisions + 1).keys()).map(i => {
var theta = Math.PI * 2 * i / numCircleDivisions;
return [Math.cos(theta), Math.sin(theta)];
});
Insert cell
Insert cell
instanceTheta = Array.from(Array(numCircleInstances).keys()).map(i =>
i / numCircleInstances * 2 * Math.PI
);
Insert cell
Insert cell
draw = regl({
vert: `
precision highp float;
attribute float theta;
attribute vec2 circlePoint;
varying vec3 vColor;
uniform vec2 aspectRatio;
uniform float time;
const float PI = 3.1415926535;
void main () {
// Use lots of sines and cosines to place the circles
vec2 circleCenter = vec2(cos(theta), sin(theta))
* (0.6 + 0.2 * cos(theta * 6.0 + cos(theta * 8.0 + time)));

// Modulate the circle sizes around the circle and in time
float circleSize = 0.2 + 0.12 * cos(theta * 9.0 - time * 2.0);

vec2 xy = circleCenter + circlePoint * circleSize;

// Define some pretty colors
float th = 8.0 * theta + time * 2.0;
vColor = 0.6 + 0.4 * vec3(
cos(th),
cos(th - PI / 3.0),
cos(th - PI * 2.0 / 3.0)
);

gl_Position = vec4(xy / aspectRatio, 0, 1);
}`,
frag: `
precision highp float;
varying vec3 vColor;
uniform float alpha;
void main () {
gl_FragColor = vec4(vColor, alpha);
}`,
attributes: {
// This attribute defines what we draw; we fundamentally draw circle vertices
circlePoint: circleInstanceGeometry,
// This attribute allows us to compute where we draw each circle. the divisor
// means we step through one value *per circle*.
theta: {buffer: instanceTheta, divisor: 1},
},
uniforms: {
// Scale so that it fits in the view whether it's portrait or landscape:
aspectRatio: ctx => ctx.framebufferWidth > ctx.framebufferHeight ?
[ctx.framebufferWidth / ctx.framebufferHeight, 1] :
[1, ctx.framebufferHeight / ctx.framebufferWidth],
time: regl.context('time'),
// Decrease opacity when there are more circles
alpha: Math.max(0, Math.min(1, 0.15 * 2000 / numCircleInstances)),
},
blend: {
// Additive blending
enable: true,
func: {srcRGB: 'src alpha', srcAlpha: 1, dstRGB: 1, dstAlpha: 1},
equation: {rgb: 'add', alpha: 'add'}
},
// GL_LINES are in general *pretty bad*, but they're good for some things
primitive: 'line strip',
depth: {enable: false},
count: numCircleDivisions + 1,
instances: numCircleInstances,
})
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