Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Drawing with pydot/Graphviz and inline IPython displays #812

Merged
merged 20 commits into from

2 participants

@chebee7i
Owner

NetworkX is primarily a graph analysis tool and not a graph visualization tool. However, people (myself included) continue to use NetworkX's graph drawing features. There have been past proposals on how we might be able to provide "nicer" looking drawings (esp for directed graphs), but progress has been slow.

Graphviz is ubiquitous and provides a quick, flexible visualization solution. The idea is to draw graphs using Graphviz and then display them using the OS's default viewer. This is a "non-Python" solution, but it works. We already have nx.view_pygraphviz(), but I suspect its rarely used. And Pygraphviz on Windows is problematic enough.

In this pull request, I've revamped the pydot conversions and added a new function: nx.draw_pydot(). This function converts NetworkX graphs to the pydot graph, draws the graphs externally using graphviz, and then displays the graph. Additionally, one can display the graphs inline within an IPython Notebook.

To facilitate the various ways one might want to display a graph, I've added a rcParams-like option to NetworkX. It is accessible via networkx.nxParams. For pydot displays, you can set the 'pydot_show' param to 'external', 'ipynb', or 'none'. It defaults to 'external', but when using an IPython notebook, you will want to set it to 'ipynb'. Then, every call to draw_pydot() will display inline.

My hope is that this quick and dirty solution will serve as a nice band-aid until we get something more Pythonic.

pydotdraw

@hagberg hagberg was assigned
@chebee7i
Owner

Hey all, I was wondering if we could get a second set of eyes on this. This has been a pretty useful feature for me, and I use it regularly now---the IPython notebook user-base has been growing steadily, so I suspect inline drawing (via graphviz+pydot) will be desired by others as well. It would be neat to get in before SciPy this year.

To use this drawing feature, the user is required to have pydot installed. Note, pydot is a single module package. What are the thoughts on shipping it with NetworkX? It uses an MIT license, which is compatible.

@chebee7i
Owner

I incorporated changes introduced in #905.

@hagberg
Owner

I finally had a chance to look at this. I am ambivalent about adding this code.

If you want to help maintain this in NetworkX then let's go for it.

On the positive side it is obviously a great idea and good code and should be available for people to use. On the negative side it requires os-specific stuff with some trickiness around files that I prefer not to include since it is a pain to maintain and debug. Also this would mean that the PyGraphviz/NetworkX interface will be different so they won't be interchangeable (they are supposed to be more-or-less that way now).

In general I am leaning toward moving the drawing parts of NetworkX to separate packages. So that may eventually be the recommendation for this code too.

@chebee7i
Owner

Definitely understand the concerns. I'm happy to maintain it. So if you think the drawing code will remain in NetworkX at least for a while, let's go ahead and merge.

If we might pull out the drawing-related code in the near future, then let's close the request instead. I could always make this a separate module. nxpd.draw(G) for "NetworkX PyDot". Doubt I'd put it on PyPI, but I'd probably make it installable and put it in a repo my github account.

@hagberg
Owner

Moving the drawing code probably won't happen before the end of the year.

@chebee7i chebee7i merged commit e736d4b into networkx:master
@chebee7i chebee7i referenced this pull request from a commit in chebee7i/networkx
@chebee7i chebee7i Revert "Merge pull request #812 from chebee7i/pydot". Addresses #812.
This reverts commit e736d4b, reversing
changes made to ceb31d0.
cfda4a1
@chebee7i chebee7i referenced this pull request from a commit in chebee7i/networkx
@chebee7i chebee7i Revert 'Revert "Merge pull request #812 from chebee7i/pydot". Addresses
#812.'

This reverts commit cfda4a1.
44a4799
@chebee7i
Owner

TLDR: The pull request was reverted and there aren't any plans to re-merge it. Look for something separate in the near future.

I reverted the merge for a few reasons....

This pull request had an older version of .travis.yml, which did not install pydot and so its unit tests were skipped. Once merged with master, it was clear that there was a problem with how pydot was writing its DOT files. This was not detected originally because I was using a newer version of pydot (which did not have the problem). The version being installed by .travis.yml on master was 1.0.2 and the fix for the writing problem appeared in pydot 1.0.28 (https://code.google.com/p/pydot/issues/detail?id=61)

It's straight-forward enough to fix .travis.yml to install via pip instead of the OS's package manager, but then there is another conflict: If pyparsing is newer than 2.0.0, then pydot will be broken (no matter what version) due to deprecated functions in pyparsing. None of this addresses another reality: pydot doesn't support Python 3.0 (see #933). There is a unofficial fork of pydot that seems to address all of these issues, but it changes the layout of pydot in subtle ways which requires further editing to NetworkX.

All of these points combined make the whole pull request a bit too messy for my tastes. Given that we are considering pulling drawing related functionality, it might be cleaner to include the proper pydot and pyparsing as part of some separate NetworkX+Pydot drawing package. I have pushed a branch "pydot_fix" to my fork: https://github.com/chebee7i/networkx, which includes a check for pydot 1.0.28 and properly handles the new layout of pydot from the 2/3 version. It works just fine with the unofficial pydot and pyparsing >2.0.0. It's up there until I make this more convenient.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 8, 2013
  1. @chebee7i
  2. @chebee7i
  3. @chebee7i
  4. @chebee7i
  5. @chebee7i
  6. @chebee7i
  7. @chebee7i
Commits on Apr 24, 2013
  1. @chebee7i
  2. @chebee7i
  3. @chebee7i
Commits on Jul 10, 2013
  1. @chebee7i
Commits on Jul 11, 2013
  1. @chebee7i
Commits on Jul 20, 2013
  1. @hagberg
  2. @hagberg
  3. @hagberg

    Adjust travis-ci config

    hagberg authored
  4. @hagberg
  5. @hagberg
  6. @hagberg
  7. @chebee7i

    Merge branch 'pydot' into pydot2

    chebee7i authored
    Incorporating changes introduced in #905.
    
    Conflicts:
    	networkx/drawing/nx_pydot.py
  8. @chebee7i
This page is out of date. Refresh to see the latest.
View
7 .travis.yml
@@ -5,7 +5,12 @@ python:
- "3.2"
- "3.3"
- "pypy"
+before_install:
+# - pip install numpy
+# - pip install pyparsing
+# - pip install pydot
+ - pip install pyyaml
# command to install dependencies
# install: "pip install"
# command to run tests
-script: nosetests -v
+script: nosetests -vv
View
1  doc/source/reference/drawing.rst
@@ -51,6 +51,7 @@ Graphviz with pydot
read_dot
graphviz_layout
pydot_layout
+ draw_pydot
Graph Layout
View
37 doc/source/tutorial/tutorial.rst
@@ -465,5 +465,40 @@ and PyGraphviz, or pydot, are available on your system, you can also use
>>> nx.draw_graphviz(G)
>>> nx.write_dot(G,'file.dot')
-Details on drawing graphs: :doc:`/reference/drawing`
+Drawing graphs with Pydot/Graphviz
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If `pydot <http://code.google.com/p/pydot/>`_ and
+`Graphviz <http://www.graphviz.org/>`_ are both installed, then graphs can
+be 1) converted from NetworkX to the pydot format, 2) drawn externally using
+Graphviz, and 3) displayed using the operating system's default graphics
+viewer.
+
+The example below constructs a NetworkX graph whose attributes are compatible
+with the Graphviz DOT language and then displays the resulting PNG.
+Unsupported attributes in NetworkX graphs are silently ignored during the
+conversion to a pydot graph. Other output formats are also possible.
+For details, consult the function's docstring and the Graphviz documentation.
+
+>>> G = nx.DiGraph()
+>>> G.graph['rankdir'] = 'LR'
+>>> G.graph['dpi'] = 120
+>>> G.add_cycle(range(4))
+>>> G.add_node(0, color='red', style='filled', fillcolor='pink')
+>>> G.add_node(1, shape='square')
+>>> G.add_edge(0, 1, color='red', style='dashed')
+>>> G.add_edge(3, 3, label='a')
+>>> nx.draw_pydot(G)
+
+If you are working within an IPython Notebook, then this same function can
+display the graph inline:
+
+>>> nx.draw_pydot(G, show='ipynb')
+
+To make all graphs displayed inline, a global default value can be modified
+(e.g., in the startup file for the profile):
+
+>>> nx.nxParams['pydot_show'] = 'ipynb'
+
+For other details on drawing graphs: :doc:`/reference/drawing`
View
2  networkx/__init__.py
@@ -50,6 +50,8 @@
__date__ = release.date
__version__ = release.version
+from networkx.params import nxParams, reset_params
+
#These are import orderwise
from networkx.exception import *
import networkx.external
View
351 networkx/drawing/nx_pydot.py
@@ -10,8 +10,9 @@
See Also
--------
Pydot: http://code.google.com/p/pydot/
-Graphviz: http://www.research.att.com/sw/tools/graphviz/
-DOT Language: http://www.graphviz.org/doc/info/lang.html
+Graphviz: http://www.research.att.com/sw/tools/graphviz/
+DOT Language: http://www.graphviz.org/doc/info/lang.html
+
"""
# Copyright (C) 2004-2013 by
# Aric Hagberg <hagberg@lanl.gov>
@@ -19,28 +20,32 @@
# Pieter Swart <swart@lanl.gov>
# All rights reserved.
# BSD license.
-from networkx.utils import open_file, make_str
+
+import os
+import sys
+import tempfile
+import time
+
+from networkx.utils import (
+ open_file, get_fobj, make_str, default_opener
+)
import networkx as nx
+
__author__ = """Aric Hagberg (aric.hagberg@gmail.com)"""
__all__ = ['write_dot', 'read_dot', 'graphviz_layout', 'pydot_layout',
- 'to_pydot', 'from_pydot']
+ 'to_pydot', 'from_pydot', 'draw_pydot']
-@open_file(1,mode='w')
-def write_dot(G,path):
+@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.
"""
- try:
- import pydot
- except ImportError:
- raise ImportError("write_dot() requires pydot",
- "http://code.google.com/p/pydot/")
P=to_pydot(G)
path.write(P.to_string())
return
-@open_file(0,mode='r')
+@open_file(0, mode='r')
def read_dot(path):
"""Return a NetworkX MultiGraph or MultiDiGraph from a dot file on path.
@@ -134,85 +139,126 @@ def from_pydot(P):
N.graph['edge']={}
return N
-def to_pydot(N, strict=True):
- """Return a pydot graph from a NetworkX graph N.
+def filter_attrs(attrs, attr_type):
+ """
+ Helper function to keep only pydot supported attributes.
+
+ All unsupported attributes are filtered out.
Parameters
----------
- N : NetworkX graph
- A graph created with NetworkX
+ attrs : dict
+ A dictionary of attributes.
+ attr_type : str
+ The type of attributes. Must be 'edge', 'graph', or 'node'.
+
+ Returns
+ -------
+ d : dict
+ The filtered attributes.
+
+ """
+ import pydot
+
+ if attr_type == 'edge':
+ accepted = pydot.EDGE_ATTRIBUTES
+ elif attr_type == 'graph':
+ accepted = pydot.GRAPH_ATTRIBUTES
+ elif attr_type == 'node':
+ accepted = pydot.NODE_ATTRIBUTES
+ else:
+ raise Exception("Invalid attr_type.")
+
+ d = dict( [(k,v) for (k,v) in attrs.items() if k in accepted] )
+ return d
+
+def to_pydot(G, raise_exceptions=True):
+ """Return a pydot graph from a NetworkX graph G.
+
+ All node names are converted to strings. However, no preprocessing is
+ performed on the edge/graph/node attribute values since some attributes
+ need to be strings while other need to be floats. If pydot does not handle
+ needed conversions, then your graph should be modified beforehand.
+
+ Generally, the rule is: If the attribute is a supported Graphviz
+ attribute, then it will be added to the Pydot graph (and thus, assumed to
+ be in the proper format for Graphviz).
+
+ Parameters
+ ----------
+ G : NetworkX graph
+ A graph created with NetworkX.
+ raise_exceptions : bool
+ If `True`, raise any exceptions. Otherwise, the exception is ignored
+ and the procedure continues.
Examples
--------
- >>> K5=nx.complete_graph(5)
- >>> P=nx.to_pydot(K5)
-
- Notes
- -----
+ >>> G = nx.complete_graph(5)
+ >>> G.add_edge(2, 10, color='red')
+ >>> P = nx.to_pydot(G)
"""
- try:
- import pydot
- except ImportError:
- raise ImportError('to_pydot() requires pydot: '
- 'http://code.google.com/p/pydot/')
+ import pydot
- # set Graphviz graph type
- if N.is_directed():
- graph_type='digraph'
+ # Set Graphviz graph type.
+ if G.is_directed():
+ graph_type = 'digraph'
else:
- graph_type='graph'
- strict=N.number_of_selfloops()==0 and not N.is_multigraph()
+ graph_type = 'graph'
- name = N.graph.get('name')
- graph_defaults=N.graph.get('graph',{})
+ strict = G.number_of_selfloops() == 0 and not G.is_multigraph()
+
+ # Create the Pydot graph.
+ name = G.graph.get('name')
+ graph_defaults = filter_attrs(G.graph, 'graph')
if name is None:
- P = pydot.Dot(graph_type=graph_type,strict=strict,**graph_defaults)
+ P = pydot.Dot(graph_type=graph_type, strict=strict, **graph_defaults)
else:
- P = pydot.Dot('"%s"'%name,graph_type=graph_type,strict=strict,
+ P = pydot.Dot(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_iter(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_iter(data=True,keys=True):
- str_edgedata=dict((k,make_str(v)) for k,v in edgedata.items())
- edge=pydot.Edge(make_str(u),make_str(v),key=make_str(key),**str_edgedata)
- P.add_edge(edge)
+ # Set default node attributes, if possible.
+ node_defaults = filter_attrs(G.graph.get('node', {}), 'node')
+ if node_defaults:
+ try:
+ P.set_node_defaults(**node_defaults)
+ except:
+ if raise_exceptions:
+ raise
+
+ # Set default edge attributes, if possible.
+ edge_defaults = filter_attrs(G.graph.get('edge', {}), 'edge')
+ if edge_defaults:
+ # This adds a node called "edge" to the graph.
+ try:
+ P.set_edge_defaults(**edge_defaults)
+ except:
+ if raise_exceptions:
+ raise
+
+ # Add the nodes.
+ for n,nodedata in G.nodes_iter(data=True):
+ attrs = filter_attrs(nodedata, 'node')
+ node = pydot.Node(make_str(n), **attrs)
+ P.add_node(node)
+
+ # Add the edges.
+ if G.is_multigraph():
+ for u,v,key,edgedata in G.edges_iter(data=True,keys=True):
+ attrs = filter_attrs(edgedata, 'edge')
+ uu, vv, kk = make_str(u), make_str(v), make_str(key)
+ edge = pydot.Edge(uu, vv, key=kk, **attrs)
+ P.add_edge(edge)
else:
- for u,v,edgedata in N.edges_iter(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)
+ for u,v,edgedata in G.edges_iter(data=True):
+ attrs = filter_attrs(edgedata, 'edge')
+ uu, vv = make_str(u), make_str(v)
+ edge = pydot.Edge(uu, vv, **attrs)
P.add_edge(edge)
return P
-
-def pydot_from_networkx(N):
- """Create a Pydot graph from a NetworkX graph."""
- from warnings import warn
- warn('pydot_from_networkx is replaced by to_pydot', DeprecationWarning)
- return to_pydot(N)
-
-def networkx_from_pydot(D, create_using=None):
- """Create a NetworkX graph from a Pydot graph."""
- from warnings import warn
- warn('networkx_from_pydot is replaced by from_pydot',
- DeprecationWarning)
- return from_pydot(D)
-
-def graphviz_layout(G,prog='neato',root=None, **kwds):
+def graphviz_layout(G, prog='neato', root=None, **kwds):
"""Create node positions using Pydot and Graphviz.
Returns a dictionary of positions keyed by node.
@@ -230,7 +276,7 @@ def graphviz_layout(G,prog='neato',root=None, **kwds):
return pydot_layout(G=G,prog=prog,root=root,**kwds)
-def pydot_layout(G,prog='neato',root=None, **kwds):
+def pydot_layout(G, prog='neato', root=None, **kwds):
"""Create node positions using Pydot and Graphviz.
Returns a dictionary of positions keyed by node.
@@ -240,6 +286,7 @@ def pydot_layout(G,prog='neato',root=None, **kwds):
>>> G=nx.complete_graph(4)
>>> pos=nx.pydot_layout(G)
>>> pos=nx.pydot_layout(G,prog='dot')
+
"""
try:
import pydot
@@ -277,6 +324,164 @@ def pydot_layout(G,prog='neato',root=None, **kwds):
node_pos[n]=(float(xx),float(yy))
return node_pos
+
+def safer_pydot_write(self, path, prog=None, format='raw'):
+ """
+ pydot.Dot.write() is not safe to use with temporary files since it
+ requires a string be passed in for the filename. We provide a modified
+ version here.
+
+ This was needed in Pydot 1.0.28.
+
+ """
+ if prog is None:
+ prog = self.prog
+
+ fobj, close = get_fobj(path, 'w+b')
+ try:
+ if format == 'raw':
+ data = self.to_string()
+ if isinstance(data, basestring):
+ if not isinstance(data, unicode):
+ try:
+ data = unicode(data, 'utf-8')
+ except:
+ pass
+
+ try:
+ data = data.encode('utf-8')
+ except:
+ pass
+ fobj.write(data)
+ else:
+ fobj.write(self.create(prog, format))
+ finally:
+ if close:
+ fobj.close()
+
+ return True
+
+
+def draw_pydot(G, filename=None, format=None, prefix=None, suffix=None,
+ layout='dot', args=None, show=None):
+ """Draws the graph G using pydot and graphviz.
+
+ Parameters
+ ----------
+ G : graph
+ A NetworkX graph object (e.g., Graph, DiGraph).
+
+ filename : str, None, file object
+ The name of the file to save the image to. If None, save to a
+ temporary file with the name:
+ nx_PREFIX_RANDOMSTRING_SUFFIX.ext.
+ File formats are inferred from the extension of the filename, when
+ provided. If the `format` parameter is not `None`, it overwrites any
+ inferred value for the extension.
+
+ format : str
+ An output format. Note that not all may be available on every system
+ depending on how Graphviz was built. If no filename is provided and
+ no format is specified, then a 'png' image is created. Other values
+ for `format` are:
+
+ 'canon', 'cmap', 'cmapx', 'cmapx_np', 'dia', 'dot',
+ 'fig', 'gd', 'gd2', 'gif', 'hpgl', 'imap', 'imap_np',
+ 'ismap', 'jpe', 'jpeg', 'jpg', 'mif', 'mp', 'pcl', 'pdf',
+ 'pic', 'plain', 'plain-ext', 'png', 'ps', 'ps2', 'svg',
+ 'svgz', 'vml', 'vmlz', 'vrml', 'vtx', 'wbmp', 'xdot', 'xlib'
+
+ prefix : str | None
+ If `filename` is None, we save to a temporary file. The value of
+ `prefix` will appear after 'nx_' but before random string
+ and file extension. If None, then the graph name will be used.
+
+ suffix : str | None
+ If `filename` is None, we save to a temporary file. The value of
+ `suffix` will appear at after the prefix and random string but before
+ the file extension. If None, then no suffix is used.
+
+ layout : str
+ The graphviz layout program. Pydot is responsible for locating the
+ binary. Common values for the layout program are:
+ 'neato','dot','twopi','circo','fdp','nop', 'wc','acyclic','gvpr',
+ 'gvcolor','ccomps','sccmap','tred'
+
+ args : list
+ Additional arguments to pass to the Graphviz layout program.
+ This should be a list of strings. For example, ['-s10', '-maxiter=10'].
+
+ show : bool
+ If `True`, then the image is displayed using the OS's default viewer
+ after drawing it. If show equals 'ipynb', then the image is displayed
+ inline for an IPython notebook. If `None`, then the value of
+ nxParams['pydot_show'] is used. By default, it is set to `True`.
+
+ """
+ # Determine the output format
+ if format is None:
+ # grab extension from filename
+ if filename is None:
+ # default to png
+ ext = 'png'
+ else:
+ ext = os.path.splitext(filename)[-1].lower()[1:]
+ else:
+ ext = format
+
+ # Determine the "path" to be passed to pydot.Dot.write()
+ if filename is None:
+ if prefix is None:
+ prefix = G.graph.get("name", '')
+
+ if prefix:
+ fn_prefix = "nx_{0}_".format(prefix)
+ else:
+ fn_prefix = "nx_"
+
+ if suffix:
+ fn_suffix = '_{0}.{1}'.format(suffix, ext)
+ else:
+ fn_suffix = '.{0}'.format(ext)
+
+ fobj = tempfile.NamedTemporaryFile(prefix=fn_prefix,
+ suffix=fn_suffix,
+ delete=False)
+ fname = fobj.name
+ close = True
+ else:
+ fobj, close = get_fobj(filename, 'w+b')
+ fname = fobj.name
+
+ # Include additional command line arguments to the layout program.
+ if args is None:
+ args = []
+ prog = layout
+ else:
+ args = list(args)
+ prog = [layout] + args
+
+ # Draw the image.
+ G2 = to_pydot(G)
+ safer_pydot_write(G2, fobj, prog=prog, format=ext)
+ if close:
+ fobj.close()
+
+ if show is None:
+ show = nx.nxParams['pydot_show']
+
+ if show:
+ if show == 'ipynb':
+ from IPython.core.display import Image
+ return Image(filename=fname, embed=True)
+ else:
+ default_opener(fname)
+ if sys.platform == 'linux2':
+ # necessary when opening many images in a row
+ time.sleep(.5)
+
+ return fname
+
# fixture for nose tests
def setup_module(module):
from nose import SkipTest
View
4 networkx/drawing/tests/test_pydot.py
@@ -1,3 +1,4 @@
+
"""
Unit tests for pydot drawing functions.
"""
@@ -25,6 +26,9 @@ def build_graph(self, G):
G.add_edge('B','C')
G.add_edge('A','D')
G.add_node('E')
+ G.graph['node'] = {'style':'filled'}
+ G.graph['name'] = 'demo'
+ G.graph['edge'] = {'color':'red'}
return G, nx.to_pydot(G)
def assert_equal(self, G1, G2):
View
139 networkx/params.py
@@ -0,0 +1,139 @@
+"""
+Defines NetworkX configuration parameters.
+
+Eventually we could support some sort of config file. For now, it can be
+updated via the dictionary API only.
+
+"""
+
+import warnings
+
+__all__ = ['nxParams', 'reset_params']
+
+### Generic validations
+
+def validate_boolean(b):
+ """Convert b to a boolean or raise a ValueError."""
+ try:
+ b = b.lower()
+ except AttributeError:
+ pass
+ if b in ('t', 'y', 'yes', 'on', 'true', '1', 1, True): return True
+ elif b in ('f', 'n', 'no', 'off', 'false', '0', 0, False): return False
+ else:
+ raise ValueError('Could not convert {0!r} to boolean'.format(b))
+
+def validate_float(s):
+ """Convert s to float or raise a ValueError."""
+ try:
+ return float(s)
+ except ValueError:
+ raise ValueError('Could not convert {0!r} to float'.format(s))
+
+def validate_choice(s, choices):
+ try:
+ s = s.lower()
+ except AttributeError:
+ pass
+ if s not in choices:
+ raise ValueError("{0!r} is an invalid specification.".format(s))
+ else:
+ return s
+
+### Specific validations
+
+def validate_pydot_show(s):
+ choices = ['ipynb', 'external', 'none']
+ return validate_choice(s, choices)
+
+
+### The main parameter class
+
+class NetworkXParams(dict):
+ """
+ A dictionary including validation, representing NetworkX parameters.
+
+ """
+
+ def __init__(self, *args, **kwargs):
+ # A dictionary relating params to validators.
+ self.validate = dict([(key, converter) for key, (default, converter) \
+ in defaultParams.items()])
+
+ dict.__init__(self, *args, **kwargs)
+
+ def _deprecation_check(self, param):
+ """
+ Raise warning if param is deprecated.
+
+ Return the param to use. This is the alternative parameter if available,
+ otherwise it is the original parameter.
+
+ """
+ if param in deprecatedParams:
+ alt = deprecatedParams[param]
+ if alt is None:
+ msg = "{0!r} is deprecated. There is no replacement."
+ msg = msg.format(key)
+ else:
+ msg = "{0!r} is deprecated. Use {1!r} instead."
+ msg = msg.format(key, alt)
+ param = alt
+
+ warnings.warn(msg, DeprecationWarning, stacklevel=2)
+
+ if param not in self.validate:
+ msg = '{0!r} is not a valid NetworkX parameter. '.format(key)
+ msg += 'See nxParams.keys() for a list of valid parameters.'
+ raise KeyError(msg)
+
+ return param
+
+ def __setitem__(self, key, val):
+ key = self._deprecation_check(key)
+ cval = self.validate[key](val)
+ dict.__setitem__(self, key, cval)
+
+ def __getitem__(self, key):
+ key = self._deprecation_check(key)
+ return dict.__getitem__(self, key)
+
+def reset_params():
+ """
+ Restore rcParams to defaults for NetworkX.
+
+ """
+ # This modifies the global parameters.
+ nxParams.update(nxParamsDefault)
+
+def set_params(filename=None):
+ """
+ Return the default params, after updating from the .nxrc file.
+
+ """
+ # Currently, we don't support a config file.
+ # For now, just return a NetworkXParams instance with the default values.
+ ret = NetworkXParams([ (key, tup[0]) for key, tup in defaultParams.items()])
+ return ret
+
+
+### Globals
+
+
+
+### TODO: key -> (value, validator, info_string)
+defaultParams = {
+ # parameter : (default value, validator)
+ 'pydot_show': ('external', validate_pydot_show),
+}
+
+### Dictionary relating deprecated parameter names to new names.
+deprecatedParams = {
+ # old parameter : new parameter, (use None if no new parameter)
+}
+
+### This is what will be used by NetworkX modules.
+nxParams = set_params()
+nxParamsDefault = NetworkXParams([(key, tup[0]) \
+ for key, tup in defaultParams.items()])
+
View
38 networkx/utils/misc.py
@@ -97,7 +97,7 @@ def default_opener(filename):
"""
cmds = {'darwin': ['open'],
'linux2': ['xdg-open'],
- 'win32': ['cmd.exe', '/C', 'start', '']}
+ 'win32': ['cmd.exe', '/c', 'start', '']}
cmd = cmds[sys.platform] + [filename]
subprocess.call(cmd)
@@ -149,3 +149,39 @@ def dict_to_numpy_array1(d,mapping=None):
i = mapping[k1]
a[i] = value
return a
+
+def get_fobj(fname, mode='w+'):
+ """Obtain a proper file object.
+
+ Parameters
+ ----------
+ fname : string, file object, file descriptor
+ If a string or file descriptor, then we create a file object. If *fname*
+ is a file object, then we do nothing and ignore the specified *mode*
+ parameter.
+ mode : str
+ The mode of the file to be opened.
+
+ Returns
+ -------
+ fobj : file object
+ The file object.
+ close : bool
+ If *fname* was a string, then *close* will be *True* to signify that
+ the file object should be closed after writing to it. Otherwise, *close*
+ will be *False* signifying that the user, in essence, created the file
+ object already and that subsequent operations should not close it.
+
+ """
+ if is_string_like(fname):
+ fobj = open(fname, mode)
+ close = True
+ elif hasattr(fname, 'write'):
+ # fname is a file-like object, perhaps a StringIO (for example)
+ fobj = fname
+ close = False
+ else:
+ # assume it is a file descriptor
+ fobj = os.fdopen(fname, mode)
+ close = False
+ return fobj, close
Something went wrong with that request. Please try again.