Back

How to create animated charts with Python and Plotly

April 14, 2023 4 minute read
Stock Charts during a live trading session
Source: Unsplash

Introduction

In this short article we'll learn how to create animated charts using Python and Plotly. This follows on from the theme of the previous article How to build and visualise a Monte Carlo simulation with Python and Plotly Using these techniques can better help to tell the story when it comes to communicating data insights and changes over time periods or stages.

The best way to get started quickly with animated charts, is to learn from examples and then start to apply your own datasets to them. All the examples in this article will follow the pattern 'show me the code that generates the chart' then 'show me what that chart looks like'.

You'll see that Plotly makes generating animated charts in Python relatively straightforward, and attaches a play and stop button to the chart as standard. Auto play is enabled by default, but for the charts embedded on this page, I set this to false, so you need to hit the play button on the chart to start the animation.

There are of course options to customise Plotly charts further. All of the code and outputs for the charts can be found on GitHub.

Animated bar chart

import plotly.express as px

df = px.data.gapminder()

fig = px.bar(df, 
             x="continent", 
             y="pop", 
             animation_frame="year", 
             animation_group="country", 
             hover_name="country",
             range_y=[0,4000000000],
             color="continent",
             color_discrete_map={
                'Asia': '#1d70b8',
                'Europe': '#f47738',
                'Africa': '#28a197',
                'Americas': '#6f72af',
                'Oceania': '#d53880'
            })

fig.update_layout(
        title="Global population growth over time.",
        xaxis_title="Continent",
        yaxis_title="Population",
        legend_title="Legend Title",
        showlegend=False,
        font=dict(
            family="Arial",
            size=14
        ),
        paper_bgcolor='rgba(0,0,0,0)',
        plot_bgcolor='rgba(0,0,0,0)')

fig.write_html("outputs/animated_bar.html", auto_play=False)

Animated line chart

import plotly.graph_objects as go
import pandas as pd

dates = ["2022-12-03", "2022-12-04", "2022-12-05", "2022-12-06", "2022-12-07", "2022-12-08", "2022-12-09"]
school_a = [86.77, 80.74, 79.48, 76.47, 75.44, 74.49, 70.41]
school_b = [92.77, 91.64, 90.68, 92.37, 92.84, 90.29, 92.71]

df = pd.DataFrame(list(zip(dates, school_a, school_b)),
                  columns=['date', 'school_a', 'school_b'])

fig = go.Figure(
    layout=go.Layout(
        updatemenus=[dict(type="buttons", direction="right", x=0.9, y=1.16), ],
        xaxis=dict(range=["2022-12-02", "2022-12-10"],
                   autorange=False, tickwidth=2,
                   title_text="Time"),
        yaxis=dict(range=[0, 100],
                   autorange=False,
                   title_text="Price")
    ))

# Add traces
i = 1

fig.add_trace(
    go.Scatter(x=df.date[:i],
               y=df.school_a[:i],
               name="School A",
               visible=True,
               line=dict(color="#f47738", dash="dash")))

fig.add_trace(
    go.Scatter(x=df.date[:i],
               y=df.school_b[:i],
               name="School B",
               visible=True,
               line=dict(color="#1d70b8", dash="dash")))

# Animation
fig.update(frames=[
    go.Frame(
        data=[
            go.Scatter(x=df.date[:k], y=df.school_a[:k]),
            go.Scatter(x=df.date[:k], y=df.school_b[:k])]
    )
    for k in range(i, len(df) + 1)])

fig.update_xaxes(ticks="outside", tickwidth=2, tickcolor='white', ticklen=10)
fig.update_yaxes(ticks="outside", tickwidth=2, tickcolor='white', ticklen=1)
fig.update_layout(yaxis_tickformat=',')
fig.update_layout(legend=dict(x=0, y=1.1), legend_orientation="h")

# Buttons
fig.update_layout(title="Attendance % of two schools over time.",
                  xaxis_title="Date",
                  yaxis_title="Attendance %",
                  legend_title="Legend Title",
                  showlegend=False,
                  font=dict(
                      family="Arial",
                      size=14
                  ),
                  paper_bgcolor='rgba(0,0,0,0)',
                  plot_bgcolor='rgba(0,0,0,0)',
                  hovermode="x",
                  updatemenus=[
                        dict(
                            buttons=list([
                                dict(label="Play",
                                     method="animate",
                                     args=[None, {"frame": {"duration": 500}}]),
                                dict(label="School A",
                                     method="update",
                                     args=[{"visible": [False, True]},
                                           {"showlegend": True}]),
                                dict(label="School B",
                                     method="update",
                                     args=[{"visible": [True, False]},
                                          {"showlegend": True}]),
                                dict(label="All",
                                     method="update",
                                     args=[{"visible": [True, True, True]},
                                          {"showlegend": True}]),
                            ]))
                        ]
                    )

fig.write_html("outputs/animated_line.html", auto_play=False)

Animated scatter chart

import plotly.express as px


df = px.data.gapminder()


fig = px.scatter(df, 
                 x="gdpPercap", 
                 y="lifeExp", 
                 animation_frame="year", 
                 animation_group="country",
                 size="pop", 
                 color="continent", 
                 hover_name="country",
                 log_x=True,
                 size_max=55, 
                 range_x=[100,100000], 
                 range_y=[25,90])

fig.add_hline(y=72, 
              line_width=2, 
              line_dash='dash', 
              line_color='lightgray',
              annotation_text='',
              annotation_font=dict(
                family="Arial",
                size=15,
                color="lightgray"
               ),
               annotation_font_size=15,
               annotation_position='bottom left',
               fillcolor='lightgray')

fig.add_shape(type="line",
              x0=12000, 
              y0=0, 
              x1=12000, 
              y1=100,
              line_width=2,
              line_color='lightgray',
              line_dash='dash')

fig.add_annotation(x=0.15,
                   xref='paper',
                   yref='paper',
                   xanchor='left',
                   y=0.15,
                   yanchor='top',
                   text="Below average",
                   font=dict(
                        color="black",
                        size=20,
                        family="Arial"
                    ),
                    showarrow=False)

fig.add_annotation(x=0.85,
                   xref='paper',
                   yref='paper',
                   xanchor='left',
                   y=0.95,
                   yanchor='top',
                   text="Above average",
                   font=dict(
                        color="black",
                        size=20,
                        family="Arial"
                    ),
                    showarrow=False)


fig.add_annotation(x=.99,
                   xref='paper',
                   xanchor='right',
                   y=27,
                   yanchor='bottom',
                   text="<b>Data last updated 2008</b>",
                   font=dict(
                       color="gray",
                       size=14
                   ),
                   showarrow=False)


fig.update_layout(
        title="Global life expectancy and GDP per capita over time.",
        xaxis_title="GDP per capita",
        yaxis_title="Life expectancy",
        legend_title="Legend Title",
        showlegend=False,
        font=dict(
            family="Arial",
            size=14
        ),
        paper_bgcolor='rgba(0,0,0,0)',
        plot_bgcolor='rgba(0,0,0,0)')


fig.write_html("outputs/animated_scatter.html", auto_play=False)

Bonus: Animated choropleth map

For this chart I had to use a Jupyter Notebook launched with Anaconda with an environment dedicated to GeoPandas. I had a few issues installing GeoPandas but this method worked ok. You can view the entire notebook on GitHub.

You'll see in the Jupyter Notebook, the final cell creates the choropleth map using mapbox - you will need to sign up to get a free API key to use this.

fig = px.choropleth_mapbox(df_crime_final,
                           geojson=geojson,
                           featureidkey='properties.name',
                           locations='NEIGHBOURHOOD',
                           color='Count',
                           hover_name='NEIGHBOURHOOD',
                           hover_data=['Count'],
                           color_continuous_scale='Reds',
                           animation_frame='Date',
                           mapbox_style='carto-positron',
                           title='Cumulative Numbers of Crimes in Vancouver Neighborhoods',
                           center={'lat':49.25, 'lon':-123.13},
                           zoom=11,
                           opacity=0.75,
                           labels={'Count':'Count'},
                           width=1200,
                           height=800)

fig.write_html("outputs/animated_choropleth.html", auto_play=False)

Deployment options

Now you've seen some examples of animated charts you can start putting together your own, but what's the best way to share these charts with others? Well you could export to an HTML file the same as in this article, and then even embed that into a web page. My previous article discussed this approach, here's the code snippet which uses htmlpreview.github.io.

<iframe height="800" width="100%" loading="lazy" src="https://htmlpreview.github.io/?https://raw.githubusercontent.com/shedloadofcode/monte-carlo-simulation/main/outputs/mc-percentiles.html"></iframe>

Final note

I usually post an article every month (at least) but I missed February and March as I was busy preparing to bring my new German Shepherd puppy home. His name is Kaiser and he's settled in to the home very well 😄 I've been doing lots of training with him, teaching commands like sit, stay, down, come, leave it, out and heel. Maybe I'll write a fun article soon on that since I guess it's related to coding and logic - 'Programming my German Shepherd' 😆

I should now be back on track with my (at least) monthly new article releases. Anyway, I hope you enjoyed the article and as always be sure to check out other articles on the site. You may be interested in: