Why is updating Plotly traces so slow?

Hi,

I have a bunch of Plotly plots and subplots (about 40 in total) that I need to update when the user clicks a button. Specifically, there’s one trace where I need to add a point every time the button is clicked, and this trace needs to be updated on all plots at the same time (same trace, replicated on all plots).

But this is very slow: about 15 seconds. It’s slower than loading the Panel from scratch, and much slower than refreshing it once loaded (when I save some changes to the Python script). Yet I’m doing this in a way that seems like it should be efficient: I keep a list of that trace’s points in memory, update the list once when the button is clicked, convert that list to a tuple once, and then replace the existing tuple for that specific trace on every plot with the new one.

But it takes a very long time, and I can clearly see each plot updating one after the other. This leads me to believe that perhaps when I update a given trace, Panel (or Plotly?) reloads/rebuilds/refreshes a bunch of stuff in the background, and that I’m paying this overhead 40 times instead of just once.

If this guess is correct, how I can stop this needless refreshing? Or am I just trying to be too clever for my own good here, and should I just somehow destroy the figures and rebuild them all from scratch? That doesn’t seem optimal, but perhaps it is.

Thanks!

1 Like

Hi @Lexoka

Could you try posting a minmum, reproducible example? There could be so many things causing this, that without some code its almost impossible to identifify the cause.

1 Like

Hi @Marc, that’s a good point, thanks.

The following bit of code is about as minimal as I can make it, I think. The update_meta_fig() function is the one that results in very slow behavior, the make_meta_fig() one just creates the subplots, and actually runs pretty fast, even though it seems like it should be doing a lot more work.

from functools import partial
import panel as pn
import plotly.graph_objects as go
from plotly.subplots import make_subplots
pn.extension('plotly')

def make_meta_fig(mandated_width, nb_points):
    nb_figures = 40
    meta_fig = make_subplots(cols=1, rows=nb_figures)

    # Initially empty trace to be updated by the user later on
    xSeries = list(range(nb_points))
    ySeries = nb_points * [None]
    events_trace = go.Scatter(x = xSeries, y = ySeries, name="registered events", mode="markers")

    # Traces with fixed data
    for i in range(nb_figures):
        xSeries = list(range(nb_points))
        ySeries = [i*x for x in xSeries]
        my_trace = go.Scatter(x = xSeries, y = ySeries, name="fixed_data")
        meta_fig.add_trace(my_trace, col=1, row=i+1)
        meta_fig.add_trace(events_trace, col=1, row=i+1)

    meta_fig.update_layout(height = nb_figures * 100, width = mandated_width)

    return(meta_fig)


def update_meta_fig(meta_fig, nb_figures, nb_points, event):
    list_trace = nb_points * [None]
    list_trace[42] = 84
    new_trace = tuple(list_trace)
    for trace in meta_fig["data"]:
        if trace["name"] == "registered events":
            trace["y"] = new_trace


def main():
    nb_figures = 40
    pn.extension()
    gb = pn.GridBox(ncols = 1)
    FIXED_WIDTH = 600
    nb_points = 500

    meta_fig = make_meta_fig(FIXED_WIDTH, nb_points)
    plot_panel = pn.pane.Plotly(meta_fig, width=FIXED_WIDTH)
    gb.append(plot_panel)
    validationButton = pn.widgets.Button(name='Validate', button_type='primary')
    validationButton.on_click(partial(update_meta_fig, meta_fig, nb_figures, nb_points))
    gb.append(validationButton)
    return(gb)


if __name__.startswith("bokeh"):
    dashboard = main()
    dashboard.servable()
1 Like

Hi @Lexoka

Looking at the code I see you update the meta_fig Plotly Figure object in place when the button is clicked. I actually don’t understand why this updates the plot. I would have expected you need to update the plot_panel.object to get it to update.

What you can do is instead take a copy of the existing object go.Figure(meta_fig), update it in place and then update the plot_panel.object.

It updates in ~1½ seconds for me.

from functools import partial
import panel as pn
import plotly.graph_objects as go
from plotly.subplots import make_subplots

pn.extension('plotly')

FIXED_WIDTH = 600

def make_meta_fig(mandated_width, nb_points):
    nb_figures = 40
    meta_fig = make_subplots(cols=1, rows=nb_figures)

    # Initially empty trace to be updated by the user later on
    xSeries = list(range(nb_points))
    ySeries = nb_points * [None]
    events_trace = go.Scatter(x = xSeries, y = ySeries, name="registered events", mode="markers")

    # Traces with fixed data
    for i in range(nb_figures):
        xSeries = list(range(nb_points))
        ySeries = [i*x for x in xSeries]
        my_trace = go.Scatter(x = xSeries, y = ySeries, name="fixed_data")
        meta_fig.add_trace(my_trace, col=1, row=i+1)
        meta_fig.add_trace(events_trace, col=1, row=i+1)

    meta_fig.update_layout(height = nb_figures * 100, width = mandated_width)

    return meta_fig

def update_meta_fig(meta_fig, nb_figures, nb_points):
    meta_fig = go.Figure(meta_fig)
    
    list_trace = nb_points * [None]
    list_trace[42] = 84
    new_trace = tuple(list_trace)
    for trace in meta_fig["data"]:
        if trace["name"] == "registered events":
            trace["y"] = new_trace
    return meta_fig

def main():
    nb_figures = 40
    pn.extension()
    gb = pn.GridBox(ncols = 1)
    nb_points = 500

    meta_fig = make_meta_fig(FIXED_WIDTH, nb_points)
    plot_panel = pn.pane.Plotly(meta_fig, width=FIXED_WIDTH)
    gb.append(plot_panel)
    validationButton = pn.widgets.Button(name='Validate', button_type='primary')
    
    @pn.depends(validationButton, watch=True)
    def update(event):
        plot_panel.object = update_meta_fig(meta_fig, nb_figures, nb_points)

    
    gb.append(validationButton)
    return(gb)


if __name__.startswith("bokeh"):
    dashboard = main()
    dashboard.servable()

I created an issue on github Plotly figure updated in place triggers update of plotly pane??? · Issue #4141 · holoviz/panel (github.com)

1 Like

Thank you very much, Marc, that is indeed much faster, including on my actual code in addition to the minimal example provided here. If I understand the code you referenced in the GitHub issue correctly, any change to a figure will send a message that Panel will interpret as a signal to refresh the pane, right?

1 Like

Yes

1 Like