Search code examples
pythonplotlyslider

Can you share a figure slider between two subpanels with plotly.express?


I want to have a figure with two subplots which are both updated using the same x axis slider with plotly.express.

I have successfully done a single subplot with a slider using a command like:

import plotly.express as px

px.line(plot_df, x='context', y='probability',animation_frame='trial',animation_group='t', color="t", range_y=[0,1])

but I have no idea how to extend it to having two subplots controlled by the same slider or if this is possible at all. I can obviously add another subplot in the same manner but then the frames will not update synchronously. Any suggestions? Thank you.

EDIT

Question wasn't very clear so I am providing example data and extra description:

# Example Data for plot A
npi = 8
nc = 4
data = np.zeros([npi,nc,t])
for c in range(nc):
    data[:,c,:] = np.tile(np.arange(npi).reshape(npi,1),t) +     np.random.normal(0,2,size=[npi,t])

pols = np.arange(npi)
ts = np.arange(t)
cs = np.arange(nc)

mi = pd.MultiIndex.from_product([pols, cs, ts], names=['policy', 'context', 'trial'])
df = pd.Series(index=mi, data=data.flatten())
df = df.reset_index().rename(columns = {0:'probability'})

# Desired animation for plot A
px.line(df, x='policy', y='probability',animation_frame='trial',animation_group='context', color="context")

# Example Data for plot B
t = 10
nc = 6
tau = 4
data = np.zeros([nc,tau,t])
for ti in range(tau):
    data[:,ti,:] = np.tile(np.arange(nc).reshape(nc,1),t) + np.random.normal(0,2,size=[nc,t])

conts = np.arange(nc)
ts = np.arange(t)
taus = np.arange(tau)

mi = pd.MultiIndex.from_product([conts, taus, ts], names=['context', 'timepoint', 'trial'])
df = pd.Series(index=mi, data=data.flatten())
df = df.reset_index().rename(columns = {0:'probability'})

# Desired animation for plot B
px.line(df, x='context', y='probability',animation_frame='trial',animation_group='timepoint', color="timepoint")

I want a figure where subplot A and B are plotted next to each other and controlled by the same "trial" slider so I can see their evolution side by side.


Solution

    • taking your generator code. fig1 is first figure, fig2 is second figure
    • now create a new figure that integrates all the traces and frames from both figures. For traces / frames from second figure put them on x2 / y2 axes
    • finally fix up integrated layout
    import plotly.graph_objects as go
    
    # integrate the two figures, putting second figure on separate axis
    fig = go.Figure(
        data=[t for t in fig1.data] + [t.update(xaxis="x2", yaxis="y2") for t in fig2.data],
        frames=[
            go.Frame(
                name=fr1.name,
                data=[t for t in fr1.data]
                + [t.update(xaxis="x2", yaxis="y2") for t in fr2.data],
            )
            for fr2, fr1 in zip(fig2.frames, fig1.frames)
        ],
        layout=fig1.layout,
    )
    
    # now config axes appropriately
    fig.update_layout(
        xaxis_domain=[0, 0.49],
        xaxis2={"domain": [0.51, 1], "matches": None, "title":{"text":fig2.layout.xaxis.title.text}},
        yaxis2={"matches":"y"},
        showlegend=False,
    )
    
    
    

    enter image description here

    generator code

    import pandas as pd
    import numpy as np
    import plotly.express as px
    
    # Example Data for plot A
    t = 10
    npi = 8
    nc = 4
    data = np.zeros([npi,nc,t])
    for c in range(nc):
        data[:,c,:] = np.tile(np.arange(npi).reshape(npi,1),t) +     np.random.normal(0,2,size=[npi,t])
    
    pols = np.arange(npi)
    ts = np.arange(t)
    cs = np.arange(nc)
    
    mi = pd.MultiIndex.from_product([pols, cs, ts], names=['policy', 'context', 'trial'])
    df = pd.Series(index=mi, data=data.flatten())
    df = df.reset_index().rename(columns = {0:'probability'})
    
    # Desired animation for plot A
    fig1 = px.line(df, x='policy', y='probability',animation_frame='trial',animation_group='context', color="context")
    
    # Example Data for plot B
    t = 10
    nc = 6
    tau = 4
    data = np.zeros([nc,tau,t])
    for ti in range(tau):
        data[:,ti,:] = np.tile(np.arange(nc).reshape(nc,1),t) + np.random.normal(0,2,size=[nc,t])
    
    conts = np.arange(nc)
    ts = np.arange(t)
    taus = np.arange(tau)
    
    mi = pd.MultiIndex.from_product([conts, taus, ts], names=['context', 'timepoint', 'trial'])
    df = pd.Series(index=mi, data=data.flatten())
    df = df.reset_index().rename(columns = {0:'probability'})
    
    # Desired animation for plot B
    fig2 = px.line(df, x='context', y='probability',animation_frame='trial',animation_group='timepoint', color="timepoint")
    
    fig2