Python Matplotlib误差线图例选取

4
我正在尝试编写一个与matplotlib errorbar图类似的图例选择器,类似于这个示例。我希望能够通过单击图例中的误差线/数据点来切换它们在轴上的可见性。问题是plt.legend()返回的图例对象不包含有关用于创建图例的艺术家的任何数据。例如,如果我执行以下操作:
import numpy as np
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
x = np.linspace(0,10,100)
y = np.sin(x) + np.random.rand(100)
yerr = np.random.rand(100)

erbpl1 = ax.errorbar(x, y, yerr=yerr, fmt='o', label='A')
erbpl2 = ax.errorbar(x, 0.02*y, yerr=yerr, fmt='o', label='B')

leg = ax.legend()

从这里看来,通过使用leg对象似乎无法访问图例的艺术家。通常,可以在更简单的图例中执行此操作,例如:

plt.plot(x, y, label='whatever')
leg = plt.legend()
proxy_lines = leg.get_lines()

该函数返回用于图例的Line2D对象。但是,在errorbar图中,leg.get_lines()会返回一个空列表。这有点合理,因为plt.errorbar返回一个matplotlib.container.ErrorbarContainer对象(其中包含数据点、误差线端点和误差线)。我希望图例有一个类似的数据容器,但我找不到它。我能找到的最接近的是leg.legendHandles,它指向误差线,但没有数据点或端点。如果您可以选择图例,您可以使用字典将其映射到原始图,并使用以下函数打开/关闭误差线。

def toggle_errorbars(erb_pl):
    points, caps, bars = erb_pl
    vis = bars[0].get_visible()
    for line in caps:
        line.set_visible(not vis)
    for bar in bars:
        bar.set_visible(not vis)
    return vis

def onpick(event):
    # on the pick event, find the orig line corresponding to the
    # legend proxy line, and toggle the visibility
    legline = event.artist
    origline = lined[legline]

    vis = toggle_errorbars(origline)
    ## Change the alpha on the line in the legend so we can see what lines
    ## have been toggled
    if vis:
        legline.set_alpha(.2)
    else:
        legline.set_alpha(1.)
    fig.canvas.draw()

我的问题是,是否有一种解决方法可以让我在误差线/其他复杂图例上进行事件选择?


你使用的matplotlib版本是哪个?这在我的机器上正常工作(非常接近当前的主要版本)。 - tacaswell
我正在使用Python 3.2和Matplotlib 1.3.0。 - codeMonkey
抱歉,我读得太快了。明确一下,您在图例中获取了艺术家,但现在正在试图挑选它们。您应该包含演示实际问题的代码,而不仅仅是样板文件。 - tacaswell
我的问题是我根本看不到图例的艺术家,因此我也无法选择它们。 - codeMonkey
哦,你有90%的把握发现了一个bug或者未涵盖到的情况,请给我一点时间重新回忆这段代码的工作原理(它非常通用,但是有点晦涩,并且没有很好的文档记录)。 - tacaswell
1个回答

5
这使得标记可以被选择:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.legend_handler

from matplotlib.container import ErrorbarContainer

class re_order_errorbarHandler(matplotlib.legend_handler.HandlerErrorbar):
    """
    Sub-class the standard error-bar handler 
    """
    def create_artists(self, *args, **kwargs):
        #  call the parent class function
        a_list = matplotlib.legend_handler.HandlerErrorbar.create_artists(self, *args, **kwargs)
        # re-order the artist list, only the first artist is added to the
        # legend artist list, this is the one that corresponds to the markers
        a_list = a_list[-1:] + a_list[:-1]
        return a_list

my_handler_map = {ErrorbarContainer: re_order_errorbarHandler(numpoints=2)}

fig, ax = plt.subplots()
x = np.linspace(0,10,100)
y = np.sin(x) + np.random.rand(100)
yerr = np.random.rand(100)

erbpl1 = ax.errorbar(x, y, yerr=yerr, fmt='o', label='A')
erbpl2 = ax.errorbar(x, 0.02*y, yerr=yerr, fmt='o', label='B')

leg = ax.legend(handler_map=my_handler_map)

lines = [erbpl1, erbpl2]
lined = dict()
# not strictly sure about ordering, but 
for legline, origline in zip(leg.legendHandles, lines):
    legline.set_picker(5)  # 5 pts tolerance
    lined[legline] = origline


def onpick(event):
    # on the pick event, find the orig line corresponding to the
    # legend proxy line, and toggle the visibility
    legline = event.artist
    origline = lined[legline]
    for a in origline.get_children():
        vis = not a.get_visible()
        a.set_visible(vis)
    # Change the alpha on the line in the legend so we can see what lines
    # have been toggled
    if vis:
        legline.set_alpha(1.0)
    else:
        legline.set_alpha(0.2)
    fig.canvas.draw()

fig.canvas.mpl_connect('pick_event', onpick)

这里的情况是,ErrorbarContainers 的标准处理程序使用4个艺术家来生成图例条目(对于条形图,使用 LineCollection ;对于 caps,也使用 LineCollection ;对于连接线,使用 Line2D ;对于标记,也使用 Line2D )。生成艺术家的代码仅返回添加到图例中的艺术家列表中的第一个艺术家(请参阅 matplotlib.legend_handler.HandlerBase.__call__)。对于误差条的艺术家列表中的第一个艺术家恰好是垂直线的线集合,这就是最终出现在 leg.legendHandles 中的内容。拾取不起作用的原因似乎是它们被其他艺术家遮盖了(我想是这个原因)。
解决方案是创建一个本地的 HandlerErrorbar 子类,重新排序艺术家列表,以便保存在 leg.legendHandles 中的是标记的 Line2D 对象。
我可能会开启一个 PR 来使这成为默认行为。

太棒了!感谢您的回答!我想要的实际行为是切换误差条的开/关状态。我在上面包含了一小段代码来实现这个功能。唯一的问题是,你仍然只能在图例中选择Line2D作为标记。似乎没有其他方法。理想情况下,我希望可以独立地在图例中选择标记和误差条,但我会接受这个解决方案!再次感谢! - codeMonkey
@apodemus 这次编辑被拒绝了。你应该把那段代码放在你的问题描述中(包括你想要使用的toggle_errorbar和'on_pick'函数)。 - tacaswell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接