diff --git a/panel/io/save.py b/panel/io/save.py index 563c24667c..050f2ed03d 100644 --- a/panel/io/save.py +++ b/panel/io/save.py @@ -7,6 +7,8 @@ from six import string_types +import bokeh + from bokeh.document.document import Document from bokeh.embed import file_html from bokeh.io.export import export_png @@ -23,8 +25,7 @@ # Private API #--------------------------------------------------------------------- - -def save_png(model, filename): +def save_png(model, filename, template=None, template_variables=None): """ Saves a bokeh model to png @@ -34,13 +35,33 @@ def save_png(model, filename): Model to save to png filename: str Filename to save to + template: + template file, as used by bokeh.file_html. If None will use bokeh defaults + template_variables: + template_variables file dict, as used by bokeh.file_html """ from bokeh.io.webdriver import webdriver_control if not state.webdriver: state.webdriver = webdriver_control.create() webdriver = state.webdriver - export_png(model, filename=filename, webdriver=webdriver) + + try: + if template: + def get_layout_html(obj, resources, width, height): + return file_html( + obj, resources, title="", template=template, + template_variables=template_variables, + suppress_callback_warning=True, _always_new=True + ) + old_layout_fn = bokeh.io.export.get_layout_html + bokeh.io.export.get_layout_html = get_layout_html + export_png(model, filename=filename, webdriver=webdriver) + except Exception: + raise + finally: + if template: + bokeh.io.export.get_layout_html = old_layout_fn #--------------------------------------------------------------------- @@ -86,27 +107,38 @@ def save(panel, filename, title=None, resources=None, template=None, Whether to report progress """ from ..pane import PaneBase + from ..template import Template if isinstance(panel, PaneBase) and len(panel.layout) > 1: panel = panel.layout as_png = isinstance(filename, string_types) and filename.endswith('png') - doc = Document() + if isinstance(panel, Document): + doc = panel + else: + doc = Document() + comm = Comm() with config.set(embed=embed): - model = panel.get_root(doc, comm) - if embed: - embed_state( - panel, model, doc, max_states, max_opts, embed_json, - json_prefix, save_path, load_path, progress - ) + if isinstance(panel, Document): + model = panel + elif isinstance(panel, Template): + panel._init_doc(doc, title=title) + model = doc else: - add_to_doc(model, doc, True) + model = panel.get_root(doc, comm) + if embed: + embed_state( + panel, model, doc, max_states, max_opts, embed_json, + json_prefix, save_path, load_path, progress + ) + else: + add_to_doc(model, doc, True) if as_png: - save_png(model, filename=filename) - return + return save_png(model, filename=filename, template=template, + template_variables=template_variables) elif isinstance(filename, string_types) and not filename.endswith('.html'): filename = filename + '.html' diff --git a/panel/template.py b/panel/template.py index b4c76d8f4a..603103f314 100644 --- a/panel/template.py +++ b/panel/template.py @@ -20,6 +20,7 @@ from .config import config, panel_extension from .io.model import add_to_doc from .io.notebook import render_template +from .io.save import save from .io.state import state from .layout import Column from .models.comm_manager import CommManager @@ -67,7 +68,10 @@ class Template(param.Parameterized, ServableMixin): def __init__(self, template=None, items=None, nb_template=None, **params): super(Template, self).__init__(**params) if isinstance(template, string_types): + self._code = template template = _Template(template) + else: + self._code = None self.template = template if isinstance(nb_template, string_types): nb_template = _Template(nb_template) @@ -244,3 +248,58 @@ def server_doc(self, doc=None, title=None): The Bokeh document the panel was attached to """ return self._init_doc(doc, title=title) + + def save(self, filename, title=None, resources=None, embed=False, + max_states=1000, max_opts=3, embed_json=False, + json_prefix='', save_path='./', load_path=None): + """ + Saves Panel objects to file. + + Arguments + --------- + filename: string or file-like object + Filename to save the plot to + title: string + Optional title for the plot + resources: bokeh resources + One of the valid bokeh.resources (e.g. CDN or INLINE) + embed: bool + Whether the state space should be embedded in the saved file. + max_states: int + The maximum number of states to embed + max_opts: int + The maximum number of states for a single widget + embed_json: boolean (default=True) + Whether to export the data to json files + json_prefix: str (default='') + Prefix for the auto-generated json directory + save_path: str (default='./') + The path to save json files to + load_path: str (default=None) + The path or URL the json files will be loaded from. + """ + if embed: + raise ValueError("Embedding is not yet supported on Template.") + return save(self, filename, title, resources, self.template, + self._render_variables, embed, max_states, max_opts, + embed_json, json_prefix, save_path, load_path) + + def select(self, selector=None): + """ + Iterates over the Template and any potential children in the + applying the Selector. + + Arguments + --------- + selector: type or callable or None + The selector allows selecting a subset of Viewables by + declaring a type or callable function to filter by. + + Returns + ------- + viewables: list(Viewable) + """ + objects = [] + for obj, _ in self._render_items.values(): + objects += obj.select(selector) + return objects