How to simultaneously animate multiple traces of the same Figure

Hi everybody,

I have various type of data that I have added them to my figure with different names (“Nodes”, “Nodes1”,"“Nodes2”) as shown in below code. Now I want to make an animation and update their values in different frames. What I did is in this way:

#Using UpdateTrace figure
import plotly.graph_objects as go
import numpy as np
from plotly.offline import iplot
import copy
import time as tm

t0=tm.time()
def rndata():
    return np.random.randint(1,10,100)
    
theta=[i for i in range(3000)]


fig = go.Figure(go.Scatter3d(x=rndata(),y=rndata(),z=rndata(),name='Nodes',marker=dict(color='black')))
fig.add_traces(go.Scatter3d(x=rndata(),y=rndata(),z=rndata(),name='Nodes1',marker=dict(color='blue')))
fig.add_traces(go.Scatter3d(x=rndata(),y=rndata(),z=rndata(),name='Nodes2',marker=dict(color='red')))

frames = []
frames.append({'data':copy.deepcopy(fig['data']),'name':f'frame{0}'})

for k in range(len(theta)):
    fig.update_traces(x=tuple(rndata()),y=tuple(rndata()),z=tuple(rndata()),selector = ({'name':'Nodes'}))
    fig.update_traces(x=tuple(rndata()),y=tuple(rndata()),z=tuple(rndata()),selector = ({'name':'Nodes1'}))
    frames.append({'data':copy.deepcopy(fig['data']),'name':f'frame{k+1}'})
    

fig.update(frames=frames)

print(tm.time()-t0)
iplot(fig)

Let me know is there any more efficient way that make above code faster or this is proper?

Thanks

@Bijan Over the past 6 years I have posted several times how to update multiple traces. Please search this forum to find out
how to proceed.
You can also search among my notebooks posted here: https://chart-studio.plotly.com/~empet#/ , deactivating Charts and Data to browse only through Jupyter Notebooks.

1 Like

Dear @empet

I found somethings in you posts and I have modified the code as the Following:

import plotly.graph_objects as go
import numpy as np
from plotly.offline import iplot
import copy
import time as tm

t0=tm.time()
def rndata():
    return np.random.randint(1,10,100)
    
theta=[i for i in range(3000)]


fig = go.Figure(go.Scatter3d(x=rndata(),y=rndata(),z=rndata(),name='Nodes',marker=dict(color='yellow')))
fig.add_traces(go.Scatter3d(x=rndata(),y=rndata(),z=rndata(),name='Nodes1',marker=dict(color='blue')))
fig.add_traces(go.Scatter3d(x=rndata(),y=rndata(),z=rndata(),name='Nodes2',marker=dict(color='red')))

frames = []
frames.append({'data':copy.deepcopy(fig['data']),'name':f'frame{0}'})

for k in range(len(theta)):
    frames.append(go.Frame(data=[go.Scatter3d(x=tuple(rndata()),y=tuple(rndata()),z=tuple(rndata()),name='Nodes'),
                                 go.Scatter3d(x=tuple(rndata()),y=tuple(rndata()),z=tuple(rndata()),name='Nodes1'),
                                 go.Scatter3d(x=tuple(rndata()),y=tuple(rndata()),z=tuple(rndata()),name='Nodes2')]))
    

fig.update(frames=frames)

print(tm.time()-t0)
iplot(fig)

Let me know this structure is what you believe that is better than previous? (However there is no much difference in running time between both of them. If there is a better structure guide me please if it is possible)

With all the best,
Bijan

@Bijan Please change the title of your question as: “How to simultaneously animate multiple traces of the same Figure”.

As I stressed in my previous answers related to this topic, I repeat it here again:

  • First, defines a go.Figure, and add your traces; Let us say fig.data[0], Fig.data[1], …
  • Within go.Frame definition we set only the attributes in fig.data[k] trace that change from frame to frame. Don’t repeat the fixed attributes here!
  • to let plotly.js know what trace is updated by each go.Frame.data[k], insert in go.Frame definition the line
    traces=[0,1,..].
  • each frame has a name, to be used in sliders definition in order to connect that frame with a slider step.

Below is an example with two traces. Each frame updates the z-values, and the line_color in the trace fig.data[0], respectively the marker_symbol in fig.data[1].

import plotly.graph_objects as go
import numpy as np
#from plotly.offline import iplot
def rndata():
    return np.random.randint(1,10,10)

n_frames=10
my_colors = ["RoyalBlue", "#09ffff", "#19d3f3", "#e763fa", "#ab63fa", 
             "#636efa", "#00cc96", "#EF553B", "#119DFF", "#0D76BF" ]    

my_symbols= ['circle', 'circle-open', 'cross', 'diamond',
            'diamond-open', 'square', 'square-open', 'x', 'circle', 'diamond']

fig = go.Figure(go.Scatter3d(x=rndata(),y=rndata(),z=rndata(), name='Nodes1',  mode="lines",
                             line_color="RoyalBlue", line_width=2
                             ))
fig.add_scatter3d(x=rndata(),y=rndata(),z=rndata(), 
                  mode="markers", marker_symbol=["circle"]*10, 
                  name='Nodes2', marker_size=6)

frames = []

for k in range(n_frames):
    frames.append(go.Frame(data=[go.Scatter3d(z=rndata(), line_color=my_colors[k]),
                                 go.Scatter3d(marker_symbol=[my_symbols[k]]*10)],
                           traces=[0,1], #This means that the above trace updates (within go.Frame definition)
                                         #are performed for fig.data[0], fig.data[1]
                           name=f"fr{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(n_frames)], 
                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)
fig.show()
1 Like

The above solution was what I needed exactly. Among using the above structure I found that I need to add data traces with their name and there is problem for me with data order number definitions. So I solved this problem using defining a dictionary (nameDataindexdict) how is shown in the below code, and I put it here maybe useful someday for someone:
Again thanks dear @empet


import plotly.graph_objects as go
import numpy as np
#from plotly.offline import iplot
def rndata():
    return np.random.randint(1,10,10)

n_frames=10
my_colors = ["RoyalBlue", "#09ffff", "#19d3f3", "#e763fa", "#ab63fa", 
             "#636efa", "#00cc96", "#EF553B", "#119DFF", "#0D76BF" ]    

my_symbols= ['circle', 'circle-open', 'cross', 'diamond',
            'diamond-open', 'square', 'square-open', 'x', 'circle', 'diamond']

fig = go.Figure(go.Scatter3d(x=rndata(),y=rndata(),z=rndata(), name='Nodes1',  mode="lines",
                             line_color="RoyalBlue", line_width=2
                             ))
fig.add_scatter3d(x=rndata(),y=rndata(),z=rndata(), 
                  mode="markers", marker_symbol=["circle"]*10, 
                  name='Nodes2', marker_size=6)

#Store Names and their corresponding order of the 
nameDataindexdict={}
for dt in fig.data:
    nameDataindexdict[dt['name']]=fig.data.index(dt)

frames = []

for k in range(n_frames):
    frames.append(go.Frame(data=[go.Scatter3d(z=rndata(), line_color=my_colors[k]),
                                 go.Scatter3d(marker_symbol=[my_symbols[k]]*10)],
                            #traces=[0,1], #This means that the above trace updates (within go.Frame definition)  #are performed for fig.data[0], fig.data[1]
                            traces=[nameDataindexdict['Nodes1'],nameDataindexdict['Nodes2']], #This means that the above trace updates are for traces with name ...          
                            name=f"fr{k}"))      
    

fig.update(frames=frames)
updatemenus = [dict(
        buttons = [
            dict(
                args = [None, {"frame": {"duration": 50, "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(n_frames)], 
                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=1000, height=600,  
                  
                  updatemenus=updatemenus,
                  sliders=sliders)
fig.show()