/
display_hooks.py
329 lines (263 loc) · 9.94 KB
/
display_hooks.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
"""
Definition and registration of display hooks for the IPython Notebook.
"""
from functools import wraps
from contextlib import contextmanager
import sys, traceback
import IPython
from IPython import get_ipython
from IPython.display import HTML
import holoviews
from ..core.options import (Store, StoreOptions, SkipRendering,
AbbreviatedException)
from ..core import (
ViewableElement, HoloMap, AdjointLayout, NdLayout, GridSpace,
Layout, CompositeOverlay, DynamicMap, Dimensioned, Empty
)
from ..core.traversal import unique_dimkeys
from ..core.io import FileArchive
from ..core.util import mimebundle_to_html
from ..plotting import Plot
from ..plotting.renderer import MIME_TYPES
from ..util.settings import OutputSettings
from .magics import OptsMagic, OutputMagic
# To assist with debugging of display hooks
FULL_TRACEBACK = None
ABBREVIATE_TRACEBACKS = True
#==================#
# Helper functions #
#==================#
def max_frame_warning(max_frames):
sys.stderr.write(
f"Animation longer than the max_frames limit {max_frames};\n"
"skipping rendering to avoid unexpected lengthy computations.\n"
"If desired, the limit can be increased using:\n"
"hv.output(max_frames=<insert number>)"
)
def process_object(obj):
"Hook to process the object currently being displayed."
invalid_options = OptsMagic.process_element(obj)
if invalid_options: return invalid_options
OutputMagic.info(obj)
def render(obj, **kwargs):
info = process_object(obj)
if info:
display(HTML(info))
return
if render_anim is not None:
return render_anim(obj)
backend = Store.current_backend
renderer = Store.renderers[backend]
# Drop back to png if pdf selected, notebook PDF rendering is buggy
if renderer.fig == 'pdf':
renderer = renderer.instance(fig='png')
return renderer.components(obj, **kwargs)
def single_frame_plot(obj):
"""
Returns plot, renderer and format for single frame export.
"""
obj = Layout(obj) if isinstance(obj, AdjointLayout) else obj
backend = Store.current_backend
renderer = Store.renderers[backend]
plot_cls = renderer.plotting_class(obj)
plot = plot_cls(obj, **renderer.plot_options(obj, renderer.size))
fmt = (renderer.param.objects('existing')['fig'].objects[0]
if renderer.fig == 'auto' else renderer.fig)
return plot, renderer, fmt
def first_frame(obj):
"Only display the first frame of an animated plot"
plot, renderer, fmt = single_frame_plot(obj)
plot.update(0)
return {'text/html': renderer.html(plot, fmt)}
def middle_frame(obj):
"Only display the (approximately) middle frame of an animated plot"
plot, renderer, fmt = single_frame_plot(obj)
middle_frame = int(len(plot) / 2)
plot.update(middle_frame)
return {'text/html': renderer.html(plot, fmt)}
def last_frame(obj):
"Only display the last frame of an animated plot"
plot, renderer, fmt = single_frame_plot(obj)
plot.update(len(plot))
return {'text/html': renderer.html(plot, fmt)}
#===============#
# Display hooks #
#===============#
def dynamic_optstate(element, state=None):
# Temporary fix to avoid issues with DynamicMap traversal
DynamicMap._deep_indexable = False
optstate = StoreOptions.state(element,state=state)
DynamicMap._deep_indexable = True
return optstate
@contextmanager
def option_state(element):
optstate = dynamic_optstate(element)
try:
yield
except Exception:
dynamic_optstate(element, state=optstate)
raise
def display_hook(fn):
"""
A decorator to wrap display hooks that return a MIME bundle or None.
Additionally it handles adding output to the notebook archive, saves
files specified with the output magic and handles tracebacks.
"""
@wraps(fn)
def wrapped(element):
global FULL_TRACEBACK
if Store.current_backend is None:
return {}, {}
try:
max_frames = OutputSettings.options['max_frames']
mimebundle = fn(element, max_frames=max_frames)
if mimebundle is None:
return {}, {}
mime_data, mime_metadata = mimebundle
if MIME_TYPES['js'] in mime_data:
mime_data['text/html'] = mimebundle_to_html(mime_data)
del mime_data[MIME_TYPES['js']]
# Only want to add to the archive for one display hook...
disabled_suffixes = ['png_display', 'svg_display']
if not any(fn.__name__.endswith(suffix) for suffix in disabled_suffixes):
if type(holoviews.archive) is not FileArchive and 'text/html' in mime_data:
holoviews.archive.add(element, html=mime_data['text/html'])
filename = OutputSettings.options['filename']
if filename:
Store.renderers[Store.current_backend].save(element, filename)
return mime_data, mime_metadata
except SkipRendering as e:
if e.warn:
sys.stderr.write(str(e))
return {}, {}
except AbbreviatedException as e:
FULL_TRACEBACK = '\n'.join(traceback.format_exception(e.etype,
e.value,
e.traceback))
info = dict(name=e.etype.__name__,
message=str(e.value).replace('\n','<br>'))
msg = '<i> [Call holoviews.ipython.show_traceback() for details]</i>'
return {'text/html': "<b>{name}</b>{msg}<br>{message}".format(msg=msg, **info)}, {}
except Exception:
raise
return wrapped
@display_hook
def element_display(element, max_frames):
info = process_object(element)
if info:
display(HTML(info))
return None
backend = Store.current_backend
if type(element) not in Store.registry[backend]:
return None
return render(element)
@display_hook
def map_display(vmap, max_frames):
if not isinstance(vmap, (HoloMap, DynamicMap)): return None
if len(vmap) == 0 and not isinstance(vmap, DynamicMap):
return None
elif len(vmap) > max_frames:
max_frame_warning(max_frames)
return None
return render(vmap)
@display_hook
def layout_display(layout, max_frames):
if isinstance(layout, AdjointLayout):
layout = Layout(layout).opts(layout.opts.get('plot'))
if not isinstance(layout, (Layout, NdLayout)): return None
nframes = len(unique_dimkeys(layout)[1])
if nframes > max_frames:
max_frame_warning(max_frames)
return None
return render(layout)
@display_hook
def grid_display(grid, max_frames):
if not isinstance(grid, GridSpace): return None
nframes = len(unique_dimkeys(grid)[1])
if nframes > max_frames:
max_frame_warning(max_frames)
return None
return render(grid)
def display(obj, raw_output=False, **kwargs):
"""
Renders any HoloViews object to HTML and displays it
using the IPython display function. If raw is enabled
the raw HTML is returned instead of displaying it directly.
"""
if not Store.loaded_backends() and isinstance(obj, Dimensioned):
raise RuntimeError('To use display on a HoloViews object ensure '
'a backend is loaded using the holoviews '
'extension.')
raw = True
if isinstance(obj, GridSpace):
with option_state(obj):
output = grid_display(obj)
elif isinstance(obj, (CompositeOverlay, ViewableElement)):
with option_state(obj):
output = element_display(obj)
elif isinstance(obj, (Layout, NdLayout, AdjointLayout)):
with option_state(obj):
output = layout_display(obj)
elif isinstance(obj, (HoloMap, DynamicMap)):
with option_state(obj):
output = map_display(obj)
elif isinstance(obj, Plot):
output = render(obj)
elif isinstance(obj, Empty):
output = ({}, {})
else:
output = obj
raw = kwargs.pop('raw', False)
if raw_output:
return output
elif isinstance(output, tuple):
data, metadata = output
else:
data, metadata = output, {}
return IPython.display.display(data, raw=raw, metadata=metadata, **kwargs)
def pprint_display(obj):
if 'html' not in Store.display_formats:
return None
# If pretty printing is off, return None (fallback to next display format)
ip = get_ipython() # # noqa (in IPython namespace)
if not ip.display_formatter.formatters['text/plain'].pprint:
return None
return display(obj, raw_output=True)
def image_display(element, max_frames, fmt):
"""
Used to render elements to an image format (svg or png) if requested
in the display formats.
"""
if fmt not in Store.display_formats:
return None
info = process_object(element)
if info:
display(HTML(info))
return
backend = Store.current_backend
if type(element) not in Store.registry[backend]:
return None
renderer = Store.renderers[backend]
plot = renderer.get_plot(element)
# Current renderer does not support the image format
if fmt not in renderer.param.objects('existing')['fig'].objects:
return None
data, info = renderer(plot, fmt=fmt)
return {info['mime_type']: data}, {}
@display_hook
def png_display(element, max_frames):
"""
Used to render elements to PNG if requested in the display formats.
"""
return image_display(element, max_frames, fmt='png')
@display_hook
def svg_display(element, max_frames):
"""
Used to render elements to SVG if requested in the display formats.
"""
return image_display(element, max_frames, fmt='svg')
# display_video output by default, but may be set to first_frame,
# middle_frame or last_frame (e.g. for testing purposes)
render_anim = None
def plot_display(plot):
return plot.renderer.components(plot)