Black Lives Matter. Please consider donating to Black Girls Code today.

Rotating 3D plots with plotly

Hi,
I’m working on making a rotating 3D plot in plotly. I found the following topic in this forum:
https://community.plotly.com/t/how-to-animate-a-rotation-of-a-3d-plot/20974

The help looked really promising, but like the last person commenting I have some trouble determining exactly where the different parts are supposed to go! Please help me find out what I am doing incorrectly.

Here is my minimal working example:

import plotly.graph_objects as go
import numpy as np

# Helix equation
t = np.linspace(0, 10, 50)
x, y, z = np.cos(t), np.sin(t), t

data=[go.Scatter3d(x=x, y=y, z=z, mode='markers')]

x_eye = -1.25
y_eye = 2
z_eye = 0.5

layout = go.Layout(
         title='Animation Test',
         width=600,
         height=600,
         scene=dict(camera=dict(eye=dict(x=x_eye, y=y_eye, z=z_eye))),
         updatemenus=[dict(type='buttons',
                  showactive=False,
                  y=1,
                  x=0.8,
                  xanchor='left',
                  yanchor='bottom',
                  pad=dict(t=45, r=10),
                  buttons=[dict(label='Play',
                                 method='animate',
                                 args=[None, dict(frame=dict(duration=2, redraw=False), 
                                                             transition=dict(duration=0),
                                                             fromcurrent=True,
                                                             mode='immediate'
                                                            )]
                                            )
                                      ]
                              )
                        ]
)


def rotate_z(x, y, z, theta):
    w = x+1j*y
    return np.real(np.exp(1j*theta)*w), np.imag(np.exp(1j*theta)*w), z

frames=[]
for t in np.arange(0, 6.26, 0.1):
    xe, ye, ze = rotate_z(x_eye, y_eye, z_eye, -t)
    frames.append(dict(layout=dict(scene=dict(camera=dict(eye=dict(x=xe, y=ye, z=ze))))))

fig = go.Figure(data=data, layout=layout, frames=frames)

fig.show()

Hi @ChocolatePlotter,

I updated your code to work with Plotly 4.5 (the last version). Your code follows the animation code for Plotly <=4.0.

import plotly.graph_objects as go
import numpy as np

# Helix equation
t = np.linspace(0, 10, 50)
x, y, z = np.cos(t), np.sin(t), t

fig= go.Figure(go.Scatter3d(x=x, y=y, z=z, mode='markers'))

x_eye = -1.25
y_eye = 2
z_eye = 0.5

fig.update_layout(
         title='Animation Test',
         width=600,
         height=600,
         scene_camera_eye=dict(x=x_eye, y=y_eye, z=z_eye),
         updatemenus=[dict(type='buttons',
                  showactive=False,
                  y=1,
                  x=0.8,
                  xanchor='left',
                  yanchor='bottom',
                  pad=dict(t=45, r=10),
                  buttons=[dict(label='Play',
                                 method='animate',
                                 args=[None, dict(frame=dict(duration=5, redraw=True), 
                                                             transition=dict(duration=0),
                                                             fromcurrent=True,
                                                             mode='immediate'
                                                            )]
                                            )
                                      ]
                              )
                        ]
)


def rotate_z(x, y, z, theta):
    w = x+1j*y
    return np.real(np.exp(1j*theta)*w), np.imag(np.exp(1j*theta)*w), z

frames=[]
for t in np.arange(0, 6.26, 0.1):
    xe, ye, ze = rotate_z(x_eye, y_eye, z_eye, -t)
    frames.append(go.Frame(layout=dict(scene_camera_eye=dict(x=xe, y=ye, z=ze))))
fig.frames=frames

fig.show()

The rotation is implemented as a rotation of the observer (camera) around the plot. Since the trace definition (i.e. the Scatter3d for helix curve) is unchanged from frame to frame, we are only updating the observer(camera) position in go.Frame.

1 Like

It worked like a charm! Thank you!

I eventually saw that I hadn’t defined the frames as go.Frame objects, but was very confused when that didn’t work either. Probably some version issues.

But no matter. Now it works! :slight_smile:

@ChocolatePlotter,

The first version didn’t work because in go.Layout.updatemenus the key redraw of the dict frame was set on False. Before some Plotly version it worked with this setting, but later it changed to True (notice that in the new working code it is True). Only scatter (2d) works without redrawing from frame to frame see: https://plot.ly/~empet/14896.

Your initial frame definition, using dicts, still works, but I changed it to go.Frame because it is more clear.

Update: Meanwhile I realized that an explanation of why the 3D rotation is expressed in complex numbers could be useful for code understanding. Here is an image I cropped from a LaTeX file I edited: