Published
Edited
Jul 3, 2019
12 forks
Importers
87 stars
Insert cell
Insert cell
Insert cell
{
const context = DOM.context2d(width, height);
const R = [];
context.lineCap = "round";
context.lineJoin = "round";
for (let t = 0; true; ++t) {
const scale = viewof zoom.value * width / viewbox.width;
const a = t * 2 / q * Math.PI;
context.save();
context.clearRect(0, 0, width, height);

// Calculate the current point.
let p = [0, 0];
for (let i = 0; i < M; ++i) {
p = add(p, mul(DFT[i], expim(a * K[i])));
}

// Zoom.
context.translate(width / 2, height / 2);
context.scale(scale, scale);
context.translate(-p[0], -p[1]);

// Draw circles.
context.beginPath();
for (let i = 0, p = [0, 0]; i < M; ++i) {
const r = abs(DFT[i]);
context.moveTo(p[0] + r, p[1]);
context.arc(...p, r, 0, 2 * Math.PI);
p = add(p, mul(DFT[i], expim(a * K[i])));
}
context.lineWidth = 0.25 / scale;
context.strokeStyle = "#999";
context.stroke();

// Draw lines.
context.beginPath();
context.moveTo(0, 0);
for (let i = 0, p = [0, 0]; i < M; ++i) {
context.lineTo(...p = add(p, mul(DFT[i], expim(a * K[i]))));
}
context.lineWidth = 0.75 / scale;
context.strokeStyle = "#333";
context.stroke();

// Draw arrowheads.
context.beginPath();
for (let i = 0, p = [0, 0]; i < M; ++i) {
arrow(context, p, p = add(p, mul(DFT[i], expim(a * K[i]))), {size: 8 / scale});
}
context.fillStyle = "#333";
context.fill();

// Draw the path.
if (R.length < q) R.push(p);
context.beginPath();
context.moveTo(...R[0]);
for (let i = 1, n = R.length; i < n; ++i) {
context.lineTo(...R[i]);
}
if (R.length >= q) context.closePath();
context.lineWidth = 1.5 / scale;
context.strokeStyle = "#f00";
context.stroke();

context.restore();
yield context.canvas;
}
}
Insert cell
Insert cell
function arrow(context, [x0, y0], [x1, y1], {size = 1, delta = Math.PI / 6} = {}){
const dx = x1 - x0;
const dy = y1 - y0;
size = Math.min(size, Math.hypot(dx, dy) / 2);
const a = Math.atan2(dy, dx);
const a0 = a - delta;
const a1 = a + delta;
context.moveTo(x1 - size * Math.cos(a0), y1 - size * Math.sin(a0));
context.lineTo(x1, y1);
context.lineTo(x1 - size * Math.cos(a1), y1 - size * Math.sin(a1));
}
Insert cell
svg = fetch("https://gist.githubusercontent.com/mbostock/a4fd7a68925d4039c22996cc1d4862ce/raw/d813a42956d311d73fee336e1b5aac899c835883/fourier.svg")
.then(response => response.text())
.then(text => (new DOMParser).parseFromString(text, "image/svg+xml"))
.then(svg => svg.documentElement)
Insert cell
path = svg.querySelector("path")
Insert cell
l = path.getTotalLength()
Insert cell
P = Array.from({length: N}, (_, i) => {
const {x, y} = path.getPointAtLength(i / N * l);
return [x - viewbox.width / 2, y - viewbox.height / 2];
})
Insert cell
K = Int16Array.from({length: M}, (_, i) => (1 + i >> 1) * (i & 1 ? -1 : 1))
Insert cell
DFT = Array.from(K, k => {
let x = [0, 0];
for (let i = 0, N = P.length; i < N; ++i) {
x = add(x, mul(P[i], expim(k * i / N * 2 * -Math.PI)));
}
return [x[0] / N, x[1] / N];
})
Insert cell
N = 1600 // number of input samples
Insert cell
M = 250 // number of epicycles
Insert cell
q = 20000 // number of output samples
Insert cell
function abs([re, im]) {
return Math.hypot(re, im);
}
Insert cell
function expim(im) {
return [Math.cos(im), Math.sin(im)];
}
Insert cell
function add([rea, ima], [reb, imb]) {
return [rea + reb, ima + imb];
}
Insert cell
function mul([rea, ima], [reb, imb]) {
return [rea * reb - ima * imb, rea * imb + ima * reb];
}
Insert cell
viewbox = svg.viewBox.baseVal
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