Make edges start from outside the node in Networkx - python

I have a weighted circular layout plot.
I wanted to make edges start from the outside of the node, but cannot find a way to do so. I tried setting alpha=1, but that didn't give me the desired outcome.
The image below displays what I'm getting right now
This is the following code I have for nodes right now:
for n in G.nodes():
if n in set1:
G.nodes[n]['color'] = '#7a8eff'
elif n in set2:
G.nodes[n]['color'] = '#eb2c30'
elif n in set3:
G.nodes[n]['color'] = '#7300ff'
else:
G.nodes[n]['color'] = '#730a15'
colors = [node[1]['color'] for node in G.nodes(data=True)]
nx.draw_networkx_nodes(G, pos, node_size=1000, node_color=colors)
# edges
for edge in G.edges():
source, target = edge
rad = 0.25
node_color_dict = dict(G.nodes(data='color'))
if node_color_dict[source] == node_color_dict[target]:
arrowprops=dict(lw=G.edges[(source,target)]['weight'],
arrowstyle="-",
color='blue',
connectionstyle=f"arc3,rad={rad}",
linestyle= '-',
alpha=0.45)
ax.annotate("",
xy=pos[source],
xytext=pos[target],
arrowprops=arrowprops
)
else:
arrowprops=dict(lw=G.edges[(source,target)]['weight'],
arrowstyle="-",
color='purple',
connectionstyle=f"arc3,rad={rad}",
linestyle= '-',
alpha=0.45)
ax.annotate("",
xy=pos[source],
xytext=pos[target],
arrowprops=arrowprops
)
# labels
nx.draw_networkx_labels(G, pos, font_size=11, font_family="monospace", font_color='white', font_weight='bold', alpha=1.0)

A full example, which produces the desired result:
import networkx as nx
import matplotlib.pylab as pl
G = nx.karate_club_graph()
pos = nx.kamada_kawai_layout(G)
set1 = set(node for node in G if G.nodes[node]["club"] == "Mr. Hi")
set2 = set(node for node in G if G.nodes[node]["club"] != "Mr. Hi")
for n in G.nodes():
if n in set1:
G.nodes[n]['color'] = '#7a8eff'
elif n in set2:
G.nodes[n]['color'] = '#eb2c30'
for u, v in G.edges():
G.edges[(u,v)]["weight"] = 1
colors = [node[1]['color'] for node in G.nodes(data=True)]
nodes_draw = nx.draw_networkx_nodes(G, pos, node_size=1000, node_color=colors)
ax = pl.gca()
# draw in the order, edges, nodes, node labels
zorder_edges = 3
zorder_nodes = 4
zorder_node_labels = 5
# edges
for edge in G.edges():
source, target = edge
rad = 0.25
node_color_dict = dict(G.nodes(data='color'))
if node_color_dict[source] == node_color_dict[target]:
arrowprops=dict(lw=G.edges[(source,target)]['weight'],
arrowstyle="-",
color='blue',
connectionstyle=f"arc3,rad={rad}",
linestyle= '-',
#alpha=0.45,
zorder=zorder_edges,
)
ax.annotate("",
xy=pos[source],
xytext=pos[target],
arrowprops=arrowprops
)
else:
arrowprops=dict(lw=G.edges[(source,target)]['weight'],
arrowstyle="-",
color='purple',
connectionstyle=f"arc3,rad={rad}",
linestyle= '-',
#alpha=0.45,
zorder=zorder_edges,
)
ax.annotate("",
xy=pos[source],
xytext=pos[target],
arrowprops=arrowprops
)
# labels
node_labels_dict = nx.draw_networkx_labels(G, pos, font_size=11, font_family="monospace", font_color='white', font_weight='bold',
#alpha=1.0
)
nodes_draw.set_zorder(zorder_nodes)
for node_labels_draw in node_labels_dict.values():
node_labels_draw.set_zorder(zorder_node_labels)
pl.axis("off")
# do don't cut off nodes
ax.set_xlim([1.1*x for x in ax.get_xlim()])
ax.set_ylim([1.1*y for y in ax.get_ylim()])
pl.show()
The result:
Background
You can change the zorder of the created matplotlib objects:
import networkx as nx
import matplotlib.pylab as pl
# an example graph with string (names) as nodes
g = nx.karate_club_graph()
pos = nx.kamada_kawai_layout(g)
e = nx.draw_networkx_edges(g, pos=pos, )
n = nx.draw_networkx_nodes(g, pos=pos, )
e.set_zorder(5)
n.set_zorder(10)
pl.show()
In case you use some advanced edge drawing, add the zorder parameter to the arrowprops parameter (all possible parameters) of annotate, e.g.
arrowprops=dict(lw=G.edges[(source,target)]['weight'],
arrowstyle="-",
color='blue',
connectionstyle=f"arc3,rad={rad}",
linestyle= '-',
alpha=0.45,
zorder=0)
I've included this answer to avoid cutting nodes at the border.

Related

While plotting the graph using networkx and matplotlib, the weight for self-loop is not being diaplyed

Basically I want the weights of all my edges, including the self loop to be displayed.
def graphTrial():
G = nx.Graph()
# Add nodes
G.add_nodes_from(['A', 'B', 'C'])
# Add edges with weights
G.add_edge('A', 'A', weight=5)
G.add_edge('A', 'B', weight=2)
G.add_edge('B', 'C', weight=3)
# Define positions for nodes
pos = nx.spring_layout(G)
# Draw nodes and labels
nx.draw_networkx_nodes(G, pos)
nx.draw_networkx_labels(G, pos)
# Draw edges with weights
edge_labels = {(u, v): d['weight'] for u, v, d in G.edges(data=True)}
nx.draw_networkx_edges(G, pos)
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels)
plt.axis('off')
plt.show()
Output
Note, that the text object exists but is placed behind the node cause the interpolation does not take self loops into account.
To place it visibly, I adapted the networkx code from here networkx. Here is the custom function.
def draw_networkx_edge_labels(
G,
pos,
edge_labels=None,
label_pos=0.5,
font_size=10,
font_color="k",
font_family="sans-serif",
font_weight="normal",
alpha=None,
bbox=None,
horizontalalignment="center",
verticalalignment="center",
ax=None,
rotate=True,
clip_on=True,
):
"""Draw edge labels.
Parameters
----------
G : graph
A networkx graph
pos : dictionary
A dictionary with nodes as keys and positions as values.
Positions should be sequences of length 2.
edge_labels : dictionary (default=None)
Edge labels in a dictionary of labels keyed by edge two-tuple.
Only labels for the keys in the dictionary are drawn.
label_pos : float (default=0.5)
Position of edge label along edge (0=head, 0.5=center, 1=tail)
font_size : int (default=10)
Font size for text labels
font_color : string (default='k' black)
Font color string
font_weight : string (default='normal')
Font weight
font_family : string (default='sans-serif')
Font family
alpha : float or None (default=None)
The text transparency
bbox : Matplotlib bbox, optional
Specify text box properties (e.g. shape, color etc.) for edge labels.
Default is {boxstyle='round', ec=(1.0, 1.0, 1.0), fc=(1.0, 1.0, 1.0)}.
horizontalalignment : string (default='center')
Horizontal alignment {'center', 'right', 'left'}
verticalalignment : string (default='center')
Vertical alignment {'center', 'top', 'bottom', 'baseline', 'center_baseline'}
ax : Matplotlib Axes object, optional
Draw the graph in the specified Matplotlib axes.
rotate : bool (default=True)
Rotate edge labels to lie parallel to edges
clip_on : bool (default=True)
Turn on clipping of edge labels at axis boundaries
Returns
-------
dict
`dict` of labels keyed by edge
Examples
--------
>>> G = nx.dodecahedral_graph()
>>> edge_labels = nx.draw_networkx_edge_labels(G, pos=nx.spring_layout(G))
Also see the NetworkX drawing examples at
https://networkx.org/documentation/latest/auto_examples/index.html
See Also
--------
draw
draw_networkx
draw_networkx_nodes
draw_networkx_edges
draw_networkx_labels
"""
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
# Informative exception for multiedges
try:
(u, v) = next(iter(labels)) # ensures no edge key provided
except ValueError as err:
raise nx.NetworkXError(
"draw_networkx_edge_labels does not support multiedges."
) from err
except StopIteration:
pass
text_items = {}
for (n1, n2), label in labels.items():
(x1, y1) = pos[n1]
(x2, y2) = pos[n2]
# no self-loop
if n1 != n2:
# this interpolates between pos[n1] and pos[n2] to place the label,
# but just yields x1, y1 in case of self loops placing the label
# behind the node
(x, y) = (
x1 * label_pos + x2 * (1.0 - label_pos),
y1 * label_pos + y2 * (1.0 - label_pos),
)
# self-loop; we find the loop coordinate to place the text
else:
# to identify the self-loop that corresponds to the node n1==n2
loc = pos[n1]
path_distance = np.zeros(len(ax.patches))
for i, patch in enumerate(ax.patches):
path = patch.get_path()
loc = np.repeat(loc[None], len(path.vertices), axis=0)
path_distance[i] = np.min(np.linalg.norm(loc - path.vertices, axis=1))
patch = ax.patches[np.argmin(path_distance)]
path = patch.get_path()
x, y = path.vertices[int((0.5 - label_pos) * len(path.vertices))]
if rotate:
# in degrees
angle = np.arctan2(y2 - y1, x2 - x1) / (2.0 * np.pi) * 360
# make label orientation "right-side-up"
if angle > 90:
angle -= 180
if angle < -90:
angle += 180
# transform data coordinate angle to screen coordinate angle
xy = np.array((x, y))
trans_angle = ax.transData.transform_angles(
np.array((angle,)), xy.reshape((1, 2))
)[0]
else:
trans_angle = 0.0
# use default box of white with white border
if bbox is None:
bbox = dict(boxstyle="round", ec=(1.0, 1.0, 1.0), fc=(1.0, 1.0, 1.0))
if not isinstance(label, str):
label = str(label) # this makes "1" and 1 labeled the same
t = ax.text(
x,
y,
label,
size=font_size,
color=font_color,
family=font_family,
weight=font_weight,
alpha=alpha,
horizontalalignment=horizontalalignment,
verticalalignment=verticalalignment,
rotation=trans_angle,
transform=ax.transData,
bbox=bbox,
zorder=1,
clip_on=clip_on,
)
text_items[(n1, n2)] = t
ax.tick_params(
axis="both",
which="both",
bottom=False,
left=False,
labelbottom=False,
labelleft=False,
)
return text_items
Then your code yields the wanted output.
G = nx.Graph()
# Add nodes
G.add_nodes_from(['A', 'B', 'C', 'D'])
# Add edges with weights
G.add_edge('A', 'A', weight=5)
G.add_edge('A', 'B', weight=2)
G.add_edge('B', 'C', weight=3)
G.add_edge('C', 'C', weight=5)
G.add_edge('B', 'B', weight=2)
G.add_edge('D', 'A', weight=5)
# Define positions for nodes
pos = nx.spring_layout(G)
# Draw nodes and labels
nx.draw_networkx_nodes(G, pos)
nx.draw_networkx_labels(G, pos)
# Draw edges with weights
edge_labels = {(u, v): d['weight'] for u, v, d in G.edges(data=True)}
nx.draw_networkx_edges(G, pos)
texts = draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, label_pos=0.5, rotate=True)
plt.axis('off')
plt.show()

Curved edges in NetworkX

I currently have the following code for my networkx graph:
import matplotlib.pyplot as plt
import networkx as nx
g = nx.Graph()
# add edges
g.add_edge("a", "b", weight=0.6)
g.add_edge("a", "c", weight=0.2)
g.add_edge("c", "d", weight=0.1)
g.add_edge("c", "e", weight=0.7)
g.add_edge("c", "f", weight=0.9)
g.add_edge("a", "d", weight=0.3)
# group edges by attribute "weight"
elarge = [
(u, v) for (u, v, d) in g.edges(data=True)
if d["weight"] > 0.5]
esmall = [
(u, v) for (u, v, d) in g.edges(data=True)
if d["weight"] <= 0.5]
# compute the positions of nodes
pos = nx.circular_layout(g)
# plot the graph nodes
nx.draw_networkx_nodes(g, pos, node_size=700)
# plot the graph edges
nx.draw_networkx_edges(g, pos, edgelist=elarge, width=6)
nx.draw_networkx_edges(
g, pos, edgelist=esmall, width=6,
alpha=0.5, edge_color="b", style="dashed")
# annotate the nodes
nx.draw_networkx_labels(
g, pos, font_size=20, font_family="sans-serif")
# graphics config
plt.axis("off")
plt.show() # wait before exiting
which has the following output:
But what I want is for the connecting lines to be curved not straight. I couldn't find a line style in matplotlib that allows me to do that.
Similarly to this answer, you could do:
plt.figure(figsize=(15,8))
pos = nx.circular_layout(G) # positions for all nodes
ax = plt.gca()
for edge in G.edges():
source, target = edge
rad = 0.2
c = edge in elarge
arrowprops=dict(arrowstyle="-",
color='black' if c else 'blue',
connectionstyle=f"arc3,rad={rad}",
linestyle= '-' if c else '--',
alpha=0.6,
linewidth=5)
ax.annotate("",
xy=pos[source],
xytext=pos[target],
arrowprops=arrowprops
)
# nodes
nx.draw_networkx_nodes(G, pos, node_size=700, node_color='black')
# labels
nx.draw_networkx_labels(G, pos, font_size=20,
font_family="sans-serif",
font_color ='white')
plt.box(False)
plt.show()

Curved edges using matplotlib and Networkx in Python 3.x

I want to draw curved edges using the Networkx framework and matplotlib.
Basically the same problem as linked below:
Networkx: Overlapping edges when visualizing MultiGraph
One answer is:
import networkx as nx
G = nx.DiGraph()
G.add_nodes_from([0,1])
pos = nx.circular_layout(G)
nx.draw_networkx_nodes(G, pos, connectionstyle='arc3, rad = 0.1', node_color = 'r', node_size = 100, alpha = 1)
nx.draw_networkx_edges(G, pos,connectionstyle='arc3, rad = 0.1', edgelist = [(0,1)], width = 2, alpha = 0.5, edge_color='b')
nx.draw_networkx_edges(G, pos,connectionstyle='arc3, rad = 0.1', edgelist= [(1,0)], width = 1, alpha = 1)
plt.axis('off')
plt.show()
But that produces:
In the end I want to produce something like this:
I don't think you can do this directly with networkx functions. But you can use matplotlib directly using the node positions you have calculated.
E.g. based on this:
import networkx as nx
G = nx.DiGraph()
G.add_nodes_from([0,1])
pos = nx.circular_layout(G)
nx.draw_networkx_nodes(G, pos, node_color = 'r', node_size = 100, alpha = 1)
ax = plt.gca()
ax.annotate("",
xy=pos[0], xycoords='data',
xytext=pos[1], textcoords='data',
arrowprops=dict(arrowstyle="->", color="0.5",
shrinkA=5, shrinkB=5,
patchA=None, patchB=None,
connectionstyle="arc3,rad=0.3",
),
)
plt.axis('off')
plt.show()
Gives:

Colorbar for edges in networkx

I am trying to get a colorbar for edges in a networkx graph. Here is a code snippet
import networkx as nx
import matplotlib.colors as colors
import matplotlib.cm as cmx
n = 12 # Number of clusters
w = 21 # Number of weeks
m = Basemap(
projection='merc',
ellps = 'WGS84',
llcrnrlon=-98.5,
llcrnrlat=25,
urcrnrlon=-60,
urcrnrlat=50,
lat_ts=0,
resolution='i',
suppress_ticks=True)
mx, my = m(list(ccentroids['lon']), list(ccentroids['lat']))
# The NetworkX part
# put map projection coordinates in pos dictionary
G = nx.DiGraph()
G.add_nodes_from(range(n))
for i in range(n):
for j in range(n):
if P_opt[i,j] > 0.5 and i != j:
G.add_edge(i,j, weight = P_opt[i,j])
pos = {i : (mx[i], my[i]) for i in range(n)}
# Add a color_map for the edges
jet = cm = plt.get_cmap('jet')
cNorm = colors.Normalize(vmin=0, vmax=np.max(P_opt))
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=jet)
colorList = []
weights_list = []
for i in G.edges():
a, b = i
colorVal = scalarMap.to_rgba(G.edge[a][b]['weight'])
colorList.append(colorVal)
weights_list.append(G.edge[a][b]['weight'])
plt.clf()
fig = plt.figure()
ax = fig.add_subplot(111, axisbg='w', frame_on=False)
fig.set_size_inches(18.5, 10.5)
# draw the network
nodes = nx.draw_networkx_nodes(G, pos, node_size=100, node_color=q[:,t], cmap = plt.cm.jet,
font_size=8, with_labels=False, label='Cluster centroids')
edges = nx.draw_networkx_edges(G, pos, edge_color=colorList)
m.drawcountries()
m.bluemarble()
This gives me the following image:
Now I want to add a colorbar for the edges. I tried doing something like,
plt.sci(edges)
edges.set_array(np.array(weights_list))
plt.colorbar(shrink = 0.8)
This gives me an image like:
The colours of the arrows and edges seem to differ. How can I correct that? Thanks.
EDIT: I tried to use the following code by modifying the edge line:
edges = nx.draw_networkx_edges(G, pos, edge_color=colorList, edge_cmap = plt.cm.jet)
plt.colorbar(edges)
This gives me an error TypeError: You must first set_array for mappable
Changing the edge_color to be the weights_list I get the following picture:
It seems you are getting the colomap/colorbar for the nodes. Here is how to set a colormap and draw a colorbar for edge colors:
![import networkx as nx
import matplotlib.pyplot as plt
G = nx.star_graph(20)
pos = nx.spring_layout(G)
colors = range(20)
nodes = nx.draw_networkx_nodes(G,pos,node_color='k', with_labels=False)
edges = nx.draw_networkx_edges(G,pos,edge_color=colors,width=4,
edge_cmap=plt.cm.Blues)
plt.colorbar(edges)
plt.axis('off')
I know this is an old one, but I spent some time figuring this out, and maybe it is still useful for someone.
nx.draw_networkx_edges
returns
matplotlib.collection.LineCollection
if there are no arrows.
returns
list of matplotlib.patches.FancyArrowPatch
if there are arrows.
docs
For my specific case I could get a colorbar like in Aric's answer only if I set arrows to False:
edges = nx.draw_networkx_edges(G, edge_color=colors, arrows=False)
plt.colorbar(edges)

Networkx: Overlapping edges when visualizing MultiGraph

The following multigraph plots correctly (i.e. parallel edges do not overlap) using graphviz neato to generate a png (as shown in this answer)
import networkx as nx
nx.MultiGraph ([(1,2),(1,2),(1,2),(3,1),(3,2)])
nx.write_dot(Gm,'multi.dot')
!neato -T png multi.dot > multi.png
However using the draw function of Networkx doesn't do the trick
nx.draw_graphviz(Gm,prog='neato')
Is it possible to prevent overlapping edges using the draw methods from Networkx?
Thanks
Unfortunately not. It is technically possible to do but so far nobody has written the code.
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()
Well I know its probably not what you're looking for, but I was facing a similar problem where I wanted to have a directed graph where the edge between two nodes had a different weight depending on the direction (whether it was going into or out of the node) and the work around I did was I used a different color for each edge and decreased the opacity for one of them so it would show even if they overlap.
I only needed two edges between my two nodes so it did the trick for me.
G = nx.DiGraph()
G.add_nodes_from([0,1])
pos = nx.circular_layout(G)
nx.draw_networkx_nodes(G, pos, node_color = 'r', node_size = 100, alpha = 1)
nx.draw_networkx_edges(G, pos, edgelist = [(0,1)], width = 2, alpha = 0.5, edge_color='b')
nx.draw_networkx_edges(G, pos, edgelist= [(1,0)], width = 1, alpha = 1)
plt.axis('off')
plt.show()
An improvement to the answer above is adding the connectionstyle argument to nx.draw:
import networkx as nx
G = nx.DiGraph()
G.add_nodes_from([0,1])
pos = nx.circular_layout(G)
nx.draw_networkx_nodes(G, pos, connectionstyle='arc3, rad = 0.1', node_color = 'r', node_size = 100, alpha = 1)
nx.draw_networkx_edges(G, pos,connectionstyle='arc3, rad = 0.1', edgelist = [(0,1)], width = 2, alpha = 0.5, edge_color='b')
nx.draw_networkx_edges(G, pos,connectionstyle='arc3, rad = 0.1', edgelist= [(1,0)], width = 1, alpha = 1)
plt.axis('off')
plt.show()

Categories