Dynamic number of inputs based on anther input

Not sure why I have such a hard time wrapping my head around dash! I’m trying to migrate an app from shiny to dash. You can see it here. Here was my attempt via dash:

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


app = dash.Dash()
app.css.append_css({'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'})
app.config['suppress_callback_exceptions']=True

app.layout = html.Div([
    html.Div([
        html.Label('number of inputs'),
        dcc.Slider(id='n_inputs', min=1, max=5, step=1, value=1,
                   marks={i: i for i in [1, 2, 3, 4, 5]}),
        html.Br(),
        html.Div(id='input_container')],
        style={'float': 'left', 'width': '20%',
               'padding': '0 0 50px 5px'}),
    html.Div(id='input_values',
             style={'float': 'right', 'width': '75%'})
])


@app.callback(
    Output('input_container', 'children'),
    [Input('n_inputs', 'value')])
def make_sliders(n):
    return [dcc.Input(id='input_{}'.format(i),
                      type='text',
                      value='') for i in range(n)]

@app.callback(
    Output('input_values', 'children'),
    [Input('input_{}'.format(i), 'value') for i in range(5)])
def printcontents(txt0, txt1, txt2, txt3, txt4):
    vals = [txt0, txt1, txt2, txt3, txt4]
    print(vals)
    return vals
    

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

Some issues:

  • the container for input values doesn’t update unless the slider is at 5. Does my list of [Input()] ids imply that they all need to be present for that callback to fire?

  • the text box contents don’t persist when changing the slider. Each time the input slider changes, I return a fresh list of dcc.Slider components, but I’m not sure how to know what their values were and keep them… do I need to do something like this were you’re storing the last value/history in a hidden div and bring it back?

  • I found this and this (replicates of each other) and see what you’re doing, but the inputs were all defined ahead of time. Should I do that? Is there no way to generate them on the fly?

  • Is there no way to generate an Input list based on some other value? It’s so tempting to want to get at the value of the slider when specifying a list of Inputs so I can know how many there should be.

Thanks for any pointers. I have some other attempts at the bottom here, but decided to re-post as that was initially about something else (solved by finding out about State), and now it’s about a dynamic number of UI components.

1 Like

Yep, I think this is the key point. If you open up the developer console (F12), you’ll see that there’s a javascript error for every position in the slider except 5.

You’e essentially trying to tell Dash, this callback function takes 5 arguments, and they have these IDs, Dash goes off and goes to look for those IDs to get the values, but any time the slider is not at 5, there will be at least one of those elements missing from the DOM, so you’ve now got an undefined value/error.

I think you might be confusing your generated sliders with text inputs. dcc.Input is just a text input.

You definitely can generate a list of dependency Inputs based on some other value, the key thing is that you need to make sure they’re all in the DOM when you trigger the callback – as is the problem in your code.

If I were you, I would push as much of the generation of layout and Inputs to be upfront, evaluated when Dash loads. You know ahead of time how many sliders there will be for each model, so why not have a callback that targets the value of a dropdown, and inside the callback you test the value, and then go lookup a dictionary of precomputed layout fragments that have the sliders you need? That way, you know that you’ll have all sliders that are inputs for the relevant model loaded in the DOM. It’s still dynamic, you’re changing the layout based on the dropdown, but the layouts you’re choosing from have already been precomputed.

The other advantage of that is you’re less likely to run into runtime bugs in your layout generation code while a user is using it-- you’ll hit them as soon as the app is loaded.

3 Likes

Oh and I think you might want to be doing range(1,6), as list(range(5)) will yield [0,1,2,3,4].

Just a quick reply on the last point. I hadn’t contemplated how to best generate IDs (which can be 0-4) vs. values (which should all be +1 from that… Thanks for the tip on range(1, 6)!

I’ll ponder through the above comments as well. I was using dcc.Input just for a simple input to demo with. I figured once I get the concept/principles, I can make it work for anything. Regarding making them all upfront, it looked like @chriddyp was using id values on the fly in display_controls() here, but that wasn’t an Input, so maybe it’s a different animal.

…the key thing is that you need to make sure they’re all in the DOM when you trigger the callback

Would it work/be better to define a list of Inputs up front and then return input_list[0:n] based on the callback? So they all “exist,” but I only send them to the UI in the quantity selected by the user via the slider?

Thanks for the food for thought!

In that example, he’s making all the Dropdown components up front and indeed as you say deriving the ID of the output element dynamically within the callback based on the two non-dynamic data sources. And yep, there’s nothing special about the IDs, they’re just strings. All that Dash cares about (assuming callback validation is turned off) is that for a callback to work, the DOM must include elements with IDs corresponding to all Inputs and the one Output.

The potentially tricky thing if you’re using dynamic controls–and therefore dynamic IDs–is ensuring that you wire up the right elements as inputs and outputs to the right callback. That’s the purpose of generate_control_id in @chriddyp’s example, to ensure that inputs in the layout and callback definition are synchronised.

If I were porting your R app to Dash, I would probably do a similar thing to @chriddyp’s example that you posted: have a top level dictionary in your app that maps model names (eg employee_only, employee_spouse etc) to layout fragments that include all the sliders needed. Then you just need to create a single callback with the model selection dropdown as the one input and the element containing the sliders as the output. This just needs to look up the dictionary and return the appropriate layout.

And as in @chriddyp’s example, you will also need to register all callbacks for the sliders across all the models in advance, which means you need to make sure the slider IDs from the layout fragments are synchronised with the callback definitions.

2 Likes

@nedned thanks for the continued pointers. Going to take baby steps here :slight_smile: Here’s my modified attempt, but I still get nothing until the slider is at 5… now I’m thinking the slider id has to exist in the layout, not just in the list for the callback to succeed. Is that corect?

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


app = dash.Dash()
app.css.append_css({'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'})
app.config['suppress_callback_exceptions']=True

input_list = [dcc.Slider(id='slider_{}'.format(n),
                         min=1, max=5, step=1, value=1,
                         marks={i: i for i in range(1, 6)}) for n in range(1, 6)]

app.layout = html.Div([
    html.Div([
        html.Label('number of inputs'),
        dcc.Slider(id='n_sliders', min=1, max=5, step=1, value=1,
                   marks={i: i for i in range(1, 6)}),
        html.Br(),
        html.Hr(),
        html.Div(id='input_container')],
        style={'float': 'left', 'width': '20%',
               'padding': '0 0 50px 5px'}),
    html.Div([
        html.Div(id='slider_values')],
        style={'float': 'right', 'width': '75%'})
])


@app.callback(
    Output('input_container', 'children'),
    [Input('n_sliders', 'value')])
def make_sliders(n):
    print(n)
    return [html.Div(slider,
                     style={'padding': '0 0 20px'}) for slider in input_list[0:n]]


@app.callback(
    Output('slider_values', 'children'),
    [Input('slider_{}'.format(i), 'value') for i in range(1, 6)],
    [State('n_sliders', 'value')])
def printcontents(i1, i2, i3, i4, i5, n):
    vals = [i1, i2, i3, i4, i5][0:n]
    print(vals)
    return ', '.join(str(val) for val in vals)
    

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

I’m assuming this from this piece of code I forgot, then re-remembered, and still haven’t entirely wrapped my head around from the dynamic example we’ve been discussing:

# create a callback for all possible combinations of dynamic controls
# each unique dynamic control pairing is linked to a dynamic output component
for value1, value2 in itertools.product(
        [o['value'] for o in app.layout['datasource-1'].options],
        [o['value'] for o in app.layout['datasource-2'].options]):
    app.callback(
        Output(generate_output_id(value1, value2), 'children'),
        [Input(generate_control_id(value1), 'value'),
         Input(generate_control_id(value2), 'value')])(
        generate_output_callback(value1, value2)
    )

For my example, what’s the translation here? I need callbacks for 1 slider, 2 sliders, … 5 sliders that are all separately? Or my first callback where I return the [0:n] slice of my input_list should also generate a tailored callback based on how many exist?

Once I get this, then I’ll try and understand what you’re saying about all combinations of plan and slider!

Another approach.

def generate_slider_callback(n):
    app.callback(
        Output('slider_values', 'children'),
        [Input('slider_{}'.format(i), 'value') for i in range(1, n)],
        [State('n_sliders', 'value')])
    def print_contents(*vals, n):
        return ', '.join(str(val) for val in vals)


@app.callback(
    Output('input_container', 'children'),
    [Input('n_sliders', 'value')])
def make_sliders(n):
    print(n)
    generate_slider_callback(n)
    return [html.Div(slider,
                     style={'padding': '0 0 20px'}) for slider in input_list[0:n]]

That gives an error:

dash.exceptions.MissingEventsException: 
This callback has 1 `State` element
but no `Input` elements or `Event` elements.

Without `Input` or `Event` elements, this callback
will never get called.

(Subscribing to input components will cause the
callback to be called whenver their values
change and subscribing to an event will cause the
callback to be called whenever the event is fired.)

I tried to use *vals, thinking I could be clever and create my Input list on the fly and then just take however many that resulted based on the value of n. Any tips on what I’m doing wrong here?

Different from @chriddyp’s approach in that other example, he knows what every possible input might be and how many there are… it’s just a matter of creating them all. My understanding of that is many of them are pointing at nonexistent inputs, but none are really “invalid”; they just don’t exist until the right menu combo.

In my example, I haven’t figured out the analog. I know what they all might be called, but they don’t exist in the UI and I can’t seem to figure out how to pass a mystery Input list of unknown length to a callback.

It’s certainly all a bit abstract this stuff, but I’m sure you’ll feel it click at some point :slight_smile:

The reason you’re seeing that behaviour still is because you’ve still only got one callback. That callback takes 5 arguments that correspond to Inputs which need to be present in the DOM – however when the callback make_sliders executes, for values of n < 5, you will not have all those elements present in the DOM.

You’re going to need a separate callback for each group of sliders. And yep, this is the part that corresponds to that for loop (however in your case I don’t think you need the for loop, you could just define them explicitly – see below). I would think about it like you’re developing a Python function for each model – which is actually what one does with Dash callbacks anyway. So you would want callbacks defined like this:

employee_only(slider1, slider2, slider3)
employee_spouse(slider1, slider2, slider3, slider4)

etc

Just as a vanilla Python function requires all positional arguments to be present, a callback requires all those sliders to be present in the DOM. So you wanna bake into your logic that each whole group of input sliders that correspond to function arguments of your model are swapped in and out together – so that you never have an incomplete argument list. Here’s how I would go about defining the callback that does this:

INPUTS_LAYOUT_MAP = {
    'employee_only': html.Div([
        dcc.Input(id='employee_only_input1', type='text', value=''),
        dcc.Input(id='employee_only_input2', type='text', value=''),
        dcc.Input(id='employee_only_input3', type='text', value=''),
    ])
    'employee_spouse': html.Div([
        dcc.Input(id='employee_spouse_input1', type='text', value=''),
        dcc.Input(id='employee_spouse_input2', type='text', value=''),
        dcc.Input(id='employee_spouse_input3', type='text', value=''),
    ])
}

@app.callback(Output('input_container', 'children'), [Input('model_select_dropdown', 'value')])
def select_model(model_name):
        return INPUTS_LAYOUT_MAP[model_name]

Seeing as you only have a small number of models, you could just continue with that approach, filling in the rest of the keys in INPUT_LAYOUT_MAP and defining a callback corresponding to each model. There’s no real need to dynamically generate the callbacks if writing them out isn’t going to take that long. It will certainly make your code more explicit and readable too.

Sorry, didn’t see your last post. I’m not exactly clear on why you have a mystery number of things. Looking at the R app you’re trying to port, it looks like you do know the number of sliders for each model in advance. Why can’t you just create them all up front as I described in the last post?

Sorry our comments crossed paths. Trying to merge them together using the newest example, this is responding to the suggested reasons things aren’t working:

1) … you’ve still only got one callback. That callback takes 5 arguments

I did try to fix this. So now I have one “normal” callback which I thought would be generating another based on it’s value:

def generate_slider_callback(n):
    app.callback(
        Output('slider_values', 'children'),
        [Input('slider_{}'.format(i), 'value') for i in range(1, n)],
        [State('n_sliders', 'value')])

Does that address this issue of taking 5 arguments?

2) Inputs which need to be present in the DOM

Am I right in interpreting that “present in the DOM” == inside of app.layout? In other words, defining them in input_list is not sufficient? That was my original attempt to remedy that issue (create them all, but just don’t use them all), but perhaps I misunderstood.

3) You’re going to need a separate callback for each group of sliders.

I think you’re misinterpreting what the R app is doing. The plan type doesn’t have anything to do with the sliders other than employee only doesn’t ask how many people are on your plan. If you choose anything else, a new slider appears, analogous to the one I’m using here. That slider dictates how many additional sliders you get, which represent each person in your family and your estimate of their min/max health expenses.

This might be a better example: the drop down asks for a stock, the slider asks how many people are in your investment group, and the subsequent sliders let each of you pick an amount you want to contribute to this stock purchase. If you change the dropdown, it has nothing to do with your contribution amount. Does that make sense? Like why have 5 sliders for Apple, 5 sliders for IBM, 5 for Google… you just want 5 sliders, period, as they all mean the same thing.

I think it’s best to just avoid this topic until I figure out the simpler case above though: one slider which dictates how many additional sliders are created.

4) I’m not exactly clear on why you have a mystery number of things. Looking at the R app you’re trying to port, it looks like you do know the number of sliders for each model in advance. Why can’t you just create them all up front as I described in the last post?

I want my one slider to create between 1 and 5 additional sliders. The crux of this whole problem is creating a callback to do something based on those subsequent slider values without knowing how many there are. I don’t put them all up there, because it simplifies the UI. Most people will have a spouse and let’s say, 1-3 kids. For someone who has 10 kids, I don’t want 10 sliders clobbering the interface just to handle the case… but I still let them select 10 kids if that applies to them, and then 10 will be given to them.

Hopefully that makes sense!

Side inquiry: with app.config['suppress_callback_exceptions']=True, will a match still work even if others silently fail? In other words, is the analog of generating all of those spurious dynamic callbacks as in @chriddyp’s example for me to create a callback for all combinations of between 1 and n callbacks?

@app.callback(
    Output('slider_values', 'children'),
    [Input('slider_{}'.format(i), 'value') for i in range(1, 2)])

@app.callback(
    Output('slider_values', 'children'),
    [Input('slider_{}'.format(i), 'value') for i in range(1, 3)])
...
@app.callback(
    Output('slider_values', 'children'),
    [Input('slider_{}'.format(i), 'value') for i in range(1, 6)])

Will all the ones hunting for > n silently fail, but the one looking only for 1-n will succeed?

Ah, scratch that as I’d need separate output containers for all of these combos I suppose as well. I also don’t know how to avoid double duty, as in the callback looking for slider_1 only would also fire for the condition that more than 1 exists.

Yep, exactly. But note that it’s fine to have inserted them into the layout via a callback at a later time, they don’t have to be present at app initialisation time, so long as they’re present when the callback that needs them is triggered (and you’ve set suppress_callback_exceptions to True)

Ahhh, you’re right, I wasn’t quite following what that app does, and now I see why you’re producing the sliders like that. Sorry for the misunderstanding.

That still generates only one callback with a variable number of Inputs. I was thinking of this:

max_people = 10
for i in range(1,max_people+1):
    inputs = [Input(f'slider_{i}', 'value')]
    app.callback(Output('target', 'children'), inputs)(sliders_callback)

However, I’ve just realised this isn’t going to work, as I’ve only just discovered that you can’t have multiple callbacks targeting the same element+property as an Output. This means my strategy of creating multiple callbacks isn’t going to work. Ughhh sorry, I seem to have lead you down the wrong garden path a little bit.

Maybe you’re gonna have to go back to one callback as you were initially doing, that takes inputs from the maximum number of people but have the irrelevant sliders hidden via CSS and defaulting to some kind of non-values. I’ve coded something up that does what I just described:

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


MAX_PEOPLE = 10
SLIDER_STYLES = {
    'display':'none',
    'margin-bottom':'1em',
    'margin-top':'1em',
}

app = dash.Dash()
app.layout = html.Div([
    html.Label('Number of people:'),
    dcc.Slider(
        id='num_people_slider',
        min=1,
        max=10,
        step=1,
        value=1,
        marks = {i: i for i in range(1, MAX_PEOPLE+1)}
    ),
    html.Div(
        id='multi_sliders_box',
        style={'margin-bottom':'2em'},
        children=[
            html.Div(
                id=f'slider_{i}_container',
                style={**SLIDER_STYLES},
                children=[
                    f'Person {i}:',
                    dcc.Slider(
                        id=f'slider_{i}',
                        min=0,
                        max=5000,
                        step=500,
                        value=None,
                        marks={x:x for x in range(0, 5001, 500)}
                    ),
                ]) for i in range(1, MAX_PEOPLE+1)]
    ),
    html.Div(id='target')
])

slider_inputs = [Input(f'slider_{i}', 'value') for i in range(1, MAX_PEOPLE + 1)]
@app.callback(Output('target', 'children'), slider_inputs)
def update_chart(*slider_values):
    print(slider_values)
    vals = ', '.join(str(x) for x in slider_values)
    return f'Slider values are {vals}'


# dcc.Slider does not take style as keyword arg??                                                                                                                             

for n in range(1, MAX_PEOPLE + 1):
    @app.callback(Output(f'slider_{n}_container', 'style'),
                 [Input('num_people_slider', 'value')])
    def callback(num_people, n=n):
        new_styles = {**SLIDER_STYLES}
        if n <= num_people:
            new_styles['display'] = 'block'
        else:
            new_styles['display'] = 'none'
        return new_styles

With the idea being that update_chart is the one callback that updates… the chart :stuck_out_tongue:

Hopefully this is getting closer to your needs!

1 Like

Mind. F-ing. Blown.

for n in range(1, MAX_PEOPLE + 1):
    @app.callback(Output(f'slider_{n}_container', 'style'),
                 [Input('num_people_slider', 'value')])
    def callback(num_people, n=n):
        new_styles = {**SLIDER_STYLES}
        if n <= num_people:
            new_styles['display'] = 'block'
        else:
            new_styles['display'] = 'none'
        return new_styles

I have to say, being new to python, I’m really loving it and this conversation is somewhat enthralling as my noob-ishness to python and dash creates this fun game of trying to figure out how to do something. It’s also awesome to read the various answers (many, many from you and @chriddyp) and have that revelation moment of thinking “Oh! That’s how a pro would do this.”

I can’t fathom how many months or years it would have taken me to ever consider hiding them all in there so that they exist, but freaking toggle the style as a way to make them appear and disappear!?!?!? Sonuva. That’s clever. I’ll fiddle with this more later to make sure I really get it (haven’t done much with **keyword_args yet, for example), but really neat to get a peek at it!

My last question is why this isn’t okay:

That still generates only one callback with a variable number of Inputs.

If the callback Input targets are matched to what’s created, I would think this should work? That said, maybe that’s easier done in my head than in code :slight_smile:

3 Likes

It might be hard to be clear on exactly why it wouldn’t work given that that was aiming towards a solution that wasn’t going to work in general due to the limitation of callbacks having only output. However the bit of information that you might be missing for this to make sense is that you have to define your callback(s) upfront when the app is initialised. If you try to register a callback in response to user input, it won’t do anything. So you either need to define one big monolithic callback handling all inputs or if you need multiple callbacks, every possible one that is needed must be registered upfront.

The reason for passing through n as a keyword argument, is because without doing this, the callback definition creates a closure over the variable n in the top level scope, but that n is getting updated on each iteration of the loop. If we didn’t explicitly pass the variable through, when the callback is executed the variable n inside the callback function would have the value that it recieved in the outer scope on the last iteration of the for loop – ie MAX_PEOPLE. It has to be a keyword argument simply because it would no longer align with the number of arguments Dash derives from the Ouptut and Inputs/State if we tried to pass it in as a positional argument.

I think what I’m enjoying about Dash is I kinda get to do frontend web development in Python… The trick about hiding divs for example is more of a CSS trick that we happen to have access to from within Python code. Such fun :slight_smile:

select the div you need to hide/show the content then once the drop down value of my-dropdown selected . choose the condition (in my case its if selected_value in [‘x’,‘y’] then show else hide

@app.callback(Output('target_div','style'),[Input('my-dropdown','value')])
def add_options(selected_value):
    if selected_value in ['x','y'],: #condition
        return {'display':'block'}
    else:
        return {'display':'none'}![hide|690x387]

(upload://p8seRr7FSdozAVOAzU10RMi9ad5.gif)

Did you find a solution?

When I was using your code, btw I think it’s pretty smart using for-loop to solve the problem. But it warned me that ‘‘list’ object is not callable’.
But I personally think it would have worked. Can anyone tell me why?

FYI - This constraint is no longer the case. See the newly released Pattern Matching Callbacks: Pattern-Matching Callbacks | Dash for Python Documentation | Plotly

I’ll close this thread for now since it’s outdated, but feel free to create a new topic if you have any questions about this feature :clinking_glasses:

1 Like