holoviz / panel
1
"""
2
Pane class which render various markup languages including HTML,
3
Markdown, and also regular strings.
4
"""
5 7
import json
6 7
import textwrap
7

8 7
from six import string_types
9

10 7
import param
11

12 7
from ..models import HTML as _BkHTML, JSON as _BkJSON
13 7
from ..util import escape
14 7
from ..viewable import Layoutable
15 7
from .base import PaneBase
16

17

18 7
class DivPaneBase(PaneBase):
19
    """
20
    Baseclass for Panes which render HTML inside a Bokeh Div.
21
    See the documentation for Bokeh Div for more detail about
22
    the supported options like style and sizing_mode.
23
    """
24

25 7
    style = param.Dict(default=None, doc="""
26
        Dictionary of CSS property:value pairs to apply to this Div.""")
27

28 7
    _bokeh_model = _BkHTML
29

30 7
    _rename = {'object': 'text'}
31

32 7
    _updates = True
33

34 7
    __abstract = True
35

36 7
    def _get_properties(self):
37 7
        return {p : getattr(self, p) for p in list(Layoutable.param) + ['style']
38
                if getattr(self, p) is not None}
39

40 7
    def _get_model(self, doc, root=None, parent=None, comm=None):
41 7
        model = self._bokeh_model(**self._get_properties())
42 7
        if root is None:
43 7
            root = model
44 7
        self._models[root.ref['id']] = (model, parent)
45 7
        return model
46

47 7
    def _update(self, ref=None, model=None):
48 7
        model.update(**self._get_properties())
49

50

51 7
class HTML(DivPaneBase):
52
    """
53
    HTML panes wrap HTML text in a Panel HTML model. The
54
    provided object can either be a text string, or an object that
55
    has a `_repr_html_` method that can be called to get the HTML
56
    text string.  The height and width can optionally be specified, to
57
    allow room for whatever is being wrapped.
58
    """
59

60
    # Priority is dependent on the data type
61 7
    priority = None
62

63 7
    @classmethod
64 2
    def applies(cls, obj):
65 7
        module, name = getattr(obj, '__module__', ''), type(obj).__name__
66 7
        if ((any(m in module for m in ('pandas', 'dask')) and
67
            name in ('DataFrame', 'Series')) or hasattr(obj, '_repr_html_')):
68 7
            return 0.2
69 7
        elif isinstance(obj, string_types):
70 7
            return None
71
        else:
72 7
            return False
73

74 7
    def _get_properties(self):
75 7
        properties = super()._get_properties()
76 7
        text = '' if self.object is None else self.object
77 7
        if hasattr(text, '_repr_html_'):
78 0
            text = text._repr_html_()
79 7
        return dict(properties, text=escape(text))
80

81

82 7
class DataFrame(HTML):
83
    """
84
    DataFrame renders pandas, dask and streamz DataFrame types using
85
    their custom HTML repr. In the case of a streamz DataFrame the
86
    rendered data will update periodically.
87
    """
88

89 7
    bold_rows = param.Boolean(default=True, doc="""
90
        Make the row labels bold in the output.""")
91

92 7
    border = param.Integer(default=0, doc="""
93
        A ``border=border`` attribute is included in the opening
94
        `<table>` tag.""")
95

96 7
    classes = param.List(default=['panel-df'], doc="""
97
        CSS class(es) to apply to the resulting html table.""")
98

99 7
    col_space = param.ClassSelector(default=None, class_=(str, int), doc="""
100
        The minimum width of each column in CSS length units. An int
101
        is assumed to be px units.""")
102

103 7
    decimal = param.String(default='.', doc="""
104
        Character recognized as decimal separator, e.g. ',' in Europe.""")
105

106 7
    float_format = param.Callable(default=None, doc="""
107
        Formatter function to apply to columns' elements if they are
108
        floats. The result of this function must be a unicode string.""")
109

110 7
    formatters = param.ClassSelector(default=None, class_=(dict, list), doc="""
111
        Formatter functions to apply to columns' elements by position
112
        or name. The result of each function must be a unicode string.""")
113

114 7
    header = param.Boolean(default=True, doc="""
115
        Whether to print column labels.""")
116

117 7
    index = param.Boolean(default=True, doc="""
118
        Whether to print index (row) labels.""")
119

120 7
    index_names = param.Boolean(default=True, doc="""
121
        Prints the names of the indexes.""")
122

123 7
    justify = param.ObjectSelector(default=None, allow_None=True, objects=[
124
        'left', 'right', 'center', 'justify', 'justify-all', 'start',
125
        'end', 'inherit', 'match-parent', 'initial', 'unset'], doc="""
126
        How to justify the column labels.""")
127

128 7
    max_rows = param.Integer(default=None, doc="""
129
        Maximum number of rows to display.""")
130

131 7
    max_cols = param.Integer(default=None, doc="""
132
        Maximum number of columns to display.""")
133

134 7
    na_rep = param.String(default='NaN', doc="""
135
        String representation of NAN to use.""")
136

137 7
    render_links = param.Boolean(default=False, doc="""
138
        Convert URLs to HTML links.""")
139

140 7
    show_dimensions = param.Boolean(default=False, doc="""
141
        Display DataFrame dimensions (number of rows by number of
142
        columns).""")
143

144 7
    sparsify = param.Boolean(default=True, doc="""
145
        Set to False for a DataFrame with a hierarchical index to
146
        print every multi-index key at each row.""")
147

148 7
    _object = param.Parameter(default=None, doc="""Hidden parameter.""")
149

150 7
    _dask_params = ['max_rows']
151

152 7
    _rerender_params = [
153
        'object', '_object', 'bold_rows', 'border', 'classes',
154
        'col_space', 'decimal', 'float_format', 'formatters',
155
        'header', 'index', 'index_names', 'justify', 'max_rows',
156
        'max_cols', 'na_rep', 'render_links', 'show_dimensions',
157
        'sparsify', 'sizing_mode'
158
    ]
159

160 7
    def __init__(self, object=None, **params):
161 7
        super().__init__(object, **params)
162 7
        self._stream = None
163 7
        self._setup_stream()
164

165 7
    @classmethod
166 2
    def applies(cls, obj):
167 7
        module = getattr(obj, '__module__', '')
168 7
        name = type(obj).__name__
169 7
        if (any(m in module for m in ('pandas', 'dask', 'streamz')) and
170
            name in ('DataFrame', 'Series', 'Random', 'DataFrames', 'Seriess')):
171 7
            return 0.3
172
        else:
173 7
            return False
174

175 7
    def _set_object(self, object):
176 0
        self._object = object
177

178 7
    @param.depends('object', watch=True)
179 2
    def _setup_stream(self):
180 7
        if not self._models or not hasattr(self.object, 'stream'):
181 7
            return
182 7
        elif self._stream:
183 7
            self._stream.destroy()
184 7
            self._stream = None
185 7
        self._stream = self.object.stream.latest().rate_limit(0.5).gather()
186 7
        self._stream.sink(self._set_object)
187

188 7
    def _get_model(self, doc, root=None, parent=None, comm=None):
189 7
        model = super()._get_model(doc, root, parent, comm)
190 7
        self._setup_stream()
191 7
        return model
192

193 7
    def _cleanup(self, model):
194 7
        super()._cleanup(model)
195 7
        if not self._models and self._stream:
196 7
            self._stream.destroy()
197 7
            self._stream = None
198

199 7
    def _get_properties(self):
200 7
        properties = DivPaneBase._get_properties(self)
201 7
        if self._stream:
202 7
            df = self._object
203
        else:
204 7
            df = self.object
205 7
        if hasattr(df, 'to_frame'):
206 7
            df = df.to_frame()
207

208 7
        module = getattr(df, '__module__', '')
209 7
        if hasattr(df, 'to_html'):
210 7
            if 'dask' in module:
211 0
                html = df.to_html(max_rows=self.max_rows).replace('border="1"', '')
212
            else:
213 7
                kwargs = {p: getattr(self, p) for p in self._rerender_params
214
                          if p not in DivPaneBase.param and p != '_object'}
215 7
                html = df.to_html(**kwargs)
216
        else:
217 7
            html = ''
218 7
        return dict(properties, text=escape(html))
219

220

221 7
class Str(DivPaneBase):
222
    """
223
    A Str pane renders any object for which `str()` can be called,
224
    escaping any HTML markup and then wrapping the resulting string in
225
    a bokeh Div model.  Set to a low priority because generally one
226
    will want a better representation, but allows arbitrary objects to
227
    be used as a Pane (numbers, arrays, objects, etc.).
228
    """
229

230 7
    priority = 0
231

232 7
    _target_transforms = {'object': """JSON.stringify(value).replace(/,/g, ", ").replace(/:/g, ": ")"""}
233

234 7
    _bokeh_model = _BkHTML
235

236 7
    @classmethod
237 2
    def applies(cls, obj):
238 7
        return True
239

240 7
    def _get_properties(self):
241 7
        properties = super()._get_properties()
242 7
        if self.object is None:
243 7
            text = ''
244
        else:
245 7
            text = '<pre>'+str(self.object)+'</pre>'
246 7
        return dict(properties, text=escape(text))
247

248

249 7
class Markdown(DivPaneBase):
250
    """
251
    A Markdown pane renders the markdown markup language to HTML and
252
    displays it inside a bokeh Div model. It has no explicit
253
    priority since it cannot be easily be distinguished from a
254
    standard string, therefore it has to be invoked explicitly.
255
    """
256

257 7
    dedent = param.Boolean(default=True, doc="""
258
        Whether to dedent common whitespace across all lines.""")
259

260 7
    extensions = param.List(default=[
261
        "extra", "smarty", "codehilite"], doc="""
262
        Markdown extension to apply when transforming markup.""")
263

264
    # Priority depends on the data type
265 7
    priority = None
266

267 7
    _target_transforms = {'object': None}
268

269 7
    _rerender_params = ['object', 'dedent', 'extensions', 'css_classes']
270

271 7
    @classmethod
272 2
    def applies(cls, obj):
273 7
        if hasattr(obj, '_repr_markdown_'):
274 0
            return 0.3
275 7
        elif isinstance(obj, string_types):
276 7
            return 0.1
277
        else:
278 7
            return False
279

280 7
    def _get_properties(self):
281 7
        import markdown
282 7
        data = self.object
283 7
        if data is None:
284 7
            data = ''
285 7
        elif not isinstance(data, string_types):
286 0
            data = data._repr_markdown_()
287 7
        if self.dedent:
288 7
            data = textwrap.dedent(data)
289 7
        properties = super()._get_properties()
290 7
        properties['style'] = properties.get('style', {})
291 7
        css_classes = properties.pop('css_classes', []) + ['markdown']
292 7
        html = markdown.markdown(data, extensions=self.extensions,
293
                                 output_format='html5')
294 7
        return dict(properties, text=escape(html), css_classes=css_classes)
295

296

297

298 7
class JSON(DivPaneBase):
299

300 7
    depth = param.Integer(default=1, bounds=(-1, None), doc="""
301
        Depth to which the JSON tree will be expanded on initialization.""")
302

303 7
    encoder = param.ClassSelector(class_=json.JSONEncoder, is_instance=False, doc="""
304
        Custom JSONEncoder class used to serialize objects to JSON string.""")
305

306 7
    hover_preview = param.Boolean(default=False, doc="""
307
        Whether to display a hover preview for collapsed nodes.""")
308

309 7
    margin = param.Parameter(default=(5, 20, 5, 5), doc="""
310
        Allows to create additional space around the component. May
311
        be specified as a two-tuple of the form (vertical, horizontal)
312
        or a four-tuple (top, right, bottom, left).""")
313

314 7
    theme = param.ObjectSelector(default="dark", objects=["light", "dark"], doc="""
315
        Whether the JSON tree view is expanded by default.""")
316

317 7
    priority = None
318

319 7
    _applies_kw = True
320 7
    _bokeh_model = _BkJSON
321 7
    _rename = {"name": None, "object": "text", "encoder": None}
322

323 7
    _rerender_params = ['object', 'depth', 'encoder', 'hover_preview', 'theme']
324

325 7
    @classmethod
326 2
    def applies(cls, obj, **params):
327 7
        if isinstance(obj, (list, dict)):
328 7
            try:
329 7
                json.dumps(obj, cls=params.get('encoder', cls.encoder))
330 7
            except Exception:
331 7
                return False
332
            else:
333 7
                return 0.1
334 7
        elif isinstance(obj, string_types):
335 7
            return 0
336
        else:
337 7
            return None
338

339 7
    def _get_properties(self):
340 7
        properties = super()._get_properties()
341 7
        try:
342 7
            data = json.loads(self.object)
343 7
        except Exception:
344 7
            data = self.object
345 7
        text = json.dumps(data or {}, cls=self.encoder)
346 7
        depth = None if self.depth < 0 else self.depth
347 7
        return dict(text=text, theme=self.theme, depth=depth,
348
                    hover_preview=self.hover_preview, **properties)

Read our documentation on viewing source code .

Loading