Skip to content

Instantly share code, notes, and snippets.

@ritwikraha
Created April 9, 2025 08:24
Show Gist options
  • Save ritwikraha/222e5a0f55920843db10cfa35780935d to your computer and use it in GitHub Desktop.
Save ritwikraha/222e5a0f55920843db10cfa35780935d to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<title>Unit Circle and Sine Wave Animation</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
}
.animation-container {
display: flex;
align-items: center;
margin-bottom: 20px;
}
canvas {
border: 1px solid #ccc;
}
.controls {
margin: 20px 0;
text-align: center;
}
.info {
margin: 20px 0;
width: 600px;
text-align: center;
}
label {
margin-right: 10px;
}
input[type="range"] {
vertical-align: middle;
}
</style>
</head>
<body>
<div class="container">
<h1>Unit Circle and Sine Wave Animation</h1>
<div class="animation-container">
<canvas id="circleCanvas" width="300" height="300"></canvas>
<canvas id="waveCanvas" width="600" height="300"></canvas>
</div>
<div class="controls">
<label for="frequencySlider">Angular Frequency (ω = 2πf):</label>
<input type="range" id="frequencySlider" min="0.5" max="5" step="0.1" value="1">
<span id="frequencyValue">1.0</span>
<button id="pauseButton">Pause</button>
<button id="resetButton">Reset</button>
</div>
<div class="info">
<p>This animation demonstrates the relationship between a point moving on a unit circle and a sine wave. The x-coordinate of the point on the circle (cos(θ)) and the y-coordinate (sin(θ)) are plotted over time.</p>
<p>Angle θ = ωt, where ω is the angular frequency (2πf) and t is time.</p>
</div>
</div>
<script>
// Get canvas elements and contexts
const circleCanvas = document.getElementById('circleCanvas');
const waveCanvas = document.getElementById('waveCanvas');
const circleCtx = circleCanvas.getContext('2d');
const waveCtx = waveCanvas.getContext('2d');
// Get UI elements
const frequencySlider = document.getElementById('frequencySlider');
const frequencyValue = document.getElementById('frequencyValue');
const pauseButton = document.getElementById('pauseButton');
const resetButton = document.getElementById('resetButton');
// Constants and variables
const circleRadius = 100;
const circleCenterX = circleCanvas.width / 2;
const circleCenterY = circleCanvas.height / 2;
const waveStartX = 0;
const waveHeight = 200;
const waveCenterY = waveCanvas.height / 2;
let time = 0;
let frequency = parseFloat(frequencySlider.value);
let wavePoints = [];
let animating = true;
let animationId;
// Update frequency display
function updateFrequencyDisplay() {
frequency = parseFloat(frequencySlider.value);
frequencyValue.textContent = frequency.toFixed(1);
}
// Initialize the wave points array
function initWavePoints() {
wavePoints = [];
for (let i = 0; i < waveCanvas.width; i++) {
wavePoints.push(0);
}
}
// Draw the unit circle with the current angle
function drawCircle(angle) {
circleCtx.clearRect(0, 0, circleCanvas.width, circleCanvas.height);
// Draw coordinate axes
circleCtx.strokeStyle = '#999';
circleCtx.beginPath();
circleCtx.moveTo(0, circleCenterY);
circleCtx.lineTo(circleCanvas.width, circleCenterY);
circleCtx.moveTo(circleCenterX, 0);
circleCtx.lineTo(circleCenterX, circleCanvas.height);
circleCtx.stroke();
// Draw the unit circle
circleCtx.strokeStyle = '#333';
circleCtx.beginPath();
circleCtx.arc(circleCenterX, circleCenterY, circleRadius, 0, 2 * Math.PI);
circleCtx.stroke();
// Calculate the point on the circle
const x = Math.cos(angle) * circleRadius;
const y = Math.sin(angle) * circleRadius;
const pointX = circleCenterX + x;
const pointY = circleCenterY - y; // Flip y to match canvas coordinates
// Draw the vector from center to point
circleCtx.strokeStyle = 'red';
circleCtx.lineWidth = 2;
circleCtx.beginPath();
circleCtx.moveTo(circleCenterX, circleCenterY);
circleCtx.lineTo(pointX, pointY);
circleCtx.stroke();
// Draw the point
circleCtx.fillStyle = 'red';
circleCtx.beginPath();
circleCtx.arc(pointX, pointY, 5, 0, 2 * Math.PI);
circleCtx.fill();
// Draw the projection on x-axis
circleCtx.strokeStyle = 'blue';
circleCtx.setLineDash([5, 3]);
circleCtx.beginPath();
circleCtx.moveTo(pointX, pointY);
circleCtx.lineTo(pointX, circleCenterY);
circleCtx.stroke();
circleCtx.setLineDash([]);
// Draw the projection on y-axis
circleCtx.strokeStyle = 'green';
circleCtx.setLineDash([5, 3]);
circleCtx.beginPath();
circleCtx.moveTo(pointX, pointY);
circleCtx.lineTo(circleCenterX, pointY);
circleCtx.stroke();
circleCtx.setLineDash([]);
// Draw the angle
circleCtx.strokeStyle = 'orange';
circleCtx.beginPath();
circleCtx.arc(circleCenterX, circleCenterY, 20, 0, -angle, true);
circleCtx.stroke();
// Draw angle label
circleCtx.fillStyle = 'black';
circleCtx.font = '14px Arial';
circleCtx.fillText(`θ = ${angle.toFixed(2)} rad`, circleCenterX + 25, circleCenterY - 25);
// Draw the projection points
circleCtx.fillStyle = 'blue';
circleCtx.beginPath();
circleCtx.arc(pointX, circleCenterY, 5, 0, 2 * Math.PI);
circleCtx.fill();
circleCtx.fillStyle = 'green';
circleCtx.beginPath();
circleCtx.arc(circleCenterX, pointY, 5, 0, 2 * Math.PI);
circleCtx.fill();
// Draw labels
circleCtx.fillStyle = 'blue';
circleCtx.fillText(`cos(θ) = ${Math.cos(angle).toFixed(2)}`, circleCenterX - 90, circleCenterY + 30);
circleCtx.fillStyle = 'green';
circleCtx.fillText(`sin(θ) = ${Math.sin(angle).toFixed(2)}`, circleCenterX + 20, pointY - 10);
return { x: Math.cos(angle), y: Math.sin(angle) };
}
// Update and draw the wave
function updateWave(angle, point) {
// Shift all points to the left
for (let i = 0; i < wavePoints.length - 1; i++) {
wavePoints[i] = wavePoints[i + 1];
}
// Add the new point
wavePoints[wavePoints.length - 1] = point.y;
// Clear the wave canvas
waveCtx.clearRect(0, 0, waveCanvas.width, waveCanvas.height);
// Draw coordinate axes
waveCtx.strokeStyle = '#999';
waveCtx.beginPath();
waveCtx.moveTo(0, waveCenterY);
waveCtx.lineTo(waveCanvas.width, waveCenterY);
waveCtx.stroke();
// Draw time labels on x-axis
waveCtx.fillStyle = 'black';
waveCtx.font = '12px Arial';
waveCtx.fillText('0', 5, waveCenterY + 15);
waveCtx.fillText('π', waveCanvas.width / 4, waveCenterY + 15);
waveCtx.fillText('2π', waveCanvas.width / 2, waveCenterY + 15);
waveCtx.fillText('3π', 3 * waveCanvas.width / 4, waveCenterY + 15);
waveCtx.fillText('4π', waveCanvas.width - 20, waveCenterY + 15);
// Draw the wave
waveCtx.strokeStyle = 'green';
waveCtx.lineWidth = 2;
waveCtx.beginPath();
for (let i = 0; i < wavePoints.length; i++) {
const y = waveCenterY - wavePoints[i] * circleRadius;
if (i === 0) {
waveCtx.moveTo(i, y);
} else {
waveCtx.lineTo(i, y);
}
}
waveCtx.stroke();
// Draw vertical line connecting to current point
waveCtx.strokeStyle = 'red';
waveCtx.setLineDash([5, 3]);
waveCtx.beginPath();
waveCtx.moveTo(0, waveCenterY - point.y * circleRadius);
waveCtx.lineTo(0, waveCenterY);
waveCtx.stroke();
waveCtx.setLineDash([]);
// Draw the current point
waveCtx.fillStyle = 'red';
waveCtx.beginPath();
waveCtx.arc(0, waveCenterY - point.y * circleRadius, 5, 0, 2 * Math.PI);
waveCtx.fill();
// Draw phase information
waveCtx.fillStyle = 'black';
waveCtx.font = '14px Arial';
waveCtx.fillText(`ω = ${frequency.toFixed(1)} rad/s`, waveCanvas.width - 150, 30);
waveCtx.fillText(`t = ${time.toFixed(2)} s`, waveCanvas.width - 150, 50);
waveCtx.fillText(`θ = ωt = ${angle.toFixed(2)} rad`, waveCanvas.width - 150, 70);
}
// Animation loop
function animate() {
if (!animating) return;
// Calculate the current angle based on time and frequency
// ω = 2πf, θ = ωt
const angle = (frequency * time) % (2 * Math.PI);
// Draw the circle and get the point coordinates
const point = drawCircle(angle);
// Update and draw the wave
updateWave(angle, point);
// Increment time
time += 0.02;
// Continue the animation loop
animationId = requestAnimationFrame(animate);
}
// Initialize and start animation
function init() {
initWavePoints();
time = 0;
updateFrequencyDisplay();
animate();
}
// Event listeners
frequencySlider.addEventListener('input', updateFrequencyDisplay);
pauseButton.addEventListener('click', function() {
animating = !animating;
if (animating) {
pauseButton.textContent = 'Pause';
animate();
} else {
pauseButton.textContent = 'Resume';
cancelAnimationFrame(animationId);
}
});
resetButton.addEventListener('click', function() {
time = 0;
initWavePoints();
});
// Start the animation
init();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment