Search code examples
pythonmatplotlibplotscatterz-order

Set properties for specific patch in matplotlib.collections.PathCollection


I would like to set individual properties (zorder and label for example) for a specific element of a matplotlib.collections.PathCollection. I couldn't find a way in the documentation.

Here I jot down a user case.
Let say we have the following snippet, and we'd like to change the zorder of the red ball, bringing it to the top, by using the balls handle, which is a matplotlib.collections.PathCollection.

balls = plt.scatter([-1, 1], [0, 0], c = ['r', 'b'], s = 4e4)
plt.axis([-5, 5, -5, 5])

Overlapped balls

Does anyone have any idea about how to tweak individual paths of a PathCollection?

The alternative would be using plt.plot('o'), which actually returns a list of handles. Unfortunately the plt.plot('o') solution won't allow me to set a different colour per ball, since they would all belong to the same chart. So a for loop would be required.

The drastic solution, which I bet I'll go for, since my deadline, is going of Inkscape :/


Solution

  • Not sure if this is the best solution, but it might help you.

    From what I can see, the paths in the PathCollection are always plotted in the order they are created. So in your case, the path with the x-position of -1 is created first, then the one with 1.

    You can switch that order after initially plotting them, by changing the offsets, in your case using balls.set_offsets():

    In [4]: balls = plt.scatter([-1, 1], [0, 0], c = ['r', 'b'], s = 4e4)
    In [5]: plt.axis([-5, 5, -5, 5])
    

    This creates the following figure:enter image description here

    In [42]: print balls.get_offsets()
    [[-1.  0.]
     [ 1.  0.]]
    
    On [43]: balls.set_offsets([[1,0],[-1,0]])
    

    Now, this has plotted the left-hand ball on top of the right-hand ball:

    enter image description here

    But as you can see, this has also switched the facecolors around (since we set the order of that in the call to plt.scatter as ['r','b']. There's a solution to this, which is to also switch the facecolors around:

    In [46]: balls.set_facecolors(['b','r'])
    

    enter image description here


    Great, so putting that all together, we can define a function to switch the offsets and facecolors of any two arbitrary paths in the PathCollection.

    import matplotlib.pyplot as plt
    
    fig,ax = plt.subplots()
    balls = ax.scatter([-3, -1, 1, 3], [0, 0, 0, 0], c = ['r', 'b', 'g', 'm'], s = 4e4)
    ax.set_xlim(-6,6)
    ax.set_ylim(-6,6)
    
    plt.savefig('balls_01.png')
    
    def switch_scatter(pathcoll,a,b):
    
        # Switch offsets
        offsets = pathcoll.get_offsets()[:]
        offsets[[a,b]] = offsets[[b,a]]
    
        # Switch facecolors
        facecolors = pathcoll.get_facecolors()
        facecolors[[a,b]] = facecolors[[b,a]]
    
        # Switch sizes 
        sizes = pathcoll.get_sizes() 
        sizes[[a,b]] = sizes[[b,a]] 
    
        # Set the new offsets, facecolors and sizes on the PathCollection
        pathcoll.set_offsets(offsets)
        pathcoll.set_facecolors(facecolors)
        pathcoll.set_sizes(sizes)
    
    switch_scatter(balls,2,1)
    
    plt.savefig('balls_02.png')
    

    Heres balls_01.png:

    enter image description here

    And here is balls_02.png (where we switch ball 1 and ball 2 (the blue and green balls)

    enter image description here


    A final note: if you have other properties varying in your scatter plot (e.g. linecolor), you will also need to switch them around in the function I defined above.