A Quick Graphviz Tutorial
Note - all code for this tutorial - which is written in quarto - is here. You can learn more about quarto and graphs/diagrams on their website.
Graphviz is a language for creating, well, graphs! Things with notes and arrows between them. You can put these into quarto documents or render them online with sites like this. So, how do you get something like this?
1 Setting up your graph
The first thing you have to do is setup your graph and its properties. This usually begins with a call like
digraph G{
}This sets up a directed graph for a DAG.
2 Add those arrows!
OK, a blank graph. Exciting. Wow. Let’s actually do some stuff here and setup the basic of our variables. We’re going to use variable names that are all one continuous string - no spaces or funky characters. We’ll get there! What’s great, is that you….. just add arrows. a -> b. Each link is called an edge if you’re not familiar with the parlance.
digraph G{
// Edges
urchins -> kelp ;
temperature -> kelp;
temperature -> urchins;
kelp_lag -> urchins;
kelp_lag -> kelp;
urchins_lag -> urchins;
}Hey, not bad as a first cut!
Check out how I used // for comments there. Always be commenting!
3 Make it flow better!
OK, don’t like the top to bottom? We can setup that - and a number of other formatting bits - in the header of the graph. Let’s start with flow. Think, top to bottom, or left to right. We can use the rankdir argument. For example, LR - left to right - would give something like this.
digraph G{
rankdir = LR
// Edges
urchins -> kelp ;
temperature -> kelp;
temperature -> urchins;
kelp_lag -> urchins;
kelp_lag -> kelp;
urchins_lag -> urchins;
}Better!
Try out TB, BT, RL, and LR!
Note that TB is the default if you don’t include a rankdir
Note - if you don’t just want to think about flow, there are other automated layout engines.
digraph G{
layout = "twopi"
// Edges
urchins -> kelp ;
temperature -> kelp;
temperature -> urchins;
kelp_lag -> urchins;
kelp_lag -> kelp;
urchins_lag -> urchins;
}But I find I often just want to think about rankdir, or I get more specific with node placement - which you can!
4 Making Nodes Better
To add nodes and add some formatting, you can specify them along with information about their formatting. This can include things like labels (which can have spaces, and more), colors, fill types, etc. etc. Heck, it’s not just nodes, but, for just about any element, you can add formatting by putting formatting arguments in [] after the element itself.
4.1 Labels
Let’s try this out with our nodes - let’s give them better names using the label attribute.
digraph G{
rankdir = LR
// Current-time nodes
urchins [label="urchins"];
kelp [label="kelp"];
temperature [label="temperature"];
// Lagged nodes
urchins_lag [label="urchins(t-1)"];
kelp_lag [label="kelp(t-1)"];
// Edges
urchins -> kelp ;
temperature -> kelp;
temperature -> urchins;
kelp_lag -> urchins;
kelp_lag -> kelp;
urchins_lag -> urchins;
}4.2 Shape
We’ve got one down - what are more? Let’s say we want to abandon ovals and make things boxes. We can use the shape attribute - there are a lot of options!
digraph G{
rankdir = LR
// Current-time nodes
urchins [label="urchins", shape=box];
kelp [label="kelp", shape=box];
temperature [label="temperature", shape=box];
// Lagged nodes
urchins_lag [label="urchins(t-1)", shape=box];
kelp_lag [label="kelp(t-1)", shape=box];
// Edges
urchins -> kelp ;
temperature -> kelp;
temperature -> urchins;
kelp_lag -> urchins;
kelp_lag -> kelp;
urchins_lag -> urchins;
}4.3 Universal Formatting & Fonts
That’s kind of annoying to have to type that over and over again. We can specify attributes for ALL nodes by putting those attributes at the top with the node header followed by formatting in []. For example:
digraph G{
rankdir = LR
node [shape = box, fontname="Helvetica"];
// Current-time nodes
urchins [label="urchins"];
kelp [label="kelp"];
temperature [label="temperature"];
// Lagged nodes
urchins_lag [label="urchins(t-1)"];
kelp_lag [label="kelp(t-1)"];
// Edges
urchins -> kelp ;
temperature -> kelp;
temperature -> urchins;
kelp_lag -> urchins;
kelp_lag -> kelp;
urchins_lag -> urchins;
}Now I’ve changed the font to Helvetica and made all of the nodes boxes! There are a lot of more attributes to explore, but let’s leave this here for now.
For more attributes, see here
5 Making Edges Great (Again?)
We can do similar things to edges. For example, let’s say we wanted to accomplish three things:
- Make the urchin -> kelp and urchin_lag -> urchins link both very thick.
- Make the temperature -> urchins link dashed.
- Add a label to the kelp_lag -> kelp edge that says ‘growth’.
We can look at this list of edge attributes and see that, for that last one, you guessed it, we can just add a label! For the other two, we can use the penwidth and the style arguments. Note, style has a pretty deep list of things it can do!
digraph G{
rankdir = LR
node [shape = box, fontname="Helvetica"];
// Current-time nodes
urchins [label="urchins"];
kelp [label="kelp"];
temperature [label="temperature"];
// Lagged nodes
urchins_lag [label="urchins(t-1)"];
kelp_lag [label="kelp(t-1)"];
// Edges
urchins -> kelp [penwidth=8];
temperature -> kelp;
temperature -> urchins [style=dashed];
kelp_lag -> urchins;
kelp_lag -> kelp [label="growth"];
urchins_lag -> urchins [penwidth=3];
}For more edge attributes, see here
6 Positioning
OK, this is nice, but I want temperature all the way to the left. How do I do this? There are two ways. One is if you are using the neato there is a pos argument for nodes. But, I dunno, I have a hard time with it. The other is to think about the flow of the diagram and subgraphs.
6.1 Positioning and Subgraphs
To make a subgraph, you enclose a part of your graph in {} and then you can specify information about the rank of the nodes within it. So, for example, we could put kelp and urchins into the same subgraph, and say that their rank is the same, thus putting them on top of one another {rank=max; temperature -> kelp;}
digraph G{
rankdir = LR
node [shape = box, fontname="Helvetica"];
// Current-time nodes
urchins [label="urchins"];
kelp [label="kelp"];
temperature [label="temperature", pos="5,1"];
// Lagged nodes
urchins_lag [label="urchins(t-1)"];
kelp_lag [label="kelp(t-1)"];
// Edges
urchins -> kelp [penwidth=8];
{rank=max; temperature -> kelp;}
temperature -> urchins [style=dashed];
kelp_lag -> urchins;
kelp_lag -> kelp [label="growth"];
urchins_lag -> urchins [penwidth=3];
}6.2 Positioning and Invisible Edges
That’s one solution. Another is to work with the LR flow we have going to specify that temperature should be somewhere else. If there is flow, than links from kelp and urchins to temperature would move it downstream. BUT - we don’t want to see them. So, we can set their style to be invisible.
digraph G{
rankdir = LR
node [shape = box, fontname="Helvetica"];
// Current-time nodes
urchins [label="urchins"];
kelp [label="kelp"];
temperature [label="temperature", pos="5,1"];
// Lagged nodes
urchins_lag [label="urchins(t-1)"];
kelp_lag [label="kelp(t-1)"];
// Edges
urchins -> kelp [penwidth=8];
temperature -> kelp;
temperature -> urchins [style=dashed];
kelp_lag -> urchins;
kelp_lag -> kelp [label="growth"];
urchins_lag -> urchins [penwidth=3];
// Invisible edges for positioning
kelp->temperature [style=invis]
urchins->temperature[style=invis]
}Or, if we want to avoid typing, we can add spcify that all edges after a certain point are invisible.
digraph G{
rankdir = LR;
node [shape = box, fontname="Helvetica"];
// Current-time nodes
urchins [label="urchins"];
kelp [label="kelp"];
temperature [label="temperature", pos="5,1"];
// Lagged nodes
urchins_lag [label="urchins(t-1)"];
kelp_lag [label="kelp(t-1)"];
// Edges
urchins -> kelp [penwidth=8];
temperature -> kelp;
temperature -> urchins [style=dashed];
kelp_lag -> urchins;
kelp_lag -> kelp [label="growth"];
urchins_lag -> urchins [penwidth=3];
// Invisible edges for positioning
edge[style=invis]
kelp->temperature;
urchins->temperature;
}7 Make it fancy with subgraphs
This is great, but - what if we want to have the lagged variables standout? Rather than embroil them in a subgraph via edges, we can do the same thing with nodes. We can wrap the nodes in a call to subgraph to make a kind of supernode! We do this by giving the supernode a name, let’s say cluster_lag
digraph G{
rankdir = LR;
node [shape = box, fontname="Helvetica"];
// Current-time nodes
urchins [label="urchins"];
kelp [label="kelp"];
temperature [label="temperature", pos="5,1"];
// Lagged nodes
// Lagged nodes
subgraph cluster_lag {
urchins_lag [label="urchins(t-1)"];
kelp_lag [label="kelp(t-1)"];
}
// Edges
urchins -> kelp [penwidth=8];
temperature -> kelp;
temperature -> urchins [style=dashed];
kelp_lag -> urchins;
kelp_lag -> kelp [label="growth"];
urchins_lag -> urchins [penwidth=3];
// Invisible edges for positioning
edge[style=invis]
kelp->temperature;
urchins->temperature;
}OK - this is kinda bad looking, because we see the drawing of the rectangle outside of them. What if we want a solid color? Well, we can use any node attribute we want to edit this subgraph supernode. Let’s go with style=filled and work from there.
digraph G{
rankdir = LR;
node [shape = box, fontname="Helvetica"];
// Current-time nodes
urchins [label="urchins"];
kelp [label="kelp"];
temperature [label="temperature", pos="5,1"];
// Lagged nodes
// Lagged nodes
subgraph cluster_lag {
style=filled;
color=lightgrey;
fillcolor=lightgrey;
urchins_lag [label="urchins(t-1)"];
kelp_lag [label="kelp(t-1)"];
}
// Edges
urchins -> kelp [penwidth=8];
temperature -> kelp;
temperature -> urchins [style=dashed];
kelp_lag -> urchins;
kelp_lag -> kelp [label="growth"];
urchins_lag -> urchins [penwidth=3];
// Invisible edges for positioning
edge[style=invis]
kelp->temperature;
urchins->temperature;
}Oh! Better! If we want OUR nodes to pop, we’ll have to fill them, too, but this time with white. Last, let’s add a label to our subgraph.
digraph G{
rankdir = LR;
node [shape = box, fontname="Helvetica"];
// Current-time nodes
urchins [label="urchins"];
kelp [label="kelp"];
temperature [label="temperature", pos="5,1"];
// Lagged nodes
// Lagged nodes
subgraph cluster_lag {
label="(t-1)";
style=filled;
color=lightgrey;
fillcolor=lightgrey;
urchins_lag [label="urchins(t-1)", style=filled, fillcolor = white];
kelp_lag [label="kelp(t-1)", style=filled, fillcolor = white];
}
// Edges
urchins -> kelp [penwidth=8];
temperature -> kelp;
temperature -> urchins [style=dashed];
kelp_lag -> urchins;
kelp_lag -> kelp [label="growth"];
urchins_lag -> urchins [penwidth=3];
// Invisible edges for positioning
edge[style=invis]
kelp->temperature;
urchins->temperature;
}Ah! It reset the font. OK, let’s fix that, too.
digraph G{
rankdir = LR;
node [shape = box, fontname="Helvetica"];
// Current-time nodes
urchins [label="urchins"];
kelp [label="kelp"];
temperature [label="temperature", pos="5,1"];
// Lagged nodes
// Lagged nodes
subgraph cluster_lag {
label="(t-1)";
style=filled;
color=lightgrey;
fillcolor=lightgrey;
fontname="Helvetica";
urchins_lag [label="urchins(t-1)", style=filled, fillcolor = white];
kelp_lag [label="kelp(t-1)", style=filled, fillcolor = white];
}
// Edges
urchins -> kelp [penwidth=8];
temperature -> kelp;
temperature -> urchins [style=dashed];
kelp_lag -> urchins;
kelp_lag -> kelp [label="growth"];
urchins_lag -> urchins [penwidth=3];
// Invisible edges for positioning
edge[style=invis]
kelp->temperature;
urchins->temperature;
}8 Some last bits of general formatting
That’s about it! Our edge is still the wrong font, but we can reset that with some additional edge formatting at the head. But, isn’t that nice!
digraph G{
rankdir = LR;
graph [fontname="Helvetica"];
node [shape=box, fontname="Helvetica"];
edge [fontname="Helvetica"];
// Current-time nodes
urchins [label="urchins"];
kelp [label="kelp"];
temperature [label="temperature", pos="5,1"];
// Lagged nodes
// Lagged nodes
subgraph cluster_lag {
label="(t-1)";
style=filled;
color=lightgrey;
fillcolor=lightgrey;
urchins_lag [label="urchins(t-1)", style=filled, fillcolor = white];
kelp_lag [label="kelp(t-1)", style=filled, fillcolor = white];
}
// Edges
urchins -> kelp [penwidth=8];
temperature -> kelp;
temperature -> urchins [style=dashed];
kelp_lag -> urchins;
kelp_lag -> kelp [label="growth"];
urchins_lag -> urchins [penwidth=3];
// Invisible edges for positioning
edge[style=invis]
kelp->temperature;
urchins->temperature;
}Our completed graph! Look how far we’ve come!
It’s worth thinking at this point - is this what you wanted? Could you do with less? The sky is the limit when it comes to making graphs, so, ponder what YOU want to do!