2019-10-25

Programmatically Drawing Simple Graphviz Graphs

For my book, I had to draw a number of graphs. Obviously, I wanted to have a programmatic way to do that, and Graphviz is the goto-library for that. In Lisp, the interface to Graphviz is cl-dot, but, for me, it wasn't easy to figure out from the manual the "simple and easy" way to use it. I.e. I couldn't find a stupid beginner-level interface, so I had to code it myself. Here's the implementation that allows anyone with a REPL to send to Graphviz lists of edges and obtain graph images.

(defclass dwim-graph () ())
(defmethod cl-dot:graph-object-node ((graph (eql 'dwim-graph)) object)
(make-instance 'cl-dot:node
:attributes (list :label (format nil "~A" object)
:shape :circle)))
(defmacro dwim-graph ((file &key (directed t)) &body edges)
`(progn
(eval-when (:compile-toplevel :load-toplevel :execute)
(defmethod cl-dot:graph-object-edges ((graph (eql 'dwim-graph)))
,(coerce (mapcar (lambda (edge)
(destructuring-bind (beg end &rest args) edge
(append (list beg end)
(unless directed `((:arrowhead :none)))
args)))))
edges)
'vector)))
(cl-dot:dot-graph
(cl-dot:generate-graph-from-roots 'dwim-graph () '(:rankdir "LR"))
,file
:format (intern (or (etypecase ,file
(pathname (pathname-type ,file))
(string (let ((dot-pos (position #\. ,file
:from-end t)))
(when dot-pos
(subseq ,file (1+ dot-pos))))))
"jpg")
:keyword))
,file))
#+directed-weighted-graph
(dwim-graph ("/tmp/g.jpg")
(0 1 4)
(0 2 4)
(1 3 4)
(1 4 1)
(2 3 2)
(2 4 2)
(3 5 3)
(4 5 5))
#+undirected-unweighted-graph
(dwim-graph ("/tmp/g.jpg" :directed nil)
(0 1)
(0 2)
(1 3)
(1 4)
(2 3)
(2 4)
(3 5)
(4 5))
view raw dwim-graph.lisp hosted with ❤ by GitHub

Generated images: