Fixed database typo and removed unnecessary class identifier.

This commit is contained in:
Batuhan Berk Başoğlu 2020-10-14 10:10:37 -04:00
parent 00ad49a143
commit 45fb349a7d
5098 changed files with 952558 additions and 85 deletions

View file

@ -0,0 +1,6 @@
# graph drawing and interface to graphviz
from .layout import *
from .nx_pylab import *
from . import nx_agraph
from . import nx_pydot

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,483 @@
"""
***************
Graphviz AGraph
***************
Interface to pygraphviz AGraph class.
Examples
--------
>>> G = nx.complete_graph(5)
>>> A = nx.nx_agraph.to_agraph(G)
>>> H = nx.nx_agraph.from_agraph(A)
See Also
--------
Pygraphviz: http://pygraphviz.github.io/
"""
import os
import tempfile
import networkx as nx
__all__ = [
"from_agraph",
"to_agraph",
"write_dot",
"read_dot",
"graphviz_layout",
"pygraphviz_layout",
"view_pygraphviz",
]
def from_agraph(A, create_using=None):
"""Returns a NetworkX Graph or DiGraph from a PyGraphviz graph.
Parameters
----------
A : PyGraphviz AGraph
A graph created with PyGraphviz
create_using : NetworkX graph constructor, optional (default=None)
Graph type to create. If graph instance, then cleared before populated.
If `None`, then the appropriate Graph type is inferred from `A`.
Examples
--------
>>> K5 = nx.complete_graph(5)
>>> A = nx.nx_agraph.to_agraph(K5)
>>> G = nx.nx_agraph.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)
if A.name is not None:
N.name = A.name
# add graph attributes
N.graph.update(A.graph_attr)
# add nodes, attributes to N.node_attr
for n in A.nodes():
str_attr = {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 = {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):
"""Returns 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.nx_agraph.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 as e:
raise ImportError("requires pygraphviz " "http://pygraphviz.github.io/") from e
directed = N.is_directed()
strict = nx.number_of_selfloops(N) == 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", {}))
A.graph_attr.update(
(k, v) for k, v in N.graph.items() if k not in ("graph", "node", "edge")
)
# add nodes
for n, nodedata in N.nodes(data=True):
A.add_node(n)
# Add node data
a = A.get_node(n)
a.attr.update({k: str(v) for k, v in nodedata.items()})
# loop over edges
if N.is_multigraph():
for u, v, key, edgedata in N.edges(data=True, keys=True):
str_edgedata = {k: str(v) for k, v in edgedata.items() if k != "key"}
A.add_edge(u, v, key=str(key))
# Add edge data
a = A.get_edge(u, v)
a.attr.update(str_edgedata)
else:
for u, v, edgedata in N.edges(data=True):
str_edgedata = {k: str(v) for k, v in edgedata.items()}
A.add_edge(u, v)
# Add edge data
a = A.get_edge(u, v)
a.attr.update(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
"""
A = to_agraph(G)
A.write(path)
A.clear()
return
def read_dot(path):
"""Returns 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 as e:
raise ImportError(
"read_dot() requires pygraphviz " "http://pygraphviz.github.io/"
) from e
A = pygraphviz.AGraph(file=path)
gr = from_agraph(A)
A.clear()
return gr
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 of x, y, positions keyed by node.
Examples
--------
>>> G = nx.petersen_graph()
>>> pos = nx.nx_agraph.graphviz_layout(G)
>>> pos = nx.nx_agraph.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
-------
node_pos : dict
Dictionary of x, y, positions keyed by node.
Examples
--------
>>> G = nx.petersen_graph()
>>> pos = nx.nx_agraph.graphviz_layout(G)
>>> pos = nx.nx_agraph.graphviz_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_agraph.pygraphviz_layout(G, prog="dot")
>>> G_layout = {H.nodes[n]["node_label"]: p for n, p in H_layout.items()}
"""
try:
import pygraphviz
except ImportError as e:
raise ImportError("requires pygraphviz " "http://pygraphviz.github.io/") from e
if root is not None:
args += f"-Groot={root}"
A = to_agraph(G)
A.layout(prog=prog, args=args)
node_pos = {}
for n in G:
node = pygraphviz.Node(A, n)
try:
xs = node.attr["pos"].split(",")
node_pos[n] = tuple(float(x) for x in xs)
except:
print("no position for node", n)
node_pos[n] = (0.0, 0.0)
return node_pos
@nx.utils.open_file(5, "w+b")
def view_pygraphviz(
G, edgelabel=None, prog="dot", args="", suffix="", path=None, show=True
):
"""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.
show : bool, default = True
Whether to display the graph with `networkx.utils.default_opener`,
default is `True`. If `False`, the rendered graph is still available
at `path`.
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.")
# 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(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(data=True):
edge = A.get_edge(u, v)
edge.attr["label"] = str(func(data))
if path is None:
ext = "png"
if suffix:
suffix = f"_{suffix}.{ext}"
else:
suffix = f".{ext}"
path = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
else:
# Assume the decorator worked and it is a file-object.
pass
# Write graph to file
A.draw(path=path, format=None, prog=prog, args=args)
path.close()
# Show graph in a new window (depends on platform configuration)
if show:
nx.utils.default_opener(path.name)
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.
"""
import warnings
warnings.warn(
"display_pygraphviz is deprecated and will be removed in NetworkX 3.0. "
"To view a graph G using pygraphviz, use nx.nx_agraph.view_pygraphviz(G). "
"To view a graph from file, consider nx.utils.default_opener(filename).",
DeprecationWarning,
)
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)

View file

@ -0,0 +1,338 @@
"""
*****
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: https://www.graphviz.org
DOT Language: http://www.graphviz.org/doc/info/lang.html
"""
from locale import getpreferredencoding
from networkx.utils import open_file
import networkx as nx
__all__ = [
"write_dot",
"read_dot",
"graphviz_layout",
"pydot_layout",
"to_pydot",
"from_pydot",
]
@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
@open_file(0, mode="r")
def read_dot(path):
"""Returns 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`.
"""
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])
def from_pydot(P):
"""Returns 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, str):
s.append(u.strip('"'))
else:
for unodes in u["nodes"]:
s.append(unodes.strip('"'))
if isinstance(v, str):
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
def to_pydot(N):
"""Returns 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
-----
"""
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 == "":
P = pydot.Dot("", graph_type=graph_type, strict=strict, **graph_defaults)
else:
P = pydot.Dot(
f'"{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 = {k: str(v) for k, v in nodedata.items()}
p = pydot.Node(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 = {k: str(v) for k, v in edgedata.items() if k != "key"}
edge = pydot.Edge(str(u), str(v), key=str(key), **str_edgedata)
P.add_edge(edge)
else:
for u, v, edgedata in N.edges(data=True):
str_edgedata = {k: str(v) for k, v in edgedata.items()}
edge = pydot.Edge(str(u), str(v), **str_edgedata)
P.add_edge(edge)
return P
def graphviz_layout(G, prog="neato", root=None):
"""Create node positions using Pydot and Graphviz.
Returns a dictionary of positions keyed by node.
Parameters
----------
G : NetworkX Graph
The graph for which the layout is computed.
prog : string (default: 'neato')
The name of the GraphViz program to use for layout.
Options depend on GraphViz version but may include:
'dot', 'twopi', 'fdp', 'sfdp', 'circo'
root : Node from G or None (default: None)
The node of G from which to start some layout algorithms.
Returns
-------
Dictionary of (x, y) 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)
def pydot_layout(G, prog="neato", root=None):
"""Create node positions using :mod:`pydot` and Graphviz.
Parameters
--------
G : Graph
NetworkX graph to be laid out.
prog : string (default: 'neato')
Name of the GraphViz command to use for layout.
Options depend on GraphViz version but may include:
'dot', 'twopi', 'fdp', 'sfdp', 'circo'
root : Node from G or None (default: None)
The node of G from which to start some layout algorithms.
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()}
"""
import pydot
P = to_pydot(G)
if root is not None:
P.set("root", 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 = str(D_bytes, encoding=getpreferredencoding())
if D == "": # no data returned
print(f"Graphviz layout with {prog} failed")
print()
print("To debug what happened try:")
print("P = nx.nx_pydot.to_pydot(G)")
print('P.write_dot("file.dot")')
print(f"And then run {prog} on file.dot")
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(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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,249 @@
"""Unit tests for PyGraphviz interface."""
import os
import tempfile
import pytest
pygraphviz = pytest.importorskip("pygraphviz")
from networkx.testing import assert_edges_equal, assert_nodes_equal, assert_graphs_equal
import networkx as nx
class TestAGraph:
def build_graph(self, G):
edges = [("A", "B"), ("A", "C"), ("A", "C"), ("B", "C"), ("A", "D")]
G.add_edges_from(edges)
G.add_node("E")
G.graph["metal"] = "bronze"
return G
def assert_equal(self, G1, G2):
assert_nodes_equal(G1.nodes(), G2.nodes())
assert_edges_equal(G1.edges(), G2.edges())
assert G1.graph["metal"] == G2.graph["metal"]
def agraph_checks(self, G):
G = self.build_graph(G)
A = nx.nx_agraph.to_agraph(G)
H = nx.nx_agraph.from_agraph(A)
self.assert_equal(G, H)
fname = tempfile.mktemp()
nx.drawing.nx_agraph.write_dot(H, fname)
Hin = nx.nx_agraph.read_dot(fname)
os.unlink(fname)
self.assert_equal(H, Hin)
(fd, fname) = tempfile.mkstemp()
with open(fname, "w") as fh:
nx.drawing.nx_agraph.write_dot(H, fh)
with open(fname) as fh:
Hin = nx.nx_agraph.read_dot(fh)
os.unlink(fname)
self.assert_equal(H, Hin)
def test_from_agraph_name(self):
G = nx.Graph(name="test")
A = nx.nx_agraph.to_agraph(G)
H = nx.nx_agraph.from_agraph(A)
assert G.name == "test"
@pytest.mark.parametrize(
"graph_class", (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)
)
def test_from_agraph_create_using(self, graph_class):
G = nx.path_graph(3)
A = nx.nx_agraph.to_agraph(G)
H = nx.nx_agraph.from_agraph(A, create_using=graph_class)
assert isinstance(H, graph_class)
def test_from_agraph_named_edges(self):
# Create an AGraph from an existing (non-multi) Graph
G = nx.Graph()
G.add_nodes_from([0, 1])
A = nx.nx_agraph.to_agraph(G)
# Add edge (+ name, given by key) to the AGraph
A.add_edge(0, 1, key="foo")
# Verify a.name roundtrips out to 'key' in from_agraph
H = nx.nx_agraph.from_agraph(A)
assert isinstance(H, nx.Graph)
assert ("0", "1", {"key": "foo"}) in H.edges(data=True)
def test_undirected(self):
self.agraph_checks(nx.Graph())
def test_directed(self):
self.agraph_checks(nx.DiGraph())
def test_multi_undirected(self):
self.agraph_checks(nx.MultiGraph())
def test_multi_directed(self):
self.agraph_checks(nx.MultiDiGraph())
def test_to_agraph_with_nodedata(self):
G = nx.Graph()
G.add_node(1, color="red")
A = nx.nx_agraph.to_agraph(G)
assert dict(A.nodes()[0].attr) == {"color": "red"}
@pytest.mark.parametrize("graph_class", (nx.Graph, nx.MultiGraph))
def test_to_agraph_with_edgedata(self, graph_class):
G = graph_class()
G.add_nodes_from([0, 1])
G.add_edge(0, 1, color="yellow")
A = nx.nx_agraph.to_agraph(G)
assert dict(A.edges()[0].attr) == {"color": "yellow"}
def test_view_pygraphviz_path(self, tmp_path):
G = nx.complete_graph(3)
input_path = str(tmp_path / "graph.png")
out_path, A = nx.nx_agraph.view_pygraphviz(G, path=input_path, show=False)
assert out_path == input_path
# Ensure file is not empty
with open(input_path, "rb") as fh:
data = fh.read()
assert len(data) > 0
def test_view_pygraphviz_file_suffix(self, tmp_path):
G = nx.complete_graph(3)
path, A = nx.nx_agraph.view_pygraphviz(G, suffix=1, show=False)
assert path[-6:] == "_1.png"
def test_view_pygraphviz(self):
G = nx.Graph() # "An empty graph cannot be drawn."
pytest.raises(nx.NetworkXException, nx.nx_agraph.view_pygraphviz, G)
G = nx.barbell_graph(4, 6)
nx.nx_agraph.view_pygraphviz(G, show=False)
def test_view_pygraphviz_edgelabel(self):
G = nx.Graph()
G.add_edge(1, 2, weight=7)
G.add_edge(2, 3, weight=8)
path, A = nx.nx_agraph.view_pygraphviz(G, edgelabel="weight", show=False)
for edge in A.edges():
assert edge.attr["weight"] in ("7", "8")
def test_view_pygraphviz_callable_edgelabel(self):
G = nx.complete_graph(3)
def foo_label(data):
return "foo"
path, A = nx.nx_agraph.view_pygraphviz(G, edgelabel=foo_label, show=False)
for edge in A.edges():
assert edge.attr["label"] == "foo"
def test_view_pygraphviz_multigraph_edgelabels(self):
G = nx.MultiGraph()
G.add_edge(0, 1, key=0, name="left_fork")
G.add_edge(0, 1, key=1, name="right_fork")
path, A = nx.nx_agraph.view_pygraphviz(G, edgelabel="name", show=False)
edges = A.edges()
assert len(edges) == 2
for edge in edges:
assert edge.attr["label"].strip() in ("left_fork", "right_fork")
def test_graph_with_reserved_keywords(self):
# test attribute/keyword clash case for #1582
# node: n
# edges: u,v
G = nx.Graph()
G = self.build_graph(G)
G.nodes["E"]["n"] = "keyword"
G.edges[("A", "B")]["u"] = "keyword"
G.edges[("A", "B")]["v"] = "keyword"
A = nx.nx_agraph.to_agraph(G)
def test_view_pygraphviz_no_added_attrs_to_input(self):
G = nx.complete_graph(2)
path, A = nx.nx_agraph.view_pygraphviz(G, show=False)
assert G.graph == {}
@pytest.mark.xfail(reason="known bug in clean_attrs")
def test_view_pygraphviz_leaves_input_graph_unmodified(self):
G = nx.complete_graph(2)
# Add entries to graph dict that to_agraph handles specially
G.graph["node"] = {"width": "0.80"}
G.graph["edge"] = {"fontsize": "14"}
path, A = nx.nx_agraph.view_pygraphviz(G, show=False)
assert G.graph == {"node": {"width": "0.80"}, "edge": {"fontsize": "14"}}
def test_graph_with_AGraph_attrs(self):
G = nx.complete_graph(2)
# Add entries to graph dict that to_agraph handles specially
G.graph["node"] = {"width": "0.80"}
G.graph["edge"] = {"fontsize": "14"}
path, A = nx.nx_agraph.view_pygraphviz(G, show=False)
# Ensure user-specified values are not lost
assert dict(A.node_attr)["width"] == "0.80"
assert dict(A.edge_attr)["fontsize"] == "14"
def test_round_trip_empty_graph(self):
G = nx.Graph()
A = nx.nx_agraph.to_agraph(G)
H = nx.nx_agraph.from_agraph(A)
# assert_graphs_equal(G, H)
AA = nx.nx_agraph.to_agraph(H)
HH = nx.nx_agraph.from_agraph(AA)
assert_graphs_equal(H, HH)
G.graph["graph"] = {}
G.graph["node"] = {}
G.graph["edge"] = {}
assert_graphs_equal(G, HH)
@pytest.mark.xfail(reason="integer->string node conversion in round trip")
def test_round_trip_integer_nodes(self):
G = nx.complete_graph(3)
A = nx.nx_agraph.to_agraph(G)
H = nx.nx_agraph.from_agraph(A)
assert_graphs_equal(G, H)
def test_graphviz_alias(self):
G = self.build_graph(nx.Graph())
pos_graphviz = nx.nx_agraph.graphviz_layout(G)
pos_pygraphviz = nx.nx_agraph.pygraphviz_layout(G)
assert pos_graphviz == pos_pygraphviz
@pytest.mark.parametrize("root", range(5))
def test_pygraphviz_layout_root(self, root):
# NOTE: test depends on layout prog being deterministic
G = nx.complete_graph(5)
A = nx.nx_agraph.to_agraph(G)
# Get layout with root arg is not None
pygv_layout = nx.nx_agraph.pygraphviz_layout(G, prog="circo", root=root)
# Equivalent layout directly on AGraph
A.layout(args=f"-Groot={root}", prog="circo")
# Parse AGraph layout
a1_pos = tuple(float(v) for v in dict(A.get_node("1").attr)["pos"].split(","))
assert pygv_layout[1] == a1_pos
def test_2d_layout(self):
G = nx.Graph()
G = self.build_graph(G)
G.graph["dimen"] = 2
pos = nx.nx_agraph.pygraphviz_layout(G, prog="neato")
pos = list(pos.values())
assert len(pos) == 5
assert len(pos[0]) == 2
def test_3d_layout(self):
G = nx.Graph()
G = self.build_graph(G)
G.graph["dimen"] = 3
pos = nx.nx_agraph.pygraphviz_layout(G, prog="neato")
pos = list(pos.values())
assert len(pos) == 5
assert len(pos[0]) == 3
def test_display_pygraphviz_deprecation_warning(self):
G = nx.complete_graph(2)
path_name, A = nx.nx_agraph.view_pygraphviz(G, show=False)
# Monkeypatch default_opener to prevent window opening
nx.utils.default_opener = lambda x: None
with pytest.warns(DeprecationWarning, match="display_pygraphviz is deprecated"):
with open(path_name, "wb") as fh:
nx.nx_agraph.display_pygraphviz(A, fh, prog="dot")

View file

@ -0,0 +1,406 @@
"""Unit tests for layout functions."""
import networkx as nx
from networkx.testing import almost_equal
import pytest
numpy = pytest.importorskip("numpy")
test_smoke_empty_graphscipy = pytest.importorskip("scipy")
class TestLayout:
@classmethod
def setup_class(cls):
cls.Gi = nx.grid_2d_graph(5, 5)
cls.Gs = nx.Graph()
nx.add_path(cls.Gs, "abcdef")
cls.bigG = nx.grid_2d_graph(25, 25) # > 500 nodes for sparse
@staticmethod
def collect_node_distances(positions):
distances = []
prev_val = None
for k in positions:
if prev_val is not None:
diff = positions[k] - prev_val
distances.append(numpy.dot(diff, diff) ** 0.5)
prev_val = positions[k]
return distances
def test_spring_fixed_without_pos(self):
G = nx.path_graph(4)
pytest.raises(ValueError, nx.spring_layout, G, fixed=[0])
pos = {0: (1, 1), 2: (0, 0)}
pytest.raises(ValueError, nx.spring_layout, G, fixed=[0, 1], pos=pos)
nx.spring_layout(G, fixed=[0, 2], pos=pos) # No ValueError
def test_spring_init_pos(self):
# Tests GH #2448
import math
G = nx.Graph()
G.add_edges_from([(0, 1), (1, 2), (2, 0), (2, 3)])
init_pos = {0: (0.0, 0.0)}
fixed_pos = [0]
pos = nx.fruchterman_reingold_layout(G, pos=init_pos, fixed=fixed_pos)
has_nan = any(math.isnan(c) for coords in pos.values() for c in coords)
assert not has_nan, "values should not be nan"
def test_smoke_empty_graph(self):
G = []
nx.random_layout(G)
nx.circular_layout(G)
nx.planar_layout(G)
nx.spring_layout(G)
nx.fruchterman_reingold_layout(G)
nx.spectral_layout(G)
nx.shell_layout(G)
nx.bipartite_layout(G, G)
nx.spiral_layout(G)
nx.multipartite_layout(G)
nx.kamada_kawai_layout(G)
def test_smoke_int(self):
G = self.Gi
nx.random_layout(G)
nx.circular_layout(G)
nx.planar_layout(G)
nx.spring_layout(G)
nx.fruchterman_reingold_layout(G)
nx.fruchterman_reingold_layout(self.bigG)
nx.spectral_layout(G)
nx.spectral_layout(G.to_directed())
nx.spectral_layout(self.bigG)
nx.spectral_layout(self.bigG.to_directed())
nx.shell_layout(G)
nx.spiral_layout(G)
nx.kamada_kawai_layout(G)
nx.kamada_kawai_layout(G, dim=1)
nx.kamada_kawai_layout(G, dim=3)
def test_smoke_string(self):
G = self.Gs
nx.random_layout(G)
nx.circular_layout(G)
nx.planar_layout(G)
nx.spring_layout(G)
nx.fruchterman_reingold_layout(G)
nx.spectral_layout(G)
nx.shell_layout(G)
nx.spiral_layout(G)
nx.kamada_kawai_layout(G)
nx.kamada_kawai_layout(G, dim=1)
nx.kamada_kawai_layout(G, dim=3)
def check_scale_and_center(self, pos, scale, center):
center = numpy.array(center)
low = center - scale
hi = center + scale
vpos = numpy.array(list(pos.values()))
length = vpos.max(0) - vpos.min(0)
assert (length <= 2 * scale).all()
assert (vpos >= low).all()
assert (vpos <= hi).all()
def test_scale_and_center_arg(self):
sc = self.check_scale_and_center
c = (4, 5)
G = nx.complete_graph(9)
G.add_node(9)
sc(nx.random_layout(G, center=c), scale=0.5, center=(4.5, 5.5))
# rest can have 2*scale length: [-scale, scale]
sc(nx.spring_layout(G, scale=2, center=c), scale=2, center=c)
sc(nx.spectral_layout(G, scale=2, center=c), scale=2, center=c)
sc(nx.circular_layout(G, scale=2, center=c), scale=2, center=c)
sc(nx.shell_layout(G, scale=2, center=c), scale=2, center=c)
sc(nx.spiral_layout(G, scale=2, center=c), scale=2, center=c)
sc(nx.kamada_kawai_layout(G, scale=2, center=c), scale=2, center=c)
c = (2, 3, 5)
sc(nx.kamada_kawai_layout(G, dim=3, scale=2, center=c), scale=2, center=c)
def test_planar_layout_non_planar_input(self):
G = nx.complete_graph(9)
pytest.raises(nx.NetworkXException, nx.planar_layout, G)
def test_smoke_planar_layout_embedding_input(self):
embedding = nx.PlanarEmbedding()
embedding.set_data({0: [1, 2], 1: [0, 2], 2: [0, 1]})
nx.planar_layout(embedding)
def test_default_scale_and_center(self):
sc = self.check_scale_and_center
c = (0, 0)
G = nx.complete_graph(9)
G.add_node(9)
sc(nx.random_layout(G), scale=0.5, center=(0.5, 0.5))
sc(nx.spring_layout(G), scale=1, center=c)
sc(nx.spectral_layout(G), scale=1, center=c)
sc(nx.circular_layout(G), scale=1, center=c)
sc(nx.shell_layout(G), scale=1, center=c)
sc(nx.spiral_layout(G), scale=1, center=c)
sc(nx.kamada_kawai_layout(G), scale=1, center=c)
c = (0, 0, 0)
sc(nx.kamada_kawai_layout(G, dim=3), scale=1, center=c)
def test_circular_planar_and_shell_dim_error(self):
G = nx.path_graph(4)
pytest.raises(ValueError, nx.circular_layout, G, dim=1)
pytest.raises(ValueError, nx.shell_layout, G, dim=1)
pytest.raises(ValueError, nx.shell_layout, G, dim=3)
pytest.raises(ValueError, nx.planar_layout, G, dim=1)
pytest.raises(ValueError, nx.planar_layout, G, dim=3)
def test_adjacency_interface_numpy(self):
A = nx.to_numpy_array(self.Gs)
pos = nx.drawing.layout._fruchterman_reingold(A)
assert pos.shape == (6, 2)
pos = nx.drawing.layout._fruchterman_reingold(A, dim=3)
assert pos.shape == (6, 3)
pos = nx.drawing.layout._sparse_fruchterman_reingold(A)
assert pos.shape == (6, 2)
def test_adjacency_interface_scipy(self):
A = nx.to_scipy_sparse_matrix(self.Gs, dtype="d")
pos = nx.drawing.layout._sparse_fruchterman_reingold(A)
assert pos.shape == (6, 2)
pos = nx.drawing.layout._sparse_spectral(A)
assert pos.shape == (6, 2)
pos = nx.drawing.layout._sparse_fruchterman_reingold(A, dim=3)
assert pos.shape == (6, 3)
def test_single_nodes(self):
G = nx.path_graph(1)
vpos = nx.shell_layout(G)
assert not vpos[0].any()
G = nx.path_graph(4)
vpos = nx.shell_layout(G, [[0], [1, 2], [3]])
assert not vpos[0].any()
assert vpos[3].any() # ensure node 3 not at origin (#3188)
assert numpy.linalg.norm(vpos[3]) <= 1 # ensure node 3 fits (#3753)
vpos = nx.shell_layout(G, [[0], [1, 2], [3]], rotate=0)
assert numpy.linalg.norm(vpos[3]) <= 1 # ensure node 3 fits (#3753)
def test_smoke_initial_pos_fruchterman_reingold(self):
pos = nx.circular_layout(self.Gi)
npos = nx.fruchterman_reingold_layout(self.Gi, pos=pos)
def test_fixed_node_fruchterman_reingold(self):
# Dense version (numpy based)
pos = nx.circular_layout(self.Gi)
npos = nx.spring_layout(self.Gi, pos=pos, fixed=[(0, 0)])
assert tuple(pos[(0, 0)]) == tuple(npos[(0, 0)])
# Sparse version (scipy based)
pos = nx.circular_layout(self.bigG)
npos = nx.spring_layout(self.bigG, pos=pos, fixed=[(0, 0)])
for axis in range(2):
assert almost_equal(pos[(0, 0)][axis], npos[(0, 0)][axis])
def test_center_parameter(self):
G = nx.path_graph(1)
nx.random_layout(G, center=(1, 1))
vpos = nx.circular_layout(G, center=(1, 1))
assert tuple(vpos[0]) == (1, 1)
vpos = nx.planar_layout(G, center=(1, 1))
assert tuple(vpos[0]) == (1, 1)
vpos = nx.spring_layout(G, center=(1, 1))
assert tuple(vpos[0]) == (1, 1)
vpos = nx.fruchterman_reingold_layout(G, center=(1, 1))
assert tuple(vpos[0]) == (1, 1)
vpos = nx.spectral_layout(G, center=(1, 1))
assert tuple(vpos[0]) == (1, 1)
vpos = nx.shell_layout(G, center=(1, 1))
assert tuple(vpos[0]) == (1, 1)
vpos = nx.spiral_layout(G, center=(1, 1))
assert tuple(vpos[0]) == (1, 1)
def test_center_wrong_dimensions(self):
G = nx.path_graph(1)
assert id(nx.spring_layout) == id(nx.fruchterman_reingold_layout)
pytest.raises(ValueError, nx.random_layout, G, center=(1, 1, 1))
pytest.raises(ValueError, nx.circular_layout, G, center=(1, 1, 1))
pytest.raises(ValueError, nx.planar_layout, G, center=(1, 1, 1))
pytest.raises(ValueError, nx.spring_layout, G, center=(1, 1, 1))
pytest.raises(ValueError, nx.spring_layout, G, dim=3, center=(1, 1))
pytest.raises(ValueError, nx.spectral_layout, G, center=(1, 1, 1))
pytest.raises(ValueError, nx.spectral_layout, G, dim=3, center=(1, 1))
pytest.raises(ValueError, nx.shell_layout, G, center=(1, 1, 1))
pytest.raises(ValueError, nx.spiral_layout, G, center=(1, 1, 1))
pytest.raises(ValueError, nx.kamada_kawai_layout, G, center=(1, 1, 1))
def test_empty_graph(self):
G = nx.empty_graph()
vpos = nx.random_layout(G, center=(1, 1))
assert vpos == {}
vpos = nx.circular_layout(G, center=(1, 1))
assert vpos == {}
vpos = nx.planar_layout(G, center=(1, 1))
assert vpos == {}
vpos = nx.bipartite_layout(G, G)
assert vpos == {}
vpos = nx.spring_layout(G, center=(1, 1))
assert vpos == {}
vpos = nx.fruchterman_reingold_layout(G, center=(1, 1))
assert vpos == {}
vpos = nx.spectral_layout(G, center=(1, 1))
assert vpos == {}
vpos = nx.shell_layout(G, center=(1, 1))
assert vpos == {}
vpos = nx.spiral_layout(G, center=(1, 1))
assert vpos == {}
vpos = nx.multipartite_layout(G, center=(1, 1))
assert vpos == {}
vpos = nx.kamada_kawai_layout(G, center=(1, 1))
assert vpos == {}
def test_bipartite_layout(self):
G = nx.complete_bipartite_graph(3, 5)
top, bottom = nx.bipartite.sets(G)
vpos = nx.bipartite_layout(G, top)
assert len(vpos) == len(G)
top_x = vpos[list(top)[0]][0]
bottom_x = vpos[list(bottom)[0]][0]
for node in top:
assert vpos[node][0] == top_x
for node in bottom:
assert vpos[node][0] == bottom_x
vpos = nx.bipartite_layout(
G, top, align="horizontal", center=(2, 2), scale=2, aspect_ratio=1
)
assert len(vpos) == len(G)
top_y = vpos[list(top)[0]][1]
bottom_y = vpos[list(bottom)[0]][1]
for node in top:
assert vpos[node][1] == top_y
for node in bottom:
assert vpos[node][1] == bottom_y
pytest.raises(ValueError, nx.bipartite_layout, G, top, align="foo")
def test_multipartite_layout(self):
sizes = (0, 5, 7, 2, 8)
G = nx.complete_multipartite_graph(*sizes)
vpos = nx.multipartite_layout(G)
assert len(vpos) == len(G)
start = 0
for n in sizes:
end = start + n
assert all(vpos[start][0] == vpos[i][0] for i in range(start + 1, end))
start += n
vpos = nx.multipartite_layout(G, align="horizontal", scale=2, center=(2, 2))
assert len(vpos) == len(G)
start = 0
for n in sizes:
end = start + n
assert all(vpos[start][1] == vpos[i][1] for i in range(start + 1, end))
start += n
pytest.raises(ValueError, nx.multipartite_layout, G, align="foo")
def test_kamada_kawai_costfn_1d(self):
costfn = nx.drawing.layout._kamada_kawai_costfn
pos = numpy.array([4.0, 7.0])
invdist = 1 / numpy.array([[0.1, 2.0], [2.0, 0.3]])
cost, grad = costfn(pos, numpy, invdist, meanweight=0, dim=1)
assert almost_equal(cost, ((3 / 2.0 - 1) ** 2))
assert almost_equal(grad[0], -0.5)
assert almost_equal(grad[1], 0.5)
def check_kamada_kawai_costfn(self, pos, invdist, meanwt, dim):
costfn = nx.drawing.layout._kamada_kawai_costfn
cost, grad = costfn(pos.ravel(), numpy, invdist, meanweight=meanwt, dim=dim)
expected_cost = 0.5 * meanwt * numpy.sum(numpy.sum(pos, axis=0) ** 2)
for i in range(pos.shape[0]):
for j in range(i + 1, pos.shape[0]):
diff = numpy.linalg.norm(pos[i] - pos[j])
expected_cost += (diff * invdist[i][j] - 1.0) ** 2
assert almost_equal(cost, expected_cost)
dx = 1e-4
for nd in range(pos.shape[0]):
for dm in range(pos.shape[1]):
idx = nd * pos.shape[1] + dm
pos0 = pos.flatten()
pos0[idx] += dx
cplus = costfn(
pos0, numpy, invdist, meanweight=meanwt, dim=pos.shape[1]
)[0]
pos0[idx] -= 2 * dx
cminus = costfn(
pos0, numpy, invdist, meanweight=meanwt, dim=pos.shape[1]
)[0]
assert almost_equal(grad[idx], (cplus - cminus) / (2 * dx), places=5)
def test_kamada_kawai_costfn(self):
invdist = 1 / numpy.array([[0.1, 2.1, 1.7], [2.1, 0.2, 0.6], [1.7, 0.6, 0.3]])
meanwt = 0.3
# 2d
pos = numpy.array([[1.3, -3.2], [2.7, -0.3], [5.1, 2.5]])
self.check_kamada_kawai_costfn(pos, invdist, meanwt, 2)
# 3d
pos = numpy.array([[0.9, 8.6, -8.7], [-10, -0.5, -7.1], [9.1, -8.1, 1.6]])
self.check_kamada_kawai_costfn(pos, invdist, meanwt, 3)
def test_spiral_layout(self):
G = self.Gs
# a lower value of resolution should result in a more compact layout
# intuitively, the total distance from the start and end nodes
# via each node in between (transiting through each) will be less,
# assuming rescaling does not occur on the computed node positions
pos_standard = nx.spiral_layout(G, resolution=0.35)
pos_tighter = nx.spiral_layout(G, resolution=0.34)
distances = self.collect_node_distances(pos_standard)
distances_tighter = self.collect_node_distances(pos_tighter)
assert sum(distances) > sum(distances_tighter)
# return near-equidistant points after the first value if set to true
pos_equidistant = nx.spiral_layout(G, equidistant=True)
distances_equidistant = self.collect_node_distances(pos_equidistant)
for d in range(1, len(distances_equidistant) - 1):
# test similarity to two decimal places
assert almost_equal(
distances_equidistant[d], distances_equidistant[d + 1], 2
)
def test_rescale_layout_dict(self):
G = nx.empty_graph()
vpos = nx.random_layout(G, center=(1, 1))
assert nx.rescale_layout_dict(vpos) == {}
G = nx.empty_graph(2)
vpos = {0: (0.0, 0.0), 1: (1.0, 1.0)}
s_vpos = nx.rescale_layout_dict(vpos)
norm = numpy.linalg.norm
assert norm([sum(x) for x in zip(*s_vpos.values())]) < 1e-6
G = nx.empty_graph(3)
vpos = {0: (0, 0), 1: (1, 1), 2: (0.5, 0.5)}
s_vpos = nx.rescale_layout_dict(vpos)
assert s_vpos == {0: (-1, -1), 1: (1, 1), 2: (0, 0)}
s_vpos = nx.rescale_layout_dict(vpos, scale=2)
assert s_vpos == {0: (-2, -2), 1: (2, 2), 2: (0, 0)}

View file

@ -0,0 +1,94 @@
"""Unit tests for pydot drawing functions."""
from io import StringIO
import tempfile
import networkx as nx
from networkx.testing import assert_graphs_equal
import pytest
pydot = pytest.importorskip("pydot")
class TestPydot:
def pydot_checks(self, G, prog):
"""
Validate :mod:`pydot`-based usage of the passed NetworkX graph with the
passed basename of an external GraphViz command (e.g., `dot`, `neato`).
"""
# Set the name of this graph to... "G". Failing to do so will
# subsequently trip an assertion expecting this name.
G.graph["name"] = "G"
# Add arbitrary nodes and edges to the passed empty graph.
G.add_edges_from([("A", "B"), ("A", "C"), ("B", "C"), ("A", "D")])
G.add_node("E")
# Validate layout of this graph with the passed GraphViz command.
graph_layout = nx.nx_pydot.pydot_layout(G, prog=prog)
assert isinstance(graph_layout, dict)
# Convert this graph into a "pydot.Dot" instance.
P = nx.nx_pydot.to_pydot(G)
# Convert this "pydot.Dot" instance back into a graph of the same type.
G2 = G.__class__(nx.nx_pydot.from_pydot(P))
# Validate the original and resulting graphs to be the same.
assert_graphs_equal(G, G2)
# Serialize this "pydot.Dot" instance to a temporary file in dot format
fname = tempfile.mktemp()
P.write_raw(fname)
# Deserialize a list of new "pydot.Dot" instances back from this file.
Pin_list = pydot.graph_from_dot_file(path=fname, encoding="utf-8")
# Validate this file to contain only one graph.
assert len(Pin_list) == 1
# The single "pydot.Dot" instance deserialized from this file.
Pin = Pin_list[0]
# Sorted list of all nodes in the original "pydot.Dot" instance.
n1 = sorted([p.get_name() for p in P.get_node_list()])
# Sorted list of all nodes in the deserialized "pydot.Dot" instance.
n2 = sorted([p.get_name() for p in Pin.get_node_list()])
# Validate these instances to contain the same nodes.
assert n1 == n2
# Sorted list of all edges in the original "pydot.Dot" instance.
e1 = sorted([(e.get_source(), e.get_destination()) for e in P.get_edge_list()])
# Sorted list of all edges in the original "pydot.Dot" instance.
e2 = sorted(
[(e.get_source(), e.get_destination()) for e in Pin.get_edge_list()]
)
# Validate these instances to contain the same edges.
assert e1 == e2
# Deserialize a new graph of the same type back from this file.
Hin = nx.nx_pydot.read_dot(fname)
Hin = G.__class__(Hin)
# Validate the original and resulting graphs to be the same.
assert_graphs_equal(G, Hin)
def test_undirected(self):
self.pydot_checks(nx.Graph(), prog="neato")
def test_directed(self):
self.pydot_checks(nx.DiGraph(), prog="dot")
def test_read_write(self):
G = nx.MultiGraph()
G.graph["name"] = "G"
G.add_edge("1", "2", key="0") # read assumes strings
fh = StringIO()
nx.nx_pydot.write_dot(G, fh)
fh.seek(0)
H = nx.nx_pydot.read_dot(fh)
assert_graphs_equal(G, H)

View file

@ -0,0 +1,257 @@
"""Unit tests for matplotlib drawing functions."""
import os
import itertools
import pytest
mpl = pytest.importorskip("matplotlib")
mpl.use("PS")
plt = pytest.importorskip("matplotlib.pyplot")
plt.rcParams["text.usetex"] = False
import networkx as nx
class TestPylab:
@classmethod
def setup_class(cls):
cls.G = nx.barbell_graph(4, 6)
def test_draw(self):
try:
functions = [
nx.draw_circular,
nx.draw_kamada_kawai,
nx.draw_planar,
nx.draw_random,
nx.draw_spectral,
nx.draw_spring,
nx.draw_shell,
]
options = [{"node_color": "black", "node_size": 100, "width": 3}]
for function, option in itertools.product(functions, options):
function(self.G, **option)
plt.savefig("test.ps")
finally:
try:
os.unlink("test.ps")
except OSError:
pass
def test_draw_shell_nlist(self):
try:
nlist = [list(range(4)), list(range(4, 10)), list(range(10, 14))]
nx.draw_shell(self.G, nlist=nlist)
plt.savefig("test.ps")
finally:
try:
os.unlink("test.ps")
except OSError:
pass
def test_edge_colormap(self):
colors = range(self.G.number_of_edges())
nx.draw_spring(
self.G, edge_color=colors, width=4, edge_cmap=plt.cm.Blues, with_labels=True
)
# plt.show()
def test_arrows(self):
nx.draw_spring(self.G.to_directed())
# plt.show()
def test_edge_colors_and_widths(self):
pos = nx.circular_layout(self.G)
for G in (self.G, self.G.to_directed()):
nx.draw_networkx_nodes(G, pos, node_color=[(1.0, 1.0, 0.2, 0.5)])
nx.draw_networkx_labels(G, pos)
# edge with default color and width
nx.draw_networkx_edges(
G, pos, edgelist=[(0, 1)], width=None, edge_color=None
)
# edges with global color strings and widths in lists
nx.draw_networkx_edges(
G, pos, edgelist=[(0, 2), (0, 3)], width=[3], edge_color=["r"]
)
# edges with color strings and widths for each edge
nx.draw_networkx_edges(
G, pos, edgelist=[(0, 2), (0, 3)], width=[1, 3], edge_color=["r", "b"]
)
# edges with fewer color strings and widths than edges
nx.draw_networkx_edges(
G,
pos,
edgelist=[(1, 2), (1, 3), (2, 3), (3, 4)],
width=[1, 3],
edge_color=["g", "m", "c"],
)
# edges with more color strings and widths than edges
nx.draw_networkx_edges(
G,
pos,
edgelist=[(3, 4)],
width=[1, 2, 3, 4],
edge_color=["r", "b", "g", "k"],
)
# with rgb tuple and 3 edges - is interpreted with cmap
nx.draw_networkx_edges(
G, pos, edgelist=[(4, 5), (5, 6), (6, 7)], edge_color=(1.0, 0.4, 0.3)
)
# with rgb tuple in list
nx.draw_networkx_edges(
G, pos, edgelist=[(7, 8), (8, 9)], edge_color=[(0.4, 1.0, 0.0)]
)
# with rgba tuple and 4 edges - is interpretted with cmap
nx.draw_networkx_edges(
G,
pos,
edgelist=[(9, 10), (10, 11), (10, 12), (10, 13)],
edge_color=(0.0, 1.0, 1.0, 0.5),
)
# with rgba tuple in list
nx.draw_networkx_edges(
G,
pos,
edgelist=[(9, 10), (10, 11), (10, 12), (10, 13)],
edge_color=[(0.0, 1.0, 1.0, 0.5)],
)
# with color string and global alpha
nx.draw_networkx_edges(
G, pos, edgelist=[(11, 12), (11, 13)], edge_color="purple", alpha=0.2
)
# with color string in a list
nx.draw_networkx_edges(
G, pos, edgelist=[(11, 12), (11, 13)], edge_color=["purple"]
)
# with single edge and hex color string
nx.draw_networkx_edges(G, pos, edgelist=[(12, 13)], edge_color="#1f78b4f0")
# edge_color as numeric using vmin, vmax
nx.draw_networkx_edges(
G,
pos,
edgelist=[(7, 8), (8, 9)],
edge_color=[0.2, 0.5],
edge_vmin=0.1,
edge_vmax=0.6,
)
# plt.show()
def test_labels_and_colors(self):
G = nx.cubical_graph()
pos = nx.spring_layout(G) # positions for all nodes
# nodes
nx.draw_networkx_nodes(
G, pos, nodelist=[0, 1, 2, 3], node_color="r", node_size=500, alpha=0.75
)
nx.draw_networkx_nodes(
G,
pos,
nodelist=[4, 5, 6, 7],
node_color="b",
node_size=500,
alpha=[0.25, 0.5, 0.75, 1.0],
)
# edges
nx.draw_networkx_edges(G, pos, width=1.0, alpha=0.5)
nx.draw_networkx_edges(
G,
pos,
edgelist=[(0, 1), (1, 2), (2, 3), (3, 0)],
width=8,
alpha=0.5,
edge_color="r",
)
nx.draw_networkx_edges(
G,
pos,
edgelist=[(4, 5), (5, 6), (6, 7), (7, 4)],
width=8,
alpha=0.5,
edge_color="b",
)
nx.draw_networkx_edges(
G,
pos,
edgelist=[(4, 5), (5, 6), (6, 7), (7, 4)],
min_source_margin=0.5,
min_target_margin=0.75,
width=8,
edge_color="b",
)
# some math labels
labels = {}
labels[0] = r"$a$"
labels[1] = r"$b$"
labels[2] = r"$c$"
labels[3] = r"$d$"
labels[4] = r"$\alpha$"
labels[5] = r"$\beta$"
labels[6] = r"$\gamma$"
labels[7] = r"$\delta$"
nx.draw_networkx_labels(G, pos, labels, font_size=16)
nx.draw_networkx_edge_labels(G, pos, edge_labels=None, rotate=False)
nx.draw_networkx_edge_labels(G, pos, edge_labels={(4, 5): "4-5"})
# plt.show()
def test_axes(self):
fig, ax = plt.subplots()
nx.draw(self.G, ax=ax)
def test_empty_graph(self):
G = nx.Graph()
nx.draw(G)
def test_draw_empty_nodes_return_values(self):
# See Issue #3833
from matplotlib.collections import PathCollection, LineCollection
G = nx.Graph([(1, 2), (2, 3)])
DG = nx.DiGraph([(1, 2), (2, 3)])
pos = nx.circular_layout(G)
assert isinstance(nx.draw_networkx_nodes(G, pos, nodelist=[]), PathCollection)
assert isinstance(nx.draw_networkx_nodes(DG, pos, nodelist=[]), PathCollection)
# drawing empty edges either return an empty LineCollection or empty list.
assert isinstance(
nx.draw_networkx_edges(G, pos, edgelist=[], arrows=True), LineCollection
)
assert isinstance(
nx.draw_networkx_edges(G, pos, edgelist=[], arrows=False), LineCollection
)
assert isinstance(
nx.draw_networkx_edges(DG, pos, edgelist=[], arrows=False), LineCollection
)
assert nx.draw_networkx_edges(DG, pos, edgelist=[], arrows=True) == []
def test_multigraph_edgelist_tuples(self):
# See Issue #3295
G = nx.path_graph(3, create_using=nx.MultiDiGraph)
nx.draw_networkx(G, edgelist=[(0, 1, 0)])
nx.draw_networkx(G, edgelist=[(0, 1, 0)], node_size=[10, 20, 0])
def test_alpha_iter(self):
pos = nx.random_layout(self.G)
# with fewer alpha elements than nodes
plt.subplot(131)
nx.draw_networkx_nodes(self.G, pos, alpha=[0.1, 0.2])
# with equal alpha elements and nodes
num_nodes = len(self.G.nodes)
alpha = [x / num_nodes for x in range(num_nodes)]
colors = range(num_nodes)
plt.subplot(132)
nx.draw_networkx_nodes(self.G, pos, node_color=colors, alpha=alpha)
# with more alpha elements than nodes
alpha.append(1)
plt.subplot(133)
nx.draw_networkx_nodes(self.G, pos, alpha=alpha)
def test_error_invalid_kwds(self):
with pytest.raises(ValueError, match="Received invalid argument"):
nx.draw(self.G, foo="bar")
def test_np_edgelist(self):
# see issue #4129
np = pytest.importorskip("numpy")
nx.draw_networkx(self.G, edgelist=np.array([(0, 2), (0, 3)]))