jak utworzyć pojedynczą legendę dla wielu wątków podrzędnych za pomocą matplotlib?

166

Kreślę ten sam typ informacji, ale dla różnych krajów, z wieloma podplotami z matplotlib. Oznacza to, że mam 9 działek na siatce 3x3, wszystkie z tym samym dla linii (oczywiście różne wartości na linię).

Jednak nie wymyśliłem, jak umieścić jedną legendę (ponieważ wszystkie 9 wątków ma te same linie) na rysunku tylko raz.

W jaki sposób mogę to zrobić?

pocketfullofcheese
źródło

Odpowiedzi:

160

Jest też fajna funkcja, get_legend_handles_labels()którą możesz wywołać na ostatniej osi (jeśli ją iterujesz), która zbierze wszystko, czego potrzebujesz z label=argumentów:

handles, labels = ax.get_legend_handles_labels()
fig.legend(handles, labels, loc='upper center')
Ben Usman
źródło
13
To powinna być najlepsza odpowiedź.
naught101
1
To rzeczywiście o wiele bardziej przydatna odpowiedź! To działało tak samo w przypadku bardziej skomplikowanej dla mnie sprawy.
gmaravel
1
doskonała odpowiedź!
Dorgham
4
Jak usunąć legendę z wątków pobocznych?
BND
5
Wystarczy dodać do tej wspaniałej odpowiedzi. Jeśli masz drugorzędną oś Y na swoich wykresach i chcesz je połączyć, użyj tego:handles, labels = [(a + b) for a, b in zip(ax1.get_legend_handles_labels(), ax2.get_legend_handles_labels())]
Bill
114

figlegend może być tym, czego szukasz: http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.figlegend

Przykład tutaj: http://matplotlib.org/examples/pylab_examples/figlegend_demo.html

Inny przykład:

plt.figlegend( lines, labels, loc = 'lower center', ncol=5, labelspacing=0. )

lub:

fig.legend( lines, labels, loc = (0.5, 0), ncol=5 )
Nathan Musoke
źródło
1
Znam wiersze, które chcę umieścić w legendzie, ale w jaki sposób linesustawić zmienną jako argument legend?
patapouf_ai,
1
@patapouf_ai linesto lista wyników, które są zwracane przez axes.plot()(tj. każda axes.plotlub podobna procedura zwraca „wiersz”). Zobacz także połączony przykład.
17

W przypadku automatycznego pozycjonowania pojedynczej legendy w a figurez wieloma osiami, jak te uzyskane za pomocą subplots(), następujące rozwiązanie działa naprawdę dobrze:

plt.legend( lines, labels, loc = 'lower center', bbox_to_anchor = (0,-0.1,1,1),
            bbox_transform = plt.gcf().transFigure )

Za pomocą bbox_to_anchori bbox_transform=plt.gcf().transFiguredefiniujesz nową obwiednię o swoim rozmiarze, figuredo której ma być odniesienie loc. Użycie (0,-0.1,1,1)przesuwa tę kostkę w dół, aby zapobiec umieszczeniu legendy nad innymi artystami.

OBS: użyj tego rozwiązania PO fig.set_size_inches()i PRZED użyciemfig.tight_layout()

Saullo GP Castro
źródło
1
Lub po prostu loc='upper center', bbox_to_anchor=(0.5, 0), bbox_transform=plt.gcf().transFigurei na pewno się nie pokryje.
Davor Josipovic
2
Nadal nie jestem pewien, dlaczego, ale rozwiązanie Everta nie zadziałało dla mnie - legenda wciąż się urywała. To rozwiązanie (wraz z komentarzem Davora) działało bardzo przejrzyście - legenda została umieszczona zgodnie z oczekiwaniami iw pełni widoczna. Dzięki!
sudo make install
16

Wystarczy tylko raz zapytać o legendę, poza swoją pętlą.

Na przykład w tym przypadku mam 4 wykresy podrzędne z tymi samymi liniami i jedną legendą.

from matplotlib.pyplot import *

ficheiros = ['120318.nc', '120319.nc', '120320.nc', '120321.nc']

fig = figure()
fig.suptitle('concentration profile analysis')

for a in range(len(ficheiros)):
    # dados is here defined
    level = dados.variables['level'][:]

    ax = fig.add_subplot(2,2,a+1)
    xticks(range(8), ['0h','3h','6h','9h','12h','15h','18h','21h']) 
    ax.set_xlabel('time (hours)')
    ax.set_ylabel('CONC ($\mu g. m^{-3}$)')

    for index in range(len(level)):
        conc = dados.variables['CONC'][4:12,index] * 1e9
        ax.plot(conc,label=str(level[index])+'m')

    dados.close()

ax.legend(bbox_to_anchor=(1.05, 0), loc='lower left', borderaxespad=0.)
         # it will place the legend on the outer right-hand side of the last axes

show()
Carla
źródło
3
figlegendJak sugested przez Evert, wydaje się być o wiele lepsze rozwiązanie;)
Carla
11
problem fig.legend()polega na tym, że wymaga identyfikacji dla wszystkich linii (działek) ... ponieważ dla każdego wykresu cząstkowego używam pętli do generowania linii, jedynym rozwiązaniem, które wymyśliłem, aby to przezwyciężyć, jest utworzenie pustej listy przed druga pętla, a następnie dołączam wiersze podczas ich tworzenia ... Następnie używam tej listy jako argumentu fig.legend()funkcji.
Carla
Podobne pytanie tutaj
emmmphd
Co dadostam jest ?
Shyamkkhadka
1
@Shyamkkhadka, w moim oryginalnym skrypcie dadosbył to zbiór danych z pliku netCDF4 (dla każdego z plików zdefiniowanych na liście ficheiros). W każdej pętli odczytywany jest inny plik i do figury dodawany jest wykres cząstkowy.
Carla
14

Zauważyłem, że żadna odpowiedź nie wyświetla obrazu z pojedynczą legendą odwołującą się do wielu krzywych na różnych wykresach cząstkowych, więc muszę ci pokazać jedną ... żeby zaciekawić ...

wprowadź opis obrazu tutaj

Teraz chcesz spojrzeć na kod, prawda?

from numpy import linspace
import matplotlib.pyplot as plt

# Calling the axes.prop_cycle returns an itertoools.cycle

color_cycle = plt.rcParams['axes.prop_cycle']()

# I need some curves to plot

x = linspace(0, 1, 51)
f1 = x*(1-x)   ; lab1 = 'x - x x'
f2 = 0.25-f1   ; lab2 = '1/4 - x + x x' 
f3 = x*x*(1-x) ; lab3 = 'x x - x x x'
f4 = 0.25-f3   ; lab4 = '1/4 - x x + x x x'

# let's plot our curves (note the use of color cycle, otherwise the curves colors in
# the two subplots will be repeated and a single legend becomes difficult to read)
fig, (a13, a24) = plt.subplots(2)

a13.plot(x, f1, label=lab1, **next(color_cycle))
a13.plot(x, f3, label=lab3, **next(color_cycle))
a24.plot(x, f2, label=lab2, **next(color_cycle))
a24.plot(x, f4, label=lab4, **next(color_cycle))

# so far so good, now the trick

lines_labels = [ax.get_legend_handles_labels() for ax in fig.axes]
lines, labels = [sum(lol, []) for lol in zip(*lines_labels)]

# finally we invoke the legend (that you probably would like to customize...)

fig.legend(lines, labels)
plt.show()

Dwie linie

lines_labels = [ax.get_legend_handles_labels() for ax in fig.axes]
lines, labels = [sum(lol, []) for lol in zip(*lines_labels)]

zasługuję na wyjaśnienie - w tym celu zawarłem trudną część w funkcji, tylko 4 linie kodu, ale mocno skomentowałem

def fig_legend(fig, **kwdargs):

    # generate a sequence of tuples, each contains
    #  - a list of handles (lohand) and
    #  - a list of labels (lolbl)
    tuples_lohand_lolbl = (ax.get_legend_handles_labels() for ax in fig.axes)
    # e.g. a figure with two axes, ax0 with two curves, ax1 with one curve
    # yields:   ([ax0h0, ax0h1], [ax0l0, ax0l1]) and ([ax1h0], [ax1l0])

    # legend needs a list of handles and a list of labels, 
    # so our first step is to transpose our data,
    # generating two tuples of lists of homogeneous stuff(tolohs), i.e
    # we yield ([ax0h0, ax0h1], [ax1h0]) and ([ax0l0, ax0l1], [ax1l0])
    tolohs = zip(*tuples_lohand_lolbl)

    # finally we need to concatenate the individual lists in the two
    # lists of lists: [ax0h0, ax0h1, ax1h0] and [ax0l0, ax0l1, ax1l0]
    # a possible solution is to sum the sublists - we use unpacking
    handles, labels = (sum(list_of_lists, []) for list_of_lists in tolohs)

    # call fig.legend with the keyword arguments, return the legend object

    return fig.legend(handles, labels, **kwdargs)

PS Zdaję sobie sprawę, że sum(list_of_lists, [])jest to naprawdę nieefektywna metoda spłaszczania listy list, ale ① Uwielbiam jego zwartość ② zwykle jest kilka krzywych w kilku podplotach i ③ Matplotlib i wydajność? ;-)

gboffi
źródło
3

Chociaż dość późno w grze, podam tutaj inne rozwiązanie, ponieważ jest to nadal jeden z pierwszych linków, które pojawiają się w Google. Korzystając z matplotlib 2.2.2, można to osiągnąć za pomocą funkcji gridspec. W poniższym przykładzie celem jest utworzenie czterech wykresów cząstkowych ułożonych w układzie 2x2 z legendą pokazaną na dole. U dołu tworzona jest „fałszywa” oś, aby umieścić legendę w ustalonym miejscu. „Faux” oś jest następnie wyłączana, więc widać tylko legendę. Wynik: https://i.stack.imgur.com/5LUWM.png .

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

#Gridspec demo
fig = plt.figure()
fig.set_size_inches(8,9)
fig.set_dpi(100)

rows   = 17 #the larger the number here, the smaller the spacing around the legend
start1 = 0
end1   = int((rows-1)/2)
start2 = end1
end2   = int(rows-1)

gspec = gridspec.GridSpec(ncols=4, nrows=rows)

axes = []
axes.append(fig.add_subplot(gspec[start1:end1,0:2]))
axes.append(fig.add_subplot(gspec[start2:end2,0:2]))
axes.append(fig.add_subplot(gspec[start1:end1,2:4]))
axes.append(fig.add_subplot(gspec[start2:end2,2:4]))
axes.append(fig.add_subplot(gspec[end2,0:4]))

line, = axes[0].plot([0,1],[0,1],'b')           #add some data
axes[-1].legend((line,),('Test',),loc='center') #create legend on bottommost axis
axes[-1].set_axis_off()                         #don't show bottommost axis

fig.tight_layout()
plt.show()
gigo318
źródło
3

jeśli używasz wykresów podrzędnych z wykresami słupkowymi, z różnymi kolorami dla każdego słupka. samodzielne tworzenie artefaktów może być szybszempatches

Powiedzmy, że masz cztery paski w różnych kolorach, ponieważ r m c kmożesz ustawić legendę w następujący sposób

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
labels = ['Red Bar', 'Magenta Bar', 'Cyan Bar', 'Black Bar']


#####################################
# insert code for the subplots here #
#####################################


# now, create an artist for each color
red_patch = mpatches.Patch(facecolor='r', edgecolor='#000000') #this will create a red bar with black borders, you can leave out edgecolor if you do not want the borders
black_patch = mpatches.Patch(facecolor='k', edgecolor='#000000')
magenta_patch = mpatches.Patch(facecolor='m', edgecolor='#000000')
cyan_patch = mpatches.Patch(facecolor='c', edgecolor='#000000')
fig.legend(handles = [red_patch, magenta_patch, cyan_patch, black_patch],labels=labels,
       loc="center right", 
       borderaxespad=0.1)
plt.subplots_adjust(right=0.85) #adjust the subplot to the right for the legend
Chidi
źródło
1
+1 Najlepsze! Użyłem go w ten sposób, dodając bezpośrednio do plt.legendjednej legendy dla wszystkich moich wątków pobocznych
Użytkownik
Szybciej jest połączyć automatyczne uchwyty i ręcznie robione etykiety handles, _ = plt.gca().get_legend_handles_labels()fig.legend(handles, labels)
:,
1

Ta odpowiedź jest uzupełnieniem @ Evert na pozycji legendy.

Moja pierwsza próba rozwiązania @ Evert nie powiodła się z powodu nakładania się legendy i tytułu wątku cząstkowego.

W rzeczywistości nakładanie się jest spowodowane przez fig.tight_layout(), co zmienia układ wykresów podrzędnych bez uwzględnienia legendy rysunku. Jednak fig.tight_layout()jest to konieczne.

Aby uniknąć nakładania się, możemy fig.tight_layout()zostawić spacje na legendę postaci przez fig.tight_layout(rect=(0,0,1,0.9)).

Opis parametrów tight_layout () .

laven_qa
źródło
1

Aby zbudować na podstawie odpowiedzi @ gboffi i Bena Usmana:

W sytuacji, gdy ktoś ma różne linie na różnych działkach o tym samym kolorze i etykiecie, można zrobić coś na wzór linii

labels_handles = {
  label: handle for ax in fig.axes for handle, label in zip(*ax.get_legend_handles_labels())
}

fig.legend(
  labels_handles.values(),
  labels_handles.keys(),
  loc="upper center",
  bbox_to_anchor=(0.5, 0),
  bbox_transform=plt.gcf().transFigure,
)
Heiner
źródło