Skip to content

Commit

Permalink
Use Panel to generate HTML output (#3851)
Browse files Browse the repository at this point in the history
* Use the Panel HoloViews model for all HTML format requests
(not only for the hv objects with widgets)

* Don't call `_figure_data` with 'html' format, handle these through Panel

* Remove fmt='html' logic from _figure_data methods.

`as_script` arg still supported to return static images as HTML fragments,
but since there is no associated JavaScript in this case the method no longer
returns a tuple when `as_script=True`.

* Initialize appropriate panel extensions in load_nb methods

Remove unneeded notebook initialization code

* Remove unneeded HTML/JS code and templates from Bokeh and Plotly renderers

* Fix plotly save to SVG encoding
  • Loading branch information
jonmmease authored and philippjfr committed Aug 5, 2019
1 parent 618a649 commit 7b7711f
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 172 deletions.
38 changes: 8 additions & 30 deletions holoviews/plotting/bokeh/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from bokeh.protocol import Protocol
from bokeh.resources import CDN, INLINE
from bokeh.themes.theme import Theme

import panel as pn
from panel.pane import HoloViews, Viewable

from ...core import Store, HoloMap
Expand All @@ -26,13 +28,6 @@
from .util import compute_plot_size, silence_warnings


NOTEBOOK_DIV = """
{plot_div}
<script type="text/javascript">
{plot_script}
</script>
"""

default_theme = Theme(json={
'attrs': {
'Title': {'text_color': 'black', 'text_font_size': '12pt'}
Expand Down Expand Up @@ -97,10 +92,6 @@ def __call__(self, obj, fmt=None, doc=None):
elif fmt == 'png':
png = self._figure_data(plot, fmt=fmt, doc=doc)
return png, info
elif fmt == 'html':
html = self._figure_data(plot, doc=doc)
html = "<div style='display: table; margin: 0 auto;'>%s</div>" % html
return self._apply_post_render_hooks(html, obj, fmt), info
elif fmt == 'json':
return self.diff(plot), info

Expand Down Expand Up @@ -165,7 +156,7 @@ def components(self, obj, fmt=None, comm=True, **kwargs):
return super(BokehRenderer, self).components(obj, fmt, comm, **kwargs)


def _figure_data(self, plot, fmt='html', doc=None, as_script=False, **kwargs):
def _figure_data(self, plot, fmt, doc=None, as_script=False, **kwargs):
"""
Given a plot instance, an output format and an optional bokeh
document, return the corresponding data. If as_script is True,
Expand Down Expand Up @@ -199,23 +190,14 @@ def _figure_data(self, plot, fmt='html', doc=None, as_script=False, **kwargs):
(mime_type, tag) = MIME_TYPES[fmt], HTML_TAGS[fmt]
src = HTML_TAGS['base64'].format(mime_type=mime_type, b64=b64)
div = tag.format(src=src, mime_type=mime_type, css='')
js = ''
else:
try:
with silence_warnings(EMPTY_LAYOUT, MISSING_RENDERERS):
js, div, _ = notebook_content(model)
html = NOTEBOOK_DIV.format(plot_script=js, plot_div=div)
data = encode_utf8(html)
doc.hold()
except:
logger.disabled = False
raise
logger.disabled = False
raise ValueError('Unsupported format: {fmt}'.format(fmt=fmt))

plot.document = doc
if as_script:
return div, js
return data
return div
else:
return data


def diff(self, plot, binary=True):
Expand Down Expand Up @@ -282,8 +264,4 @@ def load_nb(cls, inline=True):
"""
Loads the bokeh notebook resources.
"""
LOAD_MIME_TYPE = bokeh.io.notebook.LOAD_MIME_TYPE
bokeh.io.notebook.LOAD_MIME_TYPE = MIME_TYPES['jlab-hv-load']
load_notebook(hide_banner=True, resources=INLINE if inline else CDN)
bokeh.io.notebook.LOAD_MIME_TYPE = LOAD_MIME_TYPE
bokeh.io.notebook.curstate().output_notebook()
pn.extension()
29 changes: 19 additions & 10 deletions holoviews/plotting/mpl/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
from itertools import chain

import param

import panel as pn
from panel.pane import Viewable

import matplotlib as mpl

from matplotlib import pyplot as plt
Expand Down Expand Up @@ -95,16 +99,19 @@ def __call__(self, obj, fmt='auto'):
Render the supplied HoloViews component or MPLPlot instance
using matplotlib.
"""
plot, fmt = self._validate(obj, fmt)
if plot is None: return

with mpl.rc_context(rc=plot.fig_rcparams):
data = self._figure_data(plot, fmt, **({'dpi':self.dpi} if self.dpi else {}))
plot, fmt = self._validate(obj, fmt)
info = {'file-ext': fmt, 'mime_type': MIME_TYPES[fmt]}

data = self._apply_post_render_hooks(data, obj, fmt)
return data, {'file-ext':fmt,
'mime_type':MIME_TYPES[fmt]}
if plot is None:
return
elif isinstance(plot, Viewable):
return plot, info
else:
with mpl.rc_context(rc=plot.fig_rcparams):
data = self._figure_data(plot, fmt, **({'dpi':self.dpi} if self.dpi else {}))

data = self._apply_post_render_hooks(data, obj, fmt)
return data, info

def show(self, obj):
"""
Expand Down Expand Up @@ -173,7 +180,7 @@ def diff(self, plot):
return self.html(plot, figure_format)


def _figure_data(self, plot, fmt='png', bbox_inches='tight', as_script=False, **kwargs):
def _figure_data(self, plot, fmt, bbox_inches='tight', as_script=False, **kwargs):
"""
Render matplotlib figure object and return the corresponding
data. If as_script is True, the content will be split in an
Expand Down Expand Up @@ -219,7 +226,7 @@ def _figure_data(self, plot, fmt='png', bbox_inches='tight', as_script=False, **
(mime_type, tag) = MIME_TYPES[fmt], HTML_TAGS[fmt]
src = HTML_TAGS['base64'].format(mime_type=mime_type, b64=b64)
html = tag.format(src=src, mime_type=mime_type, css='')
return html, ''
return html
if fmt == 'svg':
data = data.decode('utf-8')
return data
Expand Down Expand Up @@ -293,3 +300,5 @@ def load_nb(cls, inline=True):
backend = plt.get_backend()
if backend not in ['agg', 'module://ipykernel.pylab.backend_inline']:
plt.switch_backend('agg')

pn.extension()
46 changes: 0 additions & 46 deletions holoviews/plotting/plotly/plotlywidgets.js

This file was deleted.

86 changes: 16 additions & 70 deletions holoviews/plotting/plotly/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,14 @@
import param
with param.logging_level('CRITICAL'):
from plotly import utils
from plotly.offline import get_plotlyjs, init_notebook_mode
import plotly.graph_objs as go

from ..renderer import Renderer, MIME_TYPES, HTML_TAGS
from ...core.options import Store
from ...core import HoloMap


plotly_msg_handler = """
/* Backend specific body of the msg_handler, updates displayed frame */
var plot = $('#{plot_id}')[0];
var data = JSON.parse(msg);
$.each(data.data, function(i, obj) {{
$.each(Object.keys(obj), function(j, key) {{
plot.data[i][key] = obj[key];
}});
}});
var plotly = window._Plotly || window.Plotly;
plotly.relayout(plot, data.layout);
plotly.redraw(plot);
"""

import panel as pn
from panel.pane import Viewable

class PlotlyRenderer(Renderer):

Expand All @@ -42,17 +28,16 @@ class PlotlyRenderer(Renderer):

widgets = ['scrubber', 'widgets']

backend_dependencies = {'js': (get_plotlyjs(),)}

comm_msg_handler = plotly_msg_handler

_loaded = False

def __call__(self, obj, fmt='html', divuuid=None):
plot, fmt = self._validate(obj, fmt)
mime_types = {'file-ext':fmt, 'mime_type': MIME_TYPES[fmt]}

if fmt in ('html', 'png', 'svg'):
if isinstance(plot, Viewable):
# fmt == 'html'
return plot, mime_types
elif fmt in ('png', 'svg'):
return self._figure_data(plot, fmt, divuuid=divuuid), mime_types
elif fmt == 'json':
return self.diff(plot), mime_types
Expand All @@ -70,64 +55,28 @@ def diff(self, plot, serialize=True):
return diff


def _figure_data(self, plot, fmt=None, divuuid=None, comm=True, as_script=False, width=800, height=600):
def _figure_data(self, plot, fmt, as_script=False, **kwargs):
# Wrapping plot.state in go.Figure here performs validation
# and applies any default theme.
figure = go.Figure(plot.state)

if fmt in ('png', 'svg'):
import plotly.io as pio
data = pio.to_image(figure, fmt)

if fmt == 'svg':
data = data.decode('utf-8')

if as_script:
b64 = base64.b64encode(data).decode("utf-8")
(mime_type, tag) = MIME_TYPES[fmt], HTML_TAGS[fmt]
src = HTML_TAGS['base64'].format(mime_type=mime_type, b64=b64)
div = tag.format(src=src, mime_type=mime_type, css='')
js = ''
return div, js
return data

if divuuid is None:
divuuid = plot.id

jdata = json.dumps(figure.data, cls=utils.PlotlyJSONEncoder)
jlayout = json.dumps(figure.layout, cls=utils.PlotlyJSONEncoder)

config = {}
config['showLink'] = False
jconfig = json.dumps(config)

if as_script:
header = 'window.PLOTLYENV=window.PLOTLYENV || {};'
return div
else:
return data
else:
header = ('<script type="text/javascript">'
'window.PLOTLYENV=window.PLOTLYENV || {};'
'</script>')

script = '\n'.join([
'var plotly = window._Plotly || window.Plotly;'
'plotly.plot("{id}", {data}, {layout}, {config}).then(function() {{',
' var elem = document.getElementById("{id}.loading"); elem.parentNode.removeChild(elem);',
'}})']).format(id=divuuid,
data=jdata,
layout=jlayout,
config=jconfig)

html = ('<div id="{id}.loading" style="color: rgb(50,50,50);">'
'Drawing...</div>'
'<div id="{id}" style="height: {height}; width: {width};" '
'class="plotly-graph-div">'
'</div>'.format(id=divuuid, height=height, width=width))
if as_script:
return html, header + script

content = (
'{html}'
'<script type="text/javascript">'
' {script}'
'</script>'
).format(html=html, script=script)
return '\n'.join([header, content])
raise ValueError("Unsupported format: {fmt}".format(fmt=fmt))


@classmethod
Expand All @@ -146,8 +95,5 @@ def load_nb(cls, inline=True):
"""
Loads the plotly notebook resources.
"""
from IPython.display import publish_display_data
cls._loaded = True
init_notebook_mode(connected=not inline)
publish_display_data(data={MIME_TYPES['jlab-hv-load']:
get_plotlyjs()})
pn.extension("plotly")
20 changes: 4 additions & 16 deletions holoviews/plotting/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ def _validate(self, obj, fmt, **kwargs):
if fmt in self.widgets:
plot = self.get_widget(obj, fmt, display_options={'fps': self.fps})
fmt = 'html'
elif fmt == 'html':
plot, fmt = HoloViews(obj), 'html'
else:
plot = self.get_plot(obj, renderer=self, **kwargs)

Expand Down Expand Up @@ -320,24 +322,10 @@ def components(self, obj, fmt=None, comm=True, **kwargs):
with config.set(embed=not bool(plot.object.traverse(DynamicMap))):
return plot.layout._repr_mimebundle_()
else:
html, js = self._figure_data(plot, fmt, as_script=True, **kwargs)
plot_id = plot.id
if comm and plot.comm is not None and self.comm_msg_handler:
msg_handler = self.comm_msg_handler.format(plot_id=plot_id)
html = plot.comm.html_template.format(init_frame=html,
plot_id=plot_id)
comm_js = plot.comm.js_template.format(msg_handler=msg_handler,
comm_id=plot.comm.id,
plot_id=plot_id)
js = '\n'.join([js, comm_js])
html = "<div id='%s' style='display: table; margin: 0 auto;'>%s</div>" % (plot_id, html)
html = self._figure_data(plot, fmt, as_script=True, **kwargs)

data['text/html'] = html
if js:
data[MIME_TYPES['js']] = js
data[MIME_TYPES['jlab-hv-exec']] = ''
metadata['id'] = plot_id
self._plots[plot_id] = plot

return (data, {MIME_TYPES['jlab-hv-exec']: metadata})


Expand Down

0 comments on commit 7b7711f

Please sign in to comment.