How to enhance performance when a request contains large amounts of request?

I would like to create a customized dropdown to filter an HTML table.
The primary goal is to update the hidden state of each row.
However, a single update involves a significant number of requests, leading to slow page updates, particularly when updating all rows.

Does anyone have suggestions for improving performance?

Note: I chose to use html.Table instead of dash_table due to specific customization requirements.

import dash
from dash import html, dcc
from dash.dependencies import Input, Output, ALL, State
import pandas as pd
import random

# Sample DataFrame with 99rows
df = pd.DataFrame({
    'Name': [f'Person {i%3}' for i in range(99)],
    'Age': [25 + random.randint(0,3) for _ in range(99)],
    'City': ['City A', 'City B', 'City C'] * 33  # Three unique values repeated
})

# Initialize the Dash app
app = dash.Dash(__name__)
setting_init = {'Name': 'ALL', 'Age': 'ALL', 'City': 'ALL'} 

def create_table():
    header = [html.Tr([
        html.Th([col,
                 dcc.Dropdown(value=setting_init[col],
                              options=[{'label': val, 'value': val} for val in df[col].unique().tolist()+['ALL']],
                              id={'type': 'dropdown', 'col': col})
        ], style={'border': '1px solid'}) for col in df.columns])]

    content = [html.Tr([html.Td(df.iloc[i][col],
                                style={'border': '1px solid'}) for col in df.columns],
                       id={'type': 'row', 'row': i}) for i in range(len(df))]
    
    table = html.Table(header + content, style={'border': '1px solid'})
    return table

table = create_table()

# Define the layout of the app
app.layout = html.Div([
    table,
    dcc.Store(id='setting-save', data=setting_init)])


# Callback to update the table based on the selected dropdowns
@app.callback(
    Output({'type': 'row', 'row': ALL}, 'hidden'),
    Output('setting-save', 'data'),
    Input({'type': 'dropdown', 'col': ALL}, 'value'),
    State('setting-save', 'data'),
    prevent_initial_call = True
)
def filter_table(filter, setting_save):
    Name, Age, City = filter[0], filter[1], filter[2]
    idx_all = ['Name', 'Age', 'City']
    for idx in idx_all:
        if eval(idx)!=setting_save[idx]:
            idx_change = idx
    
    dic_display = {setting_save[idx_change]: True, eval(idx_change): False}  #hide old option and display new option

    #setting hidden state
    list_hidden = []
    idx_check = [idx for idx in idx_all if idx!=idx_change]

    if setting_save[idx_change]!='ALL':
        if eval(idx_change)!='ALL':
            for i in range(99):
                if df.iloc[i][idx_change] in dic_display.keys():
                    list_hidden.append(dic_display[df.iloc[i][idx_change]])
                else:
                    list_hidden.append(dash.no_update)
        else:
            for i in range(99):
                if df.iloc[i][idx_change] in dic_display.keys():
                    list_hidden.append(dash.no_update)
                else:
                    #check other dropdowns meet setting
                    flag = False
                    for idx in idx_check:
                        if eval(idx)!='ALL' and df.iloc[i][idx]==eval(idx):
                            list_hidden.append(False)
                            flag = True
                            break
                    if flag == False:
                        list_hidden.append(dash.no_update)

    else:  #if previous option is 'ALL'
        for i in range(99):
            if df.iloc[i][idx_change]==eval(idx_change):
                list_hidden.append(dash.no_update)
            else:
                list_hidden.append(True)

    #save new option to record
    setting_save[idx_change] = eval(idx_change)

    return list_hidden, setting_save

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

Hello @wen,

Just curious, did you also test out AG Grid, it has ton of customization ability.

Hello @jinnyzor ,

The main reason is I’m not so familiar with AG Grid…
I’m implementing a grouping rows but I don’t really know how to achieve that by AG Grid.
So I’m implementing using html table and it is obviously has poor performance when I also want to implement a dropdown filter to it.
It would be helpful if you can give me some hints about my grouping table.
Thank you in advance.
(I know there is a Tree data function in dash enterprise but I’m wondering if it is possible to implement similar function without license limitation)

[Raw data]

Name Role Family Age Birthday Month
Alice Parents Johnson 80 March
Nina Child Johnson 35 April
Emma Child Johnson 38 April
Anna Parents Liam 62 November
Bob Child Liam 20 December

[Display]
→ Group rows when the Role is child and has same Family name

Name Role Family Age Birthday Month
Alice >Parents Johnson 80 March
Anna >Parents Liam 62 November

And expand details when people click on Role / Family column

Name Role Family Age Birthday Month
Alice ˇ Parents Johnson 80 March
Nina Child Johnson 35 April
Emma Child Johnson 38 April
Anna ˇ Parents Liam 62 November
Bob Child Liam 20 December

This is not in dash enterprise, but in AG Grid Enterprise, this is much different.

To use tree data, you’d need to configure your data to have a path as such:

data = [['Name','Role','Family', 'Age','BirthdayMonth'],
["Alice","Parents","Johnson","80","March"],
["Nina","Child","Johnson","35","April"],
["Emma","Child","Johnson","38","April"],
["Anna","Parents","Liam","62","November"],
["Bob","Child","Liam","20","December"]]

df = pd.DataFrame(data[1:], columns=data[0])

df['path'] = ''

parents = {}

for i, row in df.iterrows():
    if row['Role'] == 'Parents':
        parents[row['Family']] = [f"{row['Name']}-{row['Family']}"]
        df.at[i, 'path'] = [f"{row['Name']}-{row['Family']}"]
    else:
        df.at[i, 'path'] = [*parents[row['Family']],f"{row['Name']}-{row['Family']}"]

Now, here I will point out a flaw in your data, you should really have each person setup with an id, and create a new column called ‘parent’, which is where you place the parent id, this can create a stronger link than just the last name. Then in the above parents dictionary, you can use the id instead of the family name.

Adding this will allow you to navigate up and down a tree and span multiple generations.

Once your path is configured, then you can send the data over to the tree data from AG Grid, or try one that I spun up myself.

app.py

from dash import *
import dash_ag_grid as dag
import pandas as pd
import dash_bootstrap_components as dbc

app = Dash(__name__,
           external_stylesheets=[
               dbc.icons.FONT_AWESOME,
               dbc.themes.BOOTSTRAP
           ],
           )

data = [['Name','Role','Family', 'Age','BirthdayMonth'],
["Alice","Parents","Johnson","80","March"],
["Nina","Child","Johnson","35","April"],
["Emma","Child","Johnson","38","April"],
["Anna","Parents","Liam","62","November"],
["Bob","Child","Liam","20","December"]]

df = pd.DataFrame(data[1:], columns=data[0])

df['path'] = ''
df['expanded'] = False

parents = {}

for i, row in df.iterrows():
    if row['Role'] == 'Parents':
        parents[row['Family']] = [f"{row['Name']}-{row['Family']}"]
        df.at[i, 'path'] = [f"{row['Name']}-{row['Family']}"]
    else:
        df.at[i, 'path'] = [*parents[row['Family']],f"{row['Name']}-{row['Family']}"]

app.layout = dag.AgGrid(
    id='tree_data_layout',
    columnDefs=[
        # we're using the auto group column by default!
        {"field": "path",
         "cellRenderer": "PathRenderer", 'cellClassRules': {'hover-over': 'potentialParents(params)'}
         },
        {"field": "Name"},
        {"field": "Role"},
        {"field": "Family"},
        {"field": "Age"},
        {"field": "BirthdayMonth"},
        {'field': 'expanded', 'hide': True}
    ],
    defaultColDef={
        "flex": 1,
        'resizable': True
    },
    dashGridOptions={
        "getRowHeight": {"function": "TreeDataExpand(params)"},
        "rowClassRules": {"hidden": "params.node.rowHeight == 1"}
    },
    rowData=df.to_dict('records')
)

app.run(debug=True)

dashGridFunctions.js

var dagfuncs = window.dashAgGridFunctions = window.dashAgGridFunctions || {};
var dagcompfuncs = window.dashAgGridComponentFunctions = window.dashAgGridComponentFunctions || {};

dagfuncs.getDataPath = function (data) {
    return data.path;
}

dagfuncs.potentialParents = function (params) {
    return params.node === potentialParent
}

dagfuncs.TreeDataExpand = function (params) {
    if (params.node.data.path.length > 1) {
        newNodes = mapNewNodes(params.api)
        parentNode = {'data': {'expanded': true}, rowHeight: 42}
        newNodes.forEach((node) => {
            if (node.id === params.node.id) {
                parentNode = node.parent
            }
        })
        try {
            return (parentNode.data.expanded ? parentNode.rowHeight : 1)
        } catch {}
    }
    return params.node.rowHeight
}

dagcompfuncs.PathRenderer = (props) => {
    if (props.data.Name) {
        childrenMapped = mapChildren(props.api)
        if (childrenMapped[props.value[props.value.length-1]]['childrenAfterSort'].length) {
            expandable = React.createElement('i', {className: props.data.expanded ? 'fa-solid fa-chevron-down' : 'fa-solid fa-chevron-right', style: {'cursor': 'pointer'},
            onClick: () => {
            props.node.setDataValue("expanded",!props.data.expanded)
            props.api.redrawRows()
            props.api.resetRowHeights()
            }, 'key': `${props.id}_icon`})
        } else {
            expandable = ''
        }
    } else {
        expandable = ''
    }
    if (Object.keys(props.value[props.value.length-1]).includes('text')) {
            showValue = props.value[props.value.length-1]['text']
        } else {
            showValue = props.value[props.value.length-1]
        }
    if (props.value.length != 1) {
        newValue = ''
        for (var y=0; y<props.value.length-1; y++) {
            newValue += '.\t.\t'
        }

        return React.createElement('div',{'style': {'display':'flex', 'alignItems': 'center'},
        'key': `${props.id}_div`},
        [expandable,
        React.createElement('span', {"style":{"color": 'rgba(0,0,0,0)', 'fontSize': '25pt'}, 'key': `${props.id}_span`},
        `${newValue}.`), React.createElement('span', {'key': `${props.id}_info`},`↳\t${showValue}`)])
    }
    return React.createElement('div',{'style': {'display':'flex', 'alignItems': 'center', 'gap': '7px'}},
        [expandable,showValue])
}

style.css

.hidden {
    display: none !important;
}

1 Like