Search code examples
matplotlibclip

Cut parts of plotted artists


Assume I have drawn some background image like this:

fig, ax = plt.subplots()
imdata = np.random.randn(10, 10)
im = ax.imshow(imdata, extent=(0, 1, 0, 1), aspect='auto',
               cmap='coolwarm', interpolation='nearest')

Now I'm adding a number of rectangles like:

rect = matplotlib.patches.Rectangle((0.3,0.3),0.4,0.4)
ax.add_artist(rect)

Now I want to cut several other rectangles out of the previously added rectangle, so the underlying image is shown again. By cut, I really mean that specifying such a "deletion rectangle" will cut out parts from the previously drawn rectangles. So if they overlap, only the overlapping parts will be cut away. Where the "deletion rectangles" do not intersect space occupied by the rectangles above, nothing shall happen to the visible area.

How can I achieve that?


Solution

  • You can use a path to construct the rectangles. To position the rectangles the vertices of the path can be translated and transformed. Then, using the fact that inverted vertices will be cut out of a path, one create holes in the outer rectangle.

    import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib.path import Path
    from matplotlib.patches import PathPatch, Rectangle
    
    
    fig, ax = plt.subplots()
    imdata = np.random.randn(10, 10)
    
    
    # create rectangle, coordinates are ignored
    rec = Rectangle((0,0),1,1).get_path()
    
    #the big rectangle
    r0 = rec.vertices+0.5
    # r1 and r2 are the rectangles to cut out of r0
    r1 = 0.6+rec.vertices[::-1]*0.35
    r2 = 1+rec.vertices[::-1]*0.35
    
    path = Path(vertices=np.concatenate([r0, r1, r2]),
                   codes=np.concatenate([rec.codes]*3)) 
    
    im = ax.imshow(imdata, extent=(0, 2, 0, 2), aspect='equal',
                   cmap='coolwarm', interpolation='nearest')
    
    
    patch = PathPatch(path, facecolor='w')
    ax.add_patch(patch)
    
    plt.tight_layout()
    plt.show()
    

    enter image description here


    Or, a solution which makes it easier to specify the coordinates of the rectangles:

    import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib.path import Path
    from matplotlib.patches import PathPatch, Rectangle
    
    
    fig, ax = plt.subplots()
    imdata = np.random.randn(10, 10)
    
    
    def create_rec(x0, y0, width, height):
        rec_patch = Rectangle((x0, y0),width, height)
        rec_path = rec_patch.get_path()
        rec_path = rec_patch.get_patch_transform().transform_path(rec_path) 
        return rec_path.vertices, rec_path.codes
    
    #the big rectangle
    r0,c = create_rec(0.3, 0.6, 1, 1.2)
    # r1 and r2 are the rectangles to cut out of r0
    r1,c = create_rec(0.4, 0.7, 0.3, 0.4)
    r2,c = create_rec(0.8, 1, 0.4, 0.5)
    
    path = Path(vertices=np.concatenate([r0, r1[::-1], r2[::-1]]),
                   codes=np.concatenate([c]*3)) 
    
    im = ax.imshow(imdata, extent=(0, 2, 0, 2), aspect='equal',
                   cmap='coolwarm', interpolation='nearest')
    
    
    patch = PathPatch(path, facecolor='w')
    ax.add_patch(patch)
    
    plt.tight_layout()
    plt.show()
    


    To account for the case where the rectangle is partially outside the original rectangle, the following (based on the second solution) might help:

    import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib.path import Path
    from matplotlib.patches import PathPatch, Rectangle
    
    
    fig, ax = plt.subplots()
    imdata = np.random.randn(10, 10)
    
    
    def create_rec(x0, y0, width, height):
        rec_patch = Rectangle((x0, y0),width, height)
        rec_path = rec_patch.get_path()
        rec_path = rec_patch.get_patch_transform().transform_path(rec_path) 
        return rec_path.vertices, rec_path.codes
    
    #the big rectangle
    r0,c = create_rec(0.3, 0.6, 1, 1.2)
    # r1 and r2 are the rectangles to cut out of r0
    r1,c = create_rec(0.2, 0.5, 0.3, 0.4)
    r2,c = create_rec(0.8, 1, 0.4, 0.5)
    
    path = Path(vertices=np.concatenate([r0, r1[::-1], r2[::-1]]),
                   codes=np.concatenate([c]*3)) 
    
    im = ax.imshow(imdata, extent=(0, 2, 0, 2), aspect='equal',
                   cmap='coolwarm', interpolation='nearest')
    
    patho = Path(vertices=r0,codes=c)
    patcho = PathPatch(patho, facecolor='none', edgecolor="none")
    ax.add_patch(patcho)
    patch = PathPatch(path, facecolor='w', clip_path=patcho, edgecolor="none")
    ax.add_patch(patch)
    
    plt.show()
    

    enter image description here