# The GraphViz renderer
[GraphViz](http://www.graphviz.org) is a popular rendering program for graphs. It takes the specification for a graph in the _dot_ language and produces a variety of output formats. We are particularly interested in its ability to output SVG, Scalable Vector Graphics, which we can embed in the SWISH output window.
The `graphviz` renderer needs to obtain a dot specification and it needs to know the layout algorithm to use. It takes two terms:
- Using Layout(DotString), it simply renders a dot specification using the
layout engine Layout. Provided layout engines are =dot= (most commonly
used), =neato=, =fdp=, =sfdp=, =twopi= and =circo=. Please visit the
[GraphViz](http://www.graphviz.org) site for details.
- Using Layout(Graph), where `Graph` is a Prolog term representing the
dot AST (Abstract Syntax Tree). The AST is translated into concrete
dot syntax using a DCG grammar.
Before we go into details, we do the _Hello World_ demo. First, load the `graphviz` renderer:
:- use_rendering(graphviz).
First, we use the _Hello World_ example from the GrahViz demo pages using a dot string as specification:
X = dot("digraph G { Hello->World }").
Using a dot string as specification is of course not desirable because it makes
it hard to generate a graph from computed data. We introduce the *Prolog representation* for dot with the same example. The only noticible difference is that the _render selection corner_ becomes visible because the graph is given
the attribute `bgcolor=transparent` if no background is given, while the graphviz
default is `white`.
X = dot(digraph(['Hello'->'World'])).
The default `dot` program may be omitted. This example also shows that you can use arbitrary terms as node identifiers and that you can add graph attributes.
X = digraph([rankdir='LR', t(Y)->t(y)]).
## A more complex example
The following example is taken from the [Graphviz gallery](http://www.graphviz.org/Gallery.php),
where the first query uses the dot syntax and the second represents the same graph as a Prolog
term.
X = dot("digraph G {
subgraph cluster_0 {
style=filled;
color=lightgrey;
node [style=filled,color=white];
a0 -> a1 -> a2 -> a3;
label = \"process #1\";
}
subgraph cluster_1 {
node [style=filled];
b0 -> b1 -> b2 -> b3;
label = \"process #2\";
color=blue
}
start -> a0;
start -> b0;
a1 -> b3;
b2 -> a3;
a3 -> a0;
a3 -> end;
b3 -> end;
start [shape=Mdiamond];
end [shape=Msquare];
}").
X = dot(digraph([
subgraph(cluster_0,
[ style=filled,
color=lightgrey,
node([style=filled,color=white]),
a0 -> a1 -> a2 -> a3,
label = "process #1"
]),
subgraph(cluster_1,
[ node([style=filled]),
b0 -> b1 -> b2 -> b3,
label = "process #2",
color=blue
]),
start -> a0,
start -> b0,
a1 -> b3,
b2 -> a3,
a3 -> a0,
a3 -> end,
b3 -> end,
node(start, [shape='Mdiamond']),
node(end, [shape='Msquare'])
])).
## Representing dot as Prolog
The full dot language is represented as a Prolog term. The shape of this term closely follows the
[dot language ](http://www.graphviz.org/content/dot-language) and is informally defined by the
grammar below:
```
Graph := graph(Statements)
| graph(Options, Statements)
| digraph(Statements)
| digraph(Options, Statements)
Options := ID | [ID] | [strict, ID]
Statements := List of statements
Statement := NodeStm | EdgeStm | AttrStm | Name = Value | SubGraph | Group
NodeStm := NodeID | node(NodeID, AttrList)
NodeID := ID | ID:Port | ID:Port:CompassPT
CompassPT := n | ne | e | se | s | sw | w | nw | c | _
EdgeStm := (NodeID|SubGraph) (EdgeOp (NodeID|SubGraph))+
EdgeStm | edge(NodeID|SubGraph) (EdgeOp (NodeID|SubGraph))+), AttrList)
EdgeOp := - | ->
AttrStm := graph(AttrList)
| node(AttrList)
| edge(AttrList)
AttrList := List of attributes
Attribute := Name = Value
| Name(Value)
SubGraph := subgraph(ID, Statements)
Group := group(Statements)
```
A term group(Statements) generates a {...} delimited group in the output. A typical usage is group([rank=same, a, b, c]) to place the nodes a..c on the same rank.
## Generating Graphs from data
Generating a graph from static data is of course not very interesting. This section provides an example for generating graphs from Prolog data. This is where the Prolog representation of a _dot_ program comes in: it takes away the burden of generating the concrete dot syntax, avoiding issues such as proper escaping of labels. In the example below we render an arbitrary (acyclic) Prolog term as a tree. We do this by walking down the term, generating a node-id and a label for each node representing a compound term as well as each leaf. Note that we cannot use the label as node id beause the same label may appear in multiple locations. Thus, we generate dot statements like this:
- node(Id, [label=Label])
- `ChildID -> ParentId`
If `Label` is atomic, the plain value (without quotes, see write/1) will be rendered.
Otherwise the label is handed to print/1 by the graphviz rendering. Note that the
graphviz rendering code is called _after_ the toplevel preparation of the answer.
The toplevel preparation removes cycles, combines multiple variables bound to the
same variable, binds named variables to a term =|'$VAR'(Name)|= and extracts
_redidual goals_. In particular, this implies you can *render a variable using
its name* by using the plain variable as the label: The toplevel will bind the label to
=|'$VAR'(Name)|= and print/1 will print this as the variable name.
This is exploited by any_label/2 below.
:- use_rendering(graphviz).
tree(Compound, Root, Options0, Options) -->
{ compound(Compound), !,
atom_concat(n, Options0.id, Root),
compound_name_arguments(Compound, Name, Arguments),
format(string(Label), '~q', [Name]),
ID1 is Options0.id+1
},
[node(Root, [label=Label])],
children(Arguments, Root, Options0.put(id, ID1), Options).
tree(Any, Leaf, Options0, Options) -->
{ atom_concat(n, Options0.id, Leaf),
ID1 is Options0.id+1,
any_label(Any, Label, Color),
Options = Options0.put(id, ID1)
},
[ node(Leaf, [label=Label, shape=none, fontcolor=Color]) ].
any_label(Any, Label, red4) :-
var(Any), !, Label = Any.
any_label(Any, Label, blue) :-
format(string(Label), '~p', [Any]).
children([], _, Options, Options) --> [].
children([H|T], Parent, Options0, Options) -->
[ Child -> Parent ],
tree(H, Child, Options0, Options1),
children(T, Parent, Options1, Options).
gvtree(Term, digraph([rankdir='BT',size=5|Statements])) :-
phrase(tree(Term, _, _{id:1}, _), Statements).
A = f(1, a, `XY`, "XY", X), gvtree(A, T).