matplotlib (mplot3d) - comment augmenter la taille d'un axe (stretch) dans un graphique 3D?

j'ai ceci:

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

Qui crée: enter image description here

ce que j'aimerais faire c'est étirer ça pour faire l'axe Z 9 fois plus haut et garder X et Y les mêmes. J'aimerais garder les mêmes coordonnées.

jusqu'à présent, j'ai essayé ce gars-là:

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

Mais que vient s'étend de la parcelle.Image png.

21
demandé sur Greg 2015-05-13 21:58:38

6 réponses

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

(Il existe 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

multipliée par (1, 2, 3):

Scale_x=1, Scale_y=2, Scale_z=3

Scale_x=1, Scale_y=1, Scale_z=3

Swap z and x, scale_x=4

ci-Dessous est une version plus courte 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()
19
répondu Christian Sarofeen 2015-05-30 13:15:51

veuillez noter que la réponse ci-dessous simplifie le patch, mais utilise le même principe sous-jacent que la réponse de @ChristianSarofeen.

Solution

comme déjà indiqué dans d'autres réponses, ce n'est pas une fonctionnalité qui est actuellement implémentée dans matplotlib. Cependant, puisque ce que vous demandez est simplement un transformation 3D qui peut être appliqué à la matrice de projection existante utilisée par matplotlib, et grâce à merveilleuses caractéristiques 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 vont redimensionner votre tracé le long de chacun des axes en conséquence. ax est simplement les axes 3D que l'on peut obtenir avec ax = fig.gca(projection='3d')

Explication

pour expliquer, la fonction get_projAxes3D génère la matrice de projection à partir de la position courante. Multipliant par une mise à l'échelle de la matrice:

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 client. Donc, ce que nous faisons ici c'est Remplacer l'original get_proj fonction avec une expression prenant le résultat de l'original get_proj et le multiplie 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, nous obtenir:

enter image description here

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

enter image description here

7
répondu Andrzej Pronobis 2015-05-25 17:12:42

je ressemble par défaut, mplot3d laissera un peu de place en haut et en bas d'une très haute de la parcelle. Mais, vous pouvez le piéger pour remplir cet espace en utilisant fig.subplots_adjust, et l'extension du haut et du bas hors de la zone de pointage normale (c.-à-d. top > 1 et bottom < 0). Certains d'essai et d'erreur ici est probablement nécessaire pour votre parcelle.

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

Vous pouvez également modifier ax.view_init pour établir un bel angle d'observation.

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

5
répondu tom 2015-05-19 13:49:21

on dirait que vous essayez d'ajuster l'échelle de l'intrigue. Je ne pense pas qu'il y ait un moyen de s'étirer!--8-->linéaire échelle selon les spécifications de l'utilisateur, mais vous pouvez utiliser set_yscale(),set_xscale(),set_zscale() 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 option probablement meilleure: spécifiez un stretch, définissez-les tous à symlog avec la même base de log puis spécifiez l'échelle symlog de l'axe Z avec linscalex/linscaley kwargs selon vos spécifications.

la suite ici:

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

2
répondu manglano 2015-05-19 02:06:52

j'ai trouvé ceci en cherchant sur un problème similaire. Après quelques expériences, je peux peut-être partager quelques-unes de mes découvertes préliminaires ici..la bibliothèque matplotlib est vaste!! (suis un nouveau venu). Notez que tout à fait semblable à cette question, tout ce que je voulais était "visuellement" étirer la carte sans la déformer.

histoire de Fond ( seuls les extraits de code clé sont affichés pour éviter le désordre inutile pour ceux qui connaissent la bibliothèque, et si vous voulez un code exécutable s'il Vous Plaît laisser tomber un commentaire): J'ai trois ndarrays 1-d représentant respectivement les points de données X,Y et Z. Clairement Je ne peux pas utiliser plot_surface (comme il nécessite ndarrays 2d pour chaque dim) donc j'ai choisi le 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

>>>> Z.) Bien que les points de l'intrigue soient corrects, je voulais quelque chose de plus "étiré" visuellement à tout le moins. Je cherchais une solution rapide, si je peux me permettre. Longue histoire courte, j'ai trouvé un peu de succès avec..."figure.Réglage général de 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 #
                              })

enter image description here

c'est pas mal!!

alors j'ai commencé à la pousser.... et s'est levé à [20,6] avant de décider de régler y..

enter image description here

si vous voulez essayer d'étirer visuellement l'axe vertical, essayez avec des rapports comme... [7,10], ce qui en l'espèce me donne ...

enter image description here

pas trop minable !

devrait le faire pour les prouesses visuelles.

0
répondu aaronlhe 2018-04-06 13:35:00

multipliez toutes vos valeurs de z par 9,

ax.scatter(x, y, 9*z, zdir='z', c= 'red')

et ensuite donner les étiquettes de tracé et l'espacement de l'axe z personnalisé.

ax.ZTick = [0,-9*50, -9*100, -9*150, -9*200];
ax.ZTickLabel = {'0','-50','-100','-150','-200'};
-2
répondu jmcmahon443 2015-05-19 22:14:20