about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/networkx/drawing/tests
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/networkx/drawing/tests
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/networkx/drawing/tests')
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/drawing/tests/__init__.py0
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/drawing/tests/baseline/test_house_with_colors.pngbin0 -> 21918 bytes
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_agraph.py241
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_latex.py292
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_layout.py538
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_pydot.py146
-rw-r--r--.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_pylab.py1029
7 files changed, 2246 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/networkx/drawing/tests/__init__.py b/.venv/lib/python3.12/site-packages/networkx/drawing/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/drawing/tests/__init__.py
diff --git a/.venv/lib/python3.12/site-packages/networkx/drawing/tests/baseline/test_house_with_colors.png b/.venv/lib/python3.12/site-packages/networkx/drawing/tests/baseline/test_house_with_colors.png
new file mode 100644
index 00000000..31f4962e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/drawing/tests/baseline/test_house_with_colors.png
Binary files differdiff --git a/.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_agraph.py b/.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_agraph.py
new file mode 100644
index 00000000..b351a1d9
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_agraph.py
@@ -0,0 +1,241 @@
+"""Unit tests for PyGraphviz interface."""
+
+import warnings
+
+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"]
+
+    @pytest.mark.parametrize(
+        "G", (nx.Graph(), nx.DiGraph(), nx.MultiGraph(), nx.MultiDiGraph())
+    )
+    def test_agraph_roundtripping(self, G, tmp_path):
+        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 = tmp_path / "test.dot"
+        nx.drawing.nx_agraph.write_dot(H, fname)
+        Hin = nx.nx_agraph.read_dot(fname)
+        self.assert_equal(H, Hin)
+
+        fname = tmp_path / "fh_test.dot"
+        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)
+        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_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_no_warnings_raised(self):
+        # Test that no warnings are raised when Networkx graph
+        # is converted to Pygraphviz graph and 'pos'
+        # attribute is given
+        G = nx.Graph()
+        G.add_node(0, pos=(0, 0))
+        G.add_node(1, pos=(1, 1))
+        A = nx.nx_agraph.to_agraph(G)
+        with warnings.catch_warnings(record=True) as record:
+            A.layout()
+        assert len(record) == 0
diff --git a/.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_latex.py b/.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_latex.py
new file mode 100644
index 00000000..14ab5423
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_latex.py
@@ -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)
diff --git a/.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_layout.py b/.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_layout.py
new file mode 100644
index 00000000..7f0412ce
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_layout.py
@@ -0,0 +1,538 @@
+"""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.forceatlas2_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.forceatlas2_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_forceatlas2(self):
+        pos = nx.circular_layout(self.Gi)
+        npos = nx.forceatlas2_layout(self.Gi, pos=pos)
+
+    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.forceatlas2_layout(G)
+        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_forceatlas2_layout_partial_input_test(self):
+        # check whether partial pos input still returns a full proper position
+        G = self.Gs
+        node = nx.utils.arbitrary_element(G)
+        pos = nx.circular_layout(G)
+        del pos[node]
+        pos = nx.forceatlas2_layout(G, pos=pos)
+        assert len(pos) == len(G)
+
+    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_smoke_seed_input(self):
+        G = self.Gs
+        nx.random_layout(G, seed=42)
+        nx.spring_layout(G, seed=42)
+        nx.arf_layout(G, seed=42)
+        nx.forceatlas2_layout(G, seed=42)
+
+
+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()
+    node_group = dict(zip(("a", "b", "c", "d", "e"), (2, 3, 1, 2, 4)))
+    for node, layer in node_group.items():
+        G.add_node(node, subset=layer)
+
+    # Horizontal alignment, therefore y-coord determines layers
+    pos = nx.multipartite_layout(G, align="horizontal")
+
+    layers = nx.utils.groups(node_group)
+    pos_from_layers = nx.multipartite_layout(G, align="horizontal", subset_key=layers)
+    for (n1, p1), (n2, p2) in zip(pos.items(), pos_from_layers.items()):
+        assert n1 == n2 and (p1 == p2).all()
+
+    # 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()
+
+
+def _num_nodes_per_bfs_layer(pos):
+    """Helper function to extract the number of nodes in each layer of bfs_layout"""
+    x = np.array(list(pos.values()))[:, 0]  # node positions in layered dimension
+    _, layer_count = np.unique(x, return_counts=True)
+    return layer_count
+
+
+@pytest.mark.parametrize("n", range(2, 7))
+def test_bfs_layout_complete_graph(n):
+    """The complete graph should result in two layers: the starting node and
+    a second layer containing all neighbors."""
+    G = nx.complete_graph(n)
+    pos = nx.bfs_layout(G, start=0)
+    assert np.array_equal(_num_nodes_per_bfs_layer(pos), [1, n - 1])
+
+
+def test_bfs_layout_barbell():
+    G = nx.barbell_graph(5, 3)
+    # Start in one of the "bells"
+    pos = nx.bfs_layout(G, start=0)
+    # start, bell-1, [1] * len(bar)+1, bell-1
+    expected_nodes_per_layer = [1, 4, 1, 1, 1, 1, 4]
+    assert np.array_equal(_num_nodes_per_bfs_layer(pos), expected_nodes_per_layer)
+    # Start in the other "bell" - expect same layer pattern
+    pos = nx.bfs_layout(G, start=12)
+    assert np.array_equal(_num_nodes_per_bfs_layer(pos), expected_nodes_per_layer)
+    # Starting in the center of the bar, expect layers to be symmetric
+    pos = nx.bfs_layout(G, start=6)
+    # Expected layers: {6 (start)}, {5, 7}, {4, 8}, {8 nodes from remainder of bells}
+    expected_nodes_per_layer = [1, 2, 2, 8]
+    assert np.array_equal(_num_nodes_per_bfs_layer(pos), expected_nodes_per_layer)
+
+
+def test_bfs_layout_disconnected():
+    G = nx.complete_graph(5)
+    G.add_edges_from([(10, 11), (11, 12)])
+    with pytest.raises(nx.NetworkXError, match="bfs_layout didn't include all nodes"):
+        nx.bfs_layout(G, start=0)
diff --git a/.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_pydot.py b/.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_pydot.py
new file mode 100644
index 00000000..acf93d77
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_pydot.py
@@ -0,0 +1,146 @@
+"""Unit tests for pydot drawing functions."""
+
+from io import StringIO
+
+import pytest
+
+import networkx as nx
+from networkx.utils import graphs_equal
+
+pydot = pytest.importorskip("pydot")
+
+
+class TestPydot:
+    @pytest.mark.parametrize("G", (nx.Graph(), nx.DiGraph()))
+    @pytest.mark.parametrize("prog", ("neato", "dot"))
+    def test_pydot(self, G, prog, tmp_path):
+        """
+        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)
+
+        fname = tmp_path / "out.dot"
+
+        # 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)
+
+    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_7581(tmp_path):
+    """Validate that `nx_pydot.pydot_layout` handles nodes
+    with characters like "\n", " ".
+
+    Those characters cause `pydot` to escape and quote them on output,
+    which caused #7581.
+    """
+    G = nx.Graph()
+    G.add_edges_from([("A\nbig test", "B"), ("A\nbig test", "C"), ("B", "C")])
+
+    graph_layout = nx.nx_pydot.pydot_layout(G, prog="dot")
+    assert isinstance(graph_layout, dict)
+
+    # Convert the graph to pydot and back into a graph. There should be no difference.
+    P = nx.nx_pydot.to_pydot(G)
+    G2 = nx.Graph(nx.nx_pydot.from_pydot(P))
+    assert graphs_equal(G, G2)
+
+
+@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_numerical_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
diff --git a/.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_pylab.py b/.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_pylab.py
new file mode 100644
index 00000000..c9931db8
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/networkx/drawing/tests/test_pylab.py
@@ -0,0 +1,1029 @@
+"""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")
+    except ModuleNotFoundError:  # draw_kamada_kawai requires scipy
+        pass
+    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_return_types():
+    from matplotlib.collections import LineCollection, PathCollection
+    from matplotlib.patches import FancyArrowPatch
+
+    G = nx.cubical_graph(nx.Graph)
+    dG = nx.cubical_graph(nx.DiGraph)
+    pos = nx.spring_layout(G)
+    dpos = nx.spring_layout(dG)
+    # nodes
+    nodes = nx.draw_networkx_nodes(G, pos)
+    assert isinstance(nodes, PathCollection)
+    # edges
+    edges = nx.draw_networkx_edges(dG, dpos, arrows=True)
+    assert isinstance(edges, list)
+    if len(edges) > 0:
+        assert isinstance(edges[0], FancyArrowPatch)
+    edges = nx.draw_networkx_edges(dG, dpos, arrows=False)
+    assert isinstance(edges, LineCollection)
+    edges = nx.draw_networkx_edges(G, dpos, arrows=None)
+    assert isinstance(edges, LineCollection)
+    edges = nx.draw_networkx_edges(dG, pos, arrows=None)
+    assert isinstance(edges, list)
+    if len(edges) > 0:
+        assert isinstance(edges[0], FancyArrowPatch)
+
+
+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$"
+    colors = {n: "k" if n % 2 == 0 else "r" for n in range(8)}
+    nx.draw_networkx_labels(G, pos, labels, font_size=16)
+    nx.draw_networkx_labels(G, pos, labels, font_size=16, font_color=colors)
+    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_multiple_node_shapes():
+    G = nx.path_graph(4)
+    ax = plt.figure().add_subplot(111)
+    nx.draw(G, node_shape=["o", "h", "s", "^"], ax=ax)
+    scatters = [
+        s for s in ax.get_children() if isinstance(s, mpl.collections.PathCollection)
+    ]
+    assert len(scatters) == 4
+
+
+def test_individualized_font_attributes():
+    G = nx.karate_club_graph()
+    ax = plt.figure().add_subplot(111)
+    nx.draw(
+        G,
+        ax=ax,
+        font_color={n: "k" if n % 2 else "r" for n in G.nodes()},
+        font_size={n: int(n / (34 / 15) + 5) for n in G.nodes()},
+    )
+    for n, t in zip(
+        G.nodes(),
+        [
+            t
+            for t in ax.get_children()
+            if isinstance(t, mpl.text.Text) and len(t.get_text()) > 0
+        ],
+    ):
+        expected = "black" if n % 2 else "red"
+
+        assert mpl.colors.same_color(t.get_color(), expected)
+        assert int(n / (34 / 15) + 5) == t.get_size()
+
+
+def test_individualized_edge_attributes():
+    G = nx.karate_club_graph()
+    ax = plt.figure().add_subplot(111)
+    arrowstyles = ["-|>" if (u + v) % 2 == 0 else "-[" for u, v in G.edges()]
+    arrowsizes = [10 * (u % 2 + v % 2) + 10 for u, v in G.edges()]
+    nx.draw(G, ax=ax, arrows=True, arrowstyle=arrowstyles, arrowsize=arrowsizes)
+    arrows = [
+        f for f in ax.get_children() if isinstance(f, mpl.patches.FancyArrowPatch)
+    ]
+    for e, a in zip(G.edges(), arrows):
+        assert a.get_mutation_scale() == 10 * (e[0] % 2 + e[1] % 2) + 10
+        expected = (
+            mpl.patches.ArrowStyle.BracketB
+            if sum(e) % 2
+            else mpl.patches.ArrowStyle.CurveFilledB
+        )
+        assert isinstance(a.get_arrowstyle(), expected)
+
+
+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
+
+
+@pytest.mark.parametrize("arrowstyle", ("-|>", ["-|>", "-[", "<|-|>"]))
+def test_draw_edges_arrowstyle(arrowstyle):
+    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, arrowstyle=arrowstyle)
+
+    arrowstyle = (
+        itertools.repeat(arrowstyle) if isinstance(arrowstyle, str) else arrowstyle
+    )
+
+    arrow_objects = {
+        "-|>": mpl.patches.ArrowStyle.CurveFilledB,
+        "-[": mpl.patches.ArrowStyle.BracketB,
+        "<|-|>": mpl.patches.ArrowStyle.CurveFilledAB,
+    }
+
+    for fap, expected in zip(edges, arrowstyle):
+        assert isinstance(fap, mpl.patches.FancyArrowPatch)
+        assert isinstance(fap.get_arrowstyle(), arrow_objects[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]
+
+
+# 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_individual(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), (1, 2)])
+    pos = {0: (0, 0), 1: (1, 0), 2: (2, 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)
+    default_extent = [d.get_extents().corners()[::2, 0] for d in default_patch]
+    # 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=[98, 102],
+        min_target_margin=[98, 102],
+    )
+    padded_extent = [p.get_extents().corners()[::2, 0] for p in padded_patch]
+    for d, p in zip(default_extent, padded_extent):
+        print(f"{p=}, {d=}")
+        # With padding, the left-most extent of the edge should be further to the
+        # right
+        assert p[0] > d[0]
+        # And the rightmost extent of the edge, further to the left
+        assert p[1] < d[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)
+    plt.close()
+
+
+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)
+    plt.close()
+
+
+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)
+    plt.close()
+
+
+@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)
+    plt.close()
+
+
+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)
+    plt.close()
+
+
+@pytest.mark.parametrize(
+    ("G", "expected_n_edges"),
+    ([nx.DiGraph(), 2], [nx.MultiGraph(), 4], [nx.MultiDiGraph(), 4]),
+)
+def test_draw_networkx_edges_multiedge_connectionstyle(G, expected_n_edges):
+    """Draws edges correctly for 3 types of graphs and checks for valid length"""
+    for i, (u, v) in enumerate([(0, 1), (0, 1), (0, 1), (0, 2)]):
+        G.add_edge(u, v, weight=round(i / 3, 2))
+    pos = {n: (n, n) for n in G}
+    # Raises on insufficient connectionstyle length
+    for conn_style in [
+        "arc3,rad=0.1",
+        ["arc3,rad=0.1", "arc3,rad=0.1"],
+        ["arc3,rad=0.1", "arc3,rad=0.1", "arc3,rad=0.2"],
+    ]:
+        nx.draw_networkx_edges(G, pos, connectionstyle=conn_style)
+        arrows = nx.draw_networkx_edges(G, pos, connectionstyle=conn_style)
+        assert len(arrows) == expected_n_edges
+
+
+@pytest.mark.parametrize(
+    ("G", "expected_n_edges"),
+    ([nx.DiGraph(), 2], [nx.MultiGraph(), 4], [nx.MultiDiGraph(), 4]),
+)
+def test_draw_networkx_edge_labels_multiedge_connectionstyle(G, expected_n_edges):
+    """Draws labels correctly for 3 types of graphs and checks for valid length and class names"""
+    for i, (u, v) in enumerate([(0, 1), (0, 1), (0, 1), (0, 2)]):
+        G.add_edge(u, v, weight=round(i / 3, 2))
+    pos = {n: (n, n) for n in G}
+    # Raises on insufficient connectionstyle length
+    arrows = nx.draw_networkx_edges(
+        G, pos, connectionstyle=["arc3,rad=0.1", "arc3,rad=0.1", "arc3,rad=0.1"]
+    )
+    for conn_style in [
+        "arc3,rad=0.1",
+        ["arc3,rad=0.1", "arc3,rad=0.2"],
+        ["arc3,rad=0.1", "arc3,rad=0.1", "arc3,rad=0.1"],
+    ]:
+        text_items = nx.draw_networkx_edge_labels(G, pos, connectionstyle=conn_style)
+        assert len(text_items) == expected_n_edges
+        for ti in text_items.values():
+            assert ti.__class__.__name__ == "CurvedArrowText"
+
+
+def test_draw_networkx_edge_label_multiedge():
+    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}
+    text_items = nx.draw_networkx_edge_labels(
+        G,
+        pos,
+        edge_labels=edge_labels,
+        connectionstyle=["arc3,rad=0.1", "arc3,rad=0.2"],
+    )
+    assert len(text_items) == 2
+
+
+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)
+    plt.close()
+
+
+@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)
+    plt.close()
+
+
+@pytest.mark.parametrize("draw_fn", (nx.draw, nx.draw_circular))
+def test_no_warning_on_default_draw_arrowstyle(draw_fn):
+    # See gh-7284
+    fig, ax = plt.subplots()
+    G = nx.cycle_graph(5)
+    with warnings.catch_warnings(record=True) as w:
+        draw_fn(G, ax=ax)
+    assert len(w) == 0
+
+    plt.delaxes(ax)
+    plt.close()
+
+
+@pytest.mark.parametrize("hide_ticks", [False, True])
+@pytest.mark.parametrize(
+    "method",
+    [
+        nx.draw_networkx,
+        nx.draw_networkx_edge_labels,
+        nx.draw_networkx_edges,
+        nx.draw_networkx_labels,
+        nx.draw_networkx_nodes,
+    ],
+)
+def test_hide_ticks(method, hide_ticks):
+    G = nx.path_graph(3)
+    pos = {n: (n, n) for n in G.nodes}
+    _, ax = plt.subplots()
+    method(G, pos=pos, ax=ax, hide_ticks=hide_ticks)
+    for axis in [ax.xaxis, ax.yaxis]:
+        assert bool(axis.get_ticklabels()) != hide_ticks
+
+    plt.delaxes(ax)
+    plt.close()