module Graphics.EasyPlot (
Plot (plot, plot'),
Graph2D (..), Graph3D (..),
TerminalType (..),
Color (..), Style (..),
Option (..), Option2D (..), Option3D (..),
GnuplotOption (..)
) where
import Numeric (showHex)
import Data.Char (toUpper)
import Data.List (sortBy, nubBy)
import System.Cmd (rawSystem)
import System.Exit (ExitCode (ExitSuccess))
data TerminalType = Aqua
| Windows
| X11
| PS FilePath
| EPS FilePath
| PNG FilePath
| PDF FilePath
| SVG FilePath
| GIF FilePath
| JPEG FilePath
| Latex FilePath
data Style = Lines
| Points
| Dots
| Impulses
| Linespoints
data Color = Red | Blue | Green | Yellow | Orange | Magenta | Cyan
| DarkRed | DarkBlue | DarkGreen | DarkYellow | DarkOrange | DarkMagenta | DarkCyan
| LightRed | LightBlue | LightGreen | LightMagenta
| Violet | White | Brown | Grey | DarkGrey | Black
| RGB Int Int Int
data Style2D = Boxerrorbars | Boxes | Boxyerrorbars
| Filledcurves | Financebars | Fsteps | Histeps | Histograms
| Steps | Xerrorbars | Xyerrorbars | Yerrorbars | Xerrorlines
| Xyerrorlines | Yerrorlines
data Option = Style Style
| Title String
| Color Color
data Option2D x y = Range x x
| For [x]
| Step x
data Option3D x y z = RangeX x x
| RangeY y y
| ForX [x]
| ForY [y]
| StepX x
| StepY y
data Graph2D x y =
Function2D [Option] [Option2D x y] (x -> y)
| Data2D [Option] [Option2D x y] [(x, y)]
| Gnuplot2D [Option] [Option2D x y] String
data Graph3D x y z =
Function3D [Option] [Option3D x y z] (x -> y -> z)
| Data3D [Option] [Option3D x y z] [(x, y, z)]
| Gnuplot3D [Option] [Option3D x y z] String
data GnuplotOption = Interactive
| Debug
deriving Eq
class Plot a where
plot :: TerminalType
-> a
-> IO Bool
plot = plot' []
plot' :: [GnuplotOption]
-> TerminalType
-> a
-> IO Bool
instance (Fractional x, Enum x, Show x, Num y, Show y) => Plot (Graph2D x y) where
plot' options term graph = plot' options term [graph]
instance (Fractional x, Enum x, Show x, Num y, Show y) => Plot [Graph2D x y] where
plot' options term graphs = exec options [toString term] "plot" options' datasources
where (options', datasources) = unzip $ map prepare graphs
prepare (Gnuplot2D opt opt2d g) = (opts $ sanitize opt, Right $ g)
prepare (Data2D opt opt2d d) = (opts $ sanitize opt, Left $ toString d)
prepare (Function2D opt opt2d f) = (opt', Left $ plotData)
where (opt', plotData) = render2D opt opt2d f
instance (Fractional x, Enum x, Show x, Fractional y, Enum y, Show y, Num z, Show z) => Plot (Graph3D x y z) where
plot' options term graph = plot' options term [graph]
instance (Fractional x, Enum x, Show x, Fractional y, Enum y, Show y, Num z, Show z) => Plot [Graph3D x y z] where
plot' options term graphs = exec options [toString term] "splot" options' datasources
where (options', datasources) = unzip $ map prepare graphs
prepare (Gnuplot3D opt opt3d g) = (opts $ sanitize opt, Right $ g)
prepare (Data3D opt opt3d d) = (opts $ sanitize opt, Left $ toString d)
prepare (Function3D opt opt3d f) = (opt', Left $ plotData)
where (opt', plotData) = render3D opt opt3d f
instance (Fractional x, Enum x, Show x, Num y, Show y) => Plot (x -> y) where
plot' options term f = plot' options term $ Function2D [] [] f
instance (Fractional x, Enum x, Show x, Num y, Show y) => Plot [x -> y] where
plot' options term fs = plot' options term $ map (Function2D [] []) fs
instance (Fractional x, Enum x, Show x, Fractional y, Enum y, Show y, Num z, Show z) => Plot (x -> y -> z) where
plot' options term f = plot' options term $ Function3D [] [] f
instance (Fractional x, Enum x, Show x, Fractional y, Enum y, Show y, Num z, Show z) => Plot [x -> y -> z] where
plot' options term fs = plot' options term $ map (Function3D [] []) fs
instance (Fractional x, Enum x, Num x, Show x, Num y, Show y) => Plot [(x, y)] where
plot' options term d = plot' options term $ Data2D [] [] d
instance (Fractional x, Enum x, Show x, Fractional y, Enum y, Show y, Num z, Show z) => Plot [(x, y, z)] where
plot' options term d = plot' options term $ Data3D [] [] d
instance Plot String where
plot' options term g = plot' options term $ Gnuplot3D [] [] g
instance Plot [String] where
plot' options term g = plot' options term $ map (Gnuplot3D [] []) g
render2D opt opt2d f = (opts $ sanitize (opt ++ [Style Lines]), plot2D f)
where plot2D f = toString [(x, f x) | x <- maybe [x1,sx..x2] id $ for opt2d]
(x1, x2) = range opt2d
sx = x1 + step opt2d
render3D opt opt3d f = (opts $ sanitize (opt), plot3D f)
where plot3D f = toString [(x, y, f x y) | x <- xs, y <- ys]
xs = maybe [x1,sx..x2] id $ forX opt3d
ys = maybe [y1,sy..y2] id $ forY opt3d
((x1, x2), (y1, y2)) = (rangeX opt3d, rangeY opt3d)
(sx, sy) = (x1 + stepX opt3d, y1 + stepY opt3d)
for [] = Nothing
for ((For xs) : _) = Just xs
for (_ : xs) = for xs
range [] = (5, 5)
range ((Range x1 x2) : _) = (x1, x2)
range (_ : xs) = range xs
step [] = 0.05
step ((Step x) : _) = x
step (_ : xs) = step xs
forX [] = Nothing
forX ((ForX xs) : _) = Just xs
forX (_ : xs) = forX xs
forY [] = Nothing
forY ((ForY ys) : _) = Just ys
forY (_ : ys) = forY ys
rangeX [] = (5, 5)
rangeX ((RangeX x1 x2) : _) = (x1, x2)
rangeX (_ : xs) = rangeX xs
rangeY [] = (5, 5)
rangeY ((RangeY y1 y2) : _) = (y1, y2)
rangeY (_ : ys) = rangeY ys
stepX [] = 0.1
stepX ((StepX x) : _) = x
stepX (_ : xs) = stepX xs
stepY [] = 0.1
stepY ((StepY y) : _) = y
stepY (_ : ys) = stepY ys
sanitize = sortBy ord . nubBy dup
where ord a b
| dup a b = EQ
| True = ord' a b
ord' (Style _) (Title _) = LT
ord' (Style _) (Color _) = LT
ord' (Color _) (Title _) = GT
ord' a b
| ord' b a == LT = GT
| True = LT
dup (Title _) (Title _) = True
dup (Style _) (Style _) = True
dup (Color _) (Color _) = True
dup _ _ = False
opts [] = ""
opts [x] = toString x
opts (x:xs) = toString x ++ " " ++ opts xs
exec :: [GnuplotOption] -> [String] -> String -> [String] -> [Either String String] -> IO Bool
exec options preamble plotfunc plotops datasets =
do
let filenames = zipWith (\x y -> x ++ show y ++ ".dat")
(cycle ["plot"]) [1..length datasets]
mapM (uncurry writeFile) (zip filenames (map (either id id) datasets))
let datasources = zipWith (\x y -> either (const (Left x)) Right y) filenames datasets
file y x = "\"" ++ x ++ "\" " ++ y
func y x = x ++ " " ++ y
plotcmds = zipWith (\x y -> either (file y) (func y) x) datasources plotops
plotstmt = foldl1 (\x y -> x ++ ", " ++ y) plotcmds
plotcmd = foldl1 (\x y -> x ++ "; " ++ y)
(preamble ++ [plotfunc ++ " " ++ plotstmt])
args = ["-e", plotcmd] ++ if Interactive `elem` options then ["-"] else []
if Debug `elem` options then putStrLn plotcmd else return ()
exitCode <- rawSystem "gnuplot" args
return $ exitCode == ExitSuccess
class GnuplotIdiom a where
toString :: a -> String
instance (Num x, Show x, Num y, Show y) => GnuplotIdiom (x, y) where
toString (x, y) = space $ shows x $ space $ show y
instance (Num x, Show x, Num y, Show y, Num z, Show z) => GnuplotIdiom (x, y, z) where
toString (x, y, z) = space $ shows x $ space $ shows y $ space $ show z
space x = ' ' : x
instance GnuplotIdiom Style where
toString x = case x of
Lines -> "with lines"
Points -> "with points"
Dots -> "with dots"
Impulses -> "with impulses"
Linespoints -> "with linespoints"
instance GnuplotIdiom Option where
toString x = case x of
Title t -> "title \"" ++ t ++ "\""
Style s -> toString s
Color c -> "lc rgb \"" ++ toString c ++ "\""
instance GnuplotIdiom x => GnuplotIdiom [x] where
toString = unlines . map toString
instance GnuplotIdiom (TerminalType) where
toString t = case t of
PNG f -> "set term png; set output \"" ++ f ++ "\""
PDF f -> "set term pdf enhanced; set output \"" ++ f ++ "\""
SVG f -> "set term svg dynamic; set output \"" ++ f ++ "\""
GIF f -> "set term gif; set output \"" ++ f ++ "\""
JPEG f -> "set term jpeg; set output \"" ++ f ++ "\""
Latex f -> "set term latex; set output \"" ++ f ++ "\""
EPS f -> "set term postscript eps; set output \"" ++ f ++ "\""
PS f -> "set term postscript; set output \"" ++ f ++ "\""
Aqua -> "set term aqua"
Windows -> "set term windows"
X11 -> "set term x11 persist"
instance GnuplotIdiom (Color) where
toString (RGB r g b) = '#' : map toUpper (showHex r $ showHex g $ showHex b "")
toString color = case color of
Red -> "red"
Blue -> "blue"
Green -> "green"
Yellow -> "yellow"
Orange -> "orange"
Magenta -> "magenta"
Cyan -> "cyan"
DarkRed -> "dark-red"
DarkBlue -> "dark-blue"
DarkGreen -> "dark-green"
DarkYellow -> "dark-yellow"
DarkOrange -> "dark-orange"
DarkMagenta -> "aark-magenta"
DarkCyan -> "dark-cyan"
LightRed -> "light-red"
LightBlue -> "light-blue"
LightGreen -> "light-green"
LightMagenta -> "light-magenta"
Violet -> "violet"
Grey -> "grey"
White -> "white"
Brown -> "brown"
DarkGrey -> "dark-grey"
Black -> "black"