这个问题与以下两个问题密切相关,但是这个问题更为笼统。
Matplotlib pick event order for overlapping artists
Multiple pick events interfering
问题:
在单个画布上拾取重叠的艺术家时,将为每个艺术家创建单独的拾取事件。在下面的示例中,单击一个红色点会两次调用on_pick
,一次是lines
,一次是points
。由于points
位于该行的上方(根据它们各自的zorder
值),所以我希望只为最顶级的艺术家(在这种情况下为points
)生成一个单独的选择事件。
例:
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()
凌乱的解决方案:
一种解决方案是使用Matplotlib的
button_press_event
,然后计算鼠标与所有艺术家之间的距离,如下所示。但是,此解决方案非常混乱,因为添加其他重叠的美术师会使此代码变得相当复杂,从而增加了要检查的案例和条件的数量。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()
问题摘要:
是否有更好的方法从一组重叠的艺术家中选择最高的艺术家?
理想情况下,我希望能够执行以下操作:
pyplot.set_pick_stack( [points, line] )
表示将为重叠的选择在
points
上选择line
。 最佳答案
在button_press_event
发生时创建自己的事件可能是最简单的。为了说明问题中表达的“ set_pick_stack”的概念,可能如下所示。这个想法是存储一组艺术家,并在button_press_event中检查该事件是否包含在艺术家中。然后在自定义onpick
函数上触发回调。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backend_bases import PickEvent
class PickStack():
def __init__(self, stack, on_pick):
self.stack = stack
self.ax = [artist.axes for artist in self.stack][0]
self.on_pick = on_pick
self.cid = self.ax.figure.canvas.mpl_connect('button_press_event',
self.fire_pick_event)
def fire_pick_event(self, event):
if not event.inaxes:
return
cont = [a for a in self.stack if a.contains(event)[0]]
if not cont:
return
pick_event = PickEvent("pick_Event", self.ax.figure.canvas,
event, cont[0],
guiEvent=event.guiEvent,
**cont[0].contains(event)[1])
self.on_pick(pick_event)
用法看起来像
fig, ax = plt.subplots()
# add line:
x = np.arange(10)
y = np.random.randn(10)
line, = ax.plot(x, y, 'b-', label="Line", picker=5)
# add points overlapping the line:
xpoints = [2, 4, 7]
points, = ax.plot(x[xpoints], y[xpoints], 'ro', label="Points", picker=5)
def onpick(event):
txt = f"You picked {event.artist} at xy: " + \
f"{event.mouseevent.xdata:.2f},{event.mouseevent.xdata:.2f}" + \
f" index: {event.ind}"
print(txt)
p = PickStack([points, line], onpick)
plt.show()
这里的想法是按挑选事件所需的顺序提供艺术家列表。当然,也可以使用
zorder
确定顺序。这看起来像self.stack = list(stack).sort(key=lambda x: x.get_zorder(), reverse=True)
在
__init__
函数中。因为问题引起了评论,所以让我们看看为什么matplotlib不自动执行此过滤。好吧,首先,我想至少在50%的情况下都是不希望的,在这种情况下,您希望每个挑选的艺术家都参加一次活动。而且,对于matplotlib而言,为每个受mouseevent击中的艺术家发出事件比过滤它们要容易得多。对于前者,您只需比较坐标(非常类似于问题中的“混乱解决方案”)。很难只获得最高的艺术家;当然,如果两个艺术家的zorder不同,这是有可能的,但是如果他们具有相同的zorder,则只是他们在子代轴列表中出现的顺序决定了哪个在前。 “ pick_upmost_event”将需要检查完整的轴子代堆栈,以找出要选择的轴。话虽如此,这并不是没有可能,但是到目前为止,可能还没有人相信这是值得的。当然,人们可以针对这样的“ pick_upmost_event”打开问题或将实施提交为matplotlib的PR。
关于python - 在Matplotlib中从一组重叠的艺术家中挑选一个艺术家,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56015753/