Repository: networkx/networkx

Issue:
draw_networkx_edge_labels examples don't work with MultiDiGraphs
<!-- If you have a general question about NetworkX, please use the discussions tab to create a new discussion -->

<!--- Provide a general summary of the issue in the Title above -->
The canonical examples I seem to be able to find for drawing edge labels look something like this:
```
edge_labels = nx.get_edge_attributes(G, 'title')
nx.draw(G)
nx.draw_networkx_edge_labels(G, edge_labels=edge_labels)
nx.draw_networkx(G)
plt.show()
```

### Current Behavior
<!--- Tell us what happens instead of the expected behavior -->
When applied to a MultiDiGraph, the way in which this data is structured isn't supported by `draw_networkx_edge_labels`. You get something like the following exception:

```
        import matplotlib.pyplot as plt
        import numpy as np
    
        if ax is None:
            ax = plt.gca()
        if edge_labels is None:
            labels = {(u, v): d for u, v, d in G.edges(data=True)}
        else:
            labels = edge_labels
        text_items = {}
>       for (n1, n2), label in labels.items():
E       ValueError: too many values to unpack (expected 2)
```

This is because the edge attributes on a MultiDiGraph are a 3-tuple which includes the key.

### Expected Behavior
<!--- Tell us what should happen -->
The `draw_networkx_edge_labels` function should handle both the 2-tuple for single graphs and the 3-tuple case for multi graphs.

### Steps to Reproduce
<!--- Provide a minimal example that reproduces the bug -->

### Environment
<!--- Please provide details about your local environment -->
Python version: 3.9
NetworkX version:


### Additional context
<!--- Add any other context about the problem here, screenshots, etc. -->



Discussion:
You can transform the edge labels returned by `nx.get_edge_attributes` into the correct form with the following snippet:
```
edge_labels = {(u, v): label for (u, v, k), label in nx.get_edge_attributes(G, 'title').items()}
```
Good point - there isn't really much in the way of automatic support for multiedge visualization. It would be a very nice enhancement, but would likely require quite a bit of effort to figure out how to handle the positioning of multiple edges in a generic way.

As you've pointed out, one solution is to "reduce" multiedges into a single edge. It's important to note that the suggestion above implicitly sets the edge attribute to whatever the value is for the last edge in terms of insertion order. A more generic solution to multiedge reduction might look something like:

```python
>>> from collections import defaultdict
>>> all_edge_attrs = defaultdict(list)
>>> for u, v, val in G.edges(data="title"):
...     all_edge_attrs[(u, v)].append(val)
>>> edge_labels = {k: op(v) for k, v in all_edge_attrs.items()}
```
Where `op` in this case can be whatever reduction function you want, e.g `sum`, `min`, `max`, `np.median`, etc.

In the absence of generic support for multiedge positioning/labeling, I think a more informative error message for this scenario would be an improvement.

Resolving patch:
diff --git a/networkx/drawing/nx_pylab.py b/networkx/drawing/nx_pylab.py
--- a/networkx/drawing/nx_pylab.py
+++ b/networkx/drawing/nx_pylab.py
@@ -1130,6 +1130,13 @@ def draw_networkx_edge_labels(
         labels = {(u, v): d for u, v, d in G.edges(data=True)}
     else:
         labels = edge_labels
+        # Informative exception for multiedges
+        try:
+            (u, v), d = next(iter(labels.items()))
+        except ValueError as err:
+            raise nx.NetworkXError(
+                "draw_networkx_edge_labels does not support multiedges."
+            ) from err
     text_items = {}
     for (n1, n2), label in labels.items():
         (x1, y1) = pos[n1]


Test patch:
diff --git a/networkx/drawing/tests/test_pylab.py b/networkx/drawing/tests/test_pylab.py
--- a/networkx/drawing/tests/test_pylab.py
+++ b/networkx/drawing/tests/test_pylab.py
@@ -675,3 +675,18 @@ def test_edgelist_kwarg_not_ignored():
     nx.draw(G, edgelist=[(0, 1), (1, 2)], ax=ax)  # Exclude self-loop from edgelist
     assert not ax.patches
     plt.delaxes(ax)
+
+
+def test_draw_networkx_edge_label_multiedge_exception():
+    """
+    draw_networkx_edge_labels should raise an informative error message when
+    the edge label includes keys
+    """
+    exception_msg = "draw_networkx_edge_labels does not support multiedges"
+    G = nx.MultiGraph()
+    G.add_edge(0, 1, weight=10)
+    G.add_edge(0, 1, weight=20)
+    edge_labels = nx.get_edge_attributes(G, "weight")  # Includes edge keys
+    pos = {n: (n, n) for n in G}
+    with pytest.raises(nx.NetworkXError, match=exception_msg):
+        nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels)