Comment puis-je définir le rapport hauteur / largeur dans matplotlib?

108

J'essaie de faire un tracé carré (en utilisant imshow), c'est-à-dire un rapport hauteur / largeur de 1: 1, mais je ne peux pas. Aucun de ces travaux:

import matplotlib.pyplot as plt

ax = fig.add_subplot(111,aspect='equal')
ax = fig.add_subplot(111,aspect=1.0)
ax.set_aspect('equal')
plt.axes().set_aspect('equal')

Il semble que les appels soient simplement ignorés (un problème que je semble souvent avoir avec matplotlib).

jtlz2
la source
6
Avez-vous essayé ax.axis('equal'), par hasard? Comme tout le monde l'a dit, ce que vous avez fait devrait fonctionner, mais ax.axispourrait être un autre moyen d'essayer une solution de contournement.
Joe Kington

Réponses:

77

Troisième fois le charme. Je suppose que c'est un bogue et la réponse de Zhenya suggère qu'il est corrigé dans la dernière version. J'ai la version 0.99.1.1 et j'ai créé la solution suivante:

import matplotlib.pyplot as plt
import numpy as np

def forceAspect(ax,aspect=1):
    im = ax.get_images()
    extent =  im[0].get_extent()
    ax.set_aspect(abs((extent[1]-extent[0])/(extent[3]-extent[2]))/aspect)

data = np.random.rand(10,20)

fig = plt.figure()
ax = fig.add_subplot(111)
ax.imshow(data)
ax.set_xlabel('xlabel')
ax.set_aspect(2)
fig.savefig('equal.png')
ax.set_aspect('auto')
fig.savefig('auto.png')
forceAspect(ax,aspect=1)
fig.savefig('force.png')

C'est 'force.png': entrez la description de l'image ici

Voici mes tentatives infructueuses, mais j'espère informatives.

Deuxième réponse:

Ma «réponse originale» ci-dessous est exagérée, car elle fait quelque chose de similaire à axes.set_aspect(). Je pense que vous voulez utiliser axes.set_aspect('auto'). Je ne comprends pas pourquoi c'est le cas, mais cela produit un tracé d'image carrée pour moi, par exemple ce script:

import matplotlib.pyplot as plt
import numpy as np

data = np.random.rand(10,20)

fig = plt.figure()
ax = fig.add_subplot(111)
ax.imshow(data)
ax.set_aspect('equal')
fig.savefig('equal.png')
ax.set_aspect('auto')
fig.savefig('auto.png')

Produit un tracé d'image avec un rapport hauteur / largeur 'égal': entrez la description de l'image ici et un avec un rapport hauteur / largeur 'auto': entrez la description de l'image ici

Le code fourni ci-dessous dans la «réponse originale» fournit un point de départ pour un rapport hauteur / largeur explicitement contrôlé, mais il semble être ignoré une fois qu'un imshow est appelé.

Réponse originale:

Voici un exemple de routine qui ajustera les paramètres du sous-tracé afin que vous obteniez le rapport hauteur / largeur souhaité:

import matplotlib.pyplot as plt

def adjustFigAspect(fig,aspect=1):
    '''
    Adjust the subplot parameters so that the figure has the correct
    aspect ratio.
    '''
    xsize,ysize = fig.get_size_inches()
    minsize = min(xsize,ysize)
    xlim = .4*minsize/xsize
    ylim = .4*minsize/ysize
    if aspect < 1:
        xlim *= aspect
    else:
        ylim /= aspect
    fig.subplots_adjust(left=.5-xlim,
                        right=.5+xlim,
                        bottom=.5-ylim,
                        top=.5+ylim)

fig = plt.figure()
adjustFigAspect(fig,aspect=.5)
ax = fig.add_subplot(111)
ax.plot(range(10),range(10))

fig.savefig('axAspect.png')

Cela produit un chiffre comme celui-ci: entrez la description de l'image ici

Je peux imaginer que si vous avez plusieurs sous-graphiques dans la figure, vous voudriez inclure le nombre de sous-graphiques y et x en tant que paramètres de mot-clé (par défaut à 1 chacun) à la routine fournie. Ensuite, en utilisant ces nombres et les mots hspace- wspaceclés et , vous pouvez faire en sorte que tous les sous-graphiques aient le bon rapport hauteur / largeur.

Yann
la source
1
Pour les cas où il get_imagesy a une liste vide (comme cela se passerait avec ax.plot([0,1],[0,2]), vous pouvez utiliser get_xlimetget_ylim
Joel
Il me semble que cela ne fonctionnera pas si cela est fait avec logscale. J'ai ajouté une réponse qui teste cela et la gère. N'hésitez pas à intégrer cela dans votre réponse et je supprimerai la mienne.
Joel
1
La raison pour laquelle l'aspect semble inégal est que l'aspect égal signifie que la distance visuelle en x sera la même que y. Si l'image est carrée, mais que les dx et dy du tracé sont différents, il ne s'agit pas d'un rapport hauteur / largeur de 1: 1. Le rapport hauteur / largeur sera dy / dx dans ce cas.
bart cubrich
22

Quelle est la matplotlibversion que vous utilisez? J'ai récemment dû passer à 1.1.0, et avec cela, cela add_subplot(111,aspect='equal')fonctionne pour moi.

ev-br
la source
1
Fonctionne bien pour moi en matplotlibversion 2.0.2. jupyter notebookversion 5.0.0. Merci.
Sathish
5

Après de nombreuses années de succès avec les réponses ci-dessus, j'ai trouvé que cela ne fonctionnait plus - mais j'ai trouvé une solution de travail pour les sous-parcelles à

https://jdhao.github.io/2017/06/03/change-aspect-ratio-in-mpl

Avec tout le crédit bien sûr à l'auteur ci-dessus (qui peut peut-être plutôt poster ici), les lignes pertinentes sont:

ratio = 1.0
xleft, xright = ax.get_xlim()
ybottom, ytop = ax.get_ylim()
ax.set_aspect(abs((xright-xleft)/(ybottom-ytop))*ratio)

Le lien a également une explication claire des différents systèmes de coordonnées utilisés par matplotlib.

Merci pour toutes les bonnes réponses reçues - en particulier @ Yann's qui restera le gagnant.

jtlz2
la source
3

vous devriez essayer avec figaspect. Ça marche pour moi. À partir de la documentation:

Créez une figure avec un rapport hauteur / largeur spécifié. Si arg est un nombre, utilisez ce rapport hauteur / largeur. > Si arg est un tableau, figaspect déterminera la largeur et la hauteur d'une figure qui s'adapterait au tableau en préservant les proportions. La largeur et la hauteur de la figure en pouces sont renvoyées. Assurez-vous de créer des axes égaux à et hauteur, par exemple

Exemple d'utilisation:

  # make a figure twice as tall as it is wide
  w, h = figaspect(2.)
  fig = Figure(figsize=(w,h))
  ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
  ax.imshow(A, **kwargs)

  # make a figure with the proper aspect for an array
  A = rand(5,3)
  w, h = figaspect(A)
  fig = Figure(figsize=(w,h))
  ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
  ax.imshow(A, **kwargs)

Edit: Je ne suis pas sûr de ce que vous recherchez. Le code ci-dessus modifie le canevas (la taille du tracé). Si vous souhaitez modifier la taille de la fenêtre matplotlib, de la figure, utilisez:

In [68]: f = figure(figsize=(5,1))

cela produit une fenêtre de 5x1 (lxh).

Joaquin
la source
Merci pour cela - cela a un effet, en changeant le rapport hauteur / largeur de la toile: Pour être plus précis, je dois changer le rapport hauteur / largeur de la figure elle-même, ce qui ne fait pas ce qui suit (formatage apols ..): fig = plt.figure (figsize = (plt.figaspect (2.0)))
jtlz2
3

Cette réponse est basée sur la réponse de Yann. Il définira le rapport hauteur / largeur pour les graphiques linéaires ou log-log. J'ai utilisé des informations supplémentaires de https://stackoverflow.com/a/16290035/2966723 pour tester si les axes sont à l'échelle logarithmique.

def forceAspect(ax,aspect=1):
    #aspect is width/height
    scale_str = ax.get_yaxis().get_scale()
    xmin,xmax = ax.get_xlim()
    ymin,ymax = ax.get_ylim()
    if scale_str=='linear':
        asp = abs((xmax-xmin)/(ymax-ymin))/aspect
    elif scale_str=='log':
        asp = abs((scipy.log(xmax)-scipy.log(xmin))/(scipy.log(ymax)-scipy.log(ymin)))/aspect
    ax.set_aspect(asp)

De toute évidence, vous pouvez utiliser n'importe quelle version de logvotre choix, que j'ai utilisée scipy, mais numpyou mathdevrait être bien.

Joël
la source