Haskell Graph Plotter - Part 1
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:
- input settings for the graph, e.g. width, height of the image produced, the function we want to plot
- convert the function from string to tree form
- evaluate the function tree at regular intervals (one sample per pixel) within the x-range given
- create a bitmap from the samples
- save it
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 plotted for f(x) = x*x
The next part is coming soon!
Copyright (C) 2006-8 Ryan Lothian. All rights reserved.
