Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

3D plot aspect ratio [matplotlib]

I am using the code written here to make a plot like this one below.

The problem is, that I want to adjust an aspect ratio, namely, to stretch it along the z-axis, so that all the stacked images are more or less visible. Is there an easy way of doing that?

enter image description here

like image 714
hayk Avatar asked Jul 01 '16 16:07

hayk


2 Answers

Looks like there is no “proper” way to do it, but we can try monkey-patching our way around the problem. Here’s the best kludge I could do:

from mpl_toolkits.mplot3d import proj3d

def make_get_proj(self, rx, ry, rz):
    '''
    Return a variation on :func:`~mpl_toolkit.mplot2d.axes3d.Axes3D.getproj` that
    makes the box aspect ratio equal to *rx:ry:rz*, using an axes object *self*.
    '''

    rm = max(rx, ry, rz)
    kx = rm / rx; ky = rm / ry; kz = rm / rz;

    # Copied directly from mpl_toolkit/mplot3d/axes3d.py. New or modified lines are
    # marked by ##
    def get_proj():
        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)

        # adjust the aspect ratio                          ##
        aspectM = proj3d.world_transformation(-kx + 1, kx, ##
                                              -ky + 1, ky, ##
                                              -kz + 1, kz) ##

        # 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, np.dot(aspectM, worldM)) ##
        M = np.dot(perspM, M0)
        return M
    return get_proj

# and later in the code:
ax.get_proj = make_get_proj(ax, 1, 1, 2)
ax.set_aspect(1.0)

3D plot inside a 1:1:2 box rendered by Matplotlib

like image 106
Alex Shpilkin Avatar answered Oct 27 '22 17:10

Alex Shpilkin


As of matplotlib 3.3.0, Axes3D.set_box_aspect seems to be the recommended approach.

import matplotlib.pyplot as plt

ax = plt.axes(projection='3d')

N = 10  # some number > 1 that stretches z axis as you desire
ax.set_box_aspect((1, 1, N))  # xy aspect ratio is 1:1, but stretches z axis
like image 3
Matt Panzer Avatar answered Oct 27 '22 15:10

Matt Panzer