{-# LANGUAGE OverloadedStrings #-} -- | Generating graph contents module Text.Dot.Gen ( module Text.Dot.Gen , module Text.Dot.Attributes , Dot , DotGraph , NodeId , Attribute , AttributeName , AttributeValue , DecType , RankdirType ) where import Text.Dot.Attributes import Text.Dot.Types.Internal import Control.Monad.State (StateT, get, modify, put, runStateT) import Control.Monad.Writer (WriterT, runWriterT, tell) import Data.Monoid ((<>)) import Data.Text (Text) import qualified Data.Text as T -- | Generate a haphviz graph with a given name and content -- -- >>> graph directed "mygraph" $ do -- a <- node "a" -- b <- node "b" -- a --> b -- > graph mygraph { -- > 0 [label="a"]; -- > 1 [label="b"]; -- > 0 -- 1; -- > } graph :: GraphType -> GraphName -- ^ Internal graph name -> DotGen a -- ^ Content -> DotGraph graph gt gn func = Graph gt gn $ genDot func -- | Like 'graph' but without an internal graph name -- -- > graph_ gt func = graph gt "haphviz" func graph_ :: GraphType -> DotGen a -- ^ Content -> DotGraph graph_ gt func = graph gt "haphviz" func -- | Generate Internal dot content AST genDot :: DotGen a -> Dot genDot = genSubDot 0 -- | Utility function to generate a graph with nameless nodes starting from a given starting number. genSubDot :: Int -> DotGen a -> Dot genSubDot n func = snd $ genSubDot' n func genSubDot' :: Int -> DotGen a -> ((a, State), Dot) genSubDot' n func = runIdentity $ runWriterT $ runStateT func n -- * Graph types -- | Directed graph -- -- >>> directed -- > digraph directed :: GraphType directed = DirectedGraph -- | Undirected graph -- -- >>> undirected -- > graph undirected :: GraphType undirected = UndirectedGraph -- * Nodes -- | Most general node declaration -- -- This allows you to specify a node identifier for the node. -- -- In general it is more efficient to use nameless nodes and have the identifiers generated for you. -- -- It also allows you to specify attributes. -- In general it is better to use 'namelessNode'. -- -- >>> n <- newNode -- >>> genNode n [color =: green] -- > 0 [color="green"]; genNode :: NodeId -> [Attribute] -> DotGen () genNode ni ats = tell $ Node ni ats -- | Node with given (internal) name and attributes -- -- Aside from human-readable output, there is no reason to use named nodes. -- Use 'node' instead. -- -- >>> void $ namedNode "woohoo" [color =: red] -- > wohoo [color="red"]; namedNode :: Text -- ^ Name -> [Attribute] -> DotGen NodeId namedNode t ats = do let ni = UserId t genNode ni ats return ni -- | Nameless node with attributes -- -- This generates a nameless node for you but still allows you to specify its individual attributes. -- In general it is better to use 'nodeDec' and then 'node'. -- -- >>> void $ namelessNode [color =: blue] -- > 0 [color="blue"]; namelessNode :: [Attribute] -> DotGen NodeId namelessNode ats = do ni <- newNode genNode ni ats return ni -- | Node with a label but no other attributes -- -- A node with a given label and no other attributes. -- Usually used in conjunction with 'nodeDec'. -- -- >>> void $ node "server" -- > 0 [label="server"]; node :: Text -- ^ Label -> DotGen NodeId node l = namelessNode [label =: l] -- | Node with given node Id and label -- -- > node_ ni l = genNode ni [label =: l] node_ :: NodeId -- ^ given Node ID -> Text -- ^ Label -> DotGen () node_ ni l = genNode ni [label =: l] -- | Generate a new internally nameless node ID -- -- It is not generally a good idea to use this directly but it can be used to define node identifiers before a subgraph to reference them both in- and outside of it. newNode :: DotGen NodeId newNode = do i <- get modify (+1) return $ Nameless i -- * Edges -- | Most general edge declaration -- -- This allows you to specify attributes for a single edge. -- -- Usually it is better to use 'edgeDec' and then '-->'. -- -- >>> genEdge a b [label =: "MyEdge"] -- > a -> b [label="MyEdge"]; genEdge :: NodeId -> NodeId -> [Attribute] -> DotGen () genEdge n1 n2 ats = tell $ Edge n1 n2 ats -- | Infix edge constructor. (No attributes) -- -- This takes care of using the right edge declaration for the given graph. -- -- For undirected graphs, the output would be @--@ ... -- -- >>> a --> b -- > a -- b; -- -- ... and for directed graphs it would be @->@. -- -- >>> a --> b -- > a -> b; (-->) :: NodeId -> NodeId -> DotGen () n1 --> n2 = genEdge n1 n2 [] -- * Attributes -- | Infix operator for an attribute pair -- -- >>> [label =: "MyNode"] -- > [label="MyNode"] (=:) :: AttributeName -> AttributeValue -> Attribute (=:) = (,) -- * Declarations -- | General declaration of common attributes genDec :: DecType -> [Attribute] -> DotGen () genDec t ats = tell $ Declaration t ats -- | Graph declaration -- -- >>> graphDec [compound =: true] -- > graph [compound=true]; graphDec :: [Attribute] -> DotGen () graphDec = genDec DecGraph -- | Node declaration -- -- >>> nodeDec [shape =: none] -- > node [shape=none]; nodeDec :: [Attribute] -> DotGen () nodeDec = genDec DecNode -- | Edge declaration -- -- >>> edgeDec [color =: "red:blue"] -- > edge [color="red:blue"]; edgeDec :: [Attribute] -> DotGen () edgeDec = genDec DecEdge -- * Subgraphs -- | Cluster with a given name -- -- The @cluster_@ prefix is taken care of. cluster :: Text -> DotGen a -> DotGen (GraphName, a) cluster name = subgraph $ "cluster_" <> name -- | Like 'cluster', discarding the graph name. cluster_ :: Text -> DotGen a -> DotGen a cluster_ name subgraph = snd <$> cluster name subgraph -- | Subgraph declaration -- -- This is rarely useful. Just use 'cluster'. subgraph :: Text -> DotGen a -> DotGen (GraphName, a) subgraph name content = do n <- get let ((a, newn), dot) = genSubDot' n content tell $ Subgraph name dot put newn return (name, a) -- * Miscelaneous -- ** Rankdir -- | The rankdir declaration -- -- This changes the default layout of nodes -- -- >>> rankdir leftRight -- > rankdir = LR; rankdir :: RankdirType -> DotGen () rankdir = tell . Rankdir -- | -- >>> leftRight -- > LR leftRight :: RankdirType leftRight = LR -- | -- >>> rightLeft -- > RL rightLeft :: RankdirType rightLeft = RL -- | -- >>> topBottom -- > TB topBottom :: RankdirType topBottom = TB -- | -- >>> bottomTop -- > BT bottomTop :: RankdirType bottomTop = BT -- ** Labels -- | Label declaration for graphs or subgraphs labelDec :: Text -> DotGen () labelDec = tell . Label -- ** Ports -- | Use a certain port on a given node's label as an endpoint for an edge (.:) :: NodeId -> Text -- ^ Port -> NodeId (UserId t) .: p = UserId $ t <> ":" <> p (Nameless i) .: p = UserId $ T.pack (show i) <> ":" <> p -- * Ranks -- | {rank=same ... } declaration -- -- >>> ranksame $ node [shape =: none] -- > node [shape=none]; ranksame :: DotGen a -> DotGen a ranksame content = do n <- get let ((a, state), dot) = genSubDot' n content put state tell $ Ranksame dot return a -- * Internals -- | Generation monad type DotGen = StateT State (WriterT Dot Identity) -- | The next id for a nameless node type State = Int