web-dev-qa-db-fra.com

matplotlib (mplot3d) - Comment augmenter la taille d'un axe (stretch) dans un tracé 3D?

J'ai ceci jusqu'à présent:

x,y,z = data.nonzero()    
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x, y, z, zdir='z', c= 'red')
plt.savefig("plot.png")

Ce qui crée: enter image description here

Ce que j'aimerais faire, c’est d’étirer cela pour que l’axe Z soit 9 fois plus grand et que X et Y restent identiques. J'aimerais cependant garder les mêmes coordonnées.

Jusqu'ici j'ai essayé ce gars:

fig = plt.figure(figsize=(4.,35.))

Mais cela ne fait qu'étendre l'image plot.png. 

21
Greg

L'exemple de code ci-dessous fournit un moyen de redimensionner chaque axe par rapport aux autres. Toutefois, pour ce faire, vous devez modifier la fonction Axes3D.get_proj. Voici un exemple basé sur l'exemple fourni par matplot lib: http://matplotlib.org/1.4.0/mpl_toolkits/mplot3d/tutorial.html#line-plots

(Il y a une version plus courte à la fin de cette réponse)

from mpl_toolkits.mplot3d.axes3d import Axes3D
from mpl_toolkits.mplot3d import proj3d

import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt

#Make sure these are floating point values:                                                                                                                                                                                              
scale_x = 1.0
scale_y = 2.0
scale_z = 3.0

#Axes are scaled down to fit in scene                                                                                                                                                                                                    
max_scale=max(scale_x, scale_y, scale_z)

scale_x=scale_x/max_scale
scale_y=scale_y/max_scale
scale_z=scale_z/max_scale

#Create scaling matrix                                                                                                                                                                                                                   
scale = np.array([[scale_x,0,0,0],
                  [0,scale_y,0,0],
                  [0,0,scale_z,0],
                  [0,0,0,1]])
print scale

def get_proj_scale(self):
    """                                                                                                                                                                                                                                    
    Create the projection matrix from the current viewing position.                                                                                                                                                                        

    elev stores the elevation angle in the z plane                                                                                                                                                                                         
    azim stores the azimuth angle in the x,y plane                                                                                                                                                                                         

    dist is the distance of the eye viewing point from the object                                                                                                                                                                          
    point.                                                                                                                                                                                                                                 

    """
    relev, razim = np.pi * self.elev/180, np.pi * self.azim/180

    xmin, xmax = self.get_xlim3d()
    ymin, ymax = self.get_ylim3d()
    zmin, zmax = self.get_zlim3d()

    # transform to uniform world coordinates 0-1.0,0-1.0,0-1.0                                                                                                                                                                             
    worldM = proj3d.world_transformation(
        xmin, xmax,
        ymin, ymax,
        zmin, zmax)

    # look into the middle of the new coordinates                                                                                                                                                                                          
    R = np.array([0.5, 0.5, 0.5])

    xp = R[0] + np.cos(razim) * np.cos(relev) * self.dist
    yp = R[1] + np.sin(razim) * np.cos(relev) * self.dist
    zp = R[2] + np.sin(relev) * self.dist
    E = np.array((xp, yp, zp))

    self.eye = E
    self.vvec = R - E
    self.vvec = self.vvec / proj3d.mod(self.vvec)

    if abs(relev) > np.pi/2:
    # upside down                                                                                                                                                                                                                          
      V = np.array((0, 0, -1))
    else:
      V = np.array((0, 0, 1))
    zfront, zback = -self.dist, self.dist

    viewM = proj3d.view_transformation(E, R, V)
    perspM = proj3d.persp_transformation(zfront, zback)
    M0 = np.dot(viewM, worldM)
    M = np.dot(perspM, M0)

    return np.dot(M, scale);

Axes3D.get_proj=get_proj_scale

"""
You need to include all the code above.
From here on you should be able to plot as usual.
"""

mpl.rcParams['legend.fontsize'] = 10

fig = plt.figure(figsize=(5,5))
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
ax.plot(x, y, z, label='parametric curve')
ax.legend()

plt.show()

Sortie standard:

Normal Scale

Échelle de (1, 2, 3):

Scale_x=1, Scale_y=2, Scale_z=3

Mise à l'échelle de (1, 1, 3):

Scale_x=1, Scale_y=1, Scale_z=3

La raison pour laquelle j'aime particulièrement cette méthode, Échangez z et x, mettez à l'échelle par (3, 1, 1):

Swap z and x, scale_x=4

Vous trouverez ci-dessous une version abrégée du code.

from mpl_toolkits.mplot3d.axes3d import Axes3D
from mpl_toolkits.mplot3d import proj3d

import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt

mpl.rcParams['legend.fontsize'] = 10

fig = plt.figure(figsize=(5,5))
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)


"""                                                                                                                                                    
Scaling is done from here...                                                                                                                           
"""
x_scale=1
y_scale=1
z_scale=2

scale=np.diag([x_scale, y_scale, z_scale, 1.0])
scale=scale*(1.0/scale.max())
scale[3,3]=1.0

def short_proj():
  return np.dot(Axes3D.get_proj(ax), scale)

ax.get_proj=short_proj
"""                                                                                                                                                    
to here                                                                                                                                                
"""

ax.plot(z, y, x, label='parametric curve')
ax.legend()

plt.show()
20

Veuillez noter que la réponse ci-dessous simplifie le correctif, mais utilise le même principe de base que la réponse de @ChristianSarofeen.

Solution

Comme déjà indiqué dans d'autres réponses, ce n'est pas une fonctionnalité actuellement implémentée dans matplotlib. Cependant, puisque vous demandez simplement une transformation 3D qui peut être appliquée à la matrice de projection existante utilisée par matplotlib, et grâce aux merveilleuses fonctionnalités de Python, ce problème peut être résolu avec un simple oneliner :

ax.get_proj = lambda: np.dot(Axes3D.get_proj(ax), np.diag([scale_x, scale_y, scale_z, 1]))

scale_x, scale_y et scale_z sont des valeurs de 0 à 1 qui redimensionnent votre tracé le long de chacun des axes en conséquence. ax sont simplement les axes 3D que l'on peut obtenir avec ax = fig.gca(projection='3d')

Explication

Pour expliquer, la fonction get_proj de Axes3D génère la matrice de projection à partir de la position de visualisation actuelle. En le multipliant par une matrice de mise à l'échelle:

scale_x, 0,       0
0,       scale_y, 0
0,       0,       scale_z
0,       0,       1

inclut la mise à l'échelle dans la projection utilisée par le rendu. Donc, ce que nous faisons ici, c'est substituer la fonction get_proj originale avec une expression prenant le résultat du get_proj original et le multipliant par la matrice de mise à l'échelle. 

Exemple

Pour illustrer le résultat avec l'exemple de fonction paramétrique standard:

from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z ** 2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)

# OUR ONE LINER ADDED HERE:
ax.get_proj = lambda: np.dot(Axes3D.get_proj(ax), np.diag([0.5, 0.5, 1, 1]))

ax.plot(x, y, z)
plt.show()

pour les valeurs 0.5, 0.5, 1, on obtient:

enter image description here

alors que pour les valeurs 0.2, 1.0, 0.2, on obtient:

enter image description here

10
Andrzej Pronobis

J'ai l’impression que par défaut, mplot3d laissera un peu de place en haut et en bas d’un très grand complot. Mais vous pouvez le tromper en remplissant cet espace en utilisant fig.subplots_adjust et en prolongeant le haut et le bas hors de la zone de traçage normale (c'est-à-dire top > 1 et bottom < 0). Quelques essais et erreurs ici sont probablement nécessaires pour votre intrigue particulière. 

J'ai créé des tableaux aléatoires pour x, y et z avec des limites similaires à celles de votre graphique, et les paramètres ci-dessous (bottom=-0.15, top = 1.2) semblent fonctionner correctement.

Vous pouvez également vouloir changer ax.view_init pour définir un angle de vision de Nice.

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from numpy import random

# Make some random data with similar limits to the OP's example
x,y,z=random.Rand(3,100)
z*=250
y*=800
y+=900
x*=350
x+=1200

fig=plt.figure(figsize=(4,35))

# Set the bottom and top outside the actual figure limits, 
# to stretch the 3D axis
fig.subplots_adjust(bottom=-0.15,top=1.2)

ax = fig.add_subplot(111, projection='3d')

# Change the viewing angle to an agreeable one
ax.view_init(2,None)

ax.scatter(x, y, z, zdir='z', c= 'red')
plt.savefig("plot.png")

6
tmdavison

On dirait que vous essayez d'ajuster l'échelle de l'intrigue. Je ne pense pas qu'il soit possible d'étirer une échelle linear conformément aux spécifications de l'utilisateur, mais vous pouvez utiliser set_yscale(), set_xscale(), set_zscale() pour modifier les échelles les unes par rapport aux autres.

Intuitivement, set_yscale(log), set_xscale(log), set_zscale(linear) pourrait résoudre vos problèmes. 

Une meilleure option est probablement la suivante: spécifiez une extension, réglez-les tous sur symlog avec la même base de journal, puis spécifiez l'échelle de symlog de l'axe Z avec le kwargs linscalex/linscaley selon vos spécifications.

Plus ici:

http://matplotlib.org/mpl_toolkits/mplot3d/api.html

2
manglano

J'ai trouvé cela en cherchant un problème similaire. Après avoir essayé un peu, je peux peut-être partager certaines de mes découvertes préliminaires ici .. La bibliothèque matplotlib est VAST !! (je suis un nouveau venu). Notez que tout à fait à côté de cette question, tout ce que je voulais, c’était d’étirer «visuellement» le graphique sans le déformer.

Article de fond (seuls des extraits de code sont affichés pour éviter toute confusion inutile pour ceux qui connaissent la bibliothèque. Si vous voulez un code pouvant être exécuté, veuillez laisser un commentaire): J'ai trois ndarrays à une dimension représentant les points de données X, Y et Z respectivement. Il est clair que je ne peux pas utiliser plot_surface (car il nécessite 2d ndarrays pour chaque dim), je me suis donc tourné vers l'intrigue très utile plot_trisurf:

fig = plt.figure()
ax = Axes3D(fig)
3d_surf_obj = ax.plot_trisurf(X, Y, Z_defl, cmap=cm.jet,linewidth=0,antialiased=True)

 enter image description here

Vous pouvez voir l’intrigue comme une barge flottante se déformant par vagues ... Comme vous pouvez le constater, les axes étirés la rendent assez trompeuse visuellement (notez que x est censé être x6 fois plus long que y et >>>>> z ). Bien que les points de l'intrigue soient corrects, je voulais au moins quelque chose de plus étiré visuellement. Je cherchais un correctif rapide, si je peux. Longue histoire courte, j'ai trouvé un peu de succès avec ... le réglage général 'figure.figsize' (voir l'extrait ci-dessous). 

    matplotlib.rcParams.update({'font.serif': 'Times New Roman',
                                'font.size': 10.0,
                                'axes.labelsize': 'Medium',
                                'axes.labelweight': 'normal',
                                'axes.linewidth': 0.8,
                                 ###########################################
                                 # THIS IS THE IMPORTANT ONE FOR STRETCHING
                                 # default is [6,4] but...i changed it to
                                'figure.figsize':[15,5]   # THIS ONE #
                              })

Pour [15,5] j'ai quelque chose comme ...

 enter image description here

Génial!! 

Alors j'ai commencé à pousser ... et je suis monté à [20,6] avant de décider de m'installer là-bas ..

 enter image description here

Si vous voulez essayer d'étirer visuellement l'axe vertical, essayez avec des ratios comme ... [7,10], ce qui dans ce cas me donne ... 

 enter image description here

Pas trop mal ! 

Devrait le faire pour les prouesses visuelles. 

0
aaronlhe