Show Sidebar Hide Sidebar

Intro to Animations in Python

An introduction to creating animations with Plotly in Python.

New to Plotly?

Plotly's Python library is free and open source! Get started by downloading the client and reading the primer.
You can set up Plotly to work in online or offline mode, or in jupyter notebooks.
We also have a quick-reference cheatsheet (new!) to help you get started!

Version Check

Note: Animations are available in version 1.12.10+ Run pip install plotly --upgrade to update your Plotly version.

In [1]:
import plotly
plotly.__version__
Out[1]:
'1.12.12'

Frames

Now, along with data and layout, frames is added to the keys that figure allows. Your frames key points to a list of figures, each of which will be cycled through upon instantiation of the plot.

Online Mode

You can use plotly.plotly.icreate_animations() or plotly.plotly.create_animations() to make online animations that you save on the Plotly cloud.

There are two steps for making an online animation:

  1. Make a grid
  2. Make the plot with data from the grid

The reason for making a grid is because animations are created through our v2 api. In this process, we create a grid composed of columns, and then make a plot which contains referenced data from the grid columns. You can learn how to upload a grid at the grid endpoint of the and how to make a plot with a grid at the plot endpoint of the v2 api.

A grid consists of columns which fundamentally are 1D lists of numerical data with an associated name. They are instantiated with the grid_objs class Column. To make a column, simply assign a varaible with a Column:

$$ \begin{align*} Column([...], name) \end{align*} $$

The Grid class is also part of the grid_objs module. A Grid takes a list of columns:

$$ \begin{align*} grid = Grid([column_1, column_2, ...]) \end{align*} $$

Please Note: filenames MUST BE unique. An error will be thrown if a grid is not created with a unique filename. Therefore we reccomend appending a timestamp to your grid filename to ensure the filename is unique.

In [2]:
import plotly.plotly as py
from plotly.grid_objs import Grid, Column

import time

column_1 = Column([0.5], 'x')
column_2 = Column([0.5], 'y')
column_3 = Column([1.5], 'x2')
column_4 = Column([1.5], 'y2')

grid = Grid([column_1, column_2, column_3, column_4])
py.grid_ops.upload(grid, 'ping_pong_grid'+str(time.time()), auto_open=False)
Out[2]:
u'https://plot.ly/~chelsea_lyn/13819/'

Now you need to reference the columns from the grid that you just uploaded. You can do so by using the built-in grid method get_column_reference() which takes the column name as its argument and returns the reference to the data in the grid. Since we are dealing with referenced data which is pointing to data and not raw data, we use xsrc and ysrc in the figure to represent the x and y analogues that are normally used.

Make your figure and create an animated plot!

In [3]:
figure = {
    'data': [
        {
            'xsrc': grid.get_column_reference('x'),
            'ysrc': grid.get_column_reference('y'),
            'mode': 'markers',
        }
    ],
    'layout': {'title': 'Ping Pong Animation',
               'xaxis': {'range': [0, 2], 'autorange': False},
               'yaxis': {'range': [0, 2], 'autorange': False},
               'updatemenus': [{
                   'buttons': [
                       {'args': [None],
                        'label': 'Play',
                        'method': 'animate'}
               ],
               'pad': {'r': 10, 't': 87},
               'showactive': False,
               'type': 'buttons'
                }]},
    'frames': [
        {
            'data': [
                {
                    'xsrc': grid.get_column_reference('x2'),
                    'ysrc': grid.get_column_reference('y2'),
                    'mode': 'markers',
                }
            ]
        },
        {
            'data': [
                {
                    'xsrc': grid.get_column_reference('x'),
                    'ysrc': grid.get_column_reference('y'),
                    'mode': 'markers',
                }
            ]
        }
    ]
}

py.icreate_animations(figure, 'ping_pong'+str(time.time()))
Out[3]:

Adding Control Buttons to Animations

You can add play and pause buttons to control your animated charts by adding an updatemenus array to the layout of your figure. More information on style and placement of the buttons is available in Plotly's updatemenus reference.

The buttons are defined as follows:

'updatemenus': [{'type': 'buttons',
                 'buttons': [{'label': 'Your Label',
                              'method': 'animate',
                              'args': [See Below]}]}]

Defining Button Arguments

  • None: Setting 'args' to undefined (i.e. 'args': [None]) will create a simple play button that will animate all frames.
  • string: Animate all frames with group '<some string>'. This is a way of scoping the animations in case you would prefer to animate without explicitly enumerating all frames.
  • ['frame1', 'frame2', ...]: Animate a sequence of named frames.
  • [{data: [], layout: {}, traces: []}, {...}]: Nearly identical to animating named frames; though this variant lets you inline data instead of adding it as named frames. This can be useful for interaction where it's undesirable to add and manage named frames for ephemeral changes.
  • [null]: A simple way to create a pause button (requires mode: 'immediate'). This argument dumps the currently queued frames (mode: 'immediate'), and then animates an empty sequence of frames ([null]).
  • Please Note: We do not recommend using: [ ]. This syntax may cause confusion because it looks indistinguishable from a "pause button", but nested properties have logic that treats empty arrays as entirely removable, so it will function as a play button.

    Refer to the examples below to see the buttons in action!

Points Changing Size

In [4]:
import plotly.plotly as py
from plotly.grid_objs import Grid, Column

import time

column_1 = Column([0.9, 1.1], 'x')
column_2 = Column([1.0, 1.0], 'y')
column_3 = Column([0.8, 1.2], 'x2')
column_4 = Column([1.2, 0.8], 'y2')
column_5 = Column([0.7, 1.3], 'x3')
column_6 = Column([0.7, 1.3], 'y3')
column_7 = Column([0.6, 1.4], 'x4')
column_8 = Column([1.5, 0.5], 'y4')
column_9 = Column([0.4, 1.6], 'x5')
column_10 = Column([1.2, 0.8], 'y5')

grid = Grid([column_1, column_2, column_3, column_4, column_5,
             column_6, column_7, column_8, column_9, column_10])
py.grid_ops.upload(grid, 'points_changing_size_grid'+str(time.time()), auto_open=False)

# create figure
figure = {
    'data': [
        {
            'xsrc': grid.get_column_reference('x'),
            'ysrc': grid.get_column_reference('y'),
            'mode': 'markers',
            'marker': {'color': '#48186a', 'size': 10}
        }
    ],
    'layout': {'title': 'Growing Circles',
               'xaxis': {'range': [0, 2], 'autorange': False},
               'yaxis': {'range': [0, 2], 'autorange': False},
               'updatemenus': [{
                   'buttons': [
                       {'args': [None],
                        'label': 'Play',
                        'method': 'animate'}
               ],
               'pad': {'r': 10, 't': 87},
               'showactive': False,
               'type': 'buttons'
                }]},
    'frames': [
        {
            'data': [
                {
                    'xsrc': grid.get_column_reference('x2'),
                    'ysrc': grid.get_column_reference('y2'),
                    'mode': 'markers',
                    'marker': {'color': '#3b528b', 'size': 25}
                }
            ]
        },
        {
            'data': [
                {
                    'xsrc': grid.get_column_reference('x3'),
                    'ysrc': grid.get_column_reference('y3'),
                    'mode': 'markers',
                    'marker': {'color': '#26828e', 'size': 50}
                }
            ]
        },
        {
            'data': [
                {
                    'xsrc': grid.get_column_reference('x4'),
                    'ysrc': grid.get_column_reference('y4'),
                    'mode': 'markers',
                    'marker': {'color': '#5ec962', 'size': 80}
                }
            ]
        },
        {
            'data': [
                {
                    'xsrc': grid.get_column_reference('x5'),
                    'ysrc': grid.get_column_reference('y5'),
                    'mode': 'markers',
                    'marker': {'color': '#d8e219', 'size': 100}
                }
            ]
        }
    ]
}
py.icreate_animations(figure, 'points_changing_size'+str(time.time()))
Out[4]:

Offline Mode

Animations can be created either offline or online. To learn about how to set up working offline, check out the offline documentation.

Basic Example

To re-run the animation see the following example with a play button.

In [5]:
from plotly.offline import init_notebook_mode, iplot
from IPython.display import display, HTML

init_notebook_mode(connected=True)

figure = {'data': [{'x': [0, 1], 'y': [0, 1]}],
          'layout': {'xaxis': {'range': [0, 5], 'autorange': False},
                     'yaxis': {'range': [0, 5], 'autorange': False},
                     'title': 'Start Title'},
          'frames': [{'data': [{'x': [1, 2], 'y': [1, 2]}]},
                     {'data': [{'x': [1, 4], 'y': [1, 4]}]},
                     {'data': [{'x': [3, 4], 'y': [3, 4]}],
                      'layout': {'title': 'End Title'}}]}

iplot(figure)

Simple Play Button

In [6]:
from plotly.offline import init_notebook_mode, iplot
from IPython.display import display, HTML

init_notebook_mode(connected=True)

figure = {'data': [{'x': [0, 1], 'y': [0, 1]}],
          'layout': {'xaxis': {'range': [0, 5], 'autorange': False},
                     'yaxis': {'range': [0, 5], 'autorange': False},
                     'title': 'Start Title',
                     'updatemenus': [{'type': 'buttons',
                                      'buttons': [{'label': 'Play',
                                                   'method': 'animate',
                                                   'args': [None]}]}]
                    },
          'frames': [{'data': [{'x': [1, 2], 'y': [1, 2]}]},
                     {'data': [{'x': [1, 4], 'y': [1, 4]}]},
                     {'data': [{'x': [3, 4], 'y': [3, 4]}],
                      'layout': {'title': 'End Title'}}]}

iplot(figure)

Moving Point on a Curve

In [7]:
from plotly.offline import init_notebook_mode, iplot
from IPython.display import display, HTML
import numpy as np

init_notebook_mode(connected=True)

t=np.linspace(-1,1,100)
x=t+t**2
y=t-t**2
xm=np.min(x)-1.5
xM=np.max(x)+1.5
ym=np.min(y)-1.5
yM=np.max(y)+1.5
N=50
s=np.linspace(-1,1,N)
xx=s+s**2
yy=s-s**2


data=[dict(x=x, y=y, 
           mode='lines', 
           line=dict(width=2, color='blue')
          ),
      dict(x=x, y=y, 
           mode='lines', 
           line=dict(width=2, color='blue')
          )
    ]

layout=dict(xaxis=dict(range=[xm, xM], autorange=False, zeroline=False),
            yaxis=dict(range=[ym, yM], autorange=False, zeroline=False),
            title='Kinematic Generation of a Planar Curve', hovermode='closest',
            updatemenus= [{'type': 'buttons',
                           'buttons': [{'label': 'Play',
                                        'method': 'animate',
                                        'args': [None]}]}])

frames=[dict(data=[dict(x=[xx[k]], 
                        y=[yy[k]], 
                        mode='markers', 
                        marker=dict(color='red', size=10)
                        )
                  ]) for k in range(N)]    
          
figure1=dict(data=data, layout=layout, frames=frames)          
iplot(figure1)

Moving Frenet Frame Along a Planar Curve

In [8]:
from plotly.offline import init_notebook_mode, iplot
from IPython.display import display, HTML
import numpy as np

init_notebook_mode(connected=True)

N=50
s=np.linspace(-1,1,N)
vx=1+2*s
vy=1-2*s #v=(vx, vy) is the velocity
speed=np.sqrt(vx**2+vy**2)
ux=vx/speed #(ux, uy) unit tangent vector, (-uy, ux) unit normal vector
uy=vy/speed

xend=xx+ux #end coordinates for the unit tangent vector at (xx, yy)
yend=yy+uy

xnoe=xx-uy #end coordinates for the unit normal vector at (xx,yy)
ynoe=yy+ux


data=[dict(x=x, y=y,
           name='frame',
           mode='lines', 
           line=dict(width=2, color='blue')),
      dict(x=x, y=y,
           name='curve',
           mode='lines', 
           line=dict(width=2, color='blue'))
    ]

layout=dict(width=600, height=600,
            xaxis=dict(range=[xm, xM], autorange=False, zeroline=False),
            yaxis=dict(range=[ym, yM], autorange=False, zeroline=False),
            title='Moving Frenet Frame Along a Planar Curve', hovermode='closest',
            updatemenus= [{'type': 'buttons',
                           'buttons': [{'label': 'Play',
                                        'method': 'animate',
                                        'args': [None]}]}])

frames=[dict(data=[dict(x=[xx[k], xend[k], None, xx[k], xnoe[k]], 
                        y=[yy[k], yend[k], None, yy[k], ynoe[k]], 
                        mode='lines', 
                        line=dict(color='red', width=2))
                  ]) for k in range(N)]    
          
figure2=dict(data=data, layout=layout, frames=frames)          
iplot(figure2)

Using a Slider and Buttons

The following example uses the well known Gapminder dataset to exemplify animation capabilites. This bubble chart animation shows the change in 'GDP per Capita' against the 'Life Expectancy' of several countries from the year 1952 to 2007, colored by their respective continent and sized by population.

In [9]:
from plotly.offline import init_notebook_mode, iplot
from IPython.display import display, HTML

import pandas as pd

init_notebook_mode(connected=True)

url = 'https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv'
dataset = pd.read_csv(url)

years = ['1952', '1962', '1967', '1972', '1977', '1982', '1987', '1992', '1997', '2002', '2007']
# make list of continents
continents = []
for continent in dataset['continent']:
    if continent not in continents:
        continents.append(continent)
# make figure
figure = {
    'data': [],
    'layout': {},
    'frames': [],
    'config': {'scrollzoom': True}
}

# fill in most of layout
figure['layout']['xaxis'] = {'range': [30, 85], 'title': 'Life Expectancy'}
figure['layout']['yaxis'] = {'title': 'GDP per Capita', 'type': 'log'}
figure['layout']['hovermode'] = 'closest'
figure['layout']['slider'] = {
    'args': [
        'slider.value', {
            'duration': 400,
            'ease': 'cubic-in-out'
        }
    ],
    'initialValue': '1952',
    'plotlycommand': 'animate',
    'values': years,
    'visible': True
}
figure['layout']['updatemenus'] = [
    {
        'buttons': [
            {
                'args': [None, {'frame': {'duration': 500, 'redraw': False},
                         'fromcurrent': True, 'transition': {'duration': 300, 'easing': 'quadratic-in-out'}}],
                'label': 'Play',
                'method': 'animate'
            },
            {
                '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': 'Year:',
        '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
year = 1952
for continent in continents:
    dataset_by_year = dataset[dataset['year'] == year]
    dataset_by_year_and_cont = dataset_by_year[dataset_by_year['continent'] == continent]

    data_dict = {
        'x': list(dataset_by_year_and_cont['lifeExp']),
        'y': list(dataset_by_year_and_cont['gdpPercap']),
        'mode': 'markers',
        'text': list(dataset_by_year_and_cont['country']),
        'marker': {
            'sizemode': 'area',
            'sizeref': 200000,
            'size': list(dataset_by_year_and_cont['pop'])
        },
        'name': continent
    }
    figure['data'].append(data_dict)
    
# make frames
for year in years:
    frame = {'data': [], 'name': str(year)}
    for continent in continents:
        dataset_by_year = dataset[dataset['year'] == int(year)]
        dataset_by_year_and_cont = dataset_by_year[dataset_by_year['continent'] == continent]

        data_dict = {
            'x': list(dataset_by_year_and_cont['lifeExp']),
            'y': list(dataset_by_year_and_cont['gdpPercap']),
            'mode': 'markers',
            'text': list(dataset_by_year_and_cont['country']),
            'marker': {
                'sizemode': 'area',
                'sizeref': 200000,
                'size': list(dataset_by_year_and_cont['pop'])
            },
            'name': continent
        }
        frame['data'].append(data_dict)

    figure['frames'].append(frame)
    slider_step = {'args': [
        [year],
        {'frame': {'duration': 300, 'redraw': False},
         'mode': 'immediate',
       'transition': {'duration': 300}}
     ],
     'label': year,
     'method': 'animate'}
    sliders_dict['steps'].append(slider_step)

    
figure['layout']['sliders'] = [sliders_dict]

iplot(figure)

Important Notes

  • Defining redraw: Setting redraw: false is an optimization for scatter plots so that animate just makes changes without redrawing the whole plot. For other plot types, such as contour plots, every frame must be a total plot redraw, i.e. redraw: true.

Reference

For additional information and attributes for creating bubble charts in Plotly see: https://plot.ly/python/bubble-charts/. For more documentation on creating animations with Plotly, see https://plot.ly/python/#animations.

Still need help?
Contact Us

For guaranteed 24 hour response turnarounds, upgrade to a Developer Support Plan.