Build a signal by combining sine waves of different frequencies. The spectrum shows how much of each frequency is present. Then watch spectral differentiation: each frequency component is multiplied by its wave number to produce the exact derivative.
<p class="hint">Build a signal from sine waves. The <b>spectrum</b> shows each wave's amplitude. The <b>derivative</b> is computed spectrally — each frequency component is simply multiplied by its wave number k, turning calculus into algebra.</p>
<div class="controls">
<div class="wave-row">
<label>k=1 (1 cycle): <span id="v1">0</span></label>
<input type="range" id="s1" min="0" max="4" step="0.5" value="0">
</div>
<div class="wave-row">
<label>k=2 (2 cycles): <span id="v2">0</span></label>
<input type="range" id="s2" min="0" max="4" step="0.5" value="2">
</div>
<div class="wave-row">
<label>k=3 (3 cycles): <span id="v3">0</span></label>
<input type="range" id="s3" min="0" max="4" step="0.5" value="0">
</div>
<div class="wave-row">
<label>k=4 (4 cycles): <span id="v4">0</span></label>
<input type="range" id="s4" min="0" max="4" step="0.5" value="1">
</div>
</div>
<div class="canvases">
<div class="panel">
<div class="panel-title">Signal f(x)</div>
<canvas id="cSignal" width="280" height="100"></canvas>
</div>
<div class="panel">
<div class="panel-title">Spectrum (amplitudes)</div>
<canvas id="cSpectrum" width="280" height="100"></canvas>
</div>
<div class="panel">
<div class="panel-title">Derivative f′(x) — spectral</div>
<canvas id="cDeriv" width="280" height="100"></canvas>
</div>
</div>
<div class="formula" id="formula"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: 14px; background: #f8fafb; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.controls { display: flex; flex-direction: column; gap: .3rem; margin-bottom: .8rem; }
.wave-row { display: flex; align-items: center; gap: .6rem; font-size: .85rem; }
.wave-row label { width: 160px; flex-shrink: 0; }
.wave-row input[type=range] { flex: 1; }
.canvases { display: flex; flex-wrap: wrap; gap: .5rem; }
.panel { background: #fff; border: 1px solid #dde3ea; border-radius: 8px; padding: .4rem .5rem; flex: 1 1 240px; }
.panel-title { font-size: .78rem; font-weight: 700; color: #4a6275; margin-bottom: .25rem; text-transform: uppercase; letter-spacing: .04em; }
canvas { display: block; width: 100%; height: 100px; }
.formula { margin-top: .7rem; font-size: .82rem; color: #333; line-height: 1.6; background: #eef3f7; border-radius: 6px; padding: .45rem .7rem; min-height: 1.8em; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Spectral method demo: build f(x) = sum of A_k * sin(k * 2π x) for k=1..4
// Derivative d/dx [A_k * sin(k*2π*x)] = A_k * k * 2π * cos(k*2π*x)
// In frequency space this is just multiplication by k*2π — no finite differences needed.
const N = 200; // sample points in [0,1)
const xs = Array.from({length: N}, (_, i) => i / N);
const ids = ['s1','s2','s3','s4'];
const amps = [0, 2, 0, 1]; // defaults
function getAmps() {
return ids.map((id, i) => parseFloat(document.getElementById(id).value));
}
function signal(A, x) {
// f(x) = sum_k A[k] * sin((k+1) * 2π x)
return A.reduce((s, a, i) => s + a * Math.sin((i + 1) * 2 * Math.PI * x), 0);
}
function derivative(A, x) {
// f'(x) = sum_k A[k] * (k+1) * 2π * cos((k+1) * 2π x)
return A.reduce((s, a, i) => s + a * (i + 1) * 2 * Math.PI * Math.cos((i + 1) * 2 * Math.PI * x), 0);
}
function drawCurve(canvas, ys, color, label) {
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
// draw zero line
ctx.strokeStyle = '#ccc'; ctx.lineWidth = 0.8;
ctx.beginPath(); ctx.moveTo(0, H/2); ctx.lineTo(W, H/2); ctx.stroke();
const max = Math.max(...ys.map(Math.abs), 0.001);
ctx.strokeStyle = color; ctx.lineWidth = 2; ctx.lineJoin = 'round';
ctx.beginPath();
ys.forEach((y, i) => {
const px = (i / (ys.length - 1)) * W;
const py = H/2 - (y / max) * (H/2 - 6);
i === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
});
ctx.stroke();
}
function drawSpectrum(canvas, A) {
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
const maxA = Math.max(...A, 0.001);
const colors = ['#1d6fa4','#e05a2b','#2a9d44','#9b2f9e'];
const barW = W / (A.length * 2);
A.forEach((a, i) => {
const x = (W / (A.length)) * i + W / (A.length * 2) - barW / 2;
const barH = (a / maxA) * (H - 16);
ctx.fillStyle = colors[i % colors.length];
ctx.fillRect(x, H - barH, barW, barH);
ctx.fillStyle = '#555';
ctx.font = '9px system-ui';
ctx.fillText('k=' + (i + 1), x + barW * 0.1, H - 2);
});
}
function buildFormula(A) {
const terms = A.map((a, i) => {
if (a === 0) return null;
const k = i + 1;
return `${a}·sin(${k}·2πx)`;
}).filter(Boolean);
const derivTerms = A.map((a, i) => {
if (a === 0) return null;
const k = i + 1;
const coeff = +(a * k * 2 * Math.PI).toFixed(2);
return `${coeff}·cos(${k}·2πx)`;
}).filter(Boolean);
const fStr = terms.length ? terms.join(' + ') : '0';
const dStr = derivTerms.length ? derivTerms.join(' + ') : '0';
return fmt('f(x) = %fStr%<br>f′(x) = %dStr% <span style="color:#888">(spectral: multiply each k by its amplitude)</span>', { fStr: fStr, dStr: dStr });
}
function update() {
const A = getAmps();
ids.forEach((id, i) => {
document.getElementById('v' + (i + 1)).textContent = A[i];
});
const sigVals = xs.map(x => signal(A, x));
const derivVals = xs.map(x => derivative(A, x));
drawCurve(document.getElementById('cSignal'), sigVals, '#1d6fa4', 'f(x)');
drawSpectrum(document.getElementById('cSpectrum'), A);
drawCurve(document.getElementById('cDeriv'), derivVals, '#e05a2b', "f'(x)");
document.getElementById('formula').innerHTML = buildFormula(A);
}
ids.forEach(id => document.getElementById(id).addEventListener('input', update));
update();
<p class="hint">Build a signal from sine waves. The <b>spectrum</b> shows each wave's amplitude. The <b>derivative</b> is computed spectrally — each frequency component is simply multiplied by its wave number k, turning calculus into algebra.</p>
<div class="controls">
<div class="wave-row">
<label>k=1 (1 cycle): <span id="v1">0</span></label>
<input type="range" id="s1" min="0" max="4" step="0.5" value="0">
</div>
<div class="wave-row">
<label>k=2 (2 cycles): <span id="v2">0</span></label>
<input type="range" id="s2" min="0" max="4" step="0.5" value="2">
</div>
<div class="wave-row">
<label>k=3 (3 cycles): <span id="v3">0</span></label>
<input type="range" id="s3" min="0" max="4" step="0.5" value="0">
</div>
<div class="wave-row">
<label>k=4 (4 cycles): <span id="v4">0</span></label>
<input type="range" id="s4" min="0" max="4" step="0.5" value="1">
</div>
</div>
<div class="canvases">
<div class="panel">
<div class="panel-title">Signal f(x)</div>
<canvas id="cSignal" width="280" height="100"></canvas>
</div>
<div class="panel">
<div class="panel-title">Spectrum (amplitudes)</div>
<canvas id="cSpectrum" width="280" height="100"></canvas>
</div>
<div class="panel">
<div class="panel-title">Derivative f′(x) — spectral</div>
<canvas id="cDeriv" width="280" height="100"></canvas>
</div>
</div>
<div class="formula" id="formula"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: 14px; background: #f8fafb; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.controls { display: flex; flex-direction: column; gap: .3rem; margin-bottom: .8rem; }
.wave-row { display: flex; align-items: center; gap: .6rem; font-size: .85rem; }
.wave-row label { width: 160px; flex-shrink: 0; }
.wave-row input[type=range] { flex: 1; }
.canvases { display: flex; flex-wrap: wrap; gap: .5rem; }
.panel { background: #fff; border: 1px solid #dde3ea; border-radius: 8px; padding: .4rem .5rem; flex: 1 1 240px; }
.panel-title { font-size: .78rem; font-weight: 700; color: #4a6275; margin-bottom: .25rem; text-transform: uppercase; letter-spacing: .04em; }
canvas { display: block; width: 100%; height: 100px; }
.formula { margin-top: .7rem; font-size: .82rem; color: #333; line-height: 1.6; background: #eef3f7; border-radius: 6px; padding: .45rem .7rem; min-height: 1.8em; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Spectral method demo: build f(x) = sum of A_k * sin(k * 2π x) for k=1..4
// Derivative d/dx [A_k * sin(k*2π*x)] = A_k * k * 2π * cos(k*2π*x)
// In frequency space this is just multiplication by k*2π — no finite differences needed.
const N = 200; // sample points in [0,1)
const xs = Array.from({length: N}, (_, i) => i / N);
const ids = ['s1','s2','s3','s4'];
const amps = [0, 2, 0, 1]; // defaults
function getAmps() {
return ids.map((id, i) => parseFloat(document.getElementById(id).value));
}
function signal(A, x) {
// f(x) = sum_k A[k] * sin((k+1) * 2π x)
return A.reduce((s, a, i) => s + a * Math.sin((i + 1) * 2 * Math.PI * x), 0);
}
function derivative(A, x) {
// f'(x) = sum_k A[k] * (k+1) * 2π * cos((k+1) * 2π x)
return A.reduce((s, a, i) => s + a * (i + 1) * 2 * Math.PI * Math.cos((i + 1) * 2 * Math.PI * x), 0);
}
function drawCurve(canvas, ys, color, label) {
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
// draw zero line
ctx.strokeStyle = '#ccc'; ctx.lineWidth = 0.8;
ctx.beginPath(); ctx.moveTo(0, H/2); ctx.lineTo(W, H/2); ctx.stroke();
const max = Math.max(...ys.map(Math.abs), 0.001);
ctx.strokeStyle = color; ctx.lineWidth = 2; ctx.lineJoin = 'round';
ctx.beginPath();
ys.forEach((y, i) => {
const px = (i / (ys.length - 1)) * W;
const py = H/2 - (y / max) * (H/2 - 6);
i === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
});
ctx.stroke();
}
function drawSpectrum(canvas, A) {
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
const maxA = Math.max(...A, 0.001);
const colors = ['#1d6fa4','#e05a2b','#2a9d44','#9b2f9e'];
const barW = W / (A.length * 2);
A.forEach((a, i) => {
const x = (W / (A.length)) * i + W / (A.length * 2) - barW / 2;
const barH = (a / maxA) * (H - 16);
ctx.fillStyle = colors[i % colors.length];
ctx.fillRect(x, H - barH, barW, barH);
ctx.fillStyle = '#555';
ctx.font = '9px system-ui';
ctx.fillText('k=' + (i + 1), x + barW * 0.1, H - 2);
});
}
function buildFormula(A) {
const terms = A.map((a, i) => {
if (a === 0) return null;
const k = i + 1;
return `${a}·sin(${k}·2πx)`;
}).filter(Boolean);
const derivTerms = A.map((a, i) => {
if (a === 0) return null;
const k = i + 1;
const coeff = +(a * k * 2 * Math.PI).toFixed(2);
return `${coeff}·cos(${k}·2πx)`;
}).filter(Boolean);
const fStr = terms.length ? terms.join(' + ') : '0';
const dStr = derivTerms.length ? derivTerms.join(' + ') : '0';
return fmt('f(x) = %fStr%<br>f′(x) = %dStr% <span style="color:#888">(spectral: multiply each k by its amplitude)</span>', { fStr: fStr, dStr: dStr });
}
function update() {
const A = getAmps();
ids.forEach((id, i) => {
document.getElementById('v' + (i + 1)).textContent = A[i];
});
const sigVals = xs.map(x => signal(A, x));
const derivVals = xs.map(x => derivative(A, x));
drawCurve(document.getElementById('cSignal'), sigVals, '#1d6fa4', 'f(x)');
drawSpectrum(document.getElementById('cSpectrum'), A);
drawCurve(document.getElementById('cDeriv'), derivVals, '#e05a2b', "f'(x)");
document.getElementById('formula').innerHTML = buildFormula(A);
}
ids.forEach(id => document.getElementById(id).addEventListener('input', update));
update();
Comments
Loading comments...