summaryrefslogtreecommitdiff
path: root/src/Text/Pandoc/Filter/Plot.hs
blob: d2bd2a42dcc19ae6887007ef1c0ffeb24618e235 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
{-# LANGUAGE OverloadedStrings #-}
{-|
Module      : $header$
Description : Pandoc filter to create figures from code blocks using your plotting toolkit of choice
Copyright   : (c) Laurent P René de Cotret, 2020
License     : GNU GPL, version 2 or above
Maintainer  : laurent.decotret@outlook.com
Stability   : unstable
Portability : portable

This module defines a Pandoc filter @plotTransform@ and related functions
that can be used to walk over a Pandoc document and generate figures from
code blocks, using a multitude of plotting toolkits.

The syntax for code blocks is simple. Code blocks with the appropriate class
attribute will trigger the filter:

*   @matplotlib@ for matplotlib-based Python plots;
*   @plotly_python@ for Plotly-based Python plots;
*   @matlabplot@ for MATLAB plots;
*   @mathplot@ for Mathematica plots;
*   @octaveplot@ for GNU Octave plots;
*   @ggplot2@ for ggplot2-based R plots;
*   @gnuplot@ for gnuplot plots;

For example, in Markdown:

@
    This is a paragraph.

    ```{.matlabplot}
    figure()
    plot([1,2,3,4,5], [1,2,3,4,5], '-k)
    ```
@

The code block will be reworked into a script and the output figure will be captured. Optionally, the source code
 used to generate the figure will be linked in the caption.

Here are the possible attributes what pandoc-plot understands for ALL toolkits:

    * @directory=...@ : Directory where to save the figure.
    * @source=true|false@ : Whether or not to link the source code of this figure in the caption. Ideal for web pages, for example. Default is false.
    * @format=...@: Format of the generated figure. This can be an extension or an acronym, e.g. @format=PNG@.
    * @caption="..."@: Specify a plot caption (or alternate text). Format for captions is specified in the documentation for the @Configuration@ type.
    * @dpi=...@: Specify a value for figure resolution, or dots-per-inch. Certain toolkits ignore this.
    * @preamble=...@: Path to a file to include before the code block. Ideal to avoid repetition over many figures.

Default values for the above attributes are stored in the @Configuration@ datatype. These can be specified in a 
YAML file which must be named ".pandoc-plot.yml".

Here is an example code block which will render a figure using gnuplot, in Markdown:

@
    ```{.gnuplot format=png caption="Sinusoidal function" source=true}
    sin(x)

    set xlabel "x"
    set ylabel "y"
    ```
@
-}
module Text.Pandoc.Filter.Plot (
    -- * Operating on single Pandoc blocks
      makePlot
    -- * Operating on whole Pandoc documents
    , plotTransform
    -- * Cleaning output directories
    , cleanOutputDirs
    -- * Runtime configuration
    , configuration
    , Configuration(..)
    , SaveFormat(..)
    , Script
    -- * For testing and internal purposes ONLY
    , make
    , readDoc
    , availableToolkits
    , unavailableToolkits
    ) where

import           Control.Monad.IO.Class           (liftIO)
import           Control.Monad.Reader             (runReaderT)

import           System.IO                        (hPutStrLn, stderr)

import           Text.Pandoc.Definition
import           Text.Pandoc.Walk                 (walkM)

import           Text.Pandoc.Filter.Plot.Internal


-- | Highest-level function that can be walked over a Pandoc tree.
-- All code blocks that have the appropriate class names will be considered
-- figures, e.g. @.matplotlib@.
--
-- Failing to render a figure does not stop the filter, so that you may run the filter
-- on documents without having all necessary toolkits installed. In this case, error
-- messages are printed to stderr, and blocks are left unchanged.
makePlot :: Configuration -- ^ Configuration for default values
         -> Block 
         -> IO Block
makePlot conf block = maybe (return block) (\tk -> make tk conf block) (plotToolkit block)


-- | Walk over an entire Pandoc document, changing appropriate code blocks
-- into figures. 
--
-- Failing to render a figure does not stop the filter, so that you may run the filter
-- on documents without having all necessary toolkits installed. In this case, error
-- messages are printed to stderr, and blocks are left unchanged.
plotTransform :: Configuration -- ^ Configuration for default values
              -> Pandoc        -- ^ Input document
              -> IO Pandoc
plotTransform conf = walkM $ makePlot conf


-- | Force to use a particular toolkit to render appropriate code blocks.
--
-- Failing to render a figure does not stop the filter, so that you may run the filter
-- on documents without having all necessary toolkits installed. In this case, error
-- messages are printed to stderr, and blocks are left unchanged.
make :: Toolkit       -- ^ Plotting toolkit.
     -> Configuration -- ^ Configuration for default values.
     -> Block 
     -> IO Block
make tk conf block = runReaderT (makePlot' block) (PlotEnv tk conf)
    where
        makePlot' :: Block -> PlotM Block
        makePlot' blk 
            = parseFigureSpec blk 
            >>= maybe 
                    (return blk) 
                    (\s -> runScriptIfNecessary s >>= handleResult s)
            where                
                handleResult spec ScriptSuccess         = return $ toImage (captionFormat conf) spec
                handleResult _ (ScriptChecksFailed msg) = do
                    liftIO $ hPutStrLn stderr $ " ERROR (pandoc-plot) The script check failed with message: " <> msg 
                    return blk
                handleResult _ (ScriptFailure _ code) = do
                    liftIO $ hPutStrLn stderr $ "ERROR (pandoc-plot) The script failed with exit code " <> show code 
                    return blk
                handleResult _ (ToolkitNotInstalled tk') = do
                    liftIO $ hPutStrLn stderr $ "ERROR (pandoc-plot) The " <> show tk' <> " toolkit is required but not installed."
                    return blk