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 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
|
{-# LANGUAGE CPP, FlexibleInstances, MultiParamTypeClasses #-}
{- |
Module : Data.GraphViz.Types.Monadic
Description : A monadic interface for making Dot graphs.
Copyright : (c) Ivan Lazar Miljenovic
License : 3-Clause BSD-style
Maintainer : Ivan.Miljenovic@gmail.com
This module is based upon the /dotgen/ library by Andy Gill:
<http://hackage.haskell.org/package/dotgen>
It provides a monadic interface for constructing generalised Dot
graphs. Note that this does /not/ have an instance for @DotRepr@
(e.g. what would be the point of the @fromCanonical@ function, as
you can't do anything with the result): it is purely for
construction purposes. Use the generalised Dot graph instance for
printing, etc.
Note that the generalised Dot graph types are /not/ re-exported, in
case it causes a clash with other modules you may choose to import.
The example graph in "Data.GraphViz.Types" can be written as:
> digraph (Str "G") $ do
>
> cluster (Int 0) $ do
> graphAttrs [style filled, color LightGray]
> nodeAttrs [style filled, color White]
> "a0" --> "a1"
> "a1" --> "a2"
> "a2" --> "a3"
> graphAttrs [textLabel "process #1"]
>
> cluster (Int 1) $ do
> nodeAttrs [style filled]
> "b0" --> "b1"
> "b1" --> "b2"
> "b2" --> "b3"
> graphAttrs [textLabel "process #2", color Blue]
>
> "start" --> "a0"
> "start" --> "b0"
> "a1" --> "b3"
> "b2" --> "a3"
> "a3" --> "end"
> "b3" --> "end"
>
> node "start" [shape MDiamond]
> node "end" [shape MSquare]
-}
module Data.GraphViz.Types.Monadic
( Dot
, DotM
, GraphID(..)
-- * Creating a generalised DotGraph.
, digraph
, digraph'
, graph
, graph'
-- * Adding global attributes.
, graphAttrs
, nodeAttrs
, edgeAttrs
-- * Adding items to the graph.
-- ** Subgraphs and clusters
, subgraph
, anonSubgraph
, cluster
-- ** Nodes
, node
, node'
-- ** Edges
-- $edges
, edge
, (-->)
, (<->)
) where
import Data.GraphViz.Attributes (Attributes)
import Data.GraphViz.Types.Generalised
import Data.DList (DList)
import qualified Data.DList as DL
import qualified Data.Sequence as Seq
#if !(MIN_VERSION_base (4,8,0))
import Control.Applicative (Applicative(..))
import Data.Monoid (Monoid(..))
#endif
#if MIN_VERSION_base (4,9,0) && !MIN_VERSION_base (4,13,0)
import Data.Semigroup (Semigroup(..))
#endif
import Control.Monad.Fix (MonadFix (mfix))
-- -----------------------------------------------------------------------------
-- The Dot monad.
-- | The monadic representation of a Dot graph.
type Dot n = DotM n ()
-- | The actual monad; as with 'Dot' but allows you to return a value
-- within the do-block. The actual implementation is based upon the
-- Writer monad.
newtype DotM n a = DotM { runDot :: (a, DotStmts n) }
execDot :: DotM n a -> DotStmts n
execDot = snd . runDot
instance Functor (DotM n) where
fmap f (DotM (a,stmts)) = DotM (f a, stmts)
instance Applicative (DotM n) where
pure = DotM . flip (,) DL.empty
(DotM (f,stmts1)) <*> (DotM (a,stmts2)) = DotM (f a, stmts1 `DL.append` stmts2)
instance Monad (DotM n) where
return = pure
dt >>= f = DotM
$ let ~(a,stmts) = runDot dt
~(b,stmts') = runDot $ f a
in (b, stmts `DL.append` stmts')
instance MonadFix (DotM n) where
mfix m = let (a,n) = runDot $ m a
in DotM (a,n)
#if MIN_VERSION_base (4,9,0)
instance Semigroup a => Semigroup (DotM n a) where
DotM x1 <> DotM x2 = DotM (x1 <> x2)
#endif
instance Monoid a => Monoid (DotM n a) where
mappend (DotM x1) (DotM x2) = DotM (mappend x1 x2)
mempty = DotM mempty
tell :: DotStmts n -> Dot n
tell = DotM . (,) ()
tellStmt :: DotStmt n -> Dot n
tellStmt = tell . DL.singleton
-- -----------------------------------------------------------------------------
-- Creating the DotGraph
-- | Create a directed dot graph with the specified graph ID.
digraph :: GraphID -> DotM n a -> DotGraph n
digraph = mkGraph True . Just
-- | Create a directed dot graph with no graph ID.
digraph' :: DotM n a -> DotGraph n
digraph' = mkGraph True Nothing
-- | Create a undirected dot graph with the specified graph ID.
graph :: GraphID -> DotM n a -> DotGraph n
graph = mkGraph False . Just
-- | Create a undirected dot graph with no graph ID.
graph' :: DotM n a -> DotGraph n
graph' = mkGraph False Nothing
mkGraph :: Bool -> Maybe GraphID -> DotM n a -> DotGraph n
mkGraph isDir mid dot = DotGraph { strictGraph = False
, directedGraph = isDir
, graphID = mid
, graphStatements = execStmts dot
}
-- -----------------------------------------------------------------------------
-- Statements
type DotStmts n = DList (DotStmt n)
execStmts :: DotM n a -> DotStatements n
execStmts = convertStatements . execDot
convertStatements :: DotStmts n -> DotStatements n
convertStatements = Seq.fromList . map convertStatement . DL.toList
data DotStmt n = MA GlobalAttributes
| MS (Subgraph n)
| MN (DotNode n)
| ME (DotEdge n)
convertStatement :: DotStmt n -> DotStatement n
convertStatement (MA gas) = GA gas
convertStatement (MS sg) = SG . DotSG (sgIsClust sg) (sgID sg)
. execStmts $ sgStmts sg
convertStatement (MN dn) = DN dn
convertStatement (ME de) = DE de
-- -----------------------------------------------------------------------------
-- Global Attributes
-- | Add graph\/sub-graph\/cluster attributes.
graphAttrs :: Attributes -> Dot n
graphAttrs = tellStmt . MA . GraphAttrs
-- | Add global node attributes.
nodeAttrs :: Attributes -> Dot n
nodeAttrs = tellStmt . MA . NodeAttrs
-- | Add global edge attributes
edgeAttrs :: Attributes -> Dot n
edgeAttrs = tellStmt . MA . EdgeAttrs
-- -----------------------------------------------------------------------------
-- Subgraphs (including Clusters)
data Subgraph n = Sg { sgIsClust :: Bool
, sgID :: Maybe GraphID
, sgStmts :: Dot n
}
-- | Add a named subgraph to the graph.
subgraph :: GraphID -> DotM n a -> Dot n
subgraph = nonClust . Just
-- | Add an anonymous subgraph to the graph.
--
-- It is highly recommended you use 'subgraph' instead.
anonSubgraph :: DotM n a -> Dot n
anonSubgraph = nonClust Nothing
nonClust :: Maybe GraphID -> DotM n a -> Dot n
nonClust = createSubGraph False
createSubGraph :: Bool -> Maybe GraphID -> DotM n a -> Dot n
createSubGraph isCl mid = tellStmt . MS . Sg isCl mid . (>> return ())
-- | Add a named cluster to the graph.
cluster :: GraphID -> DotM n a -> Dot n
cluster = createSubGraph True . Just
-- -----------------------------------------------------------------------------
-- Nodes
-- | Add a node to the graph.
node :: n -> Attributes -> Dot n
node n = tellStmt . MN . DotNode n
-- | Add a node with no attributes to the graph.
node' :: n -> Dot n
node' = (`node` [])
-- -----------------------------------------------------------------------------
-- Edges
{- $edges
If you wish to use something analogous to Dot's ability to write
multiple edges with in-line subgraphs such as:
> {a b c} -> {d e f}
Then you can use '-->' and '<->' in combination with monadic
traversal functions such as @traverse_@, @for_@, @mapM_@, @forM_@
and @zipWithM_@; for example:
> ("a" -->) `traverse_` ["d", "e", "f"]
> ["a", "b", "c"] `for_` (--> "d")
> zipWithM_ (-->) ["a", "b", "c"] ["d", "e", "f"]
-}
-- | Add an edge to the graph.
edge :: n -> n -> Attributes -> Dot n
edge f t = tellStmt . ME . DotEdge f t
-- | Add an edge with no attributes.
(-->) :: n -> n -> Dot n
f --> t = edge f t []
infixr 9 -->
-- | An alias for '-->' to make edges look more undirected.
(<->) :: n -> n -> Dot n
(<->) = (-->)
infixr 9 <->
-- -----------------------------------------------------------------------------
|