Published
Edited
Nov 20, 2019
2 forks
20 stars
Insert cell
Insert cell
canvas = {
const context = DOM.context2d(width, height);
const canvas = context.canvas;
canvas.style.width = "100%";
canvas.style.maxWidth = "720px";
canvas.style.display = "block";
canvas.style.margin = "auto";
context.lineCap = "square";
context.fillStyle = "#fff";
while (true) {
const t = Date.now() / 20000;
context.save();
context.fillRect(0, 0, width, height);
context.translate(width / 2, height / 2 + 28);
knots(context, t, N * 2 / 9, N * 3 / 9, 0);
knots(context, t, N * 5 / 9, N * 6 / 9, 0);
knots(context, t, N * 8 / 9, N * 9 / 9, 0);
knots(context, t, N * 0 / 9, N * 1 / 9, 5);
knots(context, t, N * 3 / 9, N * 4 / 9, 5);
knots(context, t, N * 6 / 9, N * 7 / 9, 5);
knots(context, t, N * 0 / 9 - 1, N * 2 / 9 + 1, 0);
knots(context, t, N * 3 / 9 - 1, N * 5 / 9 + 1, 0);
knots(context, t, N * 6 / 9 - 1, N * 8 / 9 + 1, 0);
context.restore();
yield canvas;
}
}
Insert cell
function knots(context, t, i0, i1, wo) {
for (let a = -4; a < 5; ++a) {
knot(context, 15 * (a - 0.5), -(a + 5) * t, i0, i1, wo);
}
}
Insert cell
function knot(context, of, q, i0, i1, wo) {
const tau = 2 * Math.PI;
let theta = tau * i0 / N, w;
let vx0, vx1 = r * x(theta) + of * ypn(theta);
let vy0, vy1 = r * y(theta) + of * xpn(theta);
if (wo) context.strokeStyle = context.fillStyle;
for (let i = i0 + 1; i < i1; ++i) {
theta = tau * i / N;
vx0 = vx1, vx1 = r * x(theta) + of * ypn(theta);
vy0 = vy1, vy1 = r * y(theta) + of * xpn(theta);
w = map(Math.cos(theta + tau * q), 1, -1, 0, 1);
w = lerp(2.5, 8.5, ease(Math.max(0, Math.min(1, 3.5 * w - 2.5)), 7)) + wo;
context.beginPath();
context.moveTo(vx0, vy0);
context.lineTo(vx1, vy1);
context.lineWidth = w;
if (!wo) context.strokeStyle = color(i);
context.stroke();
}
}
Insert cell
color = {
const colors = Array.from({length: N}, (_, i) => d3.interpolateRainbow(i / N * 3));
return i => colors[i];
}
Insert cell
width = 720
Insert cell
height = 720
Insert cell
N = 360
Insert cell
r = 84
Insert cell
function x(q) {
return Math.sin(q) + 2 * Math.sin(2 * q);
}
Insert cell
function xp(q) {
return Math.cos(q) + 4 * Math.cos(2 * q);
}
Insert cell
function y(q) {
return Math.cos(q) - 2 * Math.cos(2 * q);
}
Insert cell
function yp(q) {
return -Math.sin(q) - 4 * Math.sin(2 * q);
}
Insert cell
function xpn(q) {
return xp(q) / dist(xp(q), yp(q), 0, 0);
}
Insert cell
function ypn(q) {
return yp(q) / dist(xp(q), yp(q), 0, 0);
}
Insert cell
function z(q) {
return Math.sin(3 * q);
}
Insert cell
function dist(x1, y1, x2, y2) {
return Math.hypot(x2 - x1, y2 - y1);
}
Insert cell
function map(value, start1, stop1, start2, stop2) {
return (value - start1) / (stop1 - start1) * (stop2 - start2) + start2;
}
Insert cell
function lerp(start, stop, t) {
return start * (1 - t) + stop * t;
}
Insert cell
function ease(p, g) {
return p < 0.5
? 0.5 * Math.pow(2 * p, g)
: 1 - 0.5 * Math.pow(2 * (1 - p), g);
}
Insert cell
d3 = require("d3-scale-chromatic@1")
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