blob: ff61253003f75e7eca060f9d5e8e755c10958fa8 [file] [log] [blame]
"""
***************
Graphviz AGraph
***************
Interface to pygraphviz AGraph class.
Examples
--------
>>> G=nx.complete_graph(5)
>>> A=nx.to_agraph(G)
>>> H=nx.from_agraph(A)
See Also
--------
Pygraphviz: http://networkx.lanl.gov/pygraphviz
"""
# Copyright (C) 2004-2012 by
# Aric Hagberg <hagberg@lanl.gov>
# Dan Schult <dschult@colgate.edu>
# Pieter Swart <swart@lanl.gov>
# All rights reserved.
# BSD license.
import os
import sys
import tempfile
import networkx as nx
__author__ = """Aric Hagberg (hagberg@lanl.gov)"""
__all__ = ['from_agraph', 'to_agraph',
'write_dot', 'read_dot',
'graphviz_layout',
'pygraphviz_layout',
'view_pygraphviz']
def from_agraph(A,create_using=None):
"""Return a NetworkX Graph or DiGraph from a PyGraphviz graph.
Parameters
----------
A : PyGraphviz AGraph
A graph created with PyGraphviz
create_using : NetworkX graph class instance
The output is created using the given graph class instance
Examples
--------
>>> K5=nx.complete_graph(5)
>>> A=nx.to_agraph(K5)
>>> G=nx.from_agraph(A)
>>> G=nx.from_agraph(A)
Notes
-----
The Graph G will have a dictionary G.graph_attr containing
the default graphviz attributes for graphs, nodes and edges.
Default node attributes will be in the dictionary G.node_attr
which is keyed by node.
Edge attributes will be returned as edge data in G. With
edge_attr=False the edge data will be the Graphviz edge weight
attribute or the value 1 if no edge weight attribute is found.
"""
if create_using is None:
if A.is_directed():
if A.is_strict():
create_using=nx.DiGraph()
else:
create_using=nx.MultiDiGraph()
else:
if A.is_strict():
create_using=nx.Graph()
else:
create_using=nx.MultiGraph()
# assign defaults
N=nx.empty_graph(0,create_using)
N.name=''
if A.name is not None:
N.name=A.name
# add nodes, attributes to N.node_attr
for n in A.nodes():
str_attr=dict((str(k),v) for k,v in n.attr.items())
N.add_node(str(n),**str_attr)
# add edges, assign edge data as dictionary of attributes
for e in A.edges():
u,v=str(e[0]),str(e[1])
attr=dict(e.attr)
str_attr=dict((str(k),v) for k,v in attr.items())
if not N.is_multigraph():
if e.name is not None:
str_attr['key']=e.name
N.add_edge(u,v,**str_attr)
else:
N.add_edge(u,v,key=e.name,**str_attr)
# add default attributes for graph, nodes, and edges
# hang them on N.graph_attr
N.graph['graph']=dict(A.graph_attr)
N.graph['node']=dict(A.node_attr)
N.graph['edge']=dict(A.edge_attr)
return N
def to_agraph(N):
"""Return a pygraphviz graph from a NetworkX graph N.
Parameters
----------
N : NetworkX graph
A graph created with NetworkX
Examples
--------
>>> K5=nx.complete_graph(5)
>>> A=nx.to_agraph(K5)
Notes
-----
If N has an dict N.graph_attr an attempt will be made first
to copy properties attached to the graph (see from_agraph)
and then updated with the calling arguments if any.
"""
try:
import pygraphviz
except ImportError:
raise ImportError('requires pygraphviz ',
'http://networkx.lanl.gov/pygraphviz ',
'(not available for Python3)')
directed=N.is_directed()
strict=N.number_of_selfloops()==0 and not N.is_multigraph()
A=pygraphviz.AGraph(name=N.name,strict=strict,directed=directed)
# default graph attributes
A.graph_attr.update(N.graph.get('graph',{}))
A.node_attr.update(N.graph.get('node',{}))
A.edge_attr.update(N.graph.get('edge',{}))
# add nodes
for n,nodedata in N.nodes(data=True):
A.add_node(n,**nodedata)
# loop over edges
if N.is_multigraph():
for u,v,key,edgedata in N.edges_iter(data=True,keys=True):
str_edgedata=dict((k,str(v)) for k,v in edgedata.items())
A.add_edge(u,v,key=str(key),**str_edgedata)
else:
for u,v,edgedata in N.edges_iter(data=True):
str_edgedata=dict((k,str(v)) for k,v in edgedata.items())
A.add_edge(u,v,**str_edgedata)
return A
def write_dot(G,path):
"""Write NetworkX graph G to Graphviz dot format on path.
Parameters
----------
G : graph
A networkx graph
path : filename
Filename or file handle to write
"""
try:
import pygraphviz
except ImportError:
raise ImportError('requires pygraphviz ',
'http://networkx.lanl.gov/pygraphviz ',
'(not available for Python3)')
A=to_agraph(G)
A.write(path)
A.clear()
return
def read_dot(path):
"""Return a NetworkX graph from a dot file on path.
Parameters
----------
path : file or string
File name or file handle to read.
"""
try:
import pygraphviz
except ImportError:
raise ImportError('read_dot() requires pygraphviz ',
'http://networkx.lanl.gov/pygraphviz ',
'(not available for Python3)')
A=pygraphviz.AGraph(file=path)
return from_agraph(A)
def graphviz_layout(G,prog='neato',root=None, args=''):
"""Create node positions for G using Graphviz.
Parameters
----------
G : NetworkX graph
A graph created with NetworkX
prog : string
Name of Graphviz layout program
root : string, optional
Root node for twopi layout
args : string, optional
Extra arguments to Graphviz layout program
Returns : dictionary
Dictionary of x,y, positions keyed by node.
Examples
--------
>>> G=nx.petersen_graph()
>>> pos=nx.graphviz_layout(G)
>>> pos=nx.graphviz_layout(G,prog='dot')
Notes
-----
This is a wrapper for pygraphviz_layout.
"""
return pygraphviz_layout(G,prog=prog,root=root,args=args)
def pygraphviz_layout(G,prog='neato',root=None, args=''):
"""Create node positions for G using Graphviz.
Parameters
----------
G : NetworkX graph
A graph created with NetworkX
prog : string
Name of Graphviz layout program
root : string, optional
Root node for twopi layout
args : string, optional
Extra arguments to Graphviz layout program
Returns : dictionary
Dictionary of x,y, positions keyed by node.
Examples
--------
>>> G=nx.petersen_graph()
>>> pos=nx.graphviz_layout(G)
>>> pos=nx.graphviz_layout(G,prog='dot')
"""
try:
import pygraphviz
except ImportError:
raise ImportError('requires pygraphviz ',
'http://networkx.lanl.gov/pygraphviz ',
'(not available for Python3)')
if root is not None:
args+="-Groot=%s"%root
A=to_agraph(G)
A.layout(prog=prog,args=args)
node_pos={}
for n in G:
node=pygraphviz.Node(A,n)
try:
xx,yy=node.attr["pos"].split(',')
node_pos[n]=(float(xx),float(yy))
except:
print("no position for node",n)
node_pos[n]=(0.0,0.0)
return node_pos
@nx.utils.open_file(5, 'w')
def view_pygraphviz(G, edgelabel=None, prog='dot', args='',
suffix='', path=None):
"""Views the graph G using the specified layout algorithm.
Parameters
----------
G : NetworkX graph
The machine to draw.
edgelabel : str, callable, None
If a string, then it specifes the edge attribute to be displayed
on the edge labels. If a callable, then it is called for each
edge and it should return the string to be displayed on the edges.
The function signature of `edgelabel` should be edgelabel(data),
where `data` is the edge attribute dictionary.
prog : string
Name of Graphviz layout program.
args : str
Additional arguments to pass to the Graphviz layout program.
suffix : str
If `filename` is None, we save to a temporary file. The value of
`suffix` will appear at the tail end of the temporary filename.
path : str, None
The filename used to save the image. If None, save to a temporary
file. File formats are the same as those from pygraphviz.agraph.draw.
Returns
-------
path : str
The filename of the generated image.
A : PyGraphviz graph
The PyGraphviz graph instance used to generate the image.
Notes
-----
If this function is called in succession too quickly, sometimes the
image is not displayed. So you might consider time.sleep(.5) between
calls if you experience problems.
"""
if not len(G):
raise nx.NetworkXException("An empty graph cannot be drawn.")
import pygraphviz
# If we are providing default values for graphviz, these must be set
# before any nodes or edges are added to the PyGraphviz graph object.
# The reason for this is that default values only affect incoming objects.
# If you change the default values after the objects have been added,
# then they inherit no value and are set only if explicitly set.
# to_agraph() uses these values.
attrs = ['edge', 'node', 'graph']
for attr in attrs:
if attr not in G.graph:
G.graph[attr] = {}
# These are the default values.
edge_attrs = {'fontsize': '10'}
node_attrs = {'style': 'filled',
'fillcolor': '#0000FF40',
'height': '0.75',
'width': '0.75',
'shape': 'circle'}
graph_attrs = {}
def update_attrs(which, attrs):
# Update graph attributes. Return list of those which were added.
added = []
for k,v in attrs.items():
if k not in G.graph[which]:
G.graph[which][k] = v
added.append(k)
def clean_attrs(which, added):
# Remove added attributes
for attr in added:
del G.graph[which][attr]
if not G.graph[which]:
del G.graph[which]
# Update all default values
update_attrs('edge', edge_attrs)
update_attrs('node', node_attrs)
update_attrs('graph', graph_attrs)
# Convert to agraph, so we inherit default values
A = to_agraph(G)
# Remove the default values we added to the original graph.
clean_attrs('edge', edge_attrs)
clean_attrs('node', node_attrs)
clean_attrs('graph', graph_attrs)
# If the user passed in an edgelabel, we update the labels for all edges.
if edgelabel is not None:
if not hasattr(edgelabel, '__call__'):
def func(data):
return ''.join([" ", str(data[edgelabel]), " "])
else:
func = edgelabel
# update all the edge labels
if G.is_multigraph():
for u,v,key,data in G.edges_iter(keys=True, data=True):
# PyGraphviz doesn't convert the key to a string. See #339
edge = A.get_edge(u,v,str(key))
edge.attr['label'] = str(func(data))
else:
for u,v,data in G.edges_iter(data=True):
edge = A.get_edge(u,v)
edge.attr['label'] = str(func(data))
if path is None:
ext = 'png'
if suffix:
suffix = '_%s.%s' % (suffix, ext)
else:
suffix = '.%s' % (ext,)
path = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
else:
# Assume the decorator worked and it is a file-object.
pass
display_pygraphviz(A, path=path, prog=prog, args=args)
return path.name, A
def display_pygraphviz(graph, path, format=None, prog=None, args=''):
"""Internal function to display a graph in OS dependent manner.
Parameters
----------
graph : PyGraphviz graph
A PyGraphviz AGraph instance.
path : file object
An already opened file object that will be closed.
format : str, None
An attempt is made to guess the output format based on the extension
of the filename. If that fails, the value of `format` is used.
prog : string
Name of Graphviz layout program.
args : str
Additional arguments to pass to the Graphviz layout program.
Notes
-----
If this function is called in succession too quickly, sometimes the
image is not displayed. So you might consider time.sleep(.5) between
calls if you experience problems.
"""
if format is None:
filename = path.name
format = os.path.splitext(filename)[1].lower()[1:]
if not format:
# Let the draw() function use its default
format = None
# Save to a file and display in the default viewer.
# We must close the file before viewing it.
graph.draw(path, format, prog, args)
path.close()
nx.utils.default_opener(filename)
# fixture for nose tests
def setup_module(module):
from nose import SkipTest
try:
import pygraphviz
except:
raise SkipTest("pygraphviz not available")