Search code examples
pythongraphplotlydropdown

How to implement dropdown in Plotly that will reupdate multiple graph instead of single graph


I am using dropdown to filter state in the line graph below. Here d is the dataframe, ther are 16 of them. In every dataframe I am trying to plot week as x axis and other values as y axis or traces in each graph. So far I am able to get dropdown menu for every 16 graph which would reupdate only given graph. Instead I want single dropdown value to update all the 16 graphs based on state selected on dropdown. Please see image attached.

This is the image which shows dropdown for every graph

This is my code:

state_list = list(df['GeogName'].unique())
for item in d.values(): #d is dictionary which has 16 dataframe as value, item is datafrme here.
    fig=go.Figure()
    state_plot_names = []
    buttons=[]
    default_state = "NY"

    for state_name in state_list:
        state_df = item[item["GeogName"]== region_name]
        for i in range(5): #adding 5 other traces as value in graph
            fig.add_trace(go.Scatter(x=state_df['Week'], y =state_df.iloc[:,i], line={}, visible=(state_name==default_state)))
        state_plot_names.extend([state_name])
    
    for state_name in state_list :
        buttons.append(dict(method='update',
                        label=state_name ,
                        args = [{'visible': [state_name ==s for s in state_plot_names]}]))


    fig.update_layout(autosize=False,
    width=1000,
    height=700,showlegend=True, updatemenus=[{"buttons": buttons, "direction": "down", "active": state_list.index(default_state), "showactive": True, "x": 0.5, "y": 1.15}])
    fig.show()

Solution

    • have synthesised the dict of data frames you have described in comments, taking into account value column names are different in every data frame
    • as noted, a drop down menu can only operate on a single figure. Hence only option is to move to subplots
    • the simplest way to achieve this is to structure all of the data frames into a single dataframe that can be used as an input to Plotly Express
    • can now created a *facetted (subplots) figure
    • start by making default state (NY) only visible traces
    • build drop down menu to control visibility for selection of other states
    • did a bit of work on spacing between subplots as 16 leads to a lot of white space
    import numpy as np
    import pandas as pd
    import plotly.express as px
    
    # replicate data described in question
    states = ["NY", "NH", "IN", "CT", "MT", "VA", "ME", "SD", "KS", "MN"]
    sigma = 0.01
    d = {}
    for di in range(16):
        df = pd.DataFrame()
        for s, (state, mu) in enumerate(
            zip(states, np.random.uniform(0.001, 0.005, len(states)))
        ):
            np.random.seed((s + 1) * di)
            start_price = np.random.uniform(1, 5, [5, 1])
            returns = np.random.normal(loc=mu, scale=sigma, size=100)
            df = pd.concat(
                [
                    df,
                    pd.DataFrame(
                        (start_price * (1 + returns).cumprod()).T,
                        columns=[f"value{di}_{n}" for n in range(5)],
                    )
                    .assign(GeogName=state)
                    .reset_index()
                    .rename(columns={"index": "Week"}),
                ]
            )
        d[chr(ord("A") + di)] = df
    
    # integrate all dataframes so it's simple to use plotly express
    # encode column name and GeogName into single column for definition of trace
    df = pd.concat(
        [
            df.set_index(["Week", "GeogName"])
            .stack()
            .reset_index()
            .rename(columns={"level_2": "trace", 0: "value"})
            .assign(dataframe=k, trace=lambda d: d["GeogName"]+d["trace"])
            for k, df in d.items()
        ]
    )
    
    fig = px.line(
        df,
        x="Week",
        y="value",
        color="trace",
        facet_row="dataframe",
    )
    
    # default state...
    fig.for_each_trace(lambda t: t.update(visible=(t.name[0:2] == "NY")))
    
    fig.update_layout(
        updatemenus=[
            {
                "buttons": [
                    {"label": state, "method": "restyle", "args": [{"visible":[t.name[0:2]==state for t in fig.data]}]}
                    for state in df["GeogName"].unique()
                ],
                "y":1.01,
                "x":.5
            }
        ],
        autosize=False,
        height=1500
    )
    
    # compress up space between subplots
    fig.update_layout({
        f"yaxis{'' if axis==0 else axis+1}": {"domain": [s, e-.002]}
        for axis, (s, e) in enumerate(
            zip(np.linspace(0, 1, len(d.keys())+1), np.linspace(0, 1, len(d.keys())+1)[1:])
        )
    })
    

    enter image description here