diff --git a/jwql/bokeh_templating/__init__.py b/jwql/bokeh_templating/__init__.py deleted file mode 100644 index 2cf1818b4..000000000 --- a/jwql/bokeh_templating/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .template import BokehTemplate diff --git a/jwql/bokeh_templating/example/example_interface.yaml b/jwql/bokeh_templating/example/example_interface.yaml deleted file mode 100644 index 4aec297c7..000000000 --- a/jwql/bokeh_templating/example/example_interface.yaml +++ /dev/null @@ -1,26 +0,0 @@ -- !Slider: &a_slider # a slider for the a value - ref: "a_slider" - title: "A" - value: 4 - range: !!python/tuple [1, 20, 0.1] - on_change: ['value', !self.controller ] -- !Slider: &b_slider # a slider for the b value - ref: "b_slider" - title: "B" - value: 2 - range: !!python/tuple [1, 20, 0.1] - on_change: ['value', !self.controller ] -- !ColumnDataSource: &figure_source # the ColumnDataSource for the figure - ref: "figure_source" - data: - x: !self.x - y: !self.y -- !Figure: &the_figure # the Figure itself, which includes a single line element. - ref: 'the_figure' - elements: - - {'kind': 'line', 'source': *figure_source, 'line_color': 'orange', 'line_width': 2} -- !Document: # the Bokeh document layout: a single column with the figure and two sliders - - !column: - - *the_figure # note the use of YAML anchors to add the Bokeh objects to the Document layout directly. - - *a_slider - - *b_slider \ No newline at end of file diff --git a/jwql/bokeh_templating/example/main.py b/jwql/bokeh_templating/example/main.py deleted file mode 100644 index bd91d4e87..000000000 --- a/jwql/bokeh_templating/example/main.py +++ /dev/null @@ -1,65 +0,0 @@ -""" -This is a minimal example demonstrating how to create a Bokeh app using -the ``bokeh-templating`` package and the associated YAML template files. - -Author -------- - - - Graham Kanarek - -Dependencies ------------- - - The user must have PyYAML, Bokeh, and the ``bokeh-templating`` - packages installed. -""" - -import os -import numpy as np - -from jwql.bokeh_templating import BokehTemplate - -file_dir = os.path.dirname(os.path.realpath(__file__)) - - -class TestBokehApp(BokehTemplate): - """This is a minimal ``BokehTemplate`` app.""" - - _embed = True - - def pre_init(self): - """Before creating the Bokeh interface (by parsing the interface - file), we must initialize our ``a`` and ``b`` variables, and set - the path to the interface file. - """ - - self.a, self.b = 4, 2 - - self.format_string = None - self.interface_file = os.path.join(file_dir, "example_interface.yaml") - - # No post-initialization tasks are required. - post_init = None - - @property - def x(self): - """The x-value of the Lissajous curves.""" - return 4. * np.sin(self.a * np.linspace(0, 2 * np.pi, 500)) - - @property - def y(self): - """The y-value of the Lissajous curves.""" - return 3. * np.sin(self.b * np.linspace(0, 2 * np.pi, 500)) - - def controller(self, attr, old, new): - """This is the controller function which is used to update the - curves when the sliders are adjusted. Note the use of the - ``self.refs`` dictionary for accessing the Bokeh object - attributes.""" - self.a = self.refs["a_slider"].value - self.b = self.refs["b_slider"].value - - self.refs["figure_source"].data = {'x': self.x, 'y': self.y} - - -TestBokehApp() diff --git a/jwql/bokeh_templating/factory.py b/jwql/bokeh_templating/factory.py deleted file mode 100644 index 7c77bfa5d..000000000 --- a/jwql/bokeh_templating/factory.py +++ /dev/null @@ -1,270 +0,0 @@ -""" -This module defines YAML constructors and factory functions which are -used to create Bokeh objects parsed from YAML template files. - -The ``mapping_factory`` and ``sequence_factory`` functions are used to -create a constructor function for each of the mappings (i.e., classes) -and sequences (i.e., functions) included in the keyword map. The -``document_constructor`` and ``figure_constructor`` functions are -stand-alone constructors for the ``!Document`` and ``!Figure`` tag, -respectively. - -Author -------- - - - Graham Kanarek - -Use ---- - - The functions in this file are not intended to be called by the user - directly; users should subclass the ``BokehTemplate`` class found in - ``template.py`` instead. However, they can be used as a model for - creating new constructors for user-defined tags, which can then be - registered using the ``BokehTemplate.register_mapping_constructor`` - and ``BokehTemplate.register_sequence_constructor`` classmethods. - -Dependencies ------------- - - The user must have Bokeh installed. -""" - -from bokeh.io import curdoc - -from .keyword_map import bokeh_mappings as mappings, bokeh_sequences as sequences - -# Figures get their own constructor so we remove references to Figures from -# the keyword maps. -Figure = mappings.pop("figure") - - -def mapping_factory(tool, element_type): - """ - Create a mapping constructor for the given tool, used to parse the - given element tag. - - Parameters - ---------- - tool : BokehTemplate instance - The web app class instance to which the constructor will be - attached. This will become ``self`` when the factory is a method, - and is used to both store the Bokeh objects in the - ``BokehTemplate.refs`` dictionary, and allow for app-wide - formatting choices via ``BokehTemplate.format_string``. - - element_type : str - The Bokeh element name for which a constructor is desired. For - example, an ``element_type`` of ``'Slider'`` will create a - constructor for a Bokeh ``Slider`` widget, designated by the - ``!Slider`` tag in the YAML template file. - - Notes - ----- - See the ``BokehTemplate`` class implementation in ``template.py`` - for an example of how this function is used. - """ - - def mapping_constructor(loader, node): # docstring added below - fmt = tool.formats.get(element_type, {}) - value = loader.construct_mapping(node, deep=True) - ref = value.pop("ref", "") - callback = value.pop("on_change", []) - selection_callback = value.pop("selection_on_change", []) - onclick = value.pop("on_click", None) - fmt.update(value) - # convert "range" YAML keyword of slider into something Bokeh can read - if element_type == "Slider": - fmt["start"], fmt["end"], fmt["step"] = fmt.pop("range", [0, 1, 0.1]) - - # Many of these have hybrid signatures, with both positional and - # keyword arguments, so we need to convert an "args" keyword into - # positional arguments - arg = fmt.pop("arg", None) - if arg is not None: - obj = mappings[element_type](*arg, **fmt) - else: - obj = mappings[element_type](**fmt) - - # Store the object in the tool's "refs" dictionary - if ref: - tool.refs[ref] = obj - - # Handle callbacks and on_clicks - if callback: - obj.on_change(*callback) - if onclick: - obj.on_click(onclick) - if selection_callback: - obj.selected.on_change(*selection_callback) - - yield obj - - mapping_constructor.__name__ = element_type.lower() + '_' + mapping_constructor.__name__ - mapping_constructor.__doc__ = """ - A YAML constructor for the ``{et}`` Bokeh object. This will create a - ``{et}`` object wherever the ``!{et}`` tag appears in the YAML template - file. If a ``ref`` tag is specified, the object will then be stored in - the ``BokehTemplate.refs`` dictionary. - - This constructor is used for mappings -- i.e., classes or functions - which primarily have keyword arguments in their signatures. If - positional arguments appear, they can be included in the YAML file - with the `args` keyword. - """.format(et=element_type) - - return mapping_constructor - - -def sequence_factory(tool, element_type): - """ Create a sequence constructor for the given tool, used to parse - the given element tag. - - Parameters - ---------- - tool : BokehTemplate instance - The web app class instance to which the constructor will be - attached. This will become ``self`` when the factory is a method, - and is used to both store the Bokeh objects in the - ``BokehTemplate.refs`` dictionary, and allow for app-wide - formatting choices via ``BokehTemplate.format_string``. - - element_type : str - The Bokeh element name for which a constructor is desired. For - example, an ``element_type`` of ``'Slider'`` will create a - constructor for a Bokeh ``Slider`` widget, designated by the - ``!Slider`` tag in the YAML template file. - - Notes - ----- - See the ``BokehTemplate`` class implementation in ``template.py`` - for an example of how this function is used. - """ - - def sequence_constructor(loader, node): - fmt = tool.formats.get(element_type, {}) - value = loader.construct_sequence(node, deep=True) - obj = sequences[element_type](*value, **fmt) - yield obj - - sequence_constructor.__name__ = element_type.lower() + '_' + sequence_constructor.__name__ - sequence_constructor.__doc__ = """ - A YAML constructor for the ``{et}`` Bokeh object. This will create a - ``{et}`` object wherever the ``!{et}`` tag appears in the YAML template - file. If a ``ref`` tag is specified, the object will then be stored in - the ``BokehTemplate.refs`` dictionary. - - This constructor is used for sequences -- i.e., classes or functions - which have only positional arguments in their signatures (which for - Bokeh is only functions, no classes). - """.format(et=element_type) - - return sequence_constructor - - -# These constructors need more specialized treatment - -def document_constructor(tool, loader, node): - """ A YAML constructor for the Bokeh document, which is grabbed via - the Bokeh ``curdoc()`` function. When laying out a Bokeh document - with a YAML template, the ``!Document`` tag should be used as the - top-level tag in the layout. - """ - - layout = loader.construct_sequence(node, deep=True) - for element in layout: - curdoc().add_root(element) - tool.document = curdoc() - yield tool.document - - -def figure_constructor(tool, loader, node): - """ A YAML constructor for Bokeh Figure objects, which are - complicated enough to require their own (non-factory) constructor. - Each ``!Figure`` tag in the YAML template file will be turned into a - ``Figure`` object via this constructor (once it's been registered by - the ``BokehTemplate`` class). - """ - - fig = loader.construct_mapping(node, deep=True) - fmt = tool.formats.get('Figure', {}) - - elements = fig.pop('elements', []) - cmds = [] - ref = fig.pop("ref", "") - callback = fig.pop("on_change", []) - axis = tool.formats.get("Axis", {}) - axis.update(fig.pop("axis", {})) - - for key in fig: - val = fig[key] - if key in ['text', 'add_tools', 'js_on_event']: - cmds.append((key, val)) - else: - fmt[key] = val - - figure = Figure(**fmt) - - for key, cmd in cmds: - if key == 'add_tools': - figure.add_tools(*cmd) - elif key == 'text': - figure.text(*cmd.pop('loc'), **cmd) - elif key == 'js_on_event': - for event in cmd: - figure.js_on_event(*event) - - for element in elements: - key = element.pop('kind', 'diamond') - shape = {'line': ('Line', figure.line), - 'circle': ('Circle', figure.circle), - 'step': ('Step', figure.step), - 'diamond': ('Diamond', figure.diamond), - 'triangle': ('Triangle', figure.triangle), - 'square': ('Square', figure.square), - 'asterisk': ('Asterisk', figure.asterisk), - 'x': ('XGlyph', figure.x), - 'vbar': ('VBar', figure.vbar)} - if key in shape: - fmt_key, glyph = shape[key] - shape_fmt = tool.formats.get(fmt_key, {}) - shape_fmt.update(element) - x = shape_fmt.pop('x', 'x') - y = shape_fmt.pop('y', 'y') - glyph(x, y, **shape_fmt) - elif key == 'rect': - rect_fmt = tool.formats.get('Rect', {}) - rect_fmt.update(element) - figure.rect('rx', 'ry', 'rw', 'rh', **rect_fmt) - elif key == 'quad': - quad_fmt = tool.formats.get('Quad', {}) - quad_fmt.update(element) - figure.quad(**quad_fmt) - elif key == 'image': - image_fmt = tool.formats.get('Image', {}) - image_fmt.update(element) - arg = image_fmt.pop("image", None) - figure.image(arg, **image_fmt) - elif key == 'image_rgba': - image_fmt = tool.formats.get('ImageRGBA', {}) - image_fmt.update(element) - arg = image_fmt.pop("image", None) - figure.image_rgba(arg, **image_fmt) - elif key == 'multi_line': - multi_fmt = tool.formats.get('MultiLine', {}) - multi_fmt.update(element) - figure.multi_line(**multi_fmt) - elif key == 'layout': - obj = element.pop('obj', None) - figure.add_layout(obj, **element) - - for attr, val in axis.items(): - # change axis attributes, hopefully - setattr(figure.axis, attr, val) - - if ref: - tool.refs[ref] = figure - if callback: - figure.on_change(*callback) - - yield figure diff --git a/jwql/bokeh_templating/keyword_map.py b/jwql/bokeh_templating/keyword_map.py deleted file mode 100644 index 8f1be71ce..000000000 --- a/jwql/bokeh_templating/keyword_map.py +++ /dev/null @@ -1,65 +0,0 @@ -""" -A script to scrape the Bokeh package and collate dictionaries of -classes and functions. - -The ``_parse_module`` function iterates over a module, and uses the -``inspect`` package to sort everything in the module's namespace (as -identified by ``inspect.getmembers``) into a dictionary of mappings -(requiring primarily keyword arguments) and sequences (requiring -primarily positional arguments). - -Note that thhe files ``surface3d.py`` and ``surface3d.ts``, used to -create 3D surface plots, were downloaded from the Bokeh ``surface3d`` -example. - -Author -------- - - - Graham Kanarek - -Use ---- - - To access the Bokeh elements, the user should import as follows: - - :: - - from jwql.bokeh_templating.keyword_map import bokeh_sequences, bokeh_mappings - -Dependencies ------------- - - The user must have Bokeh installed. -""" - -from bokeh import layouts, models, palettes, plotting, transform -from inspect import getmembers, isclass, isfunction - -bokeh_sequences = {} -bokeh_mappings = {} # Note that abstract base classes *are* included - - -def _parse_module(module): - """ - Sort the members of a module into dictionaries of functions (sequences) - and classes (mappings). - """ - - def accessible_member(name, member): - return (not name.startswith("_")) and (module.__name__ in member.__module__) - - seqs = {nm: mem for nm, mem in getmembers(module, isfunction) if accessible_member(nm, mem)} - maps = {nm: mem for nm, mem in getmembers(module, isclass) if accessible_member(nm, mem)} - - # these need to be mappings - if 'gridplot' in seqs: - maps['gridplot'] = seqs.pop('gridplot') - if 'Donut' in seqs: - maps['Donut'] = seqs.pop('Donut') - return (seqs, maps) - - -for module in [models, plotting, layouts, palettes, transform]: - seqs, maps = _parse_module(module) - bokeh_sequences.update(seqs) - bokeh_mappings.update(maps) diff --git a/jwql/bokeh_templating/template.py b/jwql/bokeh_templating/template.py deleted file mode 100644 index 4f854fd30..000000000 --- a/jwql/bokeh_templating/template.py +++ /dev/null @@ -1,302 +0,0 @@ -#! /usr/bin/env python - -"""This module defines the ``BokehTemplate`` class, which can be subclassed -to create a Bokeh web app with a YAML templating file. - - -Author -------- - - - Graham Kanarek - -Use ---- - - The user should subclass the ``BokehTemplate`` class to create an - app, as demonstrated in ``example.py``. - - (A full tutorial on developing Bokeh apps with ``BokehTemplate`` is - forthcoming.) - - -Dependencies ------------- - - The user must have Bokeh and PyYAML installed. -""" - -import yaml -import os -from . import factory -from bokeh.embed import components -from inspect import signature - - -class BokehTemplateParserError(Exception): - """ - A custom error for problems with parsing the interface files. - """ - - -class BokehTemplateEmbedError(Exception): - """ - A custom error for problems with embedding components. - """ - - -class BokehTemplate(object): - """The base class for creating Bokeh web apps using a YAML - templating framework. - - Attributes - ---------- - _embed : bool - A flag to indicate whether or not the individual widgets will be - embedded in a webpage. If ``False``, the YAML interface file - must include a !Document tag. Defaults to ``False``. - document: obje - The Bokeh Dpcument object (if any), equivalent to the result of - calling ``curdoc()``. - formats: dict - A dictionary of widget formating specifications, parsed from - ``format_string`` (if one exists). - format_string: str - A string of YAML formatting specifications, using the same - syntax as the interface file, for Bokeh widgets. Note that - formatting choices present in individual widget instances in the - interface file override these. - interface_file: str - The path to the YAML interface file. - refs : dict - A dictionary of Bokeh objects which are given ``ref`` strings in - the interface file. Use this to store and interact with the - Bokeh data sources and widgets in callback methods. - - Methods - ------- - _mapping_factory() - Used by the interface parser to construct Bokeh widgets - _sequence_factory() - Used by the interface parser to construct Bokeh widgets - _figure_constructor() - Used by the interface parser to construct Bokeh widgets - _document_constructor() - Used by the interface parser to construct Bokeh widgets - """ - - # Each of these functions has a ``tool`` argument, which becomes ``self`` - # when they are stored as methods. This way, the YAML constructors can - # store the Bokeh objects in the ``tool.ref`` dictionary, and can access - # the formatting string, if any. See ``factory.py`` for more details. - _mapping_factory = factory.mapping_factory - _sequence_factory = factory.sequence_factory - _figure_constructor = factory.figure_constructor - _document_constructor = factory.document_constructor - - _embed = False - document = None - format_string = "" - formats = {} - interface_file = "" - refs = {} - - def _self_constructor(self, loader, tag_suffix, node): - """ - A multi_constructor for `!self` tag in the interface file. - """ - yield eval("self" + tag_suffix, globals(), locals()) - - def _register_default_constructors(self): - """ - Register all the default constructors with ``yaml.add_constructor``. - """ - for m in factory.mappings: - yaml.add_constructor("!" + m + ":", self._mapping_factory(m)) - - for s in factory.sequences: - yaml.add_constructor("!" + s + ":", self._sequence_factory(s)) - - yaml.add_constructor("!Figure:", self._figure_constructor) - yaml.add_constructor("!Document:", self._document_constructor) - yaml.add_multi_constructor(u"!self", self._self_constructor) - - def pre_init(self, **kwargs): - """ - This should be implemented by the app subclass, to do any pre- - initialization steps that it requires (setting defaults, loading - data, etc). - - If this is not required, subclass should set `pre_init = None` - in the class definition. - """ - - raise NotImplementedError - - def post_init(self): - """ - This should be implemented by the app subclass, to do any post- - initialization steps that the tool requires. - - If this is not required, subclass should set `post_init = None` - in the class definition. - """ - - raise NotImplementedError - - def __init__(self, **kwargs): - """ - Keyword arguments are passed to self.pre_init(). - """ - self._register_default_constructors() - - # Allow for pre-initialization code from the subclass. - if self.pre_init is not None: - if signature(self.pre_init).parameters: - # If we try to call pre_init with keyword parameters when none - # are included, it will throw an error - # thus, we use inspect.signature - self.pre_init(**kwargs) - else: - self.pre_init() - - # Initialize attributes for YAML parsing - self.formats = {} - self.refs = {} - - # Parse formatting string, if any, and the interface YAML file - self.include_formatting() - self.parse_interface() - - # Allow for post-init code from the subclass. - if self.post_init is not None: - self.post_init() - - def include_formatting(self): - """ - This should simply be a dictionary of formatting keywords at the end. - """ - if not self.format_string: - return - - self.formats = yaml.load(self.format_string, Loader=yaml.SafeLoader) - - def parse_interface(self): - """ - This is the workhorse YAML parser, which creates the interface based - on the layout file. - - `interface_file` is the path to the interface .yaml file to be parsed. - """ - - if not self.interface_file: - raise NotImplementedError("Interface file required.") - - # Read the interface file into a string - filepath = os.path.abspath(os.path.expanduser(self.interface_file)) - if not os.path.exists(filepath): - raise BokehTemplateParserError("Interface file path does not exist.") - with open(filepath) as f: - interface = f.read() - - # If necessary, verify that the interface string contains !Document tag - if not self._embed and '!Document' not in interface: - raise BokehTemplateParserError("Interface file must contain a Document tag") - - # Now, since we've registered all the constructors, we can parse the - # entire string with yaml. We don't need to assign the result to a - # variable, since the constructors store everything in self.refs - # (and self.document, for the document). - try: - self.full_stream = list(yaml.load_all(interface, Loader=yaml.FullLoader)) - except yaml.YAMLError as exc: - raise BokehTemplateParserError(exc) - - def embed(self, ref): - """A wrapper for ``bokeh.embed.components`` to return embeddable - code for the given widget reference.""" - element = self.refs.get(ref, None) - if element is None: - raise BokehTemplateEmbedError("Undefined component reference") - return components(element) - - @staticmethod - def parse_string(yaml_string): - """ A utility functon to parse any YAML string using the - registered constructors. (Usually used for debugging.)""" - return list(yaml.load_all(yaml_string)) - - @classmethod - def register_sequence_constructor(cls, tag, parse_func): - """ - Register a new sequence constructor with YAML. - - Parameters - ---------- - tag : str - The YAML tag string to be used for the constructor. - parse_func: object - The parsing function to be registered with YAML. This - function should accept a multi-line string, and return a - python object. - - Notes - ----- - This classmethod should be used to register a new constructor - *before* creating & instantiating a subclass of BokehTemplate : - - :: - - from bokeh_template import BokehTemplate - BokehTemplate.register_sequence_constructor("my_tag", my_parser) - - class myTool(BokehTemplate): - pass - - myTool() - """ - if tag.startswith("!"): - tag = tag[1:] - - def user_constructor(loader, node): - value = loader.construct_sequence(node, deep=True) - yield parse_func(value) - user_constructor.__name__ = tag.lower() + "_constructor" - yaml.add_constructor("!" + tag, user_constructor) - - @classmethod - def register_mapping_constructor(cls, tag, parse_func): - """ - Register a new mapping constructor with YAML. - - Parameters - ---------- - tag : str - The YAML tag string to be used for the constructor. - parse_func: object - The parsing function to be registered with YAML. This - function should accept a multi-line string, and return a - python object. - - Notes - ----- - This classmethod should be used to register a new constructor - *before* creating & instantiating a subclass of BokehTemplate : - - :: - - from bokeh_template import BokehTemplate - BokehTemplate.register_mapping_constructor("my_tag", my_parser) - - class myTool(BokehTemplate): - pass - - myTool() - """ - if tag.startswith("!"): - tag = tag[1:] - - def user_constructor(loader, node): - value = loader.construct_mapping(node, deep=True) - yield parse_func(value) - user_constructor.__name__ = tag.lower() + "_constructor" - yaml.add_constructor("!" + tag, user_constructor) diff --git a/jwql/website/apps/jwql/monitor_pages/monitor_bias_bokeh.py b/jwql/website/apps/jwql/monitor_pages/monitor_bias_bokeh.py index 77d799a22..d3889e8e2 100644 --- a/jwql/website/apps/jwql/monitor_pages/monitor_bias_bokeh.py +++ b/jwql/website/apps/jwql/monitor_pages/monitor_bias_bokeh.py @@ -37,7 +37,6 @@ from PIL import Image from sqlalchemy import func -from jwql.bokeh_templating import BokehTemplate from jwql.database.database_interface import get_unique_values_per_column, NIRCamBiasStats, NIRISSBiasStats, NIRSpecBiasStats, session from jwql.utils.constants import FULL_FRAME_APERTURES, JWST_INSTRUMENT_NAMES_MIXEDCASE from jwql.utils.permissions import set_permissions @@ -303,7 +302,7 @@ def modify_bokeh_saved_html(self): """ # Insert into our html template and save temp_vars = {'inst': self.instrument, 'plot_script': self.script, 'plot_div': self.div} - html_lines = file_html(self.tabs, CDN, title=f'{self.instrument} bias monitor', + html_lines = file_html(self.tabs, CDN, title=f'{self.instrument} bias monitor', template=self.html_file, template_variables=temp_vars) lines = html_lines.split('\n')