0

I'd like to be able to rotate the view of a hemisphere in a 3D matplotlib plot and have the shape show correctly,

Answers to set matplotlib 3d plot aspect ratio? used in the first example don't help as they address the aspect ratio of the plot window.

Question: In the second example I show that if I make the scales equal lengths (-1, 1), (-1, 1), (-0.5, 1.5) I can preserve the shape as I rotate the view, but is this the only way to preserve the shape under view rotation?

enter image description here

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

points   = np.random.random((3, 1000)) - 0.5
points  /= np.sqrt((points**2).sum(axis=0))
x, y, z  = points[:, points[2] > 0.]  # upper hemisphere

fig = plt.figure(figsize=plt.figaspect(0.5)) # https://stackoverflow.com/a/12371373/3904031

ax1 = fig.add_subplot(2, 1, 1, projection='3d')
ax1.plot(x, y, z, '.k')
ax1.view_init(0, 90)
ax1.set_title('view_init(0, 90)', fontsize=16)

ax2 = fig.add_subplot(2, 1, 2, projection='3d')
ax2.plot(x, y, z, '.k')
ax2.view_init(90, 0)
ax2.set_title('view_init(90, 0)', fontsize=16)

plt.show()

enter image description here

fig = plt.figure()

ax1 = fig.add_subplot(1, 2, 1, projection='3d')
ax1.plot(x, y, z, '.k')
ax1.view_init(0, 90)
ax1.set_title('view_init(0, 90)', fontsize=16)
ax1.set_xlim(-1.0, 1.0)
ax1.set_ylim(-1.0, 1.0)
ax1.set_zlim(-0.5, 1.5)

ax2 = fig.add_subplot(1, 2, 2, projection='3d')
ax2.plot(x, y, z, '.k')
ax2.view_init(90, 0)
ax2.set_title('view_init(90, 0)', fontsize=16)
ax2.set_xlim(-1.0, 1.0)
ax2.set_ylim(-1.0, 1.0)
ax2.set_zlim(-0.5, 1.5)

plt.show()
uhoh
  • 3,713
  • 6
  • 42
  • 95
  • 1
    Yes you need to use an equal aspect ratio. This is not easy for 3D plots, but https://stackoverflow.com/questions/13685386/matplotlib-equal-unit-length-with-equal-aspect-ratio-z-axis-is-not-equal-to would show some ways to do it – ImportanceOfBeingErnest Sep 12 '19 at 10:39
  • @ImportanceOfBeingErnest okay thanks for the quick response. So for the second example I've used set_xlim(-1.0, 1.0); set_ylim(-1.0, 1.0); set_zlim(-0.5, 1.5) similar to [this answer](https://stackoverflow.com/a/21765085/3904031) but I could have used invisible bounding points as shown in [this answer](https://stackoverflow.com/a/13701747/3904031). So either way this is probably a duplicate and can be closed as such? Or is there some chance that a `.axis('all_three_equal')` method is iminent? – uhoh Sep 12 '19 at 11:10
  • There is no `.axis('all_three_equal')` method available. – ImportanceOfBeingErnest Sep 12 '19 at 11:17
  • @ImportanceOfBeingErnest thus the question about its *imminence*. In other words, if there's a chance that one may be forthcoming in the near future, then it would serve as an answer to this question. But if the chances are low that something like that will be added, then it's probably better to close this as a duplicate. – uhoh Sep 12 '19 at 11:18
  • 1
    Even if it was imminent, wouldn't such answer live in the duplicate anyways? – ImportanceOfBeingErnest Sep 12 '19 at 11:26
  • @ImportanceOfBeingErnest an answer, *yay!* – uhoh Jul 04 '21 at 06:52

1 Answers1

0

Finally, per this answer:

Simple fix!

I've managed to get this working in version 3.3.1.

It looks like this issue has perhaps been resolved in PR#17172; You can use the ax.set_box_aspect([1,1,1]) function to ensure the aspect is correct (see the notes for the set_aspect function).

You first make the limits in all three axes the same either by adding bounding points (invisible dots beyond your data) to define a cube of equal dimensions, or just use set_xlim, set_ylim, set_zlim as I've done here.

successful plot, yay!

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

points   = np.random.random((3, 1000)) - 0.5
points  /= np.sqrt((points**2).sum(axis=0))
x, y, z  = points[:, points[2] > 0.]  # upper hemisphere

fig = plt.figure(figsize=plt.figaspect(0.5)) # https://stackoverflow.com/a/12371373/3904031

ax1 = fig.add_subplot(1, 2, 1, projection='3d')
ax1.plot(x, y, z, '.k')
ax1.view_init(0, 90)
ax1.set_title('view_init(0, 90)', fontsize=16)

ax2 = fig.add_subplot(1, 2, 2, projection='3d')
ax2.plot(x, y, z, '.k')
ax2.view_init(90, 0)
ax2.set_title('view_init(90, 0)', fontsize=16)

for ax in (ax1, ax2):
    ax.set_xlim(-1, 1)
    ax.set_ylim(-1, 1)
    ax.set_zlim(-0.5, 1.5)
    ax.set_box_aspect([1,1,1])
plt.show()
uhoh
  • 3,713
  • 6
  • 42
  • 95