2012 0004 0007
Graphviz
is a useful toolset for describing and rendering graphs.
One of the features the graphviz language doesn’t have, however, is a
C
-like preprocessor to #include
files. Which, granted, isn’t a
particularly common use case when building graphs, but one I found
desirable when dealing with a large number of graphs with a shared
subset of nodes, differing by how they are connected.
Initially, I grafted together an unwieldy script that used an
ugly grep
+sed
combination to grab the line number and substitute
the included file contents: essentially a poor man’s preprocessor.
Thankfully, to the rescue was a particularly useful gist
(initially illustrated with JavaScript) I serendipitously found
on Reddit. The key call being this:
cpp -P -undef -Wundef -std=c99 -nostdinc -Wtrigraphs \
-fdollars-in-identifiers -C < input.dot > output.dot
In your input .dot
file, standard C
include syntax
will work as expected, despite the fact that it is a completely different language.
This solution is highly verbose if you don’t drop it in a build chain and forget about it. Which is straightforward using a standard makefile:
In the above example, I introduced an intermediate doto
file
(analogous to an object file) and doth
(header file) to recreate a
C
-like build process.
Another ingredient above is the piped invocation of gvpr
, which
removes nodes of degree 1 (so that included nodes that are not
attached to anything in the current file will be ignored). Remember
that in makefiles
, the $
must be escaped by using
$$
. Unfortunately, the delete
function in gvpr
is broken in
a number of Debian-based distros (at least), but the latest version
works bug-free in Arch.
So, given a nodes.doth
file with these contents:
node1 [label = "1"];
node2 [label = "2"];
node3 [label = "3"];
and a graph.dot
file as such:
graph g {
#include "nodes.doth"
node1 -- node2;
node2 -- node3;
node3 -- node1;
}
the makefile
will use cpp
to generate the following intermediate
file,
graph g {
node1 [label = "1"];
node2 [label = "2"];
node3 [label = "3"];
node1 -- node2;
node2 -- node3;
node3 -- node1;
}
which will then be compiled by graphviz’s dot
command into an image.
While obviously not necessary with this toy example, scaling up to
more nodes shared by multiple graphs is much more pleasant when the
nodes don’t have to be duplicated in each graph.
Very little of this is exclusive to graphviz, and is reasonable to
extrapolate to other problems fairly easily. And, since this
literally uses the C
preprocessor to do the job, there’s many more
tricks to be explored.
Quite helpful to have on hand when the need arises, and a testament to
the quality of cpp
that it can be used for arbitrary metaprogramming
in other languages.