psgraph - Common

advertisement
Here is a brief summary of how to use the PSgrapher.
The PSgrapher is a set of Lisp routines that can be called to produce
Postscript commands to display a directed acyclic graph. It uses the
same basic algorithm as the ISI Grapher. It is slower than the ISI
grapher, but produces much prettier output than using Xdump to copy an
ISI Grapher screen image to a Postscript printer. It also allows
arbitrary information to be displayed with a node, and can print as
many output pages as are necessary to accomodate large graphs (subject
to Postscript printer limits, typically one or two thousand nodes).
The PSgrapher is small enough and easy enough to use to be included in
other systems without pain. It is also free and not copyrighted.
It is written in Common Lisp, and has been tested on CMU Common Lisp 18
All interfaces described below are exported from the "PSGRAPH" package.
There are several useful global variables that can be modified and one
function to be called. The variables affect the style of the printed
image, the function walks a graph and produces output.
A graph is defined by a root node and a function for getting children
of a node. Nodes are named by Lisp objects, and these names are
checked using a user-supplied test function (defaults to EQUAL) for
purposes of walking the graph and topologically sorting it. Typically
one uses atoms to name nodes, for example in graphing an inheritence
net one could use the class names as node names.
To produce a graph call the function:
(psgraph:psgraph output-stream
root-node-name
child-function
info-function
&optional (shrink nil)
&optional (insert nil)
&optional (test #'equal)
&optional (same-height nil))
Output-stream is the stream that the postscript is output to.
Root-node-name is a Lisp object naming the root of the graph (there
must be exactly one root).
Child-function takes a node name and returns a list of node names.
Info-function takes a node name and returns a list of strings. Each
string shows up as one line in the printed form of the node, with the
first string displayed in one font (determined by variables
*fontname*, *fontsize*) and the others in another font (determined by
variables *second-fontname*, *second-fontsize*). Typically the first
string is just the name of the node, eg, (string node-name).
Shrink may be NIL, T of :WIDTH. It defaults to nil, and in this case
the graph prints on as many pages as are needed to show the whole
thing. If shrink is T then the entire graph is squashed appropriately
to fit on one page of output. If shrink is :width then the width of
the graph is shrunk appropriately to fit across one page of output but
print on as many pages as necessary to show the full height of the graph.
Insert is a boolean. It defaults to nil, and in this case the generated
Postscript file contains commands to print the output. If insert is t
then the output file is always shrunk (shrink is t) and no commands are
generated to print the page. This means the resulting file can be
included
in Scribe files, for example, and Scribe can decide when to print the
page.
Test is a function that return whether two nodes are the same.
Same-height is a boolean. It defaults to Nil, and in this case the
height of the node no large that necessary. If same-height is T the
same height is used for all the nodes which can improve the balance of
tree graphs.
-=After loading psgraph, one can play with the various global variables,
all of which have string values (except for the two font size
variables). *fontname*, *fontsize*, *second-fontname*,
*second-fontsize* are as described above. They default to "Helvetica"
in 10 point and "Helvetica-Oblique" in 8 point, respectively.
*boxgray* and *boxkind* determine how the boxes around nodes look.
*boxkind* is either "fill" or "stroke", for solid or outline boxes,
and *boxgray* is "0" for solid black, "1" for solid white, or in
between. These default to ".2" and "stroke".
Other variables:
*max-psnodes*
*extra-x-spacing*
*extra-y-spacing*
*edgewidth*
*edgegray*
*edgecap*
*textgray*
*pageheight*
*pagewidth*
*boxradius*
*boxedge*
*chunksize*
Psgraph sends its output to the output-stream, so typically it is
called from within (with-open-file ....). For example, one might use
it like this:
(load "psgraph")
(setf psgraph:*boxkind* "fill")
(setf psgraph:*boxgray* ".8")
(setf psgraph:*fontsize* 8)
(setf psgraph:*second-fontsize* 6)
(defun children (x)
(cond ((eq x 'A) '(B C D))
((member x '(B C D)) '(E F G))
((member x '(E F G)) '(H))
(t nil)))
(defun info (x)
(list (string x)))
(defun graph (&optional (shrink t))
(with-open-file (output-stream "g.ps"
:direction :output
:if-exists :supersede)
(psgraph:psgraph output-stream 'A #'children #'info shrink nil
#'eq)))
Another example:
(defun code-graph-to-file (s file &optional shrink insert)
(let ((psgraph:*fontname* "Times-Roman")
(psgraph:*fontsize* 8)
(psgraph:*second-fontname* "Times-BoldItalic")
(psgraph:*second-fontsize* 6)
(psgraph:*boxgray* "0")
(psgraph:*edgegray* "0")
(psgraph:*extra-x-spacing* 30))
(with-open-file (output-stream file
:direction :output
:if-exists :supersede)
(psgraph:psgraph s #'psg-children #'psg-info shrink insert
#'eq))))
Download