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

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

161 statements  

1""" 

2*************** 

3Graphviz AGraph 

4*************** 

5 

6Interface to pygraphviz AGraph class. 

7 

8Examples 

9-------- 

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

11>>> A = nx.nx_agraph.to_agraph(G) 

12>>> H = nx.nx_agraph.from_agraph(A) 

13 

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

20 

21import tempfile 

22 

23import networkx as nx 

24 

25__all__ = [ 

26 "from_agraph", 

27 "to_agraph", 

28 "write_dot", 

29 "read_dot", 

30 "graphviz_layout", 

31 "pygraphviz_layout", 

32 "view_pygraphviz", 

33] 

34 

35 

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

37def from_agraph(A, create_using=None): 

38 """Returns a NetworkX Graph or DiGraph from a PyGraphviz graph. 

39 

40 Parameters 

41 ---------- 

42 A : PyGraphviz AGraph 

43 A graph created with PyGraphviz 

44 

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`. 

48 

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) 

54 

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. 

59 

60 Default node attributes will be in the dictionary G.node_attr 

61 which is keyed by node. 

62 

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. 

66 

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 

79 

80 # assign defaults 

81 N = nx.empty_graph(0, create_using) 

82 if A.name is not None: 

83 N.name = A.name 

84 

85 # add graph attributes 

86 N.graph.update(A.graph_attr) 

87 

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) 

92 

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) 

104 

105 # add default attributes for graph, nodes, and edges 

106 # hang them on N.graph_attr 

107 graph_default_dict = dict(A.graph_attr) 

108 if graph_default_dict: 

109 N.graph["graph"] = graph_default_dict 

110 node_default_dict = dict(A.node_attr) 

111 if node_default_dict and node_default_dict != {"label": "\\N"}: 

112 N.graph["node"] = node_default_dict 

113 edge_default_dict = dict(A.edge_attr) 

114 if edge_default_dict: 

115 N.graph["edge"] = edge_default_dict 

116 return N 

117 

118 

119def to_agraph(N): 

120 """Returns a pygraphviz graph from a NetworkX graph N. 

121 

122 Parameters 

123 ---------- 

124 N : NetworkX graph 

125 A graph created with NetworkX 

126 

127 Examples 

128 -------- 

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

130 >>> A = nx.nx_agraph.to_agraph(K5) 

131 

132 Notes 

133 ----- 

134 If N has an dict N.graph_attr an attempt will be made first 

135 to copy properties attached to the graph (see from_agraph) 

136 and then updated with the calling arguments if any. 

137 

138 """ 

139 try: 

140 import pygraphviz 

141 except ImportError as err: 

142 raise ImportError("requires pygraphviz http://pygraphviz.github.io/") from err 

143 directed = N.is_directed() 

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

145 

146 A = pygraphviz.AGraph(name=N.name, strict=strict, directed=directed) 

147 

148 # default graph attributes 

149 A.graph_attr.update(N.graph.get("graph", {})) 

150 A.node_attr.update(N.graph.get("node", {})) 

151 A.edge_attr.update(N.graph.get("edge", {})) 

152 

153 A.graph_attr.update( 

154 (k, v) for k, v in N.graph.items() if k not in ("graph", "node", "edge") 

155 ) 

156 

157 # add nodes 

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

159 A.add_node(n) 

160 # Add node data 

161 a = A.get_node(n) 

162 for key, val in nodedata.items(): 

163 if key == "pos": 

164 a.attr["pos"] = f"{val[0]},{val[1]}!" 

165 else: 

166 a.attr[key] = str(val) 

167 

168 # loop over edges 

169 if N.is_multigraph(): 

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

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

172 A.add_edge(u, v, key=str(key)) 

173 # Add edge data 

174 a = A.get_edge(u, v) 

175 a.attr.update(str_edgedata) 

176 

177 else: 

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

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

180 A.add_edge(u, v) 

181 # Add edge data 

182 a = A.get_edge(u, v) 

183 a.attr.update(str_edgedata) 

184 

185 return A 

186 

187 

188def write_dot(G, path): 

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

190 

191 Parameters 

192 ---------- 

193 G : graph 

194 A networkx graph 

195 path : filename 

196 Filename or file handle to write 

197 

198 Notes 

199 ----- 

200 To use a specific graph layout, call ``A.layout`` prior to `write_dot`. 

201 Note that some graphviz layouts are not guaranteed to be deterministic, 

202 see https://gitlab.com/graphviz/graphviz/-/issues/1767 for more info. 

203 """ 

204 A = to_agraph(G) 

205 A.write(path) 

206 A.clear() 

207 return 

208 

209 

210@nx._dispatchable(name="agraph_read_dot", graphs=None, returns_graph=True) 

211def read_dot(path): 

212 """Returns a NetworkX graph from a dot file on path. 

213 

214 Parameters 

215 ---------- 

216 path : file or string 

217 File name or file handle to read. 

218 """ 

219 try: 

220 import pygraphviz 

221 except ImportError as err: 

222 raise ImportError( 

223 "read_dot() requires pygraphviz http://pygraphviz.github.io/" 

224 ) from err 

225 A = pygraphviz.AGraph(file=path) 

226 gr = from_agraph(A) 

227 A.clear() 

228 return gr 

229 

230 

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

232 """Create node positions for G using Graphviz. 

233 

234 Parameters 

235 ---------- 

236 G : NetworkX graph 

237 A graph created with NetworkX 

238 prog : string 

239 Name of Graphviz layout program 

240 root : string, optional 

241 Root node for twopi layout 

242 args : string, optional 

243 Extra arguments to Graphviz layout program 

244 

245 Returns 

246 ------- 

247 Dictionary of x, y, positions keyed by node. 

248 

249 Examples 

250 -------- 

251 >>> G = nx.petersen_graph() 

252 >>> pos = nx.nx_agraph.graphviz_layout(G) 

253 >>> pos = nx.nx_agraph.graphviz_layout(G, prog="dot") 

254 

255 Notes 

256 ----- 

257 This is a wrapper for pygraphviz_layout. 

258 

259 Note that some graphviz layouts are not guaranteed to be deterministic, 

260 see https://gitlab.com/graphviz/graphviz/-/issues/1767 for more info. 

261 """ 

262 return pygraphviz_layout(G, prog=prog, root=root, args=args) 

263 

264 

265def pygraphviz_layout(G, prog="neato", root=None, args=""): 

266 """Create node positions for G using Graphviz. 

267 

268 Parameters 

269 ---------- 

270 G : NetworkX graph 

271 A graph created with NetworkX 

272 prog : string 

273 Name of Graphviz layout program 

274 root : string, optional 

275 Root node for twopi layout 

276 args : string, optional 

277 Extra arguments to Graphviz layout program 

278 

279 Returns 

280 ------- 

281 node_pos : dict 

282 Dictionary of x, y, positions keyed by node. 

283 

284 Examples 

285 -------- 

286 >>> G = nx.petersen_graph() 

287 >>> pos = nx.nx_agraph.graphviz_layout(G) 

288 >>> pos = nx.nx_agraph.graphviz_layout(G, prog="dot") 

289 

290 Notes 

291 ----- 

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

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

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

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

296 for the layout computation using something similar to:: 

297 

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

299 >>> H_layout = nx.nx_agraph.pygraphviz_layout(H, prog="dot") 

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

301 

302 Note that some graphviz layouts are not guaranteed to be deterministic, 

303 see https://gitlab.com/graphviz/graphviz/-/issues/1767 for more info. 

304 """ 

305 try: 

306 import pygraphviz 

307 except ImportError as err: 

308 raise ImportError("requires pygraphviz http://pygraphviz.github.io/") 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 

323 

324 

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. 

330 

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 Filenames ending in .gz or .bz2 will be compressed. 

352 show : bool, default = True 

353 Whether to display the graph with :mod:`PIL.Image.show`, 

354 default is `True`. If `False`, the rendered graph is still available 

355 at `path`. 

356 

357 Returns 

358 ------- 

359 path : str 

360 The filename of the generated image. 

361 A : PyGraphviz graph 

362 The PyGraphviz graph instance used to generate the image. 

363 

364 Notes 

365 ----- 

366 If this function is called in succession too quickly, sometimes the 

367 image is not displayed. So you might consider time.sleep(.5) between 

368 calls if you experience problems. 

369 

370 Note that some graphviz layouts are not guaranteed to be deterministic, 

371 see https://gitlab.com/graphviz/graphviz/-/issues/1767 for more info. 

372 

373 """ 

374 if not len(G): 

375 raise nx.NetworkXException("An empty graph cannot be drawn.") 

376 

377 # If we are providing default values for graphviz, these must be set 

378 # before any nodes or edges are added to the PyGraphviz graph object. 

379 # The reason for this is that default values only affect incoming objects. 

380 # If you change the default values after the objects have been added, 

381 # then they inherit no value and are set only if explicitly set. 

382 

383 # to_agraph() uses these values. 

384 attrs = ["edge", "node", "graph"] 

385 for attr in attrs: 

386 if attr not in G.graph: 

387 G.graph[attr] = {} 

388 

389 # These are the default values. 

390 edge_attrs = {"fontsize": "10"} 

391 node_attrs = { 

392 "style": "filled", 

393 "fillcolor": "#0000FF40", 

394 "height": "0.75", 

395 "width": "0.75", 

396 "shape": "circle", 

397 } 

398 graph_attrs = {} 

399 

400 def update_attrs(which, attrs): 

401 # Update graph attributes. Return list of those which were added. 

402 added = [] 

403 for k, v in attrs.items(): 

404 if k not in G.graph[which]: 

405 G.graph[which][k] = v 

406 added.append(k) 

407 

408 def clean_attrs(which, added): 

409 # Remove added attributes 

410 for attr in added: 

411 del G.graph[which][attr] 

412 if not G.graph[which]: 

413 del G.graph[which] 

414 

415 # Update all default values 

416 update_attrs("edge", edge_attrs) 

417 update_attrs("node", node_attrs) 

418 update_attrs("graph", graph_attrs) 

419 

420 # Convert to agraph, so we inherit default values 

421 A = to_agraph(G) 

422 

423 # Remove the default values we added to the original graph. 

424 clean_attrs("edge", edge_attrs) 

425 clean_attrs("node", node_attrs) 

426 clean_attrs("graph", graph_attrs) 

427 

428 # If the user passed in an edgelabel, we update the labels for all edges. 

429 if edgelabel is not None: 

430 if not callable(edgelabel): 

431 

432 def func(data): 

433 return "".join([" ", str(data[edgelabel]), " "]) 

434 

435 else: 

436 func = edgelabel 

437 

438 # update all the edge labels 

439 if G.is_multigraph(): 

440 for u, v, key, data in G.edges(keys=True, data=True): 

441 # PyGraphviz doesn't convert the key to a string. See #339 

442 edge = A.get_edge(u, v, str(key)) 

443 edge.attr["label"] = str(func(data)) 

444 else: 

445 for u, v, data in G.edges(data=True): 

446 edge = A.get_edge(u, v) 

447 edge.attr["label"] = str(func(data)) 

448 

449 if path is None: 

450 ext = "png" 

451 if suffix: 

452 suffix = f"_{suffix}.{ext}" 

453 else: 

454 suffix = f".{ext}" 

455 path = tempfile.NamedTemporaryFile(suffix=suffix, delete=False) 

456 else: 

457 # Assume the decorator worked and it is a file-object. 

458 pass 

459 

460 # Write graph to file 

461 A.draw(path=path, format=None, prog=prog, args=args) 

462 path.close() 

463 

464 # Show graph in a new window (depends on platform configuration) 

465 if show: 

466 from PIL import Image 

467 

468 Image.open(path.name).show() 

469 

470 return path.name, A