Search code examples
pythonmatplotlibpicker

How to pick a set of lines instead of just a single line?


I'm plotting multiple lines using matplolib's Line2D and want to pick or select all lines at once.

The MWE provided plots a triangle (could be any polygon or shape) and includes a function which enables to pick every line separately. I would like to pick the whole triangle by clicking on it. Additionally I noticed that if I add another line the onPick function doesn't work at all. Does anyone have an idea what I did wrong?

Edit As suggested in the comments below I added a Polygon and a modified function pick_simple() (from: https://matplotlib.org/3.1.0/gallery/event_handling/pick_event_demo.html). But unfortunately this brings new problems. By plotting the Polygon I get a filled blue patch even though I set fill=False as well as linewidth and color. Also the pick_simple() function doesn't do anything which confuses me a lot.

from matplotlib import pyplot as plt
from matplotlib.lines import Line2D
from matplotlib.patches import Polygon
from matplotlib.collections import PatchCollection


fig = plt.figure()
ax = fig.add_subplot(111)

triangle = [[0.1, 0.3],
            [0.2, 0.8],
            [0.3, 0.5],
            [0.1, 0.3]]

for i in range(len(triangle)-1):
    tri = Line2D([triangle[i][0],triangle[i+1][0]],[triangle[i][1],
            triangle[i+1][1]], linewidth=0.75, color='#F97306')
    tri.set_picker(True)
    ax.add_line(tri)

geometry = [[0.0,0.0],[0.1,0.05],[0.2,0.15],[0.3,0.20],[0.4,0.25],[0.5,0.30],
        [0.6,0.25],[0.7,0.15],[0.8,0.05],[0.9,0.025],[1.0,0.0]]

patches = []
polygon = Polygon(geometry, closed=False, fill=False, linewidth=0.75, color='#F97306')
polygon.set_picker(True)
patches.append(polygon)
p = PatchCollection(patches)
ax.add_collection(p)


plt.show()

def pick_simple():
    def onpick(event):
        if isinstance(event.artist, Polygon):
            patch = event.artist
            print('onpick patch:', patch.get_path())
    fig.canvas.mpl_connect('pick_event', onpick)

def pick_factory(ax):
    def onPick(event):
        if event.inaxes == ax:
            for line in ax.lines:
                if line.get_picker():
                    cont, ind = line.contains(event)
                    if cont:
                        line.set_color('#029386')
                        line.set_linewidth(5)
                        ax.figure.canvas.draw_idle()

    fig = ax.get_figure() # get the figure of interest
    fig.canvas.mpl_connect('button_press_event', onPick)

pick_factory(ax)
pick_simple()

Solution

  • There are a couple of smaller mistakes.

    • To pick a member of a collection, you need to set the picker to the collection, not the initial artist. Hence, the pick callback needs to choose the member of the collection that was picked, via event.ind.
    • A PatchCollection's aim is usually to set the properties, like linewidth, color, etc., of its children itself. If you don't want that, you need to use the match_original=True

    Complete code:

    from matplotlib import pyplot as plt
    from matplotlib.patches import Polygon
    from matplotlib.collections import PatchCollection
    
    
    fig = plt.figure()
    ax = fig.add_subplot(111)
    
    
    geometry = [[0.0,0.0],[0.1,0.05],[0.2,0.15],[0.3,0.20],[0.4,0.25],[0.5,0.30],
            [0.6,0.25],[0.7,0.15],[0.8,0.05],[0.9,0.025],[1.0,0.0]]
    
    patches = []
    polygon = Polygon(geometry, closed=False, fill=False, linewidth=3, color='#F97306')
    patches.append(polygon)
    p = PatchCollection(patches, match_original=True)
    p.set_picker(True)
    ax.add_collection(p)
    
    
    def pick_simple():
        def onpick(event):
            if isinstance(event.artist, PatchCollection):
                collection = event.artist
                print('onpick collection:', collection)
                print('picked index', event.ind)
                print('path at index', collection.get_paths()[event.ind[0]])
        return fig.canvas.mpl_connect('pick_event', onpick)
    
    
    cid = pick_simple()
    plt.show()