Search code examples
pythondatatableplotly-dashdeselect

Unselect active_cell in dash datatable (python)


All,

I am trying to implement a dash datatable, where I select rows by a direct click on it (no radio buttons). Currently, I am doing this with the active_cell and it works well: No matter in which cell of a row a user clicks, a graph is updated with the data in that row. If he clicks another cell in the same row, the data is unselected (via a dcc.storage)

Here comes my problem: If the user clicks the same cell again, there is no active_cell event triggered. Therefore, my callback is not triggered and nothing happens. I would like to deselect that cell the second time the user clicks it. How can I implement that?

Thanks!


Solution

  • So... I solved this... it is not pretty but it works - it includes a loopbreaker which I had to implement to avoid a circular dependency, but yeah - I am absolutely open for cleaner solutions.

    Find below the callbacks

        # takes user selected cell (active_cell) and the current state of a dcc.storage (tableclick) which stores the last saved row that was clicked
    # output: updated selected_rows for the datatable, styling for the selected row and update for dcc.storage
    # if no cell is selected, do nothing
    # if no cell is selected, but there is a row stored as selected, highlight that row (this is a consequence from the circular dependency)
    # if a cell is selected that is different from the previous cell, highlight that new row. Otherwise, deselect the row.
    @app.callback(
        [Output('performancedatatable', 'style_data_conditional'), Output('tableclick', 'data'),
         Output('performancedatatable', 'selected_rows')],
        [
            Input('performancedatatable', 'active_cell'),
        ], [State('tableclick', 'data')]
    )
    def highlight_row(cell, prevrow):
        if cell is None:
            if prevrow is None:
                return [{}], None, []
            else:
                return [{}], None, prevrow
        elif "row" in cell:
            if cell.get("row", "") == prevrow:
                return [{}], None, []
            else:
                return ([{
                    "if": {"row_index": cell.get("row", "")},
                    "backgroundColor": "rgba(146, 192, 234, 0.5)",
                }], cell.get("row", ""), [cell.get("row", "")])
    
    
    # Is triggered by changing the dcc.storage "tableclick"
    # resets active cell and selected cell via callback below
    @app.callback([Output('loopbreaker_div', "children")], [Input('tableclick', 'data')])
    def reset_active_cell(input):
        return [html.Div(id='loopbreaker', children=True)]
    
    
    #loopbreaker to avoid circular dependency
    @app.callback([Output('performancedatatable', "active_cell"), Output('performancedatatable', 'selected_cells')], [Input('loopbreaker', 'children')])
    def reset_active_cell(input):
        time.sleep(1)
        return (None, [])
    

    Shoutout to http://yaaics.blogspot.com/2019/03/circular-references-in-plotlydash.html for helping resolving the circular dependency