J'ai passé trop de temps à chercher comment faire en sorte que deux sous-parcelles partagent le même axe des ordonnées avec une seule barre de couleur partagée par les deux dans Matplotlib.
Ce qui se passait, c’est que, lorsque j’ai appelé la fonction colorbar()
dans subplot1
ou subplot2
, le diagramme était automatiquement mis à l’échelle de sorte que la barre de couleur et le tracé s’intègrent dans le cadre de sélection de «sous-parcelle», ce qui entraîne deux tailles très différentes.
Pour contourner ce problème, j'ai essayé de créer une troisième sous-parcelle que j'ai ensuite piratée pour ne rendre aucun tracé avec juste une barre de couleur. Le seul problème est qu’à présent, les hauteurs et les largeurs des deux parcelles sont inégales et je ne vois pas comment le rendre correct.
Voici mon code:
from __future__ import division
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import patches
from matplotlib.ticker import NullFormatter
# SIS Functions
TE = 1 # Einstein radius
g1 = lambda x,y: (TE/2) * (y**2-x**2)/((x**2+y**2)**(3/2))
g2 = lambda x,y: -1*TE*x*y / ((x**2+y**2)**(3/2))
kappa = lambda x,y: TE / (2*np.sqrt(x**2+y**2))
coords = np.linspace(-2,2,400)
X,Y = np.meshgrid(coords,coords)
g1out = g1(X,Y)
g2out = g2(X,Y)
kappaout = kappa(X,Y)
for i in range(len(coords)):
for j in range(len(coords)):
if np.sqrt(coords[i]**2+coords[j]**2) <= TE:
g1out[i][j]=0
g2out[i][j]=0
fig = plt.figure()
fig.subplots_adjust(wspace=0,hspace=0)
# subplot number 1
ax1 = fig.add_subplot(1,2,1,aspect='equal',xlim=[-2,2],ylim=[-2,2])
plt.title(r"$\gamma_{1}$",fontsize="18")
plt.xlabel(r"x ($\theta_{E}$)",fontsize="15")
plt.ylabel(r"y ($\theta_{E}$)",rotation='horizontal',fontsize="15")
plt.xticks([-2.0,-1.5,-1.0,-0.5,0,0.5,1.0,1.5])
plt.xticks([-2.0,-1.5,-1.0,-0.5,0,0.5,1.0,1.5])
plt.imshow(g1out,extent=(-2,2,-2,2))
plt.axhline(y=0,linewidth=2,color='k',linestyle="--")
plt.axvline(x=0,linewidth=2,color='k',linestyle="--")
e1 = patches.Ellipse((0,0),2,2,color='white')
ax1.add_patch(e1)
# subplot number 2
ax2 = fig.add_subplot(1,2,2,sharey=ax1,xlim=[-2,2],ylim=[-2,2])
plt.title(r"$\gamma_{2}$",fontsize="18")
plt.xlabel(r"x ($\theta_{E}$)",fontsize="15")
ax2.yaxis.set_major_formatter( NullFormatter() )
plt.axhline(y=0,linewidth=2,color='k',linestyle="--")
plt.axvline(x=0,linewidth=2,color='k',linestyle="--")
plt.imshow(g2out,extent=(-2,2,-2,2))
e2 = patches.Ellipse((0,0),2,2,color='white')
ax2.add_patch(e2)
# subplot for colorbar
ax3 = fig.add_subplot(1,1,1)
ax3.axis('off')
cbar = plt.colorbar(ax=ax2)
plt.show()
Placez simplement la barre de couleur dans son propre axe et utilisez subplots_adjust
pour lui faire de la place.
Comme exemple rapide:
import numpy as np
import matplotlib.pyplot as plt
fig, axes = plt.subplots(nrows=2, ncols=2)
for ax in axes.flat:
im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1)
fig.subplots_adjust(right=0.8)
cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7])
fig.colorbar(im, cax=cbar_ax)
plt.show()
Vous pouvez simplifier le code de Joe Kington en utilisant le paramètre ax
de figure.colorbar()
avec une liste d'axes . À partir de la documentation :
hache
Aucun | objet (s) d'axes parents à partir duquel l'espace pour un nouvel axe de barre de couleurs sera volé. Si une liste d'axes est donnée, ils seront tous redimensionnés pour laisser de la place aux axes de la barre de couleur.
import numpy as np
import matplotlib.pyplot as plt
fig, axes = plt.subplots(nrows=2, ncols=2)
for ax in axes.flat:
im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1)
fig.colorbar(im, ax=axes.ravel().tolist())
plt.show()
Utiliser make_axes
est encore plus facile et donne un meilleur résultat. Il offre également la possibilité de personnaliser le positionnement de la barre de couleur ..__ Notez également l'option subplots
de partager les axes x et y.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
fig, axes = plt.subplots(nrows=2, ncols=2, sharex=True, sharey=True)
for ax in axes.flat:
im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1)
cax,kw = mpl.colorbar.make_axes([ax for ax in axes.flat])
plt.colorbar(im, cax=cax, **kw)
plt.show()
Cette solution ne nécessite pas de réglage manuel des emplacements des axes ou de la taille de la barre de couleur, fonctionne avec les dispositions multi-lignes et à une seule ligne et avec tight_layout()
. Il est adapté d'un exemple de galerie , en utilisant ImageGrid
de la boîte à outils AxesGrid de matplotlib .
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import ImageGrid
# Set up figure and image grid
fig = plt.figure(figsize=(9.75, 3))
grid = ImageGrid(fig, 111, # as in plt.subplot(111)
nrows_ncols=(1,3),
axes_pad=0.15,
share_all=True,
cbar_location="right",
cbar_mode="single",
cbar_size="7%",
cbar_pad=0.15,
)
# Add data to image grid
for ax in grid:
im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1)
# Colorbar
ax.cax.colorbar(im)
ax.cax.toggle_label(True)
#plt.tight_layout() # Works, but may still require rect paramater to keep colorbar labels visible
plt.show()
Comme indiqué dans d'autres réponses, il s'agit généralement de définir un axe dans lequel la barre de couleur doit résider. Il existe différentes façons de le faire; Un exemple qui n’a pas encore été mentionné consisterait à spécifier directement les axes de la barre de couleur lors de la création d’une sous-parcelle avec plt.subplots()
. L'avantage est que la position des axes n'a pas besoin d'être définie manuellement et que dans tous les cas d'aspect automatique, la barre de couleur aura exactement la même hauteur que les sous-graphes. Même dans de nombreux cas où des images sont utilisées, le résultat sera satisfaisant, comme indiqué ci-dessous.
Lors de l'utilisation de plt.subplots()
, l'utilisation de l'argument gridspec_kw
permet de rendre les axes de la barre de couleur beaucoup plus petits que les autres axes.
fig, (ax, ax2, cax) = plt.subplots(ncols=3,figsize=(5.5,3),
gridspec_kw={"width_ratios":[1,1, 0.05]})
Exemple:
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(1)
fig, (ax, ax2, cax) = plt.subplots(ncols=3,figsize=(5.5,3),
gridspec_kw={"width_ratios":[1,1, 0.05]})
fig.subplots_adjust(wspace=0.3)
im = ax.imshow(np.random.Rand(11,8), vmin=0, vmax=1)
im2 = ax2.imshow(np.random.Rand(11,8), vmin=0, vmax=1)
ax.set_ylabel("y label")
fig.colorbar(im, cax=cax)
plt.show()
Cela fonctionne bien si l'aspect des tracés est automatiquement calibré ou si les images sont réduites en raison de leur aspect dans le sens de la largeur (comme ci-dessus). Cependant, si les images sont plus larges que hautes, le résultat serait le suivant, ce qui pourrait être indésirable.
Une solution pour fixer la hauteur de la barre de couleur à la hauteur de la sous-parcelle consisterait à utiliser mpl_toolkits.axes_grid1.inset_locator.InsetPosition
pour définir les axes de la barre de couleur par rapport aux axes de la sous-parcelle de l'image.
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(1)
from mpl_toolkits.axes_grid1.inset_locator import InsetPosition
fig, (ax, ax2, cax) = plt.subplots(ncols=3,figsize=(7,3),
gridspec_kw={"width_ratios":[1,1, 0.05]})
fig.subplots_adjust(wspace=0.3)
im = ax.imshow(np.random.Rand(11,16), vmin=0, vmax=1)
im2 = ax2.imshow(np.random.Rand(11,16), vmin=0, vmax=1)
ax.set_ylabel("y label")
ip = InsetPosition(ax2, [1.05,0,0.05,1])
cax.set_axes_locator(ip)
fig.colorbar(im, cax=cax, ax=[ax,ax2])
plt.show()
En tant que débutant qui a trébuché sur ce fil, je voudrais ajouter une adaptation très nette de abevieiramota de python pour les nuls (parce que je suis au niveau où je devais regarder 'ravel' pour savoir ce que faisait leur code):
import numpy as np
import matplotlib.pyplot as plt
fig, ((ax1,ax2,ax3),(ax4,ax5,ax6)) = plt.subplots(2,3)
axlist = [ax1,ax2,ax3,ax4,ax5,ax6]
first = ax1.imshow(np.random.random((10,10)), vmin=0, vmax=1)
third = ax3.imshow(np.random.random((12,12)), vmin=0, vmax=1)
fig.colorbar(first, ax=axlist)
plt.show()
Beaucoup moins Pythonic, beaucoup plus facile pour les noobs comme moi de voir ce qui se passe réellement ici.
La solution consistant à utiliser une liste d'axes de abevieiramota fonctionne très bien jusqu'à ce que vous n'utilisiez qu'une seule ligne d'images, comme indiqué dans les commentaires. L'utilisation d'un format d'image raisonnable pour figsize
aide, mais est encore loin d'être parfaite. Par exemple:
import numpy as np
import matplotlib.pyplot as plt
fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(9.75, 3))
for ax in axes.flat:
im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1)
fig.colorbar(im, ax=axes.ravel().tolist())
plt.show()
La fonction colorbar fournit le paramètre shrink
, qui est un facteur de mise à l'échelle de la taille des axes de la barre de couleur. Cela nécessite quelques essais et erreurs manuels. Par exemple:
fig.colorbar(im, ax=axes.ravel().tolist(), shrink=0.75)
J'ai remarqué que presque chaque solution publiée impliquait ax.imshow(im, ...)
et ne normalisait pas les couleurs affichées dans la barre de couleurs pour les multiples sous-personnages. La mappable im
est extraite de la dernière instance, mais que se passe-t-il si les valeurs de la variable im
- s sont différentes? (Je suppose que ces mappables sont traités de la même manière que les jeux de contours et les jeux de surfaces.) J'ai un exemple utilisant un tracé de surface 3D ci-dessous qui crée deux barres de couleur pour une sous-parcelle 2x2 (une ). Bien que la question demande explicitement un arrangement différent, je pense que l'exemple aide à clarifier certaines choses. Malheureusement, je n'ai pas encore trouvé le moyen d'utiliser plt.subplots(...)
, à cause des axes 3D.
Si seulement je pouvais positionner mieux les barres de couleur ... (Il existe probablement une bien meilleure façon de le faire, mais au moins, cela ne devrait pas être trop difficile à suivre.)
import matplotlib
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
cmap = 'plasma'
ncontours = 5
def get_data(row, col):
""" get X, Y, Z, and plot number of subplot
Z > 0 for top row, Z < 0 for bottom row """
if row == 0:
x = np.linspace(1, 10, 10, dtype=int)
X, Y = np.meshgrid(x, x)
Z = np.sqrt(X**2 + Y**2)
if col == 0:
pnum = 1
else:
pnum = 2
Elif row == 1:
x = np.linspace(1, 10, 10, dtype=int)
X, Y = np.meshgrid(x, x)
Z = -np.sqrt(X**2 + Y**2)
if col == 0:
pnum = 3
else:
pnum = 4
print("\nPNUM: {}, Zmin = {}, Zmax = {}\n".format(pnum, np.min(Z), np.max(Z)))
return X, Y, Z, pnum
fig = plt.figure()
nrows, ncols = 2, 2
zz = []
axes = []
for row in range(nrows):
for col in range(ncols):
X, Y, Z, pnum = get_data(row, col)
ax = fig.add_subplot(nrows, ncols, pnum, projection='3d')
ax.set_title('row = {}, col = {}'.format(row, col))
fhandle = ax.plot_surface(X, Y, Z, cmap=cmap)
zz.append(Z)
axes.append(ax)
## get full range of Z data as flat list for top and bottom rows
zz_top = zz[0].reshape(-1).tolist() + zz[1].reshape(-1).tolist()
zz_btm = zz[2].reshape(-1).tolist() + zz[3].reshape(-1).tolist()
## get top and bottom axes
ax_top = [axes[0], axes[1]]
ax_btm = [axes[2], axes[3]]
## normalize colors to minimum and maximum values of dataset
norm_top = matplotlib.colors.Normalize(vmin=min(zz_top), vmax=max(zz_top))
norm_btm = matplotlib.colors.Normalize(vmin=min(zz_btm), vmax=max(zz_btm))
cmap = cm.get_cmap(cmap, ncontours) # number of colors on colorbar
mtop = cm.ScalarMappable(cmap=cmap, norm=norm_top)
mbtm = cm.ScalarMappable(cmap=cmap, norm=norm_btm)
for m in (mtop, mbtm):
m.set_array([])
# ## create cax to draw colorbar in
# cax_top = fig.add_axes([0.9, 0.55, 0.05, 0.4])
# cax_btm = fig.add_axes([0.9, 0.05, 0.05, 0.4])
cbar_top = fig.colorbar(mtop, ax=ax_top, orientation='vertical', shrink=0.75, pad=0.2) #, cax=cax_top)
cbar_top.set_ticks(np.linspace(min(zz_top), max(zz_top), ncontours))
cbar_btm = fig.colorbar(mbtm, ax=ax_btm, orientation='vertical', shrink=0.75, pad=0.2) #, cax=cax_btm)
cbar_btm.set_ticks(np.linspace(min(zz_btm), max(zz_btm), ncontours))
plt.show()
plt.close(fig)
## orientation of colorbar = 'horizontal' if done by column
Pour ajouter à l'excellente réponse de @ abevieiramota, vous pouvez obtenir le équivalent de tight_layout avec constrained_layout. Vous aurez toujours de grands espaces horizontaux si vous utilisez imshow
au lieu de pcolormesh
en raison du format d'image 1: 1 imposé par imshow
.
import numpy as np
import matplotlib.pyplot as plt
fig, axes = plt.subplots(nrows=2, ncols=2, constrained_layout=True)
for ax in axes.flat:
im = ax.pcolormesh(np.random.random((10,10)), vmin=0, vmax=1)
fig.colorbar(im, ax=axes.flat)
plt.show()