Python: Plotly Animation Tutorial Draws Every Data instead of Step by Step


#1

Hi,

I’ve already created a stackoverflow post but I wanted to post here as well. I have plotted my data and I want my data to shift over as the slider goes, which I see some affects of it. However, its a mess of data currently, which you can see in the photo in my other post. Please help!


#2

Probably best for #api:python


#3

@kmert10
A Plotly animation code contains basic data and frame data.
A dict frame has one more key, traces, whose value is the list of integers that represent
the indices of traces in basic data, that are updated by that frame.

From your code:

for day in keys:
    data_dict = {
        'x': list(range(24)),
        'y': list(dfMay[day]),
        'mode': 'markers',
        'marker': {
            'sizemode': 'area',
            'sizeref': 200000,
            'size': 20
        },
        'name': day
    }
    figure['data'].append(data_dict)

the basic data list has N=len(keys) traces.

When you define the list of frames you should point out the basic trace that is updated by each frame, as follows:

for k, day in enumerate(keys):
    frame = {'data': [], 'name': str(day), 'traces':[k]}
    data_dict = {
        'x': list(range(24)),
        'y': list(dfMay[day]),
        'mode': 'markers',
        'text': list(dfMay[day]),
        'marker': {
            'sizemode': 'area',
            'sizeref': 200000,
            'size': 20
        },
        'name': day,
    }
    frame['data'].append(data_dict)
    figure['frames'].append(frame)

In the frame data set only the keys that have different values from the corresponding keys in basic data. Your code seems to define the same basic data and frame data.

Take a look and run this simpler animation with slider https://plot.ly/~empet/14891.


#4

@empet

I’ve implemented your solution but the problem still remains. I am trouble finding literature on the structure of plotly animated graphs.


#5

Please post a fake data file somewhere to be downloaded, and explain in words what do you intend to animate. What data are displayed initially and what updates makes each frame. Only when all these details are given I could help you. Your example replaced only data and used the code from plotly tutorial. Since it doesn’t work it’s clear that something should be changed.


#6

@empet

The data is a grid of 31 x 24: 31 days, 24 hours for each day. In the image below, I want to create a smooth animation with a play/pause button where the graph goes through each day, plotting the values of each hour.

image:

data: np.random.random((31,24))

The image was created with another code, its there as an example!

FULL CODE:

# Animated Plot
figure = {
    'data': [],
    'layout': {},
    'frames': []
}

# fill in most of layout
figure['layout']['xaxis'] = {'range': [0, 23], 'title': 'Zaman (saat)'}
figure['layout']['yaxis'] = {'title': 'Kullanıcı Sayısı', 'type': 'linear'}
figure['layout']['title'] = 'Mayıs-Günlük Kullanıcı Sayıları'
figure['layout']['hovermode'] = 'closest'
figure['layout']['sliders'] = {
    'args': [
        'transition', {
            'duration': 400,
            'easing': 'cubic-in-out'
        }
    ],
    'initialValue': '0',
    'plotlycommand': 'animate',
    'values': list(range(24)),
    'visible': True
}
figure['layout']['updatemenus'] = [
    {
        'buttons': [
            {
                'args': [None, {'frame': {'duration': 500, 'redraw': True},
                         'fromcurrent': True, 'transition': {'duration': 300, 'easing': 'quadratic-in-out'}}],
                'label': 'Play',
                'method': 'animate'
            },
            {
                '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 = {
    'active': 0,
    'yanchor': 'top',
    'xanchor': 'left',
    'currentvalue': {
        'font': {'size': 20},
        'prefix': 'Gün:',
        '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 data
start = 0
for day in keys:
    data_dict = {
        'x': list(range(24)),
        'y': list(dfMay[day]),
        'mode': 'markers',
        'marker': {
            'sizemode': 'area',
            'sizeref': 200000,
            'size': 15
        },
        'name': day
    }
    figure['data'].append(data_dict)
    
# make frames
for k, day in enumerate(keys):
    frame = {'data': [], 'name': str(day), 'traces':[k]}
    data_dict = {
        'x': list(range(24)),
        'y': list(dfMay[day]),
        'mode': 'markers',
        'text': list(dfMay[day]),
        'marker': {
            'sizemode': 'area',
            'sizeref': 200000,
            'size': 20
        },
        'name': day,
    }
    frame['data'].append(data_dict)
    figure['frames'].append(frame)
    slider_step = {'args': [
        [day],
        {'frame': {'duration': 200, 'redraw': True},
         'mode': 'immediate',
         'transition': {'duration': 200}}
     ],
     'label': day,
     'method': 'animate'}
    sliders_dict['steps'].append(slider_step)

    
figure['layout']['sliders'] = [sliders_dict]
print('Done')
plot(figure)

#7

You did not specify whether you’d like to display points simultaneously for all 24 hours or successively.
Here is the animation https://plot.ly/~empet/14896 for the former case.


#8

Exactly like the last plot. Thank you very much!!


#9

@empet Hi again, I have another problem! I am trying to auto scale my y-axis but its failing to do so. How can I achieve it?

plotData=[dict(type=‘scatter’,
x=list(range(24)),
y=test_data[:0],
mode=‘markers’,
marker=dict(size=10, color=‘red’))
]

frames=[dict(data=[dict(y=test_data[:,k])],
traces=[0],
name=f’{k+1}’) for k in range(31)]

sliders=[dict(steps= [dict(method= ‘animate’,
args= [[ f’{k+1}’],
dict(mode= ‘e’,
frame= dict( duration=1000, redraw= False ),
transition=dict( duration= 0)
)
],
label=f’day {k+1}’
) for k in range(31)],
transition= dict(duration= 30 ),
x=0,#slider starting position
y=0,
currentvalue=dict(font=dict(size=12),
prefix='Day: ',
visible=True,
xanchor= ‘center’
),
len=1.0,
active=1) #slider length)

       ]

axis_style=dict(showline=True,
mirror=True,
zeroline=False,
ticklen=4)

layout=dict(title=‘Mayıs-Günlük Kullanıcı Sayıları’,
width=900,
height=600,
autosize=False,
xaxis=dict(axis_style, title=‘Zaman (saat)’, **dict(range=[0,24])),
yaxis=dict(axis_style, title=‘Kullanıcı Sayısı’,auto=True),
hovermode=‘closest’,
updatemenus=[dict(type=‘buttons’, showactive=True,
y=0,
x=1.15,
xanchor=‘right’,
yanchor=‘top’,
pad=dict(t=0, r=10),
buttons=[dict(label=‘Play’,
method=‘animate’,
args=[None,
dict(frame=dict(duration=2000,
redraw=True),
transition=dict(duration=2000),
fromcurrent=True,
mode=‘immediate’
)
]
),
dict(label=‘Pause’,
method=‘animate’,
args=[[None],
dict(frame=dict(duration=0,
redraw=False),
transition=dict(duration=30),
fromcurrent=True,
mode=‘immediate’
)
]
)
]
)
],
sliders=sliders
)

Animated Plot

figure = {
‘data’: plotData,
‘layout’: layout,
‘frames’: frames
}
fig=dict(data=plotData, frames=frames, layout=layout)
fig[‘data’][0].update(mode=‘markers+lines’,
line=dict(width=1.5, color=‘blue’))
plot(fig, auto_open=False)


#10

@kmert10 I tested previously with autorange=True, not False, but the range set by default (auto) is smaller than [ymin, ymax] and some points are not displayed during animation.


#11

Exactly, do you have any ideas why it is the case?


#12

@kmert10, I suppose that you want to see, during the animation, how the range of y is changing.
If this is a case, define the frames as follows:

frames=[dict(data=[dict(y=test_data[:,k])],
             traces=[0],
             name=f'{k+1}',
             layout=dict(yaxis=dict(range=[test_data[:, k].min()-1, test_data[:, k].max()+1]))) for k in range(31)]

i.e. update the yaxis range in each step.

In the initial definition of layout, set the yaxis dict with autorange=False

yaxis=dict(axis_style, title=‘Kullanıcı Sayısı’, autorange=False)

Clicking the button Play you’ll see how the range changes.


#13

Amazing, thank you very much!


#14

Is there a way to make the animation transitions faster and not bouncy? For each frame, the plot is drawn, and then the y-axis is adjusted. Is there a way to make the scatter markers transition to their new points as the same animation as well?


#15

To get a faster transition, set a smaller duration. The rate of change is controlled by the easing function. It is set within the transition dict from sliders as:
easing='easing-function-name': https://github.com/plotly/plotly.js/blob/master/src/components/sliders/attributes.js#L165

The list of easing functions is given here:
https://github.com/plotly/plotly.js/blob/master/src/plots/animation_attributes.js#L77,
and you can see see how the transition occurs in each case, here https://easings.net/.

In updatemenus you also have a dict transition, where you can set the key easing:

args=[None, 
      dict(frame=dict(duration=200, 
                      redraw=False),
                      transition=dict(duration=100, easing='elastic'),
                      fromcurrent=True,
                      mode='immediate' )
     ]

#16

Thanks @empet ! Also, the initial graph doesn’t follow any of these rules when I first open it in browser. After I Pause, Play, it works. Is there a way to automatically fix this?


#17

Unfortunately there is no way at the moment :frowning:


#18

Should I embed this in the layout of “frames”, “buttons” or “slider”?


#19

It is already embedded in the updademenus. You have only to insert the key-value pair, easing=‘easing_function_name’


#20

You’re awesome! Thank you so much for your support!