How to plot the outline of the outer edges on a Matplotlib line in Python?

后端 未结 2 1192
梦如初夏
梦如初夏 2021-02-14 11:53

I am trying to plot an outline (linestyle=\":\") on the networkx edges. I can\'t seem to figure out how to do this to the matplotlib

2条回答
  •  小蘑菇
    小蘑菇 (楼主)
    2021-02-14 12:01

    The objects in LineCollection do not have distinct edgecolor and facecolor. By trying to set the linestyle, you are affecting the style of the entire line segment. I found it easier to create the desired effect by using a series of patches. Each patch represents an edge of the graph. The edgecolor, linestyle, linewidth, and facecolor of the patches can be manipulated individually. The trick is building a function to convert an edge into a rotated Rectangle patch.

    import matplotlib.path as mpath
    import matplotlib.patches as mpatches
    import numpy as np
    from matplotlib import pyplot as plt
    import networkx as nx
    
    G = nx.Graph()
    for i in range(10):
        G.add_node(i)
    for i in range(9):
        G.add_edge(9, i)
    
    # make a square figure so the rectangles look nice
    plt.figure(figsize=(10,10))
    plt.xlim(-1.1, 1.1)
    plt.ylim(-1.1, 1.1)
    
    def create_patch(startx, starty, stopx, stopy, width, w=.1):
        # Check if lower right corner is specified.
        direction = 1
        if startx > stopx:
            direction = -1
    
        length = np.sqrt((stopy-starty)**2 + (stopx-startx)**2)
        theta = np.arctan((stopy-starty)/(stopx-startx))
        complement = np.pi/2 - theta
    
        patch = mpatches.Rectangle(
            (startx+np.cos(complement)*width/2, starty-np.sin(complement)*width/2), 
            direction * length,
            width,
            angle=180/np.pi*theta, 
            facecolor='#000000', 
            linestyle=':', 
            linewidth=width*10,
            edgecolor='k',
            alpha=.3
        )
        return patch
    
    # Create layout before building edge patches
    pos = nx.circular_layout(G)
    
    for i, edge in enumerate(G.edges()):
        startx, starty = pos[edge[0]]
        stopx, stopy = pos[edge[1]]
        plt.gca().add_patch(create_patch(startx, starty, stopx, stopy, (i+1)/10))
    
    plt.show()
    

    In your example, you noticed that we can use the X and Y positions of the edges to find the angle of rotation. We use the same trick here. Notice also that sometimes the magnitude of the rectangle length is negative. The Rectangle Patch assumes that the x and y inputs refer to the lower left corner of the rectangle. We run a quick check to make sure that's true. If false, we've specified the top first. In that case, we draw the rectangle backwards along the same angle.

    Another gotcha: it's important to run your layout algorithm before creating the patches. Once the pos is specified, we can use the edges to look up the start and stop locations.

    Opportunity for Improvement: Rather than plotting each patch as you generate it, you can use a PatchCollection and manipulate the patches in bulk. The docs claim that PatchCollection is faster, but it may not fit all use cases. Since you expressed a desire to set properties on each patch independently, the collection might not offer the flexibility you need.

提交回复
热议问题