як я можу скласти єдину легенду для багатьох субплоттів з matplotlib?


166

Я будую однотипну інформацію, але для різних країн, з декількома субплотами з matplotlib. Тобто у мене є 9 графіків на сітці 3х3, всі з однаковими для рядків (звичайно, різні значення на рядок).

Однак я не з'ясував, як поставити одну фігуру (оскільки всі 9 субплотів мають однакові лінії) на фігурі лише один раз.

Як це зробити?

Відповіді:


160

Існує також приємна функція, get_legend_handles_labels()яку можна зателефонувати на останній осі (якщо ви повторите їх), яка збирала б все необхідне з label=аргументів:

handles, labels = ax.get_legend_handles_labels()
fig.legend(handles, labels, loc='upper center')

13
Це має бути головна відповідь.
naught101

1
Це дійсно набагато корисніша відповідь! Це спрацювало просто так у більш складній для мене справі.
gmaravel

1
ідеальна відповідь!
Доргем

4
Як видалити легенду про субплоти?
БНД

5
Просто для додання цієї чудової відповіді. Якщо у вас на ділянці вторинна вісь y і вам потрібно об'єднати їх обоє, скористайтеся цим:handles, labels = [(a + b) for a, b in zip(ax1.get_legend_handles_labels(), ax2.get_legend_handles_labels())]
Білл

114

figlegend може бути те, що ви шукаєте: http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.figlegend

Приклад тут: http://matplotlib.org/examples/pylab_examples/figlegend_demo.html

Ще один приклад:

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

або:

fig.legend( lines, labels, loc = (0.5, 0), ncol=5 )

1
Я знаю рядки, які я хочу вписати в легенду, але як мені отримати linesзмінну, яку слід ввести в аргумент legend?
patapouf_ai

1
@patapouf_ai lines- це список результатів, які повертаються з axes.plot()(тобто, кожна axes.plotабо подібна програма повертає "рядок"). Дивіться також пов'язаний приклад.

17

Для автоматичного розміщення єдиної легенди в a figureз багатьма осями, як і ті, що отримані з subplots(), таке рішення працює дуже добре:

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

З bbox_to_anchorі bbox_transform=plt.gcf().transFigureви визначаєте нове обмежувальне поле розміром з вами figureдля посилання loc. Використовуючи (0,-0.1,1,1)переміщення цієї коробки відхилення трохи вниз, щоб запобігти розміщенню легенди над іншими виконавцями.

ЗАБЕЗПЕЧЕННЯ: використовуйте це рішення ПІСЛЯ використання fig.set_size_inches()та ПЕРЕДИМИfig.tight_layout()


1
Або simpy, loc='upper center', bbox_to_anchor=(0.5, 0), bbox_transform=plt.gcf().transFigureі це точно не перекриється.
Давор Йосипович

2
Я досі не впевнений, чому, але рішення Еверта не спрацювало для мене - легенда продовжувала відрізатися. Це рішення (разом із коментарем Давора) спрацювало дуже чисто - легенда була розміщена так, як очікувалося та повністю видно. Дякую!
sudo make install

16

Ви просто повинні один раз попросити легенду, поза вашої петлі.

Наприклад, у цьому випадку у мене є 4 субплоти, з однаковими лініями, і одна легенда.

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

3
figlegend, як стверджує Еверт, здається, набагато кращим рішенням;)
carla

11
Проблема fig.legend()полягає в тому, що вона вимагає ідентифікації для всіх рядків (сюжетів) ... так як для кожної підпрограми я використовую цикл для генерації ліній, єдине рішення, яке я вирішив подолати, це створити порожній список раніше другий цикл, а потім додайте рядки під час їх створення… Тоді я використовую цей список як аргумент fig.legend()функції.
carla

Аналогічне запитання тут
emmmphd

Що dadosтам?
Shyamkkhadka

1
@Shyamkkhadka, в моєму оригінальному сценарії dadosбув набір даних із файлу netCDF4 (для кожного з файлів, визначених у списку ficheiros). У кожному циклі зчитується інший файл і додається субплот до фігури.
carla

14

Я помітив, що жодна відповідь не відображає зображення з однією легендою, що посилається на багато кривих у різних підзагортах, тому я мушу показати вам одну ... щоб зробити вас цікавим ...

введіть тут опис зображення

Тепер ви хочете подивитися на код, чи не так?

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

Дві лінії

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

заслуговую пояснення - для цього я уклав складну частину функції, всього 4 рядки коду, але сильно коментував

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 Я визнаю, що sum(list_of_lists, [])це дійсно неефективний метод згладити список списків, але ① я люблю його компактність, ② зазвичай це кілька кривих у декількох підгруппах і ③ Matplotlib та ефективність? ;-)


3

Хоча досить пізно в грі, я дам тут ще одне рішення, оскільки це все ще одне з перших посилань на Google. Використовуючи matplotlib 2.2.2, цього можна досягти за допомогою функції gridspec. У наведеному нижче прикладі мета полягає в тому, щоб чотири субплоти були розташовані в 2x2 з легендою, показаною внизу. Внизу створюється вісь "штучного", щоб розмістити легенду на фіксованому місці. Ось "штучний" вісь потім вимикається, тому показує лише легенда. Результат: 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()

3

якщо ви використовуєте субплоти з діаграмами з різним кольором для кожного бара. можливо, швидше створити артефакти самостійно, використовуючиmpatches

Скажіть, у вас є чотири смуги з різними кольорами, оскільки r m c kви можете встановити легенду наступним чином

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

1
+1 найкращий! Я використовував це таким чином, додаючи безпосередньо до того, plt.legendщоб мати одну легенду для всіх моїх субплотів
Користувач

Швидше поєднувати автоматичні ручки та етикетки ручної роботи:, handles, _ = plt.gca().get_legend_handles_labels()тодіfig.legend(handles, labels)
smcs

1

Ця відповідь є доповненням до Еверта на позиції легенди.

Моя перша спроба рішення @ Evert не вдалася через перекриття легенди та назви субплота.

Насправді, перекриття спричинені тим fig.tight_layout(), що змінює компонування субплотів без урахування легенди фігури. Однак fig.tight_layout()необхідно.

Щоб уникнути перекриттів, ми можемо сказати fig.tight_layout()залишити місця для легенди фігури fig.tight_layout(rect=(0,0,1,0.9)).

Опис параметрів Close_layout () .


1

Щоб базуватись на відповіді @ gboffi та Бена Усмана:

У ситуації, коли у різних субплоттонів різні лінії з одним кольором та міткою, можна зробити щось по лінії

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,
)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.