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)) |
Generated images: