'
@contextmanager
def _wrap_callback(cb, wrapped, doc, comm, callbacks):
"""
Wraps a bokeh callback ensuring that any events triggered by it
appropriately dispatch events in the notebook. Also temporarily
replaces the wrapped callback with the real one while the callback
is exectuted to ensure the callback can be removed as usual.
"""
hold = doc.callbacks.hold_value
doc.hold('combine')
if wrapped in callbacks:
index = callbacks.index(wrapped)
callbacks[index] = cb
yield
if cb in callbacks:
index = callbacks.index(cb)
callbacks[index] = wrapped
push(doc, comm)
doc.hold(hold)
class Bokeh(PaneBase):
"""
The Bokeh pane allows displaying any displayable Bokeh model inside a
Panel app.
Reference: https://panel.holoviz.org/reference/panes/Bokeh.html
:Example:
>>> Bokeh(some_bokeh_figure)
"""
theme = param.ClassSelector(default=None, class_=(Theme, str), doc="""
Bokeh theme to apply to the plot.""")
priority = 0.8
_rename = {'theme': None}
@classmethod
def applies(cls, obj):
return isinstance(obj, LayoutDOM)
@classmethod
def _property_callback_wrapper(cls, cb, doc, comm, callbacks):
def wrapped_callback(attr, old, new):
with _wrap_callback(cb, wrapped_callback, doc, comm, callbacks):
cb(attr, old, new)
return wrapped_callback
@classmethod
def _event_callback_wrapper(cls, cb, doc, comm, callbacks):
def wrapped_callback(event):
with _wrap_callback(cb, wrapped_callback, doc, comm, callbacks):
cb(event)
return wrapped_callback
@classmethod
def _wrap_bokeh_callbacks(cls, root, bokeh_model, doc, comm):
for model in bokeh_model.select({'type': Model}):
for key, cbs in model._callbacks.items():
callbacks = model._callbacks[key]
callbacks[:] = [
cls._property_callback_wrapper(cb, doc, comm, callbacks)
for cb in cbs
]
for key, cbs in model._event_callbacks.items():
callbacks = model._event_callbacks[key]
callbacks[:] = [
cls._event_callback_wrapper(cb, doc, comm, callbacks)
for cb in cbs
]
def _get_model(self, doc, root=None, parent=None, comm=None):
if root is None:
return self._get_root(doc, comm)
if self.object is None:
model = BkSpacer()
else:
model = self.object
properties = {}
for p, value in self.param.values().items():
if (p not in Layoutable.param or p == 'name' or
value is self.param[p].default):
continue
properties[p] = value
model.update(**properties)
if comm:
self._wrap_bokeh_callbacks(root, model, doc, comm)
ref = root.ref['id']
for js in model.select({'type': CustomJS}):
js.code = js.code.replace(model.ref['id'], ref)
if model._document and doc is not model._document:
remove_root(model, doc)
self._models[ref] = (model, parent)
if self.theme:
doc.theme = self.theme
return model
class Matplotlib(PNG, IPyWidget):
"""
The `Matplotlib` pane allows displaying any displayable Matplotlib figure
inside a Panel app.
- It will render the plot to PNG at the declared DPI and then embed it.
- If you find the figure to be clipped on the edges, you can set `tight=True`
to automatically resize objects to fit within the pane.
- If you have installed `ipympl` you will also be able to use the
interactive backend.
Reference: https://panel.holoviz.org/reference/panes/Matplotlib.html
:Example:
>>> Matplotlib(some_matplotlib_figure, dpi=144)
"""
dpi = param.Integer(default=144, bounds=(1, None), doc="""
Scales the dpi of the matplotlib figure.""")
interactive = param.Boolean(default=False, constant=True, doc="""
""")
tight = param.Boolean(default=False, doc="""
Automatically adjust the figure size to fit the
subplots and other artist elements.""")
_rename = {'object': 'text', 'interactive': None, 'dpi': None, 'tight': None}
_rerender_params = PNG._rerender_params + ['object', 'dpi', 'tight']
@classmethod
def applies(cls, obj):
if 'matplotlib' not in sys.modules:
return False
from matplotlib.figure import Figure
is_fig = isinstance(obj, Figure)
if is_fig and obj.canvas is None:
raise ValueError('Matplotlib figure has no canvas and '
'cannot be rendered.')
return is_fig
def __init__(self, object=None, **params):
super().__init__(object, **params)
self._managers = {}
def _get_widget(self, fig):
import matplotlib.backends
old_backend = getattr(matplotlib.backends, 'backend', 'agg')
from ipympl.backend_nbagg import FigureManager, Canvas, is_interactive
from matplotlib._pylab_helpers import Gcf
matplotlib.use(old_backend)
def closer(event):
Gcf.destroy(0)
canvas = Canvas(fig)
fig.patch.set_alpha(0)
manager = FigureManager(canvas, 0)
if is_interactive():
fig.canvas.draw_idle()
canvas.mpl_connect('close_event', closer)
return manager
def _get_model(self, doc, root=None, parent=None, comm=None):
if not self.interactive:
return PNG._get_model(self, doc, root, parent, comm)
self.object.set_dpi(self.dpi)
manager = self._get_widget(self.object)
props = self._process_param_change(self._init_params())
kwargs = {k: v for k, v in props.items()
if k not in self._rerender_params+['interactive']}
w, h = self.object.get_size_inches()
kwargs['width'] = self.width or int(self.dpi * w)
kwargs['height'] = self.height or int(self.dpi * h)
kwargs['sizing_mode'] = self.sizing_mode
model = self._get_ipywidget(manager.canvas, doc, root, comm,
**kwargs)
if root is None:
root = model
self._models[root.ref['id']] = (model, parent)
self._managers[root.ref['id']] = manager
return model
def _update(self, ref=None, model=None):
if not self.interactive:
model.update(**self._get_properties())
return
manager = self._managers[ref]
if self.object is not manager.canvas.figure:
self.object.set_dpi(self.dpi)
self.object.patch.set_alpha(0)
manager.canvas.figure = self.object
self.object.set_canvas(manager.canvas)
event = {'width': manager.canvas._width,
'height': manager.canvas._height}
manager.canvas.handle_resize(event)
manager.canvas.draw_idle()
def _data(self):
self.object.set_dpi(self.dpi)
b = BytesIO()
if self.tight:
bbox_inches = 'tight'
else:
bbox_inches = None
self.object.canvas.print_figure(b, bbox_inches=bbox_inches)
return b.getvalue()
class RGGPlot(PNG):
"""
An RGGPlot pane renders an r2py-based ggplot2 figure to png
and wraps the base64-encoded data in a bokeh Div model.
"""
height = param.Integer(default=400)
width = param.Integer(default=400)
dpi = param.Integer(default=144, bounds=(1, None))
_rerender_params = PNG._rerender_params + ['object', 'dpi', 'width', 'height']
@classmethod
def applies(cls, obj):
return type(obj).__name__ == 'GGPlot' and hasattr(obj, 'r_repr')
def _img(self):
from rpy2.robjects.lib import grdevices
from rpy2 import robjects
with grdevices.render_to_bytesio(grdevices.png,
type="cairo-png", width=self.width, height=self.height,
res=self.dpi, antialias="subpixel") as b:
robjects.r("print")(self.object)
return b.getvalue()
class YT(HTML):
"""
YT panes wrap plottable objects from the YT library.
By default, the height and width are calculated by summing all
contained plots, but can optionally be specified explicitly to
provide additional space.
"""
priority = 0.5
@classmethod
def applies(cls, obj):
return (getattr(obj, '__module__', '').startswith('yt.') and
hasattr(obj, "plots") and
hasattr(obj, "_repr_html_"))
def _get_properties(self):
p = super()._get_properties()
if self.object is None:
return p
width = height = 0
if self.width is None or self.height is None:
for k,v in self.object.plots.items():
if hasattr(v, "_repr_png_"):
img = v._repr_png_()
w,h = PNG._imgshape(img)
height += h
width = max(w, width)
if self.width is None: p["width"] = width
if self.height is None: p["height"] = height
return p
class Folium(HTML):
"""
The Folium pane wraps Folium map components.
"""
sizing_mode = param.ObjectSelector(default='stretch_width', objects=[
'fixed', 'stretch_width', 'stretch_height', 'stretch_both',
'scale_width', 'scale_height', 'scale_both', None])
priority = 0.6
@classmethod
def applies(cls, obj):
return (getattr(obj, '__module__', '').startswith('folium.') and
hasattr(obj, "_repr_html_"))
def _get_properties(self):
properties = super()._get_properties()
text = '' if self.object is None else self.object
if hasattr(text, '_repr_html_'):
text = text._repr_html_().replace(FOLIUM_BEFORE, FOLIUM_AFTER)
return dict(properties, text=escape(text))