这个问题与下面的两个问题密切相关,但是这个问题更笼统。
Matplotlib pick event order for overlapping artists
Multiple pick events interfering
在单个画布上拾取重叠的艺术家时,将为每个艺术家创建单独的拾取事件。在下面的示例中,单击一个红色点会两次调用<topic2>
,一次调用on_pick
,一次调用lines
。由于points
位于该行上方(考虑到它们各自的points
值),我希望只为最顶级的艺术家(在这种情况下为zorder
)生成一个单独的选择事件。
示例:
points
一种解决方案是使用Matplotlib的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()
,然后计算鼠标与所有艺术家之间的距离,如下所示。但是,此解决方案非常混乱,因为添加其他重叠的美术师会使该代码变得相当复杂,从而增加了要检查的案例和条件的数量。
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
进行重叠选择。
答案 0 :(得分:1)
在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的实现。
答案 1 :(得分:0)
已编辑:
首先,保持追踪您正在绘制的所有艺术家通常是一个好主意;因此,我建议保留一个字典artists_dict
,其中所有绘制的元素都作为 key ,您可以使用它来存储一些有用的值(例如在另一个字典中)。
除此之外,以下代码依赖于使用计时器来收集list_artists
中的触发事件,然后每隔100ms通过on_pick(list_artists)
处理此列表。在此功能中,您可以检查是否选中了一位或多位艺术家,然后找到zorder最高的艺术家并做某事。
import numpy as np
from matplotlib import pyplot
artists_dict={}
def handler(event):
print('handler fired')
list_artists.append(event.artist)
def on_pick(list_artists):
## if you still want to use the artist dict for something:
# print([artists_dict[a] for a in list_artists])
if len(list_artists)==1:
print('do something to the line here')
list_artists.pop(0)## cleanup
elif len(list_artists)>1:### only for more than one plot item
zorder_list=[ a.get_zorder() for a in list_artists]
print('highest item has zorder {0}, is at position {1} of list_artists'.format(np.max(zorder_list),np.argmax(zorder_list)))
print('do something to the scatter plot here')
print(list(zip(zorder_list,list_artists)))
list_artists[:]=[]
else:
return
# create axes:
pyplot.close('all')
fig,ax=pyplot.subplots()
# add line:
x = np.arange(10)
y = np.random.randn(10)
line = ax.plot(x, y, 'b-', zorder=0)[0]
## insert the "line” into our artists_dict with some metadata
# instead of inserting zorder:line.get_zorder(), you could also
# directly insert zorder:0 of course.
artists_dict[line]={'label':'test','zorder':line.get_zorder()}
# add points overlapping the line:
xpoints = [2, 4, 7]
points = ax.plot(x[xpoints], y[xpoints], 'ro', zorder=1)[0]
## and we also add the scatter plot 'points'
artists_dict[points]={'label':'scatters','zorder':points.get_zorder()}
# set pickers:
line.set_picker(5)
points.set_picker(5)
## connect to handler function
ax.figure.canvas.mpl_connect('pick_event', handler)
list_artists=[]
## wait a bit
timer=fig.canvas.new_timer(interval=100)
timer.add_callback(on_pick,list_artists)
timer.start()
pyplot.show()