Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/drawing/nx_agraph.py: 9%
154 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
1"""
2***************
3Graphviz AGraph
4***************
6Interface to pygraphviz AGraph class.
8Examples
9--------
10>>> G = nx.complete_graph(5)
11>>> A = nx.nx_agraph.to_agraph(G)
12>>> H = nx.nx_agraph.from_agraph(A)
14See Also
15--------
16 - Pygraphviz: http://pygraphviz.github.io/
17 - Graphviz: https://www.graphviz.org
18 - DOT Language: http://www.graphviz.org/doc/info/lang.html
19"""
20import os
21import tempfile
23import networkx as nx
25__all__ = [
26 "from_agraph",
27 "to_agraph",
28 "write_dot",
29 "read_dot",
30 "graphviz_layout",
31 "pygraphviz_layout",
32 "view_pygraphviz",
33]
36@nx._dispatch(graphs=None)
37def from_agraph(A, create_using=None):
38 """Returns a NetworkX Graph or DiGraph from a PyGraphviz graph.
40 Parameters
41 ----------
42 A : PyGraphviz AGraph
43 A graph created with PyGraphviz
45 create_using : NetworkX graph constructor, optional (default=None)
46 Graph type to create. If graph instance, then cleared before populated.
47 If `None`, then the appropriate Graph type is inferred from `A`.
49 Examples
50 --------
51 >>> K5 = nx.complete_graph(5)
52 >>> A = nx.nx_agraph.to_agraph(K5)
53 >>> G = nx.nx_agraph.from_agraph(A)
55 Notes
56 -----
57 The Graph G will have a dictionary G.graph_attr containing
58 the default graphviz attributes for graphs, nodes and edges.
60 Default node attributes will be in the dictionary G.node_attr
61 which is keyed by node.
63 Edge attributes will be returned as edge data in G. With
64 edge_attr=False the edge data will be the Graphviz edge weight
65 attribute or the value 1 if no edge weight attribute is found.
67 """
68 if create_using is None:
69 if A.is_directed():
70 if A.is_strict():
71 create_using = nx.DiGraph
72 else:
73 create_using = nx.MultiDiGraph
74 else:
75 if A.is_strict():
76 create_using = nx.Graph
77 else:
78 create_using = nx.MultiGraph
80 # assign defaults
81 N = nx.empty_graph(0, create_using)
82 if A.name is not None:
83 N.name = A.name
85 # add graph attributes
86 N.graph.update(A.graph_attr)
88 # add nodes, attributes to N.node_attr
89 for n in A.nodes():
90 str_attr = {str(k): v for k, v in n.attr.items()}
91 N.add_node(str(n), **str_attr)
93 # add edges, assign edge data as dictionary of attributes
94 for e in A.edges():
95 u, v = str(e[0]), str(e[1])
96 attr = dict(e.attr)
97 str_attr = {str(k): v for k, v in attr.items()}
98 if not N.is_multigraph():
99 if e.name is not None:
100 str_attr["key"] = e.name
101 N.add_edge(u, v, **str_attr)
102 else:
103 N.add_edge(u, v, key=e.name, **str_attr)
105 # add default attributes for graph, nodes, and edges
106 # hang them on N.graph_attr
107 N.graph["graph"] = dict(A.graph_attr)
108 N.graph["node"] = dict(A.node_attr)
109 N.graph["edge"] = dict(A.edge_attr)
110 return N
113def to_agraph(N):
114 """Returns a pygraphviz graph from a NetworkX graph N.
116 Parameters
117 ----------
118 N : NetworkX graph
119 A graph created with NetworkX
121 Examples
122 --------
123 >>> K5 = nx.complete_graph(5)
124 >>> A = nx.nx_agraph.to_agraph(K5)
126 Notes
127 -----
128 If N has an dict N.graph_attr an attempt will be made first
129 to copy properties attached to the graph (see from_agraph)
130 and then updated with the calling arguments if any.
132 """
133 try:
134 import pygraphviz
135 except ImportError as err:
136 raise ImportError(
137 "requires pygraphviz " "http://pygraphviz.github.io/"
138 ) from err
139 directed = N.is_directed()
140 strict = nx.number_of_selfloops(N) == 0 and not N.is_multigraph()
142 for node in N:
143 if "pos" in N.nodes[node]:
144 N.nodes[node]["pos"] = "{},{}!".format(
145 N.nodes[node]["pos"][0], N.nodes[node]["pos"][1]
146 )
148 A = pygraphviz.AGraph(name=N.name, strict=strict, directed=directed)
150 # default graph attributes
151 A.graph_attr.update(N.graph.get("graph", {}))
152 A.node_attr.update(N.graph.get("node", {}))
153 A.edge_attr.update(N.graph.get("edge", {}))
155 A.graph_attr.update(
156 (k, v) for k, v in N.graph.items() if k not in ("graph", "node", "edge")
157 )
159 # add nodes
160 for n, nodedata in N.nodes(data=True):
161 A.add_node(n)
162 # Add node data
163 a = A.get_node(n)
164 a.attr.update({k: str(v) for k, v in nodedata.items()})
166 # loop over edges
167 if N.is_multigraph():
168 for u, v, key, edgedata in N.edges(data=True, keys=True):
169 str_edgedata = {k: str(v) for k, v in edgedata.items() if k != "key"}
170 A.add_edge(u, v, key=str(key))
171 # Add edge data
172 a = A.get_edge(u, v)
173 a.attr.update(str_edgedata)
175 else:
176 for u, v, edgedata in N.edges(data=True):
177 str_edgedata = {k: str(v) for k, v in edgedata.items()}
178 A.add_edge(u, v)
179 # Add edge data
180 a = A.get_edge(u, v)
181 a.attr.update(str_edgedata)
183 return A
186def write_dot(G, path):
187 """Write NetworkX graph G to Graphviz dot format on path.
189 Parameters
190 ----------
191 G : graph
192 A networkx graph
193 path : filename
194 Filename or file handle to write
196 Notes
197 -----
198 To use a specific graph layout, call ``A.layout`` prior to `write_dot`.
199 Note that some graphviz layouts are not guaranteed to be deterministic,
200 see https://gitlab.com/graphviz/graphviz/-/issues/1767 for more info.
201 """
202 A = to_agraph(G)
203 A.write(path)
204 A.clear()
205 return
208@nx._dispatch(name="agraph_read_dot", graphs=None)
209def read_dot(path):
210 """Returns a NetworkX graph from a dot file on path.
212 Parameters
213 ----------
214 path : file or string
215 File name or file handle to read.
216 """
217 try:
218 import pygraphviz
219 except ImportError as err:
220 raise ImportError(
221 "read_dot() requires pygraphviz " "http://pygraphviz.github.io/"
222 ) from err
223 A = pygraphviz.AGraph(file=path)
224 gr = from_agraph(A)
225 A.clear()
226 return gr
229def graphviz_layout(G, prog="neato", root=None, args=""):
230 """Create node positions for G using Graphviz.
232 Parameters
233 ----------
234 G : NetworkX graph
235 A graph created with NetworkX
236 prog : string
237 Name of Graphviz layout program
238 root : string, optional
239 Root node for twopi layout
240 args : string, optional
241 Extra arguments to Graphviz layout program
243 Returns
244 -------
245 Dictionary of x, y, positions keyed by node.
247 Examples
248 --------
249 >>> G = nx.petersen_graph()
250 >>> pos = nx.nx_agraph.graphviz_layout(G)
251 >>> pos = nx.nx_agraph.graphviz_layout(G, prog="dot")
253 Notes
254 -----
255 This is a wrapper for pygraphviz_layout.
257 Note that some graphviz layouts are not guaranteed to be deterministic,
258 see https://gitlab.com/graphviz/graphviz/-/issues/1767 for more info.
259 """
260 return pygraphviz_layout(G, prog=prog, root=root, args=args)
263def pygraphviz_layout(G, prog="neato", root=None, args=""):
264 """Create node positions for G using Graphviz.
266 Parameters
267 ----------
268 G : NetworkX graph
269 A graph created with NetworkX
270 prog : string
271 Name of Graphviz layout program
272 root : string, optional
273 Root node for twopi layout
274 args : string, optional
275 Extra arguments to Graphviz layout program
277 Returns
278 -------
279 node_pos : dict
280 Dictionary of x, y, positions keyed by node.
282 Examples
283 --------
284 >>> G = nx.petersen_graph()
285 >>> pos = nx.nx_agraph.graphviz_layout(G)
286 >>> pos = nx.nx_agraph.graphviz_layout(G, prog="dot")
288 Notes
289 -----
290 If you use complex node objects, they may have the same string
291 representation and GraphViz could treat them as the same node.
292 The layout may assign both nodes a single location. See Issue #1568
293 If this occurs in your case, consider relabeling the nodes just
294 for the layout computation using something similar to::
296 >>> H = nx.convert_node_labels_to_integers(G, label_attribute="node_label")
297 >>> H_layout = nx.nx_agraph.pygraphviz_layout(G, prog="dot")
298 >>> G_layout = {H.nodes[n]["node_label"]: p for n, p in H_layout.items()}
300 Note that some graphviz layouts are not guaranteed to be deterministic,
301 see https://gitlab.com/graphviz/graphviz/-/issues/1767 for more info.
302 """
303 try:
304 import pygraphviz
305 except ImportError as err:
306 raise ImportError(
307 "requires pygraphviz " "http://pygraphviz.github.io/"
308 ) from err
309 if root is not None:
310 args += f"-Groot={root}"
311 A = to_agraph(G)
312 A.layout(prog=prog, args=args)
313 node_pos = {}
314 for n in G:
315 node = pygraphviz.Node(A, n)
316 try:
317 xs = node.attr["pos"].split(",")
318 node_pos[n] = tuple(float(x) for x in xs)
319 except:
320 print("no position for node", n)
321 node_pos[n] = (0.0, 0.0)
322 return node_pos
325@nx.utils.open_file(5, "w+b")
326def view_pygraphviz(
327 G, edgelabel=None, prog="dot", args="", suffix="", path=None, show=True
328):
329 """Views the graph G using the specified layout algorithm.
331 Parameters
332 ----------
333 G : NetworkX graph
334 The machine to draw.
335 edgelabel : str, callable, None
336 If a string, then it specifies the edge attribute to be displayed
337 on the edge labels. If a callable, then it is called for each
338 edge and it should return the string to be displayed on the edges.
339 The function signature of `edgelabel` should be edgelabel(data),
340 where `data` is the edge attribute dictionary.
341 prog : string
342 Name of Graphviz layout program.
343 args : str
344 Additional arguments to pass to the Graphviz layout program.
345 suffix : str
346 If `filename` is None, we save to a temporary file. The value of
347 `suffix` will appear at the tail end of the temporary filename.
348 path : str, None
349 The filename used to save the image. If None, save to a temporary
350 file. File formats are the same as those from pygraphviz.agraph.draw.
351 show : bool, default = True
352 Whether to display the graph with :mod:`PIL.Image.show`,
353 default is `True`. If `False`, the rendered graph is still available
354 at `path`.
356 Returns
357 -------
358 path : str
359 The filename of the generated image.
360 A : PyGraphviz graph
361 The PyGraphviz graph instance used to generate the image.
363 Notes
364 -----
365 If this function is called in succession too quickly, sometimes the
366 image is not displayed. So you might consider time.sleep(.5) between
367 calls if you experience problems.
369 Note that some graphviz layouts are not guaranteed to be deterministic,
370 see https://gitlab.com/graphviz/graphviz/-/issues/1767 for more info.
372 """
373 if not len(G):
374 raise nx.NetworkXException("An empty graph cannot be drawn.")
376 # If we are providing default values for graphviz, these must be set
377 # before any nodes or edges are added to the PyGraphviz graph object.
378 # The reason for this is that default values only affect incoming objects.
379 # If you change the default values after the objects have been added,
380 # then they inherit no value and are set only if explicitly set.
382 # to_agraph() uses these values.
383 attrs = ["edge", "node", "graph"]
384 for attr in attrs:
385 if attr not in G.graph:
386 G.graph[attr] = {}
388 # These are the default values.
389 edge_attrs = {"fontsize": "10"}
390 node_attrs = {
391 "style": "filled",
392 "fillcolor": "#0000FF40",
393 "height": "0.75",
394 "width": "0.75",
395 "shape": "circle",
396 }
397 graph_attrs = {}
399 def update_attrs(which, attrs):
400 # Update graph attributes. Return list of those which were added.
401 added = []
402 for k, v in attrs.items():
403 if k not in G.graph[which]:
404 G.graph[which][k] = v
405 added.append(k)
407 def clean_attrs(which, added):
408 # Remove added attributes
409 for attr in added:
410 del G.graph[which][attr]
411 if not G.graph[which]:
412 del G.graph[which]
414 # Update all default values
415 update_attrs("edge", edge_attrs)
416 update_attrs("node", node_attrs)
417 update_attrs("graph", graph_attrs)
419 # Convert to agraph, so we inherit default values
420 A = to_agraph(G)
422 # Remove the default values we added to the original graph.
423 clean_attrs("edge", edge_attrs)
424 clean_attrs("node", node_attrs)
425 clean_attrs("graph", graph_attrs)
427 # If the user passed in an edgelabel, we update the labels for all edges.
428 if edgelabel is not None:
429 if not callable(edgelabel):
431 def func(data):
432 return "".join([" ", str(data[edgelabel]), " "])
434 else:
435 func = edgelabel
437 # update all the edge labels
438 if G.is_multigraph():
439 for u, v, key, data in G.edges(keys=True, data=True):
440 # PyGraphviz doesn't convert the key to a string. See #339
441 edge = A.get_edge(u, v, str(key))
442 edge.attr["label"] = str(func(data))
443 else:
444 for u, v, data in G.edges(data=True):
445 edge = A.get_edge(u, v)
446 edge.attr["label"] = str(func(data))
448 if path is None:
449 ext = "png"
450 if suffix:
451 suffix = f"_{suffix}.{ext}"
452 else:
453 suffix = f".{ext}"
454 path = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
455 else:
456 # Assume the decorator worked and it is a file-object.
457 pass
459 # Write graph to file
460 A.draw(path=path, format=None, prog=prog, args=args)
461 path.close()
463 # Show graph in a new window (depends on platform configuration)
464 if show:
465 from PIL import Image
467 Image.open(path.name).show()
469 return path.name, A