Search code examples
pythonplotlyscatter-plotplotly-python

How to update data of multiple scatter plots with dropdown buttons in plotly python?


I have two scatter plots of two variables/arrays on same axis. I want to add a dropdown which updates the data of both variables/arrays.

import numpy as np
import pandas as pd
from plotly import graph_objects as go

scen3_df = pd.DataFrame(np.random.randint(10, 20, (100, 8)), columns=['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])
orig_df = pd.DataFrame(np.random.randint(0, 10, (100, 8)), columns=['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])

first_title = scen3_df.columns.to_list()[0]
traces = []
buttons = []
for idx, col in enumerate(scen3_df.columns):

    visible = [False]*8
    visible[idx] = True
    traces.append(go.Scatter(x=scen3_df.index, y=scen3_df[col],
                             name="Scenario 3",
                             visible = True if idx==0 else False,
                             ))

    traces.append(go.Scatter(x=scen3_df.index, y=orig_df[col],
                             name="Original",
                             visible = True if idx==0 else False,
                             ))

    buttons.append(dict(label=col,
                        method="update",
                        args=[{"visible": visible},
                              {"title": f" Gate operation at {col}"}]
                        ))


updatemenus = [{'active':0, "buttons":buttons}]

fig = go.Figure(data=traces,
                 layout=dict(updatemenus=updatemenus))
fig.update_layout(title=first_title, title_x=0.5)
fig.update_yaxes(range=[0, scen3_df.max()], title="Gate Height (m)")
fig.update_xaxes(title="Time (Julian Day)")
fig.show()
fig.write_html("gate_operations.html")

What I want What I want

What I am currently getting What I get


Solution

    • consistency is always the key when building these types of figure and menus
    • it's far simpler to achieve consistency with Plotly Express so I have switched to this instead of graph objects
    • build two figures for the two data frames, then integrate them. Name is overridden to ensure legend appears as you want
    • having built figure, have all required information within it to build menu as nested list comprehensions
    import numpy as np
    import pandas as pd
    import plotly.express as px
    
    scen3_df = pd.DataFrame(np.random.randint(10, 20, (100, 8)), columns=['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])
    orig_df = pd.DataFrame(np.random.randint(0, 10, (100, 8)), columns=['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])
    
    
    
    # generate equivalent figures for both data frames
    figs = [
        px.line(df, y=df.columns)
        .update_traces(line_color=color, name=name)
        .for_each_trace(lambda t: t.update(visible=t.legendgroup == df.columns[0]))
        for df, color, name in zip(
            [scen3_df, orig_df], ["blue", "red"], ["Scenario 3", "Original"]
        )
    ]
    
    # construct overall figure
    fig = (
        figs[0]
        .add_traces(figs[1].data)
        .update_layout(xaxis_title="Time (Julian Day)", yaxis_title="Gate Height (m)")
    )
    # build the menu
    fig.update_layout(
        updatemenus=[
            {
                "buttons": [
                    {
                        "label": col,
                        "method": "update",
                        "args": [
                            {"visible": [t.legendgroup == col for t in fig.data]},
                            {"title": f" Gate operation at {col}"},
                        ],
                    }
                    for col in scen3_df.columns
                ]
            }
        ]
    )
    

    enter image description here