Matplotlib选择重叠艺术家的事件顺序

时间:2014-08-12 19:37:47

标签: python matplotlib event-handling

我在使用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的一个实现,所以我希望能够拿起一个挂钩,然后点击一个空洞将其丢弃。目前,同一个挂钩一旦掉落就会立即再次拾起。

1 个答案:

答案 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()

只有当它们没有与孔重叠时才能选择钉子。

我认为这可能是你想要的行为方式,但由于我不确切知道它是什么,我无法为你提供更精致的拣选功能。