I’m experiencing huge performance hit when using template and serving an application containing Bokeh plots to a new browser window, vs running the same application but without template on a Jupyter Notebook.
In the following example, BokehPlot
is a class responsible to create a figure, and updating it as new values of parameters come in.
InteractivePlot
is a parameterized class: it accepts any number of instances of BokehPlot
and arrange them in a GridSpec
layout. There is two modes of operation:
-
InteractivePlot(plot1, plot2, servable=False).show()
: the app is run on Jupyter Notebook. Interactivity is fast, the app feels snappy. -
InteractivePlot(plot1, plot2, servable=True).show()
: the app is launched on a new browser window and a template is applied. Users might want to use this mode when the number of parameters is getting big, or when there are many plots to show. However. interactivity is bad, there is a lot of lag.
What can I do to improve performance?
import panel as pn
import numpy as np
import param
import bokeh
pn.extension()
class BokehPlot:
def __init__(self, *functions):
self.f = functions
self.x = np.logspace(-1, 1, 1000)
self._fig = None
@property
def fig(self):
if self._fig is None:
self.create_figure()
return self._fig
def create_figure(self):
TOOLTIPS = [("x", "$x"), ("y", "$y")]
self._fig = bokeh.plotting.figure(
x_axis_type="log",
tools="pan,wheel_zoom,box_zoom,reset,hover,save",
tooltips=TOOLTIPS
)
for f in self.f:
source = {
"xs": self.x,
"ys": f(1, 0.1, 7.5, 1, 0.12, 9, self.x)
}
self._fig.line("xs", "ys", source=source, line_width=2)
def update_figure(self, v):
for i, f in enumerate(self.f):
source = {
"xs": self.x,
"ys": f(*v, self.x)
}
self._fig.renderers[i].data_source.data.update(source)
def show(self):
bokeh.plotting.show(self.fig)
def _new_class(cls, **kwargs):
"Creates a new class which overrides parameter defaults."
return type(type(cls).__name__, (cls,), kwargs)
class InteractivePlot(param.Parameterized):
a = param.Number(1, bounds=(-10, 10))
b = param.Number(0.1, bounds=(-1, 1))
c = param.Number(7.5, bounds=(-10, 10))
d = param.Number(1, bounds=(-10, 10))
e = param.Number(0.12, bounds=(-1, 1))
f = param.Number(9, bounds=(-10, 10))
check_val = param.Integer(default=0)
def __init__(self, *plots, servable=False):
super().__init__()
self.plots = plots
self.servable = servable
self._parameters = ["a", "b", "c", "d", "e", "f"]
widgets = {}
for name in self._parameters:
self.param.watch(self._increment_val, name)
widgets[name] = {"type": pn.widgets.FloatSlider}
self.controls = pn.Param(
self,
parameters=self._parameters,
widgets=widgets,
default_layout=_new_class(pn.GridBox, ncols=1 if self.servable else 2),
show_name=False,
sizing_mode="stretch_width",
)
def _increment_val(self, *depends):
self.check_val += 1
@param.depends("check_val", watch=True)
def update(self):
values = [getattr(self, p) for p in self._parameters]
for pane, plot in zip(self.panes, self.plots):
plot.update_figure(values)
fig = plot.fig
pane.param.trigger("object")
pane.object = fig
def show(self):
gs = pn.GridSpec(height=600)
self.panes = []
for i, p in enumerate(self.plots):
fig = p.fig
pane = pn.pane.panel(fig)
gs[i, 0] = pane
self.panes.append(pane)
if not self.servable:
return pn.Column(self.controls, gs)
templ = pn.template.BootstrapTemplate()
templ.sidebar.append(self.controls)
templ.main.append(gs)
return templ.servable().show()
def f1(a, b, c, d, e, f, w):
return 20*np.log(np.abs((-w**2*a + w*1j*b + c)/(-w**4*d + w**3j*e + w**2*f)))/np.log(10)
def f2(a, b, c, d, e, f, w):
return np.angle((-w**2*a + w*1j*b + c)/(w**2*(w**2*d - w*1j*e - f)))
p1 = BokehPlot(f1)
p2 = BokehPlot(f2)
# change servable=True to servable=False to run it on Jupyter Notebook
InteractivePlot(p1, p2, servable=True).show()
Here are a couple of screen records. Running on Jupyter Notebook (fast response):
Running on a browser window (note the lags):