fixed issue 1581
Handle condition where Param.parameters was set
Keep track whether parameters was explicitly set
Co-authored-by: Marc Skov Madsen <masma@orsted.dk> Co-authored-by: Philipp Rudiger <prudiger@anaconda.com>
1 |
"""
|
|
2 |
Pane class which render various markup languages including HTML,
|
|
3 |
Markdown, and also regular strings.
|
|
4 |
"""
|
|
5 | 2 |
from __future__ import absolute_import, division, unicode_literals |
6 |
|
|
7 | 2 |
import json |
8 | 2 |
import textwrap |
9 |
|
|
10 | 2 |
from six import string_types |
11 |
|
|
12 | 2 |
import param |
13 |
|
|
14 | 2 |
from ..models import HTML as _BkHTML, JSON as _BkJSON |
15 | 2 |
from ..util import escape |
16 | 2 |
from ..viewable import Layoutable |
17 | 2 |
from .base import PaneBase |
18 |
|
|
19 |
|
|
20 | 2 |
class DivPaneBase(PaneBase): |
21 |
"""
|
|
22 |
Baseclass for Panes which render HTML inside a Bokeh Div.
|
|
23 |
See the documentation for Bokeh Div for more detail about
|
|
24 |
the supported options like style and sizing_mode.
|
|
25 |
"""
|
|
26 |
|
|
27 | 2 |
style = param.Dict(default=None, doc=""" |
28 |
Dictionary of CSS property:value pairs to apply to this Div.""") |
|
29 |
|
|
30 | 2 |
_bokeh_model = _BkHTML |
31 |
|
|
32 | 2 |
_rename = {'object': 'text'} |
33 |
|
|
34 | 2 |
_updates = True |
35 |
|
|
36 | 2 |
__abstract = True |
37 |
|
|
38 | 2 |
def _get_properties(self): |
39 | 2 |
return {p : getattr(self, p) for p in list(Layoutable.param) + ['style'] |
40 |
if getattr(self, p) is not None} |
|
41 |
|
|
42 | 2 |
def _get_model(self, doc, root=None, parent=None, comm=None): |
43 | 2 |
model = self._bokeh_model(**self._get_properties()) |
44 | 2 |
if root is None: |
45 | 2 |
root = model |
46 | 2 |
self._models[root.ref['id']] = (model, parent) |
47 | 2 |
return model |
48 |
|
|
49 | 2 |
def _update(self, ref=None, model=None): |
50 | 2 |
model.update(**self._get_properties()) |
51 |
|
|
52 |
|
|
53 | 2 |
class HTML(DivPaneBase): |
54 |
"""
|
|
55 |
HTML panes wrap HTML text in a Panel HTML model. The
|
|
56 |
provided object can either be a text string, or an object that
|
|
57 |
has a `_repr_html_` method that can be called to get the HTML
|
|
58 |
text string. The height and width can optionally be specified, to
|
|
59 |
allow room for whatever is being wrapped.
|
|
60 |
"""
|
|
61 |
|
|
62 |
# Priority is dependent on the data type
|
|
63 | 2 |
priority = None |
64 |
|
|
65 | 2 |
@classmethod
|
66 |
def applies(cls, obj): |
|
67 | 2 |
module, name = getattr(obj, '__module__', ''), type(obj).__name__ |
68 | 2 |
if ((any(m in module for m in ('pandas', 'dask')) and |
69 |
name in ('DataFrame', 'Series')) or hasattr(obj, '_repr_html_')): |
|
70 | 2 |
return 0.2 |
71 | 2 |
elif isinstance(obj, string_types): |
72 | 2 |
return None |
73 |
else: |
|
74 | 2 |
return False |
75 |
|
|
76 | 2 |
def _get_properties(self): |
77 | 2 |
properties = super(HTML, self)._get_properties() |
78 | 2 |
text = '' if self.object is None else self.object |
79 | 2 |
if hasattr(text, '_repr_html_'): |
80 |
text = text._repr_html_() |
|
81 | 2 |
return dict(properties, text=escape(text)) |
82 |
|
|
83 |
|
|
84 | 2 |
class DataFrame(HTML): |
85 |
"""
|
|
86 |
DataFrame renders pandas, dask and streamz DataFrame types using
|
|
87 |
their custom HTML repr. In the case of a streamz DataFrame the
|
|
88 |
rendered data will update periodically.
|
|
89 |
|
|
90 |
"""
|
|
91 |
|
|
92 | 2 |
bold_rows = param.Boolean(default=True, doc=""" |
93 |
Make the row labels bold in the output.""") |
|
94 |
|
|
95 | 2 |
border = param.Integer(default=0, doc=""" |
96 |
A ``border=border`` attribute is included in the opening
|
|
97 |
`<table>` tag.""") |
|
98 |
|
|
99 | 2 |
classes = param.List(default=['panel-df'], doc=""" |
100 |
CSS class(es) to apply to the resulting html table.""") |
|
101 |
|
|
102 | 2 |
col_space = param.ClassSelector(default=None, class_=(str, int), doc=""" |
103 |
The minimum width of each column in CSS length units. An int
|
|
104 |
is assumed to be px units.""") |
|
105 |
|
|
106 | 2 |
decimal = param.String(default='.', doc=""" |
107 |
Character recognized as decimal separator, e.g. ',' in Europe.""") |
|
108 |
|
|
109 | 2 |
float_format = param.Callable(default=None, doc=""" |
110 |
Formatter function to apply to columns' elements if they are
|
|
111 |
floats. The result of this function must be a unicode string.""") |
|
112 |
|
|
113 | 2 |
formatters = param.ClassSelector(default=None, class_=(dict, list), doc=""" |
114 |
Formatter functions to apply to columns' elements by position
|
|
115 |
or name. The result of each function must be a unicode string.""") |
|
116 |
|
|
117 | 2 |
header = param.Boolean(default=True, doc=""" |
118 |
Whether to print column labels.""") |
|
119 |
|
|
120 | 2 |
index = param.Boolean(default=True, doc=""" |
121 |
Whether to print index (row) labels.""") |
|
122 |
|
|
123 | 2 |
index_names = param.Boolean(default=True, doc=""" |
124 |
Prints the names of the indexes.""") |
|
125 |
|
|
126 | 2 |
justify = param.ObjectSelector(default=None, allow_None=True, objects=[ |
127 |
'left', 'right', 'center', 'justify', 'justify-all', 'start', |
|
128 |
'end', 'inherit', 'match-parent', 'initial', 'unset'], doc=""" |
|
129 |
How to justify the column labels.""") |
|
130 |
|
|
131 | 2 |
max_rows = param.Integer(default=None, doc=""" |
132 |
Maximum number of rows to display.""") |
|
133 |
|
|
134 | 2 |
max_cols = param.Integer(default=None, doc=""" |
135 |
Maximum number of columns to display.""") |
|
136 |
|
|
137 | 2 |
na_rep = param.String(default='NaN', doc=""" |
138 |
String representation of NAN to use.""") |
|
139 |
|
|
140 | 2 |
render_links = param.Boolean(default=False, doc=""" |
141 |
Convert URLs to HTML links.""") |
|
142 |
|
|
143 | 2 |
show_dimensions = param.Boolean(default=False, doc=""" |
144 |
Display DataFrame dimensions (number of rows by number of
|
|
145 |
columns).""") |
|
146 |
|
|
147 | 2 |
sparsify = param.Boolean(default=True, doc=""" |
148 |
Set to False for a DataFrame with a hierarchical index to
|
|
149 |
print every multi-index key at each row.""") |
|
150 |
|
|
151 | 2 |
_object = param.Parameter(default=None, doc="""Hidden parameter.""") |
152 |
|
|
153 | 2 |
_dask_params = ['max_rows'] |
154 |
|
|
155 | 2 |
_rerender_params = [ |
156 |
'object', '_object', 'bold_rows', 'border', 'classes', |
|
157 |
'col_space', 'decimal', 'float_format', 'formatters', |
|
158 |
'header', 'index', 'index_names', 'justify', 'max_rows', |
|
159 |
'max_cols', 'na_rep', 'render_links', 'show_dimensions', |
|
160 |
'sparsify', 'sizing_mode' |
|
161 |
]
|
|
162 |
|
|
163 | 2 |
def __init__(self, object=None, **params): |
164 | 2 |
super(DataFrame, self).__init__(object, **params) |
165 | 2 |
self._stream = None |
166 | 2 |
self._setup_stream() |
167 |
|
|
168 | 2 |
@classmethod
|
169 |
def applies(cls, obj): |
|
170 | 2 |
module = getattr(obj, '__module__', '') |
171 | 2 |
name = type(obj).__name__ |
172 | 2 |
if (any(m in module for m in ('pandas', 'dask', 'streamz')) and |
173 |
name in ('DataFrame', 'Series', 'Random', 'DataFrames', 'Seriess')): |
|
174 | 2 |
return 0.3 |
175 |
else: |
|
176 | 2 |
return False |
177 |
|
|
178 | 2 |
def _set_object(self, object): |
179 |
self._object = object |
|
180 |
|
|
181 | 2 |
@param.depends('object', watch=True) |
182 |
def _setup_stream(self): |
|
183 | 2 |
if not self._models or not hasattr(self.object, 'stream'): |
184 | 2 |
return
|
185 | 2 |
elif self._stream: |
186 | 2 |
self._stream.destroy() |
187 | 2 |
self._stream = None |
188 | 2 |
self._stream = self.object.stream.latest().rate_limit(0.5).gather() |
189 | 2 |
self._stream.sink(self._set_object) |
190 |
|
|
191 | 2 |
def _get_model(self, doc, root=None, parent=None, comm=None): |
192 | 2 |
model = super(DataFrame, self)._get_model(doc, root, parent, comm) |
193 | 2 |
self._setup_stream() |
194 | 2 |
return model |
195 |
|
|
196 | 2 |
def _cleanup(self, model): |
197 | 2 |
super(DataFrame, self)._cleanup(model) |
198 | 2 |
if not self._models and self._stream: |
199 | 2 |
self._stream.destroy() |
200 | 2 |
self._stream = None |
201 |
|
|
202 | 2 |
def _get_properties(self): |
203 | 2 |
properties = DivPaneBase._get_properties(self) |
204 | 2 |
if self._stream: |
205 | 2 |
df = self._object |
206 |
else: |
|
207 | 2 |
df = self.object |
208 | 2 |
if hasattr(df, 'to_frame'): |
209 | 2 |
df = df.to_frame() |
210 |
|
|
211 | 2 |
module = getattr(df, '__module__', '') |
212 | 2 |
if hasattr(df, 'to_html'): |
213 | 2 |
if 'dask' in module: |
214 |
html = df.to_html(max_rows=self.max_rows).replace('border="1"', '') |
|
215 |
else: |
|
216 | 2 |
kwargs = {p: getattr(self, p) for p in self._rerender_params |
217 |
if p not in DivPaneBase.param and p != '_object'} |
|
218 | 2 |
html = df.to_html(**kwargs) |
219 |
else: |
|
220 | 2 |
html = '' |
221 | 2 |
return dict(properties, text=escape(html)) |
222 |
|
|
223 |
|
|
224 | 2 |
class Str(DivPaneBase): |
225 |
"""
|
|
226 |
A Str pane renders any object for which `str()` can be called,
|
|
227 |
escaping any HTML markup and then wrapping the resulting string in
|
|
228 |
a bokeh Div model. Set to a low priority because generally one
|
|
229 |
will want a better representation, but allows arbitrary objects to
|
|
230 |
be used as a Pane (numbers, arrays, objects, etc.).
|
|
231 |
"""
|
|
232 |
|
|
233 | 2 |
priority = 0 |
234 |
|
|
235 | 2 |
_target_transforms = {'object': """JSON.stringify(value).replace(/,/g, ", ").replace(/:/g, ": ")"""} |
236 |
|
|
237 | 2 |
_bokeh_model = _BkHTML |
238 |
|
|
239 | 2 |
@classmethod
|
240 |
def applies(cls, obj): |
|
241 | 2 |
return True |
242 |
|
|
243 | 2 |
def _get_properties(self): |
244 | 2 |
properties = super(Str, self)._get_properties() |
245 | 2 |
if self.object is None: |
246 | 2 |
text = '' |
247 |
else: |
|
248 | 2 |
text = '<pre>'+str(self.object)+'</pre>' |
249 | 2 |
return dict(properties, text=escape(text)) |
250 |
|
|
251 |
|
|
252 | 2 |
class Markdown(DivPaneBase): |
253 |
"""
|
|
254 |
A Markdown pane renders the markdown markup language to HTML and
|
|
255 |
displays it inside a bokeh Div model. It has no explicit
|
|
256 |
priority since it cannot be easily be distinguished from a
|
|
257 |
standard string, therefore it has to be invoked explicitly.
|
|
258 |
"""
|
|
259 |
|
|
260 | 2 |
dedent = param.Boolean(default=True, doc=""" |
261 |
Whether to dedent common whitespace across all lines.""") |
|
262 |
|
|
263 | 2 |
extensions = param.List(default=[ |
264 |
"extra", "smarty", "codehilite"], doc=""" |
|
265 |
Markdown extension to apply when transforming markup.""") |
|
266 |
|
|
267 |
# Priority depends on the data type
|
|
268 | 2 |
priority = None |
269 |
|
|
270 | 2 |
_target_transforms = {'object': None} |
271 |
|
|
272 | 2 |
_rerender_params = ['object', 'dedent', 'extensions'] |
273 |
|
|
274 | 2 |
@classmethod
|
275 |
def applies(cls, obj): |
|
276 | 2 |
if hasattr(obj, '_repr_markdown_'): |
277 |
return 0.3 |
|
278 | 2 |
elif isinstance(obj, string_types): |
279 | 2 |
return 0.1 |
280 |
else: |
|
281 | 2 |
return False |
282 |
|
|
283 | 2 |
def _get_properties(self): |
284 | 2 |
import markdown |
285 | 2 |
data = self.object |
286 | 2 |
if data is None: |
287 | 2 |
data = '' |
288 | 2 |
elif not isinstance(data, string_types): |
289 |
data = data._repr_markdown_() |
|
290 | 2 |
if self.dedent: |
291 | 2 |
data = textwrap.dedent(data) |
292 | 2 |
properties = super(Markdown, self)._get_properties() |
293 | 2 |
properties['style'] = properties.get('style', {}) |
294 | 2 |
css_classes = properties.pop('css_classes', []) + ['markdown'] |
295 | 2 |
html = markdown.markdown(data, extensions=self.extensions, |
296 |
output_format='html5') |
|
297 | 2 |
return dict(properties, text=escape(html), css_classes=css_classes) |
298 |
|
|
299 |
|
|
300 |
|
|
301 | 2 |
class JSON(DivPaneBase): |
302 |
|
|
303 | 2 |
depth = param.Integer(default=1, bounds=(-1, None), doc=""" |
304 |
Depth to which the JSON tree will be expanded on initialization.""") |
|
305 |
|
|
306 | 2 |
encoder = param.ClassSelector(class_=json.JSONEncoder, is_instance=False, doc=""" |
307 |
Custom JSONEncoder class used to serialize objects to JSON string.""") |
|
308 |
|
|
309 | 2 |
hover_preview = param.Boolean(default=False, doc=""" |
310 |
Whether to display a hover preview for collapsed nodes.""") |
|
311 |
|
|
312 | 2 |
margin = param.Parameter(default=(5, 20, 5, 5), doc=""" |
313 |
Allows to create additional space around the component. May
|
|
314 |
be specified as a two-tuple of the form (vertical, horizontal)
|
|
315 |
or a four-tuple (top, right, bottom, left).""") |
|
316 |
|
|
317 | 2 |
theme = param.ObjectSelector(default="dark", objects=["light", "dark"], doc=""" |
318 |
Whether the JSON tree view is expanded by default.""") |
|
319 |
|
|
320 | 2 |
priority = None |
321 |
|
|
322 | 2 |
_applies_kw = True |
323 | 2 |
_bokeh_model = _BkJSON |
324 | 2 |
_rename = {"name": None, "object": "text", "encoder": None} |
325 |
|
|
326 | 2 |
_rerender_params = ['object', 'depth', 'encoder', 'hover_preview', 'theme'] |
327 |
|
|
328 | 2 |
@classmethod
|
329 |
def applies(cls, obj, **params): |
|
330 | 2 |
if isinstance(obj, (list, dict)): |
331 | 2 |
try: |
332 | 2 |
json.dumps(obj, cls=params.get('encoder', cls.encoder)) |
333 | 2 |
except Exception: |
334 | 2 |
return False |
335 |
else: |
|
336 | 2 |
return 0.1 |
337 | 2 |
elif isinstance(obj, string_types): |
338 | 2 |
return 0 |
339 |
else: |
|
340 | 2 |
return None |
341 |
|
|
342 | 2 |
def _get_properties(self): |
343 | 2 |
properties = super(JSON, self)._get_properties() |
344 | 2 |
if isinstance(self.object, string_types): |
345 | 2 |
text = self.object |
346 |
else: |
|
347 | 2 |
text = json.dumps(self.object or {}, cls=self.encoder) |
348 | 2 |
depth = None if self.depth < 0 else self.depth |
349 | 2 |
return dict(text=text, theme=self.theme, depth=depth, |
350 |
hover_preview=self.hover_preview, **properties) |
Read our documentation on viewing source code .