在Matplotlib中从一组重叠的艺术家中选择一个艺术家 [英] Picking a single artist from a set of overlapping artists in Matplotlib

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

问题描述

这个问题与以下两个问题密切相关,但是这个问题更笼统.

This question is closely related to the two below, but this question is more general.

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

多个选择事件受到干扰

在单个画布上拾取重叠的艺术家时,将为每个艺术家创建单独的拾取事件.在下面的示例中,单击一个红色点会两次调用on_pick,一次是lines,一次是points.由于points位于该行的上方(考虑到它们各自的zorder值),我希望只为最顶级的艺术家生成一个单独的拾取事件(在本例中为points).

When picking overlapping artists on a single canvas, separate pick events are created for each artist. In the example below, a click on a red point calls on_pick twice, once for lines and once for points. Since the points sit above the line (given their respective zorder values), I would prefer to have just a single pick event generated for the topmost artist (in this case: 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,然后计算鼠标与所有艺术家之间的距离,如下所示.但是,此解决方案非常混乱,因为添加其他重叠的美术师会使该代码变得相当复杂,从而增加了要检查的情况和条件的数量.


Messy solution:

One solution is to use Matplotlib's button_press_event, then compute distances between the mouse and all artists, like below. However, this solution is quite messy, because adding additional overlapping artists will make this code quite complex, increasing the number of cases and conditions to check.

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


问题摘要: 是否有更好的方法从一组重叠的艺术家中选择最高的艺术家?


Question summary: Is there a better way to select the topmost artist from a set of overlapping artists?

理想情况下,我希望能够执行以下操作:

Ideally, I would love be able to do something like this:

pyplot.set_pick_stack( [points, line] )

表示将选择points代替line进行重叠拾取.

implying that points will be selected over line for an overlapping pick.

推荐答案

button_press_event事件发生时创建自己的事件可能是最简单的.为了说明问题中表达的"set_pick_stack"的概念,可能如下所示.这个想法是存储一组艺术家,并在button_press_event中检查该事件是否包含在艺术家中.然后在自定义onpick函数上触发回调.

It might be easiest to create your own event on button_press_events happening. To pusue the idea of a "set_pick_stack" expressed in the question, this could look as follows. The idea is to store a set of artists and upon a button_press_event check if that event is contained by the artist. Then fire a callback on a custom onpick function.

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确定顺序.看起来像

The idea here is to supply a list of artists in the order desired for pick events. Of course one could also use zorder to determine the order. This could look like

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"打开问题或将实施以PR的形式提交给matplotlib.

Because the question arouse in the comments, let's look at why matplotlib does not do this filtering automatically. Well, first I would guess that it's undesired in 50% of the cases at least, where you would like an event for every artist picked. But also, it is much easier for matplotlib to just emit an event for every artist that gets hit by a mouseevent than to filter them. For the former, you just compare coordinates (much like the "messy solution" from the question). Whereas it is hard to get the topmost artist only; of course in the case two artists have differing zorder, it would be possible, but if they have the same zorder, it's just the order they appear in the lists of axes children that determines which is in front. A "pick_upmost_event" would need to check the complete stack of axes children to find out which one to pick. That being said, it's not impossible, but up to now probably noone was convinced it's worth the effort. Surely, people can open an issue or submit an implementation as PR to matplotlib for such "pick_upmost_event".

这篇关于在Matplotlib中从一组重叠的艺术家中选择一个艺术家的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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