当绘制"plot"而不是"scatter"时,图例选择会中断 [英] Legend picking breaks when drawing `plot`s instead of `scatter`s
问题描述
美好的一天.这个问题是为什么选择图例仅适用于`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`?.
下面提供的最小代码分别在 ax1
和 ax2 = 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屋!