Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/networkx/drawing/nx_pydot.py: 11%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

135 statements  

1""" 

2***** 

3Pydot 

4***** 

5 

6Import and export NetworkX graphs in Graphviz dot format using pydot. 

7 

8Either this module or nx_agraph can be used to interface with graphviz. 

9 

10Examples 

11-------- 

12>>> G = nx.complete_graph(5) 

13>>> PG = nx.nx_pydot.to_pydot(G) 

14>>> H = nx.nx_pydot.from_pydot(PG) 

15 

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""" 

22 

23from locale import getpreferredencoding 

24 

25import networkx as nx 

26from networkx.utils import open_file 

27 

28__all__ = [ 

29 "write_dot", 

30 "read_dot", 

31 "graphviz_layout", 

32 "pydot_layout", 

33 "to_pydot", 

34 "from_pydot", 

35] 

36 

37 

38@open_file(1, mode="w") 

39def write_dot(G, path): 

40 """Write NetworkX graph G to Graphviz dot format on path. 

41 

42 Parameters 

43 ---------- 

44 G : NetworkX graph 

45 

46 path : string or file 

47 Filename or file handle for data output. 

48 Filenames ending in .gz or .bz2 will be compressed. 

49 """ 

50 P = to_pydot(G) 

51 path.write(P.to_string()) 

52 return 

53 

54 

55@open_file(0, mode="r") 

56@nx._dispatchable(name="pydot_read_dot", graphs=None, returns_graph=True) 

57def read_dot(path): 

58 """Returns a NetworkX :class:`MultiGraph` or :class:`MultiDiGraph` from the 

59 dot file with the passed path. 

60 

61 If this file contains multiple graphs, only the first such graph is 

62 returned. All graphs _except_ the first are silently ignored. 

63 

64 Parameters 

65 ---------- 

66 path : str or file 

67 Filename or file handle to read. 

68 Filenames ending in .gz or .bz2 will be decompressed. 

69 

70 Returns 

71 ------- 

72 G : MultiGraph or MultiDiGraph 

73 A :class:`MultiGraph` or :class:`MultiDiGraph`. 

74 

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 

81 

82 data = path.read() 

83 

84 # List of one or more "pydot.Dot" instances deserialized from this file. 

85 P_list = pydot.graph_from_dot_data(data) 

86 

87 # Convert only the first such instance into a NetworkX graph. 

88 return from_pydot(P_list[0]) 

89 

90 

91@nx._dispatchable(graphs=None, returns_graph=True) 

92def from_pydot(P): 

93 """Returns a NetworkX graph from a Pydot graph. 

94 

95 Parameters 

96 ---------- 

97 P : Pydot graph 

98 A graph created with Pydot 

99 

100 Returns 

101 ------- 

102 G : NetworkX multigraph 

103 A MultiGraph or MultiDiGraph. 

104 

105 Examples 

106 -------- 

107 >>> K5 = nx.complete_graph(5) 

108 >>> A = nx.nx_pydot.to_pydot(K5) 

109 >>> G = nx.nx_pydot.from_pydot(A) # return MultiGraph 

110 

111 # make a Graph instead of MultiGraph 

112 >>> G = nx.Graph(nx.nx_pydot.from_pydot(A)) 

113 

114 """ 

115 # NOTE: Pydot v3 expects a dummy argument whereas Pydot v4 doesn't 

116 # Remove the try-except when Pydot v4 becomes the minimum supported version 

117 try: 

118 strict = P.get_strict() 

119 except TypeError: 

120 strict = P.get_strict(None) # pydot bug: get_strict() shouldn't take argument 

121 multiedges = not strict 

122 

123 if P.get_type() == "graph": # undirected 

124 if multiedges: 

125 N = nx.MultiGraph() 

126 else: 

127 N = nx.Graph() 

128 else: 

129 if multiedges: 

130 N = nx.MultiDiGraph() 

131 else: 

132 N = nx.DiGraph() 

133 

134 # assign defaults 

135 name = P.get_name().strip('"') 

136 if name != "": 

137 N.name = name 

138 

139 # add nodes, attributes to N.node_attr 

140 for p in P.get_node_list(): 

141 n = p.get_name().strip('"') 

142 if n in ("node", "graph", "edge"): 

143 continue 

144 N.add_node(n, **p.get_attributes()) 

145 

146 # add edges 

147 for e in P.get_edge_list(): 

148 u = e.get_source() 

149 v = e.get_destination() 

150 attr = e.get_attributes() 

151 s = [] 

152 d = [] 

153 

154 if isinstance(u, str): 

155 s.append(u.strip('"')) 

156 else: 

157 for unodes in u["nodes"]: 

158 s.append(unodes.strip('"')) 

159 

160 if isinstance(v, str): 

161 d.append(v.strip('"')) 

162 else: 

163 for vnodes in v["nodes"]: 

164 d.append(vnodes.strip('"')) 

165 

166 for source_node in s: 

167 for destination_node in d: 

168 N.add_edge(source_node, destination_node, **attr) 

169 

170 # add default attributes for graph, nodes, edges 

171 pattr = P.get_attributes() 

172 if pattr: 

173 N.graph["graph"] = pattr 

174 try: 

175 N.graph["node"] = P.get_node_defaults()[0] 

176 except (IndexError, TypeError): 

177 pass # N.graph['node']={} 

178 try: 

179 N.graph["edge"] = P.get_edge_defaults()[0] 

180 except (IndexError, TypeError): 

181 pass # N.graph['edge']={} 

182 return N 

183 

184 

185def to_pydot(N): 

186 """Returns a pydot graph from a NetworkX graph N. 

187 

188 Parameters 

189 ---------- 

190 N : NetworkX graph 

191 A graph created with NetworkX 

192 

193 Examples 

194 -------- 

195 >>> K5 = nx.complete_graph(5) 

196 >>> P = nx.nx_pydot.to_pydot(K5) 

197 

198 Notes 

199 ----- 

200 

201 """ 

202 import pydot 

203 

204 # set Graphviz graph type 

205 if N.is_directed(): 

206 graph_type = "digraph" 

207 else: 

208 graph_type = "graph" 

209 strict = nx.number_of_selfloops(N) == 0 and not N.is_multigraph() 

210 

211 name = N.name 

212 graph_defaults = N.graph.get("graph", {}) 

213 if name == "": 

214 P = pydot.Dot("", graph_type=graph_type, strict=strict, **graph_defaults) 

215 else: 

216 P = pydot.Dot( 

217 f'"{name}"', graph_type=graph_type, strict=strict, **graph_defaults 

218 ) 

219 try: 

220 P.set_node_defaults(**N.graph["node"]) 

221 except KeyError: 

222 pass 

223 try: 

224 P.set_edge_defaults(**N.graph["edge"]) 

225 except KeyError: 

226 pass 

227 

228 for n, nodedata in N.nodes(data=True): 

229 str_nodedata = {str(k): str(v) for k, v in nodedata.items()} 

230 n = str(n) 

231 p = pydot.Node(n, **str_nodedata) 

232 P.add_node(p) 

233 

234 if N.is_multigraph(): 

235 for u, v, key, edgedata in N.edges(data=True, keys=True): 

236 str_edgedata = {str(k): str(v) for k, v in edgedata.items() if k != "key"} 

237 u, v = str(u), str(v) 

238 edge = pydot.Edge(u, v, key=str(key), **str_edgedata) 

239 P.add_edge(edge) 

240 

241 else: 

242 for u, v, edgedata in N.edges(data=True): 

243 str_edgedata = {str(k): str(v) for k, v in edgedata.items()} 

244 u, v = str(u), str(v) 

245 edge = pydot.Edge(u, v, **str_edgedata) 

246 P.add_edge(edge) 

247 return P 

248 

249 

250def graphviz_layout(G, prog="neato", root=None): 

251 """Create node positions using Pydot and Graphviz. 

252 

253 Returns a dictionary of positions keyed by node. 

254 

255 Parameters 

256 ---------- 

257 G : NetworkX Graph 

258 The graph for which the layout is computed. 

259 prog : string (default: 'neato') 

260 The name of the GraphViz program to use for layout. 

261 Options depend on GraphViz version but may include: 

262 'dot', 'twopi', 'fdp', 'sfdp', 'circo' 

263 root : Node from G or None (default: None) 

264 The node of G from which to start some layout algorithms. 

265 

266 Returns 

267 ------- 

268 Dictionary of (x, y) positions keyed by node. 

269 

270 Examples 

271 -------- 

272 >>> G = nx.complete_graph(4) 

273 >>> pos = nx.nx_pydot.graphviz_layout(G) 

274 >>> pos = nx.nx_pydot.graphviz_layout(G, prog="dot") 

275 

276 Notes 

277 ----- 

278 This is a wrapper for pydot_layout. 

279 """ 

280 return pydot_layout(G=G, prog=prog, root=root) 

281 

282 

283def pydot_layout(G, prog="neato", root=None): 

284 """Create node positions using :mod:`pydot` and Graphviz. 

285 

286 Parameters 

287 ---------- 

288 G : Graph 

289 NetworkX graph to be laid out. 

290 prog : string (default: 'neato') 

291 Name of the GraphViz command to use for layout. 

292 Options depend on GraphViz version but may include: 

293 'dot', 'twopi', 'fdp', 'sfdp', 'circo' 

294 root : Node from G or None (default: None) 

295 The node of G from which to start some layout algorithms. 

296 

297 Returns 

298 ------- 

299 dict 

300 Dictionary of positions keyed by node. 

301 

302 Examples 

303 -------- 

304 >>> G = nx.complete_graph(4) 

305 >>> pos = nx.nx_pydot.pydot_layout(G) 

306 >>> pos = nx.nx_pydot.pydot_layout(G, prog="dot") 

307 

308 Notes 

309 ----- 

310 If you use complex node objects, they may have the same string 

311 representation and GraphViz could treat them as the same node. 

312 The layout may assign both nodes a single location. See Issue #1568 

313 If this occurs in your case, consider relabeling the nodes just 

314 for the layout computation using something similar to:: 

315 

316 H = nx.convert_node_labels_to_integers(G, label_attribute="node_label") 

317 H_layout = nx.nx_pydot.pydot_layout(H, prog="dot") 

318 G_layout = {H.nodes[n]["node_label"]: p for n, p in H_layout.items()} 

319 

320 """ 

321 import pydot 

322 

323 P = to_pydot(G) 

324 if root is not None: 

325 P.set("root", str(root)) 

326 

327 # List of low-level bytes comprising a string in the dot language converted 

328 # from the passed graph with the passed external GraphViz command. 

329 D_bytes = P.create_dot(prog=prog) 

330 

331 # Unique string decoded from these bytes with the preferred locale encoding 

332 D = str(D_bytes, encoding=getpreferredencoding()) 

333 

334 if D == "": # no data returned 

335 print(f"Graphviz layout with {prog} failed") 

336 print() 

337 print("To debug what happened try:") 

338 print("P = nx.nx_pydot.to_pydot(G)") 

339 print('P.write_dot("file.dot")') 

340 print(f"And then run {prog} on file.dot") 

341 return 

342 

343 # List of one or more "pydot.Dot" instances deserialized from this string. 

344 Q_list = pydot.graph_from_dot_data(D) 

345 assert len(Q_list) == 1 

346 

347 # The first and only such instance, as guaranteed by the above assertion. 

348 Q = Q_list[0] 

349 

350 node_pos = {} 

351 for n in G.nodes(): 

352 str_n = str(n) 

353 node = Q.get_node(pydot.quote_id_if_necessary(str_n)) 

354 

355 if isinstance(node, list): 

356 node = node[0] 

357 pos = node.get_pos()[1:-1] # strip leading and trailing double quotes 

358 if pos is not None: 

359 xx, yy = pos.split(",") 

360 node_pos[n] = (float(xx), float(yy)) 

361 return node_pos