#----------------------------------------------------------------------------- # Copyright (c) 2012 - 2017, Anaconda, Inc. All rights reserved. # # Powered by the Bokeh Development Team. # # The full license is in the file LICENSE.txt, distributed with this software. #----------------------------------------------------------------------------- ''' ''' #----------------------------------------------------------------------------- # Boilerplate #----------------------------------------------------------------------------- from __future__ import absolute_import, division, print_function, unicode_literals import logging log = logging.getLogger(__name__) #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- # Standard library imports from contextlib import contextmanager import re # External imports # Bokeh imports from ..core.templates import AUTOLOAD_JS, AUTOLOAD_TAG, FILE, ROOT_DIV, MACROS from ..document.document import DEFAULT_TITLE, Document from ..model import Model from ..settings import settings from ..util.compiler import bundle_all_models from ..util.string import encode_utf8 from .bundle import bundle_for_objs_and_resources from .util import FromCurdoc from .util import (check_models_or_docs, check_one_model_or_doc, find_existing_docs, html_page_for_render_items, script_for_render_items, standalone_docs_json_and_render_items, wrap_in_onload, wrap_in_script_tag) #----------------------------------------------------------------------------- # Globals and constants #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # General API #----------------------------------------------------------------------------- def autoload_static(model, resources, script_path): ''' Return JavaScript code and a script tag that can be used to embed Bokeh Plots. The data for the plot is stored directly in the returned JavaScript code. Args: model (Model or Document) : resources (Resources) : script_path (str) : Returns: (js, tag) : JavaScript code to be saved at ``script_path`` and a `` Note that in Jupyter Notebooks, it is not possible to use components and show in the same notebook cell. Args: models (Model|list|dict|tuple) : A single Model, a list/tuple of Models, or a dictionary of keys and Models. wrap_script (boolean, optional) : If True, the returned javascript is wrapped in a script tag. (default: True) wrap_plot_info (boolean, optional) : If True, returns ``
`` strings. Otherwise, return dicts that can be used to build your own divs. (default: True) If False, the returned dictionary contains the following information: .. code-block:: python { 'modelid': 'The model ID, used with Document.get_model_by_id', 'elementid': 'The css identifier the BokehJS will look for to target the plot', 'docid': 'Used by Bokeh to find the doc embedded in the returned script', } theme (Theme, optional) : Defaults to the ``Theme`` instance in the current document. Setting this to ``None`` uses the default theme or the theme already specified in the document. Any other value must be an instance of the ``Theme`` class. Returns: UTF-8 encoded *(script, div[s])* or *(raw_script, plot_info[s])* Examples: With default wrapping parameter values: .. code-block:: python components(plot) # => (script, plot_div) components((plot1, plot2)) # => (script, (plot1_div, plot2_div)) components({"Plot 1": plot1, "Plot 2": plot2}) # => (script, {"Plot 1": plot1_div, "Plot 2": plot2_div}) Examples: With wrapping parameters set to ``False``: .. code-block:: python components(plot, wrap_script=False, wrap_plot_info=False) # => (javascript, plot_dict) components((plot1, plot2), wrap_script=False, wrap_plot_info=False) # => (javascript, (plot1_dict, plot2_dict)) components({"Plot 1": plot1, "Plot 2": plot2}, wrap_script=False, wrap_plot_info=False) # => (javascript, {"Plot 1": plot1_dict, "Plot 2": plot2_dict}) ''' # 1) Convert single items and dicts into list was_single_object = isinstance(models, Model) or isinstance(models, Document) # converts single to list models = check_models_or_docs(models, allow_dict=True) # now convert dict to list, saving keys in the same order model_keys = None if isinstance(models, dict): model_keys = models.keys() values = [] # don't just use .values() to ensure we are in the same order as key list for k in model_keys: values.append(models[k]) models = values # 2) Append models to one document. Either pre-existing or new and render with _ModelInDocument(models, apply_theme=theme): (docs_json, [render_item]) = standalone_docs_json_and_render_items(models) script = bundle_all_models() script += script_for_render_items(docs_json, [render_item]) if wrap_script: script = wrap_in_script_tag(script) script = encode_utf8(script) def div_for_root(root): return ROOT_DIV.render(root=root, macros=MACROS) if wrap_plot_info: results = list(div_for_root(root) for root in render_item.roots) else: results = render_item.roots # 3) convert back to the input shape if was_single_object: return script, results[0] elif model_keys is not None: result = {} for (key, value) in zip(model_keys, results): result[key] = value return script, result else: return script, tuple(results) def file_html(models, resources, title=None, template=FILE, template_variables={}, theme=FromCurdoc): ''' Return an HTML document that embeds Bokeh Model or Document objects. The data for the plot is stored directly in the returned HTML, with support for customizing the JS/CSS resources independently and customizing the jinja2 template. Args: models (Model or Document or list) : Bokeh object or objects to render typically a Model or Document resources (Resources or tuple(JSResources or None, CSSResources or None)) : i A resource configuration for Bokeh JS & CSS assets. title (str, optional) : a title for the HTML document ```` tags or None. (default: None) If None, attempt to automatically find the Document title from the given plot objects. template (Template, optional) : HTML document template (default: FILE) A Jinja2 Template, see bokeh.core.templates.FILE for the required template parameters template_variables (dict, optional) : variables to be used in the Jinja2 template. If used, the following variable names will be overwritten: title, bokeh_js, bokeh_css, plot_script, plot_div theme (Theme, optional) : Defaults to the ``Theme`` instance in the current document. Setting this to ``None`` uses the default theme or the theme already specified in the document. Any other value must be an instance of the ``Theme`` class. Returns: UTF-8 encoded HTML ''' models = check_models_or_docs(models) with _ModelInDocument(models, apply_theme=theme) as doc: (docs_json, render_items) = standalone_docs_json_and_render_items(models) title = _title_from_models(models, title) bundle = bundle_for_objs_and_resources([doc], resources) return html_page_for_render_items(bundle, docs_json, render_items, title=title, template=template, template_variables=template_variables) #----------------------------------------------------------------------------- # Dev API #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Private API #----------------------------------------------------------------------------- @contextmanager def _ModelInDocument(models, apply_theme=None): doc = find_existing_docs(models) old_theme = doc.theme if apply_theme is FromCurdoc: from ..io import curdoc; curdoc doc.theme = curdoc().theme elif apply_theme is not None: doc.theme = apply_theme models_to_dedoc = _add_doc_to_models(doc, models) if settings.perform_document_validation(): doc.validate() yield doc for model in models_to_dedoc: doc.remove_root(model, apply_theme) doc.theme = old_theme def _add_doc_to_models(doc, models): models_to_dedoc = [] for model in models: if isinstance(model, Model): if model.document is None: try: doc.add_root(model) models_to_dedoc.append(model) except RuntimeError as e: child = re.search('\((.*)\)', str(e)).group(0) msg = ('Sub-model {0} of the root model {1} is already owned ' 'by another document (Models must be owned by only a ' 'single document). This may indicate a usage ' 'error.'.format(child, model)) raise RuntimeError(msg) return models_to_dedoc def _title_from_models(models, title): # use override title if title is not None: return title # use title from any listed document for p in models: if isinstance(p, Document): return p.title # use title from any model's document for p in models: if p.document is not None: return p.document.title # use default title return DEFAULT_TITLE #----------------------------------------------------------------------------- # Code #-----------------------------------------------------------------------------