Statistics
| Branch: | Revision:

iof-tools / networkxMiCe / networkx-master / networkx / drawing / nx_agraph.py @ 5cef0f13

History | View | Annotate | Download (14.1 KB)

1
#    Copyright (C) 2004-2019 by
2
#    Aric Hagberg <hagberg@lanl.gov>
3
#    Dan Schult <dschult@colgate.edu>
4
#    Pieter Swart <swart@lanl.gov>
5
#    All rights reserved.
6
#    BSD license.
7
#
8
# Author: Aric Hagberg (hagberg@lanl.gov)
9
"""
10
***************
11
Graphviz AGraph
12
***************
13

14
Interface to pygraphviz AGraph class.
15

16
Examples
17
--------
18
>>> G = nx.complete_graph(5)
19
>>> A = nx.nx_agraph.to_agraph(G)
20
>>> H = nx.nx_agraph.from_agraph(A)
21

22
See Also
23
--------
24
Pygraphviz: http://pygraphviz.github.io/
25
"""
26
import os
27
import tempfile
28
import networkx as nx
29

    
30
__all__ = ['from_agraph', 'to_agraph',
31
           'write_dot', 'read_dot',
32
           'graphviz_layout',
33
           'pygraphviz_layout',
34
           'view_pygraphviz']
35

    
36

    
37
def 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=nx.Graph)
46
       Graph type to create. If graph instance, then cleared before populated.
47

48
    Examples
49
    --------
50
    >>> K5 = nx.complete_graph(5)
51
    >>> A = nx.nx_agraph.to_agraph(K5)
52
    >>> G = nx.nx_agraph.from_agraph(A)
53

54
    Notes
55
    -----
56
    The Graph G will have a dictionary G.graph_attr containing
57
    the default graphviz attributes for graphs, nodes and edges.
58

59
    Default node attributes will be in the dictionary G.node_attr
60
    which is keyed by node.
61

62
    Edge attributes will be returned as edge data in G.  With
63
    edge_attr=False the edge data will be the Graphviz edge weight
64
    attribute or the value 1 if no edge weight attribute is found.
65

66
    """
67
    if create_using is None:
68
        if A.is_directed():
69
            if A.is_strict():
70
                create_using = nx.DiGraph
71
            else:
72
                create_using = nx.MultiDiGraph
73
        else:
74
            if A.is_strict():
75
                create_using = nx.Graph
76
            else:
77
                create_using = nx.MultiGraph
78

    
79
    # assign defaults
80
    N = nx.empty_graph(0, create_using)
81
    if A.name is not None:
82
        N.name = A.name
83

    
84
    # add graph attributes
85
    N.graph.update(A.graph_attr)
86

    
87
    # add nodes, attributes to N.node_attr
88
    for n in A.nodes():
89
        str_attr = {str(k): v for k, v in n.attr.items()}
90
        N.add_node(str(n), **str_attr)
91

    
92
    # add edges, assign edge data as dictionary of attributes
93
    for e in A.edges():
94
        u, v = str(e[0]), str(e[1])
95
        attr = dict(e.attr)
96
        str_attr = {str(k): v for k, v in attr.items()}
97
        if not N.is_multigraph():
98
            if e.name is not None:
99
                str_attr['key'] = e.name
100
            N.add_edge(u, v, **str_attr)
101
        else:
102
            N.add_edge(u, v, key=e.name, **str_attr)
103

    
104
    # add default attributes for graph, nodes, and edges
105
    # hang them on N.graph_attr
106
    N.graph['graph'] = dict(A.graph_attr)
107
    N.graph['node'] = dict(A.node_attr)
108
    N.graph['edge'] = dict(A.edge_attr)
109
    return N
110

    
111

    
112
def to_agraph(N):
113
    """Returns a pygraphviz graph from a NetworkX graph N.
114

115
    Parameters
116
    ----------
117
    N : NetworkX graph
118
      A graph created with NetworkX
119

120
    Examples
121
    --------
122
    >>> K5 = nx.complete_graph(5)
123
    >>> A = nx.nx_agraph.to_agraph(K5)
124

125
    Notes
126
    -----
127
    If N has an dict N.graph_attr an attempt will be made first
128
    to copy properties attached to the graph (see from_agraph)
129
    and then updated with the calling arguments if any.
130

131
    """
132
    try:
133
        import pygraphviz
134
    except ImportError:
135
        raise ImportError('requires pygraphviz ',
136
                          'http://pygraphviz.github.io/')
137
    directed = N.is_directed()
138
    strict = nx.number_of_selfloops(N) == 0 and not N.is_multigraph()
139
    A = pygraphviz.AGraph(name=N.name, strict=strict, directed=directed)
140

    
141
    # default graph attributes
142
    A.graph_attr.update(N.graph.get('graph', {}))
143
    A.node_attr.update(N.graph.get('node', {}))
144
    A.edge_attr.update(N.graph.get('edge', {}))
145

    
146
    A.graph_attr.update((k, v) for k, v in N.graph.items()
147
                        if k not in ('graph', 'node', 'edge'))
148

    
149
    # add nodes
150
    for n, nodedata in N.nodes(data=True):
151
        A.add_node(n)
152
        if nodedata is not None:
153
            a = A.get_node(n)
154
            a.attr.update({k: str(v) for k, v in nodedata.items()})
155

    
156
    # loop over edges
157
    if N.is_multigraph():
158
        for u, v, key, edgedata in N.edges(data=True, keys=True):
159
            str_edgedata = {k: str(v) for k, v in edgedata.items()
160
                            if k != 'key'}
161
            A.add_edge(u, v, key=str(key))
162
            if edgedata is not None:
163
                a = A.get_edge(u, v)
164
                a.attr.update(str_edgedata)
165

    
166
    else:
167
        for u, v, edgedata in N.edges(data=True):
168
            str_edgedata = {k: str(v) for k, v in edgedata.items()}
169
            A.add_edge(u, v)
170
            if edgedata is not None:
171
                a = A.get_edge(u, v)
172
                a.attr.update(str_edgedata)
173

    
174
    return A
175

    
176

    
177
def write_dot(G, path):
178
    """Write NetworkX graph G to Graphviz dot format on path.
179

180
    Parameters
181
    ----------
182
    G : graph
183
       A networkx graph
184
    path : filename
185
       Filename or file handle to write
186
    """
187
    try:
188
        import pygraphviz
189
    except ImportError:
190
        raise ImportError('requires pygraphviz ',
191
                          'http://pygraphviz.github.io/')
192
    A = to_agraph(G)
193
    A.write(path)
194
    A.clear()
195
    return
196

    
197

    
198
def read_dot(path):
199
    """Returns a NetworkX graph from a dot file on path.
200

201
    Parameters
202
    ----------
203
    path : file or string
204
       File name or file handle to read.
205
    """
206
    try:
207
        import pygraphviz
208
    except ImportError:
209
        raise ImportError('read_dot() requires pygraphviz ',
210
                          'http://pygraphviz.github.io/')
211
    A = pygraphviz.AGraph(file=path)
212
    return from_agraph(A)
213

    
214

    
215
def graphviz_layout(G, prog='neato', root=None, args=''):
216
    """Create node positions for G using Graphviz.
217

218
    Parameters
219
    ----------
220
    G : NetworkX graph
221
      A graph created with NetworkX
222
    prog : string
223
      Name of Graphviz layout program
224
    root : string, optional
225
      Root node for twopi layout
226
    args : string, optional
227
      Extra arguments to Graphviz layout program
228

229
    Returns
230
    -------
231
      Dictionary of x, y, positions keyed by node.
232

233
    Examples
234
    --------
235
    >>> G = nx.petersen_graph()
236
    >>> pos = nx.nx_agraph.graphviz_layout(G)
237
    >>> pos = nx.nx_agraph.graphviz_layout(G, prog='dot')
238

239
    Notes
240
    -----
241
    This is a wrapper for pygraphviz_layout.
242
    """
243
    return pygraphviz_layout(G, prog=prog, root=root, args=args)
244

    
245

    
246
def pygraphviz_layout(G, prog='neato', root=None, args=''):
247
    """Create node positions for G using Graphviz.
248

249
    Parameters
250
    ----------
251
    G : NetworkX graph
252
      A graph created with NetworkX
253
    prog : string
254
      Name of Graphviz layout program
255
    root : string, optional
256
      Root node for twopi layout
257
    args : string, optional
258
      Extra arguments to Graphviz layout program
259

260
    Returns : dictionary
261
      Dictionary of x, y, positions keyed by node.
262

263
    Examples
264
    --------
265
    >>> G = nx.petersen_graph()
266
    >>> pos = nx.nx_agraph.graphviz_layout(G)
267
    >>> pos = nx.nx_agraph.graphviz_layout(G, prog='dot')
268

269
    Notes
270
    -----
271
    If you use complex node objects, they may have the same string
272
    representation and GraphViz could treat them as the same node.
273
    The layout may assign both nodes a single location. See Issue #1568
274
    If this occurs in your case, consider relabeling the nodes just
275
    for the layout computation using something similar to:
276

277
        H = nx.convert_node_labels_to_integers(G, label_attribute='node_label')
278
        H_layout = nx.nx_agraph.pygraphviz_layout(G, prog='dot')
279
        G_layout = {H.nodes[n]['node_label']: p for n, p in H_layout.items()}
280

281
    """
282
    try:
283
        import pygraphviz
284
    except ImportError:
285
        raise ImportError('requires pygraphviz ',
286
                          'http://pygraphviz.github.io/')
287
    if root is not None:
288
        args += "-Groot=%s" % root
289
    A = to_agraph(G)
290
    A.layout(prog=prog, args=args)
291
    node_pos = {}
292
    for n in G:
293
        node = pygraphviz.Node(A, n)
294
        try:
295
            xx, yy = node.attr["pos"].split(',')
296
            node_pos[n] = (float(xx), float(yy))
297
        except:
298
            print("no position for node", n)
299
            node_pos[n] = (0.0, 0.0)
300
    return node_pos
301

    
302

    
303
@nx.utils.open_file(5, 'w+b')
304
def view_pygraphviz(G, edgelabel=None, prog='dot', args='',
305
                    suffix='', path=None):
306
    """Views the graph G using the specified layout algorithm.
307

308
    Parameters
309
    ----------
310
    G : NetworkX graph
311
        The machine to draw.
312
    edgelabel : str, callable, None
313
        If a string, then it specifes the edge attribute to be displayed
314
        on the edge labels. If a callable, then it is called for each
315
        edge and it should return the string to be displayed on the edges.
316
        The function signature of `edgelabel` should be edgelabel(data),
317
        where `data` is the edge attribute dictionary.
318
    prog : string
319
        Name of Graphviz layout program.
320
    args : str
321
        Additional arguments to pass to the Graphviz layout program.
322
    suffix : str
323
        If `filename` is None, we save to a temporary file.  The value of
324
        `suffix` will appear at the tail end of the temporary filename.
325
    path : str, None
326
        The filename used to save the image.  If None, save to a temporary
327
        file.  File formats are the same as those from pygraphviz.agraph.draw.
328

329
    Returns
330
    -------
331
    path : str
332
        The filename of the generated image.
333
    A : PyGraphviz graph
334
        The PyGraphviz graph instance used to generate the image.
335

336
    Notes
337
    -----
338
    If this function is called in succession too quickly, sometimes the
339
    image is not displayed. So you might consider time.sleep(.5) between
340
    calls if you experience problems.
341

342
    """
343
    if not len(G):
344
        raise nx.NetworkXException("An empty graph cannot be drawn.")
345

    
346
    import pygraphviz
347

    
348
    # If we are providing default values for graphviz, these must be set
349
    # before any nodes or edges are added to the PyGraphviz graph object.
350
    # The reason for this is that default values only affect incoming objects.
351
    # If you change the default values after the objects have been added,
352
    # then they inherit no value and are set only if explicitly set.
353

    
354
    # to_agraph() uses these values.
355
    attrs = ['edge', 'node', 'graph']
356
    for attr in attrs:
357
        if attr not in G.graph:
358
            G.graph[attr] = {}
359

    
360
    # These are the default values.
361
    edge_attrs = {'fontsize': '10'}
362
    node_attrs = {'style': 'filled',
363
                  'fillcolor': '#0000FF40',
364
                  'height': '0.75',
365
                  'width': '0.75',
366
                  'shape': 'circle'}
367
    graph_attrs = {}
368

    
369
    def update_attrs(which, attrs):
370
        # Update graph attributes. Return list of those which were added.
371
        added = []
372
        for k, v in attrs.items():
373
            if k not in G.graph[which]:
374
                G.graph[which][k] = v
375
                added.append(k)
376

    
377
    def clean_attrs(which, added):
378
        # Remove added attributes
379
        for attr in added:
380
            del G.graph[which][attr]
381
        if not G.graph[which]:
382
            del G.graph[which]
383

    
384
    # Update all default values
385
    update_attrs('edge', edge_attrs)
386
    update_attrs('node', node_attrs)
387
    update_attrs('graph', graph_attrs)
388

    
389
    # Convert to agraph, so we inherit default values
390
    A = to_agraph(G)
391

    
392
    # Remove the default values we added to the original graph.
393
    clean_attrs('edge', edge_attrs)
394
    clean_attrs('node', node_attrs)
395
    clean_attrs('graph', graph_attrs)
396

    
397
    # If the user passed in an edgelabel, we update the labels for all edges.
398
    if edgelabel is not None:
399
        if not hasattr(edgelabel, '__call__'):
400
            def func(data):
401
                return ''.join(["  ", str(data[edgelabel]), "  "])
402
        else:
403
            func = edgelabel
404

    
405
        # update all the edge labels
406
        if G.is_multigraph():
407
            for u, v, key, data in G.edges(keys=True, data=True):
408
                # PyGraphviz doesn't convert the key to a string. See #339
409
                edge = A.get_edge(u, v, str(key))
410
                edge.attr['label'] = str(func(data))
411
        else:
412
            for u, v, data in G.edges(data=True):
413
                edge = A.get_edge(u, v)
414
                edge.attr['label'] = str(func(data))
415

    
416
    if path is None:
417
        ext = 'png'
418
        if suffix:
419
            suffix = '_%s.%s' % (suffix, ext)
420
        else:
421
            suffix = '.%s' % (ext,)
422
        path = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
423
    else:
424
        # Assume the decorator worked and it is a file-object.
425
        pass
426

    
427
    display_pygraphviz(A, path=path, prog=prog, args=args)
428

    
429
    return path.name, A
430

    
431

    
432
def display_pygraphviz(graph, path, format=None, prog=None, args=''):
433
    """Internal function to display a graph in OS dependent manner.
434

435
    Parameters
436
    ----------
437
    graph : PyGraphviz graph
438
        A PyGraphviz AGraph instance.
439
    path :  file object
440
        An already opened file object that will be closed.
441
    format : str, None
442
        An attempt is made to guess the output format based on the extension
443
        of the filename. If that fails, the value of `format` is used.
444
    prog : string
445
        Name of Graphviz layout program.
446
    args : str
447
        Additional arguments to pass to the Graphviz layout program.
448

449
    Notes
450
    -----
451
    If this function is called in succession too quickly, sometimes the
452
    image is not displayed. So you might consider time.sleep(.5) between
453
    calls if you experience problems.
454

455
    """
456
    if format is None:
457
        filename = path.name
458
        format = os.path.splitext(filename)[1].lower()[1:]
459
    if not format:
460
        # Let the draw() function use its default
461
        format = None
462

    
463
    # Save to a file and display in the default viewer.
464
    # We must close the file before viewing it.
465
    graph.draw(path, format, prog, args)
466
    path.close()
467
    nx.utils.default_opener(filename)
468

    
469

    
470
# fixture for nose tests
471
def setup_module(module):
472
    from nose import SkipTest
473
    try:
474
        import pygraphviz
475
    except:
476
        raise SkipTest("pygraphviz not available")