Je suis en train de tracer le même type d'informations, mais pour différents pays, avec plusieurs sous-parcelles avec matplotlib. C’est-à-dire que j’ai 9 parcelles sur une grille 3x3, toutes avec les mêmes caractéristiques pour les lignes (bien sûr, des valeurs différentes par ligne).
Cependant, je n'ai pas trouvé comment mettre une seule légende (puisque les 9 sous-graphes ont les mêmes lignes) sur la figure une seule fois.
Comment je fais ça?
figlegend peut être ce que vous cherchez: http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.figlegend
Exemple ici: http://matplotlib.org/examples/pylab_examples/figlegend_demo.html
Un autre exemple:
plt.figlegend( lines, labels, loc = 'lower center', ncol=5, labelspacing=0. )
ou:
fig.legend( lines, labels, loc = (0.5, 0), ncol=5 )
Il y a aussi une fonction de Nice get_legend_handles_labels()
que vous pouvez appeler sur le dernier axe (si vous les parcourez plusieurs fois) et qui collecterait tout ce dont vous avez besoin de label=
arguments:
handles, labels = ax.get_legend_handles_labels()
fig.legend(handles, labels, loc='upper center')
Il suffit de demander la légende une fois, en dehors de votre boucle.
Par exemple, dans ce cas, j'ai 4 intrigues secondaires, avec les mêmes lignes, et une seule légende.
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()
Pour le positionnement automatique d'une seule légende dans un figure
à plusieurs axes, comme ceux obtenus avec subplots()
, la solution suivante fonctionne vraiment bien:
plt.legend( lines, labels, loc = 'lower center', bbox_to_anchor = (0,-0.1,1,1),
bbox_transform = plt.gcf().transFigure )
Avec bbox_to_anchor
Et bbox_transform=plt.gcf().transFigure
, vous définissez un nouveau cadre de sélection de la taille de votre figure
pour être une référence pour loc
. L'utilisation de (0,-0.1,1,1)
Déplace cette boîte de dialogue légèrement vers le bas pour empêcher la légende d'être placée au-dessus d'autres artistes.
OBS: utilisez cette solution APRES l'utilisation de fig.set_size_inches()
et AVANT d'utiliser fig.tight_layout()
J'ai remarqué qu'aucune réponse ne permet d'afficher une image avec une seule légende référençant de nombreuses courbes dans différentes intrigues secondaires. Je dois donc vous en montrer une ... pour vous rendre curieux ...
Maintenant, vous voulez regarder le code, n'est-ce pas?
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()
Les deux lignes
lines_labels = [ax.get_legend_handles_labels() for ax in fig.axes]
lines, labels = [sum(lol, []) for lol in Zip(*lines_labels)]
mérite une explication - dans ce but, j'ai encapsulé la partie délicate dans une fonction, seulement 4 lignes de code mais fortement commenté
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 Je reconnais que sum(list_of_lists, [])
est une méthode vraiment inefficace pour aplatir une liste de listes, mais j'aime sa compacité, ② se compose généralement de quelques courbes dans quelques intrigues secondaires et Matplotlib et efficacité? ;-)
Bien que je sois assez tard pour le jeu, je vais vous donner une autre solution car ce lien est toujours l’un des premiers liens à apparaître sur Google. Matplotlib 2.2.2 permet d’atteindre cet objectif à l’aide de la fonction gridspec. Dans l'exemple ci-dessous, l'objectif est de disposer de quatre sous-parcelles disposées 2x2 avec la légende en bas. Un "faux" axe est créé en bas pour placer la légende à un endroit fixe. L'axe 'faux' est alors désactivé, ainsi seule la légende s'affiche. Résultat: 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()
Cette réponse est un complément à @ Evert sur la position de la légende.
Mon premier essai sur la solution de @ Evert a échoué en raison du chevauchement de la légende et du titre de la sous-parcelle.
En fait, les chevauchements sont causés par fig.tight_layout()
, qui modifie la disposition des sous-parcelles sans tenir compte de la légende de la figure. Cependant, fig.tight_layout()
est nécessaire.
Afin d'éviter les chevauchements, nous pouvons dire à fig.tight_layout()
de laisser des espaces pour la légende de la figure de fig.tight_layout(rect=(0,0,1,0.9))
.
si vous utilisez des sous-parcelles avec des graphiques à barres, avec une couleur différente pour chaque barre. il peut être plus rapide de créer les artefacts vous-même en utilisant mpatches
Supposons que vous ayez quatre barres de couleurs différentes: r
m
c
k
vous pouvez définir la légende comme suit
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