Сбор легенды прерывается при рисовании `plot`s вместо` scatter`s - PullRequest
2 голосов
/ 05 мая 2020

Добрый день. Этот вопрос является продолжением Почему выбор легенды работает только для `ax.twinx ()`, а не `ax`? .

Минимальный код, представленный ниже, строит две кривые соответственно, на ax1 и ax2 = ax1.twinx(), их блоки легенды создаются, и нижняя легенда перемещается на верхнюю ось, чтобы можно было использовать события выбора. Щелчок по элементу легенды скроет / покажет связанную кривую.

Если используется 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)

1 Ответ

1 голос
/ 05 мая 2020

Хорошо, я знаю, что это не ответ, но комментарии не позволяют мне проводить такого рода мозговой штурм. Я попробовал пару вещей и заметил следующее. Когда вы печатаете axes из legendHandles художников в вашем for l oop, он возвращает None для обеих легенд в случае диаграммы разброса / PathCollection художников. Однако в случае «нормальных» художников plot / Line2D он возвращает объекты осей! И даже более того; хотя в терминале их представления кажутся одинаковыми (AxesSubplot(0.125,0.11;0.775x0.77)), если вы проверите, являются ли они == ax2, для legendHandles исполнителя из legend1 он возвращает False, а для одного из legend2, возвращается True. Что здесь происходит?

Итак, я попытался не только удалить legend1 из ax1 и снова добавить его в ax2, но также сделать то же самое с объектом legendHandles. Но это не позволяет мне сделать это:

NotImplementedError: cannot remove artist

Мне кажется, что вы обнаружили ошибку или, по крайней мере, непоследовательное поведение. Вот код того, что я пробовал до сих пор, на случай, если кто-то еще захочет поиграть с ним дальше.

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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...