Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to obtain non-rounded arrows on fat lines?

I am using networkx to draw a directed graph in Python. I use different widths of edges to highlight a weight. Unfortunately, the arrow heads are rounded which looks weird. I would like to draw non-rounded arrow heads for fat lines that look like a scaled version of the thin line arrows.

directed graph with weird fat arrows

import networkx as nx
import matplotlib.pyplot as plt
import numpy

G=nx.DiGraph()
stake = [
    [0, 1, 1, 3],
    [3, 0, 0, 1],
    [0, 0, 0, 1],
    [1, 0, 3, 0]
    ]
maxS = max(max(stake))
stake1d = numpy.concatenate(stake)
minS = min(stake1d[numpy.nonzero(stake1d)])
minLineWeight = 1
maxLineWeight = 10

for x in range(len(stake)):
    for y in range(x):
        if(stake[x][y] > 0):
            weight = (stake[x][y] - minS) / (maxS - minS) * (maxLineWeight - minLineWeight) + minLineWeight
            G.add_edge(x, y, weight=weight, color='r')

for x in range(len(stake)):
    for y in [i for i in range(len(stake))][-(len(stake)-x):]:
        if(stake[x][y] > 0):
            weight = (stake[x][y] - minS) / (maxS - minS) * (maxLineWeight - minLineWeight) + minLineWeight
            G.add_edge(x, y, weight=weight, color='b')

weights=list(nx.get_edge_attributes(G,'weight').values())
colors=list(nx.get_edge_attributes(G,'color').values())
pos = nx.shell_layout(G)
nx.draw(
    G,
    pos=pos,
    width=weights,
    edge_color=colors,
    with_labels=True,
    arrows=True,
    connectionstyle='arc3',
    arrowstyle='->'
)  
plt.show()
like image 1000
SCBuergel Avatar asked Dec 13 '25 02:12

SCBuergel


1 Answers

I had this exact same question and hacked together a workaround:

  1. Replace the nx.draw call with two separate method calls which draw the nodes and edges separately. This is useful because the networkx edge-drawing method draw_networkx_edges returns a list of matplotlib patches (specifically FancyArrowPatch objects) which you can then manipulate before rendering.

  2. Loop over the arrow patches and change the FancyArrowPatch mutation_scale, as well as the joinstyle and capstyle of the line segments which make up each arrow.

Workaround

Starting with your example code, replace the call to nx.draw with the following.

# First, draw the nodes themselves
nodes = nx.draw_networkx_nodes(
    G,
    pos=pos,
    linewidths=1
)

# Draw node labels
node_label_handles = nx.draw_networkx_labels(
    G, 
    pos=pos
);

# Draw the edges and store the returned FancyArrowPatch list
arrows = nx.draw_networkx_edges(
    G,
    pos=pos,
    arrows=True,
    width=weights,
    edge_color=colors,
    arrowstyle='-|>'  # I personally think this style scales better
)

for a, w in zip(arrows, weights):
    
    # mutation_scale affects only the arrowhead size, not the arrow tail.
    # The constants here are arbitrary; you may want/need to change them
    a.set_mutation_scale(20 + w)
    
    # Sharpen arrowheads by creating a mitered joint between arrowhead 
    # line segments, instead of the default joinstyle='round'
    a.set_joinstyle('miter')
    
    # Prevent each arrow tail from jutting forward and through the arrowhead,
    # which happens with the default capstyle='projecting'
    a.set_capstyle('butt')

My result

plot result after adjusting arrow formatting

Versions

pandas=1.3.2, numpy=1.21.2, matplotlib=3.4.3

like image 186
Peter Leimbigler Avatar answered Dec 14 '25 14:12

Peter Leimbigler



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!