'use client'; import React, { useState, useEffect, useRef, useMemo } from 'react'; const VIEW_WIDTH = 1000; const VIEW_HEIGHT = 600; const MAX_WAVE_POINTS = 600; const SPEED = 0.015; export const FourierSeriesView = () => { // Animation state const [time, setTime] = useState(0); const [nTerms, setNTerms] = useState(1); const [wavePath, setWavePath] = useState<{x: number, y: number}[]>([]); // Refs for animation loop logic to avoid closure staleness const timeRef = useRef(0); const wavePathRef = useRef<{y: number}[]>([]); const requestRef = useRef(); const cycleRef = useRef(0); // Logic to auto-increment N (number of terms) to show the approximation evolving useEffect(() => { const interval = setInterval(() => { setNTerms(prev => { const next = prev + 2; return next > 15 ? 1 : next; // Cycle 1, 3, 5 ... 15 then reset }); // Clear wave path when N changes for a cleaner look wavePathRef.current = []; }, 4000); return () => clearInterval(interval); }, []); // Main Animation Loop useEffect(() => { const animate = () => { timeRef.current += SPEED; // Calculate the final Y value based on current N terms let x = 0; let y = 0; // Square wave approximation formula: 4/pi * sum(sin(n*theta)/n) for odd n for (let i = 0; i < nTerms; i++) { const n = i * 2 + 1; // 1, 3, 5... const radius = 100 * (4 / (n * Math.PI)); y += radius * Math.sin(n * timeRef.current); x += radius * Math.cos(n * timeRef.current); } // Update Wave History wavePathRef.current.unshift({ y }); if (wavePathRef.current.length > MAX_WAVE_POINTS) { wavePathRef.current.pop(); } // Sync refs to state for rendering setTime(timeRef.current); // We map the y-values to x-coordinates for the visual wave setWavePath(wavePathRef.current.map((pt, index) => ({ x: 350 + index, // Start drawing wave at x=350 y: pt.y }))); requestRef.current = requestAnimationFrame(animate); }; requestRef.current = requestAnimationFrame(animate); return () => { if (requestRef.current) cancelAnimationFrame(requestRef.current); }; }, [nTerms]); // Calculate the circles (vectors) for the current frame const circles = useMemo(() => { let cx = 200; // Center of the first circle let cy = VIEW_HEIGHT / 2; const elements: JSX.Element[] = []; for (let i = 0; i < nTerms; i++) { const n = i * 2 + 1; const radius = 100 * (4 / (n * Math.PI)); const angle = n * time; const nextX = cx + radius * Math.cos(angle); const nextY = cy + radius * Math.sin(angle); // Circle Outline elements.push( ); // Vector Line elements.push( ); // Pivot Point elements.push( ); cx = nextX; cy = nextY; } return { elements, finalX: cx, finalY: cy }; }, [time, nTerms]); // Generate SVG Path for the wave const waveSvgPath = useMemo(() => { if (wavePath.length === 0) return ""; // M x y L x y ... return `M ${wavePath.map(p => `${p.x} ${VIEW_HEIGHT / 2 + p.y}`).join(' L ')}`; }, [wavePath]); return ( {/* Glow Filter */} {/* Grid Pattern */} {/* Gradient for Wave Fade */} {/* Background Grid */} {/* Vignette Overlay */} {/* Title & Info */} FOURIER SERIES Decomposition: Square Wave Terms (N): {nTerms} {/* Math Formula */} f(t) ≈ 4/π * Σ (sin(nt)/n) for n = 1, 3, 5, ... {/* Axis Lines */} {/* Rotating Circles Group */} {circles.elements} {/* Connecting Line (Phasor Tip to Wave Start) */} 0 ? wavePath[0].x : 350} y2={wavePath.length > 0 ? VIEW_HEIGHT / 2 + wavePath[0].y : VIEW_HEIGHT/2} stroke="#94a3b8" strokeWidth="1" strokeDasharray="4 4" opacity="0.5" /> {/* The Resulting Wave */} {/* Wave Tracking Dot */} {wavePath.length > 0 && ( )} {/* Tech Decoration: Corner brackets */} ); };