Search code examples
pythonplotlyplotly-python

Use button to filter different data in plotly python


I followed the answer from @PythononToast How can I add a button or dropdown in a plot created using Plotly in Python? The plot first generated is correct, but values changed after clicking the drop-down filter. As we can see from the plot he generated was not correct. May I know the reason?

import pandas as pd
import plotly.graph_objects as go

#Dummy data
df_germany = pd.DataFrame({'Fuels':[2010,2011],'Coal':[200,250],'Gas':[400,500]})
df_poland = pd.DataFrame({'Fuels':[2010,2011],'Coal':[500,150],'Gas':[600,100]})
df_spain = pd.DataFrame({'Fuels':[2010,2011],'Coal':[700,260],'Gas':[900,400]})

#put dataframes into object for easy access:
df_dict = {'Germany': df_germany, 
           'Poland': df_poland, 
           'Spain': df_spain}

#create a figure from the graph objects (not plotly express) library
fig = go.Figure()

buttons = []
i = 0

#iterate through dataframes in dict
for country, df in df_dict.items():
    #iterate through columns in dataframe (not including the year column)
    for column in df.drop(columns=['Fuels']):
        #add a bar trace to the figure for the country we are on
        fig.add_trace(go.Bar(
                          name = column,
                          #x axis is "fuels" where dates are stored as per example
                          x = df.Fuels.to_list(),
                          #y axis is the data for the column we are on
                          y = df[column].to_list(),
                          #setting only the first country to be visible as default
                          visible = (i==0)
                        )
                     )
        
    #args is a list of booleans that tells the buttons which trace to show on click
    args = [False] * len(df_dict)
    args[i] = True
    
    #create a button object for the country we are on
    button = dict(label = country,
                  method = "update",
                  args=[{"visible": args}])
    
    #add the button to our list of buttons
    buttons.append(button)
    
    #i is an iterable used to tell our "args" list which value to set to True
    i+=1
        
fig.update_layout(
    updatemenus=[
        dict(
        #change this to "buttons" for individual buttons
        type="dropdown",
        #this can be "left" or "right" as you like
        direction="down",
        #(1,1) refers to the top right corner of the plot
        x = 1,
        y = 1,
        #the list of buttons we created earlier
        buttons = buttons)
    ],
    #stacked bar chart specified here
    barmode = "stack",
    #so the x axis increments once per year
    xaxis = dict(dtick = 1))

fig.show()

Plot


Solution

  • The source of this issue is that the code does not create the buttons list correctly. If you print this list after it is created, you will get the following:

    [{'label': 'Germany', 'method': 'update', 'args': [{'visible': [True, False, False]}]},
     {'label': 'Poland', 'method': 'update', 'args': [{'visible': [False, True, False]}]},
     {'label': 'Spain', 'method': 'update', 'args': [{'visible': [False, False, True]}]}]
    

    Each list corresponding to the key 'visible' indicates which traces should be shown in the plot when a country is selected. The problem is that the nested for loops create a total of 6 traces: one each for gas and coal data for every country. Thus the list assigned to 'visible' should consist of 6 boolean values: for a given country it should have two True values, corresponding to gas and coal traces for this country. In other words, the buttons list should look as follows:

    [{'label': 'Germany', 'method': 'update', 'args': [{'visible': [True, True, False, False, False, False]}]},
     {'label': 'Poland', 'method': 'update', 'args': [{'visible': [False, False, True, True, False, False]}]},
     {'label': 'Spain', 'method': 'update', 'args': [{'visible': [False, False, False, False, True, True]}]}]
    

    Below is the code modified to fix this. It changes only the way the args list is created inside the outer for loop, since this is the list that gets assigned to 'visible'.

    import pandas as pd
    import plotly.graph_objects as go
    
    #Dummy data
    df_germany = pd.DataFrame({'Fuels':[2010,2011],'Coal':[200,250],'Gas':[400,500]})
    df_poland = pd.DataFrame({'Fuels':[2010,2011],'Coal':[500,150],'Gas':[600,100]})
    df_spain = pd.DataFrame({'Fuels':[2010,2011],'Coal':[700,260],'Gas':[900,400]})
    
    #put dataframes into object for easy access:
    df_dict = {'Germany': df_germany, 
               'Poland': df_poland, 
               'Spain': df_spain}
    
    #create a figure from the graph objects (not plotly express) library
    fig = go.Figure()
    
    buttons = []
    i = 0
    
    n_cols = len(df_germany.columns) - 1
    
    #iterate through dataframes in dict
    for country, df in df_dict.items():
    
        #iterate through columns in dataframe (not including the year column)
        for column in df.drop(columns=['Fuels']):
            #add a bar trace to the figure for the country we are on
            fig.add_trace(go.Bar(
                              name = column,
                              #x axis is "fuels" where dates are stored as per example
                              x = df.Fuels.to_list(),
                              #y axis is the data for the column we are on
                              y = df[column].to_list(),
                              #setting only the first country to be visible as default
                              visible = (i==0)
                            )
                         )
            
        #args is a list of booleans that tells the buttons which trace to show on click
        args = [False] * len(df_dict)*(n_cols)
        args[i*n_cols:(i+1)*n_cols] = [True]*n_cols
        
        #create a button object for the country we are on
        button = dict(label = country,
                      method = "update",
                      args=[{"visible": args}])
        
        #add the button to our list of buttons
        buttons.append(button)
        
        #i is an iterable used to tell our "args" list which value to set to True
        i+=1
            
    fig.update_layout(
        updatemenus=[
            dict(
            #change this to "buttons" for individual buttons
            type="dropdown",
            #this can be "left" or "right" as you like
            direction="down",
            #(1,1) refers to the top right corner of the plot
            x = 1,
            y = 1,
            #the list of buttons we created earlier
            buttons = buttons)
        ],
        #stacked bar chart specified here
        barmode = "stack",
        #so the x axis increments once per year
        xaxis = dict(dtick = 1))
    
    fig.show()