Same input and output in a callback

Hi all,

My question is: is not possible to create a callback function with the same value as Input and Output?

To be a little more specific I have a slider whose value I only want changed if a specific condition (press of a button) is met. Otherwise, I want it to keep it’s value. For this, I have formulated my callbacks as follows:

start counter from 0 once button is clicked

@app.callback(
dash.dependencies.Output(‘interval-comp’, ‘n_intervals’),
[dash.dependencies.Input(‘button’, ‘n_clicks’)])
def btn_clicked(btn_click):
return 0

every 1 second change slider values - short demo

@app.callback(
dash.dependencies.Output(‘slider’, ‘value’),
[dash.dependencies.Input(‘button’, ‘n_clicks’),
dash.dependencies.Input(‘interval-comp’, ‘n_intervals’),
dash.dependencies.Input(‘slider’, ‘value’)])
def on_click(btn_click, count_interval, prev_value):
# don’t do this when application is loaded for the first time
if btn_click!=None and count_interval < 81:

    value = slider_vals[count_interval]
    return round(value, 2)
else:
    return prev_value

The problem is am getting an “Error loading layout” message on my browser after adding the Input slider and if I don’t include this, then None is returned as the new value of the slider (if the button hasn’t been clicked)!

Any ideas about how to go about dealing with this?

Thanks,
C.

1 Like

I’m also interested in this. In my case I wanted to use a multi-dropdown and mouse selection on the graph to filter the same data with selections made on the graph showing up in the dropdown and vice-versa. It’s also not possible (as far as a can see) to have two callbacks with opposite input/output which would be the other natural way to achieve this.

Obviously there’s the possibility of recursion but it should be possible to catch this and prevent updates with an exception when the selection hasn’t actually changed, so if these options are being disallowed rather than being hard to do on the JS side I think there should be the option to allow them.

Okay, before I was only really responding to your post’s title. I’m not 100% sure what you’re trying to do, but maybe you want to your slider to output to holding Div, e.g.:

html.Div([], id='value-container', style={'display': 'none'})

and then pass that value to the button’s callback as a State (not Input) when you press it?

Going back to the title, I have some kind of workaround that might be useful to people. A callback with the same input as output is probably out of the question but you can sidestep the issue by having two callbacks that talk to each other indirectly.

import dash
from dash.exceptions import PreventUpdate
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

# create a layout with two multi-select dropdowns
def get_dropdown(n, value=None):
    value = [] if value is None else value
    return html.Div(
        [dcc.Dropdown(
            id='dropdown'+n,
            options=[
                {'label': 'New York City', 'value': 'NYC'},
                {'label': 'Montréal', 'value': 'MTL'},
                {'label': 'San Francisco', 'value': 'SF'}
            ],
            multi=True,
            value=value
        )],
        id='dropdown-container'+n,

    )
app.layout = html.Div([
    get_dropdown('1'),
    get_dropdown('2'),
    html.Div([], id='previously-selected', style={'display': 'none'})
])


# Callback one recreates the other dropdown using new values
#   and updates the previously-selected value
@app.callback(
    [Output('dropdown-container2', 'children'), Output('previously-selected', 'children')],
    [Input('dropdown1', 'value')],
    [State('previously-selected', 'children')]
)
def update_via_children(value, prev_selected):
    print('callback one')

    if sorted(value) == sorted(prev_selected):
        raise PreventUpdate

    return get_dropdown('2', value=value), value

# Callback two updates the value of dropdown1 directly and so could be used to alter something
#   complicated like a Graph, hopefully. Does not update previously-selected, so callback one
#   will be called again, recreating dropdown2, triggering callback one a second time, but
#   this time previously-selected == value
@app.callback(
    Output('dropdown1', 'value'),
    [Input('dropdown2', 'value')],
    [State('previously-selected', 'children')]
)
def update_directly(value, prev_selected):
    print('callback two')
    if sorted(value) == sorted(prev_selected):
        raise PreventUpdate
    return value


app.run_server(debug=True)
2 Likes

I found your post this afternoon, and it saved me a lot of time. Thanks for posting.

1 Like

Hello jcthomas,

Thanks for your workaround. I was trying to use it with a graph, but I cannot get it working. It seems to look allright, but after selecting data in the graph and using the dropdown menu to deselect the data, the dropdown menu is getting empty.

I think I have to keep/save the selected data somehow, but I don’t know how exactly.

Any idea’s? Or is there someone who made this working with a graph?

Thanks in advance!

import dash
from dash.exceptions import PreventUpdate
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import plotly.express as px
import pandas as pd


external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

df = pd.DataFrame({'City': ['New York City', 'Boston', 'San Francisco'], 'Value': [100, 200, 300]})


# create a layout with a multi-select dropdown and a graph
def get_dropdown(value=None):
    value = [] if value is None else value
    return html.Div(
        [dcc.Dropdown(
            id='dropdown',
            options=[
                {'label': 'New York City', 'value': 'New York City'},
                {'label': 'Boston', 'value': 'Boston'},
                {'label': 'San Francisco', 'value': 'San Francisco'}
            ],
            multi=True,
            value=value
        )],
        id='dropdown-container',
    )


def create_graph(value=None):
    value = [] if value is None else value
    df_filter = df[df['City'].isin(value)]
    figure = px.bar(df_filter, x='City', y='Value')
    figure.update_layout(clickmode='event+select')
    return html.Div([
        dcc.Graph(
            id='graph',
            figure=figure,
            # selectedData={'New York City': 100, 'Boston': 200, 'San Francisco': 300}
        )
    ], id='graph-container')


app.layout = html.Div([
    get_dropdown(),
    create_graph(),
    html.Div([], id='previously-selected'),
    html.Div([html.Pre(id='click-data')])
])


# Callback one recreates the graph using new values
#   and updates the previously-selected value
@app.callback(
    [Output('dropdown-container', 'children'), Output('previously-selected', 'children')],
    [Input('graph', 'selectedData')],
    [State('previously-selected', 'children')]
)
def update_via_children(value, prev_selected):
    print('callback one')

    if value is not None:
        value = [i.get('label') for i in value.get('points', {})]
    else:
        value = []

    if sorted(value) == sorted(prev_selected):
        raise PreventUpdate

    return get_dropdown(value=value), value


# Callback two updates the value of dropdown directly and so could be used to alter something
#   complicated like a Graph, hopefully. Does not update previously-selected, so callback one
#   will be called again, recreating graph, triggering callback one a second time, but
#   this time previously-selected == value
@app.callback(
    Output('graph-container', 'children'),
    [Input('dropdown', 'value')],
    [State('previously-selected', 'children')]
)
def update_directly(value, prev_selected):
    print('callback two')

    if sorted(value) == sorted(prev_selected):
        raise PreventUpdate

    return create_graph(value=value)


app.run_server(debug=True)

I found it!

In callback one, replace:

        value = []

With:

        value = prev_selected