Slow update of plot even with client side callback - trying to highlight traces on hover

Hi,

I have a plot with multiple line traces. I want to highlight a line when hovering over it, by increasing the line width and by changing the opacity of all other traces. This is what it looks like:

highlight_on_hover

For a small number of traces, this works well. In my application however, I expect to have at least 100 traces.

I already tried implementing this both as a regular server side callback and as a client side callback but even with a client side callback, the plot updates are slow for 100 traces. I built a small example app here:

import dash
from dash import dcc, html, callback, clientside_callback, Input, Output, State, no_update
import pandas as pd
import numpy as np
import plotly
from plotly import graph_objects as go


app = dash.Dash(__name__)


app.layout = html.Div(
    [
        dcc.Input(id="n-curves-input", value=10, type="number", min=1),
        html.Button(id="update-button", children="Update Plot"),
        dcc.Graph(id="plot"),
    ]
)


@callback(
    Output("plot", "figure"),
    Input("update-button", "n_clicks"),
    State("n-curves-input", "value"),
)
def update_plot(_n_clicks, n_curves):
    df = pd.DataFrame()
    df["curve_id"] = np.arange(n_curves)
    curve_data = np.random.randn(n_curves, 45)

    fig = go.Figure()
    for i, row in df.iterrows():
        fig.add_trace(
            go.Scatter(
                x=np.arange(45),
                y=curve_data[i],
                mode="lines+markers",
                marker_color="rgba(0,0,0,0)",
                line_color=plotly.colors.DEFAULT_PLOTLY_COLORS[i % len(plotly.colors.DEFAULT_PLOTLY_COLORS)],
                customdata=[row["curve_id"]] * 45,
                hovertemplate="<b>%{customdata}</b><br><br>",
                line_width=2
            )
        )

    fig.update_layout(height=800)

    return fig

# server side callback
# @callback(
#     Output("plot", "figure", allow_duplicate=True),
#     Input("plot", "hoverData"),
#     State("plot", "figure"),
#     prevent_initial_call=True,
# )
# def highlight_curve(hover_data, fig):
#     if hover_data is None:
#         return no_update
#     curve_id = hover_data["points"][0]["customdata"]
#     for trace in fig["data"]:
#         if trace["customdata"][0] == curve_id:
#             trace["line"]["width"] = 4
#             trace["opacity"] = 1
#         else:
#             trace["line"]["width"] = 2
#             trace["opacity"] = 0.4

#     return fig


# client side callback
clientside_callback(
    """
    function(hoverData, fig) {
        if (!hoverData) {
            return window.dash_clientside.no_update;
        }
        const curveId = hoverData.points[0].customdata;
        for (let i = 0; i < fig.data.length; i++) {
            if (fig.data[i].customdata[0] === curveId) {
                fig.data[i].line.width = 4;
                fig.data[i].opacity = 1;
            } else {
                fig.data[i].line.width = 2;
                fig.data[i].opacity = 0.4;
            }
        }
        return {...fig};
    }
    """,
    Output("plot", "figure", allow_duplicate=True),
    Input("plot", "hoverData"),
    State("plot", "figure"),
    prevent_initial_call=True,
)


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

Do you have any idea for improving the performance of this so that the highlighting feels smooth for ~100-200 traces?

Hi @christoph.b

Try using Partial Property Updates: