Statistics
| Branch: | Revision:

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

History | View | Annotate | Download (10.2 KB)

1
"""
2
*****
3
Pydot
4
*****
5

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

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

10
See Also
11
--------
12
pydot:         https://github.com/erocarrera/pydot
13
Graphviz:      https://www.graphviz.org
14
DOT Language:  http://www.graphviz.org/doc/info/lang.html
15
"""
16
# Author: Aric Hagberg (aric.hagberg@gmail.com)
17

    
18
#    Copyright (C) 2004-2019 by
19
#    Aric Hagberg <hagberg@lanl.gov>
20
#    Dan Schult <dschult@colgate.edu>
21
#    Pieter Swart <swart@lanl.gov>
22
#    Cecil Curry <leycec@gmail.com>
23
#    All rights reserved.
24
#    BSD license.
25
from locale import getpreferredencoding
26
from networkx.utils import open_file, make_str
27
import networkx as nx
28

    
29
__all__ = ['write_dot', 'read_dot', 'graphviz_layout', 'pydot_layout',
30
           'to_pydot', 'from_pydot']
31

    
32
# 2.x/3.x compatibility
33
try:
34
    basestring
35
except NameError:
36
    basestring = str
37
    unicode = str
38

    
39

    
40
@open_file(1, mode='w')
41
def write_dot(G, path):
42
    """Write NetworkX graph G to Graphviz dot format on path.
43

44
    Path can be a string or a file handle.
45
    """
46
    P = to_pydot(G)
47
    path.write(P.to_string())
48
    return
49

    
50

    
51
@open_file(0, mode='r')
52
def read_dot(path):
53
    """Returns a NetworkX :class:`MultiGraph` or :class:`MultiDiGraph` from the
54
    dot file with the passed path.
55

56
    If this file contains multiple graphs, only the first such graph is
57
    returned. All graphs _except_ the first are silently ignored.
58

59
    Parameters
60
    ----------
61
    path : str or file
62
        Filename or file handle.
63

64
    Returns
65
    -------
66
    G : MultiGraph or MultiDiGraph
67
        A :class:`MultiGraph` or :class:`MultiDiGraph`.
68

69
    Notes
70
    -----
71
    Use `G = nx.Graph(read_dot(path))` to return a :class:`Graph` instead of a
72
    :class:`MultiGraph`.
73
    """
74
    pydot = _import_pydot()
75
    data = path.read()
76

    
77
    # List of one or more "pydot.Dot" instances deserialized from this file.
78
    P_list = pydot.graph_from_dot_data(data)
79

    
80
    # Convert only the first such instance into a NetworkX graph.
81
    return from_pydot(P_list[0])
82

    
83

    
84
def from_pydot(P):
85
    """Returns a NetworkX graph from a Pydot graph.
86

87
    Parameters
88
    ----------
89
    P : Pydot graph
90
      A graph created with Pydot
91

92
    Returns
93
    -------
94
    G : NetworkX multigraph
95
        A MultiGraph or MultiDiGraph.
96

97
    Examples
98
    --------
99
    >>> K5 = nx.complete_graph(5)
100
    >>> A = nx.nx_pydot.to_pydot(K5)
101
    >>> G = nx.nx_pydot.from_pydot(A) # return MultiGraph
102

103
    # make a Graph instead of MultiGraph
104
    >>> G = nx.Graph(nx.nx_pydot.from_pydot(A))
105

106
    """
107
    if P.get_strict(None):  # pydot bug: get_strict() shouldn't take argument
108
        multiedges = False
109
    else:
110
        multiedges = True
111

    
112
    if P.get_type() == 'graph':  # undirected
113
        if multiedges:
114
            N = nx.MultiGraph()
115
        else:
116
            N = nx.Graph()
117
    else:
118
        if multiedges:
119
            N = nx.MultiDiGraph()
120
        else:
121
            N = nx.DiGraph()
122

    
123
    # assign defaults
124
    name = P.get_name().strip('"')
125
    if name != '':
126
        N.name = name
127

    
128
    # add nodes, attributes to N.node_attr
129
    for p in P.get_node_list():
130
        n = p.get_name().strip('"')
131
        if n in ('node', 'graph', 'edge'):
132
            continue
133
        N.add_node(n, **p.get_attributes())
134

    
135
    # add edges
136
    for e in P.get_edge_list():
137
        u = e.get_source()
138
        v = e.get_destination()
139
        attr = e.get_attributes()
140
        s = []
141
        d = []
142

    
143
        if isinstance(u, basestring):
144
            s.append(u.strip('"'))
145
        else:
146
            for unodes in u['nodes']:
147
                s.append(unodes.strip('"'))
148

    
149
        if isinstance(v, basestring):
150
            d.append(v.strip('"'))
151
        else:
152
            for vnodes in v['nodes']:
153
                d.append(vnodes.strip('"'))
154

    
155
        for source_node in s:
156
            for destination_node in d:
157
                N.add_edge(source_node, destination_node, **attr)
158

    
159
    # add default attributes for graph, nodes, edges
160
    pattr = P.get_attributes()
161
    if pattr:
162
        N.graph['graph'] = pattr
163
    try:
164
        N.graph['node'] = P.get_node_defaults()[0]
165
    except:  # IndexError,TypeError:
166
        pass  # N.graph['node']={}
167
    try:
168
        N.graph['edge'] = P.get_edge_defaults()[0]
169
    except:  # IndexError,TypeError:
170
        pass  # N.graph['edge']={}
171
    return N
172

    
173

    
174
def to_pydot(N):
175
    """Returns a pydot graph from a NetworkX graph N.
176

177
    Parameters
178
    ----------
179
    N : NetworkX graph
180
      A graph created with NetworkX
181

182
    Examples
183
    --------
184
    >>> K5 = nx.complete_graph(5)
185
    >>> P = nx.nx_pydot.to_pydot(K5)
186

187
    Notes
188
    -----
189

190
    """
191
    pydot = _import_pydot()
192

    
193
    # set Graphviz graph type
194
    if N.is_directed():
195
        graph_type = 'digraph'
196
    else:
197
        graph_type = 'graph'
198
    strict = nx.number_of_selfloops(N) == 0 and not N.is_multigraph()
199

    
200
    name = N.name
201
    graph_defaults = N.graph.get('graph', {})
202
    if name is '':
203
        P = pydot.Dot('', graph_type=graph_type, strict=strict,
204
                      **graph_defaults)
205
    else:
206
        P = pydot.Dot('"%s"' % name, graph_type=graph_type, strict=strict,
207
                      **graph_defaults)
208
    try:
209
        P.set_node_defaults(**N.graph['node'])
210
    except KeyError:
211
        pass
212
    try:
213
        P.set_edge_defaults(**N.graph['edge'])
214
    except KeyError:
215
        pass
216

    
217
    for n, nodedata in N.nodes(data=True):
218
        str_nodedata = dict((k, make_str(v)) for k, v in nodedata.items())
219
        p = pydot.Node(make_str(n), **str_nodedata)
220
        P.add_node(p)
221

    
222
    if N.is_multigraph():
223
        for u, v, key, edgedata in N.edges(data=True, keys=True):
224
            str_edgedata = dict((k, make_str(v)) for k, v in edgedata.items()
225
                                if k != 'key')
226
            edge = pydot.Edge(make_str(u), make_str(v),
227
                              key=make_str(key), **str_edgedata)
228
            P.add_edge(edge)
229

    
230
    else:
231
        for u, v, edgedata in N.edges(data=True):
232
            str_edgedata = dict((k, make_str(v)) for k, v in edgedata.items())
233
            edge = pydot.Edge(make_str(u), make_str(v), **str_edgedata)
234
            P.add_edge(edge)
235
    return P
236

    
237

    
238
def graphviz_layout(G, prog='neato', root=None):
239
    """Create node positions using Pydot and Graphviz.
240

241
    Returns a dictionary of positions keyed by node.
242

243
    Parameters
244
    ----------
245
    G : NetworkX Graph
246
        The graph for which the layout is computed.
247
    prog : string (default: 'neato')
248
        The name of the GraphViz program to use for layout.
249
        Options depend on GraphViz version but may include:
250
        'dot', 'twopi', 'fdp', 'sfdp', 'circo'
251
    root : Node from G or None (default: None)
252
        The node of G from which to start some layout algorithms.
253

254
    Returns
255
    -------
256
      Dictionary of (x, y) positions keyed by node.
257

258
    Examples
259
    --------
260
    >>> G = nx.complete_graph(4)
261
    >>> pos = nx.nx_pydot.graphviz_layout(G)
262
    >>> pos = nx.nx_pydot.graphviz_layout(G, prog='dot')
263

264
    Notes
265
    -----
266
    This is a wrapper for pydot_layout.
267
    """
268
    return pydot_layout(G=G, prog=prog, root=root)
269

    
270

    
271
def pydot_layout(G, prog='neato', root=None):
272
    """Create node positions using :mod:`pydot` and Graphviz.
273

274
    Parameters
275
    --------
276
    G : Graph
277
        NetworkX graph to be laid out.
278
    prog : string  (default: 'neato')
279
        Name of the GraphViz command to use for layout.
280
        Options depend on GraphViz version but may include:
281
        'dot', 'twopi', 'fdp', 'sfdp', 'circo'
282
    root : Node from G or None (default: None)
283
        The node of G from which to start some layout algorithms.
284

285
    Returns
286
    --------
287
    dict
288
        Dictionary of positions keyed by node.
289

290
    Examples
291
    --------
292
    >>> G = nx.complete_graph(4)
293
    >>> pos = nx.nx_pydot.pydot_layout(G)
294
    >>> pos = nx.nx_pydot.pydot_layout(G, prog='dot')
295

296
    Notes
297
    -----
298
    If you use complex node objects, they may have the same string
299
    representation and GraphViz could treat them as the same node.
300
    The layout may assign both nodes a single location. See Issue #1568
301
    If this occurs in your case, consider relabeling the nodes just
302
    for the layout computation using something similar to:
303

304
        H = nx.convert_node_labels_to_integers(G, label_attribute='node_label')
305
        H_layout = nx.nx_pydot.pydot_layout(G, prog='dot')
306
        G_layout = {H.nodes[n]['node_label']: p for n, p in H_layout.items()}
307

308
    """
309
    pydot = _import_pydot()
310
    P = to_pydot(G)
311
    if root is not None:
312
        P.set("root", make_str(root))
313

    
314
    # List of low-level bytes comprising a string in the dot language converted
315
    # from the passed graph with the passed external GraphViz command.
316
    D_bytes = P.create_dot(prog=prog)
317

    
318
    # Unique string decoded from these bytes with the preferred locale encoding
319
    D = unicode(D_bytes, encoding=getpreferredencoding())
320

    
321
    if D == "":  # no data returned
322
        print("Graphviz layout with %s failed" % (prog))
323
        print()
324
        print("To debug what happened try:")
325
        print("P = nx.nx_pydot.to_pydot(G)")
326
        print("P.write_dot(\"file.dot\")")
327
        print("And then run %s on file.dot" % (prog))
328
        return
329

    
330
    # List of one or more "pydot.Dot" instances deserialized from this string.
331
    Q_list = pydot.graph_from_dot_data(D)
332
    assert len(Q_list) == 1
333

    
334
    # The first and only such instance, as guaranteed by the above assertion.
335
    Q = Q_list[0]
336

    
337
    node_pos = {}
338
    for n in G.nodes():
339
        pydot_node = pydot.Node(make_str(n)).get_name()
340
        node = Q.get_node(pydot_node)
341

    
342
        if isinstance(node, list):
343
            node = node[0]
344
        pos = node.get_pos()[1:-1]  # strip leading and trailing double quotes
345
        if pos is not None:
346
            xx, yy = pos.split(",")
347
            node_pos[n] = (float(xx), float(yy))
348
    return node_pos
349

    
350

    
351
def _import_pydot():
352
    '''
353
    Import and return the `pydot` module if the currently installed version of
354
    this module satisfies NetworkX requirements _or_ raise an exception.
355

356
    Returns
357
    --------
358
    :mod:`pydot`
359
        Imported `pydot` module object.
360

361
    Raises
362
    --------
363
    ImportError
364
        If the `pydot` module is unimportable.
365
    '''
366

    
367
    import pydot
368
    return pydot
369

    
370
# fixture for nose tests
371

    
372

    
373
def setup_module(module):
374
    from nose import SkipTest
375
    try:
376
        return _import_pydot()
377
    except ImportError:
378
        raise SkipTest("pydot not available")