using for loop to install conda package

This commit is contained in:
ton
2023-04-16 11:03:27 +07:00
parent 49da9f29c1
commit 0c2b34d6f8
12168 changed files with 2656238 additions and 1 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,242 @@
"""Unit tests for PyGraphviz interface."""
import os
import tempfile
import pytest
pygraphviz = pytest.importorskip("pygraphviz")
import networkx as nx
from networkx.utils import edges_equal, graphs_equal, nodes_equal
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)
fd, fname = tempfile.mkstemp()
nx.drawing.nx_agraph.write_dot(H, fname)
Hin = nx.nx_agraph.read_dot(fname)
self.assert_equal(H, Hin)
os.close(fd)
os.unlink(fname)
(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.close(fd)
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

View File

@@ -0,0 +1,292 @@
import pytest
import networkx as nx
def test_tikz_attributes():
G = nx.path_graph(4, create_using=nx.DiGraph)
pos = {n: (n, n) for n in G}
G.add_edge(0, 0)
G.edges[(0, 0)]["label"] = "Loop"
G.edges[(0, 0)]["label_options"] = "midway"
G.nodes[0]["style"] = "blue"
G.nodes[1]["style"] = "line width=3,draw"
G.nodes[2]["style"] = "circle,draw,blue!50"
G.nodes[3]["label"] = "Stop"
G.edges[(0, 1)]["label"] = "1st Step"
G.edges[(0, 1)]["label_options"] = "near end"
G.edges[(2, 3)]["label"] = "3rd Step"
G.edges[(2, 3)]["label_options"] = "near start"
G.edges[(2, 3)]["style"] = "bend left,green"
G.edges[(1, 2)]["label"] = "2nd"
G.edges[(1, 2)]["label_options"] = "pos=0.5"
G.edges[(1, 2)]["style"] = ">->,bend right,line width=3,green!90"
output_tex = nx.to_latex(
G,
pos=pos,
as_document=False,
tikz_options="[scale=3]",
node_options="style",
edge_options="style",
node_label="label",
edge_label="label",
edge_label_options="label_options",
)
expected_tex = r"""\begin{figure}
\begin{tikzpicture}[scale=3]
\draw
(0, 0) node[blue] (0){0}
(1, 1) node[line width=3,draw] (1){1}
(2, 2) node[circle,draw,blue!50] (2){2}
(3, 3) node (3){Stop};
\begin{scope}[->]
\draw (0) to node[near end] {1st Step} (1);
\draw[loop,] (0) to node[midway] {Loop} (0);
\draw[>->,bend right,line width=3,green!90] (1) to node[pos=0.5] {2nd} (2);
\draw[bend left,green] (2) to node[near start] {3rd Step} (3);
\end{scope}
\end{tikzpicture}
\end{figure}"""
assert output_tex == expected_tex
# print(output_tex)
# # Pretty way to assert that A.to_document() == expected_tex
# content_same = True
# for aa, bb in zip(expected_tex.split("\n"), output_tex.split("\n")):
# if aa != bb:
# content_same = False
# print(f"-{aa}|\n+{bb}|")
# assert content_same
def test_basic_multiple_graphs():
H1 = nx.path_graph(4)
H2 = nx.complete_graph(4)
H3 = nx.path_graph(8)
H4 = nx.complete_graph(8)
captions = [
"Path on 4 nodes",
"Complete graph on 4 nodes",
"Path on 8 nodes",
"Complete graph on 8 nodes",
]
labels = ["fig2a", "fig2b", "fig2c", "fig2d"]
latex_code = nx.to_latex(
[H1, H2, H3, H4],
n_rows=2,
sub_captions=captions,
sub_labels=labels,
)
# print(latex_code)
assert "begin{document}" in latex_code
assert "begin{figure}" in latex_code
assert latex_code.count("begin{subfigure}") == 4
assert latex_code.count("tikzpicture") == 8
assert latex_code.count("[-]") == 4
def test_basic_tikz():
expected_tex = r"""\documentclass{report}
\usepackage{tikz}
\usepackage{subcaption}
\begin{document}
\begin{figure}
\begin{subfigure}{0.5\textwidth}
\begin{tikzpicture}[scale=2]
\draw[gray!90]
(0.749, 0.702) node[red!90] (0){0}
(1.0, -0.014) node[red!90] (1){1}
(-0.777, -0.705) node (2){2}
(-0.984, 0.042) node (3){3}
(-0.028, 0.375) node[cyan!90] (4){4}
(-0.412, 0.888) node (5){5}
(0.448, -0.856) node (6){6}
(0.003, -0.431) node[cyan!90] (7){7};
\begin{scope}[->,gray!90]
\draw (0) to (4);
\draw (0) to (5);
\draw (0) to (6);
\draw (0) to (7);
\draw (1) to (4);
\draw (1) to (5);
\draw (1) to (6);
\draw (1) to (7);
\draw (2) to (4);
\draw (2) to (5);
\draw (2) to (6);
\draw (2) to (7);
\draw (3) to (4);
\draw (3) to (5);
\draw (3) to (6);
\draw (3) to (7);
\end{scope}
\end{tikzpicture}
\caption{My tikz number 1 of 2}\label{tikz_1_2}
\end{subfigure}
\begin{subfigure}{0.5\textwidth}
\begin{tikzpicture}[scale=2]
\draw[gray!90]
(0.749, 0.702) node[green!90] (0){0}
(1.0, -0.014) node[green!90] (1){1}
(-0.777, -0.705) node (2){2}
(-0.984, 0.042) node (3){3}
(-0.028, 0.375) node[purple!90] (4){4}
(-0.412, 0.888) node (5){5}
(0.448, -0.856) node (6){6}
(0.003, -0.431) node[purple!90] (7){7};
\begin{scope}[->,gray!90]
\draw (0) to (4);
\draw (0) to (5);
\draw (0) to (6);
\draw (0) to (7);
\draw (1) to (4);
\draw (1) to (5);
\draw (1) to (6);
\draw (1) to (7);
\draw (2) to (4);
\draw (2) to (5);
\draw (2) to (6);
\draw (2) to (7);
\draw (3) to (4);
\draw (3) to (5);
\draw (3) to (6);
\draw (3) to (7);
\end{scope}
\end{tikzpicture}
\caption{My tikz number 2 of 2}\label{tikz_2_2}
\end{subfigure}
\caption{A graph generated with python and latex.}
\end{figure}
\end{document}"""
edges = [
(0, 4),
(0, 5),
(0, 6),
(0, 7),
(1, 4),
(1, 5),
(1, 6),
(1, 7),
(2, 4),
(2, 5),
(2, 6),
(2, 7),
(3, 4),
(3, 5),
(3, 6),
(3, 7),
]
G = nx.DiGraph()
G.add_nodes_from(range(8))
G.add_edges_from(edges)
pos = {
0: (0.7490296171687696, 0.702353520257394),
1: (1.0, -0.014221357723796535),
2: (-0.7765783344161441, -0.7054170966808919),
3: (-0.9842690223417624, 0.04177547602465483),
4: (-0.02768523817180917, 0.3745724439551441),
5: (-0.41154855146767433, 0.8880106515525136),
6: (0.44780153389148264, -0.8561492709269164),
7: (0.0032499953371383505, -0.43092436645809945),
}
rc_node_color = {0: "red!90", 1: "red!90", 4: "cyan!90", 7: "cyan!90"}
gp_node_color = {0: "green!90", 1: "green!90", 4: "purple!90", 7: "purple!90"}
H = G.copy()
nx.set_node_attributes(G, rc_node_color, "color")
nx.set_node_attributes(H, gp_node_color, "color")
sub_captions = ["My tikz number 1 of 2", "My tikz number 2 of 2"]
sub_labels = ["tikz_1_2", "tikz_2_2"]
output_tex = nx.to_latex(
[G, H],
[pos, pos],
tikz_options="[scale=2]",
default_node_options="gray!90",
default_edge_options="gray!90",
node_options="color",
sub_captions=sub_captions,
sub_labels=sub_labels,
caption="A graph generated with python and latex.",
n_rows=2,
as_document=True,
)
assert output_tex == expected_tex
# print(output_tex)
# # Pretty way to assert that A.to_document() == expected_tex
# content_same = True
# for aa, bb in zip(expected_tex.split("\n"), output_tex.split("\n")):
# if aa != bb:
# content_same = False
# print(f"-{aa}|\n+{bb}|")
# assert content_same
def test_exception_pos_single_graph(to_latex=nx.to_latex):
# smoke test that pos can be a string
G = nx.path_graph(4)
to_latex(G, pos="pos")
# must include all nodes
pos = {0: (1, 2), 1: (0, 1), 2: (2, 1)}
with pytest.raises(nx.NetworkXError):
to_latex(G, pos)
# must have 2 values
pos[3] = (1, 2, 3)
with pytest.raises(nx.NetworkXError):
to_latex(G, pos)
pos[3] = 2
with pytest.raises(nx.NetworkXError):
to_latex(G, pos)
# check that passes with 2 values
pos[3] = (3, 2)
to_latex(G, pos)
def test_exception_multiple_graphs(to_latex=nx.to_latex):
G = nx.path_graph(3)
pos_bad = {0: (1, 2), 1: (0, 1)}
pos_OK = {0: (1, 2), 1: (0, 1), 2: (2, 1)}
fourG = [G, G, G, G]
fourpos = [pos_OK, pos_OK, pos_OK, pos_OK]
# input single dict to use for all graphs
to_latex(fourG, pos_OK)
with pytest.raises(nx.NetworkXError):
to_latex(fourG, pos_bad)
# input list of dicts to use for all graphs
to_latex(fourG, fourpos)
with pytest.raises(nx.NetworkXError):
to_latex(fourG, [pos_bad, pos_bad, pos_bad, pos_bad])
# every pos dict must include all nodes
with pytest.raises(nx.NetworkXError):
to_latex(fourG, [pos_OK, pos_OK, pos_bad, pos_OK])
# test sub_captions and sub_labels (len must match Gbunch)
with pytest.raises(nx.NetworkXError):
to_latex(fourG, fourpos, sub_captions=["hi", "hi"])
with pytest.raises(nx.NetworkXError):
to_latex(fourG, fourpos, sub_labels=["hi", "hi"])
# all pass
to_latex(fourG, fourpos, sub_captions=["hi"] * 4, sub_labels=["lbl"] * 4)
def test_exception_multigraph():
G = nx.path_graph(4, create_using=nx.MultiGraph)
G.add_edge(1, 2)
with pytest.raises(nx.NetworkXNotImplemented):
nx.to_latex(G)

View File

@@ -0,0 +1,469 @@
"""Unit tests for layout functions."""
import pytest
import networkx as nx
np = pytest.importorskip("numpy")
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
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)
nx.arf_layout(G)
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)
nx.arf_layout(G)
def check_scale_and_center(self, pos, scale, center):
center = np.array(center)
low = center - scale
hi = center + scale
vpos = np.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_array(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 np.linalg.norm(vpos[3]) <= 1 # ensure node 3 fits (#3753)
vpos = nx.shell_layout(G, [[0], [1, 2], [3]], rotate=0)
assert np.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_smoke_initial_pos_arf(self):
pos = nx.circular_layout(self.Gi)
npos = nx.arf_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 pos[(0, 0)][axis] == pytest.approx(npos[(0, 0)][axis], abs=1e-7)
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 == {}
vpos = nx.arf_layout(G)
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 = np.array([4.0, 7.0])
invdist = 1 / np.array([[0.1, 2.0], [2.0, 0.3]])
cost, grad = costfn(pos, np, invdist, meanweight=0, dim=1)
assert cost == pytest.approx(((3 / 2.0 - 1) ** 2), abs=1e-7)
assert grad[0] == pytest.approx((-0.5), abs=1e-7)
assert grad[1] == pytest.approx(0.5, abs=1e-7)
def check_kamada_kawai_costfn(self, pos, invdist, meanwt, dim):
costfn = nx.drawing.layout._kamada_kawai_costfn
cost, grad = costfn(pos.ravel(), np, invdist, meanweight=meanwt, dim=dim)
expected_cost = 0.5 * meanwt * np.sum(np.sum(pos, axis=0) ** 2)
for i in range(pos.shape[0]):
for j in range(i + 1, pos.shape[0]):
diff = np.linalg.norm(pos[i] - pos[j])
expected_cost += (diff * invdist[i][j] - 1.0) ** 2
assert cost == pytest.approx(expected_cost, abs=1e-7)
dx = 1e-4
for nd in range(pos.shape[0]):
for dm in range(pos.shape[1]):
idx = nd * pos.shape[1] + dm
ps = pos.flatten()
ps[idx] += dx
cplus = costfn(ps, np, invdist, meanweight=meanwt, dim=pos.shape[1])[0]
ps[idx] -= 2 * dx
cminus = costfn(ps, np, invdist, meanweight=meanwt, dim=pos.shape[1])[0]
assert grad[idx] == pytest.approx((cplus - cminus) / (2 * dx), abs=1e-5)
def test_kamada_kawai_costfn(self):
invdist = 1 / np.array([[0.1, 2.1, 1.7], [2.1, 0.2, 0.6], [1.7, 0.6, 0.3]])
meanwt = 0.3
# 2d
pos = np.array([[1.3, -3.2], [2.7, -0.3], [5.1, 2.5]])
self.check_kamada_kawai_costfn(pos, invdist, meanwt, 2)
# 3d
pos = np.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 = np.array(list(nx.spiral_layout(G, resolution=0.35).values()))
pos_tighter = np.array(list(nx.spiral_layout(G, resolution=0.34).values()))
distances = np.linalg.norm(pos_standard[:-1] - pos_standard[1:], axis=1)
distances_tighter = np.linalg.norm(pos_tighter[:-1] - pos_tighter[1:], axis=1)
assert sum(distances) > sum(distances_tighter)
# return near-equidistant points after the first value if set to true
pos_equidistant = np.array(list(nx.spiral_layout(G, equidistant=True).values()))
distances_equidistant = np.linalg.norm(
pos_equidistant[:-1] - pos_equidistant[1:], axis=1
)
assert np.allclose(
distances_equidistant[1:], distances_equidistant[-1], atol=0.01
)
def test_spiral_layout_equidistant(self):
G = nx.path_graph(10)
pos = nx.spiral_layout(G, equidistant=True)
# Extract individual node positions as an array
p = np.array(list(pos.values()))
# Elementwise-distance between node positions
dist = np.linalg.norm(p[1:] - p[:-1], axis=1)
assert np.allclose(np.diff(dist), 0, atol=1e-3)
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)
assert np.linalg.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)
expectation = {
0: np.array((-1, -1)),
1: np.array((1, 1)),
2: np.array((0, 0)),
}
for k, v in expectation.items():
assert (s_vpos[k] == v).all()
s_vpos = nx.rescale_layout_dict(vpos, scale=2)
expectation = {
0: np.array((-2, -2)),
1: np.array((2, 2)),
2: np.array((0, 0)),
}
for k, v in expectation.items():
assert (s_vpos[k] == v).all()
def test_arf_layout_partial_input_test(self):
"""
Checks whether partial pos input still returns a proper position.
"""
G = self.Gs
node = nx.utils.arbitrary_element(G)
pos = nx.circular_layout(G)
del pos[node]
pos = nx.arf_layout(G, pos=pos)
assert len(pos) == len(G)
def test_arf_layout_negative_a_check(self):
"""
Checks input parameters correctly raises errors. For example, `a` should be larger than 1
"""
G = self.Gs
pytest.raises(ValueError, nx.arf_layout, G=G, a=-1)
def test_multipartite_layout_nonnumeric_partition_labels():
"""See gh-5123."""
G = nx.Graph()
G.add_node(0, subset="s0")
G.add_node(1, subset="s0")
G.add_node(2, subset="s1")
G.add_node(3, subset="s1")
G.add_edges_from([(0, 2), (0, 3), (1, 2)])
pos = nx.multipartite_layout(G)
assert len(pos) == len(G)
def test_multipartite_layout_layer_order():
"""Return the layers in sorted order if the layers of the multipartite
graph are sortable. See gh-5691"""
G = nx.Graph()
for node, layer in zip(("a", "b", "c", "d", "e"), (2, 3, 1, 2, 4)):
G.add_node(node, subset=layer)
# Horizontal alignment, therefore y-coord determines layers
pos = nx.multipartite_layout(G, align="horizontal")
# Nodes "a" and "d" are in the same layer
assert pos["a"][-1] == pos["d"][-1]
# positions should be sorted according to layer
assert pos["c"][-1] < pos["a"][-1] < pos["b"][-1] < pos["e"][-1]
# Make sure that multipartite_layout still works when layers are not sortable
G.nodes["a"]["subset"] = "layer_0" # Can't sort mixed strs/ints
pos_nosort = nx.multipartite_layout(G) # smoke test: this should not raise
assert pos_nosort.keys() == pos.keys()

View File

@@ -0,0 +1,190 @@
"""Unit tests for pydot drawing functions."""
import os
import tempfile
from io import StringIO
import pytest
import networkx as nx
from networkx.utils import graphs_equal
pydot = pytest.importorskip("pydot")
@pytest.mark.xfail
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)
fd, fname = tempfile.mkstemp()
# Serialize this "pydot.Dot" instance to a temporary file in dot format
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)
os.close(fd)
os.unlink(fname)
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)
def test_pydot_issue_258():
G = nx.Graph([("Example:A", 1)])
with pytest.raises(ValueError):
nx.nx_pydot.to_pydot(G)
with pytest.raises(ValueError):
nx.nx_pydot.pydot_layout(G)
G = nx.Graph()
G.add_node("1.2", style="filled", fillcolor="red:yellow")
with pytest.raises(ValueError):
nx.nx_pydot.to_pydot(G)
G.remove_node("1.2")
G.add_node("1.2", style="filled", fillcolor='"red:yellow"')
assert (
G.nodes.data() == nx.nx_pydot.from_pydot(nx.nx_pydot.to_pydot(G)).nodes.data()
)
G = nx.DiGraph()
G.add_edge("1", "2", foo="bar:1")
with pytest.raises(ValueError):
nx.nx_pydot.to_pydot(G)
G = nx.DiGraph()
G.add_edge("1", "2", foo='"bar:1"')
assert G["1"]["2"] == nx.nx_pydot.from_pydot(nx.nx_pydot.to_pydot(G))["1"]["2"]
G = nx.MultiGraph()
G.add_edge("1", "2", foo="b:1")
G.add_edge("1", "2", bar="foo:foo")
with pytest.raises(ValueError):
nx.nx_pydot.to_pydot(G)
G = nx.MultiGraph()
G.add_edge("1", "2", foo='"b:1"')
G.add_edge("1", "2", bar='"foo:foo"')
# Keys as integers aren't preserved in the conversion. They are read as strings.
assert [attr for _, _, attr in G.edges.data()] == [
attr
for _, _, attr in nx.nx_pydot.from_pydot(nx.nx_pydot.to_pydot(G)).edges.data()
]
G = nx.Graph()
G.add_edge("1", "2")
G["1"]["2"]["f:oo"] = "bar"
with pytest.raises(ValueError):
nx.nx_pydot.to_pydot(G)
G = nx.Graph()
G.add_edge("1", "2")
G["1"]["2"]['"f:oo"'] = "bar"
assert G["1"]["2"] == nx.nx_pydot.from_pydot(nx.nx_pydot.to_pydot(G))["1"]["2"]
G = nx.Graph([('"Example:A"', 1)])
layout = nx.nx_pydot.pydot_layout(G)
assert isinstance(layout, dict)
@pytest.mark.parametrize(
"graph_type", [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph]
)
def test_hashable_pydot(graph_type):
# gh-5790
G = graph_type()
G.add_edge("5", frozenset([1]), t='"Example:A"', l=False)
G.add_edge("1", 2, w=True, t=("node1",), l=frozenset(["node1"]))
G.add_edge("node", (3, 3), w="string")
assert [
{"t": '"Example:A"', "l": "False"},
{"w": "True", "t": "('node1',)", "l": "frozenset({'node1'})"},
{"w": "string"},
] == [
attr
for _, _, attr in nx.nx_pydot.from_pydot(nx.nx_pydot.to_pydot(G)).edges.data()
]
assert {str(i) for i in G.nodes()} == set(
nx.nx_pydot.from_pydot(nx.nx_pydot.to_pydot(G)).nodes
)
def test_pydot_numrical_name():
G = nx.Graph()
G.add_edges_from([("A", "B"), (0, 1)])
graph_layout = nx.nx_pydot.pydot_layout(G, prog="dot")
assert isinstance(graph_layout, dict)
assert "0" not in graph_layout
assert 0 in graph_layout
assert "1" not in graph_layout
assert 1 in graph_layout
assert "A" in graph_layout
assert "B" in graph_layout

View File

@@ -0,0 +1,791 @@
"""Unit tests for matplotlib drawing functions."""
import itertools
import os
import warnings
import pytest
mpl = pytest.importorskip("matplotlib")
np = pytest.importorskip("numpy")
mpl.use("PS")
plt = pytest.importorskip("matplotlib.pyplot")
plt.rcParams["text.usetex"] = False
import networkx as nx
barbell = nx.barbell_graph(4, 6)
def test_draw():
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(barbell, **option)
plt.savefig("test.ps")
finally:
try:
os.unlink("test.ps")
except OSError:
pass
def test_draw_shell_nlist():
try:
nlist = [list(range(4)), list(range(4, 10)), list(range(10, 14))]
nx.draw_shell(barbell, nlist=nlist)
plt.savefig("test.ps")
finally:
try:
os.unlink("test.ps")
except OSError:
pass
def test_edge_colormap():
colors = range(barbell.number_of_edges())
nx.draw_spring(
barbell, edge_color=colors, width=4, edge_cmap=plt.cm.Blues, with_labels=True
)
# plt.show()
def test_arrows():
nx.draw_spring(barbell.to_directed())
# plt.show()
@pytest.mark.parametrize(
("edge_color", "expected"),
(
(None, "black"), # Default
("r", "red"), # Non-default color string
(["r"], "red"), # Single non-default color in a list
((1.0, 1.0, 0.0), "yellow"), # single color as rgb tuple
([(1.0, 1.0, 0.0)], "yellow"), # single color as rgb tuple in list
((0, 1, 0, 1), "lime"), # single color as rgba tuple
([(0, 1, 0, 1)], "lime"), # single color as rgba tuple in list
("#0000ff", "blue"), # single color hex code
(["#0000ff"], "blue"), # hex code in list
),
)
@pytest.mark.parametrize("edgelist", (None, [(0, 1)]))
def test_single_edge_color_undirected(edge_color, expected, edgelist):
"""Tests ways of specifying all edges have a single color for edges
drawn with a LineCollection"""
G = nx.path_graph(3)
drawn_edges = nx.draw_networkx_edges(
G, pos=nx.random_layout(G), edgelist=edgelist, edge_color=edge_color
)
assert mpl.colors.same_color(drawn_edges.get_color(), expected)
@pytest.mark.parametrize(
("edge_color", "expected"),
(
(None, "black"), # Default
("r", "red"), # Non-default color string
(["r"], "red"), # Single non-default color in a list
((1.0, 1.0, 0.0), "yellow"), # single color as rgb tuple
([(1.0, 1.0, 0.0)], "yellow"), # single color as rgb tuple in list
((0, 1, 0, 1), "lime"), # single color as rgba tuple
([(0, 1, 0, 1)], "lime"), # single color as rgba tuple in list
("#0000ff", "blue"), # single color hex code
(["#0000ff"], "blue"), # hex code in list
),
)
@pytest.mark.parametrize("edgelist", (None, [(0, 1)]))
def test_single_edge_color_directed(edge_color, expected, edgelist):
"""Tests ways of specifying all edges have a single color for edges drawn
with FancyArrowPatches"""
G = nx.path_graph(3, create_using=nx.DiGraph)
drawn_edges = nx.draw_networkx_edges(
G, pos=nx.random_layout(G), edgelist=edgelist, edge_color=edge_color
)
for fap in drawn_edges:
assert mpl.colors.same_color(fap.get_edgecolor(), expected)
def test_edge_color_tuple_interpretation():
"""If edge_color is a sequence with the same length as edgelist, then each
value in edge_color is mapped onto each edge via colormap."""
G = nx.path_graph(6, create_using=nx.DiGraph)
pos = {n: (n, n) for n in range(len(G))}
# num edges != 3 or 4 --> edge_color interpreted as rgb(a)
for ec in ((0, 0, 1), (0, 0, 1, 1)):
# More than 4 edges
drawn_edges = nx.draw_networkx_edges(G, pos, edge_color=ec)
for fap in drawn_edges:
assert mpl.colors.same_color(fap.get_edgecolor(), ec)
# Fewer than 3 edges
drawn_edges = nx.draw_networkx_edges(
G, pos, edgelist=[(0, 1), (1, 2)], edge_color=ec
)
for fap in drawn_edges:
assert mpl.colors.same_color(fap.get_edgecolor(), ec)
# num edges == 3, len(edge_color) == 4: interpreted as rgba
drawn_edges = nx.draw_networkx_edges(
G, pos, edgelist=[(0, 1), (1, 2), (2, 3)], edge_color=(0, 0, 1, 1)
)
for fap in drawn_edges:
assert mpl.colors.same_color(fap.get_edgecolor(), "blue")
# num edges == 4, len(edge_color) == 3: interpreted as rgb
drawn_edges = nx.draw_networkx_edges(
G, pos, edgelist=[(0, 1), (1, 2), (2, 3), (3, 4)], edge_color=(0, 0, 1)
)
for fap in drawn_edges:
assert mpl.colors.same_color(fap.get_edgecolor(), "blue")
# num edges == len(edge_color) == 3: interpreted with cmap, *not* as rgb
drawn_edges = nx.draw_networkx_edges(
G, pos, edgelist=[(0, 1), (1, 2), (2, 3)], edge_color=(0, 0, 1)
)
assert mpl.colors.same_color(
drawn_edges[0].get_edgecolor(), drawn_edges[1].get_edgecolor()
)
for fap in drawn_edges:
assert not mpl.colors.same_color(fap.get_edgecolor(), "blue")
# num edges == len(edge_color) == 4: interpreted with cmap, *not* as rgba
drawn_edges = nx.draw_networkx_edges(
G, pos, edgelist=[(0, 1), (1, 2), (2, 3), (3, 4)], edge_color=(0, 0, 1, 1)
)
assert mpl.colors.same_color(
drawn_edges[0].get_edgecolor(), drawn_edges[1].get_edgecolor()
)
assert mpl.colors.same_color(
drawn_edges[2].get_edgecolor(), drawn_edges[3].get_edgecolor()
)
for fap in drawn_edges:
assert not mpl.colors.same_color(fap.get_edgecolor(), "blue")
def test_fewer_edge_colors_than_num_edges_directed():
"""Test that the edge colors are cycled when there are fewer specified
colors than edges."""
G = barbell.to_directed()
pos = nx.random_layout(barbell)
edgecolors = ("r", "g", "b")
drawn_edges = nx.draw_networkx_edges(G, pos, edge_color=edgecolors)
for fap, expected in zip(drawn_edges, itertools.cycle(edgecolors)):
assert mpl.colors.same_color(fap.get_edgecolor(), expected)
def test_more_edge_colors_than_num_edges_directed():
"""Test that extra edge colors are ignored when there are more specified
colors than edges."""
G = nx.path_graph(4, create_using=nx.DiGraph) # 3 edges
pos = nx.random_layout(barbell)
edgecolors = ("r", "g", "b", "c") # 4 edge colors
drawn_edges = nx.draw_networkx_edges(G, pos, edge_color=edgecolors)
for fap, expected in zip(drawn_edges, edgecolors[:-1]):
assert mpl.colors.same_color(fap.get_edgecolor(), expected)
def test_edge_color_string_with_global_alpha_undirected():
edge_collection = nx.draw_networkx_edges(
barbell,
pos=nx.random_layout(barbell),
edgelist=[(0, 1), (1, 2)],
edge_color="purple",
alpha=0.2,
)
ec = edge_collection.get_color().squeeze() # as rgba tuple
assert len(edge_collection.get_paths()) == 2
assert mpl.colors.same_color(ec[:-1], "purple")
assert ec[-1] == 0.2
def test_edge_color_string_with_global_alpha_directed():
drawn_edges = nx.draw_networkx_edges(
barbell.to_directed(),
pos=nx.random_layout(barbell),
edgelist=[(0, 1), (1, 2)],
edge_color="purple",
alpha=0.2,
)
assert len(drawn_edges) == 2
for fap in drawn_edges:
ec = fap.get_edgecolor() # As rgba tuple
assert mpl.colors.same_color(ec[:-1], "purple")
assert ec[-1] == 0.2
@pytest.mark.parametrize("graph_type", (nx.Graph, nx.DiGraph))
def test_edge_width_default_value(graph_type):
"""Test the default linewidth for edges drawn either via LineCollection or
FancyArrowPatches."""
G = nx.path_graph(2, create_using=graph_type)
pos = {n: (n, n) for n in range(len(G))}
drawn_edges = nx.draw_networkx_edges(G, pos)
if isinstance(drawn_edges, list): # directed case: list of FancyArrowPatch
drawn_edges = drawn_edges[0]
assert drawn_edges.get_linewidth() == 1
@pytest.mark.parametrize(
("edgewidth", "expected"),
(
(3, 3), # single-value, non-default
([3], 3), # Single value as a list
),
)
def test_edge_width_single_value_undirected(edgewidth, expected):
G = nx.path_graph(4)
pos = {n: (n, n) for n in range(len(G))}
drawn_edges = nx.draw_networkx_edges(G, pos, width=edgewidth)
assert len(drawn_edges.get_paths()) == 3
assert drawn_edges.get_linewidth() == expected
@pytest.mark.parametrize(
("edgewidth", "expected"),
(
(3, 3), # single-value, non-default
([3], 3), # Single value as a list
),
)
def test_edge_width_single_value_directed(edgewidth, expected):
G = nx.path_graph(4, create_using=nx.DiGraph)
pos = {n: (n, n) for n in range(len(G))}
drawn_edges = nx.draw_networkx_edges(G, pos, width=edgewidth)
assert len(drawn_edges) == 3
for fap in drawn_edges:
assert fap.get_linewidth() == expected
@pytest.mark.parametrize(
"edgelist",
(
[(0, 1), (1, 2), (2, 3)], # one width specification per edge
None, # fewer widths than edges - widths cycle
[(0, 1), (1, 2)], # More widths than edges - unused widths ignored
),
)
def test_edge_width_sequence(edgelist):
G = barbell.to_directed()
pos = nx.random_layout(G)
widths = (0.5, 2.0, 12.0)
drawn_edges = nx.draw_networkx_edges(G, pos, edgelist=edgelist, width=widths)
for fap, expected_width in zip(drawn_edges, itertools.cycle(widths)):
assert fap.get_linewidth() == expected_width
def test_edge_color_with_edge_vmin_vmax():
"""Test that edge_vmin and edge_vmax properly set the dynamic range of the
color map when num edges == len(edge_colors)."""
G = nx.path_graph(3, create_using=nx.DiGraph)
pos = nx.random_layout(G)
# Extract colors from the original (unscaled) colormap
drawn_edges = nx.draw_networkx_edges(G, pos, edge_color=[0, 1.0])
orig_colors = [e.get_edgecolor() for e in drawn_edges]
# Colors from scaled colormap
drawn_edges = nx.draw_networkx_edges(
G, pos, edge_color=[0.2, 0.8], edge_vmin=0.2, edge_vmax=0.8
)
scaled_colors = [e.get_edgecolor() for e in drawn_edges]
assert mpl.colors.same_color(orig_colors, scaled_colors)
def test_directed_edges_linestyle_default():
"""Test default linestyle for edges drawn with FancyArrowPatches."""
G = nx.path_graph(4, create_using=nx.DiGraph) # Graph with 3 edges
pos = {n: (n, n) for n in range(len(G))}
# edge with default style
drawn_edges = nx.draw_networkx_edges(G, pos)
assert len(drawn_edges) == 3
for fap in drawn_edges:
assert fap.get_linestyle() == "solid"
@pytest.mark.parametrize(
"style",
(
"dashed", # edge with string style
"--", # edge with simplified string style
(1, (1, 1)), # edge with (offset, onoffseq) style
),
)
def test_directed_edges_linestyle_single_value(style):
"""Tests support for specifying linestyles with a single value to be applied to
all edges in ``draw_networkx_edges`` for FancyArrowPatch outputs
(e.g. directed edges)."""
G = nx.path_graph(4, create_using=nx.DiGraph) # Graph with 3 edges
pos = {n: (n, n) for n in range(len(G))}
drawn_edges = nx.draw_networkx_edges(G, pos, style=style)
assert len(drawn_edges) == 3
for fap in drawn_edges:
assert fap.get_linestyle() == style
@pytest.mark.parametrize(
"style_seq",
(
["dashed"], # edge with string style in list
["--"], # edge with simplified string style in list
[(1, (1, 1))], # edge with (offset, onoffseq) style in list
["--", "-", ":"], # edges with styles for each edge
["--", "-"], # edges with fewer styles than edges (styles cycle)
["--", "-", ":", "-."], # edges with more styles than edges (extra unused)
),
)
def test_directed_edges_linestyle_sequence(style_seq):
"""Tests support for specifying linestyles with sequences in
``draw_networkx_edges`` for FancyArrowPatch outputs (e.g. directed edges)."""
G = nx.path_graph(4, create_using=nx.DiGraph) # Graph with 3 edges
pos = {n: (n, n) for n in range(len(G))}
drawn_edges = nx.draw_networkx_edges(G, pos, style=style_seq)
assert len(drawn_edges) == 3
for fap, style in zip(drawn_edges, itertools.cycle(style_seq)):
assert fap.get_linestyle() == style
def test_labels_and_colors():
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)],
arrows=True,
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()
@pytest.mark.mpl_image_compare
def test_house_with_colors():
G = nx.house_graph()
# explicitly set positions
fig, ax = plt.subplots()
pos = {0: (0, 0), 1: (1, 0), 2: (0, 1), 3: (1, 1), 4: (0.5, 2.0)}
# Plot nodes with different properties for the "wall" and "roof" nodes
nx.draw_networkx_nodes(
G,
pos,
node_size=3000,
nodelist=[0, 1, 2, 3],
node_color="tab:blue",
)
nx.draw_networkx_nodes(
G, pos, node_size=2000, nodelist=[4], node_color="tab:orange"
)
nx.draw_networkx_edges(G, pos, alpha=0.5, width=6)
# Customize axes
ax.margins(0.11)
plt.tight_layout()
plt.axis("off")
return fig
def test_axes():
fig, ax = plt.subplots()
nx.draw(barbell, ax=ax)
nx.draw_networkx_edge_labels(barbell, nx.circular_layout(barbell), ax=ax)
def test_empty_graph():
G = nx.Graph()
nx.draw(G)
def test_draw_empty_nodes_return_values():
# See Issue #3833
import matplotlib.collections # call as mpl.collections
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=[]), mpl.collections.PathCollection
)
assert isinstance(
nx.draw_networkx_nodes(DG, pos, nodelist=[]), mpl.collections.PathCollection
)
# drawing empty edges used to return an empty LineCollection or empty list.
# Now it is always an empty list (because edges are now lists of FancyArrows)
assert nx.draw_networkx_edges(G, pos, edgelist=[], arrows=True) == []
assert nx.draw_networkx_edges(G, pos, edgelist=[], arrows=False) == []
assert nx.draw_networkx_edges(DG, pos, edgelist=[], arrows=False) == []
assert nx.draw_networkx_edges(DG, pos, edgelist=[], arrows=True) == []
def test_multigraph_edgelist_tuples():
# 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():
pos = nx.random_layout(barbell)
fig = plt.figure()
# with fewer alpha elements than nodes
fig.add_subplot(131) # Each test in a new axis object
nx.draw_networkx_nodes(barbell, pos, alpha=[0.1, 0.2])
# with equal alpha elements and nodes
num_nodes = len(barbell.nodes)
alpha = [x / num_nodes for x in range(num_nodes)]
colors = range(num_nodes)
fig.add_subplot(132)
nx.draw_networkx_nodes(barbell, pos, node_color=colors, alpha=alpha)
# with more alpha elements than nodes
alpha.append(1)
fig.add_subplot(133)
nx.draw_networkx_nodes(barbell, pos, alpha=alpha)
def test_error_invalid_kwds():
with pytest.raises(ValueError, match="Received invalid argument"):
nx.draw(barbell, foo="bar")
def test_draw_networkx_arrowsize_incorrect_size():
G = nx.DiGraph([(0, 1), (0, 2), (0, 3), (1, 3)])
arrowsize = [1, 2, 3]
with pytest.raises(
ValueError, match="arrowsize should have the same length as edgelist"
):
nx.draw(G, arrowsize=arrowsize)
@pytest.mark.parametrize("arrowsize", (30, [10, 20, 30]))
def test_draw_edges_arrowsize(arrowsize):
G = nx.DiGraph([(0, 1), (0, 2), (1, 2)])
pos = {0: (0, 0), 1: (0, 1), 2: (1, 0)}
edges = nx.draw_networkx_edges(G, pos=pos, arrowsize=arrowsize)
arrowsize = itertools.repeat(arrowsize) if isinstance(arrowsize, int) else arrowsize
for fap, expected in zip(edges, arrowsize):
assert isinstance(fap, mpl.patches.FancyArrowPatch)
assert fap.get_mutation_scale() == expected
def test_np_edgelist():
# see issue #4129
nx.draw_networkx(barbell, edgelist=np.array([(0, 2), (0, 3)]))
def test_draw_nodes_missing_node_from_position():
G = nx.path_graph(3)
pos = {0: (0, 0), 1: (1, 1)} # No position for node 2
with pytest.raises(nx.NetworkXError, match="has no position"):
nx.draw_networkx_nodes(G, pos)
# NOTE: parametrizing on marker to test both branches of internal
# nx.draw_networkx_edges.to_marker_edge function
@pytest.mark.parametrize("node_shape", ("o", "s"))
def test_draw_edges_min_source_target_margins(node_shape):
"""Test that there is a wider gap between the node and the start of an
incident edge when min_source_margin is specified.
This test checks that the use of min_{source/target}_margin kwargs result
in shorter (more padding) between the edges and source and target nodes.
As a crude visual example, let 's' and 't' represent source and target
nodes, respectively:
Default:
s-----------------------------t
With margins:
s ----------------------- t
"""
# Create a single axis object to get consistent pixel coords across
# multiple draws
fig, ax = plt.subplots()
G = nx.DiGraph([(0, 1)])
pos = {0: (0, 0), 1: (1, 0)} # horizontal layout
# Get leftmost and rightmost points of the FancyArrowPatch object
# representing the edge between nodes 0 and 1 (in pixel coordinates)
default_patch = nx.draw_networkx_edges(G, pos, ax=ax, node_shape=node_shape)[0]
default_extent = default_patch.get_extents().corners()[::2, 0]
# Now, do the same but with "padding" for the source and target via the
# min_{source/target}_margin kwargs
padded_patch = nx.draw_networkx_edges(
G,
pos,
ax=ax,
node_shape=node_shape,
min_source_margin=100,
min_target_margin=100,
)[0]
padded_extent = padded_patch.get_extents().corners()[::2, 0]
# With padding, the left-most extent of the edge should be further to the
# right
assert padded_extent[0] > default_extent[0]
# And the rightmost extent of the edge, further to the left
assert padded_extent[1] < default_extent[1]
def test_nonzero_selfloop_with_single_node():
"""Ensure that selfloop extent is non-zero when there is only one node."""
# Create explicit axis object for test
fig, ax = plt.subplots()
# Graph with single node + self loop
G = nx.DiGraph()
G.add_node(0)
G.add_edge(0, 0)
# Draw
patch = nx.draw_networkx_edges(G, {0: (0, 0)})[0]
# The resulting patch must have non-zero extent
bbox = patch.get_extents()
assert bbox.width > 0 and bbox.height > 0
# Cleanup
plt.delaxes(ax)
def test_nonzero_selfloop_with_single_edge_in_edgelist():
"""Ensure that selfloop extent is non-zero when only a single edge is
specified in the edgelist.
"""
# Create explicit axis object for test
fig, ax = plt.subplots()
# Graph with selfloop
G = nx.path_graph(2, create_using=nx.DiGraph)
G.add_edge(1, 1)
pos = {n: (n, n) for n in G.nodes}
# Draw only the selfloop edge via the `edgelist` kwarg
patch = nx.draw_networkx_edges(G, pos, edgelist=[(1, 1)])[0]
# The resulting patch must have non-zero extent
bbox = patch.get_extents()
assert bbox.width > 0 and bbox.height > 0
# Cleanup
plt.delaxes(ax)
def test_apply_alpha():
"""Test apply_alpha when there is a mismatch between the number of
supplied colors and elements.
"""
nodelist = [0, 1, 2]
colorlist = ["r", "g", "b"]
alpha = 0.5
rgba_colors = nx.drawing.nx_pylab.apply_alpha(colorlist, alpha, nodelist)
assert all(rgba_colors[:, -1] == alpha)
def test_draw_edges_toggling_with_arrows_kwarg():
"""
The `arrows` keyword argument is used as a 3-way switch to select which
type of object to use for drawing edges:
- ``arrows=None`` -> default (FancyArrowPatches for directed, else LineCollection)
- ``arrows=True`` -> FancyArrowPatches
- ``arrows=False`` -> LineCollection
"""
import matplotlib.collections
import matplotlib.patches
UG = nx.path_graph(3)
DG = nx.path_graph(3, create_using=nx.DiGraph)
pos = {n: (n, n) for n in UG}
# Use FancyArrowPatches when arrows=True, regardless of graph type
for G in (UG, DG):
edges = nx.draw_networkx_edges(G, pos, arrows=True)
assert len(edges) == len(G.edges)
assert isinstance(edges[0], mpl.patches.FancyArrowPatch)
# Use LineCollection when arrows=False, regardless of graph type
for G in (UG, DG):
edges = nx.draw_networkx_edges(G, pos, arrows=False)
assert isinstance(edges, mpl.collections.LineCollection)
# Default behavior when arrows=None: FAPs for directed, LC's for undirected
edges = nx.draw_networkx_edges(UG, pos)
assert isinstance(edges, mpl.collections.LineCollection)
edges = nx.draw_networkx_edges(DG, pos)
assert len(edges) == len(G.edges)
assert isinstance(edges[0], mpl.patches.FancyArrowPatch)
@pytest.mark.parametrize("drawing_func", (nx.draw, nx.draw_networkx))
def test_draw_networkx_arrows_default_undirected(drawing_func):
import matplotlib.collections
G = nx.path_graph(3)
fig, ax = plt.subplots()
drawing_func(G, ax=ax)
assert any(isinstance(c, mpl.collections.LineCollection) for c in ax.collections)
assert not ax.patches
plt.delaxes(ax)
@pytest.mark.parametrize("drawing_func", (nx.draw, nx.draw_networkx))
def test_draw_networkx_arrows_default_directed(drawing_func):
import matplotlib.collections
G = nx.path_graph(3, create_using=nx.DiGraph)
fig, ax = plt.subplots()
drawing_func(G, ax=ax)
assert not any(
isinstance(c, mpl.collections.LineCollection) for c in ax.collections
)
assert ax.patches
plt.delaxes(ax)
def test_edgelist_kwarg_not_ignored():
# See gh-4994
G = nx.path_graph(3)
G.add_edge(0, 0)
fig, ax = plt.subplots()
nx.draw(G, edgelist=[(0, 1), (1, 2)], ax=ax) # Exclude self-loop from edgelist
assert not ax.patches
plt.delaxes(ax)
def test_draw_networkx_edge_label_multiedge_exception():
"""
draw_networkx_edge_labels should raise an informative error message when
the edge label includes keys
"""
exception_msg = "draw_networkx_edge_labels does not support multiedges"
G = nx.MultiGraph()
G.add_edge(0, 1, weight=10)
G.add_edge(0, 1, weight=20)
edge_labels = nx.get_edge_attributes(G, "weight") # Includes edge keys
pos = {n: (n, n) for n in G}
with pytest.raises(nx.NetworkXError, match=exception_msg):
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels)
def test_draw_networkx_edge_label_empty_dict():
"""Regression test for draw_networkx_edge_labels with empty dict. See
gh-5372."""
G = nx.path_graph(3)
pos = {n: (n, n) for n in G.nodes}
assert nx.draw_networkx_edge_labels(G, pos, edge_labels={}) == {}
def test_draw_networkx_edges_undirected_selfloop_colors():
"""When an edgelist is supplied along with a sequence of colors, check that
the self-loops have the correct colors."""
fig, ax = plt.subplots()
# Edge list and corresponding colors
edgelist = [(1, 3), (1, 2), (2, 3), (1, 1), (3, 3), (2, 2)]
edge_colors = ["pink", "cyan", "black", "red", "blue", "green"]
G = nx.Graph(edgelist)
pos = {n: (n, n) for n in G.nodes}
nx.draw_networkx_edges(G, pos, ax=ax, edgelist=edgelist, edge_color=edge_colors)
# Verify that there are three fancy arrow patches (1 per self loop)
assert len(ax.patches) == 3
# These are points that should be contained in the self loops. For example,
# sl_points[0] will be (1, 1.1), which is inside the "path" of the first
# self-loop but outside the others
sl_points = np.array(edgelist[-3:]) + np.array([0, 0.1])
# Check that the mapping between self-loop locations and their colors is
# correct
for fap, clr, slp in zip(ax.patches, edge_colors[-3:], sl_points):
assert fap.get_path().contains_point(slp)
assert mpl.colors.same_color(fap.get_edgecolor(), clr)
plt.delaxes(ax)
@pytest.mark.parametrize(
"fap_only_kwarg", # Non-default values for kwargs that only apply to FAPs
(
{"arrowstyle": "-"},
{"arrowsize": 20},
{"connectionstyle": "arc3,rad=0.2"},
{"min_source_margin": 10},
{"min_target_margin": 10},
),
)
def test_user_warnings_for_unused_edge_drawing_kwargs(fap_only_kwarg):
"""Users should get a warning when they specify a non-default value for
one of the kwargs that applies only to edges drawn with FancyArrowPatches,
but FancyArrowPatches aren't being used under the hood."""
G = nx.path_graph(3)
pos = {n: (n, n) for n in G}
fig, ax = plt.subplots()
# By default, an undirected graph will use LineCollection to represent
# the edges
kwarg_name = list(fap_only_kwarg.keys())[0]
with pytest.warns(
UserWarning, match=f"\n\nThe {kwarg_name} keyword argument is not applicable"
):
nx.draw_networkx_edges(G, pos, ax=ax, **fap_only_kwarg)
# FancyArrowPatches are always used when `arrows=True` is specified.
# Check that warnings are *not* raised in this case
with warnings.catch_warnings():
# Escalate warnings -> errors so tests fail if warnings are raised
warnings.simplefilter("error")
nx.draw_networkx_edges(G, pos, ax=ax, arrows=True, **fap_only_kwarg)
plt.delaxes(ax)