14
votes

I need to draw a directed graph with more than one edge (with different weights) between two nodes. That is, I have nodes A and B and edges (A,B) with length=2 and (B,A) with length=3.

I have tried both using G=nx.Digraph and G=nx.Multidigraph. When I draw it, I only get to view one edge and only one of the labels. Is there any way to do it?

5

5 Answers

17
votes

An improvement to the reply above is adding the connectionstyle to nx.draw, this allows to see two parallel lines in the plot:

import networkx as nx
import matplotlib.pyplot as plt
G = nx.DiGraph() #or G = nx.MultiDiGraph()
G.add_node('A')
G.add_node('B')
G.add_edge('A', 'B', length = 2)
G.add_edge('B', 'A', length = 3)

pos = nx.spring_layout(G)
nx.draw(G, pos, with_labels=True, connectionstyle='arc3, rad = 0.1')
edge_labels=dict([((u,v,),d['length'])
             for u,v,d in G.edges(data=True)])

plt.show()

See here the result

13
votes

Try the following:

import networkx as nx
import matplotlib.pyplot as plt
G = nx.DiGraph() #or G = nx.MultiDiGraph()
G.add_node('A')
G.add_node('B')
G.add_edge('A', 'B', length = 2)
G.add_edge('B', 'A', length = 3)

pos = nx.spring_layout(G)
nx.draw(G, pos)
edge_labels=dict([((u,v,),d['length'])
             for u,v,d in G.edges(data=True)])
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, label_pos=0.3, font_size=7)
plt.show()

This will return you this graph with two edges and the length shown on the edge:

enter image description here

1
votes

You can use matplotlib directly using the node positions you have calculated.

G=nx.MultiGraph ([(1,2),(1,2),(1,2),(3,1),(3,2)])
pos = nx.random_layout(G)
nx.draw_networkx_nodes(G, pos, node_color = 'r', node_size = 100, alpha = 1)
ax = plt.gca()
for e in G.edges:
    ax.annotate("",
                xy=pos[e[0]], xycoords='data',
                xytext=pos[e[1]], textcoords='data',
                arrowprops=dict(arrowstyle="->", color="0.5",
                                shrinkA=5, shrinkB=5,
                                patchA=None, patchB=None,
                                connectionstyle="arc3,rad=rrr".replace('rrr',str(0.3*e[2])
                                ),
                                ),
                )
plt.axis('off')
plt.show()

enter image description here

0
votes

Add the following code to AMangipinto's solution to add edge labels in both directions (see link for picture):

edge_labels = dict([((u, v,), f'{d["length"]}\n\n{G.edges[(v,u)]["length"]}')
                for u, v, d in G.edges(data=True) if pos[u][0] > pos[v][0]])

nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_color='red')

The "if pos[u][0] > pos[v][0]" only adds an edge label in one direction. We add both lengths to the single label otherwise we would over write the first label on an edge. Note: The label won't show if the nodes have the same x position.

plot with edge labels

0
votes

enter image description here

There are two common ways to draw bi-directional edges between two nodes:

  1. Draw both edges as straight lines, each parallel to but slightly offset from the direct line connecting the nodes.
  2. 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:

  1. 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).

  2. 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()