187

I'm learning how to use mplot3d to produce nice plots of 3d data and I'm pretty happy so far. What I am trying to do at the moment is a little animation of a rotating surface. For that purpose, I need to set a camera position for the 3D projection. I guess this must be possible since a surface can be rotated using the mouse when using matplotlib interactively. But how can I do this from a script? I found a lot of transforms in mpl_toolkits.mplot3d.proj3d but I could not find out how to use these for my purpose and I didn't find any example for what I'm trying to do.

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
Andreas Bleuler
  • 1,991
  • 2
  • 12
  • 6

5 Answers5

223

By "camera position," it sounds like you want to adjust the elevation and the azimuth angle that you use to view the 3D plot. You can set this with ax.view_init. I've used the below script to first create the plot, then I determined a good elevation, or elev, from which to view my plot. I then adjusted the azimuth angle, or azim, to vary the full 360deg around my plot, saving the figure at each instance (and noting which azimuth angle as I saved the plot). For a more complicated camera pan, you can adjust both the elevation and angle to achieve the desired effect.

    from mpl_toolkits.mplot3d import Axes3D
    ax = Axes3D(fig)
    ax.scatter(xx,yy,zz, marker='o', s=20, c="goldenrod", alpha=0.6)
    for ii in xrange(0,360,1):
        ax.view_init(elev=10., azim=ii)
        savefig("movie%d.png" % ii)
Mateen Ulhaq
  • 24,552
  • 19
  • 101
  • 135
cosmosis
  • 6,047
  • 3
  • 32
  • 28
  • 42
    Beat me to it! On a side note, these are available as the `ax.elev` and `ax.azim` properties. You could also have just written `ax.azim = ii` or even `ax.azim += 1` to achieve the same effect. – Joe Kington Oct 15 '12 at 23:32
  • 1
    Sorry I beat you but fair points all around. This is also just a coding excerpt of mine, there was more within that for-loop than just view_init and savefig. =) – cosmosis Oct 16 '12 at 02:00
  • 4
    Thanks cosmosis and Joe, that was exactly what I was looking for. Since I now knew what to look for, I also found ax.dist which - together with ax.azim and ax.elev - allows to set the camera position in polar coordinates. – Andreas Bleuler Oct 16 '12 at 07:59
  • This "adds" a new plot to the axes. So if you pass "transparent=True" to savefig, you'll see all the previous views overlapping. This is also apparent from the file sizes. I'm still looking for a way to change the view without resetting the axes... – navidoo Jul 30 '13 at 03:37
  • 14
    You can also set the distance between camera and object point by ax.dist=15 (default to be 10) – Tim Jul 03 '14 at 03:43
  • I was looking for a way to get the current azim, elev (something like ax.get_azim is not there) but ax.azim just worked! – otterb Sep 02 '14 at 23:40
  • can i save a rotating .gif file in this way... @cosmosis – diffracteD Mar 13 '15 at 05:52
  • @diffracteD Yes. The `ii` index rotates the image the full 360 degrees, producing the images for the animated gif. However, this will not compile the images together to make the rotating gif. – cosmosis Mar 13 '15 at 21:55
  • Hello, so it seems view_init rotates from the original position. In order to attain some complicated orientations (like modify roll), I would need to change the camera view multiple times. Any leads on how to that guys? – Rakshit Kothari May 20 '21 at 19:47
  • The current code starts from the original position at 10deg elevation (i.e. the camera view) and 0deg azimuth and then rotates around 360 deg azimuth. So, if you want to do something more complicated, you'd need to set up two corresponding arrays to signify how the elevation and azimuthal change together. – cosmosis May 20 '21 at 23:30
  • azimuth and elevation are not sufficient for specifying a 3D rotation; This is fairly disappointing. Mouse controls seem to exhibit gimbal lock too, not my favorite approach. None of these solutions actually make it clear how to specify the view of a plot in 3D. – MRule Aug 10 '22 at 12:22
  • @MRule -- Azimuth, elevation, and rotation are what specify the 3D rotation. The actual rotation happens when `ii` (in degrees) iterates a full circle, or 360 degrees. Pitch or view of the plot is specified by "elev" -- right now it is set to 10deg. Please consult the [documentation](https://matplotlib.org/stable/api/_as_gen/mpl_toolkits.mplot3d.axes3d.Axes3D.html) if it is unclear. – cosmosis Aug 10 '22 at 16:26
22

What would be handy would be to apply the Camera position to a new plot. So I plot, then move the plot around with the mouse changing the distance. Then try to replicate the view including the distance on another plot. I find that axx.ax.get_axes() gets me an object with the old .azim and .elev.

IN PYTHON...

axx=ax1.get_axes()
azm=axx.azim
ele=axx.elev
dst=axx.dist       # ALWAYS GIVES 10
#dst=ax1.axes.dist # ALWAYS GIVES 10
#dst=ax1.dist      # ALWAYS GIVES 10

Later 3d graph...

ax2.view_init(elev=ele, azim=azm) #Works!
ax2.dist=dst                       # works but always 10 from axx

EDIT 1... OK, Camera position is the wrong way of thinking concerning the .dist value. It rides on top of everything as a kind of hackey scalar multiplier for the whole graph.

This works for the magnification/zoom of the view:

xlm=ax1.get_xlim3d() #These are two tupples
ylm=ax1.get_ylim3d() #we use them in the next
zlm=ax1.get_zlim3d() #graph to reproduce the magnification from mousing
axx=ax1.get_axes()
azm=axx.azim
ele=axx.elev

Later Graph...

ax2.view_init(elev=ele, azim=azm) #Reproduce view
ax2.set_xlim3d(xlm[0],xlm[1])     #Reproduce magnification
ax2.set_ylim3d(ylm[0],ylm[1])     #...
ax2.set_zlim3d(zlm[0],zlm[1])     #...
snaxxus
  • 339
  • 2
  • 6
13

Minimal example varying azim, dist and elev

To add some simple sample images to what was explained at: https://stackoverflow.com/a/12905458/895245

Here is my test program:

#!/usr/bin/env python3

import sys

import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import numpy as np

fig = plt.figure()
ax = fig.gca(projection='3d')

if len(sys.argv) > 1:
    azim = int(sys.argv[1])
else:
    azim = None
if len(sys.argv) > 2:
    dist = int(sys.argv[2])
else:
    dist = None
if len(sys.argv) > 3:
    elev = int(sys.argv[3])
else:
    elev = None

# Make data.
X = np.arange(-5, 6, 1)
Y = np.arange(-5, 6, 1)
X, Y = np.meshgrid(X, Y)
Z = X**2

# Plot the surface.
surf = ax.plot_surface(X, Y, Z, linewidth=0, antialiased=False)

# Labels.
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')

if azim is not None:
    ax.azim = azim
if dist is not None:
    ax.dist = dist
if elev is not None:
    ax.elev = elev

print('ax.azim = {}'.format(ax.azim))
print('ax.dist = {}'.format(ax.dist))
print('ax.elev = {}'.format(ax.elev))

plt.savefig(
    'main_{}_{}_{}.png'.format(ax.azim, ax.dist, ax.elev),
    format='png',
    bbox_inches='tight'
)

Running it without arguments gives the default values:

ax.azim = -60
ax.dist = 10
ax.elev = 30

main_-60_10_30.png

enter image description here

Vary azim

The azimuth is the rotation around the z axis e.g.:

  • 0 means "looking from +x"
  • 90 means "looking from +y"

main_-60_10_30.png

enter image description here

main_0_10_30.png

enter image description here

main_60_10_30.png

enter image description here

Vary dist

dist seems to be the distance from the center visible point in data coordinates.

main_-60_10_30.png

enter image description here

main_-60_5_30.png

enter image description here

main_-60_20_-30.png

enter image description here

Vary elev

From this we understand that elev is the angle between the eye and the xy plane.

main_-60_10_60.png

enter image description here

main_-60_10_30.png

enter image description here

main_-60_10_0.png

enter image description here

main_-60_10_-30.png

enter image description here

Tested on matpotlib==3.2.2.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
3

Try the following code to find the optimal camera position

Move the viewing angle of the plot using the keyboard keys as mentioned in the if clause

Use print to get the camera positions

def move_view(event):
    ax.autoscale(enable=False, axis='both') 
    koef = 8
    zkoef = (ax.get_zbound()[0] - ax.get_zbound()[1]) / koef
    xkoef = (ax.get_xbound()[0] - ax.get_xbound()[1]) / koef
    ykoef = (ax.get_ybound()[0] - ax.get_ybound()[1]) / koef
    ## Map an motion to keyboard shortcuts
    if event.key == "ctrl+down":
        ax.set_ybound(ax.get_ybound()[0] + xkoef, ax.get_ybound()[1] + xkoef)
    if event.key == "ctrl+up":
        ax.set_ybound(ax.get_ybound()[0] - xkoef, ax.get_ybound()[1] - xkoef)
    if event.key == "ctrl+right":
        ax.set_xbound(ax.get_xbound()[0] + ykoef, ax.get_xbound()[1] + ykoef)
    if event.key == "ctrl+left":
        ax.set_xbound(ax.get_xbound()[0] - ykoef, ax.get_xbound()[1] - ykoef)
    if event.key == "down":
        ax.set_zbound(ax.get_zbound()[0] - zkoef, ax.get_zbound()[1] - zkoef)
    if event.key == "up":
        ax.set_zbound(ax.get_zbound()[0] + zkoef, ax.get_zbound()[1] + zkoef)
    # zoom option
    if event.key == "alt+up":
        ax.set_xbound(ax.get_xbound()[0]*0.90, ax.get_xbound()[1]*0.90)
        ax.set_ybound(ax.get_ybound()[0]*0.90, ax.get_ybound()[1]*0.90)
        ax.set_zbound(ax.get_zbound()[0]*0.90, ax.get_zbound()[1]*0.90)
    if event.key == "alt+down":
        ax.set_xbound(ax.get_xbound()[0]*1.10, ax.get_xbound()[1]*1.10)
        ax.set_ybound(ax.get_ybound()[0]*1.10, ax.get_ybound()[1]*1.10)
        ax.set_zbound(ax.get_zbound()[0]*1.10, ax.get_zbound()[1]*1.10)
    
    # Rotational movement
    elev=ax.elev
    azim=ax.azim
    if event.key == "shift+up":
        elev+=10
    if event.key == "shift+down":
        elev-=10
    if event.key == "shift+right":
        azim+=10
    if event.key == "shift+left":
        azim-=10

    ax.view_init(elev= elev, azim = azim)

    # print which ever variable you want 

    ax.figure.canvas.draw()

fig.canvas.mpl_connect("key_press_event", move_view)

plt.show()

Bharath C
  • 55
  • 5
1

Q: How can I set view in matplotlib?

For a 3d plot, how do you fixate the view?

A: By setting properties ax.azim and ax.level

ax.elev = 0
ax.azim = 270  # xz view

ax.elev = 0
ax.azim = 0    # yz view

ax.elev = 0
ax.azim = -90  # xy view
Hunaphu
  • 589
  • 10
  • 11
  • Please reference the other answer (with a link, not by "above" or similar) and explain the additional insight you contribute. – Yunnosch Sep 03 '22 at 14:20