|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Fourier Series Square Wave Visualization</title> |
|
<style> |
|
* { |
|
margin: 0; |
|
padding: 0; |
|
box-sizing: border-box; |
|
} |
|
|
|
body { |
|
background: linear-gradient(135deg, #0f0c29, #302b63, #24243e); |
|
color: #fff; |
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
|
min-height: 100vh; |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
padding: 20px; |
|
overflow-x: hidden; |
|
} |
|
|
|
header { |
|
text-align: center; |
|
margin: 20px 0 30px; |
|
max-width: 800px; |
|
animation: fadeIn 1.5s ease-out; |
|
} |
|
|
|
h1 { |
|
font-size: 2.8rem; |
|
margin-bottom: 15px; |
|
background: linear-gradient(to right, #ff7e5f, #feb47b); |
|
-webkit-background-clip: text; |
|
background-clip: text; |
|
color: transparent; |
|
text-shadow: 0 2px 4px rgba(0,0,0,0.2); |
|
} |
|
|
|
.subtitle { |
|
font-size: 1.2rem; |
|
opacity: 0.9; |
|
margin-bottom: 20px; |
|
} |
|
|
|
.container { |
|
display: flex; |
|
flex-wrap: wrap; |
|
justify-content: center; |
|
gap: 30px; |
|
max-width: 1400px; |
|
width: 100%; |
|
} |
|
|
|
.visualization-container { |
|
flex: 1; |
|
min-width: 500px; |
|
background: rgba(255, 255, 255, 0.05); |
|
border-radius: 20px; |
|
padding: 20px; |
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); |
|
backdrop-filter: blur(10px); |
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
} |
|
|
|
.canvas-wrapper { |
|
position: relative; |
|
width: 100%; |
|
height: 500px; |
|
border-radius: 15px; |
|
overflow: hidden; |
|
background: rgba(0, 0, 0, 0.2); |
|
} |
|
|
|
canvas { |
|
display: block; |
|
} |
|
|
|
.controls { |
|
display: flex; |
|
flex-wrap: wrap; |
|
gap: 20px; |
|
margin-top: 25px; |
|
padding: 20px; |
|
background: rgba(255, 255, 255, 0.05); |
|
border-radius: 15px; |
|
} |
|
|
|
.control-group { |
|
flex: 1; |
|
min-width: 200px; |
|
} |
|
|
|
label { |
|
display: block; |
|
margin-bottom: 8px; |
|
font-weight: 500; |
|
} |
|
|
|
input[type="range"] { |
|
width: 100%; |
|
height: 8px; |
|
border-radius: 4px; |
|
background: rgba(255, 255, 255, 0.1); |
|
outline: none; |
|
-webkit-appearance: none; |
|
} |
|
|
|
input[type="range"]::-webkit-slider-thumb { |
|
-webkit-appearance: none; |
|
width: 20px; |
|
height: 20px; |
|
border-radius: 50%; |
|
background: #ff7e5f; |
|
cursor: pointer; |
|
box-shadow: 0 0 10px rgba(255, 126, 95, 0.5); |
|
} |
|
|
|
.value-display { |
|
display: inline-block; |
|
width: 40px; |
|
text-align: center; |
|
font-weight: bold; |
|
color: #ff7e5f; |
|
} |
|
|
|
.info-panel { |
|
flex: 1; |
|
min-width: 300px; |
|
background: rgba(255, 255, 255, 0.05); |
|
border-radius: 20px; |
|
padding: 25px; |
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); |
|
backdrop-filter: blur(10px); |
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
} |
|
|
|
.info-panel h2 { |
|
font-size: 1.8rem; |
|
margin-bottom: 20px; |
|
color: #feb47b; |
|
} |
|
|
|
.explanation { |
|
line-height: 1.6; |
|
margin-bottom: 25px; |
|
} |
|
|
|
.formula { |
|
background: rgba(0, 0, 0, 0.2); |
|
padding: 15px; |
|
border-radius: 10px; |
|
font-family: 'Courier New', monospace; |
|
margin: 15px 0; |
|
overflow-x: auto; |
|
} |
|
|
|
.terms-list { |
|
margin-top: 20px; |
|
} |
|
|
|
.terms-list li { |
|
margin-bottom: 10px; |
|
padding-left: 20px; |
|
position: relative; |
|
} |
|
|
|
.terms-list li:before { |
|
content: "•"; |
|
color: #ff7e5f; |
|
position: absolute; |
|
left: 0; |
|
} |
|
|
|
footer { |
|
margin-top: 40px; |
|
text-align: center; |
|
opacity: 0.7; |
|
font-size: 0.9rem; |
|
} |
|
|
|
@keyframes fadeIn { |
|
from { opacity: 0; transform: translateY(-20px); } |
|
to { opacity: 1; transform: translateY(0); } |
|
} |
|
|
|
@media (max-width: 768px) { |
|
.container { |
|
flex-direction: column; |
|
} |
|
|
|
.visualization-container, .info-panel { |
|
min-width: 100%; |
|
} |
|
|
|
h1 { |
|
font-size: 2.2rem; |
|
} |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<header> |
|
<h1>Fourier Series Square Wave Visualization</h1> |
|
<p class="subtitle">Watch the magic of rotating circles approximating a square wave</p> |
|
</header> |
|
|
|
<div class="container"> |
|
<div class="visualization-container"> |
|
<div class="canvas-wrapper"> |
|
<canvas id="fourierCanvas"></canvas> |
|
</div> |
|
|
|
<div class="controls"> |
|
<div class="control-group"> |
|
<label for="terms">Number of Terms: <span id="termsValue" class="value-display">5</span></label> |
|
<input type="range" id="terms" min="1" max="50" value="5"> |
|
</div> |
|
|
|
<div class="control-group"> |
|
<label for="speed">Animation Speed: <span id="speedValue" class="value-display">1.0</span>x</label> |
|
<input type="range" id="speed" min="0.1" max="3" step="0.1" value="1.0"> |
|
</div> |
|
|
|
<div class="control-group"> |
|
<label for="scale">Scale: <span id="scaleValue" class="value-display">1.0</span>x</label> |
|
<input type="range" id="scale" min="0.5" max="2" step="0.1" value="1.0"> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="info-panel"> |
|
<h2>About Fourier Series</h2> |
|
<div class="explanation"> |
|
<p>The Fourier series represents periodic functions as a sum of sine and cosine waves. This visualization shows how rotating circles (epicycles) can approximate a square wave.</p> |
|
|
|
<div class="formula"> |
|
f(t) = (4/π) Σ<sub>n=1,3,5...<sup>∞</sup></sub> [sin(nωt)/n] |
|
</div> |
|
|
|
<p>Each rotating circle corresponds to a term in the series. As more terms are added, the approximation becomes more accurate.</p> |
|
</div> |
|
|
|
<h3>Key Concepts:</h3> |
|
<ul class="terms-list"> |
|
<li>Only odd harmonics contribute to the square wave</li> |
|
<li>Amplitude decreases as 1/n for the nth harmonic</li> |
|
<li>Gibbs phenomenon causes overshoots near discontinuities</li> |
|
<li>Infinite terms required for perfect reconstruction</li> |
|
</ul> |
|
</div> |
|
</div> |
|
|
|
<footer> |
|
<p>Fourier Series Visualization | Created with HTML5 Canvas and JavaScript</p> |
|
</footer> |
|
|
|
<script> |
|
|
|
const canvas = document.getElementById('fourierCanvas'); |
|
const ctx = canvas.getContext('2d'); |
|
|
|
|
|
function resizeCanvas() { |
|
const wrapper = canvas.parentElement; |
|
canvas.width = wrapper.clientWidth; |
|
canvas.height = wrapper.clientHeight; |
|
} |
|
|
|
|
|
resizeCanvas(); |
|
window.addEventListener('resize', resizeCanvas); |
|
|
|
|
|
const termsSlider = document.getElementById('terms'); |
|
const speedSlider = document.getElementById('speed'); |
|
const scaleSlider = document.getElementById('scale'); |
|
const termsValue = document.getElementById('termsValue'); |
|
const speedValue = document.getElementById('speedValue'); |
|
const scaleValue = document.getElementById('scaleValue'); |
|
|
|
|
|
let numTerms = parseInt(termsSlider.value); |
|
let speed = parseFloat(speedSlider.value); |
|
let scale = parseFloat(scaleSlider.value); |
|
|
|
|
|
termsValue.textContent = numTerms; |
|
speedValue.textContent = speed.toFixed(1); |
|
scaleValue.textContent = scale.toFixed(1); |
|
|
|
|
|
termsSlider.addEventListener('input', () => { |
|
numTerms = parseInt(termsSlider.value); |
|
termsValue.textContent = numTerms; |
|
}); |
|
|
|
speedSlider.addEventListener('input', () => { |
|
speed = parseFloat(speedSlider.value); |
|
speedValue.textContent = speed.toFixed(1); |
|
}); |
|
|
|
scaleSlider.addEventListener('input', () => { |
|
scale = parseFloat(scaleSlider.value); |
|
scaleValue.textContent = scale.toFixed(1); |
|
}); |
|
|
|
|
|
let time = 0; |
|
const wave = []; |
|
const maxWavePoints = 1000; |
|
|
|
|
|
function calculateFourierSeries() { |
|
let x = 0; |
|
let y = 0; |
|
|
|
|
|
for (let i = 0; i < numTerms; i++) { |
|
const n = i * 2 + 1; |
|
const radius = 70 * (4 / (n * Math.PI)) * scale; |
|
const prevX = x; |
|
const prevY = y; |
|
|
|
x += radius * Math.cos(n * time); |
|
y += radius * Math.sin(n * time); |
|
|
|
|
|
ctx.beginPath(); |
|
ctx.arc(prevX, prevY, Math.abs(radius), 0, Math.PI * 2); |
|
ctx.strokeStyle = `hsla(${200 + i * 10}, 80%, 60%, ${0.4 + 0.6 * (1 - i/numTerms)})`; |
|
ctx.lineWidth = 1; |
|
ctx.stroke(); |
|
|
|
|
|
ctx.beginPath(); |
|
ctx.moveTo(prevX, prevY); |
|
ctx.lineTo(x, y); |
|
ctx.strokeStyle = `hsla(${200 + i * 10}, 100%, 70%, 0.7)`; |
|
ctx.lineWidth = 2; |
|
ctx.stroke(); |
|
|
|
|
|
ctx.beginPath(); |
|
ctx.arc(x, y, 3, 0, Math.PI * 2); |
|
ctx.fillStyle = `hsl(${200 + i * 10}, 100%, 70%)`; |
|
ctx.fill(); |
|
} |
|
|
|
|
|
wave.unshift(y); |
|
|
|
|
|
if (wave.length > maxWavePoints) { |
|
wave.pop(); |
|
} |
|
|
|
|
|
const waveStartX = 300; |
|
ctx.beginPath(); |
|
ctx.moveTo(waveStartX, wave[0]); |
|
|
|
for (let i = 1; i < wave.length; i++) { |
|
ctx.lineTo(waveStartX + i, wave[i]); |
|
} |
|
|
|
ctx.strokeStyle = '#ff7e5f'; |
|
ctx.lineWidth = 2; |
|
ctx.stroke(); |
|
|
|
|
|
ctx.beginPath(); |
|
ctx.moveTo(x, y); |
|
ctx.lineTo(waveStartX, wave[0]); |
|
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)'; |
|
ctx.lineWidth = 1; |
|
ctx.stroke(); |
|
|
|
|
|
ctx.beginPath(); |
|
ctx.arc(waveStartX, wave[0], 5, 0, Math.PI * 2); |
|
ctx.fillStyle = '#feb47b'; |
|
ctx.fill(); |
|
|
|
|
|
time += 0.05 * speed; |
|
} |
|
|
|
|
|
function animate() { |
|
|
|
ctx.fillStyle = 'rgba(15, 12, 41, 0.1)'; |
|
ctx.fillRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
|
ctx.save(); |
|
ctx.translate(150, canvas.height / 2); |
|
|
|
|
|
calculateFourierSeries(); |
|
|
|
|
|
ctx.restore(); |
|
|
|
|
|
requestAnimationFrame(animate); |
|
} |
|
|
|
|
|
animate(); |
|
</script> |
|
</body> |
|
</html> |