comparison env/lib/python3.9/site-packages/networkx/drawing/nx_agraph.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 """
2 ***************
3 Graphviz AGraph
4 ***************
5
6 Interface to pygraphviz AGraph class.
7
8 Examples
9 --------
10 >>> G = nx.complete_graph(5)
11 >>> A = nx.nx_agraph.to_agraph(G)
12 >>> H = nx.nx_agraph.from_agraph(A)
13
14 See Also
15 --------
16 Pygraphviz: http://pygraphviz.github.io/
17 """
18 import os
19 import tempfile
20 import networkx as nx
21
22 __all__ = [
23 "from_agraph",
24 "to_agraph",
25 "write_dot",
26 "read_dot",
27 "graphviz_layout",
28 "pygraphviz_layout",
29 "view_pygraphviz",
30 ]
31
32
33 def from_agraph(A, create_using=None):
34 """Returns a NetworkX Graph or DiGraph from a PyGraphviz graph.
35
36 Parameters
37 ----------
38 A : PyGraphviz AGraph
39 A graph created with PyGraphviz
40
41 create_using : NetworkX graph constructor, optional (default=None)
42 Graph type to create. If graph instance, then cleared before populated.
43 If `None`, then the appropriate Graph type is inferred from `A`.
44
45 Examples
46 --------
47 >>> K5 = nx.complete_graph(5)
48 >>> A = nx.nx_agraph.to_agraph(K5)
49 >>> G = nx.nx_agraph.from_agraph(A)
50
51 Notes
52 -----
53 The Graph G will have a dictionary G.graph_attr containing
54 the default graphviz attributes for graphs, nodes and edges.
55
56 Default node attributes will be in the dictionary G.node_attr
57 which is keyed by node.
58
59 Edge attributes will be returned as edge data in G. With
60 edge_attr=False the edge data will be the Graphviz edge weight
61 attribute or the value 1 if no edge weight attribute is found.
62
63 """
64 if create_using is None:
65 if A.is_directed():
66 if A.is_strict():
67 create_using = nx.DiGraph
68 else:
69 create_using = nx.MultiDiGraph
70 else:
71 if A.is_strict():
72 create_using = nx.Graph
73 else:
74 create_using = nx.MultiGraph
75
76 # assign defaults
77 N = nx.empty_graph(0, create_using)
78 if A.name is not None:
79 N.name = A.name
80
81 # add graph attributes
82 N.graph.update(A.graph_attr)
83
84 # add nodes, attributes to N.node_attr
85 for n in A.nodes():
86 str_attr = {str(k): v for k, v in n.attr.items()}
87 N.add_node(str(n), **str_attr)
88
89 # add edges, assign edge data as dictionary of attributes
90 for e in A.edges():
91 u, v = str(e[0]), str(e[1])
92 attr = dict(e.attr)
93 str_attr = {str(k): v for k, v in attr.items()}
94 if not N.is_multigraph():
95 if e.name is not None:
96 str_attr["key"] = e.name
97 N.add_edge(u, v, **str_attr)
98 else:
99 N.add_edge(u, v, key=e.name, **str_attr)
100
101 # add default attributes for graph, nodes, and edges
102 # hang them on N.graph_attr
103 N.graph["graph"] = dict(A.graph_attr)
104 N.graph["node"] = dict(A.node_attr)
105 N.graph["edge"] = dict(A.edge_attr)
106 return N
107
108
109 def to_agraph(N):
110 """Returns a pygraphviz graph from a NetworkX graph N.
111
112 Parameters
113 ----------
114 N : NetworkX graph
115 A graph created with NetworkX
116
117 Examples
118 --------
119 >>> K5 = nx.complete_graph(5)
120 >>> A = nx.nx_agraph.to_agraph(K5)
121
122 Notes
123 -----
124 If N has an dict N.graph_attr an attempt will be made first
125 to copy properties attached to the graph (see from_agraph)
126 and then updated with the calling arguments if any.
127
128 """
129 try:
130 import pygraphviz
131 except ImportError as e:
132 raise ImportError("requires pygraphviz " "http://pygraphviz.github.io/") from e
133 directed = N.is_directed()
134 strict = nx.number_of_selfloops(N) == 0 and not N.is_multigraph()
135 A = pygraphviz.AGraph(name=N.name, strict=strict, directed=directed)
136
137 # default graph attributes
138 A.graph_attr.update(N.graph.get("graph", {}))
139 A.node_attr.update(N.graph.get("node", {}))
140 A.edge_attr.update(N.graph.get("edge", {}))
141
142 A.graph_attr.update(
143 (k, v) for k, v in N.graph.items() if k not in ("graph", "node", "edge")
144 )
145
146 # add nodes
147 for n, nodedata in N.nodes(data=True):
148 A.add_node(n)
149 # Add node data
150 a = A.get_node(n)
151 a.attr.update({k: str(v) for k, v in nodedata.items()})
152
153 # loop over edges
154 if N.is_multigraph():
155 for u, v, key, edgedata in N.edges(data=True, keys=True):
156 str_edgedata = {k: str(v) for k, v in edgedata.items() if k != "key"}
157 A.add_edge(u, v, key=str(key))
158 # Add edge data
159 a = A.get_edge(u, v)
160 a.attr.update(str_edgedata)
161
162 else:
163 for u, v, edgedata in N.edges(data=True):
164 str_edgedata = {k: str(v) for k, v in edgedata.items()}
165 A.add_edge(u, v)
166 # Add edge data
167 a = A.get_edge(u, v)
168 a.attr.update(str_edgedata)
169
170 return A
171
172
173 def write_dot(G, path):
174 """Write NetworkX graph G to Graphviz dot format on path.
175
176 Parameters
177 ----------
178 G : graph
179 A networkx graph
180 path : filename
181 Filename or file handle to write
182 """
183 A = to_agraph(G)
184 A.write(path)
185 A.clear()
186 return
187
188
189 def read_dot(path):
190 """Returns a NetworkX graph from a dot file on path.
191
192 Parameters
193 ----------
194 path : file or string
195 File name or file handle to read.
196 """
197 try:
198 import pygraphviz
199 except ImportError as e:
200 raise ImportError(
201 "read_dot() requires pygraphviz " "http://pygraphviz.github.io/"
202 ) from e
203 A = pygraphviz.AGraph(file=path)
204 gr = from_agraph(A)
205 A.clear()
206 return gr
207
208
209 def graphviz_layout(G, prog="neato", root=None, args=""):
210 """Create node positions for G using Graphviz.
211
212 Parameters
213 ----------
214 G : NetworkX graph
215 A graph created with NetworkX
216 prog : string
217 Name of Graphviz layout program
218 root : string, optional
219 Root node for twopi layout
220 args : string, optional
221 Extra arguments to Graphviz layout program
222
223 Returns
224 -------
225 Dictionary of x, y, positions keyed by node.
226
227 Examples
228 --------
229 >>> G = nx.petersen_graph()
230 >>> pos = nx.nx_agraph.graphviz_layout(G)
231 >>> pos = nx.nx_agraph.graphviz_layout(G, prog="dot")
232
233 Notes
234 -----
235 This is a wrapper for pygraphviz_layout.
236 """
237 return pygraphviz_layout(G, prog=prog, root=root, args=args)
238
239
240 def pygraphviz_layout(G, prog="neato", root=None, args=""):
241 """Create node positions for G using Graphviz.
242
243 Parameters
244 ----------
245 G : NetworkX graph
246 A graph created with NetworkX
247 prog : string
248 Name of Graphviz layout program
249 root : string, optional
250 Root node for twopi layout
251 args : string, optional
252 Extra arguments to Graphviz layout program
253
254 Returns
255 -------
256 node_pos : dict
257 Dictionary of x, y, positions keyed by node.
258
259 Examples
260 --------
261 >>> G = nx.petersen_graph()
262 >>> pos = nx.nx_agraph.graphviz_layout(G)
263 >>> pos = nx.nx_agraph.graphviz_layout(G, prog="dot")
264
265 Notes
266 -----
267 If you use complex node objects, they may have the same string
268 representation and GraphViz could treat them as the same node.
269 The layout may assign both nodes a single location. See Issue #1568
270 If this occurs in your case, consider relabeling the nodes just
271 for the layout computation using something similar to::
272
273 >>> H = nx.convert_node_labels_to_integers(G, label_attribute="node_label")
274 >>> H_layout = nx.nx_agraph.pygraphviz_layout(G, prog="dot")
275 >>> G_layout = {H.nodes[n]["node_label"]: p for n, p in H_layout.items()}
276
277 """
278 try:
279 import pygraphviz
280 except ImportError as e:
281 raise ImportError("requires pygraphviz " "http://pygraphviz.github.io/") from e
282 if root is not None:
283 args += f"-Groot={root}"
284 A = to_agraph(G)
285 A.layout(prog=prog, args=args)
286 node_pos = {}
287 for n in G:
288 node = pygraphviz.Node(A, n)
289 try:
290 xs = node.attr["pos"].split(",")
291 node_pos[n] = tuple(float(x) for x in xs)
292 except:
293 print("no position for node", n)
294 node_pos[n] = (0.0, 0.0)
295 return node_pos
296
297
298 @nx.utils.open_file(5, "w+b")
299 def view_pygraphviz(
300 G, edgelabel=None, prog="dot", args="", suffix="", path=None, show=True
301 ):
302 """Views the graph G using the specified layout algorithm.
303
304 Parameters
305 ----------
306 G : NetworkX graph
307 The machine to draw.
308 edgelabel : str, callable, None
309 If a string, then it specifes the edge attribute to be displayed
310 on the edge labels. If a callable, then it is called for each
311 edge and it should return the string to be displayed on the edges.
312 The function signature of `edgelabel` should be edgelabel(data),
313 where `data` is the edge attribute dictionary.
314 prog : string
315 Name of Graphviz layout program.
316 args : str
317 Additional arguments to pass to the Graphviz layout program.
318 suffix : str
319 If `filename` is None, we save to a temporary file. The value of
320 `suffix` will appear at the tail end of the temporary filename.
321 path : str, None
322 The filename used to save the image. If None, save to a temporary
323 file. File formats are the same as those from pygraphviz.agraph.draw.
324 show : bool, default = True
325 Whether to display the graph with `networkx.utils.default_opener`,
326 default is `True`. If `False`, the rendered graph is still available
327 at `path`.
328
329 Returns
330 -------
331 path : str
332 The filename of the generated image.
333 A : PyGraphviz graph
334 The PyGraphviz graph instance used to generate the image.
335
336 Notes
337 -----
338 If this function is called in succession too quickly, sometimes the
339 image is not displayed. So you might consider time.sleep(.5) between
340 calls if you experience problems.
341
342 """
343 if not len(G):
344 raise nx.NetworkXException("An empty graph cannot be drawn.")
345
346 # If we are providing default values for graphviz, these must be set
347 # before any nodes or edges are added to the PyGraphviz graph object.
348 # The reason for this is that default values only affect incoming objects.
349 # If you change the default values after the objects have been added,
350 # then they inherit no value and are set only if explicitly set.
351
352 # to_agraph() uses these values.
353 attrs = ["edge", "node", "graph"]
354 for attr in attrs:
355 if attr not in G.graph:
356 G.graph[attr] = {}
357
358 # These are the default values.
359 edge_attrs = {"fontsize": "10"}
360 node_attrs = {
361 "style": "filled",
362 "fillcolor": "#0000FF40",
363 "height": "0.75",
364 "width": "0.75",
365 "shape": "circle",
366 }
367 graph_attrs = {}
368
369 def update_attrs(which, attrs):
370 # Update graph attributes. Return list of those which were added.
371 added = []
372 for k, v in attrs.items():
373 if k not in G.graph[which]:
374 G.graph[which][k] = v
375 added.append(k)
376
377 def clean_attrs(which, added):
378 # Remove added attributes
379 for attr in added:
380 del G.graph[which][attr]
381 if not G.graph[which]:
382 del G.graph[which]
383
384 # Update all default values
385 update_attrs("edge", edge_attrs)
386 update_attrs("node", node_attrs)
387 update_attrs("graph", graph_attrs)
388
389 # Convert to agraph, so we inherit default values
390 A = to_agraph(G)
391
392 # Remove the default values we added to the original graph.
393 clean_attrs("edge", edge_attrs)
394 clean_attrs("node", node_attrs)
395 clean_attrs("graph", graph_attrs)
396
397 # If the user passed in an edgelabel, we update the labels for all edges.
398 if edgelabel is not None:
399 if not hasattr(edgelabel, "__call__"):
400
401 def func(data):
402 return "".join([" ", str(data[edgelabel]), " "])
403
404 else:
405 func = edgelabel
406
407 # update all the edge labels
408 if G.is_multigraph():
409 for u, v, key, data in G.edges(keys=True, data=True):
410 # PyGraphviz doesn't convert the key to a string. See #339
411 edge = A.get_edge(u, v, str(key))
412 edge.attr["label"] = str(func(data))
413 else:
414 for u, v, data in G.edges(data=True):
415 edge = A.get_edge(u, v)
416 edge.attr["label"] = str(func(data))
417
418 if path is None:
419 ext = "png"
420 if suffix:
421 suffix = f"_{suffix}.{ext}"
422 else:
423 suffix = f".{ext}"
424 path = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
425 else:
426 # Assume the decorator worked and it is a file-object.
427 pass
428
429 # Write graph to file
430 A.draw(path=path, format=None, prog=prog, args=args)
431 path.close()
432
433 # Show graph in a new window (depends on platform configuration)
434 if show:
435 nx.utils.default_opener(path.name)
436
437 return path.name, A
438
439
440 def display_pygraphviz(graph, path, format=None, prog=None, args=""):
441 """Internal function to display a graph in OS dependent manner.
442
443 Parameters
444 ----------
445 graph : PyGraphviz graph
446 A PyGraphviz AGraph instance.
447 path : file object
448 An already opened file object that will be closed.
449 format : str, None
450 An attempt is made to guess the output format based on the extension
451 of the filename. If that fails, the value of `format` is used.
452 prog : string
453 Name of Graphviz layout program.
454 args : str
455 Additional arguments to pass to the Graphviz layout program.
456
457 Notes
458 -----
459 If this function is called in succession too quickly, sometimes the
460 image is not displayed. So you might consider time.sleep(.5) between
461 calls if you experience problems.
462
463 """
464 import warnings
465
466 warnings.warn(
467 "display_pygraphviz is deprecated and will be removed in NetworkX 3.0. "
468 "To view a graph G using pygraphviz, use nx.nx_agraph.view_pygraphviz(G). "
469 "To view a graph from file, consider nx.utils.default_opener(filename).",
470 DeprecationWarning,
471 )
472 if format is None:
473 filename = path.name
474 format = os.path.splitext(filename)[1].lower()[1:]
475 if not format:
476 # Let the draw() function use its default
477 format = None
478
479 # Save to a file and display in the default viewer.
480 # We must close the file before viewing it.
481 graph.draw(path, format, prog, args)
482 path.close()
483 nx.utils.default_opener(filename)