matplotlib (mplot3d): ¿cómo boost el tamaño de un eje (estiramiento) en un gráfico 3D?

Tengo esto hasta ahora:

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

Lo que crea: introduzca la descripción de la imagen aquí

Lo que me gustaría hacer es estirar esto para hacer que el eje Z 9 veces más alto y mantener X e Y iguales. Aunque me gustaría mantener las mismas coordenadas.

Hasta ahora he intentado este chico:

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

Pero eso solo extiende la imagen plot.png.

El ejemplo de código a continuación proporciona una manera de escalar cada eje en relación con los otros. Sin embargo, para hacerlo necesita modificar la función Axes3D.get_proj. A continuación se muestra un ejemplo basado en el ejemplo proporcionado por matplot lib: http://matplotlib.org/1.4.0/mpl_toolkits/mplot3d/tutorial.html#line-plots

(Hay una versión más corta al final de esta respuesta)

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

Salida estándar:

Escala normal

Escalado por (1, 2, 3):

Scale_x = 1, Scale_y = 2, Scale_z = 3

Escalado por (1, 1, 3):

Scale_x = 1, Scale_y = 1, Scale_z = 3

La razón por la que me gusta particularmente este método, Swap z y x, escala por (3, 1, 1):

Swap z y x, scale_x = 4

A continuación se muestra una versión más corta del código.

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

Tenga en cuenta que la respuesta a continuación simplifica el parche, pero utiliza el mismo principio subyacente que la respuesta de @ChristianSarofeen.

Solución

Como ya se indicó en otras respuestas, no es una función que se implementa actualmente en matplotlib. Sin embargo, dado que lo que solicita es simplemente una transformación 3D que puede aplicarse a la matriz de proyección existente utilizada por matplotlib, y gracias a las maravillosas características de Python, este problema se puede resolver con un simple oneliner :

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

donde scale_x , scale_y y scale_z son valores de 0 a 1 que ajustarán su escala a lo largo de cada uno de los ejes en consecuencia. ax es simplemente los ejes 3D que se pueden obtener con ax = fig.gca(projection='3d')

Explicación

Para explicar, la función get_proj de Axes3D genera la matriz de proyección desde la posición de visualización actual. Multiplicándolo por una matriz de escalado:

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

Incluye la escala en la proyección utilizada por el renderizador. Entonces, lo que estamos haciendo aquí es sustituir la función original get_proj con una expresión que toma el resultado del original get_proj y lo multiplica por la matriz de escalado.

Ejemplo

Para ilustrar el resultado con el ejemplo de la función paramétrica estándar:

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

para los valores 0.5, 0.5, 1 , obtenemos:

introduzca la descripción de la imagen aquí

mientras que para los valores 0.2, 1.0, 0.2 , obtenemos:

introduzca la descripción de la imagen aquí

Parece que por defecto, mplot3d dejará un poco de espacio en la parte superior e inferior de una ttwig muy alta. Pero puede engañarlo para que llene ese espacio usando fig.subplots_adjust y extendiendo la parte superior e inferior fuera del área de trazado normal (es decir, top > 1 y bottom < 0 ). Es probable que necesite un poco de prueba y error para su ttwig particular.

He creado algunas matrices aleatorias para x, y, yz con límites similares a su ttwig, y ​​he encontrado que los parámetros a continuación ( bottom=-0.15 , top = 1.2 ) parecen funcionar bien.

También es posible que desee cambiar ax.view_init para establecer un ángulo de visión agradable.

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

Parece que estás tratando de ajustar la escala de la ttwig. No creo que haya una manera de extender una escala lineal a las especificaciones del usuario, pero puede usar set_yscale() , set_xscale() , set_zscale() para alterar las escalas entre sí.

Intuitivamente, set_yscale(log) , set_xscale(log) , set_zscale(linear) pueden resolver sus problemas.

Una opción probablemente mejor: especifique un estiramiento, configúrelos todos en symlog con la misma base de registro y luego especifique la escala de symlog del eje Z con los linscalex/linscaley según sus especificaciones.

Más aquí:

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

Encontré esto mientras buscaba un problema similar. Después de experimentar un poco, tal vez pueda compartir algunos de mis hallazgos preliminares aquí … ¡la biblioteca de PLplotlib es VAST! (Soy un recién llegado). Tenga en cuenta que bastante parecido a esta pregunta, todo lo que quería era estirar visualmente el gráfico sin distorsionarlo.

Historia de fondo ( solo se muestran fragmentos de código clave para evitar el desorden innecesario para aquellos que conocen la biblioteca, y si desea un código ejecutable, deje un comentario ): tengo tres ndarrays 1-d que representan los datos de X, Y y Z puntos respectivamente. Claramente no puedo usar plot_surface (ya que requiere ndarrays 2d para cada atenuación), así que busqué el plot_trisurf extremadamente útil:

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

introduzca la descripción de la imagen aquí

Puede pensar en la ttwig como una barcaza flotante que se deforma en las olas … Como puede ver, los ejes se estiran lo hacen bastante engañoso visualmente (tenga en cuenta que x se supone que es x6 veces más largo que y y >>>>> z ). Mientras que los puntos de la ttwig son correctos, quería algo más visualmente “estirado” por lo menos. Estaba buscando una solución rápida, si puedo. Larga historia, corta, encontré un poco de éxito con la configuración general de ‘figure.figsize’ (vea el fragmento a continuación).

  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 # }) 

Para [15,5] tengo algo como …

introduzca la descripción de la imagen aquí

¡¡Con buena pinta!!

Así que empecé a presionar … y me levanté a [20,6] antes de decidirme a instalarme allí

introduzca la descripción de la imagen aquí

Si quieres intentar estirar visualmente el eje vertical, prueba con relaciones como … [7,10], que en este caso me da …

introduzca la descripción de la imagen aquí

No está nada mal !

Debería hacerlo por valor visual.

Multiplica todos tus valores z por 9,

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

Y a continuación, asigne las tags de trazado personalizado y el espaciado del eje z.

 ax.ZTick = [0,-9*50, -9*100, -9*150, -9*200]; ax.ZTickLabel = {'0','-50','-100','-150','-200'};