Skip to content

Commit

Permalink
Merge pull request #2800 from bokeh/mattpap/custom_models
Browse files Browse the repository at this point in the history
Implementation of user-defined models
  • Loading branch information
damianavila committed Sep 28, 2015
2 parents 2061725 + a3cbc22 commit caaa3c6
Show file tree
Hide file tree
Showing 74 changed files with 933 additions and 6,517 deletions.
5 changes: 5 additions & 0 deletions bokeh/_templates/plot_js.js
@@ -1,3 +1,8 @@
Bokeh.Collections.register_models({
{% for name, impl in custom_models.items() -%}
"{{ name }}": {{ impl }},
{%- endfor %}
});
var all_models = {{ all_models }};
Bokeh.load_models(all_models);
var plots = {{ plots }};
Expand Down
2 changes: 1 addition & 1 deletion bokeh/charts/utils.py
Expand Up @@ -205,7 +205,7 @@ def show(obj, title='test', filename=False, server=False, notebook=False, **kws)
it in an output cell (IPython notebook).
Args:
obj (Widget/Plot object, optional): it accepts a plot object and just shows it.
obj (Component object, optional): it accepts a plot object and just shows it.
"""
if filename:
Expand Down
3 changes: 3 additions & 0 deletions bokeh/document.py
Expand Up @@ -90,6 +90,9 @@ def context(self, value):
def ref(self):
return self._context.ref

def references(self):
return self.context.references()

def clear(self):
""" Remove all plots from this `Document`
Expand Down
62 changes: 52 additions & 10 deletions bokeh/embed.py
Expand Up @@ -13,6 +13,7 @@

from __future__ import absolute_import

import re
import uuid
from warnings import warn

Expand Down Expand Up @@ -95,12 +96,12 @@ def components(plot_objects, resources=None, wrap_script=True, wrap_plot_info=Tr
components({"Plot 1": plot1, "Plot 2": plot2}, wrap_script=False, wrap_plot_info=False)
# => (javascript, {"Plot 1": plot1_dict, "Plot 2": plot2_dict})
'''
all_models, plots, plot_info, divs = _get_components(plot_objects, resources)
custom_models, all_models, plots, plot_info, divs = _get_components(plot_objects, resources)

if wrap_script:
script = _get_script(all_models, plots)
script = _get_script(custom_models, all_models, plots)
else:
script = _get_js(all_models, plots)
script = _get_js(custom_models, all_models, plots)
script = encode_utf8(script)

if wrap_plot_info:
Expand All @@ -112,14 +113,15 @@ def components(plot_objects, resources=None, wrap_script=True, wrap_plot_info=Tr
def _get_components(plot_objects, resources=None):
plot_objects = _check_components_input(plot_objects, resources)

custom_models = {}
all_models = dict()
plots = []

if isinstance(plot_objects, Sequence):
divs = []
for idx, plot_object in enumerate(plot_objects):
elementid = str(uuid.uuid4())
_append_plot(all_models, plots, plot_object, elementid)
_append_plot(custom_models, all_models, plots, plot_object, elementid)
divs = _append_div(elementid, divs)
if len(divs) == 1:
divs = divs[0]
Expand All @@ -133,10 +135,10 @@ def _get_components(plot_objects, resources=None):
plot_info = {}
for key in plot_objects.keys():
elementid = str(uuid.uuid4())
plot_info[key] = _append_plot(all_models, plots, plot_objects[key], elementid)
plot_info[key] = _append_plot(custom_models, all_models, plots, plot_objects[key], elementid)
divs = _append_div(elementid, divs, key)

return list(all_models.values()), plots, plot_info, divs
return custom_models, list(all_models.values()), plots, plot_info, divs


def _check_components_input(plot_objects, resources=None):
Expand Down Expand Up @@ -167,26 +169,65 @@ def _check_components_input(plot_objects, resources=None):
return plot_objects


def _get_js(all_models, plots):
def _get_js(custom_models, all_models, plots):
js = PLOT_JS.render(
custom_models=custom_models,
all_models=serialize_json(all_models),
plots=plots
)
return _wrap_in_function(js)


def _get_script(all_models, plots):
js = _get_js(all_models, plots)
def _get_script(custom_models, all_models, plots):
js = _get_js(custom_models, all_models, plots)
script = PLOT_SCRIPT.render(
plot_js=js,
)
return script


def _append_plot(all_models, plots, plot_object, elementid):
def _escape_code(code):
""" Escape JS/CS source code, so that it can be embbeded in a JS string.
This is based on https://github.com/joliss/js-string-escape.
"""
def escape(match):
ch = match.group(0)

if ch == '"' or ch == "'" or ch == '\\':
return '\\' + ch
elif ch == '\n':
return '\\n'
elif ch == '\r':
return '\\r'
elif ch == '\u2028':
return '\\u2028'
elif ch == '\u2029':
return '\\u2029'

return re.sub(u"""['"\\\n\r\u2028\u2029]""", escape, code)

def _extract_custom_models(plot_object):
custom_models = {}

for obj in plot_object.references():
impl = getattr(obj.__class__, "__implementation__", None)

if impl is not None:
name = obj.__class__.__name__
impl = "['%s', {}]" % _escape_code(impl)
custom_models[name] = impl

return custom_models

def _append_plot(custom_models, all_models, plots, plot_object, elementid):
ref = plot_object.ref

custom_models.update(_extract_custom_models(plot_object))

for item in plot_object.dump():
all_models[item['id']] = item

plot_info = {
'modelid': ref["id"],
'elementid': elementid,
Expand Down Expand Up @@ -238,6 +279,7 @@ def notebook_div(plot_object):
}]

js = PLOT_JS.render(
custom_models = _extract_custom_models(plot_object),
all_models = serialize_json(plot_object.dump()),
plots = plots
)
Expand Down
4 changes: 2 additions & 2 deletions bokeh/enums.py
Expand Up @@ -66,6 +66,6 @@ def enumeration(*values, **kwargs):
"en-gb", "es-ES", "es", "et", "fi", "fr-CA", "fr-ch",
"fr", "hu", "it", "ja", "nl-nl", "pl", "pt-br",
"pt-pt", "ru", "ru-UA", "sk", "th", "tr", "uk-UA")
RenderLevel = enumeration("image", "underlay", "glyph", "annotation",
"overlay", "tool")
RenderLevel = enumeration("image", "underlay", "glyph", "annotation", "overlay", "tool")
Aggregation = enumeration("sum", "mean", "count", "nunique", "median", "min", "max")
ScriptingLanguage = enumeration("javascript", "coffeescript")
12 changes: 4 additions & 8 deletions bokeh/io.py
Expand Up @@ -26,7 +26,7 @@
from . import browserlib
from .document import Document
from .embed import notebook_div, file_html, autoload_server
from .models import Widget
from .models import Component
from .models.plots import GridPlot
from .models.widgets.layouts import HBox, VBox, VBoxForm
from .state import State
Expand Down Expand Up @@ -213,7 +213,7 @@ def show(obj, browser=None, new="tab"):
load the plot from that server session.
Args:
obj (Widget/Plot object) : a plot object to display
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
Expand Down Expand Up @@ -273,7 +273,7 @@ def save(obj, filename=None, resources=None, title=None, state=None):
are not provided.
Args:
obj (Document or Widget/Plot object) : a plot object to save
obj (Document or Component 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
Expand Down Expand Up @@ -327,15 +327,11 @@ def _get_save_args(state, filename, resources, title):
return filename, resources, title

def _save_helper(obj, filename, resources, title):

# TODO: (bev) Widget seems awkward as a base class to check here
if isinstance(obj, Widget):
if isinstance(obj, Component):
doc = Document()
doc.add(obj)

elif isinstance(obj, Document):
doc = obj

else:
raise RuntimeError("Unable to save object of type '%s'" % type(obj))

Expand Down
2 changes: 1 addition & 1 deletion bokeh/models/__init__.py
Expand Up @@ -5,6 +5,7 @@
from .annotations import *
from .axes import *
from .callbacks import *
from .component import *
from .formatters import *
from .glyphs import *
from .grids import *
Expand All @@ -17,5 +18,4 @@
from .sources import *
from .tickers import *
from .tools import *
from .widget import *
from .widgets import *
16 changes: 12 additions & 4 deletions bokeh/models/callbacks.py
Expand Up @@ -4,7 +4,8 @@

from ..plot_object import PlotObject
from ..properties import abstract
from ..properties import Dict, Instance, String
from ..properties import Dict, Instance, String, Enum
from ..enums import ScriptingLanguage

@abstract
class Callback(PlotObject):
Expand All @@ -25,16 +26,23 @@ class CustomJS(Callback):
""" Execute a JavaScript function. """

args = Dict(String, Instance(PlotObject), help="""
A mapping of names to Bokeh plot obejcts. These objects are made
A mapping of names to Bokeh plot objects. These objects are made
available to the callback code snippet as the values of named
parameters to the callback.
""")

code = String(help="""
A snippet of JavaScript code to execute in the browser. The code is
made into the body of a function, and all of of the named objects in
A snippet of JavaScript/CoffeeScript code to execute in the browser. The
code is made into the body of a function, and all of of the named objects in
``args`` are available as parameters that the code can use. Additionally,
a ``cb_obj`` parameter contains the object that triggered the callback
and an optional ``cb_data`` parameter that contains any tool-specific data
(i.e. mouse coordinates and hovered glyph indices for the HoverTool).
""")

lang = Enum(ScriptingLanguage, default="javascript", help="""
The implementation scripting language of the snippet. This can be either
raw JavaScript or CoffeeScript. In CoffeeScript's case, the snippet will
be compiled at runtime (in a web browser), so you don't need to have
node.js/io.js, etc. installed.
""")
13 changes: 2 additions & 11 deletions bokeh/models/widget.py → bokeh/models/component.py
@@ -1,9 +1,3 @@
""" Bokeh can present many kinds of UI widgets alongside plots.
When used in conjunction with the Bokeh server, it is possible to
trigger events, updates, etc. based on a user's interaction with
these widgets.
"""
from __future__ import absolute_import

from ..plot_object import PlotObject
Expand All @@ -12,11 +6,8 @@
from ..embed import notebook_div

@abstract
class Widget(PlotObject):
""" A base class for all interact widget types. ``Widget``
is not generally useful to instantiate on its own.
"""
class Component(PlotObject):
""" A base class for all embeddable models, i.e. plots and wigets. """

disabled = Bool(False, help="""
Whether the widget will be disabled when rendered. If ``True``,
Expand Down
4 changes: 2 additions & 2 deletions bokeh/models/plots.py
Expand Up @@ -21,7 +21,7 @@
from .renderers import Renderer, GlyphRenderer
from .sources import DataSource, ColumnDataSource
from .tools import Tool, ToolEvents
from .widget import Widget
from .component import Component

def _select_helper(args, kwargs):
"""
Expand Down Expand Up @@ -75,7 +75,7 @@ class PlotList(PlotContext):
# everywhere, so plotlist is the generic one
pass

class Plot(Widget):
class Plot(Component):
""" Model representing a plot, containing glyphs, guides, annotations.
"""
Expand Down
2 changes: 2 additions & 0 deletions bokeh/models/widgets/__init__.py
Expand Up @@ -2,6 +2,7 @@

# This file is excluded from flake8 checking in setup.cfg


from .buttons import *
from .dialogs import *
from .groups import *
Expand All @@ -11,3 +12,4 @@
from .markups import *
from .panels import *
from .tables import *
from .widget import *
2 changes: 1 addition & 1 deletion bokeh/models/widgets/buttons.py
Expand Up @@ -7,7 +7,7 @@
from ...properties import Bool, Int, String, Enum, Instance, List, Tuple
from ...enums import ButtonType
from ..callbacks import Callback
from ..widget import Widget
from .widget import Widget
from .icons import AbstractIcon

@abstract
Expand Down
2 changes: 1 addition & 1 deletion bokeh/models/widgets/dialogs.py
Expand Up @@ -3,7 +3,7 @@
from __future__ import absolute_import

from ...properties import Bool, String, List, Instance, Either
from ..widget import Widget
from .widget import Widget
from .layouts import BaseBox, HBox
from .buttons import Button

Expand Down
2 changes: 1 addition & 1 deletion bokeh/models/widgets/groups.py
Expand Up @@ -6,7 +6,7 @@
from ...properties import abstract
from ...properties import Bool, Int, String, Enum, List
from ...enums import ButtonType
from ..widget import Widget
from .widget import Widget

@abstract
class AbstractGroup(Widget):
Expand Down
2 changes: 1 addition & 1 deletion bokeh/models/widgets/icons.py
Expand Up @@ -6,7 +6,7 @@
from ...properties import abstract
from ...properties import Bool, Float, Enum
from ...enums import NamedIcon
from ..widget import Widget
from .widget import Widget

@abstract
class AbstractIcon(Widget):
Expand Down
2 changes: 1 addition & 1 deletion bokeh/models/widgets/inputs.py
Expand Up @@ -8,7 +8,7 @@
from ...properties import abstract
from ...properties import Bool, Int, Float, String, Date, RelativeDelta, Enum, List, Dict, Tuple, Either, Instance
from ..callbacks import Callback
from ..widget import Widget
from .widget import Widget

@abstract
class InputWidget(Widget):
Expand Down

0 comments on commit caaa3c6

Please sign in to comment.