{-# OPTIONS -Wall #-} module AnimateFunction where ------------------------------------------------------------------------- -- Make an SVG animation of a function of x and t. -- -- The main functions to use are animateFunction and animateFunctions. -- ------------------------------------------------------------------------- animateFunction :: FilePath -> ((R,R) -> R) -> (R,R,R) -> (R,R,R) -> IO () animateFunction filename f (xMin,dx,xMax) (tMin,dt,tMax) = let xs = [xMin, xMin + dx .. xMax] ts = [tMin, tMin + dt .. tMax] xyPairs :: R -> [(R,R)] xyPairs t = [(x, f (x,t)) | x <- xs] ys = concat [map snd (xyPairs t) | t <- ts] yMin = minimum ys yMax = maximum ys scaledPairs t = scalePairs xMin xMax yMin yMax (xyPairs t) svgStart = "\n" ++ "\n" ++ "\n" svgEnd = "\n" matter = concat [oneCurve t dt (0,0,255) (scaledPairs t) | t <- ts] in writeFile filename $ svgStart ++ matter ++ svgEnd animateFunctions :: FilePath -> [((R,R) -> R,(Int,Int,Int))] -> (R,R,R) -> (R,R,R) -> IO () animateFunctions filename prs (xMin,dx,xMax) (tMin,dt,tMax) = let (fs,cs) = unzip prs anims = makeAnimations fs (xMin,dx,xMax) (tMin,dt,tMax) (yMin,yMax) = animationsRange anims scaledAnims = scaleAnimations xMin xMax yMin yMax anims scaledACs = zip scaledAnims cs svgStart = "\n" ++ "\n" ++ "\n" svgEnd = "\n" matter = animationsMatter scaledACs in writeFile filename $ svgStart ++ matter ++ svgEnd -------------- -- Examples -- -------------- -- traveling wave anim01 :: IO () anim01 = let k = 1 omega = 1 in animateFunction "anim01.svg" (\(x,t) -> cos (k * x - omega * t)) (0,0.1,10) (0,0.1,10) -- standing wave anim02 :: IO () anim02 = let k = 3 * pi / 10 omega = 1 in animateFunction "anim02.svg" (\(x,t) -> sin (k * x) * cos (omega * t)) (0,0.1,10) (0,0.1,10) -- triangle pulse anim03 :: IO () anim03 = let k = 1 omega = 1 triangle x | x < 0 = 0 | x < 0.5 = 2 * x | x < 1 = 2 - 2 * x | otherwise = 0 in animateFunction "anim03.svg" (\(x,t) -> triangle (k * x - omega * t)) (0,0.1,10) (0,0.1,10) -- Gaussian pulse anim04 :: IO () anim04 = let k = 1 v = 1 in animateFunction "anim04.svg" (\(x,t) -> 1000 * exp (-k * (x - v * t)**2) - 300) (-5,0.1,5) (0,0.1,10) -- narrowing Gaussian anim05 :: IO () anim05 = let a t = 6 - t in animateFunction "anim05.svg" (\(x,t) -> exp (-(x/a t)**2) / (a t * sqrt pi)) (-2,0.1,2) (1,0.1,5) -- standing wave of E and B anim06 :: IO () anim06 = animateFunctions "anim06.svg" [(\(x,t) -> sin (x - t) + sin (x + t),(0,0,255)) ,(\(x,t) -> sin (x - t) - sin (x + t),(255,0,0))] (0,0.1,3*pi) (0,0.1,10) --------------------- -- Supporting code -- --------------------- type R = Double lineToText :: (R,R) -> String lineToText (x,y) = "L " ++ show x ++ "," ++ show (-y) ++ " " framePathText :: [(R,R)] -> String framePathText xys = 'M' : tail (concatMap lineToText xys) oneCurve :: R -> R -> (Int,Int,Int) -> [(R,R)] -> String oneCurve t dt rgb xys = let fpt = framePathText xys pre = "" middle = " \n" post = "" in pre ++ middle ++ post -- scale to the range -1 to 1 scale :: R -> R -> R -> R scale xmin xmax x = 2 * (x - xmin) / (xmax - xmin) - 1 scalePair :: R -> R -> R -> R -> (R,R) -> (R,R) scalePair xmin xmax ymin ymax (x,y) = (scale xmin xmax x, scale ymin ymax y) scalePairs :: R -> R -> R -> R -> [(R,R)] -> [(R,R)] scalePairs xmin xmax ymin ymax = map $ scalePair xmin xmax ymin ymax type Curve = [(R,R)] -- list of xy pairs type Animation = ([(Curve,R)],R) -- ([(curve,t)],dt) curveRange :: [(R,R)] -> (R,R) curveRange xys = let ys = map snd xys in (minimum ys, maximum ys) animationRange :: Animation -> (R,R) animationRange (cts,_) = curveRange $ concatMap fst cts animationsRange :: [Animation] -> (R,R) animationsRange anims = let ranges = map animationRange anims (yMins,yMaxs) = unzip ranges in (minimum yMins, maximum yMaxs) makeCurve :: ((R,R) -> R) -> (R,R,R) -> R -> Curve makeCurve f (xMin,dx,xMax) t = let xs = [xMin, xMin + dx .. xMax] in [(x, f (x,t)) | x <- xs] makeAnimation :: ((R,R) -> R) -> (R,R,R) -> (R,R,R) -> Animation makeAnimation f (xMin,dx,xMax) (tMin,dt,tMax) = let ts = [tMin, tMin + dt .. tMax] in ([(makeCurve f (xMin,dx,xMax) t,t) | t <- ts],dt) makeAnimations :: [(R,R) -> R] -> (R,R,R) -> (R,R,R) -> [Animation] makeAnimations fs (xMin,dx,xMax) (tMin,dt,tMax) = map (\f -> makeAnimation f (xMin,dx,xMax) (tMin,dt,tMax)) fs scaleCurve :: R -> R -> R -> R -> Curve -> Curve scaleCurve = scalePairs scaleAnimation :: R -> R -> R -> R -> Animation -> Animation scaleAnimation xmin xmax ymin ymax (prs,dt) = let newPrs = map (\(c,t) -> (scaleCurve xmin xmax ymin ymax c, t)) prs in (newPrs,dt) scaleAnimations :: R -> R -> R -> R -> [Animation] -> [Animation] scaleAnimations xmin xmax ymin ymax = map (scaleAnimation xmin xmax ymin ymax) -- use on a scaled animation animationMatter :: Animation -> (Int,Int,Int) -> String animationMatter (prs,dt) rgb = concat [oneCurve t dt rgb c | (c,t) <- prs] animationsMatter :: [(Animation,(Int,Int,Int))] -> String animationsMatter = concatMap (\(a,c) -> animationMatter a c)