#----------------------------------------------------------------------------- # Copyright (c) 2012 - 2015, Continuum Analytics, Inc. All rights reserved. # # Powered by the Bokeh Development Team. # # The full license is in the file LICENSE.txt, distributed with this software. #----------------------------------------------------------------------------- ''' Functions for configuring Bokeh output. ''' #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- from __future__ import absolute_import # Stdlib imports import logging logger = logging.getLogger(__name__) import io import itertools import json import os import warnings # Third-party imports # Bokeh imports from .core.state import State from .document import Document from .embed import notebook_div, standalone_html_page_for_models, autoload_server from .models import Component from .models.plots import GridPlot from .models.layouts import HBox, VBox, VBoxForm from .model import _ModelInDocument from .util.notebook import load_notebook, publish_display_data, get_comms from .util.string import decode_utf8 from .util.serialization import make_id import bokeh.util.browser as browserlib # full import needed for test mocking to work from .client import DEFAULT_SESSION_ID, push_session, show_session #----------------------------------------------------------------------------- # Globals and constants #----------------------------------------------------------------------------- _new_param = {'tab': 2, 'window': 1} _state = State() #----------------------------------------------------------------------------- # Local utilities #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Classes and functions #----------------------------------------------------------------------------- class _CommsHandle(object): _json = {} def __init__(self, comms, doc, json): self._cellno = None try: from IPython import get_ipython ip = get_ipython() hm = ip.history_manager p_prompt = list(hm.get_tail(1, include_latest=True))[0][1] self._cellno = p_prompt except Exception as e: logger.debug("Could not get Notebook cell number, reason: %s", e) self._comms = comms self._doc = doc self._json[doc] = json def _repr_html_(self): if self._cellno is not None: return "

<Bokeh Notebook handle for In[%s]>

" % str(self._cellno) else: return "

<Bokeh Notebook handle>

" @property def comms(self): return self._comms @property def doc(self): return self._doc @property def json(self): return self._json[self._doc] def update(self, doc, json): self._doc = doc self._json[doc] = json def output_file(filename, title="Bokeh Plot", autosave=False, mode="cdn", root_dir=None): '''Configure the default output state to generate output saved to a file when :func:`show` is called. Does not change the current Document from curdoc(). File, server, and notebook output may be active at the same time, so this does not clear the effects of output_server() or output_notebook(). Args: filename (str) : a filename for saving the HTML document title (str, optional) : a title for the HTML document (default: "Bokeh Plot") autosave (bool, optional) : whether to automatically save (default: False) If True, then Bokeh plotting APIs may opt to automatically save the file more frequently (e.g., after any plotting command). If False, then the file is only saved upon calling :func:`show` or :func:`save`. mode (str, optional) : how to include BokehJS (default: ``'inline'``) One of: ``'inline'``, ``'cdn'``, ``'relative(-dev)'`` or ``'absolute(-dev)'``. See :class:`bokeh.resources.Resources` for more details. root_dir (str, optional) : root directory to use for 'absolute' resources. (default: None) This value is ignored for other resource types, e.g. ``INLINE`` or ``CDN``. Returns: None .. note:: Generally, this should be called at the beginning of an interactive session or the top of a script. .. warning:: This output file will be overwritten on every save, e.g., each time show() or save() is invoked, or any time a Bokeh plotting API causes a save, if ``autosave`` is True. ''' _state.output_file( filename, title=title, autosave=autosave, mode=mode, root_dir=root_dir ) def output_notebook(resources=None, verbose=False, hide_banner=False): ''' Configure the default output state to generate output in Jupyter/IPython notebook cells when :func:`show` is called. If output_server() has also been called, the notebook cells are loaded from the configured server; otherwise, Bokeh pushes HTML to the notebook directly. Args: resources (Resource, optional) : How and where to load BokehJS from (default: INLINE) verbose (bool, optional) : whether to display detailed BokehJS banner (default: False) hide_banner (bool, optional): whether to hide the Bokeh banner (default: False) Returns: None .. note:: Generally, this should be called at the beginning of an interactive session or the top of a script. ''' load_notebook(resources, verbose, hide_banner) _state.output_notebook() # usually we default session_id to "generate a random one" but # here we default to a hardcoded one. This is to support local # usage e.g. with a notebook. def output_server(session_id=DEFAULT_SESSION_ID, url="default", app_path="/", autopush=False): """ Configure the default output state to push its document to a session on a Bokeh server. Sessions are in-memory and not persisted to disk; in a typical production deployment, you would have a fresh session ID for each browser tab. If different users share the same session ID, it will create security and scalability problems. ``output_server()`` defaults to always using the ``session_id`` ``"default"``, which is useful for running local demos or notebooks. However, if you are creating production sessions, you'll need to set ``session_id`` to None (to generate a fresh ID) or to a session ID generated elsewhere. File, server, and notebook output may be active at the same time, so output_server() does not clear the effects of output_file() or output_notebook(). output_server() changes the behavior of output_notebook(), so the notebook will load output cells from the server rather than receiving them as inline HTML. Args: session_id (str, optional) : Name of session to push on Bokeh server (default: "default") Any existing session with the same name will be overwritten. url (str, optional) : base URL of the Bokeh server (default: "default") If "default" use the default localhost URL. app_path (str, optional) : relative path of the app on the Bokeh server (default: "/") autopush (bool, optional) : whether to automatically push (default: False) If True, then Bokeh plotting APIs may opt to automatically push the document more frequently (e.g., after any plotting command). If False, then the document is only pushed upon calling :func:`show` or :func:`push`. Returns: None .. warning:: Calling this function will replace any existing server-side document in the named session. """ _state.output_server(session_id=session_id, url=url, app_path=app_path, autopush=autopush) def set_curdoc(doc): '''Configure the current document (returned by curdoc()). This is the document we will save or push according to output_file(), output_server(), etc. configuration. Args: doc (Document) : Document we will output. Returns: None .. note:: Generally, this should be called at the beginning of an interactive session or the top of a script. .. warning:: Calling this function will replace any existing document. ''' _state.document = doc def curdoc(): ''' Return the document for the current default state. Returns: doc : the current default document object. ''' return _state.document def curstate(): ''' Return the current State object Returns: state : the current default State object ''' return _state def show(obj, browser=None, new="tab"): ''' Immediately display a plot object. In an IPython/Jupyter notebook, the output is displayed in an output cell. Otherwise, a browser window or tab is autoraised to display the plot object. If both a server session and notebook output have been configured on the default output state then the notebook output will be generated to load the plot from that server session. Args: obj (Component object) : a plot object to display browser (str, optional) : browser to show with (default: None) For systems that support it, the **browser** argument allows specifying which browser to display in, e.g. "safari", "firefox", "opera", "windows-default" (see the ``webbrowser`` module documentation in the standard lib for more details). new (str, optional) : new file output mode (default: "tab") For file-based output, opens or raises the browser window showing the current output file. If **new** is 'tab', then opens a new tab. If **new** is 'window', then opens a new window. Returns: when in a a jupyter notebook (with ``output_notebook`` enabled), returns a handle that can be used by ``push_notebook``, None otherwise. .. note:: The ``browser`` and ``new`` parameters are ignored when showing in an IPython/Jupyter notebook. ''' return _show_with_state(obj, _state, browser, new) def _show_with_state(obj, state, browser, new): controller = browserlib.get_browser_controller(browser=browser) comms_handle = None if state.notebook: comms_handle = _show_notebook_with_state(obj, state) elif state.server_enabled: _show_server_with_state(obj, state, new, controller) if state.file: _show_file_with_state(obj, state, new, controller) return comms_handle def _show_file_with_state(obj, state, new, controller): save(obj, state=state) controller.open("file://" + os.path.abspath(state.file['filename']), new=_new_param[new]) def _show_notebook_with_state(obj, state): if state.server_enabled: push(state=state) snippet = autoload_server(obj, session_id=state.session_id_allowing_none, url=state.url, app_path=state.app_path) publish_display_data({'text/html': snippet}) else: comms_target = make_id() publish_display_data({'text/html': notebook_div(obj, comms_target)}) handle = _CommsHandle(get_comms(comms_target), state.document, state.document.to_json()) state.last_comms_handle = handle return handle def _show_server_with_state(obj, state, new, controller): push(state=state) show_session(session_id=state.session_id_allowing_none, url=state.url, app_path=state.app_path, new=new, controller=controller) def save(obj, filename=None, resources=None, title=None, state=None, validate=True): ''' Save an HTML file with the data for the current document. Will fall back to the default output state (or an explicitly provided :class:`State` object) for ``filename``, ``resources``, or ``title`` if they are not provided. Args: obj (Document or model object) : a plot object to save filename (str, optional) : filename to save document under (default: None) If None, use the default state configuration, otherwise raise a ``RuntimeError``. resources (Resources, optional) : A Resources config to use (default: None) If None, use the default state configuration, if there is one. otherwise use ``resources.INLINE``. title (str, optional) : a title for the HTML document (default: None) If None, use the default state title value, if there is one. Otherwise, use "Bokeh Plot" validate (bool, optional) : True to check integrity of the models Returns: None Raises: RuntimeError ''' if state is None: state = _state filename, resources, title = _get_save_args(state, filename, resources, title) _save_helper(obj, filename, resources, title, validate) def _get_save_args(state, filename, resources, title): if filename is None and state.file: filename = state.file['filename'] if resources is None and state.file: resources = state.file['resources'] if title is None and state.file: title = state.file['title'] if filename is None: raise RuntimeError("save() called but no filename was supplied and output_file(...) was never called, nothing saved") if resources is None: warnings.warn("save() called but no resources were supplied and output_file(...) was never called, defaulting to resources.CDN") from .resources import CDN resources = CDN if title is None: warnings.warn("save() called but no title was supplied and output_file(...) was never called, using default title 'Bokeh Plot'") title = "Bokeh Plot" return filename, resources, title def _save_helper(obj, filename, resources, title, validate): with _ModelInDocument(obj): if isinstance(obj, Component): doc = obj.document elif isinstance(obj, Document): doc = obj else: raise RuntimeError("Unable to save object of type '%s'" % type(obj)) if validate: doc.validate() html = standalone_html_page_for_models(obj, resources, title) with io.open(filename, "w", encoding="utf-8") as f: f.write(decode_utf8(html)) # this function exists mostly to be mocked in tests def _push_to_server(session_id, url, app_path, document, io_loop): session = push_session(document, session_id=session_id, url=url, app_path=app_path, io_loop=io_loop) session.close() session.loop_until_closed() def push(session_id=None, url=None, app_path=None, document=None, state=None, io_loop=None, validate=True): ''' Update the server with the data for the current document. Will fall back to the default output state (or an explicitly provided :class:`State` object) for ``session_id``, ``url``, ``app_path``, or ``document`` if they are not provided. Args: session_id (str, optional) : a Bokeh server session ID to push objects to url (str, optional) : a Bokeh server URL to push objects to app_path (str, optional) : Relative application path to push objects to document (Document, optional) : A :class:`bokeh.document.Document` to use state (State, optional) : A state to use for any output_server() configuration of session or url io_loop (tornado.ioloop.IOLoop, optional) : Tornado IOLoop to use for connecting to server validate (bool, optional) : True to check integrity of the document we are pushing Returns: None ''' if state is None: state = _state if not session_id: session_id = state.session_id_allowing_none if not url: url = state.url if not app_path: app_path = state.app_path # State is supposed to ensure these are set assert session_id is not None assert url is not None assert app_path is not None if not document: document = state.document if not document: warnings.warn("No document to push") if validate: document.validate() _push_to_server(session_id=session_id, url=url, app_path=app_path, document=document, io_loop=io_loop) def push_notebook(document=None, state=None, handle=None): ''' Update the last-shown plot in a Jupyter notebook with the new data or property values. Args: document (Document, optional) : A :class:`~bokeh.document.Document` to push from. If None, uses ``curdoc()``. state (State, optional) : A Bokeh State object Returns: None Examples: Typical usage is typically similar to this: .. code-block:: python from bokeh.io import push_notebook # code to create a plot show(plot) plot.title = "New Title" # This will cause the title to update push_notebook() ''' if state is None: state = _state if state.server_enabled: raise RuntimeError("output_server() has been called, use push() to push to server") if not document: document = state.document if not document: warnings.warn("No document to push") return if handle is None: handle = state.last_comms_handle if not handle: warnings.warn("Cannot find a last shown plot to update. Call output_notebook() and show() before push_notebook()") return to_json = document.to_json() if handle.doc is not document: msg = dict(doc=to_json) else: msg = Document._compute_patch_between_json(handle.json, to_json) handle.comms.send(json.dumps(msg)) handle.update(document, to_json) def reset_output(state=None): ''' Clear the default state of all output modes. Returns: None ''' _state.reset() def _remove_roots(subplots): doc = _state.document for sub in subplots: if sub in doc.roots: doc.remove_root(sub) def _push_or_save(obj): if _state.server_enabled and _state.autopush: push() if _state.file and _state.autosave: save(obj) def gridplot(plot_arrangement, **kwargs): ''' Generate a plot that arranges several subplots into a grid. Args: plot_arrangement (nested list of Plots) : plots to arrange in a grid **kwargs: additional attributes to pass in to GridPlot() constructor .. note:: ``plot_arrangement`` can be nested, e.g [[p1, p2], [p3, p4]] Returns: grid_plot: a new :class:`GridPlot ` ''' subplots = itertools.chain.from_iterable(plot_arrangement) _remove_roots(subplots) grid = GridPlot(children=plot_arrangement, **kwargs) curdoc().add_root(grid) _push_or_save(grid) return grid def hplot(*children, **kwargs): ''' Generate a layout that arranges several subplots horizontally. ''' _remove_roots(children) layout = HBox(children=list(children), **kwargs) curdoc().add_root(layout) _push_or_save(layout) return layout def vplot(*children, **kwargs): ''' Generate a layout that arranges several subplots vertically. ''' _remove_roots(children) layout = VBox(children=list(children), **kwargs) curdoc().add_root(layout) _push_or_save(layout) return layout def vform(*children, **kwargs): ''' Generate a layout that arranges several subplots vertically. ''' layout = VBoxForm(children=list(children), **kwargs) curdoc().add_root(layout) _push_or_save(layout) return layout