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.

@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.

Dear @empet

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

Really Thanks :heart: