Use hoverData and clickData from scatter plot to interact with DataTable

Hi, I would like to use hoverData and clickData from a plot to highlight certain rows in a DataTable WITHOUT RELOADING the datatable. It should work all on the front end to avoid performance issues. Does anybody know how to do it?

Thank you very much.

1 Like

Hi @HansPeter123

It’s possible to update just the style properties of the table (ie style_cell, style_data etc) in a callback. This should update the style without reloading all of the data.

Let me know if you would like more details or an example.

Hi @AnnMarieW thank you very much. I would really appreciated any details or an example. :slight_smile:

And I forgot to mention one thing: When I click on a datapoint in the plot it should not only highlight the respective row in the DataTable but also automatically show the row i.e. it should ‘automatically scroll to this row’. I hope I am asking not too much. Thanks.

Hi @HansPeter123

Well, I’m not sure how to automatically scroll to a row in a table, but this code uses a sunburst chart to do a drill down on a table with clickdata, and highlights data on hover. It will at least give you an idea of how to update style based on hover or click data.

import dash
import dash_table
from dash.dependencies import Input, Output

import dash_core_components as dcc
import dash_html_components as html
import plotly.express as px


external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

df = px.data.tips()

path = ["day", "time", "sex"]

app.layout = html.Div(
    [
        html.H4(id="title"),
        html.Div(
            [
                dcc.Graph(
                    id="sunburst",
                    figure=px.sunburst(df, path=path, values="total_bill"),
                )
            ]
        ),
        html.Div(
            [
                dash_table.DataTable(
                    id="table",
                    columns=[{"name": i, "id": i} for i in df.columns],
                    data=df.to_dict("records"),
                    sort_action="native",
                )
            ]
        ),
    ]
)


@app.callback(
    [
        Output("table", "data"),
        Output("table", "style_data_conditional"),
        Output("title", "children"),
    ],
    [Input("sunburst", "clickData"), Input("sunburst", "hoverData")],
)
def update_table(clickData, hoverData):

    click_path = "ALL"
    root = False
    data = df.to_dict("records")

    ctx = dash.callback_context
    input_id = ctx.triggered[0]["prop_id"]

    style_data_conditional = []
    if hoverData:
        hover_path = hoverData["points"][0]["id"].split("/")       
        if len(hover_path) == 1:
            selected = [hover_path[0], "none", "none"]
        elif len(hover_path) == 2:
            selected = [hover_path[0], hover_path[1], "none"]
        else:
            selected = hover_path

        style_data_conditional = [
            {
                "if": {
                    "filter_query": "{{day}} = {}".format(selected[0]),
                    "column_id": "day",
                },
                "backgroundColor": "#ffe0de",
                "color": "white",
            },
            {
                "if": {
                    "filter_query": "{{time}} = {}".format(selected[1]),
                    "column_id": "time",
                },
                "backgroundColor": "#e0deff",
                "color": "white",
            },
            {
                "if": {
                    "filter_query": "{{sex}} = {}".format(selected[2]),
                    "column_id": "sex",
                },
                "backgroundColor": "#deffe0",
            },
        ]

    if input_id == "sunburst.clickData":
        style_data_conditional = []
        click_path = clickData["points"][0]["id"].split("/")

        selected = dict(zip(path, click_path))

        if "sex" in selected:
            dff = df[
                (df["day"] == selected["day"])
                & (df["time"] == selected["time"])
                & (df["sex"] == selected["sex"])
            ]
        elif "time" in selected:
            dff = df[(df["day"] == selected["day"]) & (df["time"] == selected["time"])]
        else:
            dff = df[(df["day"] == selected["day"])]
            root = True
        data = dff.to_dict("records")

        # Show all data when returning to the root from an outer leaf
        percentEntry = (clickData["points"][0]).get("percentEntry")
        if root and percentEntry == 1:
            data = df.to_dict("records")
            click_path = "ALL"

    title = f"Selected From Sunburst Chart: {' '.join(click_path)}"

    return data, style_data_conditional, title


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

1 Like

Thank you very much, this is a very nice example. I wish you a great day.

Regarding your solution. Can any property of the DataTable be updated in this way? I have looked into the DataTable reference (https://dash.plotly.com/datatable/reference) and DataTables can have so many different properties. I would like to update the property ‘derived_virtual_row_ids’ in a comparable way as the property ‘style_data_conditional’ is updated. The logic inside my callback seems to work fine, but no changes to the DataTable are made. (The goal is again to avoid reloading the whole DT and thereby save time)
Thank you

Here in the second example (https://dashr.plotly.com/datatable/interactivity) the property ‘derived_virtual_row_ids’ is used as an input. In my use case it would be the output and the input is the value of an dccRadioItem.

Yes, all of the properties can be updated in the callback, but some are read-only. In the documentation that’s noted in some cases, like with “data_previous”.

“derived_virtual_row_ids” indicates the order in which the rows appear after sorting and filtering. It doesn’t actually do the sort or the filter if you update this property in a callback.

Dash does support server side sorting and filtering, like in this example: https://dash.plotly.com/datatable/filtering If you have a large data set, using a “custom” sort_action or filter_action can reduce the amount of data sent over the network.

Thank you, you helped me again. My final solution is to use the property ‘filter_query’ to filter the DT on the front end based on a column that is hidden from view.

1 Like