{- |
Modularized interface to gnuplot
that allows complex graphics and fine control of their components.
It is designed for non-interactive use,
e.g. scripts for plotting statistics.

The hierarchy of objects is as follows:

* @Graph2D@: A curve like a sine curve.
  Attributes of a graph are line type, thickness, color.

* @Graph3D@: A surface.

* @Plot@: An overlay of many curves.
  It is parametrized by the graph type (2D or 3D)
  in order to make sure,
  that only graphs of one type are overlayed
  and only according attributes can be accessed.
  You cannot generate plots or graphs alone,
  you can only generate plots containing graphs
  using the functions in "Graphics.Gnuplot.Plot.TwoDimensional"
  and "Graphics.Gnuplot.Plot.ThreeDimensional".
  You can combine plots using the 'Monoid' type class.

* @Frame@: Add options to a plot
  such as border, legend, title, label attributes.
  See "Graphics.Gnuplot.Frame" and "Graphics.Gnuplot.Frame.OptionSet".

* @MultiPlot@: Arrange several frames in a matrix layout.
  See "Graphics.Gnuplot.MultiPlot".


Although the Haskell wrapper shall save you from the burden
of learning gnuplot script syntax,
it happens frequently that people ask,
how to express a certain gnuplot script using this package.
Thus let's annotate the gnuplot script generated by @Demo.multiplot@
in order to show, what belongs to where:

> # the terminal selection is part of the 'plot' command of this module
> set terminal x11
> # multiplot initialization belongs to MultiPlot - of course
> set multiplot layout 3, 5
> # hiding the names of the temporary files is a FrameOption
> unset key
> set xrange [-1.0:1.0]
> # this plot contains only one graph,
> # but several graphs could be given separated by commas
> plot "curve0.csv" using 1:2 with lines
> plot "curve1.csv" using 1:2 with lines
> plot "curve2.csv" using 1:2 with lines
> plot "curve3.csv" using 1:2 with lines
> plot "curve4.csv" using 1:2 with lines
> plot "curve5.csv" using 1:2 with lines
> plot "curve6.csv" using 1:2 with lines
> set xrange [-2.5:2.5]
> set yrange [-2.5:2.5]
> # this is a plot build from a Graph3D
> splot "curve7.csv" using 1:2:3 with pm3d
> set xrange [-1.0:1.0]
> set yrange [*:*]
> plot "curve8.csv" using 1:2 with lines
> plot "curve9.csv" using 1:2 with lines
> plot "curve10.csv" using 1:2 with lines
> plot "curve11.csv" using 1:2 with lines
> plot "curve12.csv" using 1:2 with lines
> plot "curve13.csv" using 1:2 with lines
> plot "curve14.csv" using 1:2 with lines
> unset multiplot

-}
module Graphics.Gnuplot.Advanced (
    plot,
    plotDefault,
    plotSync,
    plotAsync,
    fileContents,
  ) where

import qualified Graphics.Gnuplot.Private.FrameOptionSet as OptionSet
import qualified Graphics.Gnuplot.Private.Display as Display
import qualified Graphics.Gnuplot.Private.Terminal as Terminal
import qualified Graphics.Gnuplot.Terminal.Default as DefaultTerm
import qualified Graphics.Gnuplot.File as File

import qualified Graphics.Gnuplot.Private.Command as Cmd
import Control.Concurrent (ThreadId, forkIO, )
import System.Exit (ExitCode, )

import qualified Control.Monad.Trans.Reader as MR
import qualified Control.Monad.Trans.State as MS
import Data.Monoid (Monoid, mempty, )
import Control.Functor.HT (void, )
import Data.Tuple.HT (mapFst, )


-- * User front-end


{- |
The plot function returns 'ExitCode',
which is nice for programming
but ugly for interactive GHCi sessions.
For interactive sessions,
better use "Graphics.Gnuplot.Simple".
@gfx@ must be one of the types @Plot@, @Frame@, @MultiPlot@.

This function runs gnuplot asynchronously
for interactive terminals (X11, WX)
and synchronously for file terminals (PostScript, PNG, etc.).
This emulates the behaviour of @gnuplot --persist@.
However, when running asynchronous
we cannot obtain a real 'ExitCode'.
Thus, in this case we will always return 'ExitSuccess'.
-}
plot ::
   (Terminal.C terminal, Display.C gfx) =>
   terminal -> gfx -> IO ExitCode
plot :: terminal -> gfx -> IO ExitCode
plot terminal
term gfx
gfx =
   case terminal -> T
forall terminal. C terminal => terminal -> T
Terminal.canonical terminal
term of
      T
cterm ->
         Bool -> IO ExitCode -> IO ExitCode
Cmd.asyncIfInteractive
            (T -> Bool
Terminal.interactive T
cterm)
            (T -> gfx -> IO ExitCode
forall gfx. C gfx => T -> gfx -> IO ExitCode
plotCore T
cterm gfx
gfx)

plotAsync ::
   (Terminal.C terminal, Display.C gfx) =>
   terminal -> gfx -> IO ThreadId
plotAsync :: terminal -> gfx -> IO ThreadId
plotAsync terminal
term gfx
gfx = IO () -> IO ThreadId
forkIO (IO () -> IO ThreadId) -> IO () -> IO ThreadId
forall a b. (a -> b) -> a -> b
$ IO ExitCode -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO ExitCode -> IO ()) -> IO ExitCode -> IO ()
forall a b. (a -> b) -> a -> b
$ terminal -> gfx -> IO ExitCode
forall terminal gfx.
(C terminal, C gfx) =>
terminal -> gfx -> IO ExitCode
plotSync terminal
term gfx
gfx

plotSync ::
   (Terminal.C terminal, Display.C gfx) =>
   terminal -> gfx -> IO ExitCode
plotSync :: terminal -> gfx -> IO ExitCode
plotSync = T -> gfx -> IO ExitCode
forall gfx. C gfx => T -> gfx -> IO ExitCode
plotCore (T -> gfx -> IO ExitCode)
-> (terminal -> T) -> terminal -> gfx -> IO ExitCode
forall b c a. (b -> c) -> (a -> b) -> a -> c
. terminal -> T
forall terminal. C terminal => terminal -> T
Terminal.canonical

plotCore ::
   (Display.C gfx) =>
   Terminal.T -> gfx -> IO ExitCode
plotCore :: T -> gfx -> IO ExitCode
plotCore T
term gfx
gfx =
   (FilePath -> ([FilePath], [T])) -> IO ExitCode
forall file.
C file =>
(FilePath -> ([FilePath], [file])) -> IO ExitCode
Cmd.run ((FilePath -> ([FilePath], [T])) -> IO ExitCode)
-> (FilePath -> ([FilePath], [T])) -> IO ExitCode
forall a b. (a -> b) -> a -> b
$ T -> gfx -> FilePath -> ([FilePath], [T])
forall gfx. C gfx => T -> gfx -> FilePath -> ([FilePath], [T])
render T
term gfx
gfx

{- |
Return the gnuplot script and the curve files
corresponding to your plot data.
The first parameter is the directory where the curve files are located.
This directory is baked into the gnuplot script
and the paths of the curve files.

Don't make any assumptions about the structure of the files.
Feeding the files to gnuplot, archiving them or study them
are the intended uses of them.
-}
fileContents ::
   (Terminal.C terminal, Display.C gfx) =>
   FilePath -> terminal -> gfx -> (String, [File.T])
fileContents :: FilePath -> terminal -> gfx -> (FilePath, [T])
fileContents FilePath
dir terminal
term gfx
gfx =
   ([FilePath] -> FilePath) -> ([FilePath], [T]) -> (FilePath, [T])
forall a c b. (a -> c) -> (a, b) -> (c, b)
mapFst [FilePath] -> FilePath
unlines (([FilePath], [T]) -> (FilePath, [T]))
-> ([FilePath], [T]) -> (FilePath, [T])
forall a b. (a -> b) -> a -> b
$ T -> gfx -> FilePath -> ([FilePath], [T])
forall gfx. C gfx => T -> gfx -> FilePath -> ([FilePath], [T])
render (terminal -> T
forall terminal. C terminal => terminal -> T
Terminal.canonical terminal
term) gfx
gfx FilePath
dir

render ::
   Display.C gfx =>
   Terminal.T -> gfx -> FilePath -> ([String], [File.T])
render :: T -> gfx -> FilePath -> ([FilePath], [T])
render T
term gfx
gfx FilePath
dir =
   let body :: Body
body =
          (Reader FilePath Body -> FilePath -> Body)
-> FilePath -> Reader FilePath Body -> Body
forall a b c. (a -> b -> c) -> b -> a -> c
flip Reader FilePath Body -> FilePath -> Body
forall r a. Reader r a -> r -> a
MR.runReader FilePath
dir (Reader FilePath Body -> Body) -> Reader FilePath Body -> Body
forall a b. (a -> b) -> a -> b
$
          (StateT (Int, Plain) (Reader FilePath) Body
 -> (Int, Plain) -> Reader FilePath Body)
-> (Int, Plain)
-> StateT (Int, Plain) (Reader FilePath) Body
-> Reader FilePath Body
forall a b c. (a -> b -> c) -> b -> a -> c
flip StateT (Int, Plain) (Reader FilePath) Body
-> (Int, Plain) -> Reader FilePath Body
forall (m :: * -> *) s a. Monad m => StateT s m a -> s -> m a
MS.evalStateT (Int
0, Plain
OptionSet.initial) (StateT (Int, Plain) (Reader FilePath) Body
 -> Reader FilePath Body)
-> StateT (Int, Plain) (Reader FilePath) Body
-> Reader FilePath Body
forall a b. (a -> b) -> a -> b
$
          Script -> StateT (Int, Plain) (Reader FilePath) Body
Display.runScript (Script -> StateT (Int, Plain) (Reader FilePath) Body)
-> Script -> StateT (Int, Plain) (Reader FilePath) Body
forall a b. (a -> b) -> a -> b
$
          gfx -> Script
forall gfx. C gfx => gfx -> Script
Display.toScript gfx
gfx
   in  (T -> [FilePath]
Terminal.format T
term [FilePath] -> [FilePath] -> [FilePath]
forall a. [a] -> [a] -> [a]
++ Body -> [FilePath]
Display.commands Body
body,
        Body -> [T]
Display.files Body
body)


{- |
Plot using the default gnuplot terminal.
-}
plotDefault ::
   (Display.C gfx) =>
   gfx -> IO ExitCode
plotDefault :: gfx -> IO ExitCode
plotDefault =
   T -> gfx -> IO ExitCode
forall terminal gfx.
(C terminal, C gfx) =>
terminal -> gfx -> IO ExitCode
plot T
DefaultTerm.cons

{-
In the module introduction we refer to Monoid.
That is we must import Monoid module in order to make Haddock happy.
On the other hand we do not use Monoid in the module body,
thus GHC emits a warning.
This dummy declaration makes both GHC and Haddock happy.
-}
_haddockDummy :: Monoid a => a
_haddockDummy :: a
_haddockDummy = a
forall a. Monoid a => a
mempty