Search code examples
pythonmatplotlibclip

How to use set_clip_path() with multiple polygons?


I'm trying to clip a cloud of points by several polygons, but I don't know if this is possible with plt.axis.set_clip_path().

Since set_clip_path() requires a Path or a Patch as arguments, how could you create a geometry formed by several Polygons? It would be something like a plt.MultiPolygon(), but that doesn't exist. I've tried to create a matplotlib.PatchCollection with all the Polygons, but that does not work.

Here is the desired goal (from upper to lower figure): enter image description here

Here is how I'd like the code to look like:

import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
import numpy as np

fig, ax = plt.subplots()

points = np.array([np.random.random(100)*400,
                    np.random.random(100)*100]).T

A = plt.Polygon( np.array([( 0,   0),(50,100),(100,  0)]), color='w', ec='k' )
B = plt.Polygon( np.array([(120 ,  0),(170 , 100), (220,  0)]), color='w', ec='k'  )
C = plt.Polygon( np.array([(240 ,  0),(290 , 100), (340,  0)]), color='w', ec='k'  )

[ax.add_patch(i) for i in (A,B,C)]

ax.scatter(points[:,0], points[:,1], zorder=3).set_clip_path([A,B,C])

Solution

  • You can concatenate the vertices and the codes of all polygons, and use them to create a "compound path". Matplotlib's path tutorial contains an example creating a histogram from just one compound path.

    import matplotlib.pyplot as plt
    from matplotlib.path import Path
    from matplotlib.patches import PathPatch
    import numpy as np
    
    points = np.array([np.random.random(100) * 400,
                       np.random.random(100) * 100]).T
    A = plt.Polygon(np.array([(0, 0), (50, 100), (100, 0)]), color='w', ec='k')
    B = plt.Polygon(np.array([(120, 0), (170, 100), (220, 0)]), color='w', ec='k')
    C = plt.Polygon(np.array([(240, 0), (290, 100), (340, 0)]), color='w', ec='k')
    
    fig, ax = plt.subplots()
    all_polys = [A, B, C]
    [ax.add_patch(i) for i in all_polys]
    vertices = np.concatenate([i.get_path().vertices for i in all_polys])
    codes = np.concatenate([i.get_path().codes for i in all_polys])
    
    dots = ax.scatter(points[:, 0], points[:, 1], zorder=3)
    dots.set_clip_path(PathPatch(Path(vertices, codes), transform=ax.transData))
    plt.show()
    

    clipping by multiple polygons