Warning

This documents an unmaintained version of NetworkX. Please upgrade to a maintained version and see the current NetworkX documentation.

Source code for networkx.drawing.nx_pydot

"""
*****
Pydot
*****

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

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

See Also
--------
pydot:         https://github.com/erocarrera/pydot
Graphviz:      http://www.research.att.com/sw/tools/graphviz/
DOT Language:  http://www.graphviz.org/doc/info/lang.html
"""
# Author: Aric Hagberg (aric.hagberg@gmail.com)

#    Copyright (C) 2004-2018 by
#    Aric Hagberg <hagberg@lanl.gov>
#    Dan Schult <dschult@colgate.edu>
#    Pieter Swart <swart@lanl.gov>
#    Cecil Curry <leycec@gmail.com>
#    All rights reserved.
#    BSD license.
from locale import getpreferredencoding
from networkx.utils import open_file, make_str
from pkg_resources import parse_version
import networkx as nx

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

# Minimum required version of pydot, which broke backwards API compatibility in
# non-trivial ways and is thus a hard NetworkX requirement. Note that, although
# pydot 1.2.0 was the first to do so, pydot 1.2.3 resolves a critical long-
# standing Python 2.x issue required for sane NetworkX operation. See also:
#     https://github.com/erocarrera/pydot/blob/master/ChangeLog
PYDOT_VERSION_MIN = '1.2.3'

# 2.x/3.x compatibility
try:
    basestring
except NameError:
    basestring = str
    unicode = str


[docs]@open_file(1, mode='w') def write_dot(G, path): """Write NetworkX graph G to Graphviz dot format on path. Path can be a string or a file handle. """ P = to_pydot(G) path.write(P.to_string()) return
[docs]@open_file(0, mode='r') def read_dot(path): """Return a NetworkX :class:`MultiGraph` or :class:`MultiDiGraph` from the dot file with the passed path. If this file contains multiple graphs, only the first such graph is returned. All graphs _except_ the first are silently ignored. Parameters ---------- path : str or file Filename or file handle. Returns ------- G : MultiGraph or MultiDiGraph A :class:`MultiGraph` or :class:`MultiDiGraph`. Notes ----- Use `G = nx.Graph(read_dot(path))` to return a :class:`Graph` instead of a :class:`MultiGraph`. """ pydot = _import_pydot() data = path.read() # List of one or more "pydot.Dot" instances deserialized from this file. P_list = pydot.graph_from_dot_data(data) # Convert only the first such instance into a NetworkX graph. return from_pydot(P_list[0])
[docs]def from_pydot(P): """Return a NetworkX graph from a Pydot graph. Parameters ---------- P : Pydot graph A graph created with Pydot Returns ------- G : NetworkX multigraph A MultiGraph or MultiDiGraph. Examples -------- >>> K5 = nx.complete_graph(5) >>> A = nx.nx_pydot.to_pydot(K5) >>> G = nx.nx_pydot.from_pydot(A) # return MultiGraph # make a Graph instead of MultiGraph >>> G = nx.Graph(nx.nx_pydot.from_pydot(A)) """ if P.get_strict(None): # pydot bug: get_strict() shouldn't take argument multiedges = False else: multiedges = True if P.get_type() == 'graph': # undirected if multiedges: N = nx.MultiGraph() else: N = nx.Graph() else: if multiedges: N = nx.MultiDiGraph() else: N = nx.DiGraph() # assign defaults name = P.get_name().strip('"') if name != '': N.name = name # add nodes, attributes to N.node_attr for p in P.get_node_list(): n = p.get_name().strip('"') if n in ('node', 'graph', 'edge'): continue N.add_node(n, **p.get_attributes()) # add edges for e in P.get_edge_list(): u = e.get_source() v = e.get_destination() attr = e.get_attributes() s = [] d = [] if isinstance(u, basestring): s.append(u.strip('"')) else: for unodes in u['nodes']: s.append(unodes.strip('"')) if isinstance(v, basestring): d.append(v.strip('"')) else: for vnodes in v['nodes']: d.append(vnodes.strip('"')) for source_node in s: for destination_node in d: N.add_edge(source_node, destination_node, **attr) # add default attributes for graph, nodes, edges pattr = P.get_attributes() if pattr: N.graph['graph'] = pattr try: N.graph['node'] = P.get_node_defaults()[0] except: # IndexError,TypeError: pass # N.graph['node']={} try: N.graph['edge'] = P.get_edge_defaults()[0] except: # IndexError,TypeError: pass # N.graph['edge']={} return N
[docs]def to_pydot(N): """Return a pydot graph from a NetworkX graph N. Parameters ---------- N : NetworkX graph A graph created with NetworkX Examples -------- >>> K5 = nx.complete_graph(5) >>> P = nx.nx_pydot.to_pydot(K5) Notes ----- """ pydot = _import_pydot() # set Graphviz graph type if N.is_directed(): graph_type = 'digraph' else: graph_type = 'graph' strict = nx.number_of_selfloops(N) == 0 and not N.is_multigraph() name = N.name graph_defaults = N.graph.get('graph', {}) if name is '': P = pydot.Dot('', graph_type=graph_type, strict=strict, **graph_defaults) else: P = pydot.Dot('"%s"' % name, graph_type=graph_type, strict=strict, **graph_defaults) try: P.set_node_defaults(**N.graph['node']) except KeyError: pass try: P.set_edge_defaults(**N.graph['edge']) except KeyError: pass for n, nodedata in N.nodes(data=True): str_nodedata = dict((k, make_str(v)) for k, v in nodedata.items()) p = pydot.Node(make_str(n), **str_nodedata) P.add_node(p) if N.is_multigraph(): for u, v, key, edgedata in N.edges(data=True, keys=True): str_edgedata = dict((k, make_str(v)) for k, v in edgedata.items() if k != 'key') edge = pydot.Edge(make_str(u), make_str(v), key=make_str(key), **str_edgedata) P.add_edge(edge) else: for u, v, edgedata in N.edges(data=True): str_edgedata = dict((k, make_str(v)) for k, v in edgedata.items()) edge = pydot.Edge(make_str(u), make_str(v), **str_edgedata) P.add_edge(edge) return P
[docs]def graphviz_layout(G, prog='neato', root=None, **kwds): """Create node positions using Pydot and Graphviz. Returns a dictionary of positions keyed by node. Examples -------- >>> G = nx.complete_graph(4) >>> pos = nx.nx_pydot.graphviz_layout(G) >>> pos = nx.nx_pydot.graphviz_layout(G, prog='dot') Notes ----- This is a wrapper for pydot_layout. """ return pydot_layout(G=G, prog=prog, root=root, **kwds)
# FIXME: Document the "root" parameter. # FIXME: Why does this function accept a variadic dict of keyword arguments # (i.e., "**kwds") but fail to do anything with them? This is probably # wrong, as unrecognized keyword arguments will be silently ignored.
[docs]def pydot_layout(G, prog='neato', root=None, **kwds): """Create node positions using :mod:`pydot` and Graphviz. Parameters -------- G : Graph NetworkX graph to be laid out. prog : optional[str] Basename of the GraphViz command with which to layout this graph. Defaults to `neato`: default GraphViz command for undirected graphs. Returns -------- dict Dictionary of positions keyed by node. Examples -------- >>> G = nx.complete_graph(4) >>> pos = nx.nx_pydot.pydot_layout(G) >>> pos = nx.nx_pydot.pydot_layout(G, prog='dot') Notes ----- If you use complex node objects, they may have the same string representation and GraphViz could treat them as the same node. The layout may assign both nodes a single location. See Issue #1568 If this occurs in your case, consider relabeling the nodes just for the layout computation using something similar to: H = nx.convert_node_labels_to_integers(G, label_attribute='node_label') H_layout = nx.nx_pydot.pydot_layout(G, prog='dot') G_layout = {H.nodes[n]['node_label']: p for n, p in H_layout.items()} """ pydot = _import_pydot() P = to_pydot(G) if root is not None: P.set("root", make_str(root)) # List of low-level bytes comprising a string in the dot language converted # from the passed graph with the passed external GraphViz command. D_bytes = P.create_dot(prog=prog) # Unique string decoded from these bytes with the preferred locale encoding D = unicode(D_bytes, encoding=getpreferredencoding()) if D == "": # no data returned print("Graphviz layout with %s failed" % (prog)) print() print("To debug what happened try:") print("P = nx.nx_pydot.to_pydot(G)") print("P.write_dot(\"file.dot\")") print("And then run %s on file.dot" % (prog)) return # List of one or more "pydot.Dot" instances deserialized from this string. Q_list = pydot.graph_from_dot_data(D) assert len(Q_list) == 1 # The first and only such instance, as guaranteed by the above assertion. Q = Q_list[0] node_pos = {} for n in G.nodes(): pydot_node = pydot.Node(make_str(n)).get_name() node = Q.get_node(pydot_node) if isinstance(node, list): node = node[0] pos = node.get_pos()[1:-1] # strip leading and trailing double quotes if pos is not None: xx, yy = pos.split(",") node_pos[n] = (float(xx), float(yy)) return node_pos
def _import_pydot(): ''' Import and return the `pydot` module if the currently installed version of this module satisfies NetworkX requirements _or_ raise an exception. Returns -------- :mod:`pydot` Imported `pydot` module object. Raises -------- ImportError If the `pydot` module is either unimportable _or_ importable but of insufficient version. ''' import pydot # If the currently installed version of pydot is older than this minimum, # raise an exception. The pkg_resources.parse_version() function bundled # with setuptools is commonly regarded to be the most robust means of # comparing version strings. (Your mileage may vary.) if parse_version(pydot.__version__) < parse_version(PYDOT_VERSION_MIN): raise ImportError( 'pydot %s < %s' % (pydot.__version__, PYDOT_VERSION_MIN)) return pydot # fixture for nose tests def setup_module(module): from nose import SkipTest try: return _import_pydot() except ImportError: raise SkipTest("pydot not available")