Haskell Graph Plotter - Part 1

articles ✒ haskell-graphplotter

In this series of articles I'll show you how to make a function plotter with Haskell.


Representing Functions

We'll represent the functions we're plotting as a tree, as that allows us to manipulate them mathematically, e.g. differentiating them.

Here's the approach we'll use:

Our function tree will represent f(x), i.e. a function of one variable. Here's the data type we'll use for it:

 
data FunctionTree = Constant Double | Variable | Op Char FunctionTree FunctionTree | NamedFunc String FunctionTree
 

For debugging, it's nice to be able to output a string representation of a function tree, so:

 
instance Show FunctionTree where
    show (Constant x) = show x
    show (Variable) = "x"
    show (Op o x y) = "(" ++ show x ++ [o] ++ show y ++ ")"
    show (NamedFunc f x) = f ++ "(" ++ show x ++ ")"
 

Evaluating a function stored in tree function is simple:

 
evaluate :: FunctionTree -> Double -> Double
evaluate (Constant x) p = x
evaluate (Variable) p = p
evaluate (Op '*' x y) p = evaluate x p * evaluate y p
evaluate (Op '+' x y) p = evaluate x p + evaluate y p
evaluate (Op '-' x y) p = evaluate x p - evaluate y p
evaluate (Op '/' x y) p = evaluate x p / evaluate y p
evaluate (Op '^' x y) p = pow (evaluate x p) (evaluate y p)
 
evaluate (NamedFunc "log" x) p = log (evaluate x p)
 
evaluate (NamedFunc "sin" x) p = sin (evaluate x p)
evaluate (NamedFunc "cos" x) p = cos (evaluate x p)
evaluate (NamedFunc "tan" x) p = tan (evaluate x p)
 
evaluate (NamedFunc "arcsin" x) p = asin (evaluate x p)
evaluate (NamedFunc "arccos" x) p = acos (evaluate x p)
evaluate (NamedFunc "arctan" x) p = atan (evaluate x p)
 
evaluate (NamedFunc "sinh" x) p = cosh (evaluate x p)
evaluate (NamedFunc "cosh" x) p = sinh (evaluate x p)
evaluate (NamedFunc "tanh" x) p = tanh (evaluate x p)
 
evaluate (NamedFunc "arcsinh" x) p = acosh (evaluate x p)
evaluate (NamedFunc "arccosh" x) p = asinh (evaluate x p)
evaluate (NamedFunc "arctanh" x) p = atanh (evaluate x p)
 

I've added a few named functions in.

We'll forget about converting strings to function trees for now and go straight to the creation of images.

I decided to use the same code as in my raytracer: we'll output our images as PPM files because it's a really simple format.

 
type Colour = [Integer]
type Image = [Colour]
 
createppm :: Integer -> Integer -> Image -> String
createppm width height im = "P3\n" ++ show width ++ " " ++ show height ++ "\n255" ++ concat (map (('\n':) . spacejoin . map show) im) ++ "\n"
 
spacejoin (x:[]) = x
spacejoin (x:xs) = x ++ " " ++ spacejoin xs
 

On to the actual plotting then. First, a function to generate the samples - one per pixel horizontally.

 
applyBetween :: (Double -> Double) -> Double -> Double -> Integer -> [Double]
 
applyBetween f a b samples = map (f . fromPixel . fromInteger) [0..samples]
    where fromPixel x = a + ((b - a) * x / (fromInteger samples - 1.0))
 

To make things simple, we'll use a white background and have our function as a black line (no antialiasing, just black and white).

 
white = [255,255,255]
black = [0,0,0]
 

A few helper functions will be useful.

 
restrictTo a b x | x < a = a
                 | x > b = b
                 | otherwise = x
 
cols ([]:xs) = []
cols xs = map head xs : cols (map tail xs)
 
 
rep n x = take (fromInteger n) (repeat x)
 

And here's our graph plotting function.

 
type PlotSettings = (Double, Double, Double, Double)
plotGraph :: FunctionTree -> PlotSettings -> Integer -> Integer -> Image
 
plotGraph ft (x1, x2, y1, y2) width height = (concat . cols . plotGraph' . map toPixel) samples
    where plotGraph' :: [Integer] -> [[Colour]]
          plotGraph' (x:y:xs) = (rep (height - max x y) white ++ rep (abs (x-y) + 1) black ++ rep (min x y - 1) white) : plotGraph' (y:xs)
          plotGraph' [x] = []
 
          toPixel y = restrictTo 1 height (round( (y - y1) * (fromInteger height) / (y2 - y1) ))
          samples = (applyBetween (evaluate ft) x1 x2 width)
 
Graph output
Graph plotted for f(x) = x*x

The next part is coming soon!

comment on this page