
There are two common ways to draw bi-directional edges between two nodes:
- Draw both edges as straight lines, each parallel to but slightly offset from the direct line connecting the nodes.
- Draw both edges as curved lines; ensure that they arc in different directions.
In both cases, labels can simply be placed at the centre of the two lines.
Both approaches don't mesh well with the current state of the networkx drawing utilities:
The first approach requires a good choice of offset between the
parallel edges. Common choices in other libraries include the
average edge width or a third of the node size. However, node
positions in networkx are given in data coordinates whereas node
sizes and edge widths are given in display coordinates. This makes
computation of the offset cumbersome, and -- more importantly --
the layout breaks if the figure is resized (as the transformation
from data coordinates to display coordinates changes).
As outlined in other answers, networkx can draw curved edges by
setting the correct connectionstyle. However, this feature was
added relatively recently to networkx and hence the function that
draws the labels still assumes straight edges. If the edges only
have a very small arc (i.e. are still basically straight), then the
labels can be fudged to the approximate correct positions by adding
newline characters in the right places to the labels, as
demonstrated by @PaulMenzies answer. However, this approach
generally yields suboptimal results and breaks if the curvature is
high.
If you are open to use other plotting utilities built on matplotlib,
I have an implementation of both approaches in my module
netgraph. netgraph is
fully compatible with networkx and igraph Graph objects, so it should
be easy and fast to generate good looking graphs.
#!/usr/bin/env python
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
from netgraph import Graph # pip install netgraph
triangle = nx.DiGraph([('a', 'b'), ('a', 'c'), ('b', 'a'), ('c', 'b'), ('c', 'c')])
node_positions = {
'a' : np.array([0.2, 0.2]),
'b' : np.array([0.8, 0.2]),
'c' : np.array([0.5, 0.8]),
}
edge_labels = {
('a', 'b') : 3,
('a', 'c') : 'Lorem ipsum',
('b', 'a') : 4,
('c', 'b') : 'dolor sit',
('c', 'c') : r'$\pi$'
}
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14,14))
Graph(triangle, node_labels=True, edge_labels=edge_labels,
edge_label_fontdict=dict(size=12, fontweight='bold'),
node_layout=node_positions, edge_layout='straight',
node_size=6, edge_width=4, arrows=True, ax=ax1)
Graph(triangle, node_labels=True, edge_labels=edge_labels,
edge_label_fontdict=dict(size=12, fontweight='bold'),
node_layout=node_positions, edge_layout='curved',
node_size=6, edge_width=4, arrows=True, ax=ax2)
plt.show()