# :mage_woman: GraphWiz [![build status][BuildShield]][BuildLink] [![hackage version][HackageShield]][HackageLink] GraphWiz provides a small monadic DSL to generate DOT files. It aims at being intuitive to use, by replicating in your code the structure of the resulting DOT file. It's a "wizard" for Graphviz's DOT format, hence the name "GraphWiz". [BuildLink]: https://github.com/nicuveo/graphwiz/actions/workflows/haskell.yml?query=branch%3Amain [BuildShield]: https://img.shields.io/github/actions/workflow/status/nicuveo/graphwiz/haskell.yml?event=push&style=flat&branch=main&label=build [HackageLink]: https://hackage.haskell.org/package/graphwiz [HackageShield]: https://img.shields.io/hackage/v/graphwiz ## Overview ### Graph creation This library exports a simple monad: `Dot` (and its transformer version `DotT`), that you can only run with a function that indicates how the graph should be rendered: `graph`, `digraph`, `strictGraph`, `strictDigraph`. Within this monad, you can create graph elements with `node`, `edge` (or `-->`), `subgraph`, `cluster`, and their variants. All such functions that create an element return an `Entity`, an ID that uniquely identifies that element within the graph. Additionally, the ID of the latest created entity (regardless of its type) can be accessed with `itsID`. ```haskell digraph do a <- node "a" b <- node "b" a --> b subgraph do c <- node "c" a --> c b --> c ``` ### Attributes You can set default attributes for an entity type with `defaults`. You can access the attributes of a specific entity with `attributes`, and the latest created entity's attributes are accessible with `its`. All of those give you lenses to values within the underlying state; you can manipulate them with the `=` [lens operators](https://hackage.haskell.org/package/lens-5.3.3/docs/Control-Lens-Setter.html#g:5). Attributes are represented as a simple mapping from `Text` to `Text`, to avoid being too restrictive. There is, however, one lens per attribute listed in the [Graphviz documentation](https://graphviz.org/doc/info/attrs.html), allowing you to avoid strings in attributes declarations. ```haskell graphT do defaults Edge .= [("style", "dotted"), ("color", "blue")] defaults Node . shape ?= "hexagon" x <- node "x" its fontcolor ?= "red" liftIO $ print =<< use (attributes x) ``` ### TextBuilder For efficiency and convenience, the result of running the `Dot` monad is not a `String` or `Text`; it's a [TextBuilder](https://hackage.haskell.org/package/text-builder-1.0.0.4/docs/TextBuilder.html), that can be converted to a strict `Text` or even printed directly to the standard output. ### Auto compound The DOT syntax to draw edges between clusters is quite cumbersome. We have to declare the graph to be `compound`, and we have to create the edge between two nodes within the clusters, with specific attributes. ```dot graph foo { compound = true; subgraph cluster_a { a } subgraph cluster_b { b } a -- b [ltail="cluster_a", lhead="cluster_b"] } ``` GraphWiz automates the task: if one end of an edge is a cluster, the `compound` attribute is added to the graph, and the correct attributes are set at rendering time. To be as unobtrusive as possible, the values of `ltail` and `lhead` will not be updated if already present, trusting the user to know best. ```haskell graph do (cluster_a, _) <- cluster $ node "a" (cluster_b, _) <- cluster $ node "b" cluster_a --> cluster_b ``` ## Full example From the [example](example) folder: ##### Haskell source ```haskell main = TB.putLnToStdOut $ digraph do defaults Node . style ?= "filled" ast <- cluster_ do its label ?= "front end" source <- node "source code" its fillcolor ?= "#c3ffd8" ast <- node "AST" its fillcolor ?= "yellow" source --> ast its label ?= "parsing" pure ast cluster do its label ?= "middle end" ir <- node "IR" its shape ?= "diamond" its fillcolor ?= "salmon" ast --> ir its label ?= "lowering" its style ?= "dotted" ``` #### Resulting DOT file ```DOT digraph { subgraph cluster0 { label="front end"; node1 [label="source code",style="filled",fillcolor="#c3ffd8"] node2 [label="AST",style="filled",fillcolor="yellow"] node1 -> node2 [label="parsing"] } subgraph cluster4 { label="middle end"; node5 [label="IR",style="filled",fillcolor="salmon",shape="diamond"] node2 -> node5 [label="lowering",style="dotted"] } } ``` #### Resulting PNG ![example of a generated PNG](example/output.png)