1
"""
2
Pane class which render plots from different libraries
3
"""
4 6
from __future__ import absolute_import, division, unicode_literals
5

6 6
import sys
7

8 6
from io import BytesIO
9

10 6
from contextlib import contextmanager
11

12 6
import param
13

14 6
from bokeh.models import CustomJS, LayoutDOM, Model, Spacer as BkSpacer
15

16 6
from ..io import remove_root
17 6
from ..io.notebook import push
18 6
from ..viewable import Layoutable
19 6
from .base import PaneBase
20 6
from .ipywidget import IPyWidget
21 6
from .markup import HTML
22 6
from .image import PNG
23

24

25 6
@contextmanager
26 1
def _wrap_callback(cb, wrapped, doc, comm, callbacks):
27
    """
28
    Wraps a bokeh callback ensuring that any events triggered by it
29
    appropriately dispatch events in the notebook. Also temporarily
30
    replaces the wrapped callback with the real one while the callback
31
    is exectuted to ensure the callback can be removed as usual.
32
    """
33 0
    hold = doc._hold
34 0
    doc.hold('combine')
35 0
    if wrapped in callbacks:
36 0
        index = callbacks.index(wrapped)
37 0
        callbacks[index] = cb
38 0
    yield
39 0
    if cb in callbacks:
40 0
        index = callbacks.index(cb)
41 0
        callbacks[index] = wrapped
42 0
    push(doc, comm)
43 0
    doc.hold(hold)
44

45

46 6
class Bokeh(PaneBase):
47
    """
48
    Bokeh panes allow including any Bokeh model in a panel.
49
    """
50

51 6
    priority = 0.8
52

53 6
    @classmethod
54 1
    def applies(cls, obj):
55 6
        return isinstance(obj, LayoutDOM)
56

57 6
    @classmethod
58 1
    def _property_callback_wrapper(cls, cb, doc, comm, callbacks):
59 0
        def wrapped_callback(attr, old, new):
60 0
            with _wrap_callback(cb, wrapped_callback, doc, comm, callbacks):
61 0
                cb(attr, old, new)
62 0
        return wrapped_callback
63

64 6
    @classmethod
65 1
    def _event_callback_wrapper(cls, cb, doc, comm, callbacks):
66 0
        def wrapped_callback(event):
67 0
            with _wrap_callback(cb, wrapped_callback, doc, comm, callbacks):
68 0
                cb(event)
69 0
        return wrapped_callback
70

71 6
    @classmethod
72 1
    def _wrap_bokeh_callbacks(cls, root, bokeh_model, doc, comm):
73 6
        for model in bokeh_model.select({'type': Model}):
74 6
            for key, cbs in model._callbacks.items():
75 0
                callbacks = model._callbacks[key]
76 0
                callbacks[:] = [
77
                    cls._property_callback_wrapper(cb, doc, comm, callbacks)
78
                    for cb in cbs
79
                ]
80 6
            for key, cbs in model._event_callbacks.items():
81 0
                callbacks = model._event_callbacks[key]
82 0
                callbacks[:] = [
83
                    cls._event_callback_wrapper(cb, doc, comm, callbacks)
84
                    for cb in cbs
85
                ]
86

87 6
    def _get_model(self, doc, root=None, parent=None, comm=None):
88 6
        if root is None:
89 0
            return self._get_root(doc, comm)
90

91 6
        if self.object is None:
92 0
            model = BkSpacer()
93
        else:
94 6
            model = self.object
95

96 6
        properties = {}
97 6
        for p, value in self.param.get_param_values():
98 6
            if (p not in Layoutable.param or p == 'name' or
99
                value is self.param[p].default):
100 6
                continue
101 6
            properties[p] = value
102 6
        model.update(**properties)
103 6
        if comm:
104 6
            self._wrap_bokeh_callbacks(root, model, doc, comm)
105

106 6
        ref = root.ref['id']
107 6
        for js in model.select({'type': CustomJS}):
108 0
            js.code = js.code.replace(model.ref['id'], ref)
109

110 6
        if model._document and doc is not model._document:
111 0
            remove_root(model, doc)
112

113 6
        self._models[ref] = (model, parent)
114 6
        return model
115

116

117 6
class Matplotlib(PNG, IPyWidget):
118
    """
119
    A Matplotlib pane renders a matplotlib figure to png and wraps the
120
    base64 encoded data in a bokeh Div model. The size of the image in
121
    pixels is determined by scaling the size of the figure in inches
122
    by a dpi of 72, increasing the dpi therefore controls the
123
    resolution of the image not the displayed size.
124
    """
125

126 6
    dpi = param.Integer(default=144, bounds=(1, None), doc="""
127
        Scales the dpi of the matplotlib figure.""")
128

129 6
    interactive = param.Boolean(default=False, constant=True, doc="""
130
    """)
131

132 6
    tight = param.Boolean(default=False, doc="""
133
        Automatically adjust the figure size to fit the
134
        subplots and other artist elements.""")
135

136 6
    _rename = {'object': 'text', 'interactive': None, 'dpi': None,  'tight': None}
137

138 6
    _rerender_params = PNG._rerender_params + ['object', 'dpi', 'tight']
139

140 6
    @classmethod
141 1
    def applies(cls, obj):
142 6
        if 'matplotlib' not in sys.modules:
143 0
            return False
144 6
        from matplotlib.figure import Figure
145 6
        is_fig = isinstance(obj, Figure)
146 6
        if is_fig and obj.canvas is None:
147 0
            raise ValueError('Matplotlib figure has no canvas and '
148
                             'cannot be rendered.')
149 6
        return is_fig
150

151 6
    def __init__(self, object=None, **params):
152 6
        super(Matplotlib, self).__init__(object, **params)
153 6
        self._managers = {}
154

155 6
    def _get_widget(self, fig):
156 0
        import matplotlib
157 0
        old_backend = getattr(matplotlib.backends, 'backend', 'agg')
158

159 0
        from ipympl.backend_nbagg import FigureManager, Canvas, is_interactive
160 0
        from matplotlib._pylab_helpers import Gcf
161

162 0
        matplotlib.use(old_backend)
163

164 0
        def closer(event):
165 0
            Gcf.destroy(0)
166

167 0
        canvas = Canvas(fig)
168 0
        fig.patch.set_alpha(0)
169 0
        manager = FigureManager(canvas, 0)
170

171 0
        if is_interactive():
172 0
            fig.canvas.draw_idle()
173

174 0
        canvas.mpl_connect('close_event', closer)
175 0
        return manager
176

177 6
    def _get_model(self, doc, root=None, parent=None, comm=None):
178 6
        if not self.interactive:
179 6
            return PNG._get_model(self, doc, root, parent, comm)
180 0
        self.object.set_dpi(self.dpi)
181 0
        manager = self._get_widget(self.object)
182 0
        props = self._process_param_change(self._init_properties())
183 0
        kwargs = {k: v for k, v in props.items()
184
                  if k not in self._rerender_params+['interactive']}
185 0
        model = self._get_ipywidget(manager.canvas, doc, root, comm,
186
                                    **kwargs)
187 0
        if root is None:
188 0
            root = model
189 0
        self._models[root.ref['id']] = (model, parent)
190 0
        self._managers[root.ref['id']] = manager
191 0
        return model
192

193 6
    def _update(self, ref=None, model=None):
194 6
        if not self.interactive:
195 6
            model.update(**self._get_properties())
196 6
            return
197 0
        manager = self._managers[ref]
198 0
        if self.object is not manager.canvas.figure:
199 0
            self.object.set_dpi(self.dpi)
200 0
            self.object.patch.set_alpha(0)
201 0
            manager.canvas.figure = self.object
202 0
            self.object.set_canvas(manager.canvas)
203 0
            event = {'width': manager.canvas._width,
204
                     'height': manager.canvas._height}
205 0
            manager.canvas.handle_resize(event)
206 0
        manager.canvas.draw_idle()
207

208 6
    def _imgshape(self, data):
209
        """Calculate and return image width,height"""
210 6
        w, h = self.object.get_size_inches()
211 6
        return int(w*72), int(h*72)
212

213 6
    def _img(self):
214 6
        self.object.set_dpi(self.dpi)
215 6
        b = BytesIO()
216

217 6
        if self.tight:
218 0
            bbox_inches = 'tight'
219
        else:
220 6
            bbox_inches = None
221

222 6
        self.object.canvas.print_figure(b, bbox_inches=bbox_inches)
223 6
        return b.getvalue()
224

225

226 6
class RGGPlot(PNG):
227
    """
228
    An RGGPlot pane renders an r2py-based ggplot2 figure to png
229
    and wraps the base64-encoded data in a bokeh Div model.
230
    """
231

232 6
    height = param.Integer(default=400)
233

234 6
    width = param.Integer(default=400)
235

236 6
    dpi = param.Integer(default=144, bounds=(1, None))
237

238 6
    _rerender_params = PNG._rerender_params + ['object', 'dpi', 'width', 'height']
239

240 6
    @classmethod
241 1
    def applies(cls, obj):
242 6
        return type(obj).__name__ == 'GGPlot' and hasattr(obj, 'r_repr')
243

244 6
    def _img(self):
245 0
        from rpy2.robjects.lib import grdevices
246 0
        from rpy2 import robjects
247 0
        with grdevices.render_to_bytesio(grdevices.png,
248
                 type="cairo-png", width=self.width, height=self.height,
249
                 res=self.dpi, antialias="subpixel") as b:
250 0
            robjects.r("print")(self.object)
251 0
        return b.getvalue()
252

253

254 6
class YT(HTML):
255
    """
256
    YT panes wrap plottable objects from the YT library.
257
    By default, the height and width are calculated by summing all
258
    contained plots, but can optionally be specified explicitly to
259
    provide additional space.
260
    """
261

262 6
    priority = 0.5
263

264 6
    @classmethod
265 1
    def applies(cls, obj):
266 6
        return (getattr(obj, '__module__', '').startswith('yt.') and
267
                hasattr(obj, "plots") and
268
                hasattr(obj, "_repr_html_"))
269

270 6
    def _get_properties(self):
271 6
        p = super(YT, self)._get_properties()
272 6
        if self.object is None:
273 6
            return p
274

275 0
        width = height = 0
276 0
        if self.width  is None or self.height is None:
277 0
            for k,v in self.object.plots.items():
278 0
                if hasattr(v, "_repr_png_"):
279 0
                    img = v._repr_png_()
280 0
                    w,h = PNG._imgshape(img)
281 0
                    height += h
282 0
                    width = max(w, width)
283

284 0
        if self.width  is None: p["width"]  = width
285 0
        if self.height is None: p["height"] = height
286

287 0
        return p
288

289

290 6
class Folium(HTML):
291
    """
292
    The Folium pane wraps Folium map components.
293
    """
294

295 6
    sizing_mode = param.ObjectSelector(default='stretch_width', objects=[
296
        'fixed', 'stretch_width', 'stretch_height', 'stretch_both',
297
        'scale_width', 'scale_height', 'scale_both', None])
298

299 6
    priority = 0.6
300

301 6
    @classmethod
302 1
    def applies(cls, obj):
303 6
        return (getattr(obj, '__module__', '').startswith('folium.') and
304
                hasattr(obj, "_repr_html_"))

Read our documentation on viewing source code .

Loading