graphwiz-3.0.0: src/Text/Dot/Build.hs
module Text.Dot.Build
( node
, edge
, (-->)
, subgraph
, subgraph_
, cluster
, cluster_
, registerItAs
, register
, retrieve
) where
import "this" Prelude
import Control.Lens
import Data.HashMap.Strict qualified as M
import Data.List.NonEmpty qualified as NE
import Text.Dot.Attributes
import Text.Dot.Monad
import Text.Dot.Types
--------------------------------------------------------------------------------
-- Entity creation functions
-- | Creates a node in the graph, at the current t'Path', with the given label.
--
-- The newly created node will be assigned all of the default 'Node' attributes
-- (see 'defaults'). This returns a new t'Entity' that uniquely identifies this
-- node in the graph, with the attribute "label" set to the given argument.
--
-- This function updates the 'its' entity to this node.
node :: MonadDot m => Text -> m Entity
node desc = do
entity <- record Node
its label ?= desc
pure entity
-- | Creates an edge in the graph, at the current t'Path'.
--
-- The newly created edge will be assigned all of the default 'Edge' attributes
-- (see 'defaults'). This returns a new t'Entity' that uniquely identifies this
-- edge in the graph.
--
-- There are two ways to specify each end of an edge: either by directly giving
-- an entity, or by giving an entity's "name", registered via 'register', and
-- obtained via 'retrieve'. The name is only resolved after the entire graph has
-- been processed; this method allows the user to reference a node that has yet
-- to be created, or to reference a node without manually propagating its
-- entity.
--
-- > digraph do
-- > x <- node "x"
-- > y <- node "y"
-- > edge x y
-- > edge (retrieve "z node") x
-- > edge y (retrieve "z node")
-- > z <- node "z"
-- > registerItAs "z node"
--
-- If any of the two target entiies is a cluster, the root graph's "compound"
-- property will be set to true, and the edge will be adjusted to make use of
-- 'lhead' or 'ltail' accordingly.
--
-- If an edge makes reference to a node that doesn't exist yet (see 'retrieve'),
-- its declaration will be moved to the top level of the graph, but its
-- attributes will remain unchanged.
--
-- This function updates the 'its' entity to this edge.
edge
:: (ToEdgeNode a, ToEdgeNode b, MonadDot m)
=> a
-> b
-> m Entity
edge a b = do
entity <- record Edge
edgeInfo . at entity ?= EdgeInfo (toEdgeNode a) (toEdgeNode b) Nothing Nothing
pure entity
-- | Alias for 'edge'.
--
-- This can be used in both directed and undirected graphs: the rendering
-- process will tke care of using the correct symbol in the generated DOT file.
--
-- > graph do
-- > x <- node "x"
-- > y <- node "y"
-- > z <- node "z"
-- > x --> y
-- > x --> z
--
-- This function updates the 'its' entity to this edge.
(-->)
:: (ToEdgeNode a, ToEdgeNode b, MonadDot m)
=> a
-> b
-> m Entity
(-->) = edge
-- | Creates a subgraph in the given context.
--
-- The newly created subgraph will be assigned all of the default 'Subgraph'
-- attributes (see 'defaults'). The argument to this function is an action in
-- the monad: all entities created in this action will be added to this new
-- subgraph.
--
-- This function updates the 'its' entity to this node *twice*: before executing
-- the callback, and before returning.
--
-- > graph do
-- > subgraph_ do
-- > its fontcolor ?= "green" -- points to the subgraph
-- > x <- node "x"
-- > its fontcolor ?= "red" -- points to node "x"
-- > pure x
-- > use (its fontcolor) -- points to the subgraph, returns green
--
-- The resulting monadic action will return the value return by the given action.
subgraph :: MonadDot m => m a -> m a
subgraph = recurse Subgraph
-- | Like 'subgraph', but ignores the result of the nested action.
subgraph_ :: MonadDot m => m a -> m ()
subgraph_ = void . recurse Subgraph
-- | Like 'subgraph', but creates a cluster instead.
--
-- The created entity will use the default 'Cluster' attributes.
cluster :: MonadDot m => m a -> m a
cluster = recurse Cluster
-- | Like 'cluster', but ignores the result of the nested action.
cluster_ :: MonadDot m => m a -> m ()
cluster_ = void . recurse Cluster
-- | Associate the given entity to the given name.
--
-- The 'DotT' monad will store an association from the name to the entity,
-- allowing edges to be declared by making reference to that name using
-- 'Text.Dot.retrieve'.
--
-- > digraph do
-- > cluster_ do
-- > cluster_ do
-- > cluster_ do
-- > cluster_ do
-- > a <- node "A"
-- > register a "the A node"
-- > b <- node "B"
-- > edge (retrieve "the A node") b
--
-- See also 'Text.Dot.edge'.
register :: MonadDot m => Entity -> Text -> m ()
register entity name = do
entityRegister %= M.insert name entity
-- | Associate the latest entity to the given name.
--
-- Like 'register', but uses the last created entity.
registerItAs :: MonadDot m => Text -> m ()
registerItAs name = do
currentEntity <- itsID
register currentEntity name
--------------------------------------------------------------------------------
-- Internal helpers
recurse :: MonadDot m => EntityType -> m a -> m a
recurse etype action = do
entity <- record etype
contextStack %= NE.cons mempty
result <- withPath entity action
sub <- popContext
subgraphInfo . at entity ?= sub
latest .= entity
pure result
record :: MonadDot m => EntityType -> m Entity
record etype = do
suffix <- use entityIndex
let entity = Entity etype suffix
defAttrs <- use $ defaultAttributes . at etype . non mempty
context <>:= [entity]
entityIndex += 1
attributes entity .= defAttrs
latest .= entity
pure entity