Interdependent components


#1

Hello,

I’ve had a lot of fun building a dashboard using Dash! I have run across a problem that I hope has a solution within Dash, although reading about similar users’ related problems I’m not sure.

Suppose I have two components (e.g. Slider, Dropdown, or Input) whose values are related. When I change the value of one, I want the other to change, and vice versa. This creates an obvious cyclic dependency, but this seems like something that should be accomplishable. Is it?

From a UI perspective, this can often be worked around, but I can think of cases where the natural inputs for a user have an interdependency. Suppose a user wants two dropdowns to select a city and chain restaurants. When they select a city, the restaurant dropdown should be populated by restaurants in that city, and when they select a restaurant, the city dropdown should be populated by cities that have that restaurant.

Here is a MWE with two Input components:

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

app = dash.Dash()
server = app.server

# define the layout of the dashboard
app.layout = html.Div([
    html.Div([
        html.H6('x'),
        dcc.Input(id='input_x'),
    ], className='row'),
    html.Div([
        html.H6('2x'),
        dcc.Input(id='input_2x'),
    ], className='row'),
])

# NOTE: This breaks because of a cyclic dependency
# not clear what can be done here.

# update input_x when input_2x changes
@app.callback(
    Output('input_x', 'value'),
    [Input('input_2x', 'value')]
)
def update_x(v):
    return v/2

# update input_2x when input_x changes
@app.callback(
    Output('input_2x', 'value'),
    [Input('input_x', 'value')]
)
def update_2x(v):
    return 2*v

app.css.append_css({'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'})

if __name__ == '__main__':
    app.run_server()

#2

I don’t know that there’s any workaround other than changing the UI to work differently.

I wonder also if there might be usability issues with this UI. If a user has selected a city, then the other drop down will be filtered to chains with restaurants in that city. Now if a user want’s to then select a specific chain, if it doesn’t occur in that city they selected, they then have to go and clear the city in the other dropdown before the desired chain will be available. In this case it seems like undesirable coupling between them; the filtering that might be a convenience in some contexts is now a friction point. And if the user isn’t fully aware of what’s going on with the filtering, they may incorrectly conclude that the desired chain is just not in the database.


#3

OK, thanks - I suspected that changing the UI is required.

You’re right that this example is not the best - perhaps there are generally friction points with this kind of interdependency.


#4

Yeah, this isn’t possible right now. But, it’s a valid concern. Here’s a classic example where this would be useful: a slider is synced up with the text input:


This will be quite a bit of work to support correctly. If any organization or company has the software budget to sponsor this work, please reach out: https://plot.ly/dash/pricing


#5

I have this exact use case.

I thought I could break the cycle by using raise PreventUpdate when I can assert that the two inputs (box and slider) are synced with each other. However it looks like the rendered throws an error when it detects a cycle.

Perhaps a short term solution is to allow a way to explicitly disable the cycle checking (as in “trust me”, akin to supress_callback_exceptions) ?

Update
I dived into dash-renderer, and it looks like the error for circular dependencies is thrown from createDFS from dependecy-graph. Fortunately, the more recent version of that library introduced a “circular” argument exactly to prevent that error from being thrown.

I forked dash-renderer to allow for this error to be disabled (diff here)

Happy to do a PR if this is something useful to others, although I’d probably need help with figuring out how to make the parameter configurable more upstream.


#6

Thanks! Could you at least create an issue in dash-renderer and a link to your code? We can help coach you through a PR, but the dash-renderer ones are usually very involved. We’ll get to this eventually though, and it’d be helpful to see some progress on it :slight_smile: