我在使用matplotlib选择事件时遇到了一个非常奇怪的问题。我有两个艺术家,既可以选择也可以不重叠(“洞”和“钉子”)。当我选择其中一个时,在事件处理期间我将移动另一个移动到我刚刚点击的位置(将“挂钩”移动到“洞”中)。然后,在没有做任何其他事情的情况下,即使在第一个事件生成时不存在,也会生成移动艺术家(peg)中的选择事件。我唯一的解释是,在处理事件时,事件管理器仍以某种方式移动通过艺术家图层,因此在光标移动后击中第二个艺术家。
那么我的问题是 - 挑选事件(或任何事件)如何迭代画布上的重叠艺术家,有没有办法控制它?如果它始终从上到下移动(而不是自下而上或随机),我想我会得到我想要的行为。我还没有找到足够的文档,对SO的冗长搜索没有透露这个确切的问题。以下是一个说明问题的工作示例,其中PathCollections
来自scatter
作为钉子和洞:
import matplotlib.pyplot as plt
import sys
class peg_tester():
def __init__(self):
self.fig = plt.figure(figsize=(3,1))
self.ax = self.fig.add_axes([0,0,1,1])
self.ax.set_xlim([-0.5,2.5])
self.ax.set_ylim([-0.25,0.25])
self.ax.text(-0.4, 0.15, 'One click on the hole, and I get 2 events not 1',
fontsize=8)
self.holes = self.ax.scatter([1], [0], color='black', picker=0)
self.pegs = self.ax.scatter([0], [0], s=100, facecolor='#dd8800',
edgecolor='black', picker=0)
self.fig.canvas.mpl_connect('pick_event', self.handler)
plt.show()
def handler(self, event):
if event.artist is self.holes:
# If I get a hole event, then move a peg (to that hole) ...
# but then I get a peg event also with no extra clicks!
offs = self.pegs.get_offsets()
offs[0,:] = [1,0] # Moves left peg to the middle
self.pegs.set_offsets(offs)
self.fig.canvas.draw()
print 'picked a hole, moving left peg to center'
elif event.artist is self.pegs:
print 'picked a peg'
sys.stdout.flush() # Necessary when in ipython qtconsole
if __name__ == "__main__":
pt = peg_tester()
我已经尝试设置zorder以使栓钉始终位于孔洞之上,但这并不会改变拾取事件的生成方式,特别是这个有趣的幻影事件。
修改: 上下文是peg solitaire的一个实现,所以我希望能够拿起一个挂钩,然后点击一个空洞将其丢弃。目前,同一个挂钩一旦掉落就会立即再次拾起。
答案 0 :(得分:4)
你遇到的问题是由于pick_event函数的调用方式,matplotlib测试每个艺术家,如果它被选中,立即调用你的处理函数。测试顺序取决于您定义孔和钉的顺序(您可以通过更改钉和孔定义的顺序来验证它。)
因此,避免此问题的一种方法是使用matplotlib提供的计时器。在第一次有拾取事件时,您只需将艺术家添加到队列中,然后每隔 x 毫秒处理此新数据。
以下是此类实施的示例:
import matplotlib.pyplot as plt
import sys
class peg_tester():
def __init__(self):
self.fig = plt.figure(figsize=(3,1))
self.ax = self.fig.add_axes([0,0,1,1])
self.ax.set_xlim([-0.5,2.5])
self.ax.set_ylim([-0.25,0.25])
self.ax.text(-0.4, 0.15,
'One click on the hole, and I get 2 events not 1',
fontsize=8)
self.holes = self.ax.scatter([1], [0], color='black', picker=0)
self.pegs = self.ax.scatter([0], [0], s=100, facecolor='#dd8800',
edgecolor='black', picker=0)
self.fig.canvas.mpl_connect('pick_event', self.handler)
# Creating a timer with a interval of 100 ms
self.timer=self.fig.canvas.new_timer(interval=100)
# Queue of pegs and holes that have been picked and waiting to be processed
self.list_pegs_holes=[]
self.timer.add_callback(self.update_pos,self.list_pegs_holes)
self.timer.start()
plt.show()
# Add the artist to the queue of artists awaiting to be processed
def handler(self, event):
self.list_pegs_holes.append(event.artist)
# Management of the queue
def update_pos(self,list_pegs_holes):
while len(list_pegs_holes)!=0:
artist=list_pegs_holes.pop(0)
if artist is self.holes:
# If I get a hole event, then move a peg (to that hole) ...
# but then I get a peg event also with no extra clicks!
offs = self.pegs.get_offsets()
offs[0,:] = [1,0] # Moves left peg to the middle
self.pegs.set_offsets(offs)
self.fig.canvas.draw()
print 'picked a hole, moving left peg to center'
elif artist is self.pegs:
print 'picked a peg'
sys.stdout.flush() # Necessary when in ipython qtconsole
if __name__ == "__main__":
pt = peg_tester()
大部分代码都是您提供的,我刚刚添加了计时器实现。
可以通过为艺术家定义特定的选择器来解决此问题。但是,您将无法在同一位置选择多个项目。
请参阅此示例,该示例解决了不应该选择钉子的部分:
- 通过以下方式取代钉子的定义:
self.pegs = self.ax.scatter([0], [0], s=100, facecolor='#dd8800',edgecolor='black', picker=self.pegs_picker)
- 添加函数pegs_picker:
def pegs_picker(figure,pegs,event):
# Check that the pointer is not on holes
if figure.holes.contains(event)[0]:
return False, dict()
else:
return True, dict()
只有当它们没有与孔重叠时才能选择钉子。
我认为这可能是你想要的行为方式,但由于我不确切知道它是什么,我无法为你提供更精致的拣选功能。