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

398 views Asked by At

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)
1

There are 1 answers

0
Davide_sd On BEST ANSWER

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)