Repository: networkx/networkx

Issue:
Visualization: arrowstyle warning is very noisy
Follow-up to #7010

The logic determining when the `arrowstyle` UserWarning changed in #7010, with the unintended side-effect of it being raised far more often. For example, it is now raised during the doc build for all of the simple drawings of the classic generators.

The warning logic should be reviewed and modified so that the warning is only raised when appropriate.

Discussion:

Patch:
diff --git a/doc/conf.py b/doc/conf.py
index f54a02289..e2eb2a547 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -22,10 +22,10 @@ extensions = [
     "sphinx.ext.todo",
     "sphinx.ext.viewcode",
     "sphinx_gallery.gen_gallery",
-    "nb2plots",
     "texext",
     "numpydoc",
     "matplotlib.sphinxext.plot_directive",
+    "myst_nb",
 ]
 
 # https://github.com/sphinx-gallery/sphinx-gallery
@@ -77,8 +77,9 @@ source_suffix = ".rst"
 # The encoding of source files.
 source_encoding = "utf-8"
 
-# Do not include release announcement template
-exclude_patterns = ["release/release_template.rst"]
+# Items to exclude during source collection, including release announcement
+# template, build outputs, and READMEs (markdown only)
+exclude_patterns = ["release/release_template.rst", "build/*", "README.md"]
 
 # General substitutions.
 project = "NetworkX"
@@ -258,6 +259,9 @@ plot_formats = [("png", 100)]
 def setup(app):
     app.add_css_file("custom.css")
     app.add_js_file("copybutton.js")
+    # Workaround to prevent duplicate file warnings from sphinx w/ myst-nb.
+    # See executablebooks/MyST-NB#363
+    app.registry.source_suffix.pop(".ipynb")
 
 
 # Monkeypatch numpydoc to show "Backends" section
diff --git a/doc/reference/introduction.md b/doc/reference/introduction.md
new file mode 100644
index 000000000..a00cfe52c
--- /dev/null
+++ b/doc/reference/introduction.md
@@ -0,0 +1,342 @@
+---
+jupytext:
+  text_representation:
+    extension: .md
+    format_name: myst
+    format_version: 0.13
+    jupytext_version: 1.13.1
+kernelspec:
+  display_name: Python 3 (ipykernel)
+  language: python
+  name: python3
+---
+
+# Introduction
+
+```{currentmodule} networkx
+
+```
+
+The structure of NetworkX can be seen by the organization of its source code.
+The package provides classes for graph objects, generators to create standard
+graphs, IO routines for reading in existing datasets, algorithms to analyze
+the resulting networks and some basic drawing tools.
+
+Most of the NetworkX API is provided by functions which take a graph object
+as an argument. Methods of the graph object are limited to basic manipulation
+and reporting. This provides modularity of code and documentation.
+It also makes it easier for newcomers to learn about the package in stages.
+The source code for each module is meant to be easy to read and reading
+this Python code is actually a good way to learn more about network algorithms,
+but we have put a lot of effort into making the documentation sufficient and friendly.
+If you have suggestions or questions please contact us by joining the
+[NetworkX Google group](http://groups.google.com/group/networkx-discuss).
+
+Classes are named using `CamelCase` (capital letters at the start of each word).
+functions, methods and variable names are `lower_case_underscore` (lowercase with
+an underscore representing a space between words).
+
+## NetworkX Basics
+
+After starting Python, import the networkx module with (the recommended way)
+
+```{code-cell}
+import networkx as nx
+```
+
+To save repetition, in the documentation we assume that
+NetworkX has been imported this way.
+
+If importing networkx fails, it means that Python cannot find the installed
+module. Check your installation and your `PYTHONPATH`.
+
+The following basic graph types are provided as Python classes:
+
+{class}`Graph`
+
+- This class implements an undirected graph. It ignores
+  multiple edges between two nodes. It does allow self-loop
+  edges between a node and itself.
+
+{class}`DiGraph`
+
+- Directed graphs, that is, graphs with directed edges.
+  Provides operations common to directed graphs,
+  (a subclass of Graph).
+
+{class}`MultiGraph`
+
+- A flexible graph class that allows multiple undirected edges between
+  pairs of nodes. The additional flexibility leads to some degradation
+  in performance, though usually not significant.
+
+{class}`MultiDiGraph`
+
+- A directed version of a MultiGraph.
+
+Empty graph-like objects are created with
+
+```{code-cell}
+G = nx.Graph()
+G = nx.DiGraph()
+G = nx.MultiGraph()
+G = nx.MultiDiGraph()
+```
+
+All graph classes allow any {term}`hashable` object as a node.
+Hashable objects include strings, tuples, integers, and more.
+Arbitrary edge attributes such as weights and labels
+can be associated with an edge.
+
+The graph internal data structures are based on an
+adjacency list representation and implemented using
+Python {term}`dictionary` datastructures.
+The graph adjacency structure is
+implemented as a Python dictionary of
+dictionaries; the outer dictionary is keyed by nodes to values that are
+themselves dictionaries keyed by neighboring node to the
+edge attributes associated with that edge. This "dict-of-dicts" structure
+allows fast addition, deletion, and lookup of nodes and neighbors in
+large graphs. The underlying datastructure is accessed directly
+by methods (the programming interface "API") in the class definitions.
+All functions, on the other hand, manipulate graph-like objects
+solely via those API methods and not by acting directly on the datastructure.
+This design allows for possible replacement of the 'dicts-of-dicts'-based
+datastructure with an alternative datastructure that implements the
+same methods.
+
+## Graphs
+
+The first choice to be made when using NetworkX is what type of graph
+object to use. A graph (network) is a collection of nodes together
+with a collection of edges that are pairs of nodes. Attributes are
+often associated with nodes and/or edges. NetworkX graph objects come in
+different flavors depending on two main properties of the network:
+
+> - Directed: Are the edges **directed**? Does the order of the edge
+>   pairs \$(u, v)\$ matter? A directed graph is specified by the "Di"
+>   prefix in the class name, e.g. {class}`DiGraph`. We make this distinction
+>   because many classical graph properties are defined differently for
+>   directed graphs.
+> - Multi-edges: Are multiple edges allowed between each pair of nodes?
+>   As you might imagine, multiple edges requires a different data
+>   structure, though clever users could design edge data attributes to
+>   support this functionality. We provide a standard data structure
+>   and interface for this type of graph using the prefix "Multi",
+>   e.g., {class}`MultiGraph`.
+
+The basic graph classes are named:
+{doc}`Graph </reference/classes/graph>`,
+{doc}`DiGraph</reference/classes/digraph>`,
+{doc}`MultiGraph </reference/classes/multigraph>`, and
+{doc}`MultiDiGraph </reference/classes/multidigraph>`
+
+### Nodes and Edges
+
+The next choice you have to make when specifying a graph is what kinds
+of nodes and edges to use.
+
+If the topology of the network is all you
+care about then using integers or strings as the nodes makes sense and
+you need not worry about edge data. If you have a data structure
+already in place to describe nodes you can simply use that structure
+as your nodes provided it is {term}`hashable`. If it is not hashable you can
+use a unique identifier to represent the node and assign the data
+as a {term}`node attribute`.
+
+Edges often have data associated with them. Arbitrary data
+can be associated with edges as an {term}`edge attribute`.
+If the data is numeric and the intent is to represent
+a _weighted_ graph then use the 'weight' keyword for the attribute.
+Some of the graph algorithms, such as
+Dijkstra's shortest path algorithm, use this attribute
+name by default to get the weight for each edge.
+
+Attributes can be assigned to an edge by using keyword/value
+pairs when adding edges. You can use any keyword
+to name your attribute and can then query the edge
+data using that attribute keyword.
+
+Once you've decided how to encode the nodes and edges, and whether you have
+an undirected/directed graph with or without multiedges you are ready to build
+your network.
+
+## Graph Creation
+
+NetworkX graph objects can be created in one of three ways:
+
+- Graph generators---standard algorithms to create network topologies.
+- Importing data from preexisting (usually file) sources.
+- Adding edges and nodes explicitly.
+
+Explicit addition and removal of nodes/edges is the easiest to describe.
+Each graph object supplies methods to manipulate the graph. For example,
+
+```{code-cell}
+G = nx.Graph()
+G.add_edge(1, 2)  # default edge data=1
+G.add_edge(2, 3, weight=0.9)  # specify edge data
+```
+
+Edge attributes can be anything:
+
+```{code-cell}
+import math
+G.add_edge('y', 'x', function=math.cos)
+G.add_node(math.cos)  # any hashable can be a node
+```
+
+You can add many edges at one time:
+
+```{code-cell}
+elist = [(1, 2), (2, 3), (1, 4), (4, 2)]
+G.add_edges_from(elist)
+elist = [('a', 'b', 5.0), ('b', 'c', 3.0), ('a', 'c', 1.0), ('c', 'd', 7.3)]
+G.add_weighted_edges_from(elist)
+```
+
+See the {doc}`/tutorial` for more examples.
+
+Some basic graph operations such as union and intersection
+are described in the {ref}`operators module <operators>` documentation.
+
+Graph generators such as {func}`~generators.random_graphs.binomial_graph`
+and {func}`~generators.random_graphs.erdos_renyi_graph` are
+provided in the {ref}`graph generators <generators>` subpackage.
+
+For importing network data from formats such as GML, GraphML, edge list text files
+see the {ref}`reading and writing graphs <readwrite>` subpackage.
+
+## Graph Reporting
+
+Class views provide basic reporting of nodes, neighbors, edges and degree.
+These views provide iteration over the properties as well as membership
+queries and data attribute lookup. The views refer to the graph data structure
+so changes to the graph are reflected in the views. This is analogous to
+dictionary views in Python 3. If you want to change the graph while iterating
+you will need to use e.g. `for e in list(G.edges):`. The views provide
+set-like operations, e.g. union and intersection, as well as dict-like
+lookup and iteration of the data attributes using `G.edges[u, v]['color']`
+and `for e, datadict in G.edges.items():`. Methods `G.edges.items()` and
+`G.edges.values()` are familiar from python dicts. In addition `G.edges.data()`
+provides specific attribute iteration e.g. `for e, e_color in G.edges.data('color'):`.
+
+The basic graph relationship of an edge can be obtained in two ways.
+One can look for neighbors of a node or one can look for edges.
+We jokingly refer to people who focus on nodes/neighbors as node-centric
+and people who focus on edges as edge-centric. The designers of NetworkX
+tend to be node-centric and view edges as a relationship between nodes.
+You can see this by our choice of lookup notation like `G[u]` providing neighbors
+(adjacency) while edge lookup is `G.edges[u, v]`.
+Most data structures for sparse graphs are essentially adjacency lists and so
+fit this perspective. In the end, of course, it doesn't really matter which way
+you examine the graph. `G.edges` removes duplicate representations of undirected
+edges while neighbor reporting across all nodes will naturally report both directions.
+
+Any properties that are more complicated than edges, neighbors and degree are
+provided by functions. For example `nx.triangles(G, n)` gives the number of triangles
+which include node n as a vertex. These functions are grouped in the code and
+documentation under the term {ref}`algorithms<algorithms>`.
+
+## Algorithms
+
+A number of graph algorithms are provided with NetworkX.
+These include shortest path, and breadth first search
+(see {ref}`traversal<traversal>`),
+clustering and isomorphism algorithms and others. There are
+many that we have not developed yet too. If you implement a
+graph algorithm that might be useful for others please let
+us know through the
+[NetworkX Google group](http://groups.google.com/group/networkx-discuss)
+or the GitHub [Developer Zone](https://github.com/networkx/networkx).
+
+As an example here is code to use Dijkstra's algorithm to
+find the shortest weighted path:
+
+```{code-cell}
+G = nx.Graph()
+e = [('a', 'b', 0.3), ('b', 'c', 0.9), ('a', 'c', 0.5), ('c', 'd', 1.2)]
+G.add_weighted_edges_from(e)
+print(nx.dijkstra_path(G, 'a', 'd'))
+```
+
+## Drawing
+
+While NetworkX is not designed as a network drawing tool, we provide
+a simple interface to drawing packages and some simple layout algorithms.
+We interface to the excellent Graphviz layout tools like dot and neato
+with the (suggested) pygraphviz package or the pydot interface.
+Drawing can be done using external programs or the Matplotlib Python
+package. Interactive GUI interfaces are possible, though not provided.
+The drawing tools are provided in the module {ref}`drawing <drawing>`.
+
+The basic drawing functions essentially place the nodes on a scatterplot
+using the positions you provide via a dictionary or the positions are
+computed with a layout function. The edges are lines between those dots.
+
+```{code-cell}
+import matplotlib.pyplot as plt
+G = nx.cubical_graph()
+subax1 = plt.subplot(121)
+nx.draw(G)   # default spring_layout
+subax2 = plt.subplot(122)
+nx.draw(G, pos=nx.circular_layout(G), node_color='r', edge_color='b')
+```
+
+See the {doc}`examples </auto_examples/index>` for more ideas.
+
+## Data Structure
+
+NetworkX uses a "dictionary of dictionaries of dictionaries" as the
+basic network data structure. This allows fast lookup with reasonable
+storage for large sparse networks. The keys are nodes so `G[u]` returns
+an adjacency dictionary keyed by neighbor to the edge attribute
+dictionary. A view of the adjacency data structure is provided
+by the dict-like object `G.adj` as e.g. `for node, nbrsdict in G.adj.items():`.
+The expression `G[u][v]` returns the edge attribute dictionary itself.
+A dictionary of lists would have also been possible, but not allow
+fast edge detection nor convenient storage of edge data.
+
+Advantages of dict-of-dicts-of-dicts data structure:
+
+- Find edges and remove edges with two dictionary look-ups.
+- Prefer to "lists" because of fast lookup with sparse storage.
+- Prefer to "sets" since data can be attached to edge.
+- `G[u][v]` returns the edge attribute dictionary.
+- `n in G` tests if node `n` is in graph `G`.
+- `for n in G:` iterates through the graph.
+- `for nbr in G[n]:` iterates through neighbors.
+
+As an example, here is a representation of an undirected graph with the
+edges \$(A, B)\$ and \$(B, C)\$.
+
+```{code-cell}
+G = nx.Graph()
+G.add_edge('A', 'B')
+G.add_edge('B', 'C')
+print(G.adj)
+```
+
+The data structure gets morphed slightly for each base graph class.
+For DiGraph two dict-of-dicts-of-dicts structures are provided, one
+for successors (`G.succ`) and one for predecessors (`G.pred`).
+For MultiGraph/MultiDiGraph we use a dict-of-dicts-of-dicts-of-dicts [^turtles]
+where the third dictionary is keyed by an edge key identifier to the fourth
+dictionary which contains the edge attributes for that edge between
+the two nodes.
+
+Graphs provide two interfaces to the edge data attributes: adjacency
+and edges. So `G[u][v]['width']` is the same as `G.edges[u, v]['width']`.
+
+```{code-cell}
+G = nx.Graph()
+G.add_edge(1, 2, color='red', weight=0.84, size=300)
+print(G[1][2]['size'])
+print(G.edges[1, 2]['color'])
+```
+
+```{rubric} Footnotes
+
+```
+
+[^turtles]: "It's dictionaries all the way down."
diff --git a/doc/reference/introduction.rst b/doc/reference/introduction.rst
deleted file mode 100644
index 70fc3f5c7..000000000
--- a/doc/reference/introduction.rst
+++ /dev/null
@@ -1,344 +0,0 @@
-Introduction
-============
-
-.. currentmodule:: networkx
-
-The structure of NetworkX can be seen by the organization of its source code.
-The package provides classes for graph objects, generators to create standard
-graphs, IO routines for reading in existing datasets, algorithms to analyze
-the resulting networks and some basic drawing tools.
-
-Most of the NetworkX API is provided by functions which take a graph object
-as an argument.  Methods of the graph object are limited to basic manipulation
-and reporting.  This provides modularity of code and documentation.
-It also makes it easier for newcomers to learn about the package in stages.
-The source code for each module is meant to be easy to read and reading
-this Python code is actually a good way to learn more about network algorithms,
-but we have put a lot of effort into making the documentation sufficient and friendly.
-If you have suggestions or questions please contact us by joining the
-`NetworkX Google group <http://groups.google.com/group/networkx-discuss>`_.
-
-Classes are named using ``CamelCase`` (capital letters at the start of each word).
-functions, methods and variable names are ``lower_case_underscore`` (lowercase with
-an underscore representing a space between words).
-
-
-NetworkX Basics
----------------
-
-After starting Python, import the networkx module with (the recommended way)
-
-.. nbplot::
-
-   >>> import networkx as nx
-
-To save repetition, in the documentation we assume that
-NetworkX has been imported this way.
-
-If importing networkx fails, it means that Python cannot find the installed
-module. Check your installation and your ``PYTHONPATH``.
-
-The following basic graph types are provided as Python classes:
-
-:class:`Graph`
-   This class implements an undirected graph. It ignores
-   multiple edges between two nodes.  It does allow self-loop
-   edges between a node and itself.
-
-:class:`DiGraph`
-   Directed graphs, that is, graphs with directed edges.
-   Provides operations common to directed graphs,
-   (a subclass of Graph).
-
-:class:`MultiGraph`
-   A flexible graph class that allows multiple undirected edges between
-   pairs of nodes.  The additional flexibility leads to some degradation
-   in performance, though usually not significant.
-
-:class:`MultiDiGraph`
-   A directed version of a MultiGraph.
-
-Empty graph-like objects are created with
-
-.. nbplot::
-
-   >>> G = nx.Graph()
-   >>> G = nx.DiGraph()
-   >>> G = nx.MultiGraph()
-   >>> G = nx.MultiDiGraph()
-
-All graph classes allow any :term:`hashable` object as a node.
-Hashable objects include strings, tuples, integers, and more.
-Arbitrary edge attributes such as weights and labels
-can be associated with an edge.
-
-
-The graph internal data structures are based on an
-adjacency list representation and implemented using
-Python :term:`dictionary` datastructures.
-The graph adjacency structure is
-implemented as a Python dictionary of
-dictionaries; the outer dictionary is keyed by nodes to values that are
-themselves dictionaries keyed by neighboring node to the
-edge attributes associated with that edge.  This "dict-of-dicts" structure
-allows fast addition, deletion, and lookup of nodes and neighbors in
-large graphs.  The underlying datastructure is accessed directly
-by methods (the programming interface "API") in the class definitions.
-All functions, on the other hand, manipulate graph-like objects
-solely via those API methods and not by acting directly on the datastructure.
-This design allows for possible replacement of the 'dicts-of-dicts'-based
-datastructure with an alternative datastructure that implements the
-same methods.
-
-
-Graphs
-------
-
-The first choice to be made when using NetworkX is what type of graph
-object to use.  A graph (network) is a collection of nodes together
-with a collection of edges that are pairs of nodes.  Attributes are
-often associated with nodes and/or edges.  NetworkX graph objects come in
-different flavors depending on two main properties of the network:
-
- - Directed: Are the edges **directed**?  Does the order of the edge
-   pairs $(u, v)$ matter?  A directed graph is specified by the "Di"
-   prefix in the class name, e.g. `DiGraph()`.  We make this distinction
-   because many classical graph properties are defined differently for
-   directed graphs.
-
- - Multi-edges: Are multiple edges allowed between each pair of nodes?
-   As you might imagine, multiple edges requires a different data
-   structure, though clever users could design edge data attributes to
-   support this functionality.  We provide a standard data structure
-   and interface for this type of graph using the prefix "Multi",
-   e.g., `MultiGraph()`.
-
-The basic graph classes are named:
-:doc:`Graph </reference/classes/graph>`,
-:doc:`DiGraph</reference/classes/digraph>`,
-:doc:`MultiGraph </reference/classes/multigraph>`, and
-:doc:`MultiDiGraph </reference/classes/multidigraph>`
-
-
-Nodes and Edges
-^^^^^^^^^^^^^^^
-
-The next choice you have to make when specifying a graph is what kinds
-of nodes and edges to use.
-
-If the topology of the network is all you
-care about then using integers or strings as the nodes makes sense and
-you need not worry about edge data.  If you have a data structure
-already in place to describe nodes you can simply use that structure
-as your nodes provided it is :term:`hashable`.  If it is not hashable you can
-use a unique identifier to represent the node and assign the data
-as a :term:`node attribute`.
-
-Edges often have data associated with them.  Arbitrary data
-can be associated with edges as an :term:`edge attribute`.
-If the data is numeric and the intent is to represent
-a *weighted* graph then use the 'weight' keyword for the attribute.
-Some of the graph algorithms, such as
-Dijkstra's shortest path algorithm, use this attribute
-name by default to get the weight for each edge.
-
-Attributes can be assigned to an edge by using keyword/value
-pairs when adding edges.  You can use any keyword
-to name your attribute and can then query the edge
-data using that attribute keyword.
-
-Once you've decided how to encode the nodes and edges, and whether you have
-an undirected/directed graph with or without multiedges you are ready to build
-your network.
-
-Graph Creation
---------------
-
-NetworkX graph objects can be created in one of three ways:
-
-- Graph generators---standard algorithms to create network topologies.
-- Importing data from preexisting (usually file) sources.
-- Adding edges and nodes explicitly.
-
-Explicit addition and removal of nodes/edges is the easiest to describe.
-Each graph object supplies methods to manipulate the graph.  For example,
-
-.. nbplot::
-
-   >>> import networkx as nx
-   >>> G = nx.Graph()
-   >>> G.add_edge(1, 2)  # default edge data=1
-   >>> G.add_edge(2, 3, weight=0.9)  # specify edge data
-
-Edge attributes can be anything:
-
-.. nbplot::
-
-   >>> import math
-   >>> G.add_edge('y', 'x', function=math.cos)
-   >>> G.add_node(math.cos)  # any hashable can be a node
-
-You can add many edges at one time:
-
-.. nbplot::
-
-   >>> elist = [(1, 2), (2, 3), (1, 4), (4, 2)]
-   >>> G.add_edges_from(elist)
-   >>> elist = [('a', 'b', 5.0), ('b', 'c', 3.0), ('a', 'c', 1.0), ('c', 'd', 7.3)]
-   >>> G.add_weighted_edges_from(elist)
-
-See the :doc:`/tutorial` for more examples.
-
-Some basic graph operations such as union and intersection
-are described in the :ref:`operators module <operators>` documentation.
-
-Graph generators such as :func:`~generators.random_graphs.binomial_graph`
-and :func:`~generators.random_graphs.erdos_renyi_graph` are
-provided in the :ref:`graph generators <generators>` subpackage.
-
-For importing network data from formats such as GML, GraphML, edge list text files
-see the :ref:`reading and writing graphs <readwrite>` subpackage.
-
-
-Graph Reporting
----------------
-
-Class views provide basic reporting of nodes, neighbors, edges and degree.
-These views provide iteration over the properties as well as membership
-queries and data attribute lookup. The views refer to the graph data structure
-so changes to the graph are reflected in the views. This is analogous to
-dictionary views in Python 3. If you want to change the graph while iterating
-you will need to use e.g. ``for e in list(G.edges):``. The views provide
-set-like operations, e.g. union and intersection, as well as dict-like
-lookup and iteration of the data attributes using ``G.edges[u, v]['color']``
-and ``for e, datadict in G.edges.items():``. Methods ``G.edges.items()`` and
-``G.edges.values()`` are familiar from python dicts. In addition ``G.edges.data()``
-provides specific attribute iteration e.g. ``for e, e_color in G.edges.data('color'):``.
-
-The basic graph relationship of an edge can be obtained in two ways.
-One can look for neighbors of a node or one can look for edges.
-We jokingly refer to people who focus on nodes/neighbors as node-centric
-and people who focus on edges as edge-centric.  The designers of NetworkX
-tend to be node-centric and view edges as a relationship between nodes.
-You can see this by our choice of lookup notation like ``G[u]`` providing neighbors
-(adjacency) while edge lookup is ``G.edges[u, v]``.
-Most data structures for sparse graphs are essentially adjacency lists and so
-fit this perspective. In the end, of course, it doesn't really matter which way
-you examine the graph. ``G.edges`` removes duplicate representations of undirected
-edges while neighbor reporting across all nodes will naturally report both directions.
-
-Any properties that are more complicated than edges, neighbors and degree are
-provided by functions.  For example ``nx.triangles(G, n)`` gives the number of triangles
-which include node n as a vertex.  These functions are grouped in the code and
-documentation under the term :ref:`algorithms<algorithms>`.
-
-
-Algorithms
-----------
-
-A number of graph algorithms are provided with NetworkX.
-These include shortest path, and breadth first search
-(see :ref:`traversal<traversal>`),
-clustering and isomorphism algorithms and others.  There are
-many that we have not developed yet too.  If you implement a
-graph algorithm that might be useful for others please let
-us know through the
-`NetworkX Google group <http://groups.google.com/group/networkx-discuss>`_
-or the GitHub `Developer Zone <https://github.com/networkx/networkx>`_.
-
-As an example here is code to use Dijkstra's algorithm to
-find the shortest weighted path:
-
-.. nbplot::
-
-   >>> G = nx.Graph()
-   >>> e = [('a', 'b', 0.3), ('b', 'c', 0.9), ('a', 'c', 0.5), ('c', 'd', 1.2)]
-   >>> G.add_weighted_edges_from(e)
-   >>> print(nx.dijkstra_path(G, 'a', 'd'))
-   ['a', 'c', 'd']
-
-Drawing
--------
-
-While NetworkX is not designed as a network drawing tool, we provide
-a simple interface to drawing packages and some simple layout algorithms.
-We interface to the excellent Graphviz layout tools like dot and neato
-with the (suggested) pygraphviz package or the pydot interface.
-Drawing can be done using external programs or the Matplotlib Python
-package.  Interactive GUI interfaces are possible, though not provided.
-The drawing tools are provided in the module :ref:`drawing <drawing>`.
-
-The basic drawing functions essentially place the nodes on a scatterplot
-using the positions you provide via a dictionary or the positions are
-computed with a layout function. The edges are lines between those dots.
-
-.. nbplot::
-
-   >>> import matplotlib.pyplot as plt
-   >>> G = nx.cubical_graph()
-   >>> subax1 = plt.subplot(121)
-   >>> nx.draw(G)   # default spring_layout
-   >>> subax2 = plt.subplot(122)
-   >>> nx.draw(G, pos=nx.circular_layout(G), node_color='r', edge_color='b')
-
-See the :doc:`examples </auto_examples/index>` for more ideas.
-
-Data Structure
---------------
-
-NetworkX uses a "dictionary of dictionaries of dictionaries" as the
-basic network data structure.  This allows fast lookup with reasonable
-storage for large sparse networks.  The keys are nodes so ``G[u]`` returns
-an adjacency dictionary keyed by neighbor to the edge attribute
-dictionary. A view of the adjacency data structure is provided
-by the dict-like object ``G.adj`` as e.g. ``for node, nbrsdict in G.adj.items():``.
-The expression ``G[u][v]`` returns the edge attribute dictionary itself.
-A dictionary of lists would have also been possible, but not allow
-fast edge detection nor convenient storage of edge data.
-
-Advantages of dict-of-dicts-of-dicts data structure:
-
- - Find edges and remove edges with two dictionary look-ups.
- - Prefer to "lists" because of fast lookup with sparse storage.
- - Prefer to "sets" since data can be attached to edge.
- - ``G[u][v]`` returns the edge attribute dictionary.
- - ``n in G`` tests if node ``n`` is in graph ``G``.
- - ``for n in G:`` iterates through the graph.
- - ``for nbr in G[n]:`` iterates through neighbors.
-
-As an example, here is a representation of an undirected graph with the
-edges $(A, B)$ and $(B, C)$.
-
-.. nbplot::
-
-   >>> G = nx.Graph()
-   >>> G.add_edge('A', 'B')
-   >>> G.add_edge('B', 'C')
-   >>> print(G.adj)
-   {'A': {'B': {}}, 'B': {'A': {}, 'C': {}}, 'C': {'B': {}}}
-
-The data structure gets morphed slightly for each base graph class.
-For DiGraph two dict-of-dicts-of-dicts structures are provided, one
-for successors (``G.succ``) and one for predecessors (``G.pred``).
-For MultiGraph/MultiDiGraph we use a dict-of-dicts-of-dicts-of-dicts [#turtles]_
-where the third dictionary is keyed by an edge key identifier to the fourth
-dictionary which contains the edge attributes for that edge between
-the two nodes.
-
-Graphs provide two interfaces to the edge data attributes: adjacency
-and edges. So ``G[u][v]['width']`` is the same as ``G.edges[u, v]['width']``.
-
-.. nbplot::
-
-   >>> G = nx.Graph()
-   >>> G.add_edge(1, 2, color='red', weight=0.84, size=300)
-   >>> print(G[1][2]['size'])
-   300
-   >>> print(G.edges[1, 2]['color'])
-   red
-
-.. code-links::
-
-.. rubric:: Footnotes
-
-.. [#turtles] "It's dictionaries all the way down."
diff --git a/doc/reference/randomness.rst b/doc/reference/randomness.rst
index 4ca728025..dc9db60a9 100644
--- a/doc/reference/randomness.rst
+++ b/doc/reference/randomness.rst
@@ -27,7 +27,7 @@ RNGs, you may find yourself using another package that uses the other.
 Setting the state of the two global RNGs is as simple setting the
 seed of each RNG to an arbitrary integer:
 
-.. nbplot::
+.. code-block::
 
    >>> import random
    >>> random.seed(246)        # or any integer
@@ -41,7 +41,7 @@ to functions that use an RNG.  This argument is called `seed`, but
 determines more than the seed of the RNG. It tells the function which
 RNG package to use, and whether to use a global or local RNG.
 
-.. nbplot::
+.. code-block::
 
     >>> from networkx import path_graph, random_layout
     >>> G = path_graph(9)
diff --git a/doc/tutorial.md b/doc/tutorial.md
new file mode 100644
index 000000000..f5ef87141
--- /dev/null
+++ b/doc/tutorial.md
@@ -0,0 +1,658 @@
+---
+jupytext:
+  text_representation:
+    extension: .md
+    format_name: myst
+    format_version: 0.13
+    jupytext_version: 1.13.1
+kernelspec:
+  display_name: Python 3 (ipykernel)
+  language: python
+  name: python3
+---
+
+# Tutorial
+
+```{currentmodule} networkx
+
+```
+
+This guide can help you start working with NetworkX.
+
+## Creating a graph
+
+Create an empty graph with no nodes and no edges.
+
+```{code-cell}
+import networkx as nx
+G = nx.Graph()
+```
+
+By definition, a {class}`Graph` is a collection of nodes (vertices) along with
+identified pairs of nodes (called edges, links, etc). In NetworkX, nodes can
+be any {py:term}`hashable` object e.g., a text string, an image, an XML object,
+another Graph, a customized node object, etc.
+
+```{note}
+Python's `None` object is not allowed to be used as a node. It
+determines whether optional function arguments have been assigned in many
+functions.
+```
+
+## Nodes
+
+The graph `G` can be grown in several ways. NetworkX includes many
+{doc}`graph generator functions <reference/generators>` and
+{doc}`facilities to read and write graphs in many formats <reference/readwrite/index>`.
+To get started though we'll look at simple manipulations. You can add one node
+at a time,
+
+```{code-cell}
+G.add_node(1)
+```
+
+or add nodes from any {py:term}`iterable` container, such as a list
+
+```{code-cell}
+G.add_nodes_from([2, 3])
+```
+
+You can also add nodes along with node
+attributes if your container yields 2-tuples of the form
+`(node, node_attribute_dict)`:
+
+```{code-cell}
+G.add_nodes_from([(4, {"color": "red"}), (5, {"color": "green"})])
+```
+
+Node attributes are discussed further {ref}`below <attributes>`.
+
+Nodes from one graph can be incorporated into another:
+
+```{code-cell}
+H = nx.path_graph(10)
+G.add_nodes_from(H)
+```
+
+`G` now contains the nodes of `H` as nodes of `G`.
+In contrast, you could use the graph `H` as a node in `G`.
+
+```{code-cell}
+G.add_node(H)
+```
+
+The graph `G` now contains `H` as a node. This flexibility is very powerful as
+it allows graphs of graphs, graphs of files, graphs of functions and much more.
+It is worth thinking about how to structure your application so that the nodes
+are useful entities. Of course you can always use a unique identifier in `G`
+and have a separate dictionary keyed by identifier to the node information if
+you prefer.
+
+```{note}
+You should not change the node object if the hash depends on its contents.
+```
+
+## Edges
+
+`G` can also be grown by adding one edge at a time,
+
+```{code-cell}
+G.add_edge(1, 2)
+e = (2, 3)
+G.add_edge(*e)  # unpack edge tuple*
+```
+
+by adding a list of edges,
+
+```{code-cell}
+G.add_edges_from([(1, 2), (1, 3)])
+```
+
+or by adding any {term}`ebunch` of edges. An _ebunch_ is any iterable
+container of edge-tuples. An edge-tuple can be a 2-tuple of nodes or a 3-tuple
+with 2 nodes followed by an edge attribute dictionary, e.g.,
+`(2, 3, {'weight': 3.1415})`. Edge attributes are discussed further
+{ref}`below <attributes>`.
+
+```{code-cell}
+G.add_edges_from(H.edges)
+```
+
+There are no complaints when adding existing nodes or edges. For example,
+after removing all nodes and edges,
+
+```{code-cell}
+G.clear()
+```
+
+we add new nodes/edges and NetworkX quietly ignores any that are
+already present.
+
+```{code-cell}
+G.add_edges_from([(1, 2), (1, 3)])
+G.add_node(1)
+G.add_edge(1, 2)
+G.add_node("spam")        # adds node "spam"
+G.add_nodes_from("spam")  # adds 4 nodes: 's', 'p', 'a', 'm'
+G.add_edge(3, 'm')
+```
+
+At this stage the graph `G` consists of 8 nodes and 3 edges, as can be seen by:
+
+```{code-cell}
+G.number_of_nodes()
+```
+
+```{code-cell}
+G.number_of_edges()
+```
+
+```{note}
+The order of adjacency reporting (e.g., {meth}`G.adj <networkx.Graph.adj>`,
+{meth}`G.successors <networkx.DiGraph.successors>`,
+{meth}`G.predecessors <networkx.DiGraph.predecessors>`) is the order of
+edge addition. However, the order of G.edges is the order of the adjacencies
+which includes both the order of the nodes and each
+node's adjacencies. See example below:
+```
+
+```{code-cell}
+DG = nx.DiGraph()
+DG.add_edge(2, 1)   # adds the nodes in order 2, 1
+DG.add_edge(1, 3)
+DG.add_edge(2, 4)
+DG.add_edge(1, 2)
+assert list(DG.successors(2)) == [1, 4]
+assert list(DG.edges) == [(2, 1), (2, 4), (1, 3), (1, 2)]
+```
+
+## Examining elements of a graph
+
+We can examine the nodes and edges. Four basic graph properties facilitate
+reporting: `G.nodes`, `G.edges`, `G.adj` and `G.degree`. These
+are set-like views of the nodes, edges, neighbors (adjacencies), and degrees
+of nodes in a graph. They offer a continually updated read-only view into
+the graph structure. They are also dict-like in that you can look up node
+and edge data attributes via the views and iterate with data attributes
+using methods `.items()`, `.data()`.
+If you want a specific container type instead of a view, you can specify one.
+Here we use lists, though sets, dicts, tuples and other containers may be
+better in other contexts.
+
+```{code-cell}
+list(G.nodes)
+```
+
+```{code-cell}
+list(G.edges)
+```
+
+```{code-cell}
+list(G.adj[1])  # or list(G.neighbors(1))
+```
+
+```{code-cell}
+G.degree[1]  # the number of edges incident to 1
+```
+
+One can specify to report the edges and degree from a subset of all nodes
+using an {term}`nbunch`. An _nbunch_ is any of: `None` (meaning all nodes),
+a node, or an iterable container of nodes that is not itself a node in the
+graph.
+
+```{code-cell}
+G.edges([2, 'm'])
+```
+
+```{code-cell}
+G.degree([2, 3])
+```
+
+## Removing elements from a graph
+
+One can remove nodes and edges from the graph in a similar fashion to adding.
+Use methods
+{meth}`Graph.remove_node`,
+{meth}`Graph.remove_nodes_from`,
+{meth}`Graph.remove_edge`
+and
+{meth}`Graph.remove_edges_from`, e.g.
+
+```{code-cell}
+G.remove_node(2)
+G.remove_nodes_from("spam")
+list(G.nodes)
+```
+
+```{code-cell}
+G.remove_edge(1, 3)
+list(G)
+```
+
+## Using the graph constructors
+
+Graph objects do not have to be built up incrementally - data specifying
+graph structure can be passed directly to the constructors of the various
+graph classes.
+When creating a graph structure by instantiating one of the graph
+classes you can specify data in several formats.
+
+```{code-cell}
+G.add_edge(1, 2)
+H = nx.DiGraph(G)  # create a DiGraph using the connections from G
+list(H.edges())
+```
+
+```{code-cell}
+edgelist = [(0, 1), (1, 2), (2, 3)]
+H = nx.Graph(edgelist)  # create a graph from an edge list
+list(H.edges())
+```
+
+```{code-cell}
+adjacency_dict = {0: (1, 2), 1: (0, 2), 2: (0, 1)}
+H = nx.Graph(adjacency_dict)  # create a Graph dict mapping nodes to nbrs
+list(H.edges())
+```
+
+## What to use as nodes and edges
+
+You might notice that nodes and edges are not specified as NetworkX
+objects. This leaves you free to use meaningful items as nodes and
+edges. The most common choices are numbers or strings, but a node can
+be any hashable object (except `None`), and an edge can be associated
+with any object `x` using `G.add_edge(n1, n2, object=x)`.
+
+As an example, `n1` and `n2` could be protein objects from the RCSB Protein
+Data Bank, and `x` could refer to an XML record of publications detailing
+experimental observations of their interaction.
+
+We have found this power quite useful, but its abuse
+can lead to surprising behavior unless one is familiar with Python.
+If in doubt, consider using {func}`~relabel.convert_node_labels_to_integers` to obtain
+a more traditional graph with integer labels.
+
+## Accessing edges and neighbors
+
+In addition to the views {attr}`Graph.edges`, and {attr}`Graph.adj`,
+access to edges and neighbors is possible using subscript notation.
+
+```{code-cell}
+G = nx.Graph([(1, 2, {"color": "yellow"})])
+G[1]  # same as G.adj[1]
+```
+
+```{code-cell}
+G[1][2]
+```
+
+```{code-cell}
+G.edges[1, 2]
+```
+
+You can get/set the attributes of an edge using subscript notation
+if the edge already exists.
+
+```{code-cell}
+G.add_edge(1, 3)
+G[1][3]['color'] = "blue"
+G.edges[1, 2]['color'] = "red"
+G.edges[1, 2]
+```
+
+Fast examination of all (node, adjacency) pairs is achieved using
+`G.adjacency()`, or `G.adj.items()`.
+Note that for undirected graphs, adjacency iteration sees each edge twice.
+
+```{code-cell}
+FG = nx.Graph()
+FG.add_weighted_edges_from([(1, 2, 0.125), (1, 3, 0.75), (2, 4, 1.2), (3, 4, 0.375)])
+for n, nbrs in FG.adj.items():
+   for nbr, eattr in nbrs.items():
+       wt = eattr['weight']
+       if wt < 0.5: print(f"({n}, {nbr}, {wt:.3})")
+```
+
+Convenient access to all edges is achieved with the edges property.
+
+```{code-cell}
+for (u, v, wt) in FG.edges.data('weight'):
+    if wt < 0.5:
+        print(f"({u}, {v}, {wt:.3})")
+```
+
+(attributes)=
+
+## Adding attributes to graphs, nodes, and edges
+
+Attributes such as weights, labels, colors, or whatever Python object you like,
+can be attached to graphs, nodes, or edges.
+
+Each graph, node, and edge can hold key/value attribute pairs in an associated
+attribute dictionary (the keys must be hashable). By default these are empty,
+but attributes can be added or changed using `add_edge`, `add_node` or direct
+manipulation of the attribute dictionaries named `G.graph`, `G.nodes`, and
+`G.edges` for a graph `G`.
+
+### Graph attributes
+
+Assign graph attributes when creating a new graph
+
+```{code-cell}
+G = nx.Graph(day="Friday")
+G.graph
+```
+
+Or you can modify attributes later
+
+```{code-cell}
+G.graph['day'] = "Monday"
+G.graph
+```
+
+### Node attributes
+
+Add node attributes using `add_node()`, `add_nodes_from()`, or `G.nodes`
+
+```{code-cell}
+G.add_node(1, time='5pm')
+G.add_nodes_from([3], time='2pm')
+G.nodes[1]
+```
+
+```{code-cell}
+G.nodes[1]['room'] = 714
+G.nodes.data()
+```
+
+Note that adding a node to `G.nodes` does not add it to the graph, use
+`G.add_node()` to add new nodes. Similarly for edges.
+
+### Edge Attributes
+
+Add/change edge attributes using `add_edge()`, `add_edges_from()`,
+or subscript notation.
+
+```{code-cell}
+G.add_edge(1, 2, weight=4.7 )
+G.add_edges_from([(3, 4), (4, 5)], color='red')
+G.add_edges_from([(1, 2, {'color': 'blue'}), (2, 3, {'weight': 8})])
+G[1][2]['weight'] = 4.7
+G.edges[3, 4]['weight'] = 4.2
+```
+
+The special attribute `weight` should be numeric as it is used by
+algorithms requiring weighted edges.
+
+## Directed graphs
+
+The {class}`DiGraph` class provides additional methods and properties specific
+to directed edges, e.g.,
+{attr}`DiGraph.out_edges`, {attr}`DiGraph.in_degree`,
+{meth}`DiGraph.predecessors`, {meth}`DiGraph.successors` etc.
+To allow algorithms to work with both classes easily, the directed versions of
+{meth}`neighbors <DiGraph.neighbors>` is equivalent to
+{meth}`successors <DiGraph.successors>` while {attr}`DiGraph.degree` reports the sum
+of {attr}`DiGraph.in_degree` and {attr}`DiGraph.out_degree` even though that may
+feel inconsistent at times.
+
+```{code-cell}
+DG = nx.DiGraph()
+DG.add_weighted_edges_from([(1, 2, 0.5), (3, 1, 0.75)])
+DG.out_degree(1, weight='weight')
+```
+
+```{code-cell}
+DG.degree(1, weight='weight')
+```
+
+```{code-cell}
+list(DG.successors(1))
+```
+
+```{code-cell}
+list(DG.neighbors(1))
+```
+
+Some algorithms work only for directed graphs and others are not well
+defined for directed graphs. Indeed the tendency to lump directed
+and undirected graphs together is dangerous. If you want to treat
+a directed graph as undirected for some measurement you should probably
+convert it using {meth}`Graph.to_undirected` or with
+
+```{code-cell}
+H = nx.Graph(G)  # create an undirected graph H from a directed graph G
+```
+
+## Multigraphs
+
+NetworkX provides classes for graphs which allow multiple edges
+between any pair of nodes. The {class}`MultiGraph` and
+{class}`MultiDiGraph`
+classes allow you to add the same edge twice, possibly with different
+edge data. This can be powerful for some applications, but many
+algorithms are not well defined on such graphs.
+Where results are well defined,
+e.g., {meth}`MultiGraph.degree` we provide the function. Otherwise you
+should convert to a standard graph in a way that makes the measurement
+well defined.
+
+```{code-cell}
+MG = nx.MultiGraph()
+MG.add_weighted_edges_from([(1, 2, 0.5), (1, 2, 0.75), (2, 3, 0.5)])
+dict(MG.degree(weight='weight'))
+```
+
+```{code-cell}
+GG = nx.Graph()
+for n, nbrs in MG.adjacency():
+   for nbr, edict in nbrs.items():
+       minvalue = min([d['weight'] for d in edict.values()])
+       GG.add_edge(n, nbr, weight = minvalue)
+
+nx.shortest_path(GG, 1, 3)
+```
+
+## Graph generators and graph operations
+
+In addition to constructing graphs node-by-node or edge-by-edge, they
+can also be generated by
+
+### 1. Applying classic graph operations, such as:
+
+```{eval-rst}
+.. autosummary::
+
+    ~networkx.classes.function.subgraph
+    ~networkx.algorithms.operators.binary.union
+    ~networkx.algorithms.operators.binary.disjoint_union
+    ~networkx.algorithms.operators.product.cartesian_product
+    ~networkx.algorithms.operators.binary.compose
+    ~networkx.algorithms.operators.unary.complement
+    ~networkx.classes.function.create_empty_copy
+    ~networkx.classes.function.to_undirected
+    ~networkx.classes.function.to_directed
+```
+
+### 2. Using a call to one of the classic small graphs, e.g.,
+
+```{eval-rst}
+.. autosummary::
+
+    ~networkx.generators.small.petersen_graph
+    ~networkx.generators.small.tutte_graph
+    ~networkx.generators.small.sedgewick_maze_graph
+    ~networkx.generators.small.tetrahedral_graph
+```
+
+### 3. Using a (constructive) generator for a classic graph, e.g.,
+
+```{eval-rst}
+.. autosummary::
+
+    ~networkx.generators.classic.complete_graph
+    ~networkx.algorithms.bipartite.generators.complete_bipartite_graph
+    ~networkx.generators.classic.barbell_graph
+    ~networkx.generators.classic.lollipop_graph
+```
+
+like so:
+
+```{code-cell}
+K_5 = nx.complete_graph(5)
+K_3_5 = nx.complete_bipartite_graph(3, 5)
+barbell = nx.barbell_graph(10, 10)
+lollipop = nx.lollipop_graph(10, 20)
+```
+
+### 4. Using a stochastic graph generator, e.g,
+
+```{eval-rst}
+.. autosummary::
+
+    ~networkx.generators.random_graphs.erdos_renyi_graph
+    ~networkx.generators.random_graphs.watts_strogatz_graph
+    ~networkx.generators.random_graphs.barabasi_albert_graph
+    ~networkx.generators.random_graphs.random_lobster
+```
+
+like so:
+
+```{code-cell}
+er = nx.erdos_renyi_graph(100, 0.15)
+ws = nx.watts_strogatz_graph(30, 3, 0.1)
+ba = nx.barabasi_albert_graph(100, 5)
+red = nx.random_lobster(100, 0.9, 0.9)
+```
+
+### 5. Reading a graph stored in a file using common graph formats
+
+NetworkX supports many popular formats, such as edge lists, adjacency lists,
+GML, GraphML, LEDA and others.
+
+```{code-cell}
+nx.write_gml(red, "path.to.file")
+mygraph = nx.read_gml("path.to.file")
+```
+
+For details on graph formats see {doc}`/reference/readwrite/index`
+and for graph generator functions see {doc}`/reference/generators`
+
+## Analyzing graphs
+
+The structure of `G` can be analyzed using various graph-theoretic
+functions such as:
+
+```{code-cell}
+G = nx.Graph()
+G.add_edges_from([(1, 2), (1, 3)])
+G.add_node("spam")       # adds node "spam"
+list(nx.connected_components(G))
+```
+
+```{code-cell}
+sorted(d for n, d in G.degree())
+```
+
+```{code-cell}
+nx.clustering(G)
+```
+
+Some functions with large output iterate over (node, value) 2-tuples.
+These are easily stored in a `dict` structure if you desire.
+
+```{code-cell}
+sp = dict(nx.all_pairs_shortest_path(G))
+sp[3]
+```
+
+See {doc}`/reference/algorithms/index` for details on graph algorithms
+supported.
+
+## Drawing graphs
+
+NetworkX is not primarily a graph drawing package but basic drawing with
+Matplotlib as well as an interface to use the open source Graphviz software
+package are included. These are part of the {doc}`networkx.drawing <reference/drawing>`
+module and will be imported if possible.
+
+First import Matplotlib's plot interface (pylab works too)
+
+```{code-cell}
+import matplotlib.pyplot as plt
+```
+
+To test if the import of `~networkx.drawing.nx_pylab` was successful draw `G`
+using one of
+
+```{code-cell}
+G = nx.petersen_graph()
+subax1 = plt.subplot(121)
+nx.draw(G, with_labels=True, font_weight='bold')
+subax2 = plt.subplot(122)
+nx.draw_shell(G, nlist=[range(5, 10), range(5)], with_labels=True, font_weight='bold')
+```
+
+when drawing to an interactive display. Note that you may need to issue a
+Matplotlib
+
+```python
+plt.show()
+```
+
+command if you are not using matplotlib in interactive mode.
+
+```{code-cell}
+options = {
+    'node_color': 'black',
+    'node_size': 100,
+    'width': 3,
+}
+subax1 = plt.subplot(221)
+nx.draw_random(G, **options)
+subax2 = plt.subplot(222)
+nx.draw_circular(G, **options)
+subax3 = plt.subplot(223)
+nx.draw_spectral(G, **options)
+subax4 = plt.subplot(224)
+nx.draw_shell(G, nlist=[range(5,10), range(5)], **options)
+```
+
+You can find additional options via {func}`~drawing.nx_pylab.draw_networkx` and
+layouts via the {mod}`layout module<networkx.drawing.layout>`.
+You can use multiple shells with {func}`~drawing.nx_pylab.draw_shell`.
+
+```{code-cell}
+G = nx.dodecahedral_graph()
+shells = [[2, 3, 4, 5, 6], [8, 1, 0, 19, 18, 17, 16, 15, 14, 7], [9, 10, 11, 12, 13]]
+nx.draw_shell(G, nlist=shells, **options)
+```
+
+To save drawings to a file, use, for example
+
+```pycon
+>>> nx.draw(G)
+>>> plt.savefig("path.png")
+```
+
+This function writes to the file `path.png` in the local directory. If Graphviz and
+PyGraphviz or pydot, are available on your system, you can also use
+`networkx.drawing.nx_agraph.graphviz_layout` or
+`networkx.drawing.nx_pydot.graphviz_layout` to get the node positions, or write
+the graph in dot format for further processing.
+
+```pycon
+>>> from networkx.drawing.nx_pydot import write_dot
+>>> pos = nx.nx_agraph.graphviz_layout(G)
+>>> nx.draw(G, pos=pos)
+>>> write_dot(G, 'file.dot')
+```
+
+See {doc}`/reference/drawing` for additional details.
+
+## NX-Guides
+
+If you are interested in learning more about NetworkX, graph theory and network analysis
+then you should check out {doc}`nx-guides <nx-guides:index>`. There you can find tutorials,
+real-world applications and in-depth examinations of graphs and network algorithms.
+All the material is official and was developed and curated by the NetworkX community.
diff --git a/doc/tutorial.rst b/doc/tutorial.rst
deleted file mode 100644
index 43f54b69f..000000000
--- a/doc/tutorial.rst
+++ /dev/null
@@ -1,635 +0,0 @@
-Tutorial
-========
-
-.. currentmodule:: networkx
-
-This guide can help you start working with NetworkX.
-
-Creating a graph
-----------------
-
-Create an empty graph with no nodes and no edges.
-
-.. nbplot::
-
-    >>> import networkx as nx
-    >>> G = nx.Graph()
-
-By definition, a :class:`Graph` is a collection of nodes (vertices) along with
-identified pairs of nodes (called edges, links, etc).  In NetworkX, nodes can
-be any :py:term:`hashable` object e.g., a text string, an image, an XML object,
-another Graph, a customized node object, etc.
-
-.. note:: Python's ``None`` object is not allowed to be used as a node. It
-    determines whether optional function arguments have been assigned in many
-    functions.
-
-Nodes
------
-
-The graph ``G`` can be grown in several ways.  NetworkX includes many
-:doc:`graph generator functions <reference/generators>` and
-:doc:`facilities to read and write graphs in many formats <reference/readwrite/index>`.
-To get started though we'll look at simple manipulations.  You can add one node
-at a time,
-
-.. nbplot::
-
-    >>> G.add_node(1)
-
-or add nodes from any :py:term:`iterable` container, such as a list
-
-.. nbplot::
-
-    >>> G.add_nodes_from([2, 3])
-
-You can also add nodes along with node
-attributes if your container yields 2-tuples of the form 
-``(node, node_attribute_dict)``::
-
-    >>> G.add_nodes_from([
-    ...     (4, {"color": "red"}),
-    ...     (5, {"color": "green"}),
-    ... ])
-
-Node attributes are discussed further :ref:`below <attributes>`.
-
-Nodes from one graph can be incorporated into another:
-
-.. nbplot::
-
-    >>> H = nx.path_graph(10)
-    >>> G.add_nodes_from(H)
-
-``G`` now contains the nodes of ``H`` as nodes of ``G``.
-In contrast, you could use the graph ``H`` as a node in ``G``.
-
-.. nbplot::
-
-    >>> G.add_node(H)
-
-The graph ``G`` now contains ``H`` as a node.  This flexibility is very powerful as
-it allows graphs of graphs, graphs of files, graphs of functions and much more.
-It is worth thinking about how to structure your application so that the nodes
-are useful entities.  Of course you can always use a unique identifier in ``G``
-and have a separate dictionary keyed by identifier to the node information if
-you prefer.
-
-.. note:: You should not change the node object if the hash depends
-   on its contents.
-
-Edges
------
-
-``G`` can also be grown by adding one edge at a time,
-
-.. nbplot::
-
-    >>> G.add_edge(1, 2)
-    >>> e = (2, 3)
-    >>> G.add_edge(*e)  # unpack edge tuple*
-
-by adding a list of edges,
-
-.. nbplot::
-
-    >>> G.add_edges_from([(1, 2), (1, 3)])
-
-or by adding any :term:`ebunch` of edges.  An *ebunch* is any iterable
-container of edge-tuples.  An edge-tuple can be a 2-tuple of nodes or a 3-tuple
-with 2 nodes followed by an edge attribute dictionary, e.g.,
-``(2, 3, {'weight': 3.1415})``.  Edge attributes are discussed further
-:ref:`below <attributes>`.
-
-.. nbplot::
-
-    >>> G.add_edges_from(H.edges)
-
-There are no complaints when adding existing nodes or edges. For example,
-after removing all nodes and edges,
-
-.. nbplot::
-
-    >>> G.clear()
-
-we add new nodes/edges and NetworkX quietly ignores any that are
-already present.
-
-.. nbplot::
-
-    >>> G.add_edges_from([(1, 2), (1, 3)])
-    >>> G.add_node(1)
-    >>> G.add_edge(1, 2)
-    >>> G.add_node("spam")        # adds node "spam"
-    >>> G.add_nodes_from("spam")  # adds 4 nodes: 's', 'p', 'a', 'm'
-    >>> G.add_edge(3, 'm')
-
-At this stage the graph ``G`` consists of 8 nodes and 3 edges, as can be seen by:
-
-.. nbplot::
-
-    >>> G.number_of_nodes()
-    8
-    >>> G.number_of_edges()
-    3
-
-.. note:: 
-   
-   The order of adjacency reporting (e.g., :meth:`G.adj <networkx.Graph.adj>`,
-   :meth:`G.successors <networkx.DiGraph.successors>`,
-   :meth:`G.predecessors <networkx.DiGraph.predecessors>`) is the order of
-   edge addition. However, the order of G.edges is the order of the adjacencies
-   which includes both the order of the nodes and each 
-   node's adjacencies. See example below:
-
-.. nbplot::
-
-    >>> DG = nx.DiGraph()
-    >>> DG.add_edge(2, 1)   # adds the nodes in order 2, 1
-    >>> DG.add_edge(1, 3)
-    >>> DG.add_edge(2, 4)
-    >>> DG.add_edge(1, 2)
-    >>> assert list(DG.successors(2)) == [1, 4]
-    >>> assert list(DG.edges) == [(2, 1), (2, 4), (1, 3), (1, 2)]
-
-Examining elements of a graph
------------------------------
-
-We can examine the nodes and edges. Four basic graph properties facilitate
-reporting: ``G.nodes``, ``G.edges``, ``G.adj`` and ``G.degree``.  These
-are set-like views of the nodes, edges, neighbors (adjacencies), and degrees
-of nodes in a graph. They offer a continually updated read-only view into
-the graph structure. They are also dict-like in that you can look up node
-and edge data attributes via the views and iterate with data attributes
-using methods ``.items()``, ``.data()``.
-If you want a specific container type instead of a view, you can specify one.
-Here we use lists, though sets, dicts, tuples and other containers may be
-better in other contexts.
-
-.. nbplot::
-
-    >>> list(G.nodes)
-    [1, 2, 3, 'spam', 's', 'p', 'a', 'm']
-    >>> list(G.edges)
-    [(1, 2), (1, 3), (3, 'm')]
-    >>> list(G.adj[1])  # or list(G.neighbors(1))
-    [2, 3]
-    >>> G.degree[1]  # the number of edges incident to 1
-    2
-
-One can specify to report the edges and degree from a subset of all nodes
-using an :term:`nbunch`. An *nbunch* is any of: ``None`` (meaning all nodes),
-a node, or an iterable container of nodes that is not itself a node in the
-graph.
-
-.. nbplot::
-
-    >>> G.edges([2, 'm'])
-    EdgeDataView([(2, 1), ('m', 3)])
-    >>> G.degree([2, 3])
-    DegreeView({2: 1, 3: 2})
-
-Removing elements from a graph
-------------------------------
-
-One can remove nodes and edges from the graph in a similar fashion to adding.
-Use methods
-:meth:`Graph.remove_node`,
-:meth:`Graph.remove_nodes_from`,
-:meth:`Graph.remove_edge`
-and
-:meth:`Graph.remove_edges_from`, e.g.
-
-.. nbplot::
-
-    >>> G.remove_node(2)
-    >>> G.remove_nodes_from("spam")
-    >>> list(G.nodes)
-    [1, 3, 'spam']
-    >>> G.remove_edge(1, 3)
-
-Using the graph constructors
-----------------------------
-
-Graph objects do not have to be built up incrementally - data specifying
-graph structure can be passed directly to the constructors of the various
-graph classes.
-When creating a graph structure by instantiating one of the graph
-classes you can specify data in several formats.
-
-.. nbplot::
-
-    >>> G.add_edge(1, 2)
-    >>> H = nx.DiGraph(G)  # create a DiGraph using the connections from G
-    >>> list(H.edges())
-    [(1, 2), (2, 1)]
-    >>> edgelist = [(0, 1), (1, 2), (2, 3)]
-    >>> H = nx.Graph(edgelist)  # create a graph from an edge list
-    >>> list(H.edges())
-    [(0, 1), (1, 2), (2, 3)]
-    >>> adjacency_dict = {0: (1, 2), 1: (0, 2), 2: (0, 1)}
-    >>> H = nx.Graph(adjacency_dict)  # create a Graph dict mapping nodes to nbrs
-    >>> list(H.edges())
-    [(0, 1), (0, 2), (1, 2)]
-
-What to use as nodes and edges
-------------------------------
-
-You might notice that nodes and edges are not specified as NetworkX
-objects.  This leaves you free to use meaningful items as nodes and
-edges. The most common choices are numbers or strings, but a node can
-be any hashable object (except ``None``), and an edge can be associated
-with any object ``x`` using ``G.add_edge(n1, n2, object=x)``.
-
-As an example, ``n1`` and ``n2`` could be protein objects from the RCSB Protein
-Data Bank, and ``x`` could refer to an XML record of publications detailing
-experimental observations of their interaction.
-
-We have found this power quite useful, but its abuse
-can lead to surprising behavior unless one is familiar with Python.
-If in doubt, consider using :func:`~relabel.convert_node_labels_to_integers` to obtain
-a more traditional graph with integer labels.
-
-Accessing edges and neighbors
------------------------------
-
-In addition to the views :attr:`Graph.edges`, and :attr:`Graph.adj`,
-access to edges and neighbors is possible using subscript notation.
-
-.. nbplot::
-
-    >>> G = nx.Graph([(1, 2, {"color": "yellow"})])
-    >>> G[1]  # same as G.adj[1]
-    AtlasView({2: {'color': 'yellow'}})
-    >>> G[1][2]
-    {'color': 'yellow'}
-    >>> G.edges[1, 2]
-    {'color': 'yellow'}
-
-You can get/set the attributes of an edge using subscript notation
-if the edge already exists.
-
-.. nbplot::
-
-    >>> G.add_edge(1, 3)
-    >>> G[1][3]['color'] = "blue"
-    >>> G.edges[1, 2]['color'] = "red"
-    >>> G.edges[1, 2]
-    {'color': 'red'}
-
-Fast examination of all (node, adjacency) pairs is achieved using
-``G.adjacency()``, or ``G.adj.items()``.
-Note that for undirected graphs, adjacency iteration sees each edge twice.
-
-.. nbplot::
-
-    >>> FG = nx.Graph()
-    >>> FG.add_weighted_edges_from([(1, 2, 0.125), (1, 3, 0.75), (2, 4, 1.2), (3, 4, 0.375)])
-    >>> for n, nbrs in FG.adj.items():
-    ...    for nbr, eattr in nbrs.items():
-    ...        wt = eattr['weight']
-    ...        if wt < 0.5: print(f"({n}, {nbr}, {wt:.3})")
-    (1, 2, 0.125)
-    (2, 1, 0.125)
-    (3, 4, 0.375)
-    (4, 3, 0.375)
-
-Convenient access to all edges is achieved with the edges property.
-
-.. nbplot::
-
-    >>> for (u, v, wt) in FG.edges.data('weight'):
-    ...     if wt < 0.5:
-    ...         print(f"({u}, {v}, {wt:.3})")
-    (1, 2, 0.125)
-    (3, 4, 0.375)
-
-.. _attributes:
-
-Adding attributes to graphs, nodes, and edges
----------------------------------------------
-
-Attributes such as weights, labels, colors, or whatever Python object you like,
-can be attached to graphs, nodes, or edges.
-
-Each graph, node, and edge can hold key/value attribute pairs in an associated
-attribute dictionary (the keys must be hashable).  By default these are empty,
-but attributes can be added or changed using ``add_edge``, ``add_node`` or direct
-manipulation of the attribute dictionaries named ``G.graph``, ``G.nodes``, and
-``G.edges`` for a graph ``G``.
-
-Graph attributes
-~~~~~~~~~~~~~~~~
-
-Assign graph attributes when creating a new graph
-
-.. nbplot::
-
-    >>> G = nx.Graph(day="Friday")
-    >>> G.graph
-    {'day': 'Friday'}
-
-Or you can modify attributes later
-
-.. nbplot::
-
-    >>> G.graph['day'] = "Monday"
-    >>> G.graph
-    {'day': 'Monday'}
-
-Node attributes
-~~~~~~~~~~~~~~~
-
-Add node attributes using ``add_node()``, ``add_nodes_from()``, or ``G.nodes``
-
-.. nbplot::
-
-    >>> G.add_node(1, time='5pm')
-    >>> G.add_nodes_from([3], time='2pm')
-    >>> G.nodes[1]
-    {'time': '5pm'}
-    >>> G.nodes[1]['room'] = 714
-    >>> G.nodes.data()
-    NodeDataView({1: {'time': '5pm', 'room': 714}, 3: {'time': '2pm'}})
-
-Note that adding a node to ``G.nodes`` does not add it to the graph, use
-``G.add_node()`` to add new nodes. Similarly for edges.
-
-Edge Attributes
-~~~~~~~~~~~~~~~
-
-Add/change edge attributes using ``add_edge()``, ``add_edges_from()``,
-or subscript notation.
-
-.. nbplot::
-
-    >>> G.add_edge(1, 2, weight=4.7 )
-    >>> G.add_edges_from([(3, 4), (4, 5)], color='red')
-    >>> G.add_edges_from([(1, 2, {'color': 'blue'}), (2, 3, {'weight': 8})])
-    >>> G[1][2]['weight'] = 4.7
-    >>> G.edges[3, 4]['weight'] = 4.2
-
-The special attribute ``weight`` should be numeric as it is used by
-algorithms requiring weighted edges.
-
-Directed graphs
----------------
-
-The :class:`DiGraph` class provides additional methods and properties specific
-to directed edges, e.g.,
-:attr:`DiGraph.out_edges`, :attr:`DiGraph.in_degree`,
-`DiGraph.predecessors`, `DiGraph.successors` etc.
-To allow algorithms to work with both classes easily, the directed versions of
-:meth:`neighbors <DiGraph.neighbors>` is equivalent to
-`successors <DiGraph.successors>` while `~DiGraph.degree` reports the sum
-of `~DiGraph.in_degree` and `~DiGraph.out_degree` even though that may feel inconsistent at times.
-
-.. nbplot::
-
-    >>> DG = nx.DiGraph()
-    >>> DG.add_weighted_edges_from([(1, 2, 0.5), (3, 1, 0.75)])
-    >>> DG.out_degree(1, weight='weight')
-    0.5
-    >>> DG.degree(1, weight='weight')
-    1.25
-    >>> list(DG.successors(1))
-    [2]
-    >>> list(DG.neighbors(1))
-    [2]
-
-Some algorithms work only for directed graphs and others are not well
-defined for directed graphs.  Indeed the tendency to lump directed
-and undirected graphs together is dangerous.  If you want to treat
-a directed graph as undirected for some measurement you should probably
-convert it using :meth:`Graph.to_undirected` or with
-
-.. nbplot::
-
-    >>> H = nx.Graph(G)  # create an undirected graph H from a directed graph G
-
-Multigraphs
------------
-
-NetworkX provides classes for graphs which allow multiple edges
-between any pair of nodes.  The :class:`MultiGraph` and
-:class:`MultiDiGraph`
-classes allow you to add the same edge twice, possibly with different
-edge data.  This can be powerful for some applications, but many
-algorithms are not well defined on such graphs.
-Where results are well defined,
-e.g., :meth:`MultiGraph.degree` we provide the function.  Otherwise you
-should convert to a standard graph in a way that makes the measurement
-well defined.
-
-.. nbplot::
-
-    >>> MG = nx.MultiGraph()
-    >>> MG.add_weighted_edges_from([(1, 2, 0.5), (1, 2, 0.75), (2, 3, 0.5)])
-    >>> dict(MG.degree(weight='weight'))
-    {1: 1.25, 2: 1.75, 3: 0.5}
-    >>> GG = nx.Graph()
-    >>> for n, nbrs in MG.adjacency():
-    ...    for nbr, edict in nbrs.items():
-    ...        minvalue = min([d['weight'] for d in edict.values()])
-    ...        GG.add_edge(n, nbr, weight = minvalue)
-    ...
-    >>> nx.shortest_path(GG, 1, 3)
-    [1, 2, 3]
-
-Graph generators and graph operations
--------------------------------------
-
-In addition to constructing graphs node-by-node or edge-by-edge, they
-can also be generated by
-
-1. Applying classic graph operations, such as:
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. autosummary::
-
-    ~networkx.classes.function.subgraph
-    ~networkx.algorithms.operators.binary.union
-    ~networkx.algorithms.operators.binary.disjoint_union
-    ~networkx.algorithms.operators.product.cartesian_product
-    ~networkx.algorithms.operators.binary.compose
-    ~networkx.algorithms.operators.unary.complement
-    ~networkx.classes.function.create_empty_copy
-    ~networkx.classes.function.to_undirected
-    ~networkx.classes.function.to_directed
-
-2. Using a call to one of the classic small graphs, e.g.,
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. autosummary::
-
-    ~networkx.generators.small.petersen_graph
-    ~networkx.generators.small.tutte_graph
-    ~networkx.generators.small.sedgewick_maze_graph
-    ~networkx.generators.small.tetrahedral_graph
-
-3. Using a (constructive) generator for a classic graph, e.g.,
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. autosummary::
-
-    ~networkx.generators.classic.complete_graph
-    ~networkx.algorithms.bipartite.generators.complete_bipartite_graph
-    ~networkx.generators.classic.barbell_graph
-    ~networkx.generators.classic.lollipop_graph
-
-like so:
-
-.. nbplot::
-
-    >>> K_5 = nx.complete_graph(5)
-    >>> K_3_5 = nx.complete_bipartite_graph(3, 5)
-    >>> barbell = nx.barbell_graph(10, 10)
-    >>> lollipop = nx.lollipop_graph(10, 20)
-
-4. Using a stochastic graph generator, e.g,
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. autosummary::
-
-    ~networkx.generators.random_graphs.erdos_renyi_graph
-    ~networkx.generators.random_graphs.watts_strogatz_graph
-    ~networkx.generators.random_graphs.barabasi_albert_graph
-    ~networkx.generators.random_graphs.random_lobster
-
-like so:
-
-.. nbplot::
-
-    >>> er = nx.erdos_renyi_graph(100, 0.15)
-    >>> ws = nx.watts_strogatz_graph(30, 3, 0.1)
-    >>> ba = nx.barabasi_albert_graph(100, 5)
-    >>> red = nx.random_lobster(100, 0.9, 0.9)
-
-5. Reading a graph stored in a file using common graph formats
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-NetworkX supports many popular formats, such as edge lists, adjacency lists,
-GML, GraphML, LEDA and others.
-
-.. nbplot::
-
-    >>> nx.write_gml(red, "path.to.file")
-    >>> mygraph = nx.read_gml("path.to.file")
-
-For details on graph formats see :doc:`/reference/readwrite/index`
-and for graph generator functions see :doc:`/reference/generators`
-
-Analyzing graphs
-----------------
-
-The structure of ``G`` can be analyzed using various graph-theoretic
-functions such as:
-
-.. nbplot::
-
-    >>> G = nx.Graph()
-    >>> G.add_edges_from([(1, 2), (1, 3)])
-    >>> G.add_node("spam")       # adds node "spam"
-    >>> list(nx.connected_components(G))
-    [{1, 2, 3}, {'spam'}]
-    >>> sorted(d for n, d in G.degree())
-    [0, 1, 1, 2]
-    >>> nx.clustering(G)
-    {1: 0, 2: 0, 3: 0, 'spam': 0}
-
-Some functions with large output iterate over (node, value) 2-tuples.
-These are easily stored in a `dict` structure if you desire.
-
-.. nbplot::
-
-    >>> sp = dict(nx.all_pairs_shortest_path(G))
-    >>> sp[3]
-    {3: [3], 1: [3, 1], 2: [3, 1, 2]}
-
-See :doc:`/reference/algorithms/index` for details on graph algorithms
-supported.
-
-Drawing graphs
---------------
-
-NetworkX is not primarily a graph drawing package but basic drawing with
-Matplotlib as well as an interface to use the open source Graphviz software
-package are included.  These are part of the :doc:`networkx.drawing <reference/drawing>`
-module and will be imported if possible.
-
-First import Matplotlib's plot interface (pylab works too)
-
-.. nbplot::
-
-    >>> import matplotlib.pyplot as plt
-
-To test if the import of `~networkx.drawing.nx_pylab` was successful draw ``G``
-using one of
-
-.. nbplot::
-
-    >>> G = nx.petersen_graph()
-    >>> subax1 = plt.subplot(121)
-    >>> nx.draw(G, with_labels=True, font_weight='bold')
-    >>> subax2 = plt.subplot(122)
-    >>> nx.draw_shell(G, nlist=[range(5, 10), range(5)], with_labels=True, font_weight='bold')
-
-when drawing to an interactive display.  Note that you may need to issue a
-Matplotlib
-
->>> plt.show()  # doctest: +SKIP
-
-command if you are not using matplotlib in interactive mode.
-
-.. nbplot::
-
-    >>> options = {
-    ...     'node_color': 'black',
-    ...     'node_size': 100,
-    ...     'width': 3,
-    ... }
-    >>> subax1 = plt.subplot(221)
-    >>> nx.draw_random(G, **options)
-    >>> subax2 = plt.subplot(222)
-    >>> nx.draw_circular(G, **options)
-    >>> subax3 = plt.subplot(223)
-    >>> nx.draw_spectral(G, **options)
-    >>> subax4 = plt.subplot(224)
-    >>> nx.draw_shell(G, nlist=[range(5,10), range(5)], **options)
-
-You can find additional options via :func:`~drawing.nx_pylab.draw_networkx` and
-layouts via the :mod:`layout module<networkx.drawing.layout>`.
-You can use multiple shells with :func:`~drawing.nx_pylab.draw_shell`.
-
-.. nbplot::
-
-    >>> G = nx.dodecahedral_graph()
-    >>> shells = [[2, 3, 4, 5, 6], [8, 1, 0, 19, 18, 17, 16, 15, 14, 7], [9, 10, 11, 12, 13]]
-    >>> nx.draw_shell(G, nlist=shells, **options)
-
-To save drawings to a file, use, for example
-
->>> nx.draw(G)
->>> plt.savefig("path.png")
-
-This function writes to the file ``path.png`` in the local directory. If Graphviz and
-PyGraphviz or pydot, are available on your system, you can also use
-`networkx.drawing.nx_agraph.graphviz_layout` or
-`networkx.drawing.nx_pydot.graphviz_layout` to get the node positions, or write
-the graph in dot format for further processing.
-
->>> from networkx.drawing.nx_pydot import write_dot
->>> pos = nx.nx_agraph.graphviz_layout(G)
->>> nx.draw(G, pos=pos)
->>> write_dot(G, 'file.dot')
-
-See :doc:`/reference/drawing` for additional details.
-
-.. code-links::
-
-NX-Guides
----------
-If you are interested in learning more about NetworkX, graph theory and network analysis 
-then you should check out :doc:`nx-guides <nx-guides:index>`. There you can find tutorials,
-real-world applications and in-depth examinations of graphs and network algorithms. 
-All the material is official and was developed and curated by the NetworkX community. 
diff --git a/networkx/algorithms/tree/branchings.py b/networkx/algorithms/tree/branchings.py
index eebc99a75..e6c84051d 100644
--- a/networkx/algorithms/tree/branchings.py
+++ b/networkx/algorithms/tree/branchings.py
@@ -28,7 +28,6 @@ This implementation is based on:
 # }
 import string
 from dataclasses import dataclass, field
-from enum import Enum
 from operator import itemgetter
 from queue import PriorityQueue
 
@@ -470,7 +469,6 @@ class Edmonds:
         D = set()
         nodes = iter(list(G.nodes()))
         attr = self._attr
-        G_pred = G.pred
 
         def desired_edge(v):
             """
@@ -1181,15 +1179,15 @@ def minimum_branching(
     G, attr="weight", default=1, preserve_attrs=False, partition=None
 ):
     for _, _, d in G.edges(data=True):
-        d[attr] = -d[attr]
+        d[attr] = -d.get(attr, default)
 
     B = maximum_branching(G, attr, default, preserve_attrs, partition)
 
     for _, _, d in G.edges(data=True):
-        d[attr] = -d[attr]
+        d[attr] = -d.get(attr, default)
 
     for _, _, d in B.edges(data=True):
-        d[attr] = -d[attr]
+        d[attr] = -d.get(attr, default)
 
     return B
 
@@ -1233,7 +1231,7 @@ def minimal_branching(
     """
     max_weight = -INF
     min_weight = INF
-    for _, _, w in G.edges(data=attr):
+    for _, _, w in G.edges(data=attr, default=default):
         if w > max_weight:
             max_weight = w
         if w < min_weight:
@@ -1244,16 +1242,16 @@ def minimal_branching(
         # the difference between the max and min weights. This is important
         # in order to prevent the edge weights from becoming negative during
         # computation
-        d[attr] = max_weight + 1 + (max_weight - min_weight) - d[attr]
+        d[attr] = max_weight + 1 + (max_weight - min_weight) - d.get(attr, default)
 
     B = maximum_branching(G, attr, default, preserve_attrs, partition)
 
     # Reverse the weight transformations
     for _, _, d in G.edges(data=True):
-        d[attr] = max_weight + 1 + (max_weight - min_weight) - d[attr]
+        d[attr] = max_weight + 1 + (max_weight - min_weight) - d.get(attr, default)
 
     for _, _, d in B.edges(data=True):
-        d[attr] = max_weight + 1 + (max_weight - min_weight) - d[attr]
+        d[attr] = max_weight + 1 + (max_weight - min_weight) - d.get(attr, default)
 
     return B
 
@@ -1277,22 +1275,22 @@ def maximum_spanning_arborescence(
 
     min_weight = INF
     max_weight = -INF
-    for _, _, w in G.edges(data=attr):
+    for _, _, w in G.edges(data=attr, default=default):
         if w < min_weight:
             min_weight = w
         if w > max_weight:
             max_weight = w
 
     for _, _, d in G.edges(data=True):
-        d[attr] = d[attr] - min_weight + 1 - (min_weight - max_weight)
+        d[attr] = d.get(attr, default) - min_weight + 1 - (min_weight - max_weight)
 
     B = maximum_branching(G, attr, default, preserve_attrs, partition)
 
     for _, _, d in G.edges(data=True):
-        d[attr] = d[attr] + min_weight - 1 + (min_weight - max_weight)
+        d[attr] = d.get(attr, default) + min_weight - 1 + (min_weight - max_weight)
 
     for _, _, d in B.edges(data=True):
-        d[attr] = d[attr] + min_weight - 1 + (min_weight - max_weight)
+        d[attr] = d.get(attr, default) + min_weight - 1 + (min_weight - max_weight)
 
     if not is_arborescence(B):
         raise nx.exception.NetworkXException("No maximum spanning arborescence in G.")
@@ -1365,8 +1363,8 @@ maximum_branching.__doc__ = docstring_branching.format(
 minimum_branching.__doc__ = (
     docstring_branching.format(kind="minimum", style="branching")
     + """
-See Also 
--------- 
+See Also
+--------
     minimal_branching
 """
 )
diff --git a/networkx/drawing/nx_pylab.py b/networkx/drawing/nx_pylab.py
index 727111fb7..6171c0f7d 100644
--- a/networkx/drawing/nx_pylab.py
+++ b/networkx/drawing/nx_pylab.py
@@ -858,9 +858,6 @@ def draw_networkx_edges(
             raise TypeError("Argument `arrows` must be of type bool or None")
         use_linecollection = not arrows
 
-    if arrowstyle is None:
-        arrowstyle = "-|>" if G.is_directed() else "-"
-
     if isinstance(connectionstyle, str):
         connectionstyle = [connectionstyle]
     elif np.iterable(connectionstyle):
@@ -897,6 +894,10 @@ def draw_networkx_edges(
                 msg.format("connectionstyle"), category=UserWarning, stacklevel=2
             )
 
+    # NOTE: Arrowstyle modification must occur after the warnings section
+    if arrowstyle is None:
+        arrowstyle = "-|>" if G.is_directed() else "-"
+
     if ax is None:
         ax = plt.gca()
 
diff --git a/pyproject.toml b/pyproject.toml
index dd6abc72c..47be7f9a6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -72,8 +72,8 @@ doc = [
     'sphinx-gallery>=0.14',
     'numpydoc>=1.6',
     'pillow>=9.4',
-    'nb2plots>=0.7',
     'texext>=0.6.7',
+    'myst-nb>=1.0',
 ]
 extra = [
     'lxml>=4.6',
diff --git a/requirements/doc.txt b/requirements/doc.txt
index 73cc513ad..7fe8fea6a 100644
--- a/requirements/doc.txt
+++ b/requirements/doc.txt
@@ -5,5 +5,5 @@ pydata-sphinx-theme>=0.14
 sphinx-gallery>=0.14
 numpydoc>=1.6
 pillow>=9.4
-nb2plots>=0.7
 texext>=0.6.7
+myst-nb>=1.0


Test patch:
diff --git a/networkx/algorithms/tree/tests/test_branchings.py b/networkx/algorithms/tree/tests/test_branchings.py
index ad1cc33b9..905860671 100644
--- a/networkx/algorithms/tree/tests/test_branchings.py
+++ b/networkx/algorithms/tree/tests/test_branchings.py
@@ -630,3 +630,29 @@ def test_arborescence_iterator_initial_partition():
         for e in excluded_edges:
             assert e not in B.edges
     assert arborescence_count == 16
+
+
+def test_branchings_with_default_weights():
+    """
+    Tests that various brancing algorithms work on graphs without weights.
+    For more information, see issue #7279.
+    """
+    graph = nx.erdos_renyi_graph(10, p=0.2, directed=True, seed=123)
+
+    assert all(
+        "weight" not in d for (u, v, d) in graph.edges(data=True)
+    ), "test is for graphs without a weight attribute"
+
+    # Calling these functions will modify graph inplace to add weights
+    # copy the graph to avoid this.
+    nx.minimum_spanning_arborescence(graph.copy())
+    nx.maximum_spanning_arborescence(graph.copy())
+    nx.minimum_branching(graph.copy())
+    nx.maximum_branching(graph.copy())
+    nx.algorithms.tree.minimal_branching(graph.copy())
+    nx.algorithms.tree.branching_weight(graph.copy())
+    nx.algorithms.tree.greedy_branching(graph.copy())
+
+    assert all(
+        "weight" not in d for (u, v, d) in graph.edges(data=True)
+    ), "The above calls should not modify the initial graph in-place"
diff --git a/networkx/drawing/tests/test_pylab.py b/networkx/drawing/tests/test_pylab.py
index bfc226273..38f92964d 100644
--- a/networkx/drawing/tests/test_pylab.py
+++ b/networkx/drawing/tests/test_pylab.py
@@ -833,3 +833,15 @@ def test_user_warnings_for_unused_edge_drawing_kwargs(fap_only_kwarg):
         nx.draw_networkx_edges(G, pos, ax=ax, arrows=True, **fap_only_kwarg)
 
     plt.delaxes(ax)
+
+
+@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)
diff --git a/networkx/generators/tests/test_random_clustered.py b/networkx/generators/tests/test_random_clustered.py
index e3a37b6ad..85066520a 100644
--- a/networkx/generators/tests/test_random_clustered.py
+++ b/networkx/generators/tests/test_random_clustered.py
@@ -1,34 +1,33 @@
 import pytest
 
-import networkx
+import networkx as nx
 
 
 class TestRandomClusteredGraph:
-    def test_valid(self):
+    def test_custom_joint_degree_sequence(self):
         node = [1, 1, 1, 2, 1, 2, 0, 0]
         tri = [0, 0, 0, 0, 0, 1, 1, 1]
         joint_degree_sequence = zip(node, tri)
-        G = networkx.random_clustered_graph(joint_degree_sequence)
+        G = nx.random_clustered_graph(joint_degree_sequence)
         assert G.number_of_nodes() == 8
         assert G.number_of_edges() == 7
 
-    def test_valid2(self):
-        G = networkx.random_clustered_graph(
-            [(1, 2), (2, 1), (1, 1), (1, 1), (1, 1), (2, 0)]
-        )
+    def test_tuple_joint_degree_sequence(self):
+        G = nx.random_clustered_graph([(1, 2), (2, 1), (1, 1), (1, 1), (1, 1), (2, 0)])
         assert G.number_of_nodes() == 6
         assert G.number_of_edges() == 10
 
-    def test_invalid1(self):
-        pytest.raises(
-            (TypeError, networkx.NetworkXError),
-            networkx.random_clustered_graph,
-            [[1, 1], [2, 1], [0, 1]],
-        )
+    def test_invalid_joint_degree_sequence_type(self):
+        with pytest.raises(nx.NetworkXError, match="Invalid degree sequence"):
+            nx.random_clustered_graph([[1, 1], [2, 1], [0, 1]])
 
-    def test_invalid2(self):
-        pytest.raises(
-            (TypeError, networkx.NetworkXError),
-            networkx.random_clustered_graph,
-            [[1, 1], [1, 2], [0, 1]],
-        )
+    def test_invalid_joint_degree_sequence_value(self):
+        with pytest.raises(nx.NetworkXError, match="Invalid degree sequence"):
+            nx.random_clustered_graph([[1, 1], [1, 2], [0, 1]])
+
+    def test_directed_graph_raises_error(self):
+        with pytest.raises(nx.NetworkXError, match="Directed Graph not supported"):
+            nx.random_clustered_graph(
+                [(1, 2), (2, 1), (1, 1), (1, 1), (1, 1), (2, 0)],
+                create_using=nx.DiGraph,
+            )