当绘制"绘图"而不是"散点"时,图例拾取中断



您好。这个问题是"为什么图例拾取只适用于"ax.twinx(("而不适用于"ax"?"的后续问题?。

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

如果使用ax.scatter(...),则工作正常如果改为使用ax.plot(...),则图例拾取会突然中断。为什么?其他什么都没有改变,所以这很令人困惑。我测试了其他几种绘图方法,但都没有达到预期效果。

以下是它的动作视频: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时,在散点图/PathCollection艺术家的情况下,它会为两个图例返回None。然而,在"正常"情节/Line2D艺术家的情况下,它返回轴对象!更重要的是;即使在终端中,它们的表示似乎是相同的(AxesSubplot(0.125,0.11;0.775x0.77)(,如果您检查它们是否是== ax2,对于legend1legendHandles艺术家,它返回False,而对于legend2的艺术家,它则返回True。这里发生了什么?

因此,我不仅尝试从ax1中删除legend1并将其再次添加到ax2中,而且还尝试对legendHandles对象执行同样的操作。但它不允许我这么做:

NotImplementedError: cannot remove artist

在我看来,你发现了一个bug,或者至少是不一致的行为。这是我迄今为止尝试的代码,以防其他人想进一步使用它。

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

最新更新