Created
June 14, 2026 15:36
-
-
Save gkucmierz/7c4520dff8f475e126978f896db1a9f7 to your computer and use it in GitHub Desktop.
Run this code instantly in your browser: https://instacode.app/gist/7c4520dff8f475e126978f896db1a9f7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { getContext, onResize } from 'canvas'; | |
| // Obtain the 2D rendering context | |
| const ctx = getContext('2d'); | |
| let width = 400; | |
| let height = 400; | |
| let theta = 0; // Current angle in radians | |
| let animationSpeed = 0.015; // Speed of rotation | |
| let isAnimating = true; | |
| // Setup canvas resize event listener | |
| onResize((w, h) => { | |
| width = w; | |
| height = h; | |
| draw(); | |
| }); | |
| // Main animation loop | |
| function animate() { | |
| if (isAnimating) { | |
| theta += animationSpeed; | |
| if (theta >= Math.PI) { | |
| theta = Math.PI; | |
| isAnimating = false; // Stop at pi (Euler's identity) | |
| // Pause at the end for 2 seconds, then restart the loop | |
| setTimeout(() => { | |
| theta = 0; | |
| isAnimating = true; | |
| animate(); // Safely restart the single animation loop | |
| }, 2000); | |
| } | |
| } | |
| draw(); | |
| // Only request next frame if we are actively animating! | |
| // This prevents loop accumulation and saves CPU when paused. | |
| if (isAnimating) { | |
| requestAnimationFrame(animate); | |
| } | |
| } | |
| // Draw the frame | |
| function draw() { | |
| // Clear canvas with a deep slate background | |
| ctx.fillStyle = '#0f172a'; | |
| ctx.fillRect(0, 0, width, height); | |
| const centerX = width / 2; | |
| const centerY = height / 2 + 30; // Shift down slightly to fit the headers | |
| const scale = Math.min(width, height) * 0.35; // Radius scale | |
| // Set line styles | |
| ctx.lineCap = 'round'; | |
| ctx.lineJoin = 'round'; | |
| // 1. Draw Grid/Axes | |
| ctx.strokeStyle = '#334155'; // Slate 700 | |
| ctx.lineWidth = 1.5; | |
| // Real axis (Re) | |
| ctx.beginPath(); | |
| ctx.moveTo(20, centerY); | |
| ctx.lineTo(width - 20, centerY); | |
| ctx.stroke(); | |
| // Imaginary axis (Im) | |
| ctx.beginPath(); | |
| ctx.moveTo(centerX, 20); | |
| ctx.lineTo(centerX, height - 20); | |
| ctx.stroke(); | |
| // 2. Draw Unit Circle | |
| ctx.strokeStyle = '#1e293b'; // Very dark slate | |
| ctx.lineWidth = 2; | |
| ctx.setLineDash([5, 5]); // Dotted line | |
| ctx.beginPath(); | |
| ctx.arc(centerX, centerY, scale, 0, Math.PI, true); // Semicircle | |
| ctx.stroke(); | |
| ctx.setLineDash([]); // Reset line dash | |
| // 3. Draw Axis Labels | |
| ctx.fillStyle = '#94a3b8'; // Slate 400 | |
| ctx.font = '14px sans-serif'; | |
| ctx.textAlign = 'center'; | |
| ctx.textBaseline = 'middle'; | |
| // Origin (0) | |
| ctx.fillText('0', centerX - 15, centerY + 15); | |
| // Scale labels | |
| ctx.fillText('1', centerX + scale, centerY + 20); | |
| ctx.fillText('-1', centerX - scale, centerY + 20); | |
| ctx.fillText('Re', width - 35, centerY - 15); | |
| // Imaginary labels | |
| ctx.fillText('i', centerX - 15, centerY - scale); | |
| ctx.fillText('Im', centerX + 25, 35); | |
| // 4. Draw Rotating Vector (Euler line) | |
| const px = centerX + Math.cos(theta) * scale; | |
| const py = centerY - Math.sin(theta) * scale; | |
| ctx.strokeStyle = '#6366f1'; // Indigo 500 | |
| ctx.lineWidth = 3; | |
| ctx.beginPath(); | |
| ctx.moveTo(centerX, centerY); | |
| ctx.lineTo(px, py); | |
| ctx.stroke(); | |
| // Angle arc | |
| ctx.strokeStyle = '#818cf8'; // Indigo 400 | |
| ctx.lineWidth = 1.5; | |
| ctx.beginPath(); | |
| ctx.arc(centerX, centerY, scale * 0.2, 0, -theta, true); | |
| ctx.stroke(); | |
| // 5. Draw Traced Semicircle | |
| ctx.strokeStyle = '#38bdf8'; // Sky 400 | |
| ctx.lineWidth = 4; | |
| ctx.beginPath(); | |
| ctx.arc(centerX, centerY, scale, 0, -theta, true); | |
| ctx.stroke(); | |
| // 6. Draw Moving Point with Glow | |
| ctx.fillStyle = '#38bdf8'; // Sky 400 | |
| ctx.shadowColor = '#38bdf8'; | |
| ctx.shadowBlur = 10; | |
| ctx.beginPath(); | |
| ctx.arc(px, py, 7, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.shadowBlur = 0; // Reset shadow blur to avoid performance drop on text | |
| // 7. Draw Texts & Equations | |
| ctx.fillStyle = '#f8fafc'; // White slate | |
| ctx.font = 'bold 20px sans-serif'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText("Euler's Identity Visualization", width / 2, 40); | |
| ctx.fillStyle = '#e2e8f0'; | |
| ctx.font = '16px monospace'; | |
| ctx.fillText(`theta = ${(theta / Math.PI).toFixed(2)} * pi rad`, width / 2, 80); | |
| // Display Real and Imaginary parts dynamically | |
| const cosVal = Math.cos(theta).toFixed(3); | |
| const sinVal = Math.sin(theta).toFixed(3); | |
| ctx.fillStyle = '#6366f1'; // Real part color (Indigo) | |
| ctx.textAlign = 'right'; | |
| ctx.fillText(`e^(i * theta) = ${cosVal}`, centerX + 20, 110); | |
| ctx.fillStyle = '#38bdf8'; // Imaginary part color (Sky) | |
| ctx.textAlign = 'left'; | |
| ctx.fillText(` + ${sinVal}i`, centerX + 20, 110); | |
| // Display the verified equation at the end of the rotation | |
| if (theta === Math.PI) { | |
| ctx.fillStyle = '#10b981'; // Emerald 500 | |
| ctx.font = 'bold 18px sans-serif'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText("Identity Verified: e^(i * pi) = -1", width / 2, 140); | |
| } | |
| } | |
| // Start the animation loop | |
| animate(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment