Files
50ohm-contents-dl/contents/html/applet_nyquist.html
T
SuperminarenandClaude Sonnet 5.5 f7fa8cfbc1 Swedish adaptation: Instegscertifikat and HAREC content
- Sections and slides rewritten in Swedish; German law and institutions replaced by
  Swedish/CEPT/ITU equivalents (PTS, SSA, PTSFS 2025:1, SSMFS 2008:18), unverified
  statements marked "% TODO VERIFY"
- New toc: INSTEG, HAREC, UPPGRADERING; section idents are Swedish (see ident-map.tsv)
- Exam-question tags, BNetzA question pool, solutions and the German SWL course removed
- Static pages, snippets, applets, READMEs and image alt-texts translated
- TikZ labels translated; SVG drawings regenerated (drawing 689 still fails to build
  upstream: it references a missing photo)
- Changed from the original: not endorsed by DARC e.V., SSA or PTS

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-10 15:03:03 +02:00

450 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<html>
<style>
/* ---- Applet Card ---- */
.applet-card {
overflow: hidden;
}
.applet-header {
background: #f5f5f5;
padding: 12px 20px;
font-size: 14px;
font-weight: 500;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
}
.applet-header .material-icons { font-size: 18px; color: var(--primary); }
.applet-body { padding: 20px; }
/* ---- Canvas panels ---- */
.panel {
margin-bottom: 16px;
}
.panel-label {
margin-bottom: 6px;
display: flex;
align-items: center;
gap: 6px;
}
.panel-label .dot {
width: 10px; height: 10px; border-radius: 50%;
display: inline-block;
}
canvas {
display: block;
width: 100%;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--canvas-bg);
}
/* ---- Controls ---- */
.controls {
background: #f9f9f9;
border: 1px solid var(--border);
border-radius: 6px;
padding: 16px 20px;
margin-bottom: 16px;
}
.control-row {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
}
.control-value {
text-align: right;
}
.status-row {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
margin-top: 12px;
}
.nyquist-info {
font-size: 13px;
color: var(--text-secondary);
}
/* ---- Info box ---- */
.info-box {
margin-top: 20px;
padding: 14px 18px;
background: "#DAEEFA";
border-left: 4px solid var(--primary);
border-radius: 0 4px 4px 0;
}
.info-box strong { font-weight: 700; }
/* ---- Legend ---- */
.legend {
display: flex;
gap: 20px;
flex-wrap: wrap;
margin-bottom: 16px;
font-size: 13px;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
}
.legend-line {
width: 24px; height: 3px; border-radius: 2px;
}
.legend-circle {
width: 10px; height: 10px; border-radius: 50%;
}
/* ---- Formula ---- */
.formula-box {
margin-top: 20px;
background: #263238;
color: #eceff1;
border-radius: 6px;
padding: 14px 18px;
font-size: 13px;
line-height: 1.8;
}
.formula-box .comment { color: #78909c; }
.formula-box .keyword { color: #80cbc4; }
.formula-box .func { color: #ffcc02; }
@media (max-width: 600px) {
.control-row { flex-direction: column; align-items: flex-start; }
.control-value { text-align: left; }
}
</style>
<div class="applet-card">
<div class="applet-body">
<!-- Controls -->
<div class="controls">
<div class="control-row">
<span class="control-label">Samplingsfrekvens $f_\text{s}$:</span>
<input type="range" id="sliderFs" min="1.0" max="8.0" step="0.025" value="3.0">
<span class="control-value" id="labelFs">3,0 kHz</span>
</div>
</div>
<!-- Panel 1: Original signal -->
<div class="panel">
<div class="panel-label">
<span class="dot" style="background:var(--signal-color)"></span>
Originalsignal — sinus på 1 kHz
</div>
<canvas id="canvasOriginal" height="300"></canvas>
</div>
<!-- Panel 2: Sampled signal -->
<div class="panel">
<div class="panel-label">
<span class="dot" style="background:var(--sample-color)"></span>
Samplad signal
</div>
<canvas id="canvasSampled" height="300"></canvas>
</div>
<!-- Panel 3: Reconstructed signal -->
<div class="panel">
<div class="panel-label">
<span class="dot" style="background:var(--recon-color)"></span>
Rekonstruerad signal
</div>
<canvas id="canvasRecon" height="300"></canvas>
</div>
<!-- Info box -->
<div class="info-box" id="infoBox">
<strong>✓ Nyquistvillkoret är uppfyllt:</strong>
f<sub>s</sub> = 3,0 kHz ≥ 2 · 1 kHz. Den rekonstruerade signalen stämmer överens med originalet.
</div><br>
</div>
</div>
</main>
<script>
// ============================================================
// Signal- och visningsparametrar
// ============================================================
const F_SIG = 1.0; // signalfrekvens (normerad till 1)
const T_SHOW = 3.0; // visad tidslängd i perioder
const N_DRAW = 1000; // punkter för den analoga kurvan
// sinc-funktion (normerad version: sinc(u) = sin(πu)/(πu))
function sinc(u) {
if (Math.abs(u) < 1e-10) return 1.0;
return Math.sin(Math.PI * u) / (Math.PI * u);
}
// Originalsignal
function signal(t) {
return Math.sin(2 * Math.PI * F_SIG * t);
}
// Whittaker–Shannon-rekonstruktion
function reconstruct(t, samples, fs) {
let sum = 0;
for (let n = 0; n < samples.length; n++) {
sum += samples[n].y * sinc(fs * t - n);
}
return sum;
}
// ============================================================
// Hjälpklass för canvas
// ============================================================
class Plot {
constructor(canvas, yMin=-1.3, yMax=1.3) {
this.canvas = canvas;
this.yMin = yMin;
this.yMax = yMax;
this.resize();
}
resize() {
const dpr = window.devicePixelRatio || 1;
const rect = this.canvas.getBoundingClientRect();
this.canvas.width = rect.width * dpr;
this.canvas.height = this.canvas.offsetHeight * dpr;
this.ctx = this.canvas.getContext('2d');
this.ctx.scale(dpr, dpr);
this.W = rect.width;
this.H = this.canvas.offsetHeight;
this.padL = 44; this.padR = 12;
this.padT = 10; this.padB = 22;
this.plotW = this.W - this.padL - this.padR;
this.plotH = this.H - this.padT - this.padB;
}
// map t∈[0,T_SHOW] to pixel x
tx(t) { return this.padL + (t / T_SHOW) * this.plotW; }
// map y∈[yMin,yMax] to pixel y
ty(y) { return this.padT + (1 - (y - this.yMin) / (this.yMax - this.yMin)) * this.plotH; }
clear() {
const ctx = this.ctx;
ctx.clearRect(0, 0, this.W, this.H);
// background
ctx.fillStyle = '#fafafa';
ctx.fillRect(this.padL, this.padT, this.plotW, this.plotH);
// grid lines
ctx.strokeStyle = '#e8e8e8';
ctx.lineWidth = 1;
for (let v of [-1, -0.5, 0, 0.5, 1]) {
const py = this.ty(v);
ctx.beginPath(); ctx.moveTo(this.padL, py); ctx.lineTo(this.padL + this.plotW, py);
ctx.stroke();
}
// time grid
for (let k = 0; k <= T_SHOW; k++) {
const px = this.tx(k);
ctx.beginPath(); ctx.moveTo(px, this.padT); ctx.lineTo(px, this.padT + this.plotH);
ctx.stroke();
}
// axes
ctx.strokeStyle = '#aaa';
ctx.lineWidth = 1;
ctx.strokeRect(this.padL, this.padT, this.plotW, this.plotH);
// zero line
ctx.strokeStyle = '#bbb';
ctx.lineWidth = 1.5;
const y0 = this.ty(0);
ctx.beginPath(); ctx.moveTo(this.padL, y0); ctx.lineTo(this.padL + this.plotW, y0);
ctx.stroke();
// y-axis labels
ctx.fillStyle = '#888';
ctx.textAlign = 'right';
for (let v of [-1, 0, 1]) {
ctx.fillText(v.toFixed(0), this.padL - 4, this.ty(v) + 3.5);
}
// x-axis labels
ctx.textAlign = 'center';
ctx.fillStyle = '#888';
for (let k = 0; k <= T_SHOW; k++) {
ctx.fillText(k + 'T', this.tx(k), this.padT + this.plotH + 14);
}
}
drawLine(points, color='#47ABE8', lw=2) {
const ctx = this.ctx;
ctx.strokeStyle = color;
ctx.lineWidth = lw;
ctx.lineJoin = 'round';
ctx.beginPath();
for (let i = 0; i < points.length; i++) {
const px = this.tx(points[i].t);
const py = this.ty(points[i].y);
if (i === 0) ctx.moveTo(px, py); else ctx.lineTo(px, py);
}
ctx.stroke();
}
drawStems(samples, color='#FE756C') {
const ctx = this.ctx;
const y0 = this.ty(0);
// Draw vertical stem lines
ctx.strokeStyle = color;
ctx.lineWidth = 1.5;
for (const s of samples) {
if (s.t < 0 || s.t > T_SHOW) continue;
const px = this.tx(s.t);
const py = this.ty(s.y);
ctx.beginPath(); ctx.moveTo(px, y0); ctx.lineTo(px, py); ctx.stroke();
}
// Draw sample dots on top
ctx.fillStyle = color;
ctx.strokeStyle = '#fff';
ctx.lineWidth = 1.5;
for (const s of samples) {
if (s.t < 0 || s.t > T_SHOW) continue;
const px = this.tx(s.t);
const py = this.ty(s.y);
ctx.beginPath();
ctx.arc(px, py, 4.5, 0, 2*Math.PI);
ctx.fill();
ctx.stroke();
}
}
}
// ============================================================
// Globala plotobjekt
// ============================================================
let plotOrig, plotSamp, plotRecon;
function initPlots() {
plotOrig = new Plot(document.getElementById('canvasOriginal'));
plotSamp = new Plot(document.getElementById('canvasSampled'));
plotRecon = new Plot(document.getElementById('canvasRecon'));
}
// ============================================================
// Huvudfunktion för rendering
// ============================================================
function render(fs) {
// --- 1. Skapa samplingsvärden ---
const Ts = 1 / fs; // samplingsintervall (i signalperioder)
const nSamples = Math.floor(T_SHOW * fs) + 2;
const samples = [];
for (let n = 0; n < nSamples; n++) {
const t = n * Ts;
samples.push({ t, y: signal(t) });
}
// --- 2. Skapa analog kurva ---
const analogPts = [];
for (let i = 0; i <= N_DRAW; i++) {
const t = (i / N_DRAW) * T_SHOW;
analogPts.push({ t, y: signal(t) });
}
// --- 3. Rekonstruktion (sinc-interpolation) ---
const reconPts = [];
for (let i = 0; i <= N_DRAW; i++) {
const t = (i / N_DRAW) * T_SHOW;
reconPts.push({ t, y: reconstruct(t, samples, fs) });
}
// --- 4. Panel 1: original ---
plotOrig.clear();
plotOrig.drawLine(analogPts, '#47ABE8', 2.2);
// --- 5. Panel 2: samplingar (analog + stolpar) ---
plotSamp.clear();
plotSamp.drawLine(analogPts, '#47ABE8', 1.5); // svagt dämpad i bakgrunden
plotSamp.drawStems(samples, '#FE756C');
// --- 6. Panel 3: rekonstruktion ---
const nyquistOk = fs >= 2 * F_SIG;
const reconColor = nyquistOk ? '#3BB583' : '#FE756C';
plotRecon.clear();
// Original som streckad referens
const ctx = plotRecon.ctx;
ctx.save();
ctx.setLineDash([5, 5]);
plotRecon.drawLine(analogPts, '#47ABE8', 1.2);
ctx.setLineDash([]);
ctx.restore();
// Rekonstruerad signal
plotRecon.drawLine(reconPts, reconColor, 2.2);
// --- 7. UI-uppdateringar ---
updateUI(fs, nyquistOk);
}
// decimalkomma i visade värden
function sv(s) { return String(s).replace(".", ","); }
function updateUI(fs, ok) {
const infoBox = document.getElementById('infoBox');
const ratio = (fs / F_SIG).toFixed(2);
const fAlias = Math.abs(fs - F_SIG).toFixed(2); // aliasfrekvens (förenklad)
if (ok) {
infoBox.style.borderLeftColor = '#1565c0';
infoBox.style.background = '#DAEEFA';
infoBox.innerHTML =
`<strong>✓ Nyquistvillkoret är uppfyllt:</strong> ` +
`f<sub>s</sub> = ${sv(fs.toFixed(2))} kHz ≥ 2 · ${sv(String(F_SIG))} kHz = ${sv((2*F_SIG).toFixed(2))} kHz. ` +
`Den rekonstruerade signalen stämmer överens med originalet.`;
} else {
infoBox.style.borderLeftColor = '#c62828';
infoBox.style.background = '#FFE3E2';
infoBox.innerHTML =
`<strong>⚠ Aliasing:</strong> f<sub>s</sub> = ${sv(fs.toFixed(2))} kHz < 2 · ${sv(String(F_SIG))} kHz. ` +
`Den rekonstruerade signalen uppträder med aliasfrekvensen ` +
`f<sub>alias</sub> = |f<sub>s</sub> − f<sub>signal</sub>| ≈ ${sv(fAlias)} kHz. ` +
`Öka samplingsfrekvensen över ${sv((2*F_SIG).toFixed(1))} kHz för att återge signalen korrekt.`;
}
}
// ============================================================
// Händelselyssnare och init
// ============================================================
const slider = document.getElementById('sliderFs');
const labelFs = document.getElementById('labelFs');
slider.addEventListener('input', () => {
const fs = parseFloat(slider.value);
labelFs.textContent = sv(fs.toFixed(2)) + ' kHz';
render(fs);
});
function onResize() {
if (!plotOrig || !plotSamp || !plotRecon) return;
plotOrig.resize();
plotSamp.resize();
plotRecon.resize();
render(parseFloat(slider.value));
}
window.addEventListener('resize', onResize);
// Initiera först efter laddning, när canvasdimensionerna är kända
window.addEventListener('load', () => {
initPlots();
const fs = parseFloat(slider.value);
labelFs.textContent = sv(fs.toFixed(2)) + ' kHz';
render(fs);
});
</script>
</html>