2 altgraph.Dot - Interface to the dot language
3 ============================================
5 The :py:mod:`~altgraph.Dot` module provides a simple interface to the
6 file format used in the `graphviz <http://www.research.att.com/sw/tools/graphviz/>`_
7 program. The module is intended to offload the most tedious part of the process
8 (the **dot** file generation) while transparently exposing most of its features.
10 To display the graphs or to generate image files the `graphviz <http://www.research.att.com/sw/tools/graphviz/>`_
11 package needs to be installed on the system, moreover the :command:`dot` and :command:`dotty` programs must
12 be accesible in the program path so that they can be ran from processes spawned
18 Here is a typical usage::
20 from altgraph import Graph, Dot
23 edges = [ (1,2), (1,3), (3,4), (3,5), (4,5), (5,4) ]
24 graph = Graph.Graph(edges)
26 # create a dot representation of the graph
32 # save the dot representation into the mydot.dot file
33 dot.save_dot(file_name='mydot.dot')
35 # save dot file as gif image into the graph.gif file
36 dot.save_img(file_name='graph', file_type='gif')
38 Directed graph and non-directed graph
39 -------------------------------------
41 Dot class can use for both directed graph and non-directed graph
42 by passing ``graphtype`` parameter.
46 # create directed graph(default)
47 dot = Dot.Dot(graph, graphtype="digraph")
49 # create non-directed graph
50 dot = Dot.Dot(graph, graphtype="graph")
52 Customizing the output
53 ----------------------
55 The graph drawing process may be customized by passing
56 valid :command:`dot` parameters for the nodes and edges. For a list of all
57 parameters see the `graphviz <http://www.research.att.com/sw/tools/graphviz/>`_
62 # customizing the way the overall graph is drawn
63 dot.style(size='10,10', rankdir='RL', page='5, 5' , ranksep=0.75)
65 # customizing node drawing
66 dot.node_style(1, label='BASE_NODE',shape='box', color='blue' )
67 dot.node_style(2, style='filled', fillcolor='red')
69 # customizing edge drawing
70 dot.edge_style(1, 2, style='dotted')
71 dot.edge_style(3, 5, arrowhead='dot', label='binds', labelangle='90')
72 dot.edge_style(4, 5, arrowsize=2, style='bold')
77 dotty (invoked via :py:func:`~altgraph.Dot.display`) may not be able to
78 display all graphics styles. To verify the output save it to an image file
79 and look at it that way.
84 - dot styles, passed via the :py:meth:`Dot.style` method::
86 rankdir = 'LR' (draws the graph horizontally, left to right)
87 ranksep = number (rank separation in inches)
89 - node attributes, passed via the :py:meth:`Dot.node_style` method::
91 style = 'filled' | 'invisible' | 'diagonals' | 'rounded'
92 shape = 'box' | 'ellipse' | 'circle' | 'point' | 'triangle'
94 - edge attributes, passed via the :py:meth:`Dot.edge_style` method::
96 style = 'dashed' | 'dotted' | 'solid' | 'invis' | 'bold'
97 arrowhead = 'box' | 'crow' | 'diamond' | 'dot' | 'inv' | 'none' | 'tee' | 'vee'
98 weight = number (the larger the number the closer the nodes will be)
100 - valid `graphviz colors <http://www.research.att.com/~erg/graphviz/info/colors.html>`_
102 - for more details on how to control the graph drawing process see the
103 `graphviz reference <http://www.research.att.com/sw/tools/graphviz/refs.html>`_.
108 from altgraph
import GraphError
113 A class providing a **graphviz** (dot language) representation
114 allowing a fine grained control over how the graph is being
117 If the :command:`dot` and :command:`dotty` programs are not in the current system path
118 their location needs to be specified in the contructor.
121 def __init__(self
, graph
=None, nodes
=None, edgefn
=None, nodevisitor
=None, edgevisitor
=None, name
="G", dot
='dot', dotty
='dotty', neato
='neato', graphtype
="digraph"):
125 self
.name
, self
.attr
= name
, {}
127 assert graphtype
in ['graph', 'digraph']
128 self
.type = graphtype
130 self
.temp_dot
= "tmp_dot.dot"
131 self
.temp_neo
= "tmp_neo.dot"
133 self
.dot
, self
.dotty
, self
.neato
= dot
, dotty
, neato
135 # self.nodes: node styles
136 # self.edges: edge styles
137 self
.nodes
, self
.edges
= {}, {}
139 if graph
is not None and nodes
is None:
141 if graph
is not None and edgefn
is None:
142 def edgefn(node
, graph
=graph
):
143 return graph
.out_nbrs(node
)
149 if nodevisitor
is None:
152 style
= nodevisitor(node
)
153 if style
is not None:
154 self
.nodes
[node
] = {}
155 self
.node_style(node
, **style
)
157 if edgefn
is not None:
159 for tail
in (n
for n
in edgefn(head
) if n
in seen
):
160 if edgevisitor
is None:
163 edgestyle
= edgevisitor(head
, tail
)
164 if edgestyle
is not None:
165 if head
not in self
.edges
:
166 self
.edges
[head
] = {}
167 self
.edges
[head
][tail
] = {}
168 self
.edge_style(head
, tail
, **edgestyle
)
170 def style(self
, **attr
):
172 Changes the overall style
176 def display(self
, mode
='dot'):
178 Displays the current graph via dotty
182 self
.save_dot(self
.temp_neo
)
183 neato_cmd
= "%s -o %s %s" % (self
.neato
, self
.temp_dot
, self
.temp_neo
)
186 self
.save_dot(self
.temp_dot
)
188 plot_cmd
= "%s %s" % (self
.dotty
, self
.temp_dot
)
191 def node_style(self
, node
, **kwargs
):
193 Modifies a node style to the dot representation.
195 if node
not in self
.edges
:
196 self
.edges
[node
] = {}
197 self
.nodes
[node
] = kwargs
199 def all_node_style(self
, **kwargs
):
201 Modifies all node styles
203 for node
in self
.nodes
:
204 self
.node_style(node
, **kwargs
)
206 def edge_style(self
, head
, tail
, **kwargs
):
208 Modifies an edge style to the dot representation.
210 if tail
not in self
.nodes
:
211 raise GraphError("invalid node %s" % (tail
,))
214 if tail
not in self
.edges
[head
]:
215 self
.edges
[head
][tail
]= {}
216 self
.edges
[head
][tail
] = kwargs
218 raise GraphError("invalid edge %s -> %s " % (head
, tail
) )
222 if self
.type == 'digraph':
223 yield 'digraph %s {\n' % (self
.name
,)
224 elif self
.type == 'graph':
225 yield 'graph %s {\n' % (self
.name
,)
228 raise GraphError("unsupported graphtype %s" % (self
.type,))
230 # write overall graph attributes
231 for attr_name
, attr_value
in sorted(self
.attr
.items()):
232 yield '%s="%s";' % (attr_name
, attr_value
)
235 # some reusable patterns
236 cpatt
= '%s="%s",' # to separate attributes
237 epatt
= '];\n' # to end attributes
239 # write node attributes
240 for node_name
, node_attr
in sorted(self
.nodes
.items()):
241 yield '\t"%s" [' % (node_name
,)
242 for attr_name
, attr_value
in sorted(node_attr
.items()):
243 yield cpatt
% (attr_name
, attr_value
)
246 # write edge attributes
247 for head
in sorted(self
.edges
):
248 for tail
in sorted(self
.edges
[head
]):
249 if self
.type == 'digraph':
250 yield '\t"%s" -> "%s" [' % (head
, tail
)
252 yield '\t"%s" -- "%s" [' % (head
, tail
)
253 for attr_name
, attr_value
in sorted(self
.edges
[head
][tail
].items()):
254 yield cpatt
% (attr_name
, attr_value
)
261 return self
.iterdot()
263 def save_dot(self
, file_name
=None):
265 Saves the current graph representation into a file
269 warnings
.warn(DeprecationWarning, "always pass a file_name")
270 file_name
= self
.temp_dot
272 fp
= open(file_name
, "w")
274 for chunk
in self
.iterdot():
279 def save_img(self
, file_name
=None, file_type
="gif", mode
='dot'):
281 Saves the dot file as an image file
285 warnings
.warn(DeprecationWarning, "always pass a file_name")
289 self
.save_dot(self
.temp_neo
)
290 neato_cmd
= "%s -o %s %s" % (self
.neato
, self
.temp_dot
, self
.temp_neo
)
294 self
.save_dot(self
.temp_dot
)
297 file_name
= "%s.%s" % (file_name
, file_type
)
298 create_cmd
= "%s -T%s %s -o %s" % (plot_cmd
, file_type
, self
.temp_dot
, file_name
)
299 os
.system(create_cmd
)