web-dev-qa-db-fra.com

comment créer une seule légende pour de nombreuses sous-parcelles avec matplotlib?

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?

125
pocketfullofcheese

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 )
101
user707650

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')
83
Ben Usman

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

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

15

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 ...

enter image description here

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é? ;-)

3
gboffi

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

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

Description des paramètres tight_layout () .

0
laven_qa

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: rmck 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
0
Notable1