pythonplotlyholoviz-panel

interactive 3D surface plot - how to control Plotly's update behaviour


I'm building an interactive plot with the code below: everything works fine, except that Plotly refreshes the figure every time I change a property. So when I move the slider by one tick, Plotly refreshes the figure 4 times, which is annoying to see.

Is there a way to ask Plotly to only refresh the figure at the end of the update function?

import numpy as np
import plotly.graph_objects as go
import panel as pn
pn.extension("plotly")

color_func = lambda x, y, z: x * y
f = lambda r, d: 10 * np.cos(r) * np.exp(-r * d)
x, y = np.mgrid[-7:7:100j, -7:7:100j]
r = np.sqrt(x**2 + y**2)
z = f(r, 0.1)
surfacecolor = color_func(x, y, z)
fig = go.Figure([
    go.Surface(x=x, y=y, z=z, surfacecolor=surfacecolor, cmin=surfacecolor.min(), cmax=surfacecolor.max())
])
fig.update_layout({"scene": {"aspectmode": "cube"}})

slider = pn.widgets.FloatSlider(start=0, end=1, value=0.1)
@pn.depends(slider)
def update(d):
    surfacecolor = color_func(d * x, y, z)
    fig.data[0]["z"] = f(r, d)
    fig.data[0]["surfacecolor"] = surfacecolor
    fig.data[0]["cmin"] = surfacecolor.min()
    fig.data[0]["cmax"] = surfacecolor.max()
    return fig
pn.Column(slider, update)

Solution

  • Turns out that I need to use panel's Plotly pane. Note that its constructor requires a dictionary with the keys data, layout. If you give it a Plotly figure it will work, but the update will be extremely slow and unreliable.

    So this is the correct way to achieve my goal. By executing this code, the update will be very fast in comparison to the original attempt.

    import numpy as np
    import plotly.graph_objects as go
    import panel as pn
    pn.extension("plotly")
    
    color_func = lambda x, y, z: x * y
    f = lambda r, d: 10 * np.cos(r) * np.exp(-r * d)
    x, y = np.mgrid[-7:7:100j, -7:7:100j]
    r = np.sqrt(x**2 + y**2)
    z = f(r, 0.1)
    surfacecolor = color_func(x, y, z)
    
    fig = dict(
        data=[
            go.Surface(x=x, y=y, z=z,
                surfacecolor=surfacecolor, cmin=surfacecolor.min(), cmax=surfacecolor.max())
        ],
        layout=go.Layout(scene={"aspectmode": "cube"})
    )
    slider = pn.widgets.FloatSlider(start=0, end=1, value=0.1)
    pane = pn.pane.Plotly(fig)
    
    def update(event):
        d = slider.value
        surfacecolor = color_func(d * x, y, z)
        fig["data"][0]["z"] = f(r, d)
        fig["data"][0]["surfacecolor"] = surfacecolor
        fig["data"][0]["cmin"] = surfacecolor.min()
        fig["data"][0]["cmax"] = surfacecolor.max()
        pane.object = fig
    slider.param.watch(update, "value")
    
    pn.Column(slider, pane)