Animation 2D Vs 3D (Why in 3D transition duration do not work?)

Hi

Here I have a 2d scatter model that transition duration works without any problem:

import plotly.graph_objects as go

fig = go.Figure(
    data=[go.Scatter(x=[0, 1], y=[0, 1])],
    layout=go.Layout(
        xaxis=dict(range=[0, 5], autorange=False),
        yaxis=dict(range=[0, 5], autorange=False),
        title="Start Title",
        updatemenus=[dict(
            type="buttons",
            buttons=[dict(label="Play",
                          method="animate",
                          args=[None, dict(frame=dict(redraw=True,fromcurrent=True,duration= 5000, mode='animate'),
                                            transition=dict(duration= 4500))])])]
    ),
    frames=[go.Frame(data=[go.Scatter(x=[1, 2], y=[1, 2])]),
            go.Frame(data=[go.Scatter(x=[1, 4], y=[1, 4])]),
            go.Frame(data=[go.Scatter(x=[3, 4], y=[3, 4])],
                     layout=go.Layout(title_text="End Title"))]
)

fig.show()

But when I use this structure for 3D model it doesn’t work?

import numpy as np
import plotly.graph_objects as go

def rndata():
    
    x =np.random.randint(1,10,2)
    y =np.random.randint(1,10,2)
    z =np.random.randint(1,10,2)
    
    return list(x),list(y),list(z)


fig = go.Figure(
    data=[go.Scatter3d(x=rndata()[0],y=rndata()[1],z=rndata()[2])],
    layout=go.Layout(
        scene = dict(
        xaxis=dict(range=[0, 10], autorange=False),
        yaxis=dict(range=[0, 10], autorange=False),
        zaxis=dict(range=[0, 10], autorange=False),),

        title="Start Title",
        updatemenus=[dict(
            type="buttons",
            buttons=[dict(label="Play",
                          method="animate",
                          args=[None, dict(frame=dict(redraw=True,fromcurrent=True,duration= 500, mode='animate'),
                                            transition=dict(duration= 450))],
                         
                         )])]
    ),
    frames=[
             go.Frame(data=[go.Scatter3d(x=rndata()[0],y=rndata()[1],z=rndata()[2])])     for k  in  range(10)]

)

fig.show()

Is problem from my code or …?

@Bijan
From plotly.js docs we found out that:
The transition duration defines the amount of time spent interpolating a trace from one state to another (currently limited to scatter traces), while the frame duration defines the total time spent in that state, including time spent transitioning .
Hence the transition duration works only for traces which are instances of go.Scatter, and it’s not your code that blocks frames from working as expected.

1 Like

@empet really thanks for your all guidance.

Let me know is there again same problem. For 3d model the buttons doesn’t work:

import plotly.graph_objects as go
import numpy as np
from plotly.offline import iplot
def rndata():
    n=10
    x =np.random.randint(1,10,n)
    y =np.random.randint(1,10,n)
    z =np.random.randint(1,10,n)
    
    return list(x),list(y),list(z)

size = 10
theta =  np.random.randint(17+1,size=size)*20*(3.14/180)
print(theta)

fig = go.Figure(go.Scatter3d(x=rndata()[0],y=rndata()[1],z=rndata()[2]))
                


# Define frames
frames = []
for k in range(len(theta)):
    frames.append(go.Frame(data=[go.Scatter3d(x=rndata()[0],y=rndata()[1],z=rndata()[2])],name= f'frame{k}'))
    
fig.frames = frames 

updatemenus = [dict(
        buttons = [
            dict(
                args = [None, {"frame": {"duration": 500, "redraw": False},
                                "fromcurrent": True, "transition": {"duration": 300}}],
                label = "Play",
                method = "animate"
                ),
            dict(
                 args = [[None], {"frame": {"duration": 0, "redraw": False},
                                  "mode": "immediate",
                                  "transition": {"duration": 0}}],
                label = "Pause",
                method = "animate"
                )
        ],
        direction = "left",
        pad = {"r": 10, "t": 87},
        showactive = False,
        type = "buttons",
        x = 0.1,
        xanchor = "right",
        y = 0,
        yanchor = "top"
    )]  

sliders = [dict(steps = [dict(method= 'animate',
                              args= [[f'frame{k}'],                           
                              dict(mode= 'immediate',
                                   frame= dict(duration=400, redraw=False),
                                   transition=dict(duration= 0))
                                 ],
                              label=f'{k+1}'
                             ) for k in range(len(theta))], 
                active=0,
                transition= dict(duration= 0 ),
                x=0, # slider starting position  
                y=0, 
                currentvalue=dict(font=dict(size=12), 
                                  prefix='frame: ', 
                                  visible=True, 
                                  xanchor= 'center'
                                 ),  
                len=1.0) #slider length
           ]
fig.update_layout(width=800, height=800,
                  xaxis_range = [-1, 1],
                  yaxis_range = [-1, 1],
                  updatemenus=updatemenus,
                  sliders=sliders)

iplot(fig)

While for 2d it works:

import plotly.graph_objects as go
import numpy as np
from plotly.offline import iplot

size = 10
theta =  np.random.randint(17+1,size=size)*20*(3.14/180)
print(theta)

fig = go.Figure(go.Scatter(
                        marker= {'color': '#010101', 'size': 30, 'symbol': 'diamond'},
                        mode= 'markers',
                        name= '180.0',
                        text= 'Persoon',
                        x= [-0.99999873],
                        y=[0.00159265]))
                


# Define frames
frames = []
for k in range(len(theta)):
    frames.append(go.Frame(data= 
                    [go.Scatter(x = np.array(np.cos(theta[k])),
                                y = np.array(np.sin(theta[k])))
                                ],
                          name= f'frame{k}'))
fig.frames = frames 

updatemenus = [dict(
        buttons = [
            dict(
                args = [None, {"frame": {"duration": 500, "redraw": False},
                                "fromcurrent": True, "transition": {"duration": 300}}],
                label = "Play",
                method = "animate"
                ),
            dict(
                 args = [[None], {"frame": {"duration": 0, "redraw": False},
                                  "mode": "immediate",
                                  "transition": {"duration": 0}}],
                label = "Pause",
                method = "animate"
                )
        ],
        direction = "left",
        pad = {"r": 10, "t": 87},
        showactive = False,
        type = "buttons",
        x = 0.1,
        xanchor = "right",
        y = 0,
        yanchor = "top"
    )]  

sliders = [dict(steps = [dict(method= 'animate',
                              args= [[f'frame{k}'],                           
                              dict(mode= 'immediate',
                                   frame= dict(duration=400, redraw=False),
                                   transition=dict(duration= 0))
                                 ],
                              label=f'{k+1}'
                             ) for k in range(len(theta))], 
                active=0,
                transition= dict(duration= 0 ),
                x=0, # slider starting position  
                y=0, 
                currentvalue=dict(font=dict(size=12), 
                                  prefix='frame: ', 
                                  visible=True, 
                                  xanchor= 'center'
                                 ),  
                len=1.0) #slider length
           ]
fig.update_layout(width=800, height=800,
                  xaxis_range = [-1, 1],
                  yaxis_range = [-1, 1],
                  updatemenus=updatemenus,
                  sliders=sliders)

iplot(fig)

Why this is in this way?

@Bijan
Your 3d animation displays in each frame the same data as in fig.data[0]. I suspect that it is only a minimal example to illustrate what you consider to be a bug. Both the two buttons and the slider work in a Jupyter notebook. What do you expect more?

Dear @empet why my code display only fig.data[0]!!!. I have defined frames with different name name= f'frame{k}' and for slider arguments also I have put args= [[f'frame{k}'] so why it shows the data[0]??

I just want to different scatters be shown by clicking the play button and the shoe stops when I click pause.

Beacause fig.data[0] is defined here:

fig = go.Figure(go.Scatter3d(x=rndata()[0],y=rndata()[1],z=rndata()[2]))

and each frame displays:

frames.append(go.Frame(data=[go.Scatter3d(x=rndata()[0],y=rndata()[1],z=rndata()[2])],name= f'frame{k}'))

i.e. the same data. Only the frame name is distinct from frame to frame, and in my notebook, the slider moves when I’m clicking the button Play. What do you intend to animate?

@empet
Let me know if I’m wrong. ndata() is a function that at the top of the code that generate random values at each time calling. So I suppose that data are changing in each frame.

I have various frames of Scatter3D and I want a play button to show them frame by frame.

@Bijan

  1. Your function rndata() is not performant at all. Why calling it for x, y and z when you want to set x=rndata()[0]? I modified it (see below).
  2. redraw=False in updatemenus and sliders definition works ONLY for go.Scatter traces. Hence I replaced False by True.
  3. As long as you generate x, y, z randomly in {1, 2, 3, …9} why did you set xaxis_range=[-1,1], and similarly for y?
    Moreover for 3D plots, the axes update must be performed as:
scene=dict(xaxis_range=[0, 11], yaxis_range=[0, 11], zaxis_range=[0,11])

The following code works, with a trick to keep the scene fixed during animation. Namely I added a dummy trace instead of setting axes range:

import plotly.graph_objects as go
import numpy as np
from plotly.offline import iplot
def rndata():
    return np.random.randint(1,10,10)
    
fig = go.Figure(go.Scatter3d(x=rndata(),y=rndata(),z=rndata()))
fig.add_scatter3d(x=[-1, 11], y=[-1, 11], z=[0, 11], mode="markers", 
                  showlegend=False, marker_size=0.05)  #dummy trace  to fix the scene to the cube [-1, 11]x[-1, 11] x[-1, 11]              


# Define frames
frames = []
nr_frames=10
for k in range(nr_frames):
    frames.append(go.Frame(data=[go.Scatter3d(x=rndata(),y=rndata(),z=rndata())],
                           traces=[0], #this means that only fig.data[0] is updated by frames, not fig.data[1] (the dummy trace)
                          name= f'frame{k}'))
    
fig.update(frames=frames)

updatemenus = [dict(
        buttons = [
            dict(
                args = [None, {"frame": {"duration": 500, "redraw": True},
                                "fromcurrent": True}],
                label = "Play",
                method = "animate"
                ),
            dict(
                 args = [[None], {"frame": {"duration": 0, "redraw": False},
                                  "mode": "immediate",
                                  "transition": {"duration": 0}}],
                label = "Pause",
                method = "animate"
                )
        ],
        direction = "left",
        pad = {"r": 10, "t": 87},
        showactive = False,
        type = "buttons",
        x = 0.1,
        xanchor = "right",
        y = 0,
        yanchor = "top"
    )]  

sliders = [dict(steps = [dict(method= 'animate',
                              args= [[f'frame{k}'],                           
                              dict(mode= 'immediate',
                                   frame= dict(duration=400, redraw=True),
                                   transition=dict(duration= 0))
                                 ],
                              label=f'{k+1}'
                             ) for k in range(len(theta))], 
                active=0,
                transition= dict(duration= 0 ),
                x=0, # slider starting position  
                y=0, 
                currentvalue=dict(font=dict(size=12), 
                                  prefix='frame: ', 
                                  visible=True, 
                                  xanchor= 'center'
                                 ),  
                len=1.0) #slider length
           ]
fig.update_layout(width=600, height=600,  
                  updatemenus=updatemenus,
                  sliders=sliders)

@Bijan I updated the previous code to remove the scene motion from frame to frame.

1 Like

Dear @empet

That is exactly what I needed and thanks because of mentioning my mistakes and problems.

Really Thanks :heart: