当绘制"plot"而不是"scatter"时,图例选择会中断 [英] Legend picking breaks when drawing `plot`s instead of `scatter`s

查看:46
本文介绍了当绘制"plot"而不是"scatter"时,图例选择会中断的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

美好的一天.这个问题是为什么选择图例仅适用于`ax.twinx()`而不适用于'ax`吗?/a>.

Good day. This question is a follow-up of Why does legend-picking only works for `ax.twinx()` and not `ax`?.

下面提供的最小代码分别在 ax1ax2 = ax1.twinx() 上绘制两条曲线,它们的图例框被创建,底部图例被移动到顶部斧头,以便可以使用选择器事件.单击图例项将隐藏/显示关联的曲线.

The minimal code provided below plots two curves respectively on ax1 and ax2 = ax1.twinx(), their legend boxes are created and the bottom legend is moved to the top ax so that picker events can be used. Clicking on a legend item will hide/show the associated curve.

如果使用 ax.scatter(...) 则工作正常.如果使用 ax.plot(...) 代替,图例选择会突然中断.为什么?没有其他改变,这很令人困惑.我已经测试了其他几种绘图方法,但它们都没有按预期工作.

If ax.scatter(...) is used that works fine. If ax.plot(...) is used instead, legend picking suddenly breaks. Why? Nothing else is changed so that's quite confusing. I have tested several other plotting methods and none of them work as expected.

以下是其中的视频: https://imgur.com/qsPYHKc.mp4

import matplotlib.pyplot as plt
import numpy as np

fig, ax1 = plt.subplots()
ax2 = ax1.twinx()

X = np.linspace(0, 2*np.pi, 100)
Y1 = X**0.5 * np.sin(X)
Y2 = -np.cos(X)

# This is a quick way to change the plotting function, simply modify n.
n = 0
function, container = [("scatter", "collections"),
                       ("plot",    "lines"),
                       ("bar",     "patches"),
                       ("barbs",   "collections"),
                       ("quiver",  "collections")][n]
getattr(ax1, function)(X, Y1, color="green", label="$Y_1$")
getattr(ax2, function)(X, Y2, color="red",   label="$Y_2$")

# Put both legends on ax2 so that pick events also work for ax1's legend.
legend1 = ax1.legend(loc="upper left")
legend2 = ax2.legend(loc="upper right")
legend1.remove()
ax2.add_artist(legend1)

for n, legend in enumerate((legend1, legend2)):
    legend_item = legend.legendHandles[0]
    legend_item.set_gid(n+1)
    legend_item.set_picker(10)

# When a legend element is picked, hide/show the associated curve.   
def on_graph_pick_event(event):

    gid = event.artist.get_gid()
    print(f"Picked Y{gid}'s legend.")

    ax = {1: ax1, 2: ax2}[gid]
    for artist in getattr(ax, container):
        artist.set_visible(not artist.get_visible())
    plt.draw()

fig.canvas.mpl_connect("pick_event", on_graph_pick_event)

推荐答案

好,所以我知道这不是答案,但是注释不允许我进行这种头脑风暴.我尝试了几件事,并注意到了以下内容.当您在 for 循环中打印 legendHandles 艺术家的 axes 时,它会在散点图的情况下为两个图例返回 None/PathCollection 艺术家.但是,在正常"绘图/Line2D 艺术家的情况下,它返回轴对象!甚至还不止于此;即使在终端中它们的表示似乎相同( AxesSubplot(0.125,0.11; 0.775x0.77)),如果您检查它们是否为 == ax2 ,对于 legend1 legendHandles 艺术家,它返回 False ,对于 legend2 的艺术家,它返回正确.这里发生了什么?

Ok, so I know this is not the answer, but the comments don't allow me to do this kind of brainstorming. I tried a couple of things, and noticed the following. When you print the axes of the legendHandles artists in your for loop, it returns None for both legends in the case of the scatter plot / PathCollection artists. However, in the case of the 'normal' plot / Line2D artists, it returns axes objects! And even more than that; even though in the terminal their representations seem to be the same (AxesSubplot(0.125,0.11;0.775x0.77)), if you check if they are == ax2, for the legendHandles artist of legend1 it returns False, while for the one of legend2, it returns True. What is happening here?

因此,我不仅尝试从 ax1 中删除 legend1 ,然后再次将其添加到 ax2 中,而且还对>legendHandles 对象.但它不允许我这样做:

So I tried to not only remove legend1 from ax1 and add it again to ax2 but to also do the same with the legendHandles object. But it doesn't allow me to do that:

NotImplementedError: cannot remove artist

对我来说,您似乎发现了一个错误,或者至少是不一致的行为.这是我到目前为止尝试过的代码,以防其他人想进一步尝试.

To me it looks like you found a bug, or at least inconsistent behaviour. Here is the code of what I tried so far, in case anybody else would like to play around with it further.

import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('Qt5Agg')
import numpy as np

fig, ax1 = plt.subplots()
ax2 = ax1.twinx()

X = np.linspace(0, 2*np.pi, 100)
Y1 = X**0.5 * np.sin(X)
Y2 = -np.cos(X)

USE_LINES = True  # <--- set this to True or False to test both cases.
if USE_LINES:
    ax1.plot(X, Y1, color="green", label="$Y_1$")
    ax2.plot(X, Y2, color="red",   label="$Y_2$")
else:
    ax1.scatter(X, Y1, color="green", label="$Y_1$")
    ax2.scatter(X, Y2, color="red",   label="$Y_2$")

# Put both legends on ax2 so that pick events also work for ax1's legend.
legend1 = ax1.legend(loc="upper left")
legend2 = ax2.legend(loc="upper right")
legend1.remove()
ax2.add_artist(legend1)
# legend1.legendHandles[0].remove()
# ax2.add_artist(legend1.legendHandles[0])

for n, legend in enumerate((legend1, legend2)):
    legend_item = legend.legendHandles[0]
    legend_item.set_gid(n+1)
    legend_item.set_picker(10)
    print(
        f'USE_LINES = {USE_LINES}', f'legend{n+1}',
        legend_item.axes.__repr__() == legend.axes.__repr__(),
        legend_item.axes == legend.axes,
        legend_item.axes.__repr__() == ax2.__repr__(),
        legend_item.axes == ax2, type(legend_item),
    )

# When a legend element is picked, hide/show the associated curve.
def on_graph_pick_event(event):

    gid = event.artist.get_gid()
    print(f"Picked Y{gid}'s legend.")

    ax = {1: ax1, 2: ax2}[gid]
    artist = ax.lines[0] if USE_LINES else ax.collections[0]
    artist.set_visible(not artist.get_visible())
    plt.draw()

fig.canvas.mpl_connect("pick_event", on_graph_pick_event)
plt.show()

这篇关于当绘制"plot"而不是"scatter"时,图例选择会中断的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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