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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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: