Slider not updating during animation

Hey guys, I’m new to plotly and still quite struggling with some of the syntax. I want to make the a plot with an animation button and a slider, based on a example code on the plotly website. The animation works without any problem, but I somehow can’t seem to fix the sliderbar. It won’t update (eg. show “Frame4” when the fifth frame of the animation is happening). Any ideas on where the problem may be?

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

size = 10
theta =  np.random.randint(17+1,size=size)*20*(3.14/180)
print(theta)
# Dictionary for the figure
fig_dict = {
    "data": [],
    "layout": {},
    "frames": []
}

# Add the layout
fig_dict["layout"] = dict(
    width=800,
    height=800,
    xaxis = dict(
        range = [-1, 1],
        ),
    yaxis = dict(
        range = [-1, 1],
        ),
    sliders = [dict(
        args = ["transition", {
                "duration": 400,
                "easing": "cubic-in-out"
                }],
        initialValue = "0",
        plotlycommand = "animate",
        values = [i for i in range(len(theta))],
        visible = True
        )],
    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 = {
    "active": 0,
    "yanchor": "top",
    "xanchor": "left",
    "currentvalue": {
        "font": {"size": 20},
        "prefix": "Frame:",
        "visible": True,
        "xanchor": "right"
    },
    "transition": {"duration": 300, "easing": "cubic-in-out"},
    "pad": {"b": 10, "t": 50},
    "len": 0.9,
    "x": 0.1,
    "y": 0,
    "steps": []
}

# Make each frame
for i in range(0,len(theta)-1):
    frame = {"data": [], "name": "frame" +str(i)}
    data_dict = dict(
        x = np.array(np.cos(theta[i])),
        y = np.array(np.sin(theta[i])),
        mode = "markers",
        text = "Persoon",
        marker = dict(
            color = "#010101",
            symbol = "diamond",
            size = 30),
        name = theta[i]/(3.14/180)
    )
    if (i==0):
        fig_dict["data"] = data_dict
    else:
        frame["data"] = data_dict
        fig_dict["frames"].append(frame)

    slider_step = dict(
        args = [[theta[i]],{
                    "visible": True,
                    "frame": {"duration": 300, "redraw": False},
                    "mode": "immediate",
                    "transition": {"duration": 300}}],
        label = str(i),
        method = "animate"
    )
    sliders_dict["steps"].append(slider_step)
    
fig_dict["layout"]["sliders"] = [sliders_dict]
fig = go.Figure(fig_dict)
plot(fig)

Hi @jhtong,

Your code is too dense, following this old example https://plotly.com/python/animations/#using-a-slider-and-buttons, that is not the best one to learn how to define an animation with slider.

As a rule, you have to define first fig.data and then the frames. In each frame definition you have to insert only the fig.data[0] attributes that are changing from frame to frame

I modified your code to help understand better the steps to be completed to get an animation definition:

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)
1 Like

Thanks a lot, @empet!
It’s indeed a much cleaner code and much better understandable.

I still have 2 small questions though:

  1. I’ve made the go.Scatter into a go.Scatterpolar, but now the it only does the animation and the sliders/buttons don’t work anymore. However, it did find a fix by setting the redraw's to True. I just wondered why it’s necessary here, but wasn’t previously.
  2. When I try to run it with plotly.offline.iplot, the browser never opened, but using plotly.offline.plot works. Why is that?

Below you’ll find my modified code:

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

size = 10
theta =  np.random.randint(17+1,size=size)*20 #to get a multiplicative of 20 in range(0,360)
print(theta)

fig = go.Figure(go.Scatterpolar(
                        marker= {'color': '#010101', 'size': 30, 'symbol': 4},
                        mode= 'markers',
                        name= '180.0',
                        text= 'Speaker',
                        r= [0.9],
                        theta=[theta[0]]))


# Define frames
frames = []
for k in range(len(theta)):
    frames.append(go.Frame(data= 
                    [go.Scatterpolar(theta = [theta[k]],
                                     r = [0.9])
                                     ],
                          name= f'frame{k}'))
fig.frames = frames 

updatemenus = [dict(
        buttons = [
            dict(
                args = [None, {"frame": {"duration": 500, "redraw": True},
                                "fromcurrent": True, "transition": {"duration": 300}}],
                label = "Play",
                method = "animate"
                ),
            dict(
                 args = [[None], {"frame": {"duration": 0, "redraw": True},
                                  "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=800, height=800,
                  polar = dict(
                      radialaxis = dict(range=[0, 1], showticklabels=False, ticks=''),
                      angularaxis = dict(showticklabels=True,thetaunit= "degrees", dtick=20)
                      ),
                  title = 'Meeting Room Monitoring',
                  updatemenus=updatemenus,
                  sliders=sliders)

plot(fig)

Thanks a lot!

@jhtong The animation based on your code is displayed in my notebook with both fig.show() and
iplot(fig) (plotly.offline.iplot displays Plotly plots in Jupyter notebook). Why are you expecting the browser to open using plotly.offline.iplot because its not the case?

Eventually replace this line:

from plotly.offline import iplot

by:

from plotly.offline import download_plotlyjs, init_notebook_mode,  iplot
init_notebook_mode(connected=True)

Answer to the first question : redraw=False works exclusively with go.Scatter. Animating any other trace type requires redraw =True.

I’m working with Spyder, and plotly.offline.plot opens the figure in a browser. I just thought it was supposed to do that do, my bad. I’m using Dash now thought, thanks anyway!