Skip to content
Advertisement

How do I implement Dash/Plotly checklist persistence on a layout with multiple unique graphs?

My dashboard has a 2 x 2 graph layout with one checklist overlaid on each graph.

Enter image description here

Each graph requires a unique ID, to be live-updated via callbacks. These IDs are generated in a loop (e.g., graph-BMW, graph-MB, etc.). Similarly, each checklist has a unique ID generated via the same loop (e.g., checklist-BMW, checklist-MB, etc.). These IDs are passed to the input and output of the callback.

Graph data is calculated and the checklist value and figure are returned.

Since the checklist needs to be contained in the same DIV as the graph, they are nested and then unpacked in the layout.

All of this works perfectly, with one exception: persistence doesn’t work. If a checklist is clicked, I’m unable to retain the current (new) state when refreshing the page. The checklist value always returns to unchecked, as it seems to be reset.

How can I resolve this?

A stripped-down version of the application:

cars = ['BMW', 'MB', 'AUDI', 'FIAT']

app3 = dash.Dash(__name__)

for car in cars:
    graph = 'graph-' + car
    checklist = 'checklist-' + car

    @app3.callback(Output(checklist, 'value'),
                   Output(graph, 'figure'),
                   Input(checklist, 'value'),
                   Input('interval-component', 'n_intervals'))
    def update_charts(checklist, n_interval, c=car):
        data = get_car_data(c)
        df = calcs_on_car_data(data)

        fig = go.Figure(go.Scatter(x=df.index, y=df['A'].values))
        return checklist, fig

list_graphs = []
for car in cars:
    list_graphs.append([html.Div(className='box',
                                 children=[html.Div([
                                     dcc.Checklist(id='checklist-' + str(car),
                                                   options=[{'label': '', 'value': 'on'}], value=[],
                                                   persistence=True),
                                     dcc.Graph(id='graph-' + str(car))])
                                 ])
                        ])

app3.layout = html.Div([
    *[j for i in list_graphs for j in i],  # unpacking a nested list
    dcc.Interval(id='interval-component', interval=300000, n_intervals=0)
])

if __name__ == '__main__':
    app3.run_server(debug=True)

Advertisement

Answer

It seems that the circular callback breaks the persistence functionality. Removing it by dropping the checklist output, persistence works as intended. Here is an MWE,

import plotly.express as px
from dash import dash, dcc, html, Output, Input


def make_car_card(car: str):
    graph_id = f'graph-{car}'
    checklist_id = f'checklist-{car}'
    card = html.Div(className='box',
                    children=html.Div([
                        dcc.Checklist(id=checklist_id,
                                      options=[{'label': '', 'value': 'on'}], value=[],
                                      persistence=True),
                        dcc.Graph(id=graph_id)])
                    )

    @app.callback(Output(graph_id, 'figure'), Input('interval-component', 'n_intervals'), Input(checklist_id, 'value'))
    def update_charts(_, value, this_car=car):
        if 'on' not in value:
            return px.scatter()
        return px.scatter(px.data.iris(), x="sepal_width", y="sepal_length", title=this_car)

    return card


# Create small example app.
app = dash.Dash(__name__)
app.layout = html.Div(
    [make_car_card(car) for car in ['BMW', 'MB', 'AUDI', 'FIAT']] +
    [dcc.Interval(id='interval-component', interval=300000, n_intervals=0)]
)

if __name__ == '__main__':
    app.run_server(debug=True)
User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement