graphwiz-1.0.0: Monadic DOT graph builder DSL
Safe HaskellNone
LanguageGHC2021

Text.Dot

Description

GraphWiz is a small monadic DSL used to write DOT files.

To run the monad, use one of the graph functions, like graph and digraph. Those also have a transformer version if you want to use Dot on top of other monads.

Within the monad, you can use any of the construction functions to create one of the four graph entities. Their attributes can be set via lenses such as ?=, see attributes.

The output is a Builder, that you can convert to a strict Text or print directly.

Synopsis

Entities

data Entity Source #

Opaque identifier for graph entities.

This type uniquely identifies an entity within the graph. To create one, see node, edge, subgraph, or cluster.

Instances

Instances details
Eq Entity Source # 
Instance details

Defined in Text.Dot.Types

Methods

(==) :: Entity -> Entity -> Bool #

(/=) :: Entity -> Entity -> Bool #

Ord Entity Source # 
Instance details

Defined in Text.Dot.Types

Hashable Entity Source # 
Instance details

Defined in Text.Dot.Types

Methods

hashWithSalt :: Int -> Entity -> Int #

hash :: Entity -> Int #

data EntityType Source #

Represents the type of a graph entity.

DOT distinguishes between graphs, nodes, edges, and subgraphs / clusters. This type differs slightly: it does not have a value for graphs, as it is never required, and we differentiate subgraphs and clusters.

This type is used internally to distinguish entities, mostly for the purpose of default attributes (see defaults).

Constructors

Node 
Edge 
Subgraph 
Cluster 

getType :: Entity -> EntityType Source #

Retrieves the type of a given Entity.

itsID :: MonadDot m => m Entity Source #

Retrieves the unique ID of the last created Entity.

Attributes

type Attributes = HashMap Text Text Source #

An entity's attributes.

Attributes are untyped, and are a simple mapping from Text to Text, for flexibility.

attributes :: Entity -> Lens' DotGraph Attributes Source #

Retrieves the Attributes of the given Entity.

Given an entity attribute, return a lens to the corresponding attributes map in a given DotGraph, which is an internal opaque type. This is meant to be used inside the DotT monad, relying on the fact that it is a State monad under the hood.

Using OverloadedLists makes working with the full attributes map a bit easier.

graph do
  x <- node "x"

  -- replaces the entire mapping (erases the label!)
  attributes x .= [("fontcolor", "red")]

  -- combines the existing mapping with the new one, favoring old values
  attributes x <>= [("fontcolor", "blue"), ("fontsize", "12")]

  -- combines the existing mapping with the new one, favoring new values
  attributes x <>:= [("fontcolor", "blue"), ("fontsize", "12")]

This function is best used with the provided field accessors, such as fontcolor, to be more explicit about the way to deal with previous values.

defaults :: EntityType -> Lens' DotGraph Attributes Source #

Retrieves the default Attributes of the given EntityType.

Given an entity type, return a lens to the corresponding default attributes map in a given DotGraph, which is an internal opaque type. This is meant to be used inside the DotT monad, relying on the fact that it is a State monad under the hood.

After modifying the defaults for a given entity type, any new such entity will have its attributes set to the new default values.

graph do
  node "x"
  use (its fillcolor) -- Nothing

  defaults Cluster . style ?= "dashed"
  defaults Node <>= [("style", "filled"), ("fillcolor", "forestgreen")]

  node "y"
  use (its fillcolor) -- Just "forestgreen"

This function is best used with the provided field accessors, such as fontcolor, to be more explicit about the way to deal with previous values.

attribute :: Text -> Lens' Attributes (Maybe Text) Source #

Simple alias for at.

This makes the code a tiny bit more natural when accessing fields by name:

graph do
  node "x"

  -- replace the old value, if any
  its (attribute "fontcolor") ?= "red"
  its (attribute "fontcolor") .= Just "red"

  -- erase the attribute
  its (attribute "fontcolor") .= Nothing

  -- set the value if it wasn't previously set
  its (attribute "fontcolor") %= ifAbsent "blue"

It is however preferable to use one of the provided attribute lenses to avoid raw strings.

its :: Lens' Attributes (Maybe Text) -> Lens' DotGraph (Maybe Text) Source #

Retrieves an attribute from the latest created Entity.

Given a lens for a specific attribute, such as style or label, this combinator creates a lens that points to that attribute for the latest created Entity. Like attributes, it is meant to be used within the DotT monad.

graph do
  its title ?= "my graph"

  bar <- node "bar"
  its fontsize ?= "34"

  edge bar bar
  its style ?= "dotted"

  cluster do
    its label ?= "cluster"

ifAbsent :: a -> Maybe a -> Maybe a Source #

Replaces a Maybe value only if it wasn't set.

>>> ifAbsent "foo" Nothing
Just "foo"
>>> ifAbsent "foo" (Just "bar")
Just "bar"

This is best used in conjuction with attribute, or one of the explicit attribute accessors.

foo <- node "foo"
its fillcolor %= ifAbsent "blue"

Construction

node :: MonadDot m => Text -> m Entity Source #

Creates a node in the graph, at the current Path, with the given label.

The newly created node will be assigned all of the default Node attributes (see defaults). This returns a new 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.

edge :: MonadDot m => Entity -> Entity -> m Entity Source #

Creates an edge in the graph, at the current Path.

The newly created edge will be assigned all of the default Edge attributes (see defaults). This returns a new Entity that uniquely identifies this edge in the graph.

If an entity is a cluster, we set the graph's "compound" property to true, and we attempt to locate any node within it. If there isn't any, we fail silently by outputing a valid but unexpected edge.

This function updates the its entity to this edge.

(-->) :: MonadDot m => Entity -> Entity -> m Entity Source #

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.

subgraphWith :: MonadDot m => (Entity -> m a) -> m (Entity, a) Source #

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 a callback that takes the newly minted Entity and creates the corresponding subgraph.

This function updates the its entity to this node *twice*: before executing the callback, and before returning.

graph do
  (subgraphID, nodeID) <- subgraphWith \subgraphID -> 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

This returns a pair containing the subgraph's Entity and the result of the subexpression.

subgraph :: MonadDot m => m a -> m (Entity, a) Source #

Like subgraphWith, but the subexpression doesn't take the Entity as argument.

subgraphWith_ :: MonadDot m => (Entity -> m a) -> m a Source #

Like subgraphWith, but does not return the subgraph's Entity.

subgraph_ :: MonadDot m => m a -> m a Source #

Like subgraphWith, but the subexpression doesn't take the Entity as argument, and it does not return the subgraph's Entity.

clusterWith :: MonadDot m => (Entity -> m a) -> m (Entity, a) Source #

Like subgraphWith, but creates a cluster instead.

The created entity will use the default Cluster attributes.

cluster :: MonadDot m => m a -> m (Entity, a) Source #

Like clusterWith, but the subexpression doesn't take the Entity as argument.

clusterWith_ :: MonadDot m => (Entity -> m a) -> m a Source #

Like clusterWith, but does not return the cluster's Entity.

cluster_ :: MonadDot m => m a -> m a Source #

Like clusterWith, but the subexpression doesn't take the Entity as argument, and it does not return the cluster's Entity.

Monad

data DotT (m :: Type -> Type) a Source #

Dot creation monad.

Instances

Instances details
MonadTrans DotT Source # 
Instance details

Defined in Text.Dot.Monad

Methods

lift :: Monad m => m a -> DotT m a #

MonadError e m => MonadError e (DotT m) Source # 
Instance details

Defined in Text.Dot.Monad

Methods

throwError :: e -> DotT m a #

catchError :: DotT m a -> (e -> DotT m a) -> DotT m a #

Monad m => MonadReader Path (DotT m) Source # 
Instance details

Defined in Text.Dot.Monad

Methods

ask :: DotT m Path #

local :: (Path -> Path) -> DotT m a -> DotT m a #

reader :: (Path -> a) -> DotT m a #

Monad m => MonadState DotGraph (DotT m) Source # 
Instance details

Defined in Text.Dot.Monad

Methods

get :: DotT m DotGraph #

put :: DotGraph -> DotT m () #

state :: (DotGraph -> (a, DotGraph)) -> DotT m a #

MonadWriter w m => MonadWriter w (DotT m) Source # 
Instance details

Defined in Text.Dot.Monad

Methods

writer :: (a, w) -> DotT m a #

tell :: w -> DotT m () #

listen :: DotT m a -> DotT m (a, w) #

pass :: DotT m (a, w -> w) -> DotT m a #

MonadIO m => MonadIO (DotT m) Source # 
Instance details

Defined in Text.Dot.Monad

Methods

liftIO :: IO a -> DotT m a #

Monad m => Applicative (DotT m) Source # 
Instance details

Defined in Text.Dot.Monad

Methods

pure :: a -> DotT m a #

(<*>) :: DotT m (a -> b) -> DotT m a -> DotT m b #

liftA2 :: (a -> b -> c) -> DotT m a -> DotT m b -> DotT m c #

(*>) :: DotT m a -> DotT m b -> DotT m b #

(<*) :: DotT m a -> DotT m b -> DotT m a #

Functor m => Functor (DotT m) Source # 
Instance details

Defined in Text.Dot.Monad

Methods

fmap :: (a -> b) -> DotT m a -> DotT m b #

(<$) :: a -> DotT m b -> DotT m a #

Monad m => Monad (DotT m) Source # 
Instance details

Defined in Text.Dot.Monad

Methods

(>>=) :: DotT m a -> (a -> DotT m b) -> DotT m b #

(>>) :: DotT m a -> DotT m b -> DotT m b #

return :: a -> DotT m a #

type Dot = DotT Identity Source #

An alias for DotT Identity.

type MonadDot (m :: Type -> Type) = (MonadState DotGraph m, MonadReader Path m) Source #

The constraint that all functions require.

We choose to express this as a constraint rather than a typeclass for simplicity.

data DotGraph Source #

Internal opaque graph state.

Instances

Instances details
Monad m => MonadState DotGraph (DotT m) Source # 
Instance details

Defined in Text.Dot.Monad

Methods

get :: DotT m DotGraph #

put :: DotGraph -> DotT m () #

state :: (DotGraph -> (a, DotGraph)) -> DotT m a #

data Path Source #

A path through the graph.

This opaque type represents the path from the root to the current scope. The current path can be obtained via path.

Instances

Instances details
Monad m => MonadReader Path (DotT m) Source # 
Instance details

Defined in Text.Dot.Monad

Methods

ask :: DotT m Path #

local :: (Path -> Path) -> DotT m a -> DotT m a #

reader :: (Path -> a) -> DotT m a #

currentPath :: MonadDot m => m (NonEmpty Entity) Source #

Retrieve the current path.

The path is the stack of entities, representing the graph / subgraphs / clusters between the root of the graph and the current location.

graph do
  p1 <- currentPath     -- returns [$graphID]
  subgraph do
    cluster do
      p2 <- currentPath -- returns [$clusterID, $subgraphID, $graphID]
      doStuff

rootGraph :: MonadDot m => m Entity Source #

Retrieves the unique ID of the top-level graph.

Rendering the graph

graphWithT :: Monad m => (Entity -> DotT m a) -> m Builder Source #

Renders a given graph.

Given a DotT expression that builds a graph, this function evaluates it and builds an undirected non-strict graph. It returns the result in the underlying monad, as a Builder. The callback takes the graph's identifier as argument.

The result of the graph building expression itself is ignored.

graphT :: Monad m => DotT m a -> m Builder Source #

Renders a given graph.

Like graphWithT, but the expression doesn't take the identifier as agument.

graphWith :: (Entity -> Dot a) -> Builder Source #

Renders a given graph.

Like graphWithT, but in the Dot monad.

graph :: Dot a -> Builder Source #

Renders a given graph.

Like graphT, but in the Dot monad.

digraphWithT :: Monad m => (Entity -> DotT m a) -> m Builder Source #

Renders a given graph.

Given a DotT expression that builds a graph, this function evaluates it and builds a directed non-strict graph. It returns the result in the underlying monad, as a Builder. The callback takes the graph's identifier as argument.

The result of the graph building expression itself is ignored.

digraphT :: Monad m => DotT m a -> m Builder Source #

Renders a given graph.

Like digraphWithT, but the expression doesn't take the entity as agument.

digraphWith :: (Entity -> Dot a) -> Builder Source #

Renders a given graph.

Like digraphWithT, but in the Dot monad.

digraph :: Dot a -> Builder Source #

Renders a given graph.

Like digraphT, but in the Dot monad.

strictGraphWithT :: Monad m => (Entity -> DotT m a) -> m Builder Source #

Renders a given graph.

Given a DotT expression that builds a graph, this function evaluates it and builds an undirected strict graph. It returns the result in the underlying monad, as a Builder. The callback takes the graph's identifier as argument.

The result of the graph building expression itself is ignored.

strictGraphT :: Monad m => DotT m a -> m Builder Source #

Renders a given graph.

Like strictGraphWithT, but the expression doesn't take the entity as agument.

strictGraphWith :: (Entity -> Dot a) -> Builder Source #

Renders a given graph.

Like strictGraphWithT, but in the Dot monad.

strictGraph :: Dot a -> Builder Source #

Renders a given graph.

Like strictGraphT, but in the Dot monad.

strictDigraphWithT :: Monad m => (Entity -> DotT m a) -> m Builder Source #

Renders a given graph.

Given a DotT expression that builds a graph, this function evaluates it and builds a directed strict graph. It returns the result in the underlying monad, as a Builder. The callback takes the graph's identifier as argument.

The result of the graph building expression itself is ignored.

strictDigraphT :: Monad m => DotT m a -> m Builder Source #

Renders a given graph.

Like strictDigraphWithT, but the expression doesn't take the entity as agument.

strictDigraphWith :: (Entity -> Dot a) -> Builder Source #

Renders a given graph.

Like strictDigraphWithT, but in the Dot monad.

strictDigraph :: Dot a -> Builder Source #

Renders a given graph.

Like strictDigraphT, but in the Dot monad.

Re-exports from Control.Lens.Setter

(.=) :: MonadState s m => ASetter s s a b -> b -> m () infix 4 #

Replace the target of a Lens or all of the targets of a Setter or Traversal in our monadic state with a new value, irrespective of the old.

This is an infix version of assign.

>>> execState (do _1 .= c; _2 .= d) (a,b)
(c,d)
>>> execState (both .= c) (a,b)
(c,c)
(.=) :: MonadState s m => Iso' s a       -> a -> m ()
(.=) :: MonadState s m => Lens' s a      -> a -> m ()
(.=) :: MonadState s m => Traversal' s a -> a -> m ()
(.=) :: MonadState s m => Setter' s a    -> a -> m ()

It puts the state in the monad or it gets the hose again.

(?=) :: MonadState s m => ASetter s s a (Maybe b) -> b -> m () infix 4 #

Replace the target of a Lens or all of the targets of a Setter or Traversal in our monadic state with Just a new value, irrespective of the old.

>>> execState (do at 1 ?= a; at 2 ?= b) Map.empty
fromList [(1,a),(2,b)]
>>> execState (do _1 ?= b; _2 ?= c) (Just a, Nothing)
(Just b,Just c)
(?=) :: MonadState s m => Iso' s (Maybe a)       -> a -> m ()
(?=) :: MonadState s m => Lens' s (Maybe a)      -> a -> m ()
(?=) :: MonadState s m => Traversal' s (Maybe a) -> a -> m ()
(?=) :: MonadState s m => Setter' s (Maybe a)    -> a -> m ()

(%=) :: MonadState s m => ASetter s s a b -> (a -> b) -> m () infix 4 #

Map over the target of a Lens or all of the targets of a Setter or Traversal in our monadic state.

>>> execState (do _1 %= f;_2 %= g) (a,b)
(f a,g b)
>>> execState (do both %= f) (a,b)
(f a,f b)
(%=) :: MonadState s m => Iso' s a       -> (a -> a) -> m ()
(%=) :: MonadState s m => Lens' s a      -> (a -> a) -> m ()
(%=) :: MonadState s m => Traversal' s a -> (a -> a) -> m ()
(%=) :: MonadState s m => Setter' s a    -> (a -> a) -> m ()
(%=) :: MonadState s m => ASetter s s a b -> (a -> b) -> m ()

(<>=) :: (MonadState s m, Semigroup a) => ASetter' s a -> a -> m () infix 4 #

Modify the target(s) of a Lens', Iso, Setter or Traversal by using (<>).

>>> execState (do _1 <>= Sum c; _2 <>= Product d) (Sum a,Product b)
(Sum {getSum = a + c},Product {getProduct = b * d})
>>> execState (both <>= "!!!") ("hello","world")
("hello!!!","world!!!")
(<>=) :: (MonadState s m, Semigroup a) => Setter' s a -> a -> m ()
(<>=) :: (MonadState s m, Semigroup a) => Iso' s a -> a -> m ()
(<>=) :: (MonadState s m, Semigroup a) => Lens' s a -> a -> m ()
(<>=) :: (MonadState s m, Semigroup a) => Traversal' s a -> a -> m ()

(<>:=) :: (MonadState s m, Semigroup a) => ASetter' s a -> a -> m () infix 4 #

Modify the target(s) of a Lens', Iso, Setter or Traversal by using (<>). However, unlike <>=, it is prepend to the head side.

All known attributes

Renamed attributes

All attributes listed in Graphviz's documentation have an accompanying lens, so that any standard attribute can be accessed without using strings (like with attribute). Not all of them are valid haskell names, however: the following are the ones that have been remamed, the others ones can be found below.

background :: Lens' Attributes (Maybe Text) Source #

Maps to the _background attribute.

damping :: Lens' Attributes (Maybe Text) Source #

Maps to the Damping attribute.

isCcluster :: Lens' Attributes (Maybe Text) Source #

Maps to the cluster attribute.

k :: Lens' Attributes (Maybe Text) Source #

Maps to the K attribute.

svgClass :: Lens' Attributes (Maybe Text) Source #

Maps to the class attribute.

svgID :: Lens' Attributes (Maybe Text) Source #

Maps to the id attribute.

tbbalance :: Lens' Attributes (Maybe Text) Source #

Maps to the TBbalance attribute.

url :: Lens' Attributes (Maybe Text) Source #

Maps to the URL attribute.

All others