bagaimana cara membuat legenda tunggal untuk banyak subplot dengan matplotlib?

166

Saya merencanakan jenis informasi yang sama, tetapi untuk negara yang berbeda, dengan banyak subplot dengan matplotlib. Yaitu, saya memiliki 9 plot pada kisi 3x3, semuanya dengan garis yang sama (tentu saja, nilai yang berbeda per baris).

Namun, saya belum menemukan cara untuk menempatkan legenda tunggal (karena semua 9 subplot memiliki garis yang sama) pada gambar sekali saja.

Bagaimana aku melakukan itu?

pocketfullofcheese
sumber

Jawaban:

160

Ada juga fungsi bagus get_legend_handles_labels()yang bisa Anda panggil pada sumbu terakhir (jika Anda mengulanginya) yang akan mengumpulkan semua yang Anda butuhkan dari label=argumen:

handles, labels = ax.get_legend_handles_labels()
fig.legend(handles, labels, loc='upper center')
Ben Usman
sumber
13
Ini harus menjadi jawaban teratas.
naught101
1
Ini memang jawaban yang jauh lebih berguna! Itu bekerja begitu saja dalam kasus yang lebih rumit bagi saya.
gmaravel
1
jawaban sempurna!
Dorgham
4
Bagaimana cara menghapus legenda untuk subplot?
BND
5
Hanya untuk menambah jawaban yang bagus ini. Jika Anda memiliki sumbu y sekunder pada plot Anda dan perlu menggabungkan keduanya, gunakan ini:handles, labels = [(a + b) for a, b in zip(ax1.get_legend_handles_labels(), ax2.get_legend_handles_labels())]
Bill
114

figlegend mungkin yang Anda cari: http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.figlegend

Contoh di sini: http://matplotlib.org/examples/pylab_examples/figlegend_demo.html

Contoh lain:

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

atau:

fig.legend( lines, labels, loc = (0.5, 0), ncol=5 )
Nathan Musoke
sumber
1
Saya tahu baris yang ingin saya masukkan dalam legenda, tetapi bagaimana saya mendapatkan linesvariabel untuk dimasukkan ke dalam argumen legend?
patapouf_ai
1
@patapouf_ai linesadalah daftar hasil yang dikembalikan dari axes.plot()(yaitu, setiap axes.plotrutin atau serupa mengembalikan "baris"). Lihat juga contoh terkait.
17

Untuk penentuan posisi otomatis sebuah legenda tunggal dalam figuredengan banyak sumbu, seperti yang diperoleh dengan subplots(), solusi berikut berfungsi dengan sangat baik:

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

Dengan bbox_to_anchordan bbox_transform=plt.gcf().transFigureAnda menentukan kotak pembatas baru dari ukuran Anda figureuntuk menjadi referensi loc. Menggunakan (0,-0.1,1,1)gerakan kotak bouding ini sedikit ke bawah untuk mencegah legenda ditempatkan di atas seniman lain.

OBS: gunakan solusi ini SETELAH Anda gunakan fig.set_size_inches()dan SEBELUM Anda gunakanfig.tight_layout()

Saullo GP Castro
sumber
1
Atau simpy loc='upper center', bbox_to_anchor=(0.5, 0), bbox_transform=plt.gcf().transFiguredan itu tidak akan tumpang tindih pasti.
Davor Josipovic
2
Saya masih tidak yakin mengapa, tetapi solusi Evert tidak bekerja untuk saya - legenda terus terputus. Solusi ini (bersama dengan komentar davor) bekerja sangat bersih - legenda ditempatkan seperti yang diharapkan dan sepenuhnya terlihat. Terima kasih!
sudo make install
16

Anda hanya perlu meminta legenda sekali, di luar lingkaran Anda.

Misalnya, dalam hal ini saya punya 4 subplot, dengan garis yang sama, dan satu legenda.

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
sumber
3
figlegend, seperti yang disarankan oleh Evert, tampaknya menjadi solusi yang jauh lebih baik;)
carla
11
masalahnya fig.legend()adalah bahwa itu memerlukan identifikasi untuk semua garis (plot) ... karena, untuk setiap subplot, saya menggunakan loop untuk menghasilkan garis, satu-satunya solusi yang saya cari untuk mengatasinya adalah dengan membuat daftar kosong sebelum loop kedua, dan kemudian tambahkan baris saat sedang dibuat ... Lalu saya menggunakan daftar ini sebagai argumen untuk fig.legend()fungsi.
carla
Pertanyaan serupa di sini
emmmphd
Ada apa dadosdisana
Shyamkkhadka
1
@Shyamkkhadka, dalam skrip asli saya dadosadalah dataset dari file netCDF4 (untuk setiap file yang ditentukan dalam daftar ficheiros). Di setiap loop, file yang berbeda dibaca dan subplot ditambahkan ke gambar.
carla
13

Saya perhatikan bahwa tidak ada jawaban yang menampilkan gambar dengan legenda tunggal yang mereferensikan banyak kurva di berbagai subplot, jadi saya harus menunjukkan satu ... untuk membuat Anda penasaran ...

masukkan deskripsi gambar di sini

Sekarang, Anda ingin melihat kodenya, bukan?

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

Dua garis

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

pantas mendapat penjelasan - untuk tujuan ini saya telah merangkum bagian yang sulit dalam suatu fungsi, hanya 4 baris kode tetapi banyak berkomentar

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 Saya tahu itu sum(list_of_lists, [])adalah metode yang sangat tidak efisien untuk meratakan daftar daftar tetapi love Saya suka kekompakannya, ② biasanya beberapa kurva di beberapa subplot dan ③ Matplotlib dan efisiensi? ;-)

gboffi
sumber
3

Meskipun agak terlambat ke permainan, saya akan memberikan solusi lain di sini karena ini masih salah satu tautan pertama yang muncul di google. Menggunakan matplotlib 2.2.2, ini dapat dicapai dengan menggunakan fitur gridspec. Pada contoh di bawah, tujuannya adalah untuk memiliki empat sub-plot yang disusun secara 2x2 dengan legenda yang ditunjukkan di bagian bawah. Sumbu 'palsu' dibuat di bagian bawah untuk menempatkan legenda di tempat tetap. Sumbu 'palsu' kemudian dimatikan sehingga hanya legenda yang ditampilkan. Hasil: 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
sumber
3

jika Anda menggunakan subplot dengan diagram batang, dengan warna berbeda untuk setiap batang. mungkin lebih cepat untuk membuat artefak yang Anda gunakan sendirimpatches

Katakanlah Anda memiliki empat batang dengan warna berbeda karena r m c kAnda dapat mengatur legenda sebagai berikut

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
sumber
1
+1 Yang terbaik! Saya menggunakannya dengan cara ini, menambahkan langsung plt.legendke memiliki satu legenda untuk semua subplot saya
Pengguna
Lebih cepat menggabungkan pegangan otomatis dan label buatan tangan handles, _ = plt.gca().get_legend_handles_labels()fig.legend(handles, labels)
:,
1

Jawaban ini adalah pelengkap dari @ Evert pada posisi legenda.

Percobaan pertama saya pada solusi @ Evert gagal karena tumpang tindih dari legenda dan judul subplot.

Bahkan, tumpang tindih disebabkan oleh fig.tight_layout(), yang mengubah tata letak subplot tanpa mempertimbangkan legenda gambar. Namun, fig.tight_layout()itu perlu.

Untuk menghindari tumpang tindih, kita dapat memberitahu fig.tight_layout()untuk meninggalkan ruang untuk legenda tokoh oleh fig.tight_layout(rect=(0,0,1,0.9)).

Deskripsi parameter tight_layout () .

laven_qa
sumber
1

Untuk membangun di atas jawaban @ gboffi dan Ben Usman:

Dalam situasi di mana seseorang memiliki garis yang berbeda di subplot yang berbeda dengan warna dan label yang sama, orang dapat melakukan sesuatu di sepanjang garis

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
sumber