Style changes when a row is selected and deselected in Datatable

I have a Datatable with row_selectable=‘multi’. I created a callback that adds styling to rows when they are selected. I also have code in the same callback that removes the styling if a selected row is subsequently deselected. The initial table styling is held in a list named conditional_style. The code works, but if the user selects and then deselects rows the response time in changing the styling gets quite slow, making me think I’m doing something wrong. I would appreciate any suggestions on how to improve my code. Thanks! (Also, I am new to the site, so if you have suggestions on how to improve my post, I would appreciate those as well.)

Here is the code for the callback:

# callback to highlight selected rows
@callback(
    Output('rsync_table', 'style_data_conditional'),
    Input('rsync_table', 'selected_rows'),
    State('rsync_table', 'style_data_conditional'),
    prevent_initial_call=True,
)
def style_selected_rows(selected_rows, conditional_style):
    # style selected rows
    table_style = conditional_style
    if selected_rows is not None:
        table_style += [
            {'if': {'row_index':i}, 
             'box-shadow': '0px 0px 3px 3px #eb99ff inset'} 
             for i in selected_rows]

    # remove styling for unselected rows
    if selected_rows is not None:
        for i in range(len(table_style)):
            if i not in selected_rows:
                for column_id in ['PID', 'contact', 'div_grp', 'location', 'last_sync']:
                    table_style.append({
                        'if': {'row_index': i, 'column_id': column_id},
                        'box-shadow': 'unset'
                    })

    return table_style  

Hi @hardin108 and welcome to the Dash community :slight_smile:

It’s helpful to post a complete minimal example that reproduces the issue, rather than just the callback. You can find more info in this post: How to Get your Questions Answered on the Plotly Forum - #12

In the meantime, try adding print(table_style) right before the return statement.

The issue might be that table_style is getting large and has lots of duplicates.

1 Like

@AnnMarieW , you are correct, the table_style is getting very long. It appears that perhaps when I make changes to the table_style, and then return the entire table_style in the callback, the entire set of table styles gets appended to what already existed. Is there a way to only add the changes? Is there a way to use data_style.append() in the callback to accomplish this? Better yet is there a way to EDIT the table_style rather than overriding existing styles by appending? Thank you for your help!

BTW, this was exacerbated by the fact that I was running the code in debug mode in VSCode on a Windows laptop that was bogging down for need of updates. Things improved greatly when I ported the code to a linux server, running the code in a docker container and using nginx as a proxy, but I am glad to have discovered the issue this way!

Instead of appending the table_style you could just recreate it in the callback.

Another option is to use Dash AG Grid instead. It’s possible to style with class names and works faster for large data sets.

That makes sense to recreate it. Perhaps the way to do this is to store the original table_style in a list that doesn’t get updated, and pass it into the callback. I will investigate Dash AG Grid when I have time. I was wondering about the possibility of using class names, but I didn’t see how to do this with DataTable. That seems cleaner.

Thank you for your help!

1 Like

Actually, I’m not sure how to pass in the initial style list. I attempted to use a hidden div, and pass it in as the style like so

        html.Div(id="hidden",
            className="hidden",
            style=conditional_style
        ),

Where conditional_style has the list of conditional styles I’m initially applying to the datatable. It looks like (with many more conditions):

conditional_style=[
    {
        "if": {"column_id": "PID"}, "width": "auto", "text-align": "left", "padding-left": "10px"
    }
] + [
    {
        "if": {"column_id": "contact"}, "width": "220px", "text-align": "left", "padding-left": "10px"
    }
]

I tried to then use it in a callback to reset the datatable to its original format, but with updated data (the data update at any time) as follows:

# Callback to reset the table to the initial data
@callback(
    Output('rsync_table', 'data', allow_duplicate=True),
    Output('rsync_table', 'style_data_conditional', allow_duplicate=True),
    # Output('rsync_table', 'selected_rows'),
    Input('reset-table', 'n_clicks'),
    Input('hidden', 'style'),
    # [State('full_table', 'data')],
    prevent_initial_call=True,
)
def reset_table(n_clicks, style):
    tabledata=stats.success_data.get_success_data().to_dict("records")
    style_data_conditional = style
    return tabledata, style_data_conditional

However, this gave a runtime error:

Invalid argument style passed into Div with ID “hidden”.
Expected object.
Was supplied type array.

I understand the error, but I’m not sure what to do about it. conditional_style is what I set style_data_conditional to in the initial table…
style_data_conditional=conditional_style,

Sorry for my ignorance here.

@hardin108

If you have defined the initial style data conditional as a global variable, you will have access to it in the callback. Just need to make sure you don’t change this initial value. Note that the callback uses a copy.

Try running this:



from dash import Dash, dash_table, Input, Output, State, callback
import pandas as pd
from collections import OrderedDict


data = OrderedDict(
    [
        ("Date", ["2015-01-01", "2015-10-24", "2016-05-10", "2017-01-10", "2018-05-10", "2018-08-15"]),
        ("Region", ["Montreal", "Toronto", "New York City", "Miami", "San Francisco", "London"]),
        ("Temperature", [1, -20, 3.512, 4, 10423, -441.2]),
        ("Humidity", [10, 20, 30, 40, 50, 60]),
        ("Pressure", [2, 10924, 3912, -10, 3591.2, 15]),
    ]
)

df = pd.DataFrame(data)

initial_style_data_conditional=[
    {
        'if': {
            'filter_query': '{Humidity} > 19 && {Humidity} < 41',
            'column_id': 'Humidity'
        },
        'color': 'tomato',
        'fontWeight': 'bold'
    },
]

app = Dash(__name__)

app.layout = dash_table.DataTable(
    id='rsync_table',
    data=df.to_dict('records'),
    sort_action='native',
    row_selectable="multi",
    columns=[
        {"name": i, "id": i} for i in df.columns
    ],
    style_data_conditional=initial_style_data_conditional
)

@callback(
    Output('rsync_table', 'style_data_conditional'),
    Input('rsync_table', 'selected_rows'),
    prevent_initial_call=True,
)
def style_selected_rows(selected_rows):
    # style selected rows
    table_style = initial_style_data_conditional.copy()
    if selected_rows is not None:
        table_style += [
            {'if': {'row_index':i},
             'box-shadow': '0px 0px 3px 3px #eb99ff inset'} 
             for i in selected_rows]
    return table_style


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



Wonderful! Thank you @AnnMarieW! I had read something about the danger of using global variables in callbacks, but it makes sense that it would be okay as long as you never change it. I will be out of town and may not get a chance to try this right away, but I am confident this will resolve my last issue. Thank you so very much for your time and thoughtful guidance!!!