This question is closely related to the two below, but this question is more general.
Matplotlib pick event order for overlapping artists
Multiple pick events interfering
The problem:
When picking overlapping artists on a single canvas, separate pick events are created for each artist. In the example below, a click on a red point calls on_pick
twice, once for lines
and once for points
. Since the points
sit above the line (given their respective zorder
values), I would prefer to have just a single pick event generated for the topmost artist (in this case: points
).
Example:
import numpy as np
from matplotlib import pyplot
def on_pick(event):
if event.artist == line:
print('Line picked')
elif event.artist == points:
print('Point picked')
# create axes:
pyplot.close('all')
ax = pyplot.axes()
# add line:
x = np.arange(10)
y = np.random.randn(10)
line = ax.plot(x, y, 'b-', zorder=0)[0]
# add points overlapping the line:
xpoints = [2, 4, 7]
points = ax.plot(x[xpoints], y[xpoints], 'ro', zorder=1)[0]
# set pickers:
line.set_picker(5)
points.set_picker(5)
ax.figure.canvas.mpl_connect('pick_event', on_pick)
pyplot.show()
Messy solution:
One solution is to use Matplotlib's button_press_event
, then compute distances between the mouse and all artists, like below. However, this solution is quite messy, because adding additional overlapping artists will make this code quite complex, increasing the number of cases and conditions to check.
def on_press(event):
if event.xdata is not None:
x,y = event.xdata, event.ydata #mouse click coordinates
lx,ly = line.get_xdata(), line.get_ydata() #line point coordinates
px,py = points.get_xdata(), points.get_ydata() #points
dl = np.sqrt((x - lx)**2 + (y - ly)**2) #distances to line points
dp = np.sqrt((x - px)**2 + (y - py)**2) #distances to points
if dp.min() < 0.05:
print('Point selected')
elif dl.min() < 0.05:
print('Line selected')
pyplot.close('all')
ax = pyplot.axes()
# add line:
x = np.arange(10)
y = np.random.randn(10)
line = ax.plot(x, y, 'b-', zorder=0)[0]
# add points overlapping the line:
xpoints = [2, 4, 7]
points = ax.plot(x[xpoints], y[xpoints], 'ro', zorder=1)[0]
# set picker:
ax.figure.canvas.mpl_connect('button_press_event', on_press)
pyplot.show()
Question summary: Is there a better way to select the topmost artist from a set of overlapping artists?
Ideally, I would love be able to do something like this:
pyplot.set_pick_stack( [points, line] )
implying that points
will be selected over line
for an overlapping pick.