Matplotlib挑选重叠艺术家的活动顺序 [英] Matplotlib pick event order for overlapping artists

查看:98
本文介绍了Matplotlib挑选重叠艺术家的活动顺序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在matplotlib选择事件中遇到了一个非常奇怪的问题.我有两位都是可挑剔的艺术家,而且一开始都是不重叠的(小孔"和钉子").当我选择其中一个时,在事件处理期间,我将另一个移动到我刚刚单击的位置(将钉"移到孔"中).然后,无需执行任何其他操作,就会生成感动艺术家(钉子)的选择事件,即使在生成第一个事件时它没有出现.我对此的唯一解释是,事件管理器在处理事件时仍会在某种程度上穿越艺术家层,因此在光标下移动第二位艺术家后会击中它.

I'm hitting a very strange issue with matplotlib pick events. I have two artists that are both pickable and are non-overlapping to begin with ("holes" and "pegs"). When I pick one of them, during the event handling I move the other one to where I just clicked (moving a "peg" into the "hole"). Then, without doing anything else, a pick event from the moved artist (the peg) is generated even though it wasn't there when the first event was generated. My only explanation for it is that somehow the event manager is still moving through artist layers when the event is processed, and therefore hits the second artist after it is moved under the cursor.

那么,我的问题是-选择事件(或与此有关的任何事件)如何通过画布上重叠的艺术家进行迭代,并且有一种控制它的方法吗?我认为,如果始终从上向下移动(而不是从下向上或随机移动),我将获得我想要的行为.我没有找到足够的文档,对SO的漫长搜索也没有发现这个确切的问题.以下是一个说明此问题的工作示例,其中scatter中的PathCollections作为钉子和孔:

So then my question is - how do pick events (or any events for that matter) iterate through overlapping artists on the canvas, and is there a way to control it? I think I would get my desired behavior if it moved from the top down always (rather than bottom up or randomly). I haven't been able to find sufficient enough documentation, and a lengthy search on SO has not revealed this exact issue. Below is a working example that illustrates the problem, with PathCollections from scatter as pegs and holes:

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设置为使钉始终位于孔上方,但这并不会改变拾取事件的产生方式,尤其是这种有趣的幻像事件.

I have tried setting the zorder to make the pegs always above the holes, but that doesn't change how the pick events are generated, and particularly this funny phantom event.

编辑: 上下文是钉住单人纸牌游戏的实现,因此我希望能够拾取一个钉子,然后单击一个空洞将其放到那里.目前,将同一钉子一放下便会立即再次被拾起.

EDIT: The context is an implementation of peg solitaire, so I want to be able to pick up a peg then click on an empty hole to drop it there. Currently the same peg is immediately picked up again as soon as it is dropped.

推荐答案

计时问题

您遇到的问题是由于pick_event函数的调用方式引起的,matplotlib测试了每位演出者,如果被选中,则立即调用处理函数.测试的顺序取决于您定义孔和钉的顺序(可以通过更改钉和孔的定义顺序进行验证).

Problem with the timing

The problem you encounter is due to the way the pick_event function is called, matplotlib test each artist and if it's picked immediatly call you handler functio. The order in which it is tested depend on the order in which you defined holes and pegs (you can verifiy it by changing the order of the definition of pegs and holes).

因此,避免出现此问题的一种好方法是使用matplotlib提供的计时器.第一次出现拾取事件时,您只需将艺术家添加到队列中,然后每隔 x 毫秒就处理一次新数据.

So one way that seems good to avoid this problem is to use the timer provided by matplotlib. In a first time when there is a picking event you just add the artist to a queue and then every x milliseconds you process this new data.

这是这种实现方式的一个示例:

Here's an example of this kind of implementation :

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

大多数代码是您提供的,我只是添加了计时器实现.

Most of the code is what you provided, I just added the timer implementation.

可以通过为艺术家定义特定的选择器来解决此问题.但是,您将无法在同一位置选择多个项目.

It is possible to fix this behavior by defining specific pickers for your artists. However you won't be able to select several item in the same spot.

请参见以下示例,该示例解决了不应选择的桩钉问题:

See this example that solve the part about the pick of pegs when it shouldn't :

-替换钉的定义:

self.pegs = self.ax.scatter([0], [0], s=100, facecolor='#dd8800',edgecolor='black', picker=self.pegs_picker)

-添加功能pegs_picker:

-Add the function 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()

只有在不与孔重叠的情况下,这些钉才可以被拾取.

With that pegs are pickable only when they're not superposed with a hole.

我认为这可能是实现所需行为的一种方法,但是由于我不确切知道它是什么,因此我无法为您提供更完善的拣选功能.

I think that might be the way to go to have the behavior you want, but since I don't know exactly what it is, I can't provide you more refined picking functions.

这篇关于Matplotlib挑选重叠艺术家的活动顺序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆