Search code examples
matplotlibshapely

Overlaping multiple shapes so that each is above the previous using matplotlib


I was wondering is there an easy way to plot multiple shapes so each one is ploted above the previous, including the first above the last, like shown on the image. Using zorder doesn't work since last shape will be above all other shapes. Also i would like to be able to do the same thing with images. Any ideas?

Example

My first thought was to use shapely and find the difference between last and first shape and use that to plot the last shape. Problem is that it only works if there's overlap between two shapes, and it gets complicated when ploting images with clip paths.


Solution

  • Matplotlib doesn't directly support this kind of mutual overlapping. Internally, it uses the painter's algorithm to draw everything in layers.

    A trick can be to split a polygon into two, and draw the parts with different z-orders.

    Here is an example how this can be done with images. (If you'd create a C-like clipping polygon, it could be done without drawing an image a second time.)

    import matplotlib.pyplot as plt
    import numpy as np
    from scipy.ndimage import gaussian_filter
    
    data1 = gaussian_filter(np.random.rand(100, 20), sigma=3, order=0)
    data2 = gaussian_filter(np.random.rand(20, 100), sigma=5, order=0)
    data3 = gaussian_filter(np.random.rand(100, 20), sigma=4, order=0)
    data4 = gaussian_filter(np.random.rand(20, 120), sigma=6, order=0)
    
    fig, ax = plt.subplots()
    
    img1 = ax.imshow(data1, extent=[1, 11, 6, 8], origin='lower', cmap='summer', aspect='auto')
    img2 = ax.imshow(data2, extent=[8, 10, 1, 9], origin='lower', cmap='spring', aspect='auto')
    img3 = ax.imshow(data3, extent=[1, 11, 2, 4], origin='lower', cmap='winter', aspect='auto')
    img4 = ax.imshow(data4, extent=[2, 4, 1, 9], origin='lower', cmap='autumn', aspect='auto')
    
    # plot the first image again
    img5 = ax.imshow(img1.get_array(), extent=img1.get_extent(), origin='lower', cmap=img1.get_cmap(), aspect='auto')
    # clip the new image with the box of the fourth
    x0, x1, y0, y1 = img4.get_extent()
    rect4 = plt.Rectangle((x0, y0), x1 - x0, y1 - y0, transform=ax.transData)
    img5.set_clip_path(rect4)
    
    ax.set_xlim(0, 12)
    ax.set_ylim(0, 10)
    
    plt.show()
    

    matplotlib mutually overlapping images