✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
🐇 Announcing Dash VTK for 3d simulation graphics. Check out the March webinar.

Conditional updating of graphs

I have a basic dash app with two graphs which I would like to update based on the condition of a radio item selection. I want to choose which graph to update:

html.Div([
    dcc.RadioItems(
        id='graph-selector',
        options=[{'label': i, 'value': i} for i in [1, 2]],
        value=1
     ),
])
dcc.Graph(id='graph1',
    clickData={'points': [{'x': fixed_df['Abs time'].values[0]}]}
),
dcc.Graph(id='graph2',
    clickData={'points': [{'x': fixed_df['Abs time'].values[0]}]}
)

I haven’t been able to figure out how to use @app.callback to choose which graph to update based on the radio item condition:

@app.callback(
Output('graph???', 'figure'),
Input('graph-selector', 'value))

def update_graph(graph):
  etc....
  return figure

My first idea was to use classes, and call an instance function with the callback, but apparently this is impossible with dash? Callbacks seem to want raw functions.

@app.callback(
Output('graph', 'figure'),
Input('graph-selector', 'value))

graph1.update_graph(graph=1)

SyntaxError: invalid syntax

My second idea was to tell the callback to output to both graphs, but I don’t know how to exclude one from updating.

@app.callback(
    Output('graph1', 'figure'),
    Output('graph2', 'figure'))
    Input('graph-selector', 'value'))
def update_graph(graph):
   # Updates both?
  return figure

Now I’m thinking I need to use two different callbacks and just throw away one based on condition, but this is a messy solution…

Is there a better way to do this?

Hi @harmanato

You can do that bilding the graph into the callback.
Instead of adding two graph in the layout, just put a html.Div(id=‘my_output’) in the layout.
Use that id as Output with property ‘children’ and return the dcc.Graph you want to show to that children.

Good luck :grinning:

Hi Eduardo,
Thanks for your idea. This seems like a good solution if I only want to show one graph at a time. But I’d ideally like to show both graphs simultaneously and update one at a time.

My current implementation is to use two different callback systems. This isn’t optimal though because I’m using the same function in two different places. The ‘updater’ here is a checklist, which I again need to implement separately for each graph (cluttered and not optimal; ideally I could update both graphs from one checklist.)

@app.callback(
    Output('graph1', 'figure')
    Input('updater1', 'value'))
def update_graph(update):
  return figure

@app.callback(
    Output('graph2', 'figure')
    Input('updater2', 'value'))
def update_graph(update):
  return figure

Even the ability to call a function from a callback rather than needing to define the function in the callback would make this implementation OKAY.

@harmanato

you also can return more than one dcc.Graph to the html.Div.

Thanks Eduardo. I ended up getting a somewhat functional app with this approach, but in the end I went with subplots because I wanted to share axes.

For posterity here is my final overall approach:

app = dash.Dash(__name__)

app.layout = html.Div([
    html.Div([
        html.Div([
            dcc.Checklist(
                id='yaxis-column1',
                options=[{'label': i, 'value': i} for i in df.columns],
                value=['Operating condition'],
                labelStyle={'display': 'inline-block'}
            ),
        ],
        style={'width': '49%', 'display': 'inline-block'}
        ),
        html.Div([
            dcc.Checklist(
                id='yaxis-column2',
                options=[{'label': i, 'value': i} for i in df..columns],
                value=['Operating condition'],
                labelStyle={'display': 'inline-block'}
            ),
        ],
        style={'width': '49%', 'display': 'inline-block'}
        )
    ])
])

@app.callback(
    Output('graph', 'figure'),
    Input('yaxis-column1', 'value'),
    Input('yaxis-column2', 'value'))

def update_graph(yaxis1, yaxis2):

    fig = subplots.make_subplots(rows=2, cols=1,
                                shared_xaxes=True,
                                vertical_spacing=0.01,
                                row_heights=[10, 10])

    for val in yaxis1:
        fig.add_trace(go.Scatter(
            x=df['Time'],
            y=df[val],
            ), row=1, col=1)

    for val in yaxis2:
        fig.add_trace(go.Scatter(
            x=df['Time'],
            y=df[val],
            ), row=2, col=1)

    return fig