e07e661cbe829be5176ad2f820c40e6fb91e2430
[cl-graph.git] / dev / graphviz / graphviz-support.lisp
1 ;;;-*- Mode: Lisp; Package: metabang.graph -*-
2
3 #| simple-header
4
5 $Id: graphviz-support.lisp,v 1.7 2005/06/21 20:51:51 moody Exp $
6
7 Copyright 1992 - 2005 Experimental Knowledge Systems Lab, 
8 University of Massachusetts Amherst MA, 01003-4610
9 Professor Paul Cohen, Director
10
11 Author: Gary King, Levente Mészáros, Attila Lendvai
12
13 DISCUSSION
14
15 This file contains the stuff that does not depend on cl-graphviz.
16
17 |#
18 (in-package metabang.graph)
19
20 ;;; ---------------------------------------------------------------------------
21 ;
22 ; This outputs the graph to string in accordance with the DOT file format.  
23 ; For more information about DOT file format, search the web for "DOTTY" and 
24 ; "GRAPHVIZ".
25 ;
26 (defmethod graph->dot ((g basic-graph) (stream stream)
27                        &key 
28                        (graph-formatter 'graph->dot-properties)
29                        (vertex-key 'vertex-id)
30                        (vertex-labeler nil)
31                        (vertex-formatter 'vertex->dot)
32                        (edge-key nil)
33                        (edge-labeler 'princ) 
34                        (edge-formatter 'edge->dot))
35   (format stream "~A G {~%graph " (if (contains-undirected-edge-p g) "graph" "digraph"))
36   (format stream "[")
37   (funcall graph-formatter g stream)
38   (format stream "];")
39   (terpri stream)
40   
41   ;; vertex formatting
42   (iterate-vertexes 
43    g
44    (lambda (v)
45      (terpri stream)
46      (let ((key (if vertex-key (funcall vertex-key v) v)))
47        (princ key stream)
48        (princ " [" stream)
49        (when vertex-labeler
50          (princ "label=\"" stream)
51          (funcall vertex-labeler v stream)
52          (princ "\", " stream))
53        (funcall vertex-formatter v stream)
54        (princ "];" stream))))
55   
56   (let ((directed-edge-connector (if (contains-undirected-edge-p g) "--" "->"))
57         (directed-edge-tag (when (and (contains-undirected-edge-p g)
58                                       (contains-directed-edge-p g))
59                              "dir=forward, ")))
60     (flet ((format-edge (e connector from to directed?)
61              (terpri stream)
62              (princ (funcall vertex-key from) stream)
63              (princ connector stream)
64              (princ (funcall vertex-key to) stream) 
65              (princ " [" stream)
66              (when (and directed? directed-edge-tag)
67                (princ directed-edge-tag stream))
68              (when edge-key
69                (princ "label=\"" stream)
70                (funcall edge-labeler e stream)
71                (princ "\"," stream))
72              (funcall edge-formatter e stream)
73              (princ "];" stream)))
74       ;; directed edges
75       (iterate-vertexes 
76        g
77        (lambda (v)
78          (iterate-target-edges
79           v
80           (lambda (e) 
81             (when (directed-edge-p e)
82               (format-edge e directed-edge-connector 
83                            (source-vertex e) (target-vertex e) t))))))
84       
85       ;; undirected edges
86       (let ((edges (make-container 'simple-associative-container)))
87         (iterate-vertexes 
88          g
89          (lambda (v)
90            (iterate-edges
91             v
92             (lambda (e)
93               (when (and (undirected-edge-p e)
94                          (not (item-at-1 edges e)))
95                 (setf (item-at-1 edges e) t)
96                 (format-edge e "--" (vertex-1 e) (vertex-2 e) nil)))))))))
97   
98   (terpri stream)
99   (princ "}" stream)
100   
101   (values g))
102
103
104 #+Test
105 (let ((g (make-container 'graph-container :default-edge-type :undirected)))
106   (loop for (a b) in '((a b) (b c) (b d) (d e) (e f) (d f)) do
107         (add-edge-between-vertexes g a b))
108   (graph->dot g nil))
109
110 #+Test
111 "graph G {
112 E []
113 C []
114 B []
115 A []
116 D []
117 F []
118 D--E []
119 E--F []
120 B--C []
121 A--B []
122 B--D []
123 D--F []
124 }"
125
126 #+Test
127 (let ((g (make-container 'graph-container :default-edge-type :directed)))
128   (loop for (a b) in '((a b) (b c) (b d) (d e) (e f) (d f)) do
129         (add-edge-between-vertexes g a b))
130   (graph->dot g nil))
131
132 #+Test
133 "digraph G {
134 E []
135 C []
136 B []
137 A []
138 D []
139 F []
140 E->F []
141 B->C []
142 B->D []
143 A->B []
144 D->E []
145 D->F []
146 }"
147
148 #+Test
149 (let ((g (make-container 'graph-container)))
150   (loop for (a b) in '((d e) (e f) (d f)) do
151         (add-edge-between-vertexes g a b :edge-type :directed))
152   (loop for (a b) in '((a b) (b c) (b d)) do
153         (add-edge-between-vertexes g a b :edge-type :undirected))
154   (graph->dot g nil))
155
156 #+Test
157 "graph G {
158 E []
159 C []
160 B []
161 A []
162 D []
163 F []
164 E--F [dir=forward, ]
165 D--E [dir=forward, ]
166 D--F [dir=forward, ]
167 B--C []
168 A--B []
169 B--D []
170 }"
171
172 ;;; ---------------------------------------------------------------------------
173
174 (defmethod graph->dot ((g basic-graph) (stream (eql nil))
175                        &rest args &key &allow-other-keys)
176   (declare (dynamic-extent args))
177   (with-output-to-string (out)
178     (apply #'graph->dot g out args)))
179
180 ;;; ---------------------------------------------------------------------------
181
182 (defmethod graph->dot ((g basic-graph) (stream (eql t))
183                        &rest args &key &allow-other-keys)
184   (declare (dynamic-extent args))
185   (apply #'graph->dot g *standard-output* args))
186
187 ;;; ---------------------------------------------------------------------------
188
189 (defmethod graph->dot ((g basic-graph) (stream string)
190                        &rest args &key &allow-other-keys)
191   (declare (dynamic-extent args))
192   (with-open-file (out stream :direction :output :if-exists :supersede)
193     (apply #'graph->dot g out args)))
194
195 ;;; ---------------------------------------------------------------------------
196
197 (defmethod graph->dot ((g basic-graph) (stream pathname)
198                        &rest args &key &allow-other-keys)
199   (declare (dynamic-extent args))
200   (apply #'graph->dot g (namestring stream) args))
201
202 ;;; ---------------------------------------------------------------------------
203
204 (defmethod graph->dot-properties ((g t) (stream t))
205   (values))
206
207 ;;; ---------------------------------------------------------------------------
208
209 (defmethod vertex->dot ((v basic-vertex) (stream stream))
210   (values))
211
212 ;;; ---------------------------------------------------------------------------
213
214 (defmethod edge->dot ((v basic-edge) (stream stream))
215   (values))
216
217 ;;; ---------------------------------------------------------------------------
218 ;;; dot->graph
219 ;;; ---------------------------------------------------------------------------
220
221 #|
222 (defmethod dot->graph ((dot-stream stream)
223                        &key)
224   )
225
226 ;;; ---------------------------------------------------------------------------
227
228 (defmethod dot->graph ((dot-stream string)
229                        &rest args &key &allow-other-keys)
230   (declare (dynamic-extent args))
231   (with-open-file (out stream :direction :output :if-exists :supersede)
232     (apply #'dot->graph g out args)))
233
234 ;;; ---------------------------------------------------------------------------
235
236 (defmethod dot->graph ((dot-stream pathname)
237                        &rest args &key &allow-other-keys)
238   (declare (dynamic-extent args))
239   (with-open-file (out stream :direction :output :if-exists :supersede)
240     (apply #'dot->graph g out args))
241   (apply #'dot->graph g (namestring stream) args))
242
243 |#
244
245 (defparameter *dot-graph-attributes*
246   '((:size coord)
247     (:bb bounding-box)
248     (:page text)
249     (:ratio (:fill :compress :auto)) ;; Could actually be a float number too
250     (:margin float)
251     (:nodesep float)
252     (:ranksep float)
253     (:ordering (:out))
254     (:rankdir ("LR" "RL" "BT"))
255     (:pagedir text)
256     (:rank (:same :min :max))
257     (:rotate integer)
258     (:center integer)
259     (:nslimit float)
260     (:mclimit float)
261     (:layers text)
262     (:color text)
263     (:bgcolor text)))
264
265 (defparameter *dot-vertex-attributes*
266   '((:pos coordinate)
267     (:height float)
268     (:width float)
269     (:fixed-size boolean)
270     (:label text)
271     (:shape (:record :plaintext :ellipse :circle :egg :triangle :box
272              :diamond :trapezium :parallelogram :house :hexagon :octagon
273              :doublecircle))
274     (:fontsize integer)
275     (:fontname text)
276     (:color text)
277     (:fillcolor text)
278     (:style (:filled :solid :dashed :dotted :bold :invis))
279     (:layer text)))
280
281 (defparameter *dot-edge-attributes*
282   '((:pos spline)
283     (:minlen integer)
284     (:weight integer)
285     (:label text)
286     (:fontsize integer)
287     (:fontname text)
288     (:fontcolor text)
289     (:style (:solid :dashed :dotted :bold :invis))
290     (:color text)
291     (:dir (:forward :back :both :none))
292     (:tailclip boolean)
293     (:headclip boolean)
294     (:arrowhead (:none :normal :inv :dot :odot :invdot :invodot :tee
295                  :empty :invempty :open :halfopen :diamond :odiamond
296                  :box :obox :crow))
297     (:arrowtail (:none :normal :inv :dot :odot :invdot :invodot :tee
298                  :empty :invempty :open :halfopen :diamond :odiamond
299                  :box :obox :crow))
300     (:headlabel text)
301     (:taillabel text)
302     (:labelfontsize integer)
303     (:labelfontname text)
304     (:labelfontcolor text)
305     (:labeldistance integer)
306     (:port-label-distance integer)
307     (:decorate boolean)
308     (:samehead boolean)
309     (:sametail boolean)
310     (:constraint boolean)
311     (:layer text)))
312
313 (defclass* dot-attributes-mixin ()
314   ((dot-attributes nil ia))
315   (:export-p t))
316
317 (defclass* dot-graph-mixin (dot-attributes-mixin) ()
318   (:export-p t))
319 (defclass* dot-vertex-mixin (dot-attributes-mixin) ()
320   (:export-p t))
321 (defclass* dot-edge-mixin (dot-attributes-mixin) ()
322   (:export-p t))
323
324 (defclass* dot-graph (graph-container dot-graph-mixin)
325   ()
326   (:default-initargs
327     :vertex-class 'dot-vertex
328     :directed-edge-class 'dot-directed-edge
329     :undirected-edge-class 'dot-edge)
330   (:export-p t))
331
332 (defclass* dot-vertex (graph-container-vertex dot-vertex-mixin) ()
333   (:export-p t))
334 (defclass* dot-edge (graph-container-edge dot-edge-mixin) ()
335   (:export-p t))
336 (defclass* dot-directed-edge (directed-edge-mixin dot-edge) ()
337   (:export-p t))
338
339
340 (defmethod (setf dot-attribute) :before (value (attr symbol) (thing dot-attributes-mixin))
341   (ensure-valid-dot-attribute attr thing))
342
343 (defmethod (setf dot-attribute) (value (attr symbol) (thing dot-attributes-mixin))
344   (setf (getf (dot-attributes thing) attr) value))
345
346 (defmethod dot-attribute-value ((attr symbol) (thing dot-attributes-mixin))
347   (getf (dot-attributes thing) attr))
348
349 (defmethod graph->dot-properties ((graph dot-graph-mixin) (stream t))
350   (loop for (name value) on (dot-attributes graph) by #'cddr
351         do
352         (print-dot-key-value name value *dot-graph-attributes* stream)
353         (format stream "                 ;~%")))
354
355 (defmethod vertex->dot ((vertex dot-vertex-mixin) (stream t))
356   (format-dot-attributes vertex *dot-vertex-attributes* stream))
357
358 (defmethod edge->dot ((edge dot-edge-mixin) (stream t))
359   (format-dot-attributes edge *dot-edge-attributes* stream))
360
361 (defun format-dot-attributes (object dot-attributes stream)
362   (loop for (name value) on (dot-attributes object) by #'cddr
363         for prefix = "" then ", " do
364         (write-string prefix stream)
365         (print-dot-key-value name value dot-attributes stream)))
366
367 (defmethod ensure-valid-dot-attribute (key (object dot-graph-mixin))
368   (or (assoc key *dot-graph-attributes*)
369       (error "Invalid dot graph attribute ~S" key)))
370
371 (defmethod ensure-valid-dot-attribute (key (object dot-vertex-mixin))
372   (or (assoc key *dot-vertex-attributes*)
373       (error "Invalid dot vertex attribute ~S" key)))
374
375 (defmethod ensure-valid-dot-attribute (key (object dot-edge-mixin))
376   (or (assoc key *dot-edge-attributes*)
377       (error "Invalid dot edge attribute ~S" key)))
378
379 (defun print-dot-key-value (key value dot-attributes stream)
380   (destructuring-bind (key value-type)
381       (or (assoc key dot-attributes)
382           (error "Invalid attribute ~S" key))
383     (format stream "~a=~a" (string-downcase key)
384             (etypecase value-type
385               ((member coordinate)
386                (with-output-to-string (str)
387                  (princ "\"" str)
388                  (let ((first t))
389                    (dolist (el value)
390                      (unless first
391                        (princ "," str))
392                      (princ el str)
393                      (setf first nil)))
394                  (princ "\"" str)))
395               ((member spline bounding-box)
396                (with-output-to-string (str)
397                  (princ "\"" str)
398                  (let ((first t))
399                    (dolist (el value)
400                      (unless first
401                        (princ " " str))
402                      (princ (first el) str)
403                      (princ "," str)
404                      (princ (second el) str)
405                      (setf first nil)))
406                  (princ "\"" str)))
407               ((member integer)
408                (unless (typep value 'integer)
409                  (error "Invalid value for ~S: ~S is not an integer"
410                         key value))
411                value)
412               ((member boolean)
413                (if value
414                    "true"
415                    "false"))
416               ((member text)
417                (textify value))
418               ((member float)
419                (coerce value 'single-float))
420               (list
421                (unless (member value value-type :test 'equal)
422                  (error "Invalid value for ~S: ~S is not one of ~S"
423                         key value value-type))
424                (if (symbolp value)
425                    (string-downcase value)
426                    value))))))
427
428 (defun textify (object)
429   (let ((string (princ-to-string object)))
430     (with-output-to-string (stream)
431       (write-char #\" stream)
432       (loop for c across string do
433             ;; Note: #\\ should not be escaped to allow \n, \l, \N, etc.
434             ;; to work.
435             (case c
436               ((#\")
437                (write-char #\\ stream)
438                (write-char c stream))
439               (#\Newline
440                (write-char #\\ stream)
441                (write-char #\n stream))
442               (t
443                (write-char c stream))))
444       (write-char #\" stream))))
445
446 ;;; ---------------------------------------------------------------------------
447 ;
448 ; Calls the dot executable to create external output for graphs
449 ;
450 #+(or win32 mswindows)
451 (defvar *dot-path* "\"C:/Program Files/ATT/Graphviz/bin/dot.exe\"")
452 #+(or linux unix)
453 (defvar *dot-path* "/usr/bin/dot" "Path to `dot`")
454
455 (defmethod graph->dot-external ((g basic-graph) file-name &key (type :ps))
456   "Generate an external represenation of a graph to a file, by running
457 the program in *dot-path*."
458   (let ((dot-string (graph->dot g nil))
459         (dot-type (concatenate 'string "-T" (string-downcase (symbol-name type)))))
460     #+lispworks (with-open-stream
461                     (s (sys:open-pipe (concatenate 'string *dot-path* " -Tpng -o" file-name)
462                                       :direction :input))
463                     (write-line dot-string s)
464                     (force-output s)
465                     (close s))
466     #+sbcl
467     (sb-ext:run-program *dot-path*
468                         (list dot-type "-o" file-name)
469                         :input (make-string-input-stream dot-string)
470                         :output *standard-output*)
471     #-(or sbcl lispworks)
472     (error "Don't know how to execute a program on this platform")))
473
474 ;;; ---------------------------------------------------------------------------
475 ;
476 ; Test dot external
477 ;
478 (defun test-dot-external ()
479   (let* ((g (make-graph 'dot-graph))
480          (v1 (add-vertex g 'a :dot-attributes '(:shape :box
481                                                 :color :blue)))
482          (v2 (add-vertex g 'b :dot-attributes '(:shape :circle
483                                                 :style :filled
484                                                 :color :yellow))))
485     (add-edge-between-vertexes g v1 v2
486                                :dot-attributes '(:arrowhead :open
487                                                  :arrowtail :normal
488                                                  :style :dotted))
489     (print (graph->dot g nil))
490     (graph->dot-external g "/tmp/test.gif" :type :gif)))