Update_layout versus callback for checklists and dropdowns

I have a line plot with a range slider and a dropdown to dynamically update the plot and this works very nicely, however, I would also like to remake this with a checklist instead of a dropdown, sounds simple but I’m struggling to display the data so it looks the same as the dropdown, which I love.

So far this is what I’ve done for my range slider dropdown and it works to my satisfaction.

import everything I need

path = r'C:\Users\2022.csv'
df = pd.read_csv(path)
df_zones = pd.DataFrame({
    'DK1': df['DK1'],
    'DK2': df['DK2'],
    'FI': df['FI'],
    'NO1': df['NO1'],
    'NO2': df['NO2'],
    'NO3': df['NO3'],
    'NO4': df['NO4'],
    'NO5': df['NO5'],
    'SE1': df['SE1'],
    'SE2': df['SE2'],
    'SE3': df['SE3'],
    'SE4': df['SE4'],
    })

fig = go.Figure(layout=go.Layout(
        title=go.layout.Title(text="Error (Results - Forecast)")
    ))

for column in df.columns.to_list():
    fig.add_trace(
        go.Scatter(
            x = df.index,
            y = df[column],
            name = column
        )
    )
    
fig.update_layout(
    updatemenus=[go.layout.Updatemenu(
        active=0,
        buttons=list(
            [dict(label = 'All zones',
                  method = 'update',
                  args = [{'visible': [True, True, True, True,True, True,True, True,True, True,True, True]},
                          {'title': 'All',
                           'showlegend':True}]),
             dict(label = 'DK',
                  method = 'update',
                  args = [{'visible': [True, True, False, False, False, False, False, False, False, False, False, False]}, # the index of True aligns with the indices of plot traces
                          {'title': 'Denmark',
                           'showlegend':True}]

# Here I've omitted all the other buttons I use for economy. 

)
    ])
fig.update_xaxes(rangeslider_visible=True) #ranges slider
fig.show()

Next is what I’ve attempted with the checklist and it displays, terribly. The graph is squeezed into the top 50% of the page and the buttons don’t work yet. Each selection of a checklist button should update the graph and display the new data. I think I’ve misunderstood something in the two approaches but these two methods produce vastly different results and I don’t understand why.

I’m aware I need to add some functionality to update the graph with the ‘y’ component but before I do that I want to be clear on what is the best method and why.

When should I use a callback for a graph update? and is it better than the update_layout method? What is the difference here and what would make the most sense for me to do to replicate my first attempt?


DK = df.columns[1:3]
FI = df.columns[3:4]
NO = df.columns[4:9]
SE = df.columns[9:13]

#%%

zone_dict = {'DK':'Denmark', 'FI':'Finland', 'NO':'Norway', 'SE':'Sweden' }
ID = 'DK'
zone_ID = zone_dict[ID]

# Create the line graph
line_graph = px.line(
  # Set the appropriate DataFrame and title
  data_frame= df, title='Flow for '+zone_ID, 
  # Set the x and y arguments
  x='DateTimeUtc', y= ['DK1','FI'])
  
# Create the Dash app
app = dash.Dash(__name__)

# Set up the layout with a single graph, inside a children argument so we can add multiple elements
# ID's also important for callbacks
app.layout = html.Div(children = [html.H1('Error'), 
                                  dcc.Checklist(id='my-checklist', options=[{'label': col, 'value': col} for col in df.columns],
        value=df.columns.tolist(),
        labelStyle={'display': 'inline-block'}
    ),
    dcc.Graph(id='my-line-graph', figure=line_graph)
])
line_graph.update_xaxes(rangeslider_visible=True)


# Set the app to run in development mode
if __name__ == '__main__':
    app.run_server(debug=False)

Head of dataframe


NDFrame.head of                           DK1      DK2  ...        SE3        SE4
DateTimeUtc                             ...                      
2022-01-01 00:00:00  1691.712  463.951  ... -112.99925   93.56625
2022-01-01 01:00:00  1244.341  318.301  ...   68.27500  345.15800
2022-01-01 02:00:00   927.078  175.081  ...  308.46300  648.40400
2022-01-01 03:00:00   612.140   43.902  ...  450.57400  935.90600
2022-01-01 04:00:00   934.235   98.501  ...  559.55400  794.34800
                      ...      ...  ...        ...        ...
2022-12-31 20:00:00  2217.632  -54.234  ...  581.45780  577.19120
2022-12-31 21:00:00  2751.985 -105.601  ...  605.69085  521.62715
2022-12-31 22:00:00  2972.932 -100.368  ...  725.38635  485.12365

Thanks for any input.

Don’t have your dataframe so I think you can refer something to revise your code:

  • You need to use callback to return new data to your figure.
  • Indcc.Graph instead of define figure=line_graph I think you should use figure={} and then return new Graph with callback.
from dash import Dash, dcc, html, dcc, Input, Output
from dash.exceptions import PreventUpdate
import pandas as pd
import dash_bootstrap_components as dbc
import pandas as pd
import plotly.express as px

df = px.data.gapminder().query("country==['Canada']")
app = Dash(__name__, external_stylesheets=[dbc.themes.LUX])
app.layout = html.Div([
    dbc.Row([
        dbc.Col([
            dbc.Checklist(id='my-checklist', options=[{'label':x, 'value':x} for x in df.columns[3:6]],
                          value=df.columns[3],inline=True),
                         dcc.Graph(id='my-line-graph', figure={})
        ])
    ])
])


@app.callback(Output('my-line-graph', 'figure'),
             Input('my-checklist','value'))
def update_line_graph(my_checklist):
    if my_checklist != []:
        fig = px.line(df, x="year", y=my_checklist, title='Life expectancy in Canada')
        return fig
    else:
        raise PreventUpdate
if __name__ == "__main__":
    app.run_server(debug=False)

1 Like

Aha! Thanks for the tip. I’ve added my df.head for consistency.

One question though, I was always taught to make a copy of the df before updating but you don’t do this. Is it not necessary?

Can you upload data to somewhere that I can download and run?

Thanks, I got it to work now but I have a couple more questions. I can’t send you the df but I can build one for you that’s the same:


df_2 = pd.DataFrame(np.random.randint(0,100,size=(8761, 12)), columns=list('ABCDEFGHIJKL'))
df = pd.date_range(start='1/1/2018', end='1/1/2019', freq= 'H')
df_2.set_index(df)
  • Why does the graph squeeze up to the top 33% of the page when displaying using this method? When I use the first method it fills the full page.

The first method you are talking about is the one that uses dropdown and not a callback right? I think you can change it by using style={'height':500} in your dcc.Graph.

app.layout = html.Div([
    dbc.Row([
        dbc.Col([
            dbc.Checklist(id='my-checklist', options=[{'label':x, 'value':x} for x in df.columns[3:6]],
                          value=df.columns[3],inline=True),
                         dcc.Graph(id='my-line-graph', figure={},style={'height':500})
        ])
    ])
])

That didn’t work, but adding a specific size to the figure in the px.line section did. Thanks for your help Hoatran, can I ask you to delete the title in the graph of your post though please? Specifically the html.H1 part, its just for my own peace of mind as its work related. Thanks.

1 Like

I edited my answer.

1 Like

Thank you I appreciate it