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

后端 未结 2 1232
梦如初夏
梦如初夏 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:11

    The problem of surrounding a line with a certain width by another line is that the line is defined in data coordinates, while the linewidth is in a physical unit, namely points. This is in general desireable, because it allows to have the linewidth to be independent of the data range, zooming level etc. It also ensures that the end of the line is always perpendicular to the line, independent of the axes aspect.

    So the outline of the line is always in a mixed coordinate system and the final appearance is not determined before drawing the actual line with the renderer. So for a solution that takes the (possibly changing) coordinates into account, one would need to determine the outline for the current state of the figure.

    One option is to use a new artist, which takes the existing LineCollection as input and creates new transforms depending on the current position of the lines in pixel space.

    In the following I chose a PatchCollection. Starting off with a rectangle, we can scale and rotate it and then translate it to the position of the original line.

    import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib.collections import LineCollection, PatchCollection
    import matplotlib.transforms as mtrans
    
    
    class OutlineCollection(PatchCollection):
        def __init__(self, linecollection, ax=None, **kwargs):
            self.ax = ax or plt.gca()
            self.lc = linecollection
            assert np.all(np.array(self.lc.get_segments()).shape[1:] == np.array((2,2)))
            rect = plt.Rectangle((-.5, -.5), width=1, height=1)
            super().__init__((rect,), **kwargs)
            self.set_transform(mtrans.IdentityTransform())
            self.set_offsets(np.zeros((len(self.lc.get_segments()),2)))
            self.ax.add_collection(self)
    
        def draw(self, renderer):
            segs = self.lc.get_segments()
            n = len(segs)
            factor = 72/self.ax.figure.dpi
            lws = self.lc.get_linewidth()
            if len(lws) <= 1:
                lws = lws*np.ones(n)
            transforms = []
            for i, (lw, seg) in enumerate(zip(lws, segs)):
                X = self.lc.get_transform().transform(seg)
                mean = X.mean(axis=0)
                angle = np.arctan2(*np.squeeze(np.diff(X, axis=0))[::-1])
                length = np.sqrt(np.sum(np.diff(X, axis=0)**2))
                trans = mtrans.Affine2D().scale(length,lw/factor).rotate(angle).translate(*mean)
                transforms.append(trans.get_matrix())
            self._transforms = transforms
            super().draw(renderer)
    

    Note how the actual transforms are only calculated at draw time. This ensures that they take the actual positions in pixel space into account.

    Usage could look like

    verts = np.array([[[5,10],[5,5]], [[5,5],[8,2]], [[5,5],[1,4]], [[1,4],[2,0]]])
    
    plt.rcParams["axes.xmargin"] = 0.1
    fig, (ax1, ax2) = plt.subplots(ncols=2, sharex=True, sharey=True)
    
    lc1 = LineCollection(verts, color="k", alpha=0.5, linewidth=20)
    ax1.add_collection(lc1)
    
    olc1 = OutlineCollection(lc1, ax=ax1, linewidth=2, 
                             linestyle=":", edgecolor="black", facecolor="none")
    
    
    lc2 = LineCollection(verts, color="k", alpha=0.3, linewidth=(10,20,40,15))
    ax2.add_collection(lc2)
    
    olc2 = OutlineCollection(lc2, ax=ax2, linewidth=3, 
                             linestyle="--", edgecolors=["r", "b", "gold", "indigo"], 
                            facecolor="none")
    
    for ax in (ax1,ax2):
        ax.autoscale()
    plt.show()
    

    Now of course the idea is to use the linecollection object from the question instead of the lc1 object from the above. This should be easy enough to replace in the code.

提交回复
热议问题