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

154 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-10-20 07:00 +0000

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

20import os 

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._dispatch(graphs=None) 

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 N.graph["graph"] = dict(A.graph_attr) 

108 N.graph["node"] = dict(A.node_attr) 

109 N.graph["edge"] = dict(A.edge_attr) 

110 return N 

111 

112 

113def to_agraph(N): 

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

115 

116 Parameters 

117 ---------- 

118 N : NetworkX graph 

119 A graph created with NetworkX 

120 

121 Examples 

122 -------- 

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

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

125 

126 Notes 

127 ----- 

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

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

130 and then updated with the calling arguments if any. 

131 

132 """ 

133 try: 

134 import pygraphviz 

135 except ImportError as err: 

136 raise ImportError( 

137 "requires pygraphviz " "http://pygraphviz.github.io/" 

138 ) from err 

139 directed = N.is_directed() 

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

141 

142 for node in N: 

143 if "pos" in N.nodes[node]: 

144 N.nodes[node]["pos"] = "{},{}!".format( 

145 N.nodes[node]["pos"][0], N.nodes[node]["pos"][1] 

146 ) 

147 

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

149 

150 # default graph attributes 

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

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

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

154 

155 A.graph_attr.update( 

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

157 ) 

158 

159 # add nodes 

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

161 A.add_node(n) 

162 # Add node data 

163 a = A.get_node(n) 

164 a.attr.update({k: str(v) for k, v in nodedata.items()}) 

165 

166 # loop over edges 

167 if N.is_multigraph(): 

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

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

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

171 # Add edge data 

172 a = A.get_edge(u, v) 

173 a.attr.update(str_edgedata) 

174 

175 else: 

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

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

178 A.add_edge(u, v) 

179 # Add edge data 

180 a = A.get_edge(u, v) 

181 a.attr.update(str_edgedata) 

182 

183 return A 

184 

185 

186def write_dot(G, path): 

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

188 

189 Parameters 

190 ---------- 

191 G : graph 

192 A networkx graph 

193 path : filename 

194 Filename or file handle to write 

195 

196 Notes 

197 ----- 

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

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

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

201 """ 

202 A = to_agraph(G) 

203 A.write(path) 

204 A.clear() 

205 return 

206 

207 

208@nx._dispatch(name="agraph_read_dot", graphs=None) 

209def read_dot(path): 

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

211 

212 Parameters 

213 ---------- 

214 path : file or string 

215 File name or file handle to read. 

216 """ 

217 try: 

218 import pygraphviz 

219 except ImportError as err: 

220 raise ImportError( 

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

222 ) from err 

223 A = pygraphviz.AGraph(file=path) 

224 gr = from_agraph(A) 

225 A.clear() 

226 return gr 

227 

228 

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

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

231 

232 Parameters 

233 ---------- 

234 G : NetworkX graph 

235 A graph created with NetworkX 

236 prog : string 

237 Name of Graphviz layout program 

238 root : string, optional 

239 Root node for twopi layout 

240 args : string, optional 

241 Extra arguments to Graphviz layout program 

242 

243 Returns 

244 ------- 

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

246 

247 Examples 

248 -------- 

249 >>> G = nx.petersen_graph() 

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

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

252 

253 Notes 

254 ----- 

255 This is a wrapper for pygraphviz_layout. 

256 

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

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

259 """ 

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

261 

262 

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

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

265 

266 Parameters 

267 ---------- 

268 G : NetworkX graph 

269 A graph created with NetworkX 

270 prog : string 

271 Name of Graphviz layout program 

272 root : string, optional 

273 Root node for twopi layout 

274 args : string, optional 

275 Extra arguments to Graphviz layout program 

276 

277 Returns 

278 ------- 

279 node_pos : dict 

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

281 

282 Examples 

283 -------- 

284 >>> G = nx.petersen_graph() 

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

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

287 

288 Notes 

289 ----- 

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

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

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

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

294 for the layout computation using something similar to:: 

295 

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

297 >>> H_layout = nx.nx_agraph.pygraphviz_layout(G, prog="dot") 

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

299 

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

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

302 """ 

303 try: 

304 import pygraphviz 

305 except ImportError as err: 

306 raise ImportError( 

307 "requires pygraphviz " "http://pygraphviz.github.io/" 

308 ) 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 show : bool, default = True 

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

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

354 at `path`. 

355 

356 Returns 

357 ------- 

358 path : str 

359 The filename of the generated image. 

360 A : PyGraphviz graph 

361 The PyGraphviz graph instance used to generate the image. 

362 

363 Notes 

364 ----- 

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

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

367 calls if you experience problems. 

368 

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

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

371 

372 """ 

373 if not len(G): 

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

375 

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

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

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

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

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

381 

382 # to_agraph() uses these values. 

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

384 for attr in attrs: 

385 if attr not in G.graph: 

386 G.graph[attr] = {} 

387 

388 # These are the default values. 

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

390 node_attrs = { 

391 "style": "filled", 

392 "fillcolor": "#0000FF40", 

393 "height": "0.75", 

394 "width": "0.75", 

395 "shape": "circle", 

396 } 

397 graph_attrs = {} 

398 

399 def update_attrs(which, attrs): 

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

401 added = [] 

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

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

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

405 added.append(k) 

406 

407 def clean_attrs(which, added): 

408 # Remove added attributes 

409 for attr in added: 

410 del G.graph[which][attr] 

411 if not G.graph[which]: 

412 del G.graph[which] 

413 

414 # Update all default values 

415 update_attrs("edge", edge_attrs) 

416 update_attrs("node", node_attrs) 

417 update_attrs("graph", graph_attrs) 

418 

419 # Convert to agraph, so we inherit default values 

420 A = to_agraph(G) 

421 

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

423 clean_attrs("edge", edge_attrs) 

424 clean_attrs("node", node_attrs) 

425 clean_attrs("graph", graph_attrs) 

426 

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

428 if edgelabel is not None: 

429 if not callable(edgelabel): 

430 

431 def func(data): 

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

433 

434 else: 

435 func = edgelabel 

436 

437 # update all the edge labels 

438 if G.is_multigraph(): 

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

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

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

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

443 else: 

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

445 edge = A.get_edge(u, v) 

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

447 

448 if path is None: 

449 ext = "png" 

450 if suffix: 

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

452 else: 

453 suffix = f".{ext}" 

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

455 else: 

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

457 pass 

458 

459 # Write graph to file 

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

461 path.close() 

462 

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

464 if show: 

465 from PIL import Image 

466 

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

468 

469 return path.name, A