'''
Interface to the dot language
=============================
The B{Dot} module provides a simple interface to the
file format used in the U{graphviz}
program. The module is intended to offload the most tedious part of the process
(the B{dot} file generation) while transparently exposing most of its features.
To display the graphs or to generate image files the U{graphviz}
package needs to be installed on the system, moreover the C{dot} and C{dotty} programs must
be accesible in the program path so that they can be ran from processes spawned
within the module. See the L{Dot} documentation for further information on the setup.
Example usage
-------------
Here is a typical usage::
from altgraph import Graph, Dot
# create a graph
edges = [ (1,2), (1,3), (3,4), (3,5), (4,5), (5,4) ]
graph = Graph.Graph(edges)
# create a dot representation of the graph
dot = Dot.Dot(graph)
# display the graph
dot.display()
# save the dot representation into the mydot.dot file
dot.save_dot(file_name='mydot.dot')
# save dot file as gif image into the graph.gif file
dot.save_img(file_name='graph', file_type='gif')
Customizing the output
----------------------
The graph drawing process may be customized by passing
valid B{dot} parameters for the nodes and edges. For a list of all
parameters see the U{graphviz}
documentation.
Example::
# customizing the way the overall graph is drawn
dot.style(size='10,10', rankdir='RL', page='5, 5' , ranksep=0.75)
# customizing node drawing
dot.node_style(1, label='BASE_NODE',shape='box', color='blue' )
dot.node_style(2, style='filled', fillcolor='red')
# customizing edge drawing
dot.edge_style(1, 2, style='dotted')
dot.edge_style(3, 5, arrowhead='dot', label='binds', labelangle='90')
dot.edge_style(4, 5, arrowsize=2, style='bold')
B{Observation}: dotty (invoked via L{Dot.display}) may not be able to
display all graphics styles. To verify the output save it to an image file
and look at it that way.
Valid attributes
----------------
- dot styles, passed via the L{Dot.style} method::
rankdir = 'LR' (draws the graph horizontally, left to right)
ranksep = number (rank separation in inches)
- node attributes, passed via the L{Dot.node_style} method::
style = 'filled' | 'invisible' | 'diagonals' | 'rounded'
shape = 'box' | 'ellipse' | 'circle' | 'point' | 'triangle'
- edge attributes, passed via the L{Dot.edge_style} method::
style = 'dashed' | 'dotted' | 'solid' | 'invis' | 'bold'
arrowhead = 'box' | 'crow' | 'diamond' | 'dot' | 'inv' | 'none' | 'tee' | 'vee'
weight = number (the larger the number the closer the nodes will be)
- valid U{graphviz colors}
- for more details on how to control the graph drawing process see the
U{graphviz reference }.
'''
import os
from altgraph import GraphError
from altgraph.compat import *
class Dot(object):
'''
A class providing a B{graphviz} (dot language) representation
allowing a fine grained control over how the graph is being
displayed.
If the C{dot} and C{dotty} programs are not in the current system path
their location needs to be specified in the L{constructor<__init__>}.
For detailed example usage see the L{Dot} module documentation.
'''
def __init__(self, graph=None, nodes=None, edgefn=None, nodevisitor=None, edgevisitor=None, name="G", dot='dot', dotty='dotty', neato='neato'):
'''
Initialization.
'''
self.name, self.attr = name, {}
self.temp_dot = "tmp_dot.dot"
self.temp_neo = "tmp_neo.dot"
self.dot, self.dotty, self.neato = dot, dotty, neato
self.nodes, self.edges = {}, {}
if graph is not None and nodes is None:
nodes = graph
if graph is not None and edgefn is None:
def edgefn(node, graph=graph):
return imap(graph.tail, graph.out_edges(node))
if nodes is None:
nodes = ()
seen = set()
for node in nodes:
if nodevisitor is None:
style = {}
else:
style = nodevisitor(node)
if style is not None:
self.node_style(node, **style)
seen.add(node)
if edgefn is not None:
for head in seen:
for tail in ifilter(seen.__contains__, edgefn(head)):
if edgevisitor is None:
edgestyle = {}
else:
edgestyle = edgevisitor(head, tail)
if edgestyle is not None:
self.edge_style(head, tail, **edgestyle)
def style(self, **attr):
'''
Changes the overall style
'''
self.attr = attr
def display(self, mode='dot'):
'''
Displays the current graph via dotty
'''
if mode == 'neato':
self.save_dot(self.temp_neo)
neato_cmd = "%s -o %s %s" % (self.neato, self.temp_dot, self.temp_neo)
os.system(neato_cmd)
else:
self.save_dot(self.temp_dot)
plot_cmd = "%s %s" % (self.dotty, self.temp_dot)
os.system(plot_cmd)
def node_style(self, node, **kwargs):
'''
Modifies a node style to the dot representation.
'''
if node not in self.edges:
self.edges[node] = {}
self.nodes[node] = kwargs
def all_node_style(self, **kwargs):
'''
Modifies all node styles
'''
for node in self.nodes:
self.node_style(node, **kwargs)
def edge_style(self, head, tail, **kwargs):
'''
Modifies an edge style to the dot representation.
'''
try:
if tail not in self.edges[head]:
self.edges[head][tail]= {}
self.edges[head][tail] = kwargs
except KeyError:
raise GraphError("invalid edge %s -> %s " % (head, tail) )
def iterdot(self):
# write graph title
yield 'digraph %s {\n' % (self.name,)
# write overall graph attributes
for attr_name, attr_value in self.attr.iteritems():
yield '%s="%s";' % (attr_name, attr_value)
yield '\n'
# some reusable patterns
cpatt = '%s="%s",' # to separate attributes
epatt = '];\n' # to end attributes
# write node attributes
for node_name, node_attr in self.nodes.iteritems():
yield '\t"%s" [' % (node_name,)
for attr_name, attr_value in node_attr.iteritems():
yield cpatt % (attr_name, attr_value)
yield epatt
# write edge attributes
for head in self.edges:
for tail in self.edges[head]:
yield '\t"%s" -> "%s" [' % (head, tail)
for attr_name, attr_value in self.edges[head][tail].iteritems():
yield cpatt % (attr_name, attr_value)
yield epatt
# finish file
yield '}\n'
def __iter__(self):
return self.iterdot()
def save_dot(self, file_name=None):
'''
Saves the current graph representation into a file
'''
if not file_name:
file_name = self.temp_dot
fp = open(file_name, "w")
write = fp.write
for chunk in self.iterdot():
write(chunk)
fp.close()
def save_img(self, file_name="out", file_type="gif", mode='dot'):
'''
Saves the dot file as an image file
'''
if mode == 'neato':
self.save_dot(self.temp_neo)
neato_cmd = "%s -o %s %s" % (self.neato, self.temp_dot, self.temp_neo)
os.system(neato_cmd)
plot_cmd = self.neato
else:
self.save_dot(self.temp_dot)
plot_cmd = self.dot
file_name = "%s.%s" % (file_name, file_type)
create_cmd = "%s -T%s %s -o %s" % (plot_cmd, file_type, self.temp_dot, file_name)
os.system(create_cmd)