Как сделать одну легенду для многих сюжетов с помощью matplotlib?

166

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

Тем не менее, я не выяснил, как разместить на рисунке одну легенду (поскольку все 9 вспомогательных участков имеют одинаковые линии) только один раз.

Как я могу это сделать?

pocketfullofcheese
источник

Ответы:

161

Есть также хорошая функция, которую 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
Как удалить легенду для сюжетов?
BND
5
Просто чтобы добавить к этому великому ответу. Если у вас есть вторичная ось Y на ваших графиках, и вам нужно объединить их, используйте это:handles, labels = [(a + b) for a, b in zip(ax1.get_legend_handles_labels(), ax2.get_legend_handles_labels())]
Bill
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

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

OBS: используйте это решение ПОСЛЕ того, как вы используете fig.set_size_inches()и ДО того, как вы используетеfig.tight_layout()

Saullo GP Castro
источник
1
Или просто, 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()
Carla
источник
3
figlegend, как полагает Эверт, кажется, гораздо лучшее решение;)
Карла
11
проблема fig.legend()заключается в том, что требуется идентификация для всех линий (графиков) ... поскольку для каждого подзаголовка я использую цикл для генерации линий, единственное решение, которое я нашел, чтобы преодолеть это, - создать пустой список до второй цикл, а затем добавить строки по мере их создания ... Затем я использую этот список в качестве аргумента fig.legend()функции.
Карла
Подобный вопрос здесь
emmmphd
Что dadosтам?
Шямкхадка
1
@Shyamkkhadka, в моем исходном скрипте dadosбыл набор данных из файла netCDF4 (для каждого из файлов, определенных в списке ficheiros). В каждом цикле читается отдельный файл, и к рисунку добавляется подплот.
Карла
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 и эффективность»? ;-)

gboffi
источник
3

Пока я немного опаздываю к игре, я дам здесь другое решение, так как это все еще одна из первых ссылок, которая будет отображаться в Google. Используя matplotlib 2.2.2, этого можно достичь с помощью функции gridspec. В приведенном ниже примере цель состоит в том, чтобы расположить четыре вспомогательных участка в формате 2x2 с легендой, показанной внизу. Внизу создается ось 'faux', чтобы разместить легенду в фиксированном месте. Затем ось 'faux' отключается, поэтому отображается только легенда. Результат: 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
источник
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
Chidi
источник
1
+1 лучшее! Я использовал его таким образом, добавив непосредственно к, plt.legendчтобы иметь одну легенду для всех моих подсюжетов
Пользователь
handles, _ = plt.gca().get_legend_handles_labels()fig.legend(handles, labels)
Быстрее
1

Этот ответ является дополнением к @ Evert's на позиции легенды.

Моя первая попытка решения @ Evert не удалась из-за совпадения легенды и названия подзаговора.

Фактически, перекрытия вызваны тем fig.tight_layout(), что меняет расположение подзаговоров без учета условных обозначений рисунка. Однако fig.tight_layout()это необходимо.

Чтобы избежать наложений, мы можем сказать, fig.tight_layout()чтобы оставить место для легенды фигуры fig.tight_layout(rect=(0,0,1,0.9)).

Описание параметровight_layout () .

laven_qa
источник
1

Чтобы основываться на ответе @ gboffi's и Ben Usman:

В ситуации, когда на разных участках имеются разные линии с одним и тем же цветом и меткой, можно сделать что-то вроде

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,
)
Хайнер
источник