iury simoes-sousa
iury simoes-sousa

Reputation: 1590

Changing aspect ratio for 3D plots on Matplotlib

I am trying to set up the aspect ratio for 3D plots using Matplotlib. Following the answer for this question: Setting aspect ratio of 3D plot

I kind of applied the solution as:

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

def get_proj(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.
    """
    # chosen for similarity with the initial view before gh-8896

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

    #EDITED TO HAVE SCALED AXIS
    xmin, xmax = np.divide(self.get_xlim3d(), self.pbaspect[0])
    ymin, ymax = np.divide(self.get_ylim3d(), self.pbaspect[1])
    zmin, zmax = np.divide(self.get_zlim3d(), self.pbaspect[2])

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

    # look into the middle of the new coordinates
    R = self.pbaspect / 2

    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 / np.linalg.norm(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)
    projM = self._projection(zfront, zback)
    M0 = np.dot(viewM, worldM)
    M = np.dot(projM, M0)
    return M

Axes3D.get_proj = get_proj

Then I'm creating a sample data and plotting as:

y,z,x = np.meshgrid(range(10),-np.arange(5)[::-1],range(20))
d = np.sin(x)+np.sin(y)+np.sin(z)

iy,ix = 0,-1
iz = -1

fig,ax = plt.subplots(figsize=(5,5),subplot_kw={'projection':'3d'})
ax.pbaspect = np.array([1, 1, 1])#np.array([2.0, 1.0, 0.5])

ax.contourf(x[iz], y[iz], d[iz],zdir='z',offset=0)
ax.contourf(x[:,iy,:],d[:,iy,:],z[:,iy,:],zdir='y',offset=y.min())
ax.contourf(d[:,:,ix],y[:,:,ix],z[:,:,ix],zdir='x',offset=x.max())

color = '0.3'
ax.plot(x[0,iy,:],y[0,iy,:],y[0,iy,:]*0,color,linewidth=1,zorder=1e4)
ax.plot(x[:,iy,0]*0+x.max(),y[:,iy,0],z[:,iy,0],color,linewidth=1,zorder=1e4)

ax.plot(x[0,:,ix],y[0,:,ix],y[0,:,ix]*0,color,linewidth=1,zorder=1e4)
ax.plot(x[:,0,ix],y[:,0,ix]*0+y.min(),z[:,0,ix],color,linewidth=1,zorder=1e4)


ax.set(xlim=[x.min(),x.max()],ylim=[y.min(),y.max()],zlim=[z.min(),z.max()])

fig.tight_layout()

If I set the pbaspect parameter as (1,1,1) I obtain:

enter image description here

But If I set it for (2,1,0.5) the axis seems to be correct, but it crops the data somehow:

enter image description here

Even if I let the xlim,ylim and zlim automatic. There is something strange with the aspect too. Something tells me that the the axis as not orthogonal.

enter image description here

Does someone know how to correct the aspect ratio for this? I would also like to know how to avoid the axis being cropped by the figure limits.

I searched on the web so long, but I could not find a better solution for this.

Update:

I tried using less than 1 values for pbaspect as suggested and it gets beter, but still crops the data:

enter image description here

Upvotes: 0

Views: 1276

Answers (1)

Zaraki Kenpachi
Zaraki Kenpachi

Reputation: 5730

You can try to change figsize and adjust subplots margins like below:

fig = plt.figure(figsize=(10,6))
fig.subplots_adjust(left=0.2, right=0.8, top=0.8, bottom=0.2)
ax = fig.gca(projection='3d')
ax.pbaspect = np.array([2, 1, 0.5])
ax.contourf(x[iz], y[iz], d[iz],zdir='z',offset=0)
ax.contourf(x[:,iy,:],d[:,iy,:],z[:,iy,:],zdir='y',offset=y.min())
ax.contourf(d[:,:,ix],y[:,:,ix],z[:,:,ix],zdir='x',offset=x.max())
ax.plot(x[0,iy,:],y[0,iy,:],y[0,iy,:]*0,color,linewidth=1,zorder=1e4)
ax.plot(x[:,iy,0]*0+x.max(),y[:,iy,0],z[:,iy,0],color,linewidth=1,zorder=1e4)
ax.plot(x[0,:,ix],y[0,:,ix],y[0,:,ix]*0,color,linewidth=1,zorder=1e4)
ax.plot(x[:,0,ix],y[:,0,ix]*0+y.min(),z[:,0,ix],color,linewidth=1,zorder=1e4)
plt.show()

Output for (2,1,0.5):

enter image description here

Regarding disappearing graphics there is Matplotlib issue:

The problem occurs due to the reduction of 3D data down to 2D + z-order scalar. A single value represents the 3rd dimension for all parts of 3D objects in a collection. Therefore, when the bounding boxes of two collections intersect, it becomes possible for this artifact to occur. Furthermore, the intersection of two 3D objects (such as polygons or patches) can not be rendered properly in matplotlib’s 2D rendering engine.

Upvotes: 2

Related Questions