I needed some graph visualizations for the chapter on logic and computability in The notes and I really didn't want to write about 20 pieces of repetitively constructed Graphviz code. Less than one week later, Haphviz allowed me to generate those graphs with only a few lines of Haskell code.

Disclaimer: This post assumes that you know how Graphviz' Dot language works. It is written at the time of Haphviz 0.1.4.

Demonstration

Divisors

Ever since I've learned about lattices, I've wanted to generate a Hasse diagram of natural numbers with 'divides' as the partial order. Of course it's impossible to generate an infinite diagram but I would settle for a parametric arbitrarily big diagram.

We'll generate a node in the graph for every natural number starting from $1$. Now, as far as the maths go, there should be an edge between two nodes labeled $a$ and $b$ if $a$ is divisible by $b$ and there doesn't exist a node labeled $c$ that has edges from $a$ and to $b$.

As the math is not the focus of this post, let's see how you would generate code to visualize this graph with Haphviz.

renderToStdOut $ graph directed "divisors" $ do nodes <- forM [1..n] $ node . T.pack . show let ns = zip [1..] nodes sequence_ [ni --> nj | (i, ni) <- ns, (j, nj) <- ns, i > j, primary i j]

Here the primary :: Integer -> Integer -> Bool function determines wether there should be an edge between two nodes. That wasn't so hard, was it? Running dot on the generate graphiz code with n = 10 yields the following image.

Finite state automata.

Having a Turing-complete encoding system for graph visualization means that we can simplify the process of visualizing graphs with a predictable structure.

Haphviz has a module Text.Dot.FSA with a single function fsaGraph that allows you to easily generate graphs for finite state automata.

The following code generates Graphviz code for a simple finite state automaton.

renderToStdOut $ fsaGraph [a, b, c] -- States a -- Starting state [b] -- Accepting states [(a, b, p), (b, b, q), (b, c, p)] -- Edges where (a, b, c, p, q) = ("a", "b", "c", "p" ,"q")

Design

The design of the internals of this library is based on the design of HaTeX internals. A single data type Dot represents the contents of a graph. Generating graphs by generating a Dot structure is certainly possible but it is a tad tedious.

The DotGen monad allows you to generate graphs in a simpler way. Now you only have to declare how you want to the graph to look and let the combinators take care of constructing a Dot graph.

Further work

Further extensions of this libraries are certainly possible. Here are some of such extensions I propose. Feel free to have a go at implementing them if you need them.

More predictable graph generation

As far as predictable graph generation goes, there is, at the time of writing, only support for finite state automata visualization in Haphviz For The notes, I made a similar piece of code that allowed me to generate Store-Heap model visualizations.

In future work on this library, code could be written to make visualizations of other predictable graphs easier:

Visualizations of trees from Data.Tree

Other automata from the domain of computability: Pushdown automata and Turing machines

Flow networks

...

A Graph type class

Data.Tree is just one example of a data type that could be represented as a tree. Others may well be visualizable too.

I propose a type class Graph a with a single function graph :: a -> DotGen () that generates its visualization.

A DotGen Monad transformer

DotGen is just a monad. It's currently not possible to do IO while generating a graph.