Dash rendering is too slow with callback in loop...need help

Hi All,

I am new to dash and struggling to get this working. I need help.

I have a html table with around 1500 rows and 4 columns. Each item in column 1 is a link which opens to a modal and displays info of column 5 & 6. I am generating the table from a csv file.

So far, I have got the table generated.

By referring to the dash gallery (https://github.com/plotly/dash-sample-apps/blob/master/apps/dash-web-trader/app.py) I have also got the modals to open on clicking the 1st column items and closing on ‘X’ from the modal.

This works fine if I have about 100 odd rows. But as I mentioned earlier, I have 1500 rows and it takes forever for the server to load. I looked around for clientside callback functions, but I am not sure how I could use with my scenario. Please help me on how I could get this working.

my_apis=[]
for i in range(len(df)):
        if df.columns[0] == 'XYZ':
                value = df.iloc[i]['XYZ']
                my_apis.append(value)



def generate_table(dataframe):

    rows = []
    for i in range(len(dataframe)):
        row = []
        for col in dataframe.columns:
            value = dataframe.iloc[i][col]
            # update this depending on which
            # columns you want to show links for
            # and what you want those links to be
            if col == 'XYZ':
                cell = html.Td(html.Button(children=value, id=value, n_clicks=0))
            else:
                cell = html.Td(children=value)
            row.append(cell)
        rows.append(html.Tr(row))
    return html.Table(
        # Header
        [html.Tr([html.Th(col) for col in dataframe.columns])] +

        rows
    )


def modal(api):
    return html.Div(
        id=api + "modal",
        className="modal",
        style={"display": "none"},
        children=[
            html.Div(
                className="modal-content",
                children=[
                    html.Span(
                        id=api + "closeModal", className="modal-close", children="x"
                    ),
                    html.P(id="modal" + api, children=api),


                ],
            )
        ],
    )

app.layout = html.Div([html.Div([generate_table(df)]),html.Div([modal(api) for api in my_apis])])

def generate_modal_open_callback():
    def open_modal(n):
        if n > 0:
            return {"display": "block"}
        else:
            return {"display": "none"}

    return open_modal


# Function to close modal
def generate_modal_close_callback():
    def close_modal(n):
        return 0

    return close_modal

for api in my_apis:
    app.callback(Output(api + "modal", "style"), [Input(api, "n_clicks")])(
        generate_modal_open_callback()
    )


    app.callback(
        Output(api, "n_clicks"),
        [
            Input(api + "closeModal", "n_clicks")
        ],
    )(generate_modal_close_callback())

Here is some untested code that could give you an idea. Basically, my thought is to have one modal (assuming only one modal can be displayed at any one time) that can be triggered by any of your 1500 rows. This is done via the INPUTS list. As shown below, the return value from the callback is the single modal’s style.

Take a look at ctx. It contains

from dash.dependencies import Input, Output, State

INPUTS = []

for api in my_apis:
    INPUTS.append(Input(api, 'n_clicks'))
    INPUTS.append(Input(api+'closeModal', 'n_clicks'))


@app.callback(
    output=Output(a_single_modal_api, 'style'),
    inputs=INPUTS)
def generate_modal_open_callback(*vargs):
    ctx = dash.callback_context
    out_style = {'display': 'none'}
    
    triggered_input = ctx.triggered[0]['prop_id'].split('.')[0]
    if 'close' in triggered_input:
        out_style = {'display': 'blcok'}
    
    return out_style

You could also modify the ouput to contain the message you want in your modal.

@app.callback(
    output=Output(a_single_modal_api, 'style'),
    inputs=INPUTS)

would become (notice the [ ])

@app.callback(
    output=[Output(a_single_modal_api, 'style'),
            Output(my_modal_html_P, 'children')]
    inputs=INPUTS)

and the return would be

return out_style, "my unique message"

Hope this helps…again - it’s not tested…but maybe it inspires a solution that works for you!

Thank you so much. You saved me. I got this working :slight_smile:

1 Like

Hi Again,

I went ahead and added two dropdowns in the layout along with the table to basically filter the table. The two dropdowns are unique values from two columns in the table and do not depend on each other.

I have the default value of the dropdowns set to placeholder and initially when the page loads, all the rows of the table are displayed. And when I click on a row item of a particular column, modal opens and displays the content. (Exactly what I want).

Now if I select the values from the dropdowns (1 or both), the rows in the table get updated. Now when I click on a row item of a particular column, nothing happens. I have app.config.suppress_callback_exceptions=True set. (it is a multi page app)(Expected behavior is for the modal to open and display the contents). What am I doing wrong?

Also, when the page loads the first time, by default modal opens for the first row item (not the expected behavior). I need to perform the function when n_clicks > 0 , how do I do this in the above scenario. (code is similar to what you suggested) .

Thanks for your time. You have been a great help.

I think your initial layout is causing the first row item, as app.layout calls modal(). Regarding nothing happening on row-click, double check the components are inputs to the desired callbacks. It’s crude, albiet easy, but I also add temp. print statements in callbacks that are giving me trouble in order to determine if the code flow is as expected.

Hi @flyingcujo,

Thanks for your time and suggestion. I was able to resolve the modal popup coming up on page load.

However, I am not able to resolve the issue related to row click.

I have tried several things but not sure what is going on.

when I click on the filtered table row link, I see the fillowing

<div class="_dash-loading-callback"></div>

As soon as I remove the filter and all the rows are back in the table, the row click works as expected.

I have written a different callback function and decorator for a filter value but does not get triggered.

If I comment out the call back function and decorator for which the table displays all rows, then the row click works 1 filter value (I tried with only one filter value)

I have also tried giving different Input values for call back function for each filtered combination.

I have also tried assigning different button ids each time the table generates with the filtered values.

I am maintaining different INPUT list for the filter combination and passing this input list in the callback function.

I can really use some clue here.

Here is my code:

for api in my_apis:
    INPUTS.append(Input(api, 'n_clicks'))
INPUTS.append(Input('closeModal', 'n_clicks'))

def generate_table(dataframe):

    rows = []
    for i in range(len(dataframe)):
        row = []
        for col in dataframe.columns:
            value = dataframe.iloc[i][col]
            # update this depending on which
            # columns you want to show links for
            # and what you want those links to be
            if col == 'x':
                cell = html.Td(html.Button(children=value, id=value, n_clicks=0))
            else:
                cell = html.Td(children=value)
            row.append(cell)
        rows.append(html.Tr(row))
    return html.Table(
        # Header
        [html.Tr([html.Th(col) for col in dataframe.columns ])] +

        rows
    )

def modal():
    return html.Div(
        id="a_single_modal_api",
        className="modal",
        style={"display": "none"},
        children=[
            html.Div(
                className="modal-content",
                children=[
                    html.Span(
                        id="closeModal", className="modal-close", children="x"
                    ),
                    #html.P(id="modal" , children="Hello"),
                    html.P(id="modal", children="Hello",className="c")

                ],
            )
        ],
    )


app.layout = html.Div([html.Div([

            html.Label('Select dd1'),
            dcc.Dropdown(
                 id = 'z_id',
                 options = [{
                         'label' : i,
                         'value' : i
                 } for i in df['z'].unique()],
               
                clearable=True,
                
                placeholder="Select dd1",
                 ),
                ]),

         html.Br([]),


          html.Div([

            html.Label('dropdown2'),
            dcc.Dropdown(
             id = 'y_id',
             options = [{
                     'label' : i,
                     'value' : i
             } for i in df['Y'].unique()],
            
             clearable=True,
             
             placeholder="Select dd2",
             ),

        ]),
             html.Br([]),

        html.Div([html.Button(id='filter-button', n_clicks=0, children='Filter')]),
        html.Br([]),
          html.Div(id='table-container'),
          html.Div([modal()])
          ])


@app.callback(
    dash.dependencies.Output('table-container', 'children'),
    [dash.dependencies.Input('filter-button', 'n_clicks')],
    [dash.dependencies.State('z_id', 'value'),dash.dependencies.State('y_id', 'value')])


def display_table(n_clicks,input_z,input_y):
    if n_clicks > 0:
        if (input_y is None) and (input_z is None):
            return generate_table(df)
        elif input_y is None:
            dff2 = df[df['z']==input_z]
            return generate_table(dff2)
        elif input_z is None:
            dff1 = df[df['y']==input_y]
            return generate_table(dff1)
        else:
            dff3 = (df[(df['y'] == input_y) & (df['z'] == input_z)])
            return generate_table(dff3)
    else:
        return generate_table(df)


@app.callback(
    output=[Output("a_single_modal_api", 'style'),
            Output("modal", 'children')],
    inputs=INPUTS)

def generate_modal_open_callback(*vargs):
    ctx = dash.callback_context
    out_style = {'display': 'none'}

    triggered_input = ctx.triggered[0]['prop_id'].split('.')[0]
    
    modal_message="NONE"
    if <some condition>:
        
        modal_message ="welcome"
    if 'close' in triggered_input:
        out_style = {'display': 'none'}
    else:
        out_style ={'display':'block'}
    return out_style, modal_message

Hi @flyingcujo,

Ignore my earlier comment. I got it working by capturing Input set for each combination in the dropdown and writing a callback function for each of these combination and passing the Input set.

I want to know if there’s a better solution to it.

I have 12 values in dropdown 1 and 3 values in dropdown 2. So the total combination I am capturing is 36 sets of Input. I have a callback function for each of these input.

Also, for each of these inputs, I am specifying different modal (In total 36.since the output IDs have to be unique).

I am sure there is a better way to handle all of it. As I mentioned earlier, I am new to dash and still figuring out things.

Any help is much appreciated.