Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/drawing/nx_pydot.py: 10%
159 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*****
3Pydot
4*****
6Import and export NetworkX graphs in Graphviz dot format using pydot.
8Either this module or nx_agraph can be used to interface with graphviz.
10Examples
11--------
12>>> G = nx.complete_graph(5)
13>>> PG = nx.nx_pydot.to_pydot(G)
14>>> H = nx.nx_pydot.from_pydot(PG)
16See Also
17--------
18 - pydot: https://github.com/erocarrera/pydot
19 - Graphviz: https://www.graphviz.org
20 - DOT Language: http://www.graphviz.org/doc/info/lang.html
21"""
22import warnings
23from locale import getpreferredencoding
25import networkx as nx
26from networkx.utils import open_file
28__all__ = [
29 "write_dot",
30 "read_dot",
31 "graphviz_layout",
32 "pydot_layout",
33 "to_pydot",
34 "from_pydot",
35]
38@open_file(1, mode="w")
39def write_dot(G, path):
40 """Write NetworkX graph G to Graphviz dot format on path.
42 Path can be a string or a file handle.
43 """
44 msg = (
45 "nx.nx_pydot.write_dot depends on the pydot package, which has "
46 "known issues and is not actively maintained. Consider using "
47 "nx.nx_agraph.write_dot instead.\n\n"
48 "See https://github.com/networkx/networkx/issues/5723"
49 )
50 warnings.warn(msg, DeprecationWarning, stacklevel=2)
51 P = to_pydot(G)
52 path.write(P.to_string())
53 return
56@open_file(0, mode="r")
57@nx._dispatch(name="pydot_read_dot", graphs=None)
58def read_dot(path):
59 """Returns a NetworkX :class:`MultiGraph` or :class:`MultiDiGraph` from the
60 dot file with the passed path.
62 If this file contains multiple graphs, only the first such graph is
63 returned. All graphs _except_ the first are silently ignored.
65 Parameters
66 ----------
67 path : str or file
68 Filename or file handle.
70 Returns
71 -------
72 G : MultiGraph or MultiDiGraph
73 A :class:`MultiGraph` or :class:`MultiDiGraph`.
75 Notes
76 -----
77 Use `G = nx.Graph(nx.nx_pydot.read_dot(path))` to return a :class:`Graph` instead of a
78 :class:`MultiGraph`.
79 """
80 import pydot
82 msg = (
83 "nx.nx_pydot.read_dot depends on the pydot package, which has "
84 "known issues and is not actively maintained. Consider using "
85 "nx.nx_agraph.read_dot instead.\n\n"
86 "See https://github.com/networkx/networkx/issues/5723"
87 )
88 warnings.warn(msg, DeprecationWarning, stacklevel=2)
90 data = path.read()
92 # List of one or more "pydot.Dot" instances deserialized from this file.
93 P_list = pydot.graph_from_dot_data(data)
95 # Convert only the first such instance into a NetworkX graph.
96 return from_pydot(P_list[0])
99@nx._dispatch(graphs=None)
100def from_pydot(P):
101 """Returns a NetworkX graph from a Pydot graph.
103 Parameters
104 ----------
105 P : Pydot graph
106 A graph created with Pydot
108 Returns
109 -------
110 G : NetworkX multigraph
111 A MultiGraph or MultiDiGraph.
113 Examples
114 --------
115 >>> K5 = nx.complete_graph(5)
116 >>> A = nx.nx_pydot.to_pydot(K5)
117 >>> G = nx.nx_pydot.from_pydot(A) # return MultiGraph
119 # make a Graph instead of MultiGraph
120 >>> G = nx.Graph(nx.nx_pydot.from_pydot(A))
122 """
123 msg = (
124 "nx.nx_pydot.from_pydot depends on the pydot package, which has "
125 "known issues and is not actively maintained.\n\n"
126 "See https://github.com/networkx/networkx/issues/5723"
127 )
128 warnings.warn(msg, DeprecationWarning, stacklevel=2)
130 if P.get_strict(None): # pydot bug: get_strict() shouldn't take argument
131 multiedges = False
132 else:
133 multiedges = True
135 if P.get_type() == "graph": # undirected
136 if multiedges:
137 N = nx.MultiGraph()
138 else:
139 N = nx.Graph()
140 else:
141 if multiedges:
142 N = nx.MultiDiGraph()
143 else:
144 N = nx.DiGraph()
146 # assign defaults
147 name = P.get_name().strip('"')
148 if name != "":
149 N.name = name
151 # add nodes, attributes to N.node_attr
152 for p in P.get_node_list():
153 n = p.get_name().strip('"')
154 if n in ("node", "graph", "edge"):
155 continue
156 N.add_node(n, **p.get_attributes())
158 # add edges
159 for e in P.get_edge_list():
160 u = e.get_source()
161 v = e.get_destination()
162 attr = e.get_attributes()
163 s = []
164 d = []
166 if isinstance(u, str):
167 s.append(u.strip('"'))
168 else:
169 for unodes in u["nodes"]:
170 s.append(unodes.strip('"'))
172 if isinstance(v, str):
173 d.append(v.strip('"'))
174 else:
175 for vnodes in v["nodes"]:
176 d.append(vnodes.strip('"'))
178 for source_node in s:
179 for destination_node in d:
180 N.add_edge(source_node, destination_node, **attr)
182 # add default attributes for graph, nodes, edges
183 pattr = P.get_attributes()
184 if pattr:
185 N.graph["graph"] = pattr
186 try:
187 N.graph["node"] = P.get_node_defaults()[0]
188 except (IndexError, TypeError):
189 pass # N.graph['node']={}
190 try:
191 N.graph["edge"] = P.get_edge_defaults()[0]
192 except (IndexError, TypeError):
193 pass # N.graph['edge']={}
194 return N
197def _check_colon_quotes(s):
198 # A quick helper function to check if a string has a colon in it
199 # and if it is quoted properly with double quotes.
200 # refer https://github.com/pydot/pydot/issues/258
201 return ":" in s and (s[0] != '"' or s[-1] != '"')
204def to_pydot(N):
205 """Returns a pydot graph from a NetworkX graph N.
207 Parameters
208 ----------
209 N : NetworkX graph
210 A graph created with NetworkX
212 Examples
213 --------
214 >>> K5 = nx.complete_graph(5)
215 >>> P = nx.nx_pydot.to_pydot(K5)
217 Notes
218 -----
220 """
221 import pydot
223 msg = (
224 "nx.nx_pydot.to_pydot depends on the pydot package, which has "
225 "known issues and is not actively maintained.\n\n"
226 "See https://github.com/networkx/networkx/issues/5723"
227 )
228 warnings.warn(msg, DeprecationWarning, stacklevel=2)
230 # set Graphviz graph type
231 if N.is_directed():
232 graph_type = "digraph"
233 else:
234 graph_type = "graph"
235 strict = nx.number_of_selfloops(N) == 0 and not N.is_multigraph()
237 name = N.name
238 graph_defaults = N.graph.get("graph", {})
239 if name == "":
240 P = pydot.Dot("", graph_type=graph_type, strict=strict, **graph_defaults)
241 else:
242 P = pydot.Dot(
243 f'"{name}"', graph_type=graph_type, strict=strict, **graph_defaults
244 )
245 try:
246 P.set_node_defaults(**N.graph["node"])
247 except KeyError:
248 pass
249 try:
250 P.set_edge_defaults(**N.graph["edge"])
251 except KeyError:
252 pass
254 for n, nodedata in N.nodes(data=True):
255 str_nodedata = {str(k): str(v) for k, v in nodedata.items()}
256 # Explicitly catch nodes with ":" in node names or nodedata.
257 n = str(n)
258 raise_error = _check_colon_quotes(n) or (
259 any(
260 (_check_colon_quotes(k) or _check_colon_quotes(v))
261 for k, v in str_nodedata.items()
262 )
263 )
264 if raise_error:
265 raise ValueError(
266 f'Node names and attributes should not contain ":" unless they are quoted with "".\
267 For example the string \'attribute:data1\' should be written as \'"attribute:data1"\'.\
268 Please refer https://github.com/pydot/pydot/issues/258'
269 )
270 p = pydot.Node(n, **str_nodedata)
271 P.add_node(p)
273 if N.is_multigraph():
274 for u, v, key, edgedata in N.edges(data=True, keys=True):
275 str_edgedata = {str(k): str(v) for k, v in edgedata.items() if k != "key"}
276 u, v = str(u), str(v)
277 raise_error = (
278 _check_colon_quotes(u)
279 or _check_colon_quotes(v)
280 or (
281 any(
282 (_check_colon_quotes(k) or _check_colon_quotes(val))
283 for k, val in str_edgedata.items()
284 )
285 )
286 )
287 if raise_error:
288 raise ValueError(
289 f'Node names and attributes should not contain ":" unless they are quoted with "".\
290 For example the string \'attribute:data1\' should be written as \'"attribute:data1"\'.\
291 Please refer https://github.com/pydot/pydot/issues/258'
292 )
293 edge = pydot.Edge(u, v, key=str(key), **str_edgedata)
294 P.add_edge(edge)
296 else:
297 for u, v, edgedata in N.edges(data=True):
298 str_edgedata = {str(k): str(v) for k, v in edgedata.items()}
299 u, v = str(u), str(v)
300 raise_error = (
301 _check_colon_quotes(u)
302 or _check_colon_quotes(v)
303 or (
304 any(
305 (_check_colon_quotes(k) or _check_colon_quotes(val))
306 for k, val in str_edgedata.items()
307 )
308 )
309 )
310 if raise_error:
311 raise ValueError(
312 f'Node names and attributes should not contain ":" unless they are quoted with "".\
313 For example the string \'attribute:data1\' should be written as \'"attribute:data1"\'.\
314 Please refer https://github.com/pydot/pydot/issues/258'
315 )
316 edge = pydot.Edge(u, v, **str_edgedata)
317 P.add_edge(edge)
318 return P
321def graphviz_layout(G, prog="neato", root=None):
322 """Create node positions using Pydot and Graphviz.
324 Returns a dictionary of positions keyed by node.
326 Parameters
327 ----------
328 G : NetworkX Graph
329 The graph for which the layout is computed.
330 prog : string (default: 'neato')
331 The name of the GraphViz program to use for layout.
332 Options depend on GraphViz version but may include:
333 'dot', 'twopi', 'fdp', 'sfdp', 'circo'
334 root : Node from G or None (default: None)
335 The node of G from which to start some layout algorithms.
337 Returns
338 -------
339 Dictionary of (x, y) positions keyed by node.
341 Examples
342 --------
343 >>> G = nx.complete_graph(4)
344 >>> pos = nx.nx_pydot.graphviz_layout(G)
345 >>> pos = nx.nx_pydot.graphviz_layout(G, prog="dot")
347 Notes
348 -----
349 This is a wrapper for pydot_layout.
350 """
351 msg = (
352 "nx.nx_pydot.graphviz_layout depends on the pydot package, which has "
353 "known issues and is not actively maintained. Consider using "
354 "nx.nx_agraph.graphviz_layout instead.\n\n"
355 "See https://github.com/networkx/networkx/issues/5723"
356 )
357 warnings.warn(msg, DeprecationWarning, stacklevel=2)
359 return pydot_layout(G=G, prog=prog, root=root)
362def pydot_layout(G, prog="neato", root=None):
363 """Create node positions using :mod:`pydot` and Graphviz.
365 Parameters
366 ----------
367 G : Graph
368 NetworkX graph to be laid out.
369 prog : string (default: 'neato')
370 Name of the GraphViz command to use for layout.
371 Options depend on GraphViz version but may include:
372 'dot', 'twopi', 'fdp', 'sfdp', 'circo'
373 root : Node from G or None (default: None)
374 The node of G from which to start some layout algorithms.
376 Returns
377 -------
378 dict
379 Dictionary of positions keyed by node.
381 Examples
382 --------
383 >>> G = nx.complete_graph(4)
384 >>> pos = nx.nx_pydot.pydot_layout(G)
385 >>> pos = nx.nx_pydot.pydot_layout(G, prog="dot")
387 Notes
388 -----
389 If you use complex node objects, they may have the same string
390 representation and GraphViz could treat them as the same node.
391 The layout may assign both nodes a single location. See Issue #1568
392 If this occurs in your case, consider relabeling the nodes just
393 for the layout computation using something similar to::
395 H = nx.convert_node_labels_to_integers(G, label_attribute='node_label')
396 H_layout = nx.nx_pydot.pydot_layout(G, prog='dot')
397 G_layout = {H.nodes[n]['node_label']: p for n, p in H_layout.items()}
399 """
400 import pydot
402 msg = (
403 "nx.nx_pydot.pydot_layout depends on the pydot package, which has "
404 "known issues and is not actively maintained.\n\n"
405 "See https://github.com/networkx/networkx/issues/5723"
406 )
407 warnings.warn(msg, DeprecationWarning, stacklevel=2)
408 P = to_pydot(G)
409 if root is not None:
410 P.set("root", str(root))
412 # List of low-level bytes comprising a string in the dot language converted
413 # from the passed graph with the passed external GraphViz command.
414 D_bytes = P.create_dot(prog=prog)
416 # Unique string decoded from these bytes with the preferred locale encoding
417 D = str(D_bytes, encoding=getpreferredencoding())
419 if D == "": # no data returned
420 print(f"Graphviz layout with {prog} failed")
421 print()
422 print("To debug what happened try:")
423 print("P = nx.nx_pydot.to_pydot(G)")
424 print('P.write_dot("file.dot")')
425 print(f"And then run {prog} on file.dot")
426 return
428 # List of one or more "pydot.Dot" instances deserialized from this string.
429 Q_list = pydot.graph_from_dot_data(D)
430 assert len(Q_list) == 1
432 # The first and only such instance, as guaranteed by the above assertion.
433 Q = Q_list[0]
435 node_pos = {}
436 for n in G.nodes():
437 str_n = str(n)
438 # Explicitly catch nodes with ":" in node names or nodedata.
439 if _check_colon_quotes(str_n):
440 raise ValueError(
441 f'Node names and node attributes should not contain ":" unless they are quoted with "".\
442 For example the string \'attribute:data1\' should be written as \'"attribute:data1"\'.\
443 Please refer https://github.com/pydot/pydot/issues/258'
444 )
445 pydot_node = pydot.Node(str_n).get_name()
446 node = Q.get_node(pydot_node)
448 if isinstance(node, list):
449 node = node[0]
450 pos = node.get_pos()[1:-1] # strip leading and trailing double quotes
451 if pos is not None:
452 xx, yy = pos.split(",")
453 node_pos[n] = (float(xx), float(yy))
454 return node_pos