Subplot two choropleth maps with time animation next to each other

Hi everyone :slight_smile:

I would like to plot multiple choropleth maps with their own time sliders next to each other.
I have tried lots of different approaches by now, but nothing seems to work. I created a minimal example, including example data:

import pandas as pd
import plotly.express as px



def plot(df):
    fig = px.choropleth(df,
                        locations='region',
                        locationmode='country names',
                        color='valence',
                        hover_name='valence',
                        animation_frame='date',
                        projection='natural earth',

                        color_continuous_scale=px.colors.sequential.Aggrnyl,
                        range_color=(0, 1))

    fig.update_traces(marker_line_width=0)
    fig.update_layout(showlegend=False, geo=dict(showframe=False, showcoastlines=False))
    fig.update_layout(margin=dict(l=0, r=0, b=0, t=0),
                      width=1500,
                      height=800
                      )


    return fig


data_2019 = {
    'region': ['Germany', 'Denmark', 'Japan'],
    'date': ['2019-01-01', '2019-02-01', '2019-02-01'],
    'valence':[0.3, 0.21, 0.1]}

data_2020 = {
    'region': ['Germany', 'Denmark', 'Japan'],
    'date': ['2020-01-01', '2020-02-01', '2020-02-01'],
    'valence':[0.94, 0.71, 0.81]}

df_2019 = pd.DataFrame.from_dict(data_2019)
df_2020 = pd.DataFrame.from_dict(data_2020)

fig_2019 = plot(df_2019)
fig_2020 = plot(df_2020)

My goal is to have the two plots appear next to each other, both with their own time slider, but with only one colorbar on the side.

Thank you so much upfront for any advice on how to achieve this =).

I found this solution:

import pandas as pd
import plotly.graph_objects as go
import plotly.colors as pc
from plotly.subplots import make_subplots

def create_choropleth(df, color_scale, showscale=True):
    fig = go.Choropleth(
        locations=df['region'],
        locationmode='country names',
        z=df['valence'],
        text=df['valence'],
        colorscale=color_scale,
        zmin=0,
        zmax=1,
        colorbar=dict(title='Valence', x=1.05) if showscale else None,
        marker_line_width=0,
        showscale=showscale
    )

    return fig

def create_frames(df_2019, df_2020, color_scale):
    frames = []
    dates = sorted(set(df_2019['date'].unique()) | set(df_2020['date'].unique()))
    for date in dates:
        frame_df_2019 = df_2019[df_2019['date'] == date]
        frame_df_2020 = df_2020[df_2020['date'] == date]
        frames.append(go.Frame(
            data=[
                create_choropleth(frame_df_2019, color_scale),
                create_choropleth(frame_df_2020, color_scale)
            ],
            name=date
        ))
    return frames

def create_slider(dates):
    return {
        'active': 0,
        'yanchor': 'top',
        'xanchor': 'left',
        'currentvalue': {
            'font': {'size': 17},
            'prefix': 'Date: ',
            'visible': True,
            'xanchor': 'right'
        },
        'transition': {'duration': 300, 'easing': 'cubic-in-out'},
        'pad': {'b': 10, 't': 50},
        'len': 0.9,
        'x': 0.1,
        'y': -0.1,
        'steps': [{
            'args': [[date], {'frame': {'duration': 300, 'redraw': True}, 'mode': 'immediate'}],
            'label': date,
            'method': 'animate'
        } for date in dates]
    }

def create_play_pause_buttons():
    return {
        'buttons': [
            {
                'args': [None, {'frame': {'duration': 500, 'redraw': True}, 'fromcurrent': True}],
                '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': True,
        'type': 'buttons',
        'x': 0.1,
        'xanchor': 'right',
        'y': 0,
        'yanchor': 'top'
    }

def plot(df_2019, df_2020):
    color_scale = pc.sequential.Aggrnyl

    fig = make_subplots(rows=1, cols=2, subplot_titles=('2019', '2020'),
                        specs=[[{'type': 'choropleth'}, {'type': 'choropleth'}]])

    # Initial traces
    fig.add_trace(create_choropleth(df_2019[df_2019['date'] == df_2019['date'].min()], color_scale, showscale=True), row=1, col=1)
    fig.add_trace(create_choropleth(df_2020[df_2020['date'] == df_2020['date'].min()], color_scale, showscale=True), row=1, col=2)

    frames = create_frames(df_2019, df_2020, color_scale)

    fig.frames = frames

    slider = create_slider(sorted(set(df_2019['date'].unique()) | set(df_2020['date'].unique())))
    buttons = create_play_pause_buttons()

    fig.update_layout(
        sliders=[slider],
        updatemenus=[buttons],
        geo=dict(
            showframe=False,
            showcoastlines=False,
            projection_type='natural earth'
        ),
        margin=dict(l=0, r=0, b=0, t=0),
        width=1900,
        height=850
    )

    fig.update_geos(
        showframe=False,
        showcoastlines=False,
        projection_type='natural earth'
    )

    # Hide the colorbar for the second choropleth trace
    fig.data[1].update(showscale=True)

    # Make the figure take up the whole window
    fig.layout.width = None
    fig.layout.height = None

    return fig



if __name__ == '__main__':
    data_2019 = {
        'year': 2019,
        'region': ['Germany', 'Denmark', 'Japan', 'France', 'Italy', 'Spain', 'Canada', 'Brazil', 'India', 'Australia',
                   'China', 'Russia', 'South Korea', 'Netherlands', 'Sweden', 'Norway', 'Finland', 'Mexico',
                   'Argentina', 'Chile',
                   'Germany', 'Denmark', 'Japan', 'France', 'Italy', 'Spain', 'Canada', 'Brazil', 'India', 'Australia'],
        'date': ['01-01', '01-01', '01-01', '02-01', '02-01', '02-01', '03-01', '03-01', '03-01', '04-01',
                 '04-01', '04-01', '05-01', '05-01', '05-01', '06-01', '06-01', '06-01', '07-01', '07-01',
                 '08-01', '08-01', '08-01', '09-01', '09-01', '09-01', '10-01', '10-01', '10-01', '11-01'],
        'valence': [0.3, 0.25, 0.22, 0.21, 0.18, 0.15, 0.1, 0.12, 0.14, 0.15,
                    0.17, 0.19, 0.21, 0.23, 0.25, 0.27, 0.29, 0.31, 0.33, 0.35,
                    0.37, 0.39, 0.41, 0.43, 0.45, 0.47, 0.49, 0.51, 0.53, 0.55]
    }

    data_2020 = {
        'year': 2020,
        'region': ['Germany', 'Denmark', 'Japan', 'France', 'Italy', 'Spain', 'Canada', 'Brazil', 'India', 'Australia',
                   'China', 'Russia', 'South Korea', 'Netherlands', 'Sweden', 'Norway', 'Finland', 'Mexico',
                   'Argentina', 'Chile',
                   'Germany', 'Denmark', 'Japan', 'France', 'Italy', 'Spain', 'Canada', 'Brazil', 'India', 'Australia'],
        'date': ['01-01', '01-01', '01-01', '02-01', '02-01', '02-01', '03-01', '03-01', '03-01', '04-01',
                 '04-01', '04-01', '05-01', '05-01', '05-01', '06-01', '06-01', '06-01', '07-01', '07-01',
                 '08-01', '08-01', '08-01', '09-01', '09-01', '09-01', '10-01', '10-01', '10-01', '11-01'],
        'valence': [0.94, 0.89, 0.85, 0.71, 0.67, 0.63, 0.81, 0.79, 0.77, 0.65,
                    0.62, 0.61, 0.75, 0.72, 0.69, 0.85, 0.82, 0.79, 0.75, 0.72,
                    0.91, 0.88, 0.84, 0.81, 0.78, 0.74, 0.9, 0.87, 0.83, 0.8]
    }


    df_2019 = pd.DataFrame.from_dict(data_2019)
    df_2020 = pd.DataFrame.from_dict(data_2020)

    fig = plot(df_2019, df_2020)
    fig.show()

However, this version is only using one timeslider (I realized it was better this way after all). Maybe it will help someone in the future. Looks like you have to use plotly instead of plotly express.