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

163 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 if isinstance(val, str): 

165 a.attr["pos"] = val 

166 else: 

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

168 else: 

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

170 

171 # loop over edges 

172 if N.is_multigraph(): 

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

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

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

176 # Add edge data 

177 a = A.get_edge(u, v) 

178 a.attr.update(str_edgedata) 

179 

180 else: 

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

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

183 A.add_edge(u, v) 

184 # Add edge data 

185 a = A.get_edge(u, v) 

186 a.attr.update(str_edgedata) 

187 

188 return A 

189 

190 

191def write_dot(G, path): 

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

193 

194 Parameters 

195 ---------- 

196 G : graph 

197 A networkx graph 

198 path : filename 

199 Filename or file handle to write 

200 

201 Notes 

202 ----- 

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

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

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

206 """ 

207 A = to_agraph(G) 

208 A.write(path) 

209 A.clear() 

210 return 

211 

212 

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

214def read_dot(path): 

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

216 

217 Parameters 

218 ---------- 

219 path : file or string 

220 File name or file handle to read. 

221 """ 

222 try: 

223 import pygraphviz 

224 except ImportError as err: 

225 raise ImportError( 

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

227 ) from err 

228 A = pygraphviz.AGraph(file=path) 

229 gr = from_agraph(A) 

230 A.clear() 

231 return gr 

232 

233 

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

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

236 

237 Parameters 

238 ---------- 

239 G : NetworkX graph 

240 A graph created with NetworkX 

241 prog : string 

242 Name of Graphviz layout program 

243 root : string, optional 

244 Root node for twopi layout 

245 args : string, optional 

246 Extra arguments to Graphviz layout program 

247 

248 Returns 

249 ------- 

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

251 

252 Examples 

253 -------- 

254 >>> G = nx.petersen_graph() 

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

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

257 

258 Notes 

259 ----- 

260 This is a wrapper for pygraphviz_layout. 

261 

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

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

264 """ 

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

266 

267 

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

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

270 

271 Parameters 

272 ---------- 

273 G : NetworkX graph 

274 A graph created with NetworkX 

275 prog : string 

276 Name of Graphviz layout program 

277 root : string, optional 

278 Root node for twopi layout 

279 args : string, optional 

280 Extra arguments to Graphviz layout program 

281 

282 Returns 

283 ------- 

284 node_pos : dict 

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

286 

287 Examples 

288 -------- 

289 >>> G = nx.petersen_graph() 

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

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

292 

293 Notes 

294 ----- 

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

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

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

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

299 for the layout computation using something similar to:: 

300 

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

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

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

304 

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

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

307 """ 

308 try: 

309 import pygraphviz 

310 except ImportError as err: 

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

312 if root is not None: 

313 args += f"-Groot={root}" 

314 A = to_agraph(G) 

315 A.layout(prog=prog, args=args) 

316 node_pos = {} 

317 for n in G: 

318 node = pygraphviz.Node(A, n) 

319 try: 

320 xs = node.attr["pos"].split(",") 

321 node_pos[n] = tuple(float(x) for x in xs) 

322 except: 

323 print("no position for node", n) 

324 node_pos[n] = (0.0, 0.0) 

325 return node_pos 

326 

327 

328@nx.utils.open_file(5, "w+b") 

329def view_pygraphviz( 

330 G, edgelabel=None, prog="dot", args="", suffix="", path=None, show=True 

331): 

332 """Views the graph G using the specified layout algorithm. 

333 

334 Parameters 

335 ---------- 

336 G : NetworkX graph 

337 The machine to draw. 

338 edgelabel : str, callable, None 

339 If a string, then it specifies the edge attribute to be displayed 

340 on the edge labels. If a callable, then it is called for each 

341 edge and it should return the string to be displayed on the edges. 

342 The function signature of `edgelabel` should be edgelabel(data), 

343 where `data` is the edge attribute dictionary. 

344 prog : string 

345 Name of Graphviz layout program. 

346 args : str 

347 Additional arguments to pass to the Graphviz layout program. 

348 suffix : str 

349 If `filename` is None, we save to a temporary file. The value of 

350 `suffix` will appear at the tail end of the temporary filename. 

351 path : str, None 

352 The filename used to save the image. If None, save to a temporary 

353 file. File formats are the same as those from pygraphviz.agraph.draw. 

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

355 show : bool, default = True 

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

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

358 at `path`. 

359 

360 Returns 

361 ------- 

362 path : str 

363 The filename of the generated image. 

364 A : PyGraphviz graph 

365 The PyGraphviz graph instance used to generate the image. 

366 

367 Notes 

368 ----- 

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

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

371 calls if you experience problems. 

372 

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

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

375 

376 """ 

377 if not len(G): 

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

379 

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

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

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

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

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

385 

386 # to_agraph() uses these values. 

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

388 for attr in attrs: 

389 if attr not in G.graph: 

390 G.graph[attr] = {} 

391 

392 # These are the default values. 

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

394 node_attrs = { 

395 "style": "filled", 

396 "fillcolor": "#0000FF40", 

397 "height": "0.75", 

398 "width": "0.75", 

399 "shape": "circle", 

400 } 

401 graph_attrs = {} 

402 

403 def update_attrs(which, attrs): 

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

405 added = [] 

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

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

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

409 added.append(k) 

410 

411 def clean_attrs(which, added): 

412 # Remove added attributes 

413 for attr in added: 

414 del G.graph[which][attr] 

415 if not G.graph[which]: 

416 del G.graph[which] 

417 

418 # Update all default values 

419 update_attrs("edge", edge_attrs) 

420 update_attrs("node", node_attrs) 

421 update_attrs("graph", graph_attrs) 

422 

423 # Convert to agraph, so we inherit default values 

424 A = to_agraph(G) 

425 

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

427 clean_attrs("edge", edge_attrs) 

428 clean_attrs("node", node_attrs) 

429 clean_attrs("graph", graph_attrs) 

430 

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

432 if edgelabel is not None: 

433 if not callable(edgelabel): 

434 

435 def func(data): 

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

437 

438 else: 

439 func = edgelabel 

440 

441 # update all the edge labels 

442 if G.is_multigraph(): 

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

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

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

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

447 else: 

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

449 edge = A.get_edge(u, v) 

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

451 

452 if path is None: 

453 ext = "png" 

454 if suffix: 

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

456 else: 

457 suffix = f".{ext}" 

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

459 else: 

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

461 pass 

462 

463 # Write graph to file 

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

465 path.close() 

466 

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

468 if show: 

469 from PIL import Image 

470 

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

472 

473 return path.name, A