0

I am trying to create a 3-D plot and a 2-D plot side-by-side in python. I need equal aspect ratios for both plots, which I managed using code provided by this answer: https://stackoverflow.com/a/31364297/125507. The problem I'm having now is how to effectively "crop" the 3-D plot so it doesn't take up so much white space. That is to say, I want to reduce the length of the X and Y axes while maintaining equal scale to the (longer) Z-axis. Here is a sample code and plot:

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

def set_axes_equal(ax):
    '''Make axes of 3D plot have equal scale so that spheres appear as spheres,
    cubes as cubes, etc..  This is one possible solution to Matplotlib's
    ax.set_aspect('equal') and ax.axis('equal') not working for 3D.

    Input
      ax: a matplotlib axis, e.g., as output from plt.gca().
    '''

    x_limits = ax.get_xlim3d()
    y_limits = ax.get_ylim3d()
    z_limits = ax.get_zlim3d()

    x_range = abs(x_limits[1] - x_limits[0])
    x_middle = np.mean(x_limits)
    y_range = abs(y_limits[1] - y_limits[0])
    y_middle = np.mean(y_limits)
    z_range = abs(z_limits[1] - z_limits[0])
    z_middle = np.mean(z_limits)

    # The plot bounding box is a sphere in the sense of the infinity
    # norm, hence I call half the max range the plot radius.
    plot_radius = 0.5*max([x_range, y_range, z_range])

    ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius])
    ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius])
    ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius])

ax  = [None]*2
fig = plt.figure()
ax[0] = fig.add_subplot(121, projection='3d', aspect='equal')
ax[1] = fig.add_subplot(122, aspect='equal')



nn   = 30
phis = np.linspace(0,np.pi,  nn).reshape(1,nn)
psis = np.linspace(0,np.pi*2,nn).reshape(nn,1)
ones = np.ones((nn,1))
el_h = np.linspace(-5, 5, nn).reshape(1,nn)

x_sph = np.sin(phis)*np.cos(psis)
y_sph = np.sin(phis)*np.sin(psis)
z_sph = np.cos(phis)*ones

x_elp = np.sin(phis)*np.cos(psis)*.25
y_elp = np.sin(phis)*np.sin(psis)*.25
z_elp = el_h*ones

ax[0].scatter(x_sph, y_sph, z_sph)
ax[0].scatter(x_elp, y_elp, z_elp)
ax[1].scatter(y_sph, z_sph)
ax[1].scatter(y_elp, z_elp)

for ii in range(2):
    ax[ii].set_xlabel('X')
    ax[ii].set_ylabel('Y')
ax[0].set_zlabel('Z')

set_axes_equal(ax[0])
plt.savefig('SphereElipse.png', dpi=300)

And here is its image output: 3-D and 2-D sphere and ellipse side-by-side

Clearly the 2D plot automatically modifies the length of the axes while maintaining the scale, but the 3D plot doesn't, leading to a tiny representation which does not well use the space allotted to its subplot. Is there any way to do this? This question is similar to an earlier unanswered question How do I crop an Axes3D plot with square aspect ratio?, except it adds the stipulation of multiple subplots, which means the answers provided there do not work.

Will S
  • 1
  • 3
  • In principle, you could set your `xlim` and `ylim` to `[-1,1]`, your `zlim` to `[-6,6]` and then your `aspect` to `6`. Then, if you rotate your plot such that you look at it from the side, it looks good. However, looking at it from, e.g., above makes it look really distorted. This anyway should give you an idea why what you want to achieve is so problematic: the `aspect` sets the x-y ratio of the Axes3D object in *figure coordinates*, not in the coordinates you plot in. What you would need would be an aspect ratio that changes with projection angle. – Thomas Kühn Feb 23 '18 at 12:56
  • Have you seen [this](https://stackoverflow.com/a/8141905/2454357)? – Thomas Kühn Feb 23 '18 at 13:02
  • @ThomasKühn I tried ax[0].set_xlim = [-1,1] ax[0].set_ylim = [-1,1] ax[0].set_zlim = [-6,6] ax[0].set_aspect(6) as you suggested, but get a figure that's distorted from any viewing angle, so I probable misunderstood you. As for the link, I did see that but was hoping someone found a solution in the past several years. Unfortunately I can't test the solution on the wayback forum conversation because the link to the git branch is dead (and I can't install a new branch of matplotlib on this computer either way) – Will S Feb 23 '18 at 20:27
  • You probably didn't misunderstand me -- all I wanted to point out was that it is pretty tricky to do. I played a bit with aspect ratios that depend on the viewing angles, but that was not too successful, because there is so much going on (for instance the plot size depends on how the ticks are placed). Sad that the link to the git branch is dead. – Thomas Kühn Feb 23 '18 at 20:42

0 Answers0