Ejemplo n.º 1
0
class HeatMapPlot(ColorbarPlot):

    clipping_colors = param.Dict(default={'NaN': 'white'},
                                 doc="""
        Dictionary to specify colors for clipped values, allows
        setting color for NaN values and for values above and below
        the min and max value. The min, max or NaN color may specify
        an RGB(A) color as a color hex string of the form #FFFFFF or
        #FFFFFFFF or a length 3 or length 4 tuple specifying values in
        the range 0-1 or a named HTML color.""")

    show_legend = param.Boolean(default=False,
                                doc="""
        Whether to show legend for the plot.""")

    _plot_methods = dict(single='rect')
    style_opts = ['cmap', 'color'] + line_properties + fill_properties

    _update_handles = ['color_mapper', 'source', 'glyph', 'colorbar']
    _categorical = True

    def _get_factors(self, element):
        return super(HeatMapPlot, self)._get_factors(element.gridded)

    def get_data(self, element, ranges, style):
        x, y, z = [
            dimension_sanitizer(d) for d in element.dimensions(label=True)[:3]
        ]
        if self.invert_axes: x, y = y, x
        cmapper = self._get_colormapper(element.vdims[0], element, ranges,
                                        style)
        if self.static_source:
            return {}, {
                'x': x,
                'y': y,
                'fill_color': {
                    'field': 'zvalues',
                    'transform': cmapper
                }
            }, style

        aggregate = element.gridded
        xdim, ydim = aggregate.dimensions()[:2]
        xvals, yvals = (aggregate.dimension_values(x),
                        aggregate.dimension_values(y))
        zvals = aggregate.dimension_values(2, flat=False)
        if self.invert_axes:
            xdim, ydim = ydim, xdim
            zvals = zvals.T.flatten()
        else:
            zvals = zvals.T.flatten()
        if xvals.dtype.kind not in 'SU':
            xvals = [xdim.pprint_value(xv) for xv in xvals]
        if yvals.dtype.kind not in 'SU':
            yvals = [ydim.pprint_value(yv) for yv in yvals]
        data = {x: xvals, y: yvals, 'zvalues': zvals}

        if any(isinstance(t, HoverTool)
               for t in self.state.tools) and not self.static_source:
            for vdim in element.vdims:
                sanitized = dimension_sanitizer(vdim.name)
                data[sanitized] = [
                    '-' if is_nan(v) else vdim.pprint_value(v)
                    for v in aggregate.dimension_values(vdim)
                ]
        return (data, {
            'x': x,
            'y': y,
            'fill_color': {
                'field': 'zvalues',
                'transform': cmapper
            },
            'height': 1,
            'width': 1
        }, style)
Ejemplo n.º 2
0
class ReloadService(param.Parameterized):  # pylint: disable=too-many-instance-attributes
    """The ReloadService is used by the Designer.
    For each component you want access to in the Designer you should provide a seperate
    Reload Service

    Args:
        component ([type]): For now the components that are know to be supported are

        - subclasses of `pn.reactive.Reactive`
        - subclasses of `param.Parameterized` with a `view` parameter which is a subclass of
        `pn.reactive.Reactive`

    Please NOTE that in order for the reload service to be able to reload the compoonent, the
    component specified cannot be defined in the __main__ file.

    Example
    -------

    ```python
    TITLE_COMPONENT = ReloadService(
        component=components.TitleComponent, css_path=COMPONENT_CSS, js_path=COMPONENT_JS,
    )
    EMPTY_COMPONENT = ReloadService(
        component=components.EmptyComponent, css_path=COMPONENT_CSS, js_path=COMPONENT2_JS,
    )
    ```"""

    component = param.Parameter(allow_None=False)
    component_parameters = param.Dict()
    component_instance = param.Parameter()
    css_path = param.Parameter(constant=True)
    js_path = param.Parameter(constant=True)
    modules_to_reload = param.List()

    reload_component = param.Action(label="RELOAD COMPONENT")
    reload_css_file = param.Action(label="RELOAD CSS")
    reload_js_file = param.Action(label="RELOAD JS")

    css_text = param.String()
    js_text = param.String()

    reloading = param.Boolean(default=False)
    last_reload = param.String(constant=True)
    error_message = param.String()

    def __init__(self, component, **params):
        if not isinstance(params, dict):
            params = {}
        params["component"] = component
        super().__init__(**params)

        try:
            name = self.component.name
        except AttributeError:
            name = self.component.__name__

        with param.edit_constant(self):
            self.name = name

        self.reload_component = self._reload_component
        self.reload_css_file = self._reload_css_file
        self.reload_js_file = self._reload_js_file

    def __repr__(self):
        return f"ReloadService({self.name})"

    def __str__(self):
        return f"ReloadService({self.name})"

    def _reload_component(self, _=None):
        try:
            self._signal_reload_start()

            if self.component_instance is not None:
                for mod in self.modules_to_reload:  # pylint: disable=not-an-iterable
                    importlib.reload(mod)

                mod = sys.modules[self.component.__module__]
                importlib.reload(mod)
                with param.edit_constant(self):
                    self.component = getattr(mod, self.component.__name__)

            if self.component_parameters:
                # pylint: disable=not-a-mapping
                self.component_instance = self.component(
                    **self.component_parameters)
            else:
                self.component_instance = self.component()

            self._reset_error_message()
        except Exception as ex:  # pylint: disable=broad-except
            self._report_exception(ex)
        finally:
            self._signal_reload_end()

    def _reload_css_file(self, _=None):
        try:
            self._signal_reload_start()
            if not self.css_path:
                pass
            elif isinstance(self.css_path, pathlib.Path):
                self.css_text = self.css_path.read_text()
            else:
                raise NotImplementedError

            self._reset_error_message()
        except Exception as ex:  # pylint: disable=broad-except
            self._report_exception(ex)
        finally:
            self._signal_reload_end()

    def _reload_js_file(self, _=None):
        try:
            self._signal_reload_start()
            if not self.js_path:
                pass
            elif isinstance(self.js_path, pathlib.Path):
                self.js_text = self.js_path.read_text()
            else:
                raise NotImplementedError

            self._reset_error_message()
        except Exception as ex:  # pylint: disable=broad-except
            self._report_exception(ex)
        finally:
            self._signal_reload_end()

    def _signal_reload_start(self):
        self.reloading = True
        print("reload start", self.name, datetime.datetime.now())

    def _report_exception(self, ex):  # pylint: disable=unused-argument
        self.error_message = traceback.format_exc()
        self.component_instance = ErrorView(error_message=self.error_message)
        print(self.name, self.error_message)

    def _signal_reload_end(self):
        self.reloading = False
        with param.edit_constant(self):
            self.last_reload = str(datetime.datetime.now())
        print("reload end", self.name, datetime.datetime.now())

    def _reset_error_message(self):
        self.error_message = ""
Ejemplo n.º 3
0
class Gauge(ValueIndicator):
    """
    A `Gauge` represents a value in some range as a position on
    speedometer or gauge. It is similar to a `Dial` but visually a lot
    busier.

    Reference: https://panel.holoviz.org/reference/indicators/Gauge.html

    :Example:

    >>> Gauge(name='Speed', value=79, bounds=(0, 200), colors=[(0.4, 'green'), (1, 'red')])
    """

    annulus_width = param.Integer(default=10, doc="""
      Width of the gauge annulus.""")

    bounds = param.Range(default=(0, 100), doc="""
      The upper and lower bound of the dial.""")

    colors = param.List(default=None, doc="""
      Color thresholds for the Gauge, specified as a list of tuples
      of the fractional threshold and the color to switch to.""")

    custom_opts = param.Dict(doc="""
      Additional options to pass to the ECharts Gauge definition.""")

    height = param.Integer(default=300, bounds=(0, None))

    end_angle = param.Number(default=-45, doc="""
      Angle at which the gauge ends.""")

    format = param.String(default='{value}%', doc="""
      Formatting string for the value indicator.""")

    num_splits = param.Integer(default=10, doc="""
      Number of splits along the gauge.""")

    show_ticks = param.Boolean(default=True, doc="""
      Whether to show ticks along the dials.""")

    show_labels = param.Boolean(default=True, doc="""
      Whether to show tick labels along the dials.""")

    start_angle = param.Number(default=225, doc="""
      Angle at which the gauge starts.""")

    tooltip_format = param.String(default='{b} : {c}%', doc="""
      Formatting string for the hover tooltip.""")

    title_size = param.Integer(default=18, doc="""
      Size of title font.""")

    value = param.Number(default=25, doc="""
      Value to indicate on the gauge a value within the declared bounds.""")

    width = param.Integer(default=300, bounds=(0, None))

    _rename = {}

    _source_transforms = {
        'annulus_width': None, 'bounds': None, 'colors': None,
        'custom_opts': None, 'end_angle': None, 'format': None,
        'num_splits': None, 'show_ticks': None, 'show_labels': None,
        'start_angle': None, 'tooltip_format': None, 'title_size': None,
        'value': None
    }

    @property
    def _widget_type(self):
        if 'panel.models.echarts' not in sys.modules:
            from ..models.echarts import ECharts
        else:
            ECharts = getattr(sys.modules['panel.models.echarts'], 'ECharts')
        return ECharts

    def __init__(self, **params):
        super().__init__(**params)
        self._update_value_bounds()

    @param.depends('bounds', watch=True)
    def _update_value_bounds(self):
        self.param.value.bounds = self.bounds

    def _process_param_change(self, msg):
        msg = super()._process_param_change(msg)
        vmin, vmax = msg.pop('bounds', self.bounds)
        msg['data'] = {
            'tooltip': {
                'formatter': msg.pop('tooltip_format', self.tooltip_format)
            },
            'series': [{
                'name': 'Gauge',
                'type': 'gauge',
                'axisTick': {'show': msg.pop('show_ticks', self.show_ticks)},
                'axisLabel': {'show': msg.pop('show_labels', self.show_labels)},
                'title': {'fontWeight': 'bold', 'fontSize': msg.pop('title_size', self.title_size)},
                'splitLine': {'show': True},
                'radius': '100%',
                'detail': {'formatter': msg.pop('format', self.format)},
                'min': vmin,
                'max': vmax,
                'startAngle': msg.pop('start_angle', self.start_angle),
                'endAngle': msg.pop('end_angle', self.end_angle),
                'splitNumber': msg.pop('num_splits', self.num_splits),
                'data': [{'value': msg.pop('value', self.value), 'name': self.name}],
                'axisLine': {
                    'lineStyle': {
                        'width': msg.pop('annulus_width', self.annulus_width),
                    }
                }
            }]
        }
        colors = msg.pop('colors', self.colors)
        if colors:
            msg['data']['series'][0]['axisLine']['lineStyle']['color'] = colors
        custom_opts = msg.pop('custom_opts', self.custom_opts)
        if custom_opts:
            gauge = msg['data']['series'][0]
            for k, v in custom_opts.items():
                if k not in gauge or not isinstance(gauge[k], dict):
                    gauge[k] = v
                else:
                    gauge[k].update(v)
        return msg
Ejemplo n.º 4
0
class ColorbarPlot(ElementPlot):

    clim = param.NumericTuple(default=(np.nan, np.nan), length=2, doc="""
       User-specified colorbar axis range limits for the plot, as a tuple (low,high).
       If specified, takes precedence over data and dimension ranges.""")

    colorbar = param.Boolean(default=False, doc="""
        Whether to display a colorbar.""")

    color_levels = param.ClassSelector(default=None, class_=(int, list), doc="""
        Number of discrete colors to use when colormapping or a set of color
        intervals defining the range of values to map each color to.""")

    colorbar_opts = param.Dict(default={}, doc="""
        Allows setting including borderwidth, showexponent, nticks,
        outlinecolor, thickness, bgcolor, outlinewidth, bordercolor,
        ticklen, xpad, ypad, tickangle...""")

    symmetric = param.Boolean(default=False, doc="""
        Whether to make the colormap symmetric around zero.""")

    def get_color_opts(self, eldim, element, ranges, style):
        opts = {}
        dim_name = dim_range_key(eldim)
        if self.colorbar:
            if isinstance(eldim, dim):
                title = str(eldim) if eldim.ops else str(eldim)[1:-1]
            else:
                title = eldim.pprint_label
            opts['colorbar'] = dict(title=title, **self.colorbar_opts)
            opts['showscale'] = True
        else:
            opts['showscale'] = False

        if eldim:
            auto = False
            if util.isfinite(self.clim).all():
                cmin, cmax = self.clim
            elif dim_name in ranges:
                cmin, cmax = ranges[dim_name]['combined']
            elif isinstance(eldim, dim):
                cmin, cmax = np.nan, np.nan
                auto = True
            else:
                cmin, cmax = element.range(dim_name)
            if self.symmetric:
                cabs = np.abs([cmin, cmax])
                cmin, cmax = -cabs.max(), cabs.max()
        else:
            auto = True
            cmin, cmax = None, None

        cmap = style.pop('cmap', 'viridis')
        colorscale = get_colorscale(cmap, self.color_levels, cmin, cmax)

        # Reduce colorscale length to <= 255 to work around
        # https://github.com/plotly/plotly.js/issues/3699. Plotly.js performs
        # colorscale interpolation internally so reducing the number of colors
        # here makes very little difference to the displayed colorscale.
        #
        # Note that we need to be careful to make sure the first and last
        # colorscale pairs, colorscale[0] and colorscale[-1], are preserved
        # as the first and last in the subsampled colorscale
        if isinstance(colorscale, list) and len(colorscale) > 255:
            last_clr_pair = colorscale[-1]
            step = int(np.ceil(len(colorscale) / 255))
            colorscale = colorscale[0::step]
            colorscale[-1] = last_clr_pair

        if cmin is not None:
            opts['cmin'] = cmin
        if cmax is not None:
            opts['cmax'] = cmax
        opts['cauto'] = auto
        opts['colorscale'] = colorscale
        return opts
Ejemplo n.º 5
0
class NdWidget(param.Parameterized):
    """
    NdWidget is an abstract base class implementing a method to find
    the dimensions and keys of any ViewableElement, GridSpace or
    UniformNdMapping type.  In the process it creates a mock_obj to
    hold the dimensions and keys.
    """

    display_options = param.Dict(default={},
                                 doc="""
        The display options used to generate individual frames""")

    embed = param.Boolean(default=True,
                          doc="""
        Whether to embed all plots in the Javascript, generating
        a static widget not dependent on the IPython server.""")

    #######################
    # JSON export options #
    #######################

    export_json = param.Boolean(default=False,
                                doc="""
         Whether to export plots as JSON files, which can be
         dynamically loaded through a callback from the slider.""")

    json_save_path = param.String(default='./json_figures',
                                  doc="""
         If export_json is enabled the widget will save the JSON
         data to this path. If None data will be accessible via the
         json_data attribute.""")

    json_load_path = param.String(default=None,
                                  doc="""
         If export_json is enabled the widget JS code will load the data
         from this path, if None defaults to json_save_path. For loading
         the data from within the notebook the path must be relative,
         when exporting the notebook the path can be set to another
         location like a webserver where the JSON files can be uploaded to.""")

    ##############################
    # Javascript include options #
    ##############################

    css = param.String(default=None,
                       doc="""
        Defines the local CSS file to be loaded for this widget.""")

    basejs = param.String(default='widgets.js',
                          doc="""
        JS file containing javascript baseclasses for the widget.""")

    extensionjs = param.String(default=None,
                               doc="""
        Optional javascript extension file for a particular backend.""")

    widgets = {}
    counter = 0

    def __init__(self, plot, renderer=None, **params):
        super(NdWidget, self).__init__(**params)
        self.id = plot.comm.id if plot.comm else uuid.uuid4().hex
        self.plot = plot
        self.plot_id = plot.id
        streams = []
        for stream in plot.streams:
            if any(k in plot.dimensions for k in stream.contents):
                streams.append(stream)

        keys = plot.keys[:1] if self.plot.dynamic else plot.keys
        self.dimensions, self.keys = drop_streams(streams, plot.dimensions,
                                                  keys)
        defaults = [kd.default for kd in self.dimensions]
        self.init_key = tuple(v if d is None else d
                              for v, d in zip(self.keys[0], defaults))

        self.json_data = {}
        if self.plot.dynamic: self.embed = False
        if renderer is None:
            backend = Store.current_backend
            self.renderer = Store.renderers[backend]
        else:
            self.renderer = renderer

        # Create mock NdMapping to hold the common dimensions and keys
        sorted_dims = []
        for dim in self.dimensions:
            if dim.values and all(isnumeric(v) for v in dim.values):
                dim = dim.clone(values=sorted(dim.values))
            sorted_dims.append(dim)

        if self.plot.dynamic:
            self.length = np.product(
                [len(d.values) for d in sorted_dims if d.values])
        else:
            self.length = len(self.plot)

        with item_check(False):
            self.mock_obj = NdMapping([(k, None) for k in self.keys],
                                      kdims=sorted_dims,
                                      sort=False)

        NdWidget.widgets[self.id] = self

        # Set up jinja2 templating
        import jinja2
        templateLoader = jinja2.FileSystemLoader(subdirs)
        self.jinjaEnv = jinja2.Environment(loader=templateLoader)
        if not self.embed:
            comm_manager = self.renderer.comm_manager
            self.comm = comm_manager.get_client_comm(
                id=self.id + '_client', on_msg=self._process_update)

    def cleanup(self):
        self.plot.cleanup()
        del NdWidget.widgets[self.id]

    def _process_update(self, msg):
        if 'content' not in msg:
            raise ValueError('Received widget comm message has no content.')
        self.update(msg['content'])

    def __call__(self, as_script=False):
        data = self._get_data()
        html = self.render_html(data)
        js = self.render_js(data)
        if as_script:
            return js, html
        js = '<script type="text/javascript">%s</script>' % js
        html = '\n'.join([html, js])
        return html

    def _get_data(self):
        delay = int(1000. / self.display_options.get('fps', 5))
        CDN = {}
        for name, resources in self.plot.renderer.core_dependencies.items():
            if 'js' in resources:
                CDN[name] = resources['js'][0]
        for name, resources in self.plot.renderer.extra_dependencies.items():
            if 'js' in resources:
                CDN[name] = resources['js'][0]
        name = type(self).__name__
        cached = str(self.embed).lower()
        load_json = str(self.export_json).lower()
        mode = str(self.renderer.mode)
        json_path = (self.json_save_path
                     if self.json_load_path is None else self.json_load_path)
        if json_path and json_path[-1] != '/':
            json_path = json_path + '/'
        dynamic = json.dumps(
            self.plot.dynamic) if self.plot.dynamic else 'false'
        return dict(CDN=CDN,
                    frames=self.get_frames(),
                    delay=delay,
                    cached=cached,
                    load_json=load_json,
                    mode=mode,
                    id=self.id,
                    Nframes=self.length,
                    widget_name=name,
                    json_path=json_path,
                    dynamic=dynamic,
                    plot_id=self.plot_id)

    def render_html(self, data):
        template = self.jinjaEnv.get_template(self.html_template)
        return template.render(**data)

    def render_js(self, data):
        template = self.jinjaEnv.get_template(self.js_template)
        return template.render(**data)

    def get_frames(self):
        if self.embed:
            frames = OrderedDict([(idx, self._plot_figure(idx))
                                  for idx in range(len(self.plot))])
        else:
            frames = {}
        return self.encode_frames(frames)

    def encode_frames(self, frames):
        if isinstance(frames, dict):
            frames = dict(frames)
        return json.dumps(frames)

    def save_json(self, frames):
        """
        Saves frames data into a json file at the
        specified json_path, named with the widget uuid.
        """
        if self.json_save_path is None: return
        path = os.path.join(self.json_save_path, '%s.json' % self.id)
        if not os.path.isdir(self.json_save_path):
            os.mkdir(self.json_save_path)
        with open(path, 'w') as f:
            json.dump(frames, f)
        self.json_data = frames

    def _plot_figure(self, idx):
        with self.renderer.state():
            self.plot.update(idx)
            css = self.display_options.get('css', {})
            figure_format = self.display_options.get('figure_format',
                                                     self.renderer.fig)
            return self.renderer.html(self.plot, figure_format, css=css)

    def update(self, key):
        pass
Ejemplo n.º 6
0
class Renderer(Exporter):
    """
    The job of a Renderer is to turn the plotting state held within
    Plot classes into concrete, visual output in the form of the PNG,
    SVG, MP4 or WebM formats (among others). Note that a Renderer is a
    type of Exporter and must therefore follow the Exporter interface.

    The Renderer needs to be able to use the .state property of the
    appropriate Plot classes associated with that renderer in order to
    generate output. The process of 'drawing' is execute by the Plots
    and the Renderer turns the final plotting state into output.
    """

    backend = param.String(doc="""
        The full, lowercase name of the rendering backend or third
        part plotting package used e.g 'matplotlib' or 'cairo'.""")

    dpi = param.Integer(None,
                        doc="""
        The render resolution in dpi (dots per inch)""")

    fig = param.ObjectSelector(default='auto',
                               objects=['auto'],
                               doc="""
        Output render format for static figures. If None, no figure
        rendering will occur. """)

    fps = param.Number(20,
                       doc="""
        Rendered fps (frames per second) for animated formats.""")

    holomap = param.ObjectSelector(
        default='auto',
        objects=['scrubber', 'widgets', None, 'auto'],
        doc="""
        Output render multi-frame (typically animated) format. If
        None, no multi-frame rendering will occur.""")

    mode = param.ObjectSelector(default='default',
                                objects=['default', 'server'],
                                doc="""
        Whether to render the object in regular or server mode. In server
        mode a bokeh Document will be returned which can be served as a
        bokeh server app. By default renders all output is rendered to HTML."""
                                )

    size = param.Integer(100,
                         doc="""
        The rendered size as a percentage size""")

    widget_location = param.ObjectSelector(default=None,
                                           allow_None=True,
                                           objects=[
                                               'left', 'bottom', 'right',
                                               'top', 'top_left', 'top_right',
                                               'bottom_left', 'bottom_right',
                                               'left_top', 'left_bottom',
                                               'right_top', 'right_bottom'
                                           ],
                                           doc="""
        The position of the widgets relative to the plot.""")

    widget_mode = param.ObjectSelector(default='embed',
                                       objects=['embed', 'live'],
                                       doc="""
        The widget mode determining whether frames are embedded or generated
        'live' when interacting with the widget.""")

    css = param.Dict(default={},
                     doc="""
        Dictionary of CSS attributes and values to apply to HTML output.""")

    info_fn = param.Callable(None,
                             allow_None=True,
                             constant=True,
                             doc="""
        Renderers do not support the saving of object info metadata""")

    key_fn = param.Callable(None,
                            allow_None=True,
                            constant=True,
                            doc="""
        Renderers do not support the saving of object key metadata""")

    post_render_hooks = param.Dict(default={
        'svg': [],
        'png': []
    },
                                   doc="""
       Optional dictionary of hooks that are applied to the rendered
       data (according to the output format) before it is returned.

       Each hook is passed the rendered data and the object that is
       being rendered. These hooks allow post-processing of rendered
       data before output is saved to file or displayed.""")

    # Defines the valid output formats for each mode.
    mode_formats = {'fig': [None, 'auto'], 'holomap': [None, 'auto']}

    # The comm_manager handles the creation and registering of client,
    # and server side comms
    comm_manager = CommManager

    # Define appropriate widget classes
    widgets = ['scrubber', 'widgets']

    # Whether in a notebook context, set when running Renderer.load_nb
    notebook_context = False

    # Plot registry
    _plots = {}

    # Whether to render plots with Panel
    _render_with_panel = False

    def __init__(self, **params):
        self.last_plot = None
        super(Renderer, self).__init__(**params)

    def __call__(self, obj, fmt='auto', **kwargs):
        plot, fmt = self._validate(obj, fmt)
        info = {'file-ext': fmt, 'mime_type': MIME_TYPES[fmt]}

        if plot is None:
            return None, info
        elif self.mode == 'server':
            return self.server_doc(plot, doc=kwargs.get('doc')), info
        elif isinstance(plot, Viewable):
            return self.static_html(plot), info
        else:
            data = self._figure_data(plot, fmt, **kwargs)
            data = self._apply_post_render_hooks(data, obj, fmt)
            return data, info

    @bothmethod
    def get_plot(self_or_cls,
                 obj,
                 doc=None,
                 renderer=None,
                 comm=None,
                 **kwargs):
        """
        Given a HoloViews Viewable return a corresponding plot instance.
        """
        if isinstance(obj, DynamicMap) and obj.unbounded:
            dims = ', '.join('%r' % dim for dim in obj.unbounded)
            msg = ('DynamicMap cannot be displayed without explicit indexing '
                   'as {dims} dimension(s) are unbounded. '
                   '\nSet dimensions bounds with the DynamicMap redim.range '
                   'or redim.values methods.')
            raise SkipRendering(msg.format(dims=dims))

        # Initialize DynamicMaps with first data item
        initialize_dynamic(obj)

        if not renderer:
            renderer = self_or_cls
            if not isinstance(self_or_cls, Renderer):
                renderer = self_or_cls.instance()

        if not isinstance(obj, Plot):
            if not displayable(obj):
                obj = collate(obj)
                initialize_dynamic(obj)
            obj = Compositor.map(obj, mode='data', backend=self_or_cls.backend)
            obj = Layout(obj) if isinstance(obj, AdjointLayout) else obj
            plot_opts = dict(self_or_cls.plot_options(obj, self_or_cls.size),
                             **kwargs)
            plot = self_or_cls.plotting_class(obj)(obj,
                                                   renderer=renderer,
                                                   **plot_opts)
            defaults = [kd.default for kd in plot.dimensions]
            init_key = tuple(v if d is None else d
                             for v, d in zip(plot.keys[0], defaults))
            plot.update(init_key)
        else:
            plot = obj

        if isinstance(self_or_cls, Renderer):
            self_or_cls.last_plot = plot

        if comm:
            plot.comm = comm

        if comm or self_or_cls.mode == 'server':
            from bokeh.document import Document
            from bokeh.io import curdoc
            if doc is None:
                doc = Document() if self_or_cls.notebook_context else curdoc()
            plot.document = doc
        return plot

    @bothmethod
    def get_plot_state(self_or_cls, obj, renderer=None, **kwargs):
        """
        Given a HoloViews Viewable return a corresponding plot state.
        """
        if not isinstance(obj, Plot):
            obj = self_or_cls.get_plot(obj, renderer, **kwargs)
        return obj.state

    def _validate(self, obj, fmt, **kwargs):
        """
        Helper method to be used in the __call__ method to get a
        suitable plot or widget object and the appropriate format.
        """
        if isinstance(obj, Viewable):
            return obj, 'html'

        fig_formats = self.mode_formats['fig']
        holomap_formats = self.mode_formats['holomap']

        holomaps = obj.traverse(lambda x: x, [HoloMap])
        dynamic = any(isinstance(m, DynamicMap) for m in holomaps)

        if fmt in ['auto', None]:
            if any(
                    len(o) > 1 or (isinstance(o, DynamicMap)
                                   and unbound_dimensions(o.streams, o.kdims))
                    for o in holomaps):
                fmt = holomap_formats[0] if self.holomap in [
                    'auto', None
                ] else self.holomap
            else:
                fmt = fig_formats[0] if self.fig == 'auto' else self.fig

        if fmt in self.widgets:
            plot = self.get_widget(obj, fmt)
            fmt = 'html'
        elif dynamic or (self._render_with_panel and fmt == 'html'):
            plot, fmt = HoloViewsPane(obj,
                                      center=True,
                                      backend=self.backend,
                                      renderer=self), fmt
        else:
            plot = self.get_plot(obj, renderer=self, **kwargs)

        all_formats = set(fig_formats + holomap_formats)
        if fmt not in all_formats:
            raise Exception(
                "Format %r not supported by mode %r. Allowed formats: %r" %
                (fmt, self.mode, fig_formats + holomap_formats))
        self.last_plot = plot
        return plot, fmt

    def _apply_post_render_hooks(self, data, obj, fmt):
        """
        Apply the post-render hooks to the data.
        """
        hooks = self.post_render_hooks.get(fmt, [])
        for hook in hooks:
            try:
                data = hook(data, obj)
            except Exception as e:
                self.param.warning("The post_render_hook %r could not "
                                   "be applied:\n\n %s" % (hook, e))
        return data

    def html(self, obj, fmt=None, css=None, resources='CDN', **kwargs):
        """
        Renders plot or data structure and wraps the output in HTML.
        The comm argument defines whether the HTML output includes
        code to initialize a Comm, if the plot supplies one.
        """
        plot, fmt = self._validate(obj, fmt)
        figdata, _ = self(plot, fmt, **kwargs)
        if css is None: css = self.css

        if isinstance(plot, Viewable):
            from bokeh.document import Document
            from bokeh.embed import file_html
            from bokeh.resources import CDN, INLINE
            doc = Document()
            plot._render_model(doc)
            if resources == 'cdn':
                resources = CDN
            elif resources == 'inline':
                resources = INLINE
            return file_html(doc, resources)
        elif fmt in ['html', 'json']:
            return figdata
        else:
            if fmt == 'svg':
                figdata = figdata.encode("utf-8")
            elif fmt == 'pdf' and 'height' not in css:
                _, h = self.get_size(plot)
                css['height'] = '%dpx' % (h * self.dpi * 1.15)

        if isinstance(css, dict):
            css = '; '.join("%s: %s" % (k, v) for k, v in css.items())
        else:
            raise ValueError("CSS must be supplied as Python dictionary")

        b64 = base64.b64encode(figdata).decode("utf-8")
        (mime_type, tag) = MIME_TYPES[fmt], HTML_TAGS[fmt]
        src = HTML_TAGS['base64'].format(mime_type=mime_type, b64=b64)
        html = tag.format(src=src, mime_type=mime_type, css=css)
        return html

    def components(self, obj, fmt=None, comm=True, **kwargs):
        """
        Returns data and metadata dictionaries containing HTML and JS
        components to include render in app, notebook, or standalone
        document.
        """
        if isinstance(obj, Plot):
            plot = obj
        else:
            plot, fmt = self._validate(obj, fmt)

        data, metadata = {}, {}
        if isinstance(plot, Viewable):
            from bokeh.document import Document
            dynamic = bool(plot.object.traverse(lambda x: x, [DynamicMap]))
            embed = (not (dynamic or self.widget_mode == 'live')
                     or config.embed)
            comm = self.comm_manager.get_server_comm() if comm else None
            doc = Document()
            with config.set(embed=embed):
                model = plot.layout._render_model(doc, comm)
            return render_model(model, comm) if embed else render_mimebundle(
                model, doc, comm)
        else:
            html = self._figure_data(plot, fmt, as_script=True, **kwargs)
        data['text/html'] = html

        return (data, {MIME_TYPES['jlab-hv-exec']: metadata})

    def static_html(self, obj, fmt=None, template=None):
        """
        Generates a static HTML with the rendered object in the
        supplied format. Allows supplying a template formatting string
        with fields to interpolate 'js', 'css' and the main 'html'.
        """
        html_bytes = StringIO()
        self.save(obj, html_bytes, fmt)
        html_bytes.seek(0)
        return html_bytes.read()

    @bothmethod
    def get_widget(self_or_cls, plot, widget_type, **kwargs):
        if widget_type == 'scrubber':
            widget_location = self_or_cls.widget_location or 'bottom'
        else:
            widget_type = 'individual'
            widget_location = self_or_cls.widget_location or 'right'

        layout = HoloViewsPane(plot,
                               widget_type=widget_type,
                               center=True,
                               widget_location=widget_location,
                               renderer=self_or_cls)
        interval = int((1. / self_or_cls.fps) * 1000)
        for player in layout.layout.select(PlayerBase):
            player.interval = interval
        return layout

    @bothmethod
    def export_widgets(self_or_cls,
                       obj,
                       filename,
                       fmt=None,
                       template=None,
                       json=False,
                       json_path='',
                       **kwargs):
        """
        Render and export object as a widget to a static HTML
        file. Allows supplying a custom template formatting string
        with fields to interpolate 'js', 'css' and the main 'html'
        containing the widget. Also provides options to export widget
        data to a json file in the supplied json_path (defaults to
        current path).
        """
        if fmt not in self_or_cls.widgets + ['auto', None]:
            raise ValueError("Renderer.export_widget may only export "
                             "registered widget types.")
        self_or_cls.get_widget(obj, fmt).save(filename)

    @bothmethod
    def _widget_kwargs(self_or_cls):
        if self_or_cls.holomap in ('auto', 'widgets'):
            widget_type = 'individual'
            loc = self_or_cls.widget_location or 'right'
        else:
            widget_type = 'scrubber'
            loc = self_or_cls.widget_location or 'bottom'
        return {
            'widget_location': loc,
            'widget_type': widget_type,
            'center': True
        }

    @bothmethod
    def app(self_or_cls,
            plot,
            show=False,
            new_window=False,
            websocket_origin=None,
            port=0):
        """
        Creates a bokeh app from a HoloViews object or plot. By
        default simply attaches the plot to bokeh's curdoc and returns
        the Document, if show option is supplied creates an
        Application instance and displays it either in a browser
        window or inline if notebook extension has been loaded.  Using
        the new_window option the app may be displayed in a new
        browser tab once the notebook extension has been loaded.  A
        websocket origin is required when launching from an existing
        tornado server (such as the notebook) and it is not on the
        default port ('localhost:8888').
        """
        if isinstance(plot, HoloViewsPane):
            pane = plot
        else:
            pane = HoloViewsPane(plot,
                                 backend=self_or_cls.backend,
                                 renderer=self_or_cls,
                                 **self_or_cls._widget_kwargs())
        if new_window:
            return pane._get_server(port, websocket_origin, show=show)
        else:
            kwargs = {
                'notebook_url': websocket_origin
            } if websocket_origin else {}
            return pane.app(port=port, **kwargs)

    @bothmethod
    def server_doc(self_or_cls, obj, doc=None):
        """
        Get a bokeh Document with the plot attached. May supply
        an existing doc, otherwise bokeh.io.curdoc() is used to
        attach the plot to the global document instance.
        """
        if not isinstance(obj, HoloViewsPane):
            obj = HoloViewsPane(obj,
                                renderer=self_or_cls,
                                backend=self_or_cls.backend,
                                **self_or_cls._widget_kwargs())
        return obj.layout.server_doc(doc)

    @classmethod
    def plotting_class(cls, obj):
        """
        Given an object or Element class, return the suitable plotting
        class needed to render it with the current renderer.
        """
        if isinstance(obj, AdjointLayout) or obj is AdjointLayout:
            obj = Layout
        if isinstance(obj, type):
            element_type = obj
        else:
            element_type = obj.type if isinstance(obj, HoloMap) else type(obj)
        try:
            plotclass = Store.registry[cls.backend][element_type]
        except KeyError:
            raise SkipRendering("No plotting class for {0} "
                                "found".format(element_type.__name__))
        return plotclass

    @classmethod
    def html_assets(cls, core=True, extras=True, backends=None, script=False):
        """
        Deprecated: No longer needed
        """
        param.main.warning("Renderer.html_assets is deprecated as all "
                           "JS and CSS dependencies are now handled by "
                           "Panel.")

    @classmethod
    def plot_options(cls, obj, percent_size):
        """
        Given an object and a percentage size (as supplied by the
        %output magic) return all the appropriate plot options that
        would be used to instantiate a plot class for that element.

        Default plot sizes at the plotting class level should be taken
        into account.
        """
        raise NotImplementedError

    @bothmethod
    def save(self_or_cls,
             obj,
             basename,
             fmt='auto',
             key={},
             info={},
             options=None,
             resources='inline',
             **kwargs):
        """
        Save a HoloViews object to file, either using an explicitly
        supplied format or to the appropriate default.
        """
        if info or key:
            raise Exception(
                'Renderer does not support saving metadata to file.')

        with StoreOptions.options(obj, options, **kwargs):
            plot, fmt = self_or_cls._validate(obj, fmt)

        if isinstance(plot, Viewable):
            from bokeh.resources import CDN, INLINE, Resources
            if isinstance(resources, Resources):
                pass
            elif resources.lower() == 'cdn':
                resources = CDN
            elif resources.lower() == 'inline':
                resources = INLINE
            plot.layout.save(basename, embed=True, resources=resources)
            return

        rendered = self_or_cls(plot, fmt)
        if rendered is None: return
        (data, info) = rendered
        encoded = self_or_cls.encode(rendered)
        prefix = self_or_cls._save_prefix(info['file-ext'])
        if prefix:
            encoded = prefix + encoded
        if isinstance(basename, (BytesIO, StringIO)):
            basename.write(encoded)
            basename.seek(0)
        else:
            filename = '%s.%s' % (basename, info['file-ext'])
            with open(filename, 'wb') as f:
                f.write(encoded)

    @bothmethod
    def _save_prefix(self_or_cls, ext):
        "Hook to prefix content for instance JS when saving HTML"
        return

    @bothmethod
    def get_size(self_or_cls, plot):
        """
        Return the display size associated with a plot before
        rendering to any particular format. Used to generate
        appropriate HTML display.

        Returns a tuple of (width, height) in pixels.
        """
        raise NotImplementedError

    @classmethod
    @contextmanager
    def state(cls):
        """
        Context manager to handle global state for a backend,
        allowing Plot classes to temporarily override that state.
        """
        yield

    @classmethod
    def validate(cls, options):
        """
        Validate an options dictionary for the renderer.
        """
        return options

    @classmethod
    def load_nb(cls, inline=True):
        """
        Loads any resources required for display of plots
        in the Jupyter notebook
        """
        load_notebook(inline)
        with param.logging_level('ERROR'):
            try:
                ip = get_ipython()  # noqa
            except:
                ip = None
            if not ip or not hasattr(ip, 'kernel'):
                return
            cls.notebook_context = True
            cls.comm_manager = JupyterCommManager
            state._comm_manager = JupyterCommManager

    @classmethod
    def _delete_plot(cls, plot_id):
        """
        Deletes registered plots and calls Plot.cleanup
        """
        plot = cls._plots.get(plot_id)
        if plot is None:
            return
        plot.cleanup()
        del cls._plots[plot_id]
Ejemplo n.º 7
0
class HttpxClient(param.Parameterized):
    _app_settings = param.Dict(default=None, allow_None=True)
    _self_serve = param.Boolean(False)
    server_url = param.String(
        default="http://localhost:5000/v1",
        regex=
        r"(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'\".,<>?«»“”‘’]))"
    )
    server_urls = param.List(default=[], class_=str)
    auth = param.ClassSelector(Oauth2DeviceFlow, default=Oauth2DeviceFlow())
    _log = param.String()
    _messages = param.List(default=[])
    _busy = param.Boolean(False)
    _client_ttl = param.Integer(600)
    _client_created = param.Number(0)
    _client = None

    @property
    def app(self):
        if not self._self_serve:
            return None
        if self.name not in APPS and self._app_settings:
            APPS[self.name] = eve.Eve(settings=self._app_settings)
        return APPS.get(self.name, None)

    @property
    def client(self):
        if self._client is None or (time.time() -
                                    self._client_created) > self._client_ttl:
            if self._client:
                self._client.close()
            self._client = httpx.Client(app=self.app, base_url=self.server_url)
            self._client_created = time.time()
        return self._client

    def get_client_kwargs(self):
        kwargs = {
            "headers": self.headers(),
            "base_url": self.server_url,
            "app": None,
        }
        if self._self_serve:
            kwargs["app"] = self._app_settings

        return kwargs

    def headers(self):
        headers = self.auth.get_headers()
        headers["Accept"] = "application/json"
        return headers

    def upload_file(self, url, _file, name=None, **kwargs):
        if isinstance(_file, BufferedIOBase):
            if name is None and hasattr(_file, "name"):
                name = _file.name.rpartition(".")[0]
            f = _file
        elif isinstance(_file, str):
            if not os.path.isfile(str):
                raise ValueError("File does not exist.")
            f = open(_file, "rb")
        else:
            raise TypeError("_file parameter must be a string or File object.")

        with httpx.Client(app=self.app, base_url=self.server_url) as client:
            try:
                httpx.post(url, data={"name": name}, files={"data": f}**kwargs)
            finally:
                f.close()

    def download_file(self, url, f):
        with httpx.Client(app=self.app, base_url=self.server_url) as client:
            with client.stream("GET", url,
                               headers=self.auth.get_headers()) as response:
                total = int(response.headers["Content-Length"])
                with tqdm(total=total,
                          unit_scale=True,
                          unit_divisor=1024,
                          unit="B") as progress:
                    num_bytes_downloaded = response.num_bytes_downloaded
                    for chunk in response.iter_bytes():
                        f.write(chunk)
                        progress.update(response.num_bytes_downloaded -
                                        num_bytes_downloaded)
                        num_bytes_downloaded = response.num_bytes_downloaded

    def get(self, url, timeout=10, **params):
        with httpx.Client(app=self.app, base_url=self.server_url) as client:
            self._busy = True
            try:
                resp = client.get(url,
                                  params=params,
                                  headers=self.headers(),
                                  timeout=timeout)
                self._busy = False
                if resp.is_error:
                    self.log_error(resp.text)
                else:
                    self.clear_messages()
                    return resp.json()
            except Exception as e:
                self.log_error(e)
            self._busy = False
            return {}

    def post(self,
             url,
             data=None,
             json=None,
             files=None,
             timeout=10,
             **kwargs):
        with httpx.Client(app=self.app, base_url=self.server_url) as client:
            self._busy = True
            headers = self.headers()
            if files is None:
                headers["Content-Type"] = "application/json"
            headers.update(kwargs.get("headers", {}))

            try:
                resp = client.post(url,
                                   data=data,
                                   json=json,
                                   headers=headers,
                                   files=files,
                                   timeout=timeout,
                                   **kwargs)
                self._busy = False
                if resp.is_error:
                    self.log_error(resp.text)
                    return False
                else:
                    self.clear_messages()
                    return True
            except Exception as e:
                self.log_error(e)
            self._busy = False

    def put(self,
            url,
            data=None,
            json=None,
            files=None,
            etag=None,
            timeout=10,
            **kwargs):
        with httpx.Client(app=self.app, base_url=self.server_url) as client:
            self._busy = True
            headers = self.headers()
            if files is None:
                headers["Content-Type"] = "application/json"
            if etag:
                headers["If-Match"] = etag
            try:
                resp = client.put(url,
                                  data=data,
                                  json=json,
                                  files=files,
                                  headers=headers,
                                  timeout=timeout,
                                  **kwargs)
                self._busy = False
                if resp.is_error:
                    self.log_error(resp.text)
                    return False
                else:
                    self.clear_messages()
                    return True
            except Exception as e:
                self.log_error(e)
        self._busy = False

    def patch(self,
              url,
              data,
              json=None,
              files=None,
              etag=None,
              timeout=10,
              **kwargs):
        with httpx.Client(app=self.app, base_url=self.server_url) as client:
            self._busy = True
            headers = self.headers()
            if files is None:
                headers["Content-Type"] = "application/json"
            if etag:
                headers["If-Match"] = etag
            try:
                resp = client.patch(url,
                                    data=data,
                                    json=json,
                                    files=files,
                                    headers=headers,
                                    timeout=timeout,
                                    **kwargs)
                self._busy = False
                if resp.is_error or settings.DEBUG:
                    self.log_error(resp.text)
                    return False
                else:
                    self.clear_messages()
                    return True
            except Exception as e:
                self.log_error(e)
            self._busy = False

    def delete(self, url, etag="", timeout=10):
        with httpx.Client(app=self.app, base_url=self.server_url) as client:
            self._busy = True
            headers = self.headers()
            if etag:
                headers["If-Match"] = etag
            try:
                resp = client.delete(url, headers=headers, timeout=timeout)
                self._busy = False
                if resp.is_error:
                    self.log_error(resp.text)
                    return False
                else:
                    self.clear_messages()
                    return True
            except Exception as e:
                self.log_error(e)
            self._busy = False

    def log_error(self, e):
        try:
            e = str(e)
            log = [e] + self._log.split("\n")
            self._log = "\n".join(log[:settings.MAX_LOG_SIZE])
            self._messages = (e.split("\n") +
                              self._messages)[:settings.MAX_MESSAGES]
        except:
            pass

    def clear_messages(self):
        self._messages = []

    def set_token(self, token):
        self.auth.set_token(token)

    def login(self, webbrowser=True):
        self.auth.login(webbrowser)

    def __getstate__(self):
        state = super().__getstate__()
        state.pop("_client", None)
        return state
Ejemplo n.º 8
0
class _config(_base_config):
    """
    Holds global configuration options for Panel. The options can be
    set directly on the global config instance, via keyword arguments
    in the extension or via environment variables. For example to set
    the embed option the following approaches can be used:

        pn.config.embed = True

        pn.extension(embed=True)

        os.environ['PANEL_EMBED'] = 'True'
    """

    apply_signatures = param.Boolean(default=True, doc="""
        Whether to set custom Signature which allows tab-completion
        in some IDEs and environments.""")

    autoreload = param.Boolean(default=False, doc="""
        Whether to autoreload server when script changes.""")

    loading_spinner = param.Selector(default='arcs', objects=[
        'arcs', 'bars', 'dots', 'petals'], doc="""
        Loading indicator to use when component loading parameter is set.""")

    loading_color = param.Color(default='#c3c3c3', doc="""
        Color of the loading indicator.""")

    safe_embed = param.Boolean(default=False, doc="""
        Ensure all bokeh property changes trigger events which are
        embedded. Useful when only partial updates are made in an
        app, e.g. when working with HoloViews.""")

    session_history = param.Integer(default=0, bounds=(-1, None), doc="""
        If set to a non-negative value this determines the maximum length
        of the pn.state.session_info dictionary, which tracks
        information about user sessions. A value of -1 indicates an
        unlimited history.""")

    sizing_mode = param.ObjectSelector(default=None, objects=[
        'fixed', 'stretch_width', 'stretch_height', 'stretch_both',
        'scale_width', 'scale_height', 'scale_both', None], doc="""
        Specify the default sizing mode behavior of panels.""")

    _comms = param.ObjectSelector(
        default='default', objects=['default', 'ipywidgets', 'vscode', 'colab'], doc="""
        Whether to render output in Jupyter with the default Jupyter
        extension or use the jupyter_bokeh ipywidget model.""")

    _console_output = param.ObjectSelector(default='accumulate', allow_None=True,
                                 objects=['accumulate', 'replace', 'disable',
                                          False], doc="""
        How to log errors and stdout output triggered by callbacks
        from Javascript in the notebook.""")

    _cookie_secret = param.String(default=None, doc="""
        Configure to enable getting/setting secure cookies.""")

    _embed = param.Boolean(default=False, allow_None=True, doc="""
        Whether plot data will be embedded.""")

    _embed_json = param.Boolean(default=False, doc="""
        Whether to save embedded state to json files.""")

    _embed_json_prefix = param.String(default='', doc="""
        Prefix for randomly generated json directories.""")

    _embed_load_path = param.String(default=None, doc="""
        Where to load json files for embedded state.""")

    _embed_save_path = param.String(default='./', doc="""
        Where to save json files for embedded state.""")

    _log_level = param.Selector(
        default=None, objects=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
        doc="Log level of Panel loggers")

    _oauth_provider = param.ObjectSelector(
        default=None, allow_None=True, objects=[], doc="""
        Select between a list of authentification providers.""")

    _oauth_key = param.String(default=None, doc="""
        A client key to provide to the OAuth provider.""")

    _oauth_secret = param.String(default=None, doc="""
        A client secret to provide to the OAuth provider.""")

    _oauth_jwt_user = param.String(default=None, doc="""
        The key in the ID JWT token to consider the user.""")

    _oauth_redirect_uri = param.String(default=None, doc="""
        A redirect URI to provide to the OAuth provider.""")

    _oauth_encryption_key = param.ClassSelector(default=None, class_=bytes, doc="""
        A random string used to encode OAuth related user information.""")

    _oauth_extra_params = param.Dict(default={}, doc="""
        Additional parameters required for OAuth provider.""")

    _inline = param.Boolean(default=_LOCAL_DEV_VERSION, allow_None=True, doc="""
        Whether to inline JS and CSS resources. If disabled, resources
        are loaded from CDN if one is available.""")

    _truthy = ['True', 'true', '1', True, 1]

    def __init__(self, **params):
        super().__init__(**params)
        for p in self.param:
            if p.startswith('_'):
                setattr(self, p+'_', None)

    @contextmanager
    def set(self, **kwargs):
        values = [(k, v) for k, v in self.param.get_param_values() if k != 'name']
        overrides = [(k, getattr(self, k+'_')) for k in self.param if k.startswith('_')]
        for k, v in kwargs.items():
            setattr(self, k, v)
        try:
            yield
        finally:
            self.param.set_param(**dict(values))
            for k, v in overrides:
                setattr(self, k+'_', v)

    @property
    def _doc_build(self):
        return os.environ.get('PANEL_DOC_BUILD')

    @property
    def console_output(self):
        if self._console_output_ is not None:
            return 'disable' if not self._console_output_ else self._console_output_
        elif self._doc_build:
            return 'disable'
        else:
            return os.environ.get('PANEL_CONSOLE_OUTPUT', _config._console_output)

    @console_output.setter
    def console_output(self, value):
        validate_config(self, '_console_output', value)
        self._console_output_ = value

    @property
    def embed(self):
        if self._embed_ is not None:
            return self._embed_
        else:
            return os.environ.get('PANEL_EMBED', _config._embed) in self._truthy

    @embed.setter
    def embed(self, value):
        validate_config(self, '_embed', value)
        self._embed_ = value

    @property
    def comms(self):
        if self._comms_ is not None:
            return self._comms_
        else:
            return os.environ.get('PANEL_COMMS', _config._comms)

    @comms.setter
    def comms(self, value):
        validate_config(self, '_comms', value)
        self._comms_ = value

    @property
    def embed_json(self):
        if self._embed_json_ is not None:
            return self._embed_json_
        else:
            return os.environ.get('PANEL_EMBED_JSON', _config._embed_json) in self._truthy

    @embed_json.setter
    def embed_json(self, value):
        validate_config(self, '_embed_json', value)
        self._embed_json_ = value

    @property
    def embed_json_prefix(self):
        if self._embed_json_prefix_ is not None:
            return self._embed_json_prefix_
        else:
            return os.environ.get('PANEL_EMBED_JSON_PREFIX', _config._embed_json_prefix)

    @embed_json_prefix.setter
    def embed_json_prefix(self, value):
        validate_config(self, '_embed_json_prefix', value)
        self._embed_json_prefix_ = value

    @property
    def embed_save_path(self):
        if self._embed_save_path_ is not None:
            return self._embed_save_path_
        else:
            return os.environ.get('PANEL_EMBED_SAVE_PATH', _config._embed_save_path)

    @embed_save_path.setter
    def embed_save_path(self, value):
        validate_config(self, '_embed_save_path', value)
        self._embed_save_path_ = value

    @property
    def embed_load_path(self):
        if self._embed_load_path_ is not None:
            return self._embed_load_path_
        else:
            return os.environ.get('PANEL_EMBED_LOAD_PATH', _config._embed_load_path)

    @embed_load_path.setter
    def embed_load_path(self, value):
        validate_config(self, '_embed_load_path', value)
        self._embed_load_path_ = value

    @property
    def inline(self):
        if self._inline_ is not None:
            return self._inline_
        else:
            return os.environ.get('PANEL_INLINE', _config._inline) in self._truthy

    @inline.setter
    def inline(self, value):
        validate_config(self, '_inline', value)
        self._inline_ = value

    @property
    def log_level(self):
        if self._log_level_ is not None:
            return self._log_level_
        elif 'PANEL_LOG_LEVEL' in os.environ:
            return os.environ['PANEL_LOG_LEVEL'].upper()
        else:
            return self._log_level

    @log_level.setter
    def log_level(self, value):
        validate_config(self, '_log_level', value)
        self._log_level_ = value

    @property
    def oauth_provider(self):
        if self._oauth_provider_ is not None:
            return self._oauth_provider_
        else:
            provider = os.environ.get('PANEL_OAUTH_PROVIDER', _config._oauth_provider)
            return provider.lower() if provider else None

    @oauth_provider.setter
    def oauth_provider(self, value):
        validate_config(self, '_oauth_provider', value.lower())
        self._oauth_provider_ = value.lower()

    @property
    def oauth_key(self):
        if self._oauth_key_ is not None:
            return self._oauth_key_
        else:
            return os.environ.get('PANEL_OAUTH_KEY', _config._oauth_key)

    @oauth_key.setter
    def oauth_key(self, value):
        validate_config(self, '_oauth_key', value)
        self._oauth_key_ = value

    @property
    def cookie_secret(self):
        if self._cookie_secret_ is not None:
            return self._cookie_secret_
        else:
            return os.environ.get(
                'PANEL_COOKIE_SECRET',
                os.environ.get('BOKEH_COOKIE_SECRET', _config._cookie_secret)
            )

    @cookie_secret.setter
    def cookie_secret(self, value):
        validate_config(self, '_cookie_secret', value)
        self._cookie_secret_ = value

    @property
    def oauth_secret(self):
        if self._oauth_secret_ is not None:
            return self._oauth_secret_
        else:
            return os.environ.get('PANEL_OAUTH_SECRET', _config._oauth_secret)

    @oauth_secret.setter
    def oauth_secret(self, value):
        validate_config(self, '_oauth_secret', value)
        self._oauth_secret_ = value

    @property
    def oauth_redirect_uri(self):
        if self._oauth_redirect_uri_ is not None:
            return self._oauth_redirect_uri_
        else:
            return os.environ.get('PANEL_OAUTH_REDIRECT_URI', _config._oauth_redirect_uri)

    @oauth_redirect_uri.setter
    def oauth_redirect_uri(self, value):
        validate_config(self, '_oauth_redirect_uri', value)
        self._oauth_redirect_uri_ = value

    @property
    def oauth_jwt_user(self):
        if self._oauth_jwt_user_ is not None:
            return self._oauth_jwt_user_
        else:
            return os.environ.get('PANEL_OAUTH_JWT_USER', _config._oauth_jwt_user)

    @oauth_jwt_user.setter
    def oauth_jwt_user(self, value):
        validate_config(self, '_oauth_jwt_user', value)
        self._oauth_jwt_user_ = value

    @property
    def oauth_encryption_key(self):
        if self._oauth_encryption_key_ is not None:
            return self._oauth_encryption_key_
        else:
            return os.environ.get('PANEL_OAUTH_ENCRYPTION', _config._oauth_encryption_key)

    @oauth_encryption_key.setter
    def oauth_encryption_key(self, value):
        validate_config(self, '_oauth_encryption_key', value)
        self._oauth_encryption_key_ = value

    @property
    def oauth_extra_params(self):
        if self._oauth_extra_params_ is not None:
            return self._oauth_extra_params_
        else:
            if 'PANEL_OAUTH_EXTRA_PARAMS' in os.environ:
                return ast.literal_eval(os.environ['PANEL_OAUTH_EXTRA_PARAMS'])
            else:
                return _config._oauth_extra_params

    @oauth_extra_params.setter
    def oauth_extra_params(self, value):
        validate_config(self, '_oauth_extra_params', value)
        self._oauth_extra_params_ = value
Ejemplo n.º 9
0
class HoloViews(PaneBase):
    """
    HoloViews panes render any HoloViews object to a corresponding
    Bokeh model while respecting the currently selected backend.
    """

    backend = param.ObjectSelector(default=None,
                                   objects=['bokeh', 'plotly', 'matplotlib'],
                                   doc="""
        The HoloViews backend used to render the plot (if None defaults
        to the currently selected renderer).""")

    center = param.Boolean(default=False,
                           doc="""
        Whether to center the plot.""")

    linked_axes = param.Boolean(default=True,
                                doc="""
        Whether to use link the axes of bokeh plots inside this pane
        across a panel layout.""")

    renderer = param.Parameter(default=None,
                               doc="""
        Explicit renderer instance to use for rendering the HoloViews
        plot. Overrides the backend.""")

    theme = param.ClassSelector(default=None,
                                class_=(Theme, str),
                                allow_None=True,
                                doc="""
        Bokeh theme to apply to the HoloViews plot.""")

    widget_location = param.ObjectSelector(default='right_top',
                                           objects=[
                                               'left', 'bottom', 'right',
                                               'top', 'top_left', 'top_right',
                                               'bottom_left', 'bottom_right',
                                               'left_top', 'left_bottom',
                                               'right_top', 'right_bottom'
                                           ],
                                           doc="""
        The layout of the plot and the widgets. The value refers to the
        position of the widgets relative to the plot.""")

    widget_layout = param.ObjectSelector(objects=[WidgetBox, Row, Column],
                                         constant=True,
                                         default=WidgetBox,
                                         doc="""
        The layout object to display the widgets in.""")

    widget_type = param.ObjectSelector(default='individual',
                                       objects=['individual', 'scrubber'],
                                       doc=""")
        Whether to generate individual widgets for each dimension or
        on global scrubber.""")

    widgets = param.Dict(default={},
                         doc="""
        A mapping from dimension name to a widget instance which will
        be used to override the default widgets.""")

    priority = 0.8

    _panes = {'bokeh': Bokeh, 'matplotlib': Matplotlib, 'plotly': Plotly}

    _rename = {
        'backend': None,
        'center': None,
        'linked_axes': None,
        'renderer': None,
        'theme': None,
        'widgets': None,
        'widget_layout': None,
        'widget_location': None,
        'widget_type': None
    }

    _rerender_params = ['object', 'backend']

    def __init__(self, object=None, **params):
        super(HoloViews, self).__init__(object, **params)
        self._initialized = False
        self._responsive_content = False
        self._restore_plot = None
        self.widget_box = self.widget_layout()
        self._widget_container = []
        self._update_widgets()
        self._plots = {}
        self.param.watch(self._update_widgets, self._rerender_params)
        self._initialized = True

    @param.depends('center', 'widget_location', watch=True)
    def _update_layout(self):
        loc = self.widget_location
        if not len(self.widget_box):
            widgets = []
        elif loc in ('left', 'right'):
            widgets = Column(VSpacer(), self.widget_box, VSpacer())
        elif loc in ('top', 'bottom'):
            widgets = Row(HSpacer(), self.widget_box, HSpacer())
        elif loc in ('top_left', 'bottom_left'):
            widgets = Row(self.widget_box, HSpacer())
        elif loc in ('top_right', 'bottom_right'):
            widgets = Row(HSpacer(), self.widget_box)
        elif loc in ('left_top', 'right_top'):
            widgets = Column(self.widget_box, VSpacer())
        elif loc in ('left_bottom', 'right_bottom'):
            widgets = Column(VSpacer(), self.widget_box)

        center = self.center and not self._responsive_content

        self._widget_container = widgets
        if not widgets:
            if center:
                components = [HSpacer(), self, HSpacer()]
            else:
                components = [self]
        elif center:
            if loc.startswith('left'):
                components = [widgets, HSpacer(), self, HSpacer()]
            elif loc.startswith('right'):
                components = [HSpacer(), self, HSpacer(), widgets]
            elif loc.startswith('top'):
                components = [
                    HSpacer(),
                    Column(widgets, Row(HSpacer(), self, HSpacer())),
                    HSpacer()
                ]
            elif loc.startswith('bottom'):
                components = [
                    HSpacer(),
                    Column(Row(HSpacer(), self, HSpacer()), widgets),
                    HSpacer()
                ]
        else:
            if loc.startswith('left'):
                components = [widgets, self]
            elif loc.startswith('right'):
                components = [self, widgets]
            elif loc.startswith('top'):
                components = [Column(widgets, self)]
            elif loc.startswith('bottom'):
                components = [Column(self, widgets)]
        self.layout[:] = components

    #----------------------------------------------------------------
    # Callback API
    #----------------------------------------------------------------

    @param.depends('theme', watch=True)
    def _update_theme(self, *events):
        if self.theme is None:
            return
        for (model, _) in self._models.values():
            if model.document:
                model.document.theme = self.theme

    @param.depends('widget_type', 'widgets', watch=True)
    def _update_widgets(self, *events):
        if self.object is None:
            widgets, values = [], []
        else:
            widgets, values = self.widgets_from_dimensions(
                self.object, self.widgets, self.widget_type)
        self._values = values

        # Clean up anything models listening to the previous widgets
        for cb in list(self._callbacks):
            if cb.inst in self.widget_box.objects:
                cb.inst.param.unwatch(cb)
                self._callbacks.remove(cb)

        # Add new widget callbacks
        for widget in widgets:
            watcher = widget.param.watch(self._widget_callback, 'value')
            self._callbacks.append(watcher)

        self.widget_box[:] = widgets
        if ((widgets and self.widget_box not in self._widget_container)
                or (not widgets and self.widget_box in self._widget_container)
                or not self._initialized):
            self._update_layout()

    def _update_plot(self, plot, pane):
        from holoviews.core.util import cross_index, wrap_tuple_streams

        widgets = self.widget_box.objects
        if not widgets:
            return
        elif self.widget_type == 'scrubber':
            key = cross_index([v for v in self._values.values()],
                              widgets[0].value)
        else:
            key = tuple(w.value for w in widgets)
            if plot.dynamic:
                widget_dims = [w.name for w in widgets]
                key = [
                    key[widget_dims.index(kdim)]
                    if kdim in widget_dims else None
                    for kdim in plot.dimensions
                ]
                key = wrap_tuple_streams(tuple(key), plot.dimensions,
                                         plot.streams)

        if plot.backend == 'bokeh':
            if plot.comm or state._unblocked(plot.document):
                with unlocked():
                    plot.update(key)
                if plot.comm and 'embedded' not in plot.root.tags:
                    plot.push()
            else:
                if plot.document.session_context:
                    plot.document.add_next_tick_callback(
                        partial(plot.update, key))
                else:
                    plot.update(key)
        else:
            plot.update(key)
            if hasattr(plot.renderer, 'get_plot_state'):
                pane.object = plot.renderer.get_plot_state(plot)
            else:
                # Compatibility with holoviews<1.13.0
                pane.object = plot.state

    def _widget_callback(self, event):
        for _, (plot, pane) in self._plots.items():
            self._update_plot(plot, pane)

    #----------------------------------------------------------------
    # Model API
    #----------------------------------------------------------------

    def _get_model(self, doc, root=None, parent=None, comm=None):
        from holoviews.plotting.plot import Plot
        if root is None:
            return self.get_root(doc, comm)
        ref = root.ref['id']
        if self.object is None:
            model = _BkSpacer()
            self._models[ref] = (model, parent)
            return model

        if self._restore_plot is not None:
            plot = self._restore_plot
            self._restore_plot = None
        elif isinstance(self.object, Plot):
            plot = self.object
        else:
            plot = self._render(doc, comm, root)

        plot.pane = self
        backend = plot.renderer.backend
        if hasattr(plot.renderer, 'get_plot_state'):
            state = plot.renderer.get_plot_state(plot)
        else:
            # Compatibility with holoviews<1.13.0
            state = plot.state

        # Ensure rerender if content is responsive but layout is centered
        if (backend == 'bokeh' and self.center
                and state.sizing_mode not in ('fixed', None)
                and not self._responsive_content):
            self._responsive_content = True
            self._update_layout()
            self._restore_plot = plot
            raise RerenderError()
        else:
            self._responsive_content = False

        kwargs = {
            p: v
            for p, v in self.param.get_param_values()
            if p in Layoutable.param and p != 'name'
        }
        child_pane = self._panes.get(backend, Pane)(state, **kwargs)
        self._update_plot(plot, child_pane)
        model = child_pane._get_model(doc, root, parent, comm)
        if ref in self._plots:
            old_plot, old_pane = self._plots[ref]
            old_plot.comm = None  # Ensures comm does not get cleaned up
            old_plot.cleanup()
        self._plots[ref] = (plot, child_pane)
        self._models[ref] = (model, parent)
        return model

    def _render(self, doc, comm, root):
        import holoviews as hv
        from holoviews import Store, renderer as load_renderer

        if self.renderer:
            renderer = self.renderer
            backend = renderer.backend
        else:
            if not Store.renderers:
                loaded_backend = (self.backend or 'bokeh')
                load_renderer(loaded_backend)
                Store.current_backend = loaded_backend
            backend = self.backend or Store.current_backend
            renderer = Store.renderers[backend]
        mode = 'server' if comm is None else 'default'
        if backend == 'bokeh':
            params = {}
            if self.theme is not None:
                params['theme'] = self.theme
            if mode != renderer.mode:
                params['mode'] = mode
            if params:
                renderer = renderer.instance(**params)

        kwargs = {'margin': self.margin}
        if backend == 'bokeh' or LooseVersion(str(
                hv.__version__)) >= str('1.13.0'):
            kwargs['doc'] = doc
            kwargs['root'] = root
            if comm:
                kwargs['comm'] = comm

        return renderer.get_plot(self.object, **kwargs)

    def _cleanup(self, root):
        """
        Traverses HoloViews object to find and clean up any streams
        connected to existing plots.
        """
        old_plot, old_pane = self._plots.pop(root.ref['id'], (None, None))
        if old_plot:
            old_plot.cleanup()
        if old_pane:
            old_pane._cleanup(root)
        super(HoloViews, self)._cleanup(root)

    #----------------------------------------------------------------
    # Public API
    #----------------------------------------------------------------

    @classmethod
    def applies(cls, obj):
        if 'holoviews' not in sys.modules:
            return False
        from holoviews.core.dimension import Dimensioned
        from holoviews.plotting.plot import Plot
        return isinstance(obj, Dimensioned) or isinstance(obj, Plot)

    @classmethod
    def widgets_from_dimensions(cls,
                                object,
                                widget_types=None,
                                widgets_type='individual'):
        from holoviews.core import Dimension, DynamicMap
        from holoviews.core.options import SkipRendering
        from holoviews.core.util import isnumeric, unicode, datetime_types, unique_iterator
        from holoviews.core.traversal import unique_dimkeys
        from holoviews.plotting.plot import Plot, GenericCompositePlot
        from holoviews.plotting.util import get_dynamic_mode
        from ..widgets import Widget, DiscreteSlider, Select, FloatSlider, DatetimeInput, IntSlider

        if widget_types is None:
            widget_types = {}

        if isinstance(object, GenericCompositePlot):
            object = object.layout
        elif isinstance(object, Plot):
            object = object.hmap

        if isinstance(object, DynamicMap) and object.unbounded:
            dims = ', '.join('%r' % dim for dim in object.unbounded)
            msg = ('DynamicMap cannot be displayed without explicit indexing '
                   'as {dims} dimension(s) are unbounded. '
                   '\nSet dimensions bounds with the DynamicMap redim.range '
                   'or redim.values methods.')
            raise SkipRendering(msg.format(dims=dims))

        dynamic, bounded = get_dynamic_mode(object)
        dims, keys = unique_dimkeys(object)
        if ((dims == [Dimension('Frame')] and keys == [(0, )])
                or (not dynamic and len(keys) == 1)):
            return [], {}

        nframes = 1
        values = dict() if dynamic else dict(zip(dims, zip(*keys)))
        dim_values = OrderedDict()
        widgets = []
        dims = [
            d for d in dims
            if values.get(d) is not None or d.values or d.range != (None, None)
        ]

        for i, dim in enumerate(dims):
            widget_type, widget, widget_kwargs = None, None, {}

            if widgets_type == 'individual':
                if i == 0 and i == (len(dims) - 1):
                    margin = (20, 20, 20, 20)
                elif i == 0:
                    margin = (20, 20, 5, 20)
                elif i == (len(dims) - 1):
                    margin = (5, 20, 20, 20)
                else:
                    margin = (0, 20, 5, 20)
                kwargs = {'margin': margin, 'width': 250}
            else:
                kwargs = {}

            vals = dim.values or values.get(dim, None)
            if vals is not None:
                vals = list(unique_iterator(vals))
            dim_values[dim.name] = vals
            if widgets_type == 'scrubber':
                if not vals:
                    raise ValueError(
                        'Scrubber widget may only be used if all dimensions define values.'
                    )
                nframes *= len(vals)
            elif dim.name in widget_types:
                widget = widget_types[dim.name]
                if isinstance(widget, Widget):
                    widget.param.set_param(**kwargs)
                    if not widget.name:
                        widget.name = dim.label
                    widgets.append(widget)
                    continue
                elif isinstance(widget, dict):
                    widget_type = widget.get('type', widget_type)
                    widget_kwargs = dict(widget)
                elif isinstance(widget, type) and issubclass(widget, Widget):
                    widget_type = widget
                else:
                    raise ValueError('Explicit widget definitions expected '
                                     'to be a widget instance or type, %s '
                                     'dimension widget declared as %s.' %
                                     (dim, widget))
            widget_kwargs.update(kwargs)

            if vals:
                if all(
                        isnumeric(v) or isinstance(v, datetime_types)
                        for v in vals) and len(vals) > 1:
                    vals = sorted(vals)
                    labels = [unicode(dim.pprint_value(v)) for v in vals]
                    options = OrderedDict(zip(labels, vals))
                    widget_type = widget_type or DiscreteSlider
                else:
                    options = list(vals)
                    widget_type = widget_type or Select
                default = vals[0] if dim.default is None else dim.default
                widget_kwargs = dict(
                    dict(name=dim.label, options=options, value=default),
                    **widget_kwargs)
                widget = widget_type(**widget_kwargs)
            elif dim.range != (None, None):
                start, end = dim.range
                if start == end:
                    continue
                default = start if dim.default is None else dim.default
                if widget_type is not None:
                    pass
                elif all(isinstance(v, int) for v in (start, end, default)):
                    widget_type = IntSlider
                    step = 1 if dim.step is None else dim.step
                elif isinstance(default, datetime_types):
                    widget_type = DatetimeInput
                else:
                    widget_type = FloatSlider
                    step = 0.1 if dim.step is None else dim.step
                widget_kwargs = dict(
                    dict(step=step,
                         name=dim.label,
                         start=dim.range[0],
                         end=dim.range[1],
                         value=default), **widget_kwargs)
                widget = widget_type(**widget_kwargs)
            if widget is not None:
                widgets.append(widget)
        if widgets_type == 'scrubber':
            widgets = [Player(length=nframes, width=550)]
        return widgets, dim_values
Ejemplo n.º 10
0
class Model(param.Parameterized):
    """Data defining this stage"""

    # Values set by previous stage
    report_from_composition = param.Dict()

    # Parameters for the current stage
    ready = param.Boolean(default=False)
    net_gross = param.Number(label="Net/Gross", softbounds=(0, 100))
    scenario_name = param.String(label="Scenario Name")
    porosity_modifier = param.Number(
        default=0, label="Porosity Modifier", bounds=(0, 100)
    )
    net_gross_modified = param.Number(label="Modified N/G", bounds=(0, 100))

    def __init__(self, report_from_composition, net_gross):
        """Set initial values based on previous stage"""
        super().__init__()
        self._state = state.get_user_state().setdefault(APP, {})
        self._state.setdefault("scenarios", {})
        self._current_scenario_name = None
        self.net_gross = net_gross
        self.report_from_composition = report_from_composition
        self.scenario_name = f"Scenario {len(self._state['scenarios']) + 1}"
        self.data = charts.data_as_dataframe(report_from_composition, CFG.columns)

        try:
            session_id = pn.state.curdoc.session_context.id
            logger.insights(f"New result: {self.net_gross}, SessionID: {session_id}")
            logger.insights(
                f"SessionID: {session_id}, Choices: {report_from_composition}"
            )
        except AttributeError as e:
            logger.error(f"SessionID not available: {e}")
            logger.insights(f"New result: {self.net_gross}")
            logger.insights(f"Choices: {report_from_composition}")

    @param.depends("net_gross", watch=True)
    def update_porosity_bounds(self):
        net_gross = dict(self.param.get_param_values())["net_gross"]
        if self.porosity_modifier > net_gross:
            self.porosity_modifier = self.net_gross
        self.param.porosity_modifier.bounds = (0, round(net_gross))

    @param.depends("net_gross", "porosity_modifier", watch=True)
    def update_ng_from_porosity(self):
        """Calculate modified net gross based on porosity"""
        if self.porosity_modifier > self.net_gross:
            self.porosity_modifier = self.net_gross

        self.net_gross_modified = self.net_gross - self.porosity_modifier

    def _store_scenario(self):
        """Store results for the current scenario"""
        scenarios = self._state["scenarios"]

        # Make sure name does not overwrite previous scenarios
        if (
            self.scenario_name != self._current_scenario_name
            and self.scenario_name in scenarios
        ):
            for suffix in itertools.count(start=2):
                name = f"{self.scenario_name} ({suffix})"
                if name not in scenarios:
                    break
            self.scenario_name = name

        # Get information about scenario
        if self._current_scenario_name in scenarios:
            scenario_info = scenarios.pop(self._current_scenario_name)
        else:
            scenario_info = {
                "net_gross": self.net_gross,
                **self.report_from_composition,
            }

        # Update scenario information
        scenario_info["net_gross_modified"] = self.net_gross_modified
        scenario_info["porosity_modifier"] = self.porosity_modifier

        # Store scenario information
        scenarios[self.scenario_name] = scenario_info
        self._current_scenario_name = self.scenario_name

    @pn.depends("scenario_name", "porosity_modifier")
    def scenario_table(self):
        """Table showing current scenarios"""
        self._store_scenario()

        scenarios = self._state["scenarios"]
        table_data = pd.DataFrame(
            {
                "Scenario Name": [s for s in scenarios.keys()],
                "Net/Gross": [round(s["net_gross"]) for s in scenarios.values()],
                "Modified N/G": [
                    round(s["net_gross_modified"]) for s in scenarios.values()
                ],
            }
        ).set_index("Scenario Name")
        return pn.pane.DataFrame(table_data, sizing_mode="stretch_width")

    def new_scenario_button(self):
        """Button for starting new scenario"""
        reset_button = self._state["update_view"]["workflow"]

        def restart_scenario(event):
            """Start a new scenario by restarting the workflow"""
            reset_button.clicks += 1

        button = pn.widgets.Button(
            name="New Scenario", button_type="success", width=125
        )
        button.on_click(restart_scenario)
        return button

    @param.output(param.List)
    def scenario_names(self):
        """Pass on names of scenarios to next stage"""
        return [sn for sn in self._state["scenarios"].keys()]
Ejemplo n.º 11
0
class Perspective(PaneBase, ReactiveData):
    """
    The Perspective widget enables exploring large tables of data.
    """

    aggregates = param.Dict(None,
                            doc="""
      How to aggregate. For example {"x": "distinct count"}""")

    columns = param.List(default=None,
                         doc="""
        A list of source columns to show as columns. For example ["x", "y"]""")

    computed_columns = param.List(default=None,
                                  doc="""
      A list of computed columns. For example [""x"+"index""]""")

    column_pivots = param.List(None,
                               doc="""
      A list of source columns to pivot by. For example ["x", "y"]""")

    filters = param.List(default=None,
                         doc="""
      How to filter. For example [["x", "<", 3],["y", "contains", "abc"]]""")

    object = param.Parameter(doc="""
      The plot data declared as a dictionary of arrays or a DataFrame.""")

    row_pivots = param.List(default=None,
                            doc="""
      A list of source columns to group by. For example ["x", "y"]""")

    selectable = param.Boolean(default=True,
                               allow_None=True,
                               doc="""
      Whether items are selectable.""")

    sort = param.List(default=None,
                      doc="""
      How to sort. For example[["x","desc"]]""")

    plugin = param.ObjectSelector(default=Plugin.GRID.value,
                                  objects=Plugin.options(),
                                  doc="""
      The name of a plugin to display the data. For example hypergrid or d3_xy_scatter."""
                                  )

    toggle_config = param.Boolean(default=True,
                                  doc="""
      Whether to show the config menu.""")

    theme = param.ObjectSelector(default=DEFAULT_THEME,
                                 objects=THEMES,
                                 doc="""
      The style of the PerspectiveViewer. For example material-dark""")

    _data_params = ['object']

    _rerender_params = ['object']

    _updates = True

    def applies(cls, object):
        if isinstance(object, dict):
            return True
        elif 'pandas' in sys.modules:
            import pandas as pd
            return isinstance(object, pd.DataFrame)
        return False

    def _get_data(self):
        if self.object is None:
            return {}, {}
        if isinstance(self.object, dict):
            ncols = len(self.object)
            data = self.object
        else:
            ncols = len(self.object.columns)
            data = ColumnDataSource.from_df(self.object)
        cols = set(self._as_digit(c) for c in self.object)
        if len(cols) != ncols:
            raise ValueError("Integer columns must be unique when "
                             "converted to strings.")
        return self.object, {str(k): v for k, v in data.items()}

    def _filter_properties(self, properties):
        ignored = list(Viewable.param)
        return [p for p in properties if p not in ignored]

    def _init_params(self):
        props = super()._init_params()
        props['source'] = ColumnDataSource(data=self._data)
        return props

    def _process_param_change(self, msg):
        msg = super()._process_param_change(msg)
        for p in ('columns', 'row_pivots', 'column_pivots'):
            if msg.get(p):
                msg[p] = [str(col) for col in msg[p]]
        if msg.get('sort'):
            msg['sort'] = [[str(col), d] for col, d in msg['sort']]
        if msg.get('filters'):
            msg['filters'] = [[str(col), e, val]
                              for col, e, val in msg['filters']]
        if msg.get('aggregates'):
            msg['aggregates'] = {
                str(col): agg
                for col, agg in msg['aggregates'].items()
            }
        return msg

    def _as_digit(self, col):
        if self._processed is None:
            return col
        elif col in self._processed:
            return col
        elif col.isdigit() and int(col) in self._processed:
            return int(col)
        return col

    def _process_property_change(self, msg):
        msg = super()._process_property_change(msg)
        for prop in ('columns', 'row_pivots', 'column_pivots'):
            if msg.get(prop):
                msg[prop] = [self._as_digit(col) for col in msg[prop]]
        if msg.get('sort'):
            msg['sort'] = [[self._as_digit(col), d] for col, d in msg['sort']]
        if msg.get('filters'):
            msg['filters'] = [[self._as_digit(col), e, val]
                              for col, e, val in msg['filters']]
        if msg.get('aggregates'):
            msg['aggregates'] = {
                self._as_digit(col): agg
                for col, agg in msg['aggregates'].items()
            }
        return msg

    def _get_model(self, doc, root=None, parent=None, comm=None):
        Perspective = lazy_load('panel.models.perspective', 'Perspective',
                                isinstance(comm, JupyterComm))
        properties = self._process_param_change(self._init_params())
        if properties.get('toggle_config'):
            properties['height'] = self.height or 300
        else:
            properties['height'] = self.height or 150
        model = Perspective(**properties)
        if root is None:
            root = model
        synced = list(
            set(self.param) ^ (set(PaneBase.param) | set(ReactiveData.param)))
        self._link_props(model, synced, doc, root, comm)
        self._models[root.ref['id']] = (model, parent)
        return model

    def _update(self, ref, model):
        pass
Ejemplo n.º 12
0
class QuickLookComponent(Component):

    data_repository = param.String(default=sample_data_directory,
                                   label=None,
                                   allow_None=True)

    query_filter = param.String(label="Query Expression")

    new_column_expr = param.String(label="Data Column Expression")

    tract_count = param.Number(default=0)

    status_message_queue = param.List(default=[])

    patch_count = param.Number(default=0)

    visit_count = param.Number(default=0)

    filter_count = param.Number(default=0)

    unique_object_count = param.Number(default=0)

    comparison = param.String()

    selected = param.Tuple(default=(None, None, None, None), length=4)

    selected_metrics_by_filter = param.Dict(
        default={f: []
                 for f in store.active_dataset.filters})

    selected_flag_filters = param.Dict(default={})

    view_mode = ['Skyplot View', 'Detail View']
    data_stack = ['Forced Coadd', 'Unforced Coadd']

    plot_top = None
    plots_list = []
    skyplot_list = []

    label = param.String(default='Quick Look')

    def __init__(self, store, **param):

        super().__init__(**param)

        self.store = store

        self._clear_metrics_button = pn.widgets.Button(name='Clear',
                                                       width=30,
                                                       align='end')
        self._clear_metrics_button.on_click(self._on_clear_metrics)

        self._submit_repository = pn.widgets.Button(name='Load Data',
                                                    width=50,
                                                    align='end')
        self._submit_repository.on_click(self._on_load_data_repository)

        self._submit_comparison = pn.widgets.Button(name='Submit',
                                                    width=50,
                                                    align='end')
        self._submit_comparison.on_click(self._update)

        self.flag_filter_select = pn.widgets.Select(
            name='Add Flag Filter',
            width=160,
            options=self.store.active_dataset.flags)

        self.flag_state_select = pn.widgets.Select(name='Flag State',
                                                   width=75,
                                                   options=['True', 'False'])

        self.flag_submit = pn.widgets.Button(name='Add Flag Filter',
                                             width=10,
                                             height=30,
                                             align='end')
        self.flag_submit.on_click(self.on_flag_submit_click)

        self.flag_filter_selected = pn.widgets.Select(
            name='Active Flag Filters', width=250)

        self.flag_remove = pn.widgets.Button(name='Remove Flag Filter',
                                             width=50,
                                             height=30,
                                             align='end')
        self.flag_remove.on_click(self.on_flag_remove_click)

        self.query_filter_submit = pn.widgets.Button(name='Run Query Filter',
                                                     width=100,
                                                     align='end')
        self.query_filter_submit.on_click(self.on_run_query_filter_click)

        self.query_filter_clear = pn.widgets.Button(name='Clear',
                                                    width=50,
                                                    align='end')
        self.query_filter_clear.on_click(self.on_query_filter_clear)

        self.new_column_submit = pn.widgets.Button(name='Define New Column',
                                                   width=100,
                                                   align='end')
        self.new_column_submit.on_click(self.on_define_new_column_click)

        self.status_message = pn.pane.HTML(sizing_mode='stretch_width',
                                           max_height=10)
        self.adhoc_js = pn.pane.HTML(sizing_mode='stretch_width',
                                     max_height=10)
        self._info = pn.pane.HTML(sizing_mode='stretch_width', max_height=10)
        self._flags = pn.pane.HTML(sizing_mode='stretch_width', max_height=10)
        self._metric_panels = []
        self._metric_layout = pn.Column()
        self._switch_view = self._create_switch_view_buttons()
        self._switch_stack = self._create_switch_datastack_buttons()
        self._plot_top = pn.Row(sizing_mode='stretch_width',
                                margin=(10, 10, 10, 10))

        self._plot_layout = pn.Column(sizing_mode='stretch_width',
                                      margin=(10, 10, 10, 10))

        self.skyplot_layout = pn.Column(sizing_mode='stretch_width',
                                        margin=(10, 10, 10, 10))

        self.list_layout = pn.Column(sizing_mode='stretch_width')

        self._update(None)

    def _on_load_data_repository(self, event):

        global datasets
        global datavisits
        global filtered_datavisits

        self.store.active_dataset = Dataset('')
        self.skyplot_list = []
        self.plots_list = []
        self.plot_top = None

        datasets = {}
        filtered_datasets = {}
        datavisits = {}
        filtered_datavisits = {}

        self._load_metrics()
        self._switch_view_mode()
        self.update_display()

        data_repo_path = self.data_repository
        self.add_status_message('Load Data Start...',
                                data_repo_path,
                                level='info')

        dstack_switch_val = self._switch_stack.value.lower()
        datastack = 'unforced' if 'unforced' in dstack_switch_val else 'forced'
        try:
            self.store.active_dataset = load_data(data_repo_path, datastack)

        except Exception as e:
            self.update_display()
            self.add_message_from_error('Data Loading Error', data_repo_path,
                                        e)
            raise

        self.add_status_message('Data Ready',
                                data_repo_path,
                                level='success',
                                duration=3)
        # update ui
        self.flag_filter_select.options = self.store.active_dataset.flags

        for f in self.store.active_dataset.filters:
            self.selected_metrics_by_filter[f] = []

        self._load_metrics()
        self._switch_view_mode()
        self.update_display()

    def update_display(self):
        self.set_checkbox_style()

    def set_checkbox_style(self):
        code = '''$("input[type='checkbox']").addClass("metric-checkbox");'''
        self.execute_js_script(code)

        global store
        for filter_type, fails in store.active_dataset.failures.items():
            error_metrics = json.dumps(fails)
            code = '$(".' + filter_type + '-checkboxes .metric-checkbox").siblings().filter(function () { return ' + error_metrics + '.indexOf($(this).text()) > -1;}).css("color", "orange");'
            self.execute_js_script(code)

    def add_status_message(self, title, body, level='info', duration=5):
        msg = {'title': title, 'body': body}
        msg_args = dict(msg=msg, level=level, duration=duration)
        self.status_message_queue.append(msg_args)
        self.param.trigger('status_message_queue')  # to work with panel 0.7
        # Drop message in terminal/logger too
        try:
            # temporary try/except until 'level' values are all checked
            getattr(logger, level)(msg)
        except:
            pass

    def on_flag_submit_click(self, event):
        flag_name = self.flag_filter_select.value
        flag_state = self.flag_state_select.value == 'True'
        self.selected_flag_filters.update({flag_name: flag_state})
        self.param.trigger('selected_flag_filters')
        self.add_status_message('Added Flag Filter',
                                '{} : {}'.format(flag_name, flag_state),
                                level='info')

    def on_flag_remove_click(self, event):
        flag_name = self.flag_filter_selected.value.split()[0]
        del self.selected_flag_filters[flag_name]
        self.param.trigger('selected_flag_filters')
        self.add_status_message('Removed Flag Filter', flag_name, level='info')

    def _on_clear_metrics(self, event):

        for k in self.selected_metrics_by_filter.keys():
            self.selected_metrics_by_filter[k] = []

        self.param.trigger('selected_metrics_by_filter')

        code = '''$("input[type='checkbox']").prop("checked", false);'''
        self.execute_js_script(code)

    def on_run_query_filter_click(self, event):
        pass

    def on_query_filter_clear(self, event):
        self.query_filter = ''
        pass

    def on_define_new_column_click(self, event):
        new_column_expr = self.new_column_expr
        logger.info("NEW COLUMN EXPRESSION: '{!s}'".format(new_column_expr))

    def _create_switch_view_buttons(self):
        radio_group = pn.widgets.RadioBoxGroup(name='SwitchView',
                                               options=self.view_mode,
                                               align='center',
                                               value=self.view_mode[0],
                                               inline=True)
        radio_group.param.watch(self._switch_view_mode, ['value'])
        return radio_group

    def _create_switch_datastack_buttons(self):
        radio_group = pn.widgets.RadioBoxGroup(name='SwitchDataStack',
                                               options=self.data_stack,
                                               align='center',
                                               value=self.data_stack[0],
                                               inline=True)
        radio_group.param.watch(self._switch_data_stack, ['value'])
        return radio_group

    def update_selected_by_filter(self, filter_type, selected_values):
        self.selected_metrics_by_filter.update({filter_type: selected_values})
        self.param.trigger('selected_metrics_by_filter')

    def _update(self, event):
        self._update_info()
        self._load_metrics()

    def create_info_element(self, name, value):
        box_css = """
        background-color: #EEEEEE;
        border: 1px solid #777777;
        display: inline-block;
        padding-left: 5px;
        padding-right: 5px;
        margin-left:7px;
        """

        fval = format(value, ',')
        outel = '<li><span style="{}"><b>{}</b> {}</span></li>'
        return outel.format(box_css, fval, name)

    @param.depends('tract_count',
                   'patch_count',
                   'visit_count',
                   'filter_count',
                   'unique_object_count',
                   watch=True)
    def _update_info(self):
        """
        Updates the _info HTML pane with info loaded
        from the current repository.
        """
        html = ''
        html += self.create_info_element('Tracts', self.tract_count)
        html += self.create_info_element('Patches', self.patch_count)
        html += self.create_info_element('Visits', self.visit_count)
        # html += self.create_info_element('Unique Objects',
        #                                  self.unique_object_count)
        self._info.object = '<ul class="list-group list-group-horizontal" style="list-style: none;">{}</ul>'.format(
            html)

    def create_status_message(self, msg, level='info', duration=5):

        import uuid
        msg_id = str(uuid.uuid1())
        color_levels = dict(info='rgba(0,191,255, .8)',
                            error='rgba(249, 180, 45, .8)',
                            warning='rgba(240, 255, 0, .8)',
                            success='rgba(3, 201, 169, .8)')

        box_css = """
        width: 15rem;
        background-color: {};
        border: 1px solid #CCCCCC;
        display: inline-block;
        color: white;
        padding: 5px;
        margin-top: 1rem;
        """.format(color_levels.get(level, 'rgba(0,0,0,0)'))

        remove_msg_func = ('<script>(function() { '
                           'setTimeout(function(){ document.getElementById("' +
                           msg_id + '").outerHTML = ""; }, ' +
                           str(duration * 1000) + ')})()'
                           '</script>')

        text = '<span style="{}"><h5>{}</h5><hr/><p>{}</p></span></li>'.format(
            box_css, msg.get('title'), msg.get('body'))

        return ('<li id="{}" class="status-message nav-item">'
                '{}'
                '{}'
                '</lil>').format(msg_id, remove_msg_func, text)

    def gen_clear_func(self, msg):
        async def clear_message():

            try:
                if msg in self.status_message_queue:
                    self.status_message_queue.remove(msg)
            except ValueError:
                pass

        return clear_message

    @param.depends('status_message_queue', watch=True)
    def _update_status_message(self):

        queue_css = """
        list-style-type: none;
        position: fixed;
        bottom: 2rem;
        right: 2rem;
        background-color: rgba(0,0,0,0);
        border: none;
        display: inline-block;
        margin-left: 7px;
        """

        html = ''

        for msg in self.status_message_queue:
            html += self.create_status_message(**msg)
            set_timeout(msg.get('duration', 5), self.gen_clear_func(msg))

        self.status_message.object = '<ul style="{}">{}</ul>'.format(
            queue_css, html)

    def execute_js_script(self, js_body):
        script = '<script>(function() { ' + js_body + '})()</script>'  # to work with panel 0.7
        self.adhoc_js.object = script

    def get_patch_count(self):
        return 1
        patchs = set()
        for filt, _ in self.selected_metrics_by_filter.items():
            dset = self.get_dataset_by_filter(filt)
            patchs = patchs.union(set(dset.df['patch'].unique()))
        return len(patchs)

    def get_tract_count(self):
        return 1
        tracts = set()
        for filt, _ in self.selected_metrics_by_filter.items():
            dset = self.get_dataset_by_filter(filt)
            tracts = tracts.union(set(dset.df['tract'].unique()))
        return len(tracts)

    def get_visit_count(self):
        return 1
        dvisits = self.get_datavisits()
        visits = set()
        for filt, metrics in self.selected_metrics_by_filter.items():
            for metric in metrics:
                df = dvisits[filt][metric].compute()
                visits = visits.union(set(df['visit'].unique()))
        return len(visits)

    def update_info_counts(self):
        self.tract_count = self.get_tract_count()
        self.patch_count = self.get_patch_count()
        self.visit_count = self.get_visit_count()
        self.unique_object_count = get_unique_object_count()

    def _load_metrics(self):
        """
        Populates the _metrics Row with metrics loaded from the repository
        """
        panels = [
            MetricPanel(metric='LSST',
                        filters=self.store.active_dataset.filters,
                        parent=self)
        ]
        self._metric_panels = panels

        self._metric_layout.objects = [p.panel() for p in panels]
        self.update_display()

    @param.depends('query_filter', watch=True)
    def _update_query_filter(self):
        self.filter_main_dataframe()

    @param.depends('selected_flag_filters', watch=True)
    def _update_selected_flags(self):
        selected_flags = [
            '{} : {}'.format(f, v)
            for f, v in self.selected_flag_filters.items()
        ]
        self.flag_filter_selected.options = selected_flags
        self.filter_main_dataframe()

    def filter_main_dataframe(self):
        global filtered_datasets
        global datasets

        for filt, qa_dataset in datasets.items():
            try:
                query_expr = self._assemble_query_expression()

                if query_expr:
                    filtered_datasets[filt] = QADataset(
                        datasets[filt].df.query(query_expr))

            except Exception as e:
                self.add_message_from_error('Filtering Error', '', e)
                raise
                return

        #self.filter_visits_dataframe()

        self._update_selected_metrics_by_filter()

    def filter_visits_dataframe(self):
        global filtered_datavisits
        global datavisits

        for filt, metrics in datavisits.items():
            for metric, df in metrics.items():

                try:
                    query_expr = self._assemble_query_expression(
                        ignore_query_expr=True)
                    if query_expr and datavisits[filt][metric] is not None:
                        filtered_datavisits[filt][metric] = datavisits[filt][
                            metric].query(query_expr)

                except Exception as e:
                    self.add_message_from_error('Filtering Visits Error', '',
                                                e)
                    return

    def _assemble_query_expression(self, ignore_query_expr=False):
        query_expr = ''

        flags_query = []
        for flag, state in self.selected_flag_filters.items():
            flags_query.append('{}=={}'.format(flag, state))
        if flags_query:
            query_expr += ' & '.join(flags_query)

        if ignore_query_expr:
            return query_expr

        query_filter = self.query_filter.strip()
        if query_filter:
            if query_expr:
                query_expr += ' & {!s}'.format(query_filter)
            else:
                query_expr = '{!s}'.format(query_filter)

        return query_expr

    def get_dataset_by_filter(self, filter_type):
        global datasets
        global filtered_datasets
        if self.query_filter == '' and len(self.selected_flag_filters) == 0:
            return datasets[filter_type]
        else:
            return filtered_datasets[filter_type]

    def get_datavisits(self):
        global datavisits
        global filtered_datavisits
        # if self.query_filter == '' and len(self.selected_flag_filters) == 0:
        if len(self.selected_flag_filters) == 0:
            return datavisits
        else:
            return filtered_datavisits

    def add_message_from_error(self,
                               title,
                               info,
                               exception_obj,
                               level='error'):

        tb = traceback.format_exception_only(type(exception_obj),
                                             exception_obj)[0]
        msg_body = '<b>Info:</b> ' + info + '<br />'
        msg_body += '<b>Cause:</b> ' + tb
        logger.error(title)
        logger.error(msg_body)
        self.add_status_message(title, msg_body, level=level, duration=10)

    @param.depends('selected_metrics_by_filter', watch=True)
    def _update_selected_metrics_by_filter(self):

        plots_list = []
        skyplot_list = []

        top_plot = None

        dvisits = self.get_datavisits()
        try:
            top_plot = visits_plot(dvisits, self.selected_metrics_by_filter)
        except Exception as e:
            self.add_message_from_error('Visits Plot Error', '', e)

        self.plot_top = top_plot

        filter_stream_scatter = FilterStream()
        for filt, plots in self.selected_metrics_by_filter.items():
            filter_stream = FilterStream()
            dset = self.get_dataset_by_filter(filt)
            for i, p in enumerate(plots):
                # skyplots
                plot_sky = skyplot(dset.ds,
                                   filter_stream=filter_stream,
                                   vdim=p)

                skyplot_list.append((filt + ' - ' + p, plot_sky))

                plots_ss = scattersky(dset.ds,
                                      xdim='psfMag',
                                      ydim=p,
                                      filter_stream=filter_stream_scatter)
                plot = plots_ss
                plots_list.append((p, plot))

        self.skyplot_list = skyplot_list
        self.plots_list = plots_list

        self.update_display()
        self._switch_view_mode()

    def linked_tab_plots(self):
        tabs = [(name, pn.panel(plot)) for name, plot in self.skyplot_list]
        return pn.Tabs(*tabs, sizing_mode='stretch_both')

    def attempt_to_clear(self, obj):
        try:
            obj.clear()
        except:
            pass

    def _switch_data_stack(self, *events):
        # clear existing plot layouts
        self.attempt_to_clear(self._plot_top)
        self.attempt_to_clear(self._plot_layout)
        self.attempt_to_clear(self.skyplot_layout)
        self.attempt_to_clear(self.list_layout)

        self._on_clear_metrics(event=None)
        self._on_load_data_repository(None)

    def _switch_view_mode(self, *events):
        # clear existing plot layouts
        self.attempt_to_clear(self._plot_top)
        self.attempt_to_clear(self._plot_layout)
        self.attempt_to_clear(self.skyplot_layout)
        self.attempt_to_clear(self.list_layout)

        if self._switch_view.value == 'Skyplot View':
            self.execute_js_script(
                '''$( ".skyplot-plot-area" ).show(); $( ".metrics-plot-area" ).hide();'''
            )
            self.skyplot_layout.append(self.linked_tab_plots())

        else:
            self.execute_js_script(
                '''$( ".skyplot-plot-area" ).hide(); $( ".metrics-plot-area" ).show();'''
            )
            logger.info(self.plot_top)
            self._plot_top.append(self.plot_top)
            for i, p in self.plots_list:
                self.list_layout.append(p)
            self._plot_layout.append(self.list_layout)

    def jinja(self):
        from ._jinja2_templates import quicklook
        import holoviews as hv
        tmpl = pn.Template(dashboard_html_template)

        data_repo_widget = pn.panel(self.param.data_repository,
                                    show_labels=False)
        data_repo_widget.width = 300
        #        data_repo_row = pn.Row(pn.panel('Data Repository', align='end'),
        #                               data_repo_widget, self._submit_repository)
        data_repo_row = pn.Row(data_repo_widget, self._submit_repository)
        data_repo_row.css_classes = ['data-repo-input']

        query_filter_widget = pn.panel(self.param.query_filter)
        query_filter_widget.width = 260

        new_column_widget = pn.panel(self.param.new_column_expr)
        new_column_widget.width = 260

        datastack_switcher = pn.Row(self._switch_stack)
        datastack_switcher.css_classes = ['stack-switcher']

        view_switcher = pn.Row(self._switch_view)
        view_switcher.css_classes = ['view-switcher']

        clear_button_row = pn.Row(self._clear_metrics_button)

        components = [
            ('metrics_clear_button', clear_button_row),
            ('data_repo_path', data_repo_row),
            ('status_message_queue', self.status_message),
            ('adhoc_js', self.adhoc_js),
            ('infobar', self._info),
            #            ('view_switcher', switcher_row),
            ('stack_switcher', datastack_switcher),
            ('view_switcher', view_switcher),
            ('metrics_selectors', self._metric_layout),
            ('metrics_plots', self._plot_layout),
            ('skyplot_metrics_plots', self.skyplot_layout),
            ('plot_top', self._plot_top),
            ('flags',
             pn.Column(pn.Row(self.flag_filter_select, self.flag_state_select),
                       pn.Row(self.flag_submit),
                       pn.Row(self.flag_filter_selected),
                       pn.Row(self.flag_remove))),
            (
                'query_filter',
                pn.Column(
                    query_filter_widget,
                    pn.Row(self.query_filter_submit, self.query_filter_clear)),
            ),
            (
                'new_column',
                pn.Column(new_column_widget, pn.Row(self.new_column_submit)),
            ),
        ]

        for l, c in components:
            tmpl.add_panel(l, c)

        return tmpl
Ejemplo n.º 13
0
class MedNumApp(TopIndicators):
    score_widget = pn.widgets.IntRangeSlider
    _layout = pn.Column()
    top_params = param.Dict(default={})

    def __init__(self, **params):
        super(MedNumApp, self).__init__(**params)
        self.catch_request()

    def catch_request(self):
        try:
            self.localisation = str(
                pn.state.session_args.get("localisation")[0].decode())
        except Exception as e:
            self.localisation = "Auch"

        try:
            self.level_1_column = str(
                pn.state.session_args.get("level_1_column")[0].decode())
        except Exception:
            self.level_1_column = "EPCI"

        try:
            self.level_0_column = str(
                pn.state.session_args.get("level_0_column")[0].decode())
        except Exception:
            self.level_0_column = "insee_com"
            pass

        try:
            self.level_0_column_names = str(
                pn.state.session_args.get("level_0_column_names")[0].decode())
        except Exception:
            self.level_0_column_names = "nom_com"
            pass

    def lat_widgets(self):
        self.score_controls = pn.Param(
            self.param.score,
            widgets={
                "score": {
                    "type": pn.widgets.IntRangeSlider,
                    "bar_color": "#000000",
                    "throttled": True,
                },
            },
        )

        score_panel = pn.Column("## Score", self.score_controls)
        point_ref_panel = pn.Column(
            "## Point de reference",
            pn.Param(
                self.param.point_ref,
                widgets={"point_ref": pn.widgets.RadioBoxGroup},
            ),
        )
        niveau_observation_panel = pn.Column(
            "## " + self.param.niveau_observation.label,
            pn.Param(
                self.param.niveau_observation,
                widgets={"niveau_observation": pn.widgets.RadioBoxGroup},
            ),
        )
        # niveau_details_panel = pn.Column(
        #     "## " + self.param.niveau_details.label,
        #     pn.Param(
        #         self.param.niveau_details,
        #         widgets={"niveau_details": pn.widgets.RadioBoxGroup},
        #     ),
        # )

        export_panel = pn.Column(
            """## Aller plus loin""",
            pn.pane.HTML("""
         <a href="https://lamednum.coop/actions/indice-de-fragilite-numerique/" title="En savoir plus sur la méthode" class="link2"> &gt; En savoir plus sur la méthode</a>
         """)
            # self.param.export_data,  # self.param.edit_report
        )

        localisation_panel = pn.Column(
            "## Localisation",
            pn.Param(
                self.param.localisation,
                widgets={
                    "localisation": {
                        "type": pn.widgets.AutocompleteInput,
                        "options": self.seachable_localisation,
                        "value": self.localisation,
                        "case_sensitive": False,
                    }
                },
            ),
            css_classes=['blc-search'])

        indicateurs = pn.Column("## Indicateurs", *self.g_params)

        ordered_panel = pn.Column(
            localisation_panel,
            score_panel,
            indicateurs,
            point_ref_panel,
            niveau_observation_panel,
            # niveau_details_panel,
            export_panel,
        )
        return ordered_panel

    @pn.depends("score", "localisation", "point_ref",
                "df_score")  # ,watch=True)
    def update_map_values(self):
        try:
            # Selection par localisation
            #  http://holoviews.org/user_guide/Plotting_with_Bokeh.html
            # https://docs.bokeh.org/en/latest/docs/user_guide/tools.html#custom-tooltip
            map_info = ["tout_axes", "nom_com"]

            vdims = (map_info +
                     [k + "_SCORE" for k in self.selected_indices_level_0] +
                     list(AXES_INDICES.keys()))

            TOOLTIPS_HTML = """<span style="font-size: 22px; font-weight: bold;"> @nom_com : @tout_axes</span>
            <div>
            """
            for k, indicators in AXES_INDICES.items():
                display_name = indicators["nom"]
                vdim_name = k
                if vdim_name in vdims:
                    TOOLTIPS_HTML += """<div class="mednum-hover">
                    <span style="font-size: 18px; font-weight: bold;">  {display_name} :</span> <span style=""> @{vdim_name}</span>
                </div>""".format(display_name=display_name,
                                 vdim_name=vdim_name)

                else:
                    TOOLTIPS_HTML += """<div class="mednum-hover">
                    <span style="font-size: 18px; font-weight: bold;">  {display_name} :</span> <span style="color:orange"> N/A </span>
                </div>""".format(display_name=display_name,
                                 vdim_name=vdim_name)

                for indic in indicators:
                    if indic not in ["desc", "nom"]:
                        display_name = CATEGORIES_INDICES[indic]
                        vdim_name = indic + "_SCORE"
                        if vdim_name in vdims:

                            TOOLTIPS_HTML += """<div class="mednum-hover">
                            <span style="font-size: 12px; ">  {display_name} :</span> <span style="">@{vdim_name}</span>
                            </div>""".format(display_name=display_name,
                                             vdim_name=vdim_name)
                        else:
                            TOOLTIPS_HTML += """<div class="mednum-hover">
                            <span style="font-size: 12px;">  {display_name} :</span> <span style="color:orange">N/A</span>
                            </div>""".format(display_name=display_name)

                TOOLTIPS_HTML += """
                </div>
                """

            hover_custom = HoverTool(tooltips=TOOLTIPS_HTML)

            self.maps = gv.Polygons(self.df_score, vdims=vdims)
            return self.maps.opts(
                tools=[hover_custom],
                color="tout_axes",
                colorbar=True,
                toolbar="above",
                xaxis=None,
                yaxis=None,
                fill_alpha=0.5,
            )

        except Exception as e:
            print(e)
            pass

    @pn.depends("localisation")  # , watch=True)
    def map_view(self):
        return self.tiles * gv.DynamicMap(self.update_map_values)

    @pn.depends("tout_axes", watch=True)
    def selection_indicateurs(self):
        for par in self.g_params:
            indic_name = next(iter(par.widgets))
            if "tout_axes" != indic_name:
                widg = par.widgets[indic_name].get("type", None)
                widg.param.select_all = self.tout_axes

    def table_view(self):
        script = """
        <script>
        if (document.readyState === "complete") {
        $('.mednum-df').DataTable();
        } else {
        $(document).ready(function () {
            $('.mednum-df').DataTable();
        })
        }
        </script>
        """
        df = self.df_score[self.selected_indices_level_0]
        df.index.names = [
            MAP_COL_WIDGETS_REV[name] if name in MAP_COL_WIDGETS_REV else name
            for name in df.index.names
        ]
        df.columns = [
            CATEGORIES_INDICES[name] if name in CATEGORIES_INDICES else name
            for name in df.columns
        ]

        html = df.to_html(classes=["mednum-df", "panel-df"])
        return pn.Column(self.download,
                         pn.pane.HTML(html + script,
                                      sizing_mode="stretch_width"),
                         sizing_mode="stretch_width")

    @pn.depends("localisation")
    def tabs_view(self):
        return pn.Tabs(
            ("Carte", self.map_view),
            ("Tableau", self.table_view),
            css_classes=[
                re.sub(r"(?<!^)(?=[A-Z])", "-",
                       self.get_name() + "Tabs").lower()
            ],
        )
Ejemplo n.º 14
0
class Compositor(param.Parameterized):
    """
    A Compositor is a way of specifying an operation to be automatically
    applied to Overlays that match a specified pattern upon display.

    Any ElementOperation that takes an Overlay as input may be used to
    define a compositor.

    For instance, a compositor may be defined to automatically display
    three overlaid monochrome matrices as an RGB image as long as the
    values names of those matrices match 'R', 'G' and 'B'.
    """

    mode = param.ObjectSelector(default='data',
                                objects=['data', 'display'],
                                doc="""
      The mode of the Compositor object which may be either 'data' or
      'display'.""")

    operation = param.Parameter(doc="""
       The ElementOperation to apply when collapsing overlays.""")

    pattern = param.String(doc="""
       The overlay pattern to be processed. An overlay pattern is a
       sequence of elements specified by dotted paths separated by * .

       For instance the following pattern specifies three overlayed
       matrices with values of 'RedChannel', 'GreenChannel' and
       'BlueChannel' respectively:

      'Image.RedChannel * Image.GreenChannel * Image.BlueChannel.

      This pattern specification could then be associated with the RGB
      operation that returns a single RGB matrix for display.""")

    group = param.String(doc="""
       The group identifier for the output of this particular compositor""")

    kwargs = param.Dict(doc="""
       Optional set of parameters to pass to the operation.""")

    operations = []  # The operations that can be used to define compositors.
    definitions = []  # The set of all the compositor instances

    @classmethod
    def strongest_match(cls, overlay, mode):
        """
        Returns the single strongest matching compositor operation
        given an overlay. If no matches are found, None is returned.

        The best match is defined as the compositor operation with the
        highest match value as returned by the match_level method.
        """
        match_strength = [(op.match_level(overlay), op)
                          for op in cls.definitions if op.mode == mode]
        matches = [(match[0], op, match[1]) for (match, op) in match_strength
                   if match is not None]
        if matches == []: return None
        else: return sorted(matches)[0]

    @classmethod
    def collapse_element(cls, overlay, key=None, ranges=None, mode='data'):
        """
        Finds any applicable compositor and applies it.
        """
        from .overlay import Overlay
        match = cls.strongest_match(overlay, mode)
        if match is None: return overlay
        (_, applicable_op, (start, stop)) = match
        values = overlay.values()
        sliced = Overlay.from_values(values[start:stop])
        result = applicable_op.apply(sliced, ranges, key=key)
        result = result.relabel(group=applicable_op.group)
        output = Overlay.from_values(values[:start] + [result] + values[stop:])
        output.id = overlay.id
        return output

    @classmethod
    def collapse(cls, holomap, ranges=None, mode='data'):
        """
        Given a map of Overlays, apply all applicable compositors.
        """
        # No potential compositors
        if cls.definitions == []:
            return holomap

        # Apply compositors
        clone = holomap.clone(shared_data=False)
        data = zip(ranges[1],
                   holomap.data.values()) if ranges else holomap.data.items()
        for key, overlay in data:
            clone[key] = cls.collapse_element(overlay, key, ranges, mode)
        return clone

    @classmethod
    def register(cls, compositor):
        defined_groups = [op.group for op in cls.definitions]
        if compositor.group in defined_groups:
            cls.definitions.pop(defined_groups.index(compositor.group))
        cls.definitions.append(compositor)
        if compositor.operation not in cls.operations:
            cls.operations.append(compositor.operation)

    def __init__(self, pattern, operation, group, mode, **kwargs):
        self._pattern_spec, labels = [], []

        for path in pattern.split('*'):
            path_tuple = tuple(el.strip() for el in path.strip().split('.'))
            self._pattern_spec.append(path_tuple)

            if len(path_tuple) == 3:
                labels.append(path_tuple[2])

        if len(labels) > 1 and not all(l == labels[0] for l in labels):
            raise KeyError(
                "Mismatched labels not allowed in compositor patterns")
        elif len(labels) == 1:
            self.label = labels[0]
        else:
            self.label = ''

        super(Compositor, self).__init__(group=group,
                                         pattern=pattern,
                                         operation=operation,
                                         mode=mode,
                                         kwargs=kwargs)

    @property
    def output_type(self):
        """
        Returns the operation output_type unless explicitly overridden
        in the kwargs.
        """
        if 'output_type' in self.kwargs:
            return self.kwargs['output_type']
        else:
            return self.operation.output_type

    def _slice_match_level(self, overlay_items):
        """
        Find the match strength for a list of overlay items that must
        be exactly the same length as the pattern specification.
        """
        level = 0
        for spec, el in zip(self._pattern_spec, overlay_items):
            if spec[0] != type(el).__name__:
                return None
            level += 1  # Types match
            if len(spec) == 1: continue

            group = [el.group, sanitize_identifier(el.group, escape=False)]
            if spec[1] in group: level += 1  # Values match
            else: return None

            if len(spec) == 3:
                group = [el.label, sanitize_identifier(el.label, escape=False)]
                if (spec[2] in group):
                    level += 1  # Labels match
                else:
                    return None
        return level

    def match_level(self, overlay):
        """
        Given an overlay, return the match level and applicable slice
        of the overall overlay. The level an integer if there is a
        match or None if there is no match.

        The level integer is the number of matching components. Higher
        values indicate a stronger match.
        """
        slice_width = len(self._pattern_spec)
        if slice_width > len(overlay): return None

        # Check all the possible slices and return the best matching one
        best_lvl, match_slice = (0, None)
        for i in range(len(overlay) - slice_width + 1):
            overlay_slice = overlay.values()[i:i + slice_width]
            lvl = self._slice_match_level(overlay_slice)
            if lvl is None: continue
            if lvl > best_lvl:
                best_lvl = lvl
                match_slice = (i, i + slice_width)

        return (best_lvl, match_slice) if best_lvl != 0 else None

    def apply(self, value, input_ranges, key=None):
        """
        Apply the compositor on the input with the given input ranges.
        """
        from .overlay import CompositeOverlay
        if isinstance(value, CompositeOverlay) and len(value) == 1:
            value = value.values()[0]
        if key is None:
            return self.operation(value,
                                  input_ranges=input_ranges,
                                  **self.kwargs)
        return self.operation.instance(input_ranges=input_ranges,
                                       **self.kwargs).process_element(
                                           value, key)
Ejemplo n.º 15
0
class Dynamic(param.ParameterizedFunction):
    """
    Dynamically applies a callable to the Elements in any HoloViews
    object. Will return a DynamicMap wrapping the original map object,
    which will lazily evaluate when a key is requested. By default
    Dynamic applies a no-op, making it useful for converting HoloMaps
    to a DynamicMap.

    Any supplied kwargs will be passed to the callable and any streams
    will be instantiated on the returned DynamicMap. If the supplied
    operation is a method on a parameterized object which was
    decorated with parameter dependencies Dynamic will automatically
    create a stream to watch the parameter changes. This default
    behavior may be disabled by setting watch=False.
    """

    operation = param.Callable(default=lambda x: x,
                               doc="""
        Operation or user-defined callable to apply dynamically""")

    kwargs = param.Dict(default={},
                        doc="""
        Keyword arguments passed to the function.""")

    link_inputs = param.Boolean(default=True,
                                doc="""
         If Dynamic is applied to another DynamicMap, determines whether
         linked streams attached to its Callable inputs are
         transferred to the output of the utility.

         For example if the Dynamic utility is applied to a DynamicMap
         with an RangeXY, this switch determines whether the
         corresponding visualization should update this stream with
         range changes originating from the newly generated axes.""")

    shared_data = param.Boolean(default=False,
                                doc="""
        Whether the cloned DynamicMap will share the same cache.""")

    streams = param.List(default=[],
                         doc="""
        List of streams to attach to the returned DynamicMap""")

    def __call__(self, map_obj, **params):
        watch = params.pop('watch', True)
        self.p = param.ParamOverrides(self, params)
        callback = self._dynamic_operation(map_obj)
        streams = self._get_streams(map_obj, watch)
        if isinstance(map_obj, DynamicMap):
            dmap = map_obj.clone(callback=callback,
                                 shared_data=self.p.shared_data,
                                 streams=streams)
            if self.p.shared_data:
                dmap.data = OrderedDict([(k, callback.callable(*k))
                                         for k, v in dmap.data])
        else:
            dmap = self._make_dynamic(map_obj, callback, streams)
        return dmap

    def _get_streams(self, map_obj, watch=True):
        """
        Generates a list of streams to attach to the returned DynamicMap.
        If the input is a DynamicMap any streams that are supplying values
        for the key dimension of the input are inherited. And the list
        of supplied stream classes and instances are processed and
        added to the list.
        """
        streams = []
        op = self.p.operation
        for stream in self.p.streams:
            if inspect.isclass(stream) and issubclass(stream, Stream):
                stream = stream()
            elif not (isinstance(stream, Stream)
                      or util.is_param_method(stream)):
                raise ValueError(
                    'Streams must be Stream classes or instances, found %s type'
                    % type(stream).__name__)
            if isinstance(op, Operation):
                updates = {
                    k: op.p.get(k)
                    for k, v in stream.contents.items()
                    if v is None and k in op.p
                }
                if updates:
                    reverse = {v: k for k, v in stream._rename.items()}
                    stream.update(
                        **{reverse.get(k, k): v
                           for k, v in updates.items()})
            streams.append(stream)

        params = {}
        for k, v in self.p.kwargs.items():
            if 'panel' in sys.modules:
                from panel.widgets.base import Widget
                if isinstance(v, Widget):
                    v = v.param.value
            if isinstance(v, param.Parameter) and isinstance(
                    v.owner, param.Parameterized):
                params[k] = v
        streams += Params.from_params(params)

        # Inherit dimensioned streams
        if isinstance(map_obj, DynamicMap):
            dim_streams = util.dimensioned_streams(map_obj)
            streams = list(util.unique_iterator(streams + dim_streams))

        # If callback is a parameterized method and watch is disabled add as stream
        has_dependencies = (util.is_param_method(op, has_deps=True)
                            or isinstance(op, FunctionType)
                            and hasattr(op, '_dinfo'))
        if has_dependencies and watch:
            streams.append(op)

        # Add any keyword arguments which are parameterized methods
        # with dependencies as streams
        for value in self.p.kwargs.values():
            if util.is_param_method(value, has_deps=True):
                streams.append(value)
            elif isinstance(value, FunctionType) and hasattr(value, '_dinfo'):
                dependencies = list(value._dinfo.get('dependencies', []))
                dependencies += list(value._dinfo.get('kw', {}).values())
                params = [
                    d for d in dependencies if isinstance(d, param.Parameter)
                    and isinstance(d.owner, param.Parameterized)
                ]
                streams.append(Params(parameters=params, watch_only=True))

        valid, invalid = Stream._process_streams(streams)
        if invalid:
            msg = ('The supplied streams list contains objects that '
                   'are not Stream instances: {objs}')
            raise TypeError(
                msg.format(objs=', '.join('%r' % el for el in invalid)))
        return valid

    def _process(self, element, key=None, kwargs={}):
        if util.is_param_method(self.p.operation) and util.get_method_owner(
                self.p.operation) is element:
            return self.p.operation(**kwargs)
        elif isinstance(self.p.operation, Operation):
            kwargs = {
                k: v
                for k, v in kwargs.items() if k in self.p.operation.param
            }
            return self.p.operation.process_element(element, key, **kwargs)
        else:
            return self.p.operation(element, **kwargs)

    def _dynamic_operation(self, map_obj):
        """
        Generate function to dynamically apply the operation.
        Wraps an existing HoloMap or DynamicMap.
        """
        def resolve(key, kwargs):
            if not isinstance(map_obj, HoloMap):
                return key, map_obj
            elif isinstance(map_obj,
                            DynamicMap) and map_obj._posarg_keys and not key:
                key = tuple(kwargs[k] for k in map_obj._posarg_keys)
            return key, map_obj[key]

        def apply(element, *key, **kwargs):
            kwargs = dict(util.resolve_dependent_kwargs(self.p.kwargs),
                          **kwargs)
            return self._process(element, key, kwargs)

        def dynamic_operation(*key, **kwargs):
            key, obj = resolve(key, kwargs)
            return apply(obj, *key, **kwargs)

        operation = self.p.operation
        if not isinstance(operation, Operation):
            operation = function.instance(fn=apply)
        return OperationCallable(dynamic_operation,
                                 inputs=[map_obj],
                                 link_inputs=self.p.link_inputs,
                                 operation=operation,
                                 operation_kwargs=self.p.kwargs)

    def _make_dynamic(self, hmap, dynamic_fn, streams):
        """
        Accepts a HoloMap and a dynamic callback function creating
        an equivalent DynamicMap from the HoloMap.
        """
        if isinstance(hmap, ViewableElement):
            return DynamicMap(dynamic_fn, streams=streams)
        dim_values = zip(*hmap.data.keys())
        params = util.get_param_values(hmap)
        kdims = [
            d.clone(values=list(util.unique_iterator(values)))
            for d, values in zip(hmap.kdims, dim_values)
        ]
        return DynamicMap(dynamic_fn,
                          streams=streams,
                          **dict(params, kdims=kdims))
Ejemplo n.º 16
0
class VTKVolume(AbstractVTK):

    ambient = param.Number(default=0.2,
                           step=1e-2,
                           doc="""
        Value to control the ambient lighting. It is the light an
        object gives even in the absence of strong light. It is
        constant in all directions.""")

    controller_expanded = param.Boolean(default=True,
                                        doc="""
        If True the volume controller panel options is expanded in the view""")

    colormap = param.Selector(default='erdc_rainbow_bright',
                              objects=PRESET_CMAPS,
                              doc="""
        Name of the colormap used to transform pixel value in color.""")

    diffuse = param.Number(default=0.7,
                           step=1e-2,
                           doc="""
        Value to control the diffuse Lighting. It relies on both the
        light direction and the object surface normal.""")

    display_volume = param.Boolean(default=True,
                                   doc="""
        If set to True, the 3D respresentation of the volume is
        displayed using ray casting.""")

    display_slices = param.Boolean(default=False,
                                   doc="""
        If set to true, the orthgonal slices in the three (X, Y, Z)
        directions are displayed. Position of each slice can be
        controlled using slice_(i,j,k) parameters.""")

    edge_gradient = param.Number(default=0.4,
                                 bounds=(0, 1),
                                 step=1e-2,
                                 doc="""
        Parameter to adjust the opacity of the volume based on the
        gradient between voxels.""")

    interpolation = param.Selector(
        default='fast_linear',
        objects=['fast_linear', 'linear', 'nearest'],
        doc="""
        interpolation type for sampling a volume. `nearest`
        interpolation will snap to the closest voxel, `linear` will
        perform trilinear interpolation to compute a scalar value from
        surrounding voxels.  `fast_linear` under WebGL 1 will perform
        bilinear interpolation on X and Y but use nearest for Z. This
        is slightly faster than full linear at the cost of no Z axis
        linear interpolation.""")

    mapper = param.Dict(doc="Lookup Table in format {low, high, palette}")

    max_data_size = param.Number(default=(256**3) * 2 / 1e6,
                                 doc="""
        Maximum data size transfert allowed without subsampling""")

    origin = param.Tuple(default=None, length=3, allow_None=True)

    render_background = param.Color(default='#52576e',
                                    doc="""
        Allows to specify the background color of the 3D rendering.
        The value must be specified as an hexadecimal color string.""")

    rescale = param.Boolean(default=False,
                            doc="""
        If set to True the colormap is rescaled beween min and max
        value of the non-transparent pixel, otherwise  the full range
        of the pixel values are used.""")

    shadow = param.Boolean(default=True,
                           doc="""
        If set to False, then the mapper for the volume will not
        perform shading computations, it is the same as setting
        ambient=1, diffuse=0, specular=0.""")

    sampling = param.Number(default=0.4,
                            bounds=(0, 1),
                            step=1e-2,
                            doc="""
        Parameter to adjust the distance between samples used for
        rendering. The lower the value is the more precise is the
        representation but it is more computationally intensive.""")

    spacing = param.Tuple(default=(1, 1, 1),
                          length=3,
                          doc="""
        Distance between voxel in each direction""")

    specular = param.Number(default=0.3,
                            step=1e-2,
                            doc="""
        Value to control specular lighting. It is the light reflects
        back toward the camera when hitting the object.""")

    specular_power = param.Number(default=8.,
                                  doc="""
        Specular power refers to how much light is reflected in a
        mirror like fashion, rather than scattered randomly in a
        diffuse manner.""")

    slice_i = param.Integer(per_instance=True,
                            doc="""
        Integer parameter to control the position of the slice normal
        to the X direction.""")

    slice_j = param.Integer(per_instance=True,
                            doc="""
        Integer parameter to control the position of the slice normal
        to the Y direction.""")

    slice_k = param.Integer(per_instance=True,
                            doc="""
        Integer parameter to control the position of the slice normal
        to the Z direction.""")

    _serializers = {}

    _rename = {'max_data_size': None, 'spacing': None, 'origin': None}

    _updates = True

    def __init__(self, object=None, **params):
        super(VTKVolume, self).__init__(object, **params)
        self._sub_spacing = self.spacing
        self._update()

    @classmethod
    def applies(cls, obj):
        if ((isinstance(obj, np.ndarray) and obj.ndim == 3)
                or any([isinstance(obj, k) for k in cls._serializers.keys()])):
            return True
        elif 'vtk' not in sys.modules:
            return False
        else:
            import vtk
            return isinstance(obj, vtk.vtkImageData)

    def _get_model(self, doc, root=None, parent=None, comm=None):
        """
        Should return the bokeh model to be rendered.
        """
        if 'panel.models.vtk' not in sys.modules:
            if isinstance(comm, JupyterComm):
                self.param.warning(
                    'VTKVolumePlot was not imported on instantiation '
                    'and may not render in a notebook. Restart '
                    'the notebook kernel and ensure you load '
                    'it as part of the extension using:'
                    '\n\npn.extension(\'vtk\')\n')
            from ...models.vtk import VTKVolumePlot
        else:
            VTKVolumePlot = getattr(sys.modules['panel.models.vtk'],
                                    'VTKVolumePlot')

        props = self._process_param_change(self._init_properties())
        volume_data = self._volume_data

        model = VTKVolumePlot(data=volume_data, **props)
        if root is None:
            root = model
        self._link_props(model, [
            'colormap', 'orientation_widget', 'camera', 'mapper',
            'controller_expanded'
        ], doc, root, comm)
        self._models[root.ref['id']] = (model, parent)
        return model

    def _update_object(self, ref, doc, root, parent, comm):
        self._legend = None
        super(VTKVolume, self)._update_object(ref, doc, root, parent, comm)

    def _init_properties(self):
        return {
            k: v
            for k, v in self.param.get_param_values()
            if v is not None and k not in
            ['default_layout', 'object', 'max_data_size', 'spacing', 'origin']
        }

    def _get_object_dimensions(self):
        if isinstance(self.object, np.ndarray):
            return self.object.shape
        else:
            return self.object.GetDimensions()

    def _process_param_change(self, msg):
        msg = super(VTKVolume, self)._process_param_change(msg)
        if self.object is not None:
            slice_params = {'slice_i': 0, 'slice_j': 1, 'slice_k': 2}
            for k, v in msg.items():
                sub_dim = self._subsample_dimensions
                ori_dim = self._orginal_dimensions
                if k in slice_params:
                    index = slice_params[k]
                    msg[k] = int(np.round(v * sub_dim[index] / ori_dim[index]))
        return msg

    def _process_property_change(self, msg):
        msg = super(VTKVolume, self)._process_property_change(msg)
        if self.object is not None:
            slice_params = {'slice_i': 0, 'slice_j': 1, 'slice_k': 2}
            for k, v in msg.items():
                sub_dim = self._subsample_dimensions
                ori_dim = self._orginal_dimensions
                if k in slice_params:
                    index = slice_params[k]
                    msg[k] = int(np.round(v * ori_dim[index] / sub_dim[index]))
        return msg

    def _update(self, ref=None, model=None):
        self._volume_data = self._get_volume_data()
        if self._volume_data is not None:
            self._orginal_dimensions = self._get_object_dimensions()
            self._subsample_dimensions = self._volume_data['dims']
            self.param.slice_i.bounds = (0, self._orginal_dimensions[0] - 1)
            self.slice_i = (self._orginal_dimensions[0] - 1) // 2
            self.param.slice_j.bounds = (0, self._orginal_dimensions[1] - 1)
            self.slice_j = (self._orginal_dimensions[1] - 1) // 2
            self.param.slice_k.bounds = (0, self._orginal_dimensions[2] - 1)
            self.slice_k = (self._orginal_dimensions[2] - 1) // 2
        if model is not None:
            model.data = self._volume_data

    @classmethod
    def register_serializer(cls, class_type, serializer):
        """
        Register a seriliazer for a given type of class.
        A serializer is a function which take an instance of `class_type`
        (like a vtk.vtkImageData) as input and return a numpy array of the data
        """
        cls._serializers.update({class_type: serializer})

    def _volume_from_array(self, sub_array):
        return dict(
            buffer=base64encode(
                sub_array.ravel(
                    order='F' if sub_array.flags['F_CONTIGUOUS'] else 'C')),
            dims=sub_array.shape
            if sub_array.flags['F_CONTIGUOUS'] else sub_array.shape[::-1],
            spacing=self._sub_spacing
            if sub_array.flags['F_CONTIGUOUS'] else self._sub_spacing[::-1],
            origin=self.origin,
            data_range=(sub_array.min(), sub_array.max()),
            dtype=sub_array.dtype.name)

    def _get_volume_data(self):
        if self.object is None:
            return None
        elif isinstance(self.object, np.ndarray):
            return self._volume_from_array(self._subsample_array(self.object))
        else:
            available_serializer = [
                v for k, v in VTKVolume._serializers.items()
                if isinstance(self.object, k)
            ]
            if not available_serializer:
                import vtk
                from vtk.util import numpy_support

                def volume_serializer(inst):
                    imageData = inst.object
                    array = numpy_support.vtk_to_numpy(
                        imageData.GetPointData().GetScalars())
                    dims = imageData.GetDimensions()[::-1]
                    inst.spacing = imageData.GetSpacing()[::-1]
                    inst.origin = imageData.GetOrigin()
                    return inst._volume_from_array(
                        inst._subsample_array(array.reshape(dims, order='C')))

                VTKVolume.register_serializer(vtk.vtkImageData,
                                              volume_serializer)
                serializer = volume_serializer
            else:
                serializer = available_serializer[0]
            return serializer(self)

    def _subsample_array(self, array):
        original_shape = array.shape
        spacing = self.spacing
        extent = tuple(
            (o_s - 1) * s for o_s, s in zip(original_shape, spacing))
        dim_ratio = np.cbrt((array.nbytes / 1e6) / self.max_data_size)
        max_shape = tuple(int(o_s / dim_ratio) for o_s in original_shape)
        dowsnscale_factor = [
            max(o_s, m_s) / m_s for m_s, o_s in zip(max_shape, original_shape)
        ]

        if any([d_f > 1 for d_f in dowsnscale_factor]):
            try:
                import scipy.ndimage as nd
                sub_array = nd.interpolation.zoom(
                    array,
                    zoom=[1 / d_f for d_f in dowsnscale_factor],
                    order=0)
            except ImportError:
                sub_array = array[::int(np.ceil(dowsnscale_factor[0])), ::int(
                    np.ceil(dowsnscale_factor[1])
                ), ::int(np.ceil(dowsnscale_factor[2]))]
            self._sub_spacing = tuple(e / (s - 1)
                                      for e, s in zip(extent, sub_array.shape))
        else:
            sub_array = array
            self._sub_spacing = self.spacing
        return sub_array
Ejemplo n.º 17
0
class _config(param.Parameterized):
    """
    Holds global configuration options for Panel. The options can be
    set directly on the global config instance, via keyword arguments
    in the extension or via environment variables. For example to set
    the embed option the following approaches can be used:

        pn.config.embed = True

        pn.extension(embed=True)

        os.environ['PANEL_EMBED'] = 'True'
    """

    apply_signatures = param.Boolean(default=True,
                                     doc="""
        Whether to set custom Signature which allows tab-completion
        in some IDEs and environments.""")

    css_files = param.List(default=_CSS_FILES,
                           doc="""
        External CSS files to load.""")

    js_files = param.Dict(default={},
                          doc="""
        External JS files to load. Dictionary should map from exported
        name to the URL of the JS file.""")

    raw_css = param.List(default=[],
                         doc="""
        List of raw CSS strings to add to load.""")

    safe_embed = param.Boolean(default=False,
                               doc="""
        Ensure all bokeh property changes trigger events which are
        embedded. Useful when only partial updates are made in an
        app, e.g. when working with HoloViews.""")

    sizing_mode = param.ObjectSelector(default=None,
                                       objects=[
                                           'fixed', 'stretch_width',
                                           'stretch_height', 'stretch_both',
                                           'scale_width', 'scale_height',
                                           'scale_both', None
                                       ],
                                       doc="""
        Specify the default sizing mode behavior of panels.""")

    _comms = param.ObjectSelector(default='default',
                                  objects=['default', 'ipywidgets'],
                                  doc="""
        Whether to render output in Jupyter with the default Jupyter
        extension or use the jupyter_bokeh ipywidget model.""")

    _console_output = param.ObjectSelector(
        default='accumulate',
        allow_None=True,
        objects=['accumulate', 'replace', 'disable', False],
        doc="""
        How to log errors and stdout output triggered by callbacks
        from Javascript in the notebook.""")

    _embed = param.Boolean(default=False,
                           allow_None=True,
                           doc="""
        Whether plot data will be embedded.""")

    _embed_json = param.Boolean(default=False,
                                doc="""
        Whether to save embedded state to json files.""")

    _embed_json_prefix = param.String(default='',
                                      doc="""
        Prefix for randomly generated json directories.""")

    _embed_load_path = param.String(default=None,
                                    doc="""
        Where to load json files for embedded state.""")

    _embed_save_path = param.String(default='./',
                                    doc="""
        Where to save json files for embedded state.""")

    _inline = param.Boolean(default=True,
                            allow_None=True,
                            doc="""
        Whether to inline JS and CSS resources. If disabled, resources
        are loaded from CDN if one is available.""")

    _truthy = ['True', 'true', '1', True, 1]

    def __init__(self, **params):
        super(_config, self).__init__(**params)
        for p in self.param:
            if p.startswith('_'):
                setattr(self, p + '_', None)

    @contextmanager
    def set(self, **kwargs):
        values = [(k, v) for k, v in self.param.get_param_values()
                  if k != 'name']
        overrides = [(k, getattr(self, k + '_')) for k in self.param
                     if k.startswith('_')]
        for k, v in kwargs.items():
            setattr(self, k, v)
        try:
            yield
        finally:
            self.param.set_param(**dict(values))
            for k, v in overrides:
                setattr(self, k + '_', v)

    @property
    def _doc_build(self):
        return os.environ.get('PANEL_DOC_BUILD')

    @property
    def console_output(self):
        if self._console_output_ is not None:
            return 'disable' if not self._console_output_ else self._console_output_
        elif self._doc_build:
            return 'disable'
        else:
            return os.environ.get('PANEL_CONSOLE_OUTPUT',
                                  _config._console_output)

    @console_output.setter
    def console_output(self, value):
        validate_config(self, '_console_output', value)
        self._console_output_ = value

    @property
    def embed(self):
        if self._embed_ is not None:
            return self._embed_
        else:
            return os.environ.get('PANEL_EMBED',
                                  _config._embed) in self._truthy

    @embed.setter
    def embed(self, value):
        validate_config(self, '_embed', value)
        self._embed_ = value

    @property
    def comms(self):
        if self._comms_ is not None:
            return self._comms_
        else:
            return os.environ.get('PANEL_COMMS', _config._comms)

    @comms.setter
    def comms(self, value):
        validate_config(self, '_comms', value)
        self._comms_ = value

    @property
    def embed_json(self):
        if self._embed_json_ is not None:
            return self._embed_json_
        else:
            return os.environ.get('PANEL_EMBED_JSON',
                                  _config._embed_json) in self._truthy

    @embed_json.setter
    def embed_json(self, value):
        validate_config(self, '_embed_json', value)
        self._embed_json_ = value

    @property
    def embed_json_prefix(self):
        if self._embed_json_prefix_ is not None:
            return self._embed_json_prefix_
        else:
            return os.environ.get('PANEL_EMBED_JSON_PREFIX',
                                  _config._embed_json_prefix)

    @embed_json_prefix.setter
    def embed_json_prefix(self, value):
        validate_config(self, '_embed_json_prefix', value)
        self._embed_json_prefix_ = value

    @property
    def embed_save_path(self):
        if self._embed_save_path_ is not None:
            return self._embed_save_path_
        else:
            return os.environ.get('PANEL_EMBED_SAVE_PATH',
                                  _config._embed_save_path)

    @embed_save_path.setter
    def embed_save_path(self, value):
        validate_config(self, '_embed_save_path', value)
        self._embed_save_path_ = value

    @property
    def embed_load_path(self):
        if self._embed_load_path_ is not None:
            return self._embed_load_path_
        else:
            return os.environ.get('PANEL_EMBED_LOAD_PATH',
                                  _config._embed_load_path)

    @embed_load_path.setter
    def embed_load_path(self, value):
        validate_config(self, '_embed_load_path', value)
        self._embed_load_path_ = value

    @property
    def inline(self):
        if self._inline_ is not None:
            return self._inline_
        else:
            return os.environ.get('PANEL_INLINE',
                                  _config._inline) in self._truthy

    @inline.setter
    def inline(self, value):
        validate_config(self, '_inline', value)
        self._inline_ = value
Ejemplo n.º 18
0
class RasterPlot(ColorbarPlot):

    aspect = param.Parameter(default='equal',
                             doc="""
        Raster elements respect the aspect ratio of the
        Images by default but may be set to an explicit
        aspect ratio or to 'square'.""")

    clipping_colors = param.Dict(default={'NaN': 'transparent'})

    colorbar = param.Boolean(default=False,
                             doc="""
        Whether to add a colorbar to the plot.""")

    show_legend = param.Boolean(default=False,
                                doc="""
        Whether to show legend for the plot.""")

    situate_axes = param.Boolean(default=True,
                                 doc="""
        Whether to situate the image relative to other plots. """)

    style_opts = [
        'alpha', 'cmap', 'interpolation', 'visible', 'filterrad', 'clims',
        'norm'
    ]

    _plot_methods = dict(single='imshow')

    def __init__(self, *args, **kwargs):
        super(RasterPlot, self).__init__(*args, **kwargs)
        if self.hmap.type == Raster:
            self.invert_yaxis = not self.invert_yaxis

    def get_extents(self, element, ranges, range_type='combined'):
        extents = super(RasterPlot, self).get_extents(element, ranges,
                                                      range_type)
        if self.situate_axes or range_type not in ('combined', 'data'):
            return extents
        else:
            if isinstance(element, Image):
                return element.bounds.lbrt()
            else:
                return element.extents

    def _compute_ticks(self, element, ranges):
        return None, None

    def get_data(self, element, ranges, style):
        xticks, yticks = self._compute_ticks(element, ranges)

        if isinstance(element, RGB):
            style.pop('cmap', None)

        data = get_raster_array(element)
        if type(element) is Raster:
            l, b, r, t = element.extents
            if self.invert_axes:
                data = data[:, ::-1]
            else:
                data = data[::-1]
        else:
            l, b, r, t = element.bounds.lbrt()
            if self.invert_axes:
                data = data[::-1, ::-1]

        if self.invert_axes:
            data = data.transpose([1, 0, 2]) if isinstance(element,
                                                           RGB) else data.T
            l, b, r, t = b, l, t, r

        vdim = element.vdims[0]
        self._norm_kwargs(element, ranges, style, vdim)
        style['extent'] = [l, r, b, t]
        style['origin'] = 'upper'

        return [data], style, {'xticks': xticks, 'yticks': yticks}

    def update_handles(self, key, axis, element, ranges, style):
        im = self.handles['artist']
        data, style, axis_kwargs = self.get_data(element, ranges, style)
        l, r, b, t = style['extent']
        im.set_data(data[0])
        im.set_extent((l, r, b, t))
        im.set_clim((style['vmin'], style['vmax']))
        if 'norm' in style:
            im.norm = style['norm']

        return axis_kwargs
Ejemplo n.º 19
0
class MPLPlot(DimensionedPlot):
    """
    An MPLPlot object draws a matplotlib figure object when called or
    indexed but can also return a matplotlib animation object as
    appropriate. MPLPlots take element objects such as Image, Contours
    or Points as inputs and plots them in the appropriate format using
    matplotlib. As HoloMaps are supported, all plots support animation
    via the anim() method.
    """

    renderer = MPLRenderer
    sideplots = {}

    fig_alpha = param.Number(default=1.0,
                             bounds=(0, 1),
                             doc="""
        Alpha of the overall figure background.""")

    fig_bounds = param.NumericTuple(default=(0.15, 0.15, 0.85, 0.85),
                                    doc="""
        The bounds of the overall figure as a 4-tuple of the form
        (left, bottom, right, top), defining the size of the border
        around the subplots.""")

    fig_inches = param.Parameter(default=4,
                                 doc="""
        The overall matplotlib figure size in inches.  May be set as
        an integer in which case it will be used to autocompute a
        size. Alternatively may be set with an explicit tuple or list,
        in which case it will be applied directly after being scaled
        by fig_size. If either the width or height is set to None,
        it will be computed automatically.""")

    fig_latex = param.Boolean(default=False,
                              doc="""
        Whether to use LaTeX text in the overall figure.""")

    fig_rcparams = param.Dict(default={},
                              doc="""
        matplotlib rc parameters to apply to the overall figure.""")

    fig_size = param.Integer(default=100,
                             bounds=(1, None),
                             doc="""
        Size relative to the supplied overall fig_inches in percent.""")

    finalize_hooks = param.HookList(default=[],
                                    doc="""
        Optional list of hooks called when finalizing an axis.
        The hook is passed the full set of plot handles and the
        displayed object.""")

    sublabel_format = param.String(default=None,
                                   allow_None=True,
                                   doc="""
        Allows labeling the subaxes in each plot with various formatters
        including {Alpha}, {alpha}, {numeric} and {roman}.""")

    sublabel_position = param.NumericTuple(default=(-0.35, 0.85),
                                           doc="""
         Position relative to the plot for placing the optional subfigure label."""
                                           )

    sublabel_size = param.Number(default=18,
                                 doc="""
         Size of optional subfigure label.""")

    projection = param.ObjectSelector(default=None,
                                      objects=['3d', 'polar', None],
                                      doc="""
        The projection of the plot axis, default of None is equivalent to
        2D plot, 3D and polar plots are also supported.""")

    show_frame = param.Boolean(default=True,
                               doc="""
        Whether or not to show a complete frame around the plot.""")

    fontsize = param.Parameter(default=None,
                               allow_None=True,
                               doc="""
       Specifies various fontsizes of the displayed text. By default,
       the fontsize is determined by matplotlib (via rcparams) but if
       set to an integer, this is the fontsize of all text except for
       tick labels (and subfigure labels in Layouts).

       Finer control is available by supplying a dictionary where any
       unmentioned keys reverts to the default sizes, e.g:

          {'ticks':20, 'title':15, 'ylabel':5, 'xlabel':5}""")

    def __init__(self, fig=None, axis=None, **params):
        self._create_fig = True
        super(MPLPlot, self).__init__(**params)
        # List of handles to matplotlib objects for animation update
        scale = self.fig_size / 100.
        if isinstance(self.fig_inches, (tuple, list)):
            self.fig_inches = [
                None if i is None else i * scale for i in self.fig_inches
            ]
        else:
            self.fig_inches *= scale
        fig, axis = self._init_axis(fig, axis)
        self.handles['fig'] = fig
        self.handles['axis'] = axis

    def _fontsize(self, key, label='fontsize', common=True):
        """
        To be used as kwargs e.g: **self._fontsize('title')
        """
        if not self.fontsize:
            return {}
        if isinstance(self.fontsize, dict):
            if key not in self.fontsize:
                return {}
            else:
                return {label: self.fontsize[key]}
        return {label: self.fontsize} if common else {}

    def _init_axis(self, fig, axis):
        """
        Return an axis which may need to be initialized from
        a new figure.
        """
        if not fig and self._create_fig:
            rc_params = self.fig_rcparams
            if self.fig_latex:
                rc_params['text.usetex'] = True
            with mpl.rc_context(rc=rc_params):
                fig = plt.figure()
                l, b, r, t = self.fig_bounds
                inches = self.fig_inches
                fig.subplots_adjust(left=l, bottom=b, right=r, top=t)
                fig.patch.set_alpha(self.fig_alpha)
                if isinstance(inches, (tuple, list)):
                    inches = list(inches)
                    if inches[0] is None:
                        inches[0] = inches[1]
                    elif inches[1] is None:
                        inches[1] = inches[0]
                    fig.set_size_inches(list(inches))
                else:
                    fig.set_size_inches([inches, inches])
                axis = fig.add_subplot(111, projection=self.projection)
                axis.set_aspect('auto')

        return fig, axis

    def _subplot_label(self, axis):
        layout_num = self.layout_num if self.subplot else 1
        if self.sublabel_format and not self.adjoined and layout_num > 0:
            from mpl_toolkits.axes_grid1.anchored_artists import AnchoredText
            labels = {}
            if '{Alpha}' in self.sublabel_format:
                labels['Alpha'] = int_to_alpha(layout_num - 1)
            elif '{alpha}' in self.sublabel_format:
                labels['alpha'] = int_to_alpha(layout_num - 1, upper=False)
            elif '{numeric}' in self.sublabel_format:
                labels['numeric'] = self.layout_num
            elif '{Roman}' in self.sublabel_format:
                labels['Roman'] = int_to_roman(layout_num)
            elif '{roman}' in self.sublabel_format:
                labels['roman'] = int_to_roman(layout_num).lower()
            at = AnchoredText(self.sublabel_format.format(**labels),
                              loc=3,
                              bbox_to_anchor=self.sublabel_position,
                              frameon=False,
                              prop=dict(size=self.sublabel_size,
                                        weight='bold'),
                              bbox_transform=axis.transAxes)
            at.patch.set_visible(False)
            axis.add_artist(at)

    def _finalize_axis(self, key):
        """
        General method to finalize the axis and plot.
        """
        if 'title' in self.handles:
            self.handles['title'].set_visible(self.show_title)

        self.drawn = True
        if self.subplot:
            return self.handles['axis']
        else:
            fig = self.handles['fig']
            plt.close(fig)
            return fig

    @property
    def state(self):
        return self.handles['fig']

    def anim(self, start=0, stop=None, fps=30):
        """
        Method to return a matplotlib animation. The start and stop
        frames may be specified as well as the fps.
        """
        figure = self.initialize_plot()
        anim = animation.FuncAnimation(figure,
                                       self.update_frame,
                                       frames=self.keys,
                                       interval=1000.0 / fps)
        # Close the figure handle
        plt.close(figure)
        return anim

    def update(self, key):
        rc_params = self.fig_rcparams
        if self.fig_latex:
            rc_params['text.usetex'] = True
        mpl.rcParams.update(rc_params)
        if len(self) == 1 and key == 0 and not self.drawn:
            return self.initialize_plot()
        return self.__getitem__(key)
Ejemplo n.º 20
0
class MontageBitmap(Bitmap):
    """
    A bitmap composed of tiles containing other bitmaps.

    Bitmaps are scaled to fit in the given tile size, and tiled
    right-to-left, top-to-bottom into the given number of rows and columns.
    """
    bitmaps = param.List(class_=Bitmap,
                         doc="""
        The list of bitmaps to compose.""")

    rows = param.Integer(default=2,
                         doc="""
        The number of rows in the montage.""")
    cols = param.Integer(default=2,
                         doc="""
        The number of columns in the montage.""")
    shape = param.Composite(attribs=['rows', 'cols'],
                            doc="""
        The shape of the montage. Same as (self.rows,self.cols).""")

    margin = param.Integer(default=5,
                           doc="""
        The size in pixels of the margin to put around each
        tile in the montage.""")

    tile_size = param.NumericTuple(default=(100, 100),
                                   doc="""
        The size in pixels of a tile in the montage.""")

    titles = param.List(class_=str,
                        default=[],
                        doc="""
        A list of titles to overlay on the tiles.""")

    title_pos = param.NumericTuple(default=(10, 10),
                                   doc="""
        The position of the upper left corner of the title in each tile.""")

    title_options = param.Dict(default={},
                               doc="""
        Dictionary of options for drawing the titles.  Dict should
        contain keyword options for the PIL draw.text method.  Possible
        options include 'fill' (fill color), 'outline' (outline color),
        and 'font' (an ImageFont font instance).  The PIL defaults will
        be used for any omitted options.""",
                               instantiate=False)

    hooks = param.List(default=[],
                       doc="""
        A list of functions, one per tile, that take a PIL image as
        input and return a PIL image as output.  The hooks are applied
        to the tile images before resizing.  The value None can be
        inserted as a placeholder where no hook function is needed.""")

    resize_filter = param.Integer(default=Image.NEAREST,
                                  doc="""
       The filter used for resizing the images.  Defaults
       to NEAREST.  See PIL Image module documentation for other
       options and their meanings.""")

    bg_color = param.NumericTuple(default=(0, 0, 0),
                                  doc="""
       The background color for the montage, as (r,g,b).""")

    def __init__(self, **params):
        ## JPALERT: The Bitmap class is a Parameterized object,but its
        ## __init__ doesn't take **params and doesn't call super.__init__,
        ## so we have to skip it.
        ## JAB: Good point; Bitmap should be modified to be more like
        ## other PO classes.
        param.Parameterized.__init__(self, **params)

        rows, cols = self.shape
        tilew, tileh = self.tile_size
        bgr, bgg, bgb = self.bg_color

        width = tilew * cols + self.margin * (cols * 2)
        height = tileh * rows + self.margin * (rows * 2)
        self.image = Image.new('RGB', (width, height),
                               (bgr * 255, bgg * 255, bgb * 255))

        self.title_options.setdefault('font', TITLE_FONT)

        for r in xrange(rows):
            for c in xrange(cols):
                i = r * self.cols + c
                if i < len(self.bitmaps):
                    bm = self.bitmaps[i]
                    bmw, bmh = bm.image.size
                    if bmw > bmh:
                        bmh = int(float(tilew) / bmw * bmh)
                        bmw = tilew
                    else:
                        bmw = int(float(tileh) / bmh * bmw)
                        bmh = tileh

                    if self.hooks and self.hooks[i]:
                        f = self.hooks[i]
                    else:
                        f = lambda x: x
                    new_bm = Bitmap(f(bm.image).resize((bmw, bmh)))
                    if self.titles:
                        draw = ImageDraw.Draw(new_bm.image)
                        draw.text(self.title_pos, self.titles[i],
                                  **self.title_options)
                    self.image.paste(new_bm.image,
                                     (c * width / cols + tilew / 2 - bmw / 2 +
                                      self.margin, r * height / rows +
                                      tileh / 2 - bmh / 2 + self.margin))

                else:
                    break
Ejemplo n.º 21
0
class Plotly(PaneBase):
    """
    Plotly panes allow rendering plotly Figures and traces.

    For efficiency any array objects found inside a Figure are added
    to a ColumnDataSource which allows using binary transport to sync
    the figure on bokeh server and via Comms.
    """

    click_data = param.Dict(doc="Click callback data")

    clickannotation_data = param.Dict(doc="Clickannotation callback data")

    config = param.Dict(doc="Config data")

    hover_data = param.Dict(doc="Hover callback data")

    relayout_data = param.Dict(doc="Relayout callback data")

    restyle_data = param.List(doc="Restyle callback data")

    selected_data = param.Dict(doc="Selected callback data")

    viewport = param.Dict(doc="Current viewport state")

    viewport_update_policy = param.Selector(
        default="mouseup",
        doc="""
        Policy by which the viewport parameter is updated during user interactions.

        * "mouseup": updates are synchronized when mouse button is
          released after panning
        * "continuous": updates are synchronized continually while panning
        * "throttle": updates are synchronized while panning, at 
          intervals determined by the viewport_update_throttle parameter
        """,
        objects=["mouseup", "continuous", "throttle"])

    viewport_update_throttle = param.Integer(default=200,
                                             bounds=(0, None),
                                             doc="""
        Time interval in milliseconds at which viewport updates are
        synchronized when viewport_update_policy is "throttle".""")

    _render_count = param.Integer(default=0,
                                  doc="""
        Number of renders, increment to trigger re-render""")

    priority = 0.8

    _updates = True

    @classmethod
    def applies(cls, obj):
        return ((isinstance(obj, list) and obj
                 and all(cls.applies(o) for o in obj))
                or hasattr(obj, 'to_plotly_json') or
                (isinstance(obj, dict) and 'data' in obj and 'layout' in obj))

    def __init__(self, object=None, **params):
        super().__init__(object, **params)
        self._figure = None
        self._update_figure()

    def _to_figure(self, obj):
        import plotly.graph_objs as go
        if isinstance(obj, go.Figure):
            return obj
        elif isinstance(obj, dict):
            data, layout = obj['data'], obj['layout']
        elif isinstance(obj, tuple):
            data, layout = obj
        else:
            data, layout = obj, {}
        data = data if isinstance(data, list) else [data]
        return go.Figure(data=data, layout=layout)

    @staticmethod
    def _get_sources(json):
        sources = []
        traces = json.get('data', [])
        for trace in traces:
            data = {}
            Plotly._get_sources_for_trace(trace, data)
            sources.append(ColumnDataSource(data))
        return sources

    @staticmethod
    def _get_sources_for_trace(json, data, parent_path=''):
        for key, value in list(json.items()):
            full_path = key if not parent_path else (parent_path + '.' + key)
            if isinstance(value, np.ndarray):
                # Extract numpy array
                data[full_path] = [json.pop(key)]
            elif isinstance(value, dict):
                # Recurse into dictionaries:
                Plotly._get_sources_for_trace(value,
                                              data=data,
                                              parent_path=full_path)
            elif isinstance(value, list) and value and isinstance(
                    value[0], dict):
                # recurse into object arrays:
                for i, element in enumerate(value):
                    element_path = full_path + '.' + str(i)
                    Plotly._get_sources_for_trace(element,
                                                  data=data,
                                                  parent_path=element_path)

    @param.depends('object', watch=True)
    def _update_figure(self):
        import plotly.graph_objs as go

        if (self.object is None or type(self.object) is not go.Figure
                or self.object is self._figure):
            return

        # Monkey patch the message stubs used by FigureWidget.
        # We only patch `Figure` objects (not subclasses like FigureWidget) so
        # we don't interfere with subclasses that override these methods.
        fig = self.object
        fig._send_addTraces_msg = lambda *_, **__: self.param.trigger('object')
        fig._send_moveTraces_msg = lambda *_, **__: self.param.trigger('object'
                                                                       )
        fig._send_deleteTraces_msg = lambda *_, **__: self.param.trigger(
            'object')
        fig._send_restyle_msg = lambda *_, **__: self.param.trigger('object')
        fig._send_relayout_msg = lambda *_, **__: self.param.trigger('object')
        fig._send_update_msg = lambda *_, **__: self.param.trigger('object')
        fig._send_animate_msg = lambda *_, **__: self.param.trigger('object')
        self._figure = fig

    def _update_data_sources(self, cds, trace):
        trace_arrays = {}
        Plotly._get_sources_for_trace(trace, trace_arrays)

        update_sources = False
        for key, new_col in trace_arrays.items():
            new = new_col[0]

            try:
                old = cds.data.get(key)[0]
                update_array = ((type(old) != type(new))
                                or (new.shape != old.shape)
                                or (new != old).any())
            except Exception:
                update_array = True

            if update_array:
                update_sources = True
                cds.data[key] = [new]

        return update_sources

    @staticmethod
    def _plotly_json_wrapper(fig):
        """Wraps around to_plotly_json and applies necessary fixes.

        For #382: Map datetime elements to strings.
        """
        json = fig.to_plotly_json()
        data = json['data']

        for idx in range(len(data)):
            for key in data[idx]:
                if isdatetime(data[idx][key]):
                    arr = data[idx][key]
                    if isinstance(arr, np.ndarray):
                        arr = arr.astype(str)
                    else:
                        arr = [str(v) for v in arr]
                    data[idx][key] = arr
        return json

    def _get_model(self, doc, root=None, parent=None, comm=None):
        """
        Should return the bokeh model to be rendered.
        """
        PlotlyPlot = lazy_load('panel.models.plotly', 'PlotlyPlot',
                               isinstance(comm, JupyterComm))
        viewport_params = [p for p in self.param if 'viewport' in p]
        params = list(Layoutable.param) + viewport_params
        properties = {
            p: getattr(self, p)
            for p in params if getattr(self, p) is not None
        }

        if self.object is None:
            json, sources = {}, []
        else:
            fig = self._to_figure(self.object)
            json = self._plotly_json_wrapper(fig)
            sources = Plotly._get_sources(json)

        data = json.get('data', [])
        layout = json.get('layout', {})
        if layout.get('autosize'
                      ) and self.sizing_mode is self.param.sizing_mode.default:
            properties['sizing_mode'] = 'stretch_both'

        model = PlotlyPlot(data=data,
                           layout=layout,
                           config=self.config or {},
                           data_sources=sources,
                           _render_count=self._render_count,
                           **properties)

        if root is None:
            root = model

        self._link_props(model, [
            'config', 'relayout_data', 'restyle_data', 'click_data',
            'hover_data', 'clickannotation_data', 'selected_data', 'viewport',
            'viewport_update_policy', 'viewport_update_throttle',
            '_render_count'
        ], doc, root, comm)

        if root is None:
            root = model
        self._models[root.ref['id']] = (model, parent)
        return model

    def _update(self, ref=None, model=None):
        if self.object is None:
            model.update(data=[], layout={})
            model._render_count += 1
            return

        fig = self._to_figure(self.object)
        json = self._plotly_json_wrapper(fig)
        layout = json.get('layout')

        traces = json['data']
        new_sources = []
        update_sources = False
        for i, trace in enumerate(traces):
            if i < len(model.data_sources):
                cds = model.data_sources[i]
            else:
                cds = ColumnDataSource()
                new_sources.append(cds)

            update_sources = self._update_data_sources(cds,
                                                       trace) or update_sources
        try:
            update_layout = model.layout != layout
        except Exception:
            update_layout = True

        # Determine if model needs updates
        if (len(model.data) != len(traces)):
            update_data = True
        else:
            update_data = False
            for new, old in zip(traces, model.data):
                try:
                    update_data = ({
                        k: v
                        for k, v in new.items() if k != 'uid'
                    } != {k: v
                          for k, v in old.items() if k != 'uid'})
                except Exception:
                    update_data = True
                if update_data:
                    break

        if self.sizing_mode is self.param.sizing_mode.default and 'autosize' in layout:
            autosize = layout.get('autosize')
            if autosize and model.sizing_mode != 'stretch_both':
                model.sizing_mode = 'stretch_both'
            elif not autosize and model.sizing_mode != 'fixed':
                model.sizing_mode = 'fixed'

        if new_sources:
            model.data_sources += new_sources

        if update_data:
            model.data = json.get('data')

        if update_layout:
            model.layout = layout

        # Check if we should trigger rendering
        if new_sources or update_sources or update_data or update_layout:
            model._render_count += 1
Ejemplo n.º 22
0
class NdWidget(param.Parameterized):
    """
    NdWidget is an abstract base class implementing a method to find
    the dimensions and keys of any ViewableElement, GridSpace or
    UniformNdMapping type.  In the process it creates a mock_obj to
    hold the dimensions and keys.
    """

    display_options = param.Dict(default={},
                                 doc="""
        The display options used to generate individual frames""")

    embed = param.Boolean(default=True,
                          doc="""
        Whether to embed all plots in the Javascript, generating
        a static widget not dependent on the IPython server.""")

    #######################
    # JSON export options #
    #######################

    export_json = param.Boolean(default=False,
                                doc="""Whether to export
         plots as json files, which can be dynamically loaded through
         a callback from the slider.""")

    json_save_path = param.String(default='./json_figures',
                                  doc="""
         If export_json is enabled the widget will save the json
         data to this path. If None data will be accessible via the
         json_data attribute.""")

    json_load_path = param.String(default=None,
                                  doc="""
         If export_json is enabled the widget JS code will load the data
         from this path, if None defaults to json_save_path. For loading
         the data from within the notebook the path must be relative,
         when exporting the notebook the path can be set to another
         location like a webserver where the json files can be uploaded to.""")

    ##############################
    # Javascript include options #
    ##############################

    CDN = param.Dict(
        default={
            'underscore':
            'https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js',
            'jQueryUI': 'https://code.jquery.com/ui/1.10.4/jquery-ui.min.js'
        })

    css = param.String(default=None,
                       doc="""
        Defines the local CSS file to be loaded for this widget.""")

    basejs = param.String(default='widgets.js',
                          doc="""
        JS file containing javascript baseclasses for the widget.""")

    extensionjs = param.String(default=None,
                               doc="""
        Optional javascript extension file for a particular backend.""")

    widgets = {}
    counter = 0

    def __init__(self, plot, renderer=None, **params):
        super(NdWidget, self).__init__(**params)
        self.id = uuid.uuid4().hex
        self.plot = plot
        self.dimensions = plot.dimensions
        self.keys = plot.keys

        self.json_data = {}
        if self.plot.dynamic: self.embed = False
        if renderer is None:
            backend = Store.current_backend
            self.renderer = Store.renderers[backend]
        else:
            self.renderer = renderer
        # Create mock NdMapping to hold the common dimensions and keys
        self.mock_obj = NdMapping([(k, None) for k in self.keys],
                                  kdims=self.dimensions)

        NdWidget.widgets[self.id] = self

        # Set up jinja2 templating
        import jinja2
        templateLoader = jinja2.FileSystemLoader(subdirs)
        self.jinjaEnv = jinja2.Environment(loader=templateLoader)

    def __call__(self):
        return self.render_html(self._get_data())

    def _get_data(self):
        delay = int(1000. / self.display_options.get('fps', 5))
        CDN = {k: v[:-3] for k, v in self.CDN.items()}
        template = self.jinjaEnv.get_template(self.base_template)
        name = type(self).__name__
        cached = str(self.embed).lower()
        load_json = str(self.export_json).lower()
        mode = repr(self.renderer.mode)
        json_path = (self.json_save_path
                     if self.json_load_path is None else self.json_load_path)
        if json_path and json_path[-1] != '/':
            json_path = json_path + '/'
        dynamic = json.dumps(
            self.plot.dynamic) if self.plot.dynamic else 'false'
        return dict(CDN=CDN,
                    frames=self.get_frames(),
                    delay=delay,
                    cached=cached,
                    load_json=load_json,
                    mode=mode,
                    id=self.id,
                    Nframes=len(self.plot),
                    widget_name=name,
                    json_path=json_path,
                    widget_template=template,
                    dynamic=dynamic)

    def render_html(self, data):
        template = self.jinjaEnv.get_template(self.template)
        return template.render(**data)

    def get_frames(self):
        if self.embed:
            frames = OrderedDict([(idx, self._plot_figure(idx))
                                  for idx in range(len(self.plot))])
        else:
            frames = {}
        return self.encode_frames(frames)

    def encode_frames(self, frames):
        if isinstance(frames, dict):
            frames = {idx: frame for idx, frame in frames.items()}
        return frames

    def save_json(self, frames):
        """
        Saves frames data into a json file at the
        specified json_path, named with the widget uuid.
        """
        if self.json_save_path is None: return
        path = os.path.join(self.json_save_path, '%s.json' % self.id)
        if not os.path.isdir(self.json_save_path):
            os.mkdir(self.json_save_path)
        with open(path, 'w') as f:
            json.dump(frames, f)
        self.json_data = frames

    def _plot_figure(self, idx):
        with self.renderer.state():
            self.plot.update(idx)
            css = self.display_options.get('css', {})
            figure_format = self.display_options.get('figure_format',
                                                     self.renderer.fig)
            return self.renderer.html(self.plot, figure_format, css=css)

    def update(self, key):
        return self._plot_figure(key)
Ejemplo n.º 23
0
class pattern_present(PatternPresentingCommand):
    """
    Given a set of input patterns, installs them into the specified
    GeneratorSheets, runs the simulation for the specified length of
    time, then restores the original patterns and the original
    simulation time.  Thus this input is not considered part of the
    regular simulation, and is usually for testing purposes.

    May also be used to measure the response to a pattern by calling
    it with restore_events disabled and restore_state and
    install_sheetview enabled, which will push and pop the simulation
    state and install the response in the sheets views dictionary. The
    update_activity command implements this functionality.

    As a special case, if 'inputs' is just a single pattern, and not
    a dictionary, it is presented to all GeneratorSheets.

    If this process is interrupted by the user, the temporary patterns
    may still be installed on the retina.

    If overwrite_previous is true, the given inputs overwrite those
    previously defined.

    If plastic is False, overwrites the existing values of Sheet.plastic
    to disable plasticity, then re-enables plasticity.

    If this process is interrupted by the user, the temporary patterns
    may still be installed on the retina.

    In order to to see the sequence of values presented, you may use
    the back arrow history mechanism in the GUI. Note that the GUI's Activity
    window must be open. Alternatively or access the activities through the
    Activity entry in the views.maps dictionary on the specified sheets.
    """

    apply_output_fns = param.Boolean(default=True,
                                     doc="""
        Determines whether sheet output functions will be applied.
        """)

    inputs = param.Dict(default={},
                        doc="""
        A dictionary of GeneratorSheetName:PatternGenerator pairs to be
        installed into the specified GeneratorSheets""")

    install_sheetview = param.Boolean(default=False,
                                      doc="""Determines
        whether to install a sheet view in the global storage dictionary.""")

    plastic = param.Boolean(default=False,
                            doc="""
        If plastic is False, overwrites the existing values of
        Sheet.plastic to disable plasticity, then reenables plasticity.""")

    overwrite_previous = param.Boolean(default=False,
                                       doc="""
        If overwrite_previous is true, the given inputs overwrite those
        previously defined.""")

    restore_events = param.Boolean(default=True,
                                   doc="""
        If True, restore simulation events after the response has been
        measured, so that no simulation time will have elapsed.
        Implied by restore_state=True.""")

    restore_state = param.Boolean(default=False,
                                  doc="""
        If True, restore the state of both sheet activities and simulation
        events
        after the response has been measured. Implies restore_events.""")

    return_responses = param.Boolean(default=False,
                                     doc="""
        If True, return a dictionary of the responses.""")

    __abstract = True

    def __call__(self, inputs={}, outputs=[], **params_to_override):
        p = ParamOverrides(self, dict(params_to_override, inputs=inputs))
        # ensure EPs get started (if pattern_response is called before the
        # simulation is run())
        topo.sim.run(0.0)

        if p.restore_state:
            topo.sim.state_push()

        if not p.overwrite_previous:
            save_input_generators()

        if not p.plastic:
            # turn off plasticity everywhere
            for sheet in topo.sim.objects(Sheet).values():
                sheet.override_plasticity_state(new_plasticity_state=False)

        if not p.apply_output_fns:
            for each in topo.sim.objects(Sheet).values():
                if hasattr(each, 'measure_maps'):
                    if each.measure_maps:
                        each.apply_output_fns = False

        # Register the inputs on each input sheet
        generatorsheets = topo.sim.objects(GeneratorSheet)

        if not isinstance(p.inputs, dict):
            for g in generatorsheets.values():
                g.set_input_generator(p.inputs)
        else:
            for each in p.inputs.keys():
                if generatorsheets.has_key(each):
                    generatorsheets[each].set_input_generator(p.inputs[each])
                else:
                    param.Parameterized().warning(
                        '%s not a valid Sheet name for pattern_present.' %
                        each)

        if p.restore_events:
            topo.sim.event_push()

        durations = np.diff([0] + p.durations)
        projection_dict = dict(
            (conn.name, conn) for conn in topo.sim.connections())
        outputs = outputs if len(outputs) > 0 else topo.sim.objects(
            Sheet).keys() + projection_dict.keys()

        responses = defaultdict(dict)
        for i, d in enumerate(durations):
            topo.sim.run(d)
            time = p.durations[i]
            if hasattr(topo, 'guimain'):
                update_activity(p.install_sheetview)
                topo.guimain.refresh_activity_windows()
            if p.return_responses:

                for output in outputs:
                    if output in topo.sim.objects(Sheet).keys():
                        responses[(output,
                                   time)] = topo.sim[output].activity.copy()
                    elif output in projection_dict:
                        responses[(
                            output,
                            time)] = projection_dict[output].activity.copy()

        if p.restore_events:
            topo.sim.event_pop()

        # turn sheets' plasticity and output_fn plasticity back on if we
        # turned it off before
        if not p.plastic:
            for sheet in topo.sim.objects(Sheet).values():
                sheet.restore_plasticity_state()

        if not p.apply_output_fns:
            for each in topo.sim.objects(Sheet).values():
                each.apply_output_fns = True

        if not p.overwrite_previous:
            restore_input_generators()

        if p.restore_state:
            topo.sim.state_pop()

        return responses
Ejemplo n.º 24
0
class Link(Callback):
    """
    A Link defines some connection between a source and target model.
    It allows defining callbacks in response to some change or event
    on the source object. Instead a Link directly causes some action
    to occur on the target, for JS based backends this usually means
    that a corresponding JS callback will effect some change on the
    target in response to a change on the source.

    A Link must define a source object which is what triggers events,
    but must not define a target. It is also possible to define bi-
    directional links between the source and target object.
    """

    bidirectional = param.Boolean(default=False,
                                  doc="""
        Whether to link source and target in both directions.""")

    properties = param.Dict(default={},
                            doc="""
        A dictionary mapping between source specification to target
        specification.""")

    # Whether the link requires a target
    _requires_target = True

    def __init__(self, source, target=None, **params):
        if self._requires_target and target is None:
            raise ValueError('%s must define a target.' % type(self).__name__)
        # Source is stored as a weakref to allow it to be garbage collected
        self._target = None if target is None else weakref.ref(target)
        super().__init__(source, **params)

    @property
    def target(self):
        return self._target() if self._target else None

    def link(self):
        """
        Registers the Link
        """
        self.init()
        if self.source in self.registry:
            links = self.registry[self.source]
            params = {
                k: v
                for k, v in self.param.get_param_values() if k != 'name'
            }
            for link in links:
                link_params = {
                    k: v
                    for k, v in link.param.get_param_values() if k != 'name'
                }
                if (type(link) is type(self) and link.source is self.source
                        and link.target is self.target
                        and params == link_params):
                    return
            self.registry[self.source].append(self)
        else:
            self.registry[self.source] = [self]

    def unlink(self):
        """
        Unregisters the Link
        """
        links = self.registry.get(self.source)
        if self in links:
            links.pop(links.index(self))
Ejemplo n.º 25
0
class ColorbarPlot(ElementPlot):

    colorbar = param.Boolean(default=False, doc="""
        Whether to draw a colorbar.""")

    clipping_colors = param.Dict(default={}, doc="""
        Dictionary to specify colors for clipped values, allows
        setting color for NaN values and for values above and below
        the min and max value. The min, max or NaN color may specify
        an RGB(A) color as a color hex string of the form #FFFFFF or
        #FFFFFFFF or a length 3 or length 4 tuple specifying values in
        the range 0-1 or a named HTML color.""")

    cbar_padding = param.Number(default=0.01, doc="""
        Padding between colorbar and other plots.""")

    cbar_ticks = param.Parameter(default=None, doc="""
        Ticks along colorbar-axis specified as an integer, explicit
        list of tick locations, list of tuples containing the
        locations and labels or a matplotlib tick locator object. If
        set to None default matplotlib ticking behavior is
        applied.""")

    cbar_width = param.Number(default=0.05, doc="""
        Width of the colorbar as a fraction of the main plot""")

    symmetric = param.Boolean(default=False, doc="""
        Whether to make the colormap symmetric around zero.""")

    _colorbars = {}

    def __init__(self, *args, **kwargs):
        super(ColorbarPlot, self).__init__(*args, **kwargs)
        self._cbar_extend = 'neither'

    def _adjust_cbar(self, cbar, label, dim):
        noalpha = math.floor(self.style[self.cyclic_index].get('alpha', 1)) == 1
        if (cbar.solids and noalpha):
            cbar.solids.set_edgecolor("face")
        cbar.set_label(label)
        if isinstance(self.cbar_ticks, ticker.Locator):
            cbar.ax.yaxis.set_major_locator(self.cbar_ticks)
        elif self.cbar_ticks == 0:
            cbar.set_ticks([])
        elif isinstance(self.cbar_ticks, int):
            locator = ticker.MaxNLocator(self.cbar_ticks)
            cbar.ax.yaxis.set_major_locator(locator)
        elif isinstance(self.cbar_ticks, list):
            if all(isinstance(t, tuple) for t in self.cbar_ticks):
                ticks, labels = zip(*self.cbar_ticks)
            else:
                ticks, labels = zip(*[(t, dim.pprint_value(t))
                                        for t in self.cbar_ticks])
            cbar.set_ticks(ticks)
            cbar.set_ticklabels(labels)


    def _finalize_artist(self, element):
        artist = self.handles.get('artist', None)
        if artist and self.colorbar:
            self._draw_colorbar()


    def _draw_colorbar(self, dim=None, redraw=True):
        element = self.hmap.last
        artist = self.handles.get('artist', None)
        fig = self.handles['fig']
        axis = self.handles['axis']
        ax_colorbars, position = ColorbarPlot._colorbars.get(id(axis), ([], None))
        specs = [spec[:2] for _, _, spec, _ in ax_colorbars]
        spec = util.get_spec(element)

        if position is None or not redraw:
            if redraw:
                fig.canvas.draw()
            bbox = axis.get_position()
            l, b, w, h = bbox.x0, bbox.y0, bbox.width, bbox.height
        else:
            l, b, w, h = position

        # Get colorbar label
        dim = element.get_dimension(dim)
        if dim:
            label = dim.pprint_label
        elif element.vdims:
            label = element.vdims[0].pprint_label
        elif dim is None:
            label = ''

        padding = self.cbar_padding
        width = self.cbar_width
        if spec[:2] not in specs:
            offset = len(ax_colorbars)
            scaled_w = w*width
            cax = fig.add_axes([l+w+padding+(scaled_w+padding+w*0.15)*offset,
                                b, scaled_w, h])
            cbar = fig.colorbar(artist, cax=cax, ax=axis, extend=self._cbar_extend)
            self._adjust_cbar(cbar, label, dim)
            self.handles['cax'] = cax
            self.handles['cbar'] = cbar
            ylabel = cax.yaxis.get_label()
            self.handles['bbox_extra_artists'] += [cax, ylabel]
            ax_colorbars.append((artist, cax, spec, label))

        for i, (artist, cax, spec, label) in enumerate(ax_colorbars):
            scaled_w = w*width
            cax.set_position([l+w+padding+(scaled_w+padding+w*0.15)*i,
                              b, scaled_w, h])

        ColorbarPlot._colorbars[id(axis)] = (ax_colorbars, (l, b, w, h))


    def _norm_kwargs(self, element, ranges, opts, vdim):
        """
        Returns valid color normalization kwargs
        to be passed to matplotlib plot function.
        """
        clim = opts.pop('clims', None)
        if clim is None:
            cs = element.dimension_values(vdim)
            if not isinstance(cs, np.ndarray):
                cs = np.array(cs)
            if len(cs) and cs.dtype.kind in 'if':
                clim = ranges[vdim.name] if vdim.name in ranges else element.range(vdim)
                if self.logz:
                    # Lower clim must be >0 when logz=True
                    # Choose the maximum between the lowest non-zero value
                    # and the overall range
                    if clim[0] == 0:
                        vals = element.dimension_values(vdim)
                        clim = (vals[vals!=0].min(), clim[1])
                if self.symmetric:
                    clim = -np.abs(clim).max(), np.abs(clim).max()
            else:
                clim = (0, len(np.unique(cs)))
        if self.logz:
            if self.symmetric:
                norm = mpl_colors.SymLogNorm(vmin=clim[0], vmax=clim[1],
                                             linthresh=clim[1]/np.e)
            else:
                norm = mpl_colors.LogNorm(vmin=clim[0], vmax=clim[1])
            opts['norm'] = norm
        opts['vmin'] = clim[0]
        opts['vmax'] = clim[1]

        # Check whether the colorbar should indicate clipping
        values = np.asarray(element.dimension_values(vdim))
        if values.dtype.kind not in 'OSUM':
            try:
                el_min, el_max = np.nanmin(values), np.nanmax(values)
            except ValueError:
                el_min, el_max = -np.inf, np.inf
        else:
            el_min, el_max = -np.inf, np.inf
        vmin = -np.inf if opts['vmin'] is None else opts['vmin']
        vmax = np.inf if opts['vmax'] is None else opts['vmax']
        if el_min < vmin and el_max > vmax:
            self._cbar_extend = 'both'
        elif el_min < vmin:
            self._cbar_extend = 'min'
        elif el_max > vmax:
            self._cbar_extend = 'max'

        # Define special out-of-range colors on colormap
        cmap = opts.get('cmap')
        if isinstance(cmap, list):
            cmap = mpl_colors.ListedColormap(cmap)
        elif isinstance(cmap, util.basestring):
            cmap = copy.copy(plt.cm.get_cmap(cmap))
        else:
            cmap = copy.copy(cmap)
        colors = {}
        for k, val in self.clipping_colors.items():
            if isinstance(val, tuple):
                colors[k] = {'color': val[:3],
                             'alpha': val[3] if len(val) > 3 else 1}
            elif isinstance(val, util.basestring):
                color = val
                alpha = 1
                if color.startswith('#') and len(color) == 9:
                    alpha = int(color[-2:], 16)/255.
                    color = color[:-2]
                colors[k] = {'color': color, 'alpha': alpha}
        if 'max' in colors: cmap.set_over(**colors['max'])
        if 'min' in colors: cmap.set_under(**colors['min'])
        if 'NaN' in colors: cmap.set_bad(**colors['NaN'])
        opts['cmap'] = cmap
Ejemplo n.º 26
0
class Callback(param.Parameterized):
    """
    A Callback defines some callback to be triggered when a property
    changes on the source object. A Callback can execute arbitrary
    Javascript code and will make all objects referenced in the args
    available in the JS namespace.
    """

    args = param.Dict(default={},
                      allow_None=True,
                      doc="""
        A mapping of names to Python objects. These objects are made
        available to the callback's code snippet as the values of
        named parameters to the callback.""")

    code = param.Dict(default=None,
                      doc="""
        A dictionary mapping from a source specication to a JS code
        snippet to be executed if the source property changes.""")

    # Mapping from a source id to a Link instance
    registry = weakref.WeakKeyDictionary()

    # Mapping to define callbacks by backend and Link type.
    # e.g. Callback._callbacks[Link] = Callback
    _callbacks = {}

    # Whether the link requires a target
    _requires_target = False

    def __init__(self, source, target=None, **params):
        if source is None:
            raise ValueError('%s must define a source' % type(self).__name__)
        # Source is stored as a weakref to allow it to be garbage collected
        self._source = None if source is None else weakref.ref(source)
        super().__init__(**params)
        self.init()

    def init(self):
        """
        Registers the Callback
        """
        if self.source in self.registry:
            links = self.registry[self.source]
            params = {
                k: v
                for k, v in self.param.get_param_values() if k != 'name'
            }
            for link in links:
                link_params = {
                    k: v
                    for k, v in link.param.get_param_values() if k != 'name'
                }
                if not hasattr(link, 'target'):
                    pass
                elif (type(link) is type(self) and link.source is self.source
                      and link.target is self.target
                      and params == link_params):
                    return
            self.registry[self.source].append(self)
        else:
            self.registry[self.source] = [self]

    @classmethod
    def register_callback(cls, callback):
        """
        Register a LinkCallback providing the implementation for
        the Link for a particular backend.
        """
        cls._callbacks[cls] = callback

    @property
    def source(self):
        return self._source() if self._source else None

    @classmethod
    def _process_callbacks(cls, root_view, root_model):
        if not root_model:
            return

        linkable = root_view.select(Viewable)
        linkable += root_model.select({'type': BkModel})

        if not linkable:
            return

        found = [(link, src, getattr(link, 'target', None)) for src in linkable
                 for link in cls.registry.get(src, [])
                 if not link._requires_target or link.target in linkable]

        arg_overrides = {}
        if 'holoviews' in sys.modules:
            from .pane.holoviews import HoloViews, generate_panel_bokeh_map
            hv_views = root_view.select(HoloViews)
            map_hve_bk = generate_panel_bokeh_map(root_model, hv_views)
            for src in linkable:
                for link in cls.registry.get(src, []):
                    if hasattr(link, 'target'):
                        for tgt in map_hve_bk.get(link.target, []):
                            found.append((link, src, tgt))
                    arg_overrides[id(link)] = {}
                    for k, v in link.args.items():
                        # Not all args are hashable
                        try:
                            hv_objs = map_hve_bk.get(v, [])
                        except Exception:
                            continue
                        for tgt in hv_objs:
                            arg_overrides[id(link)][k] = tgt

        ref = root_model.ref['id']
        callbacks = []
        for (link, src, tgt) in found:
            cb = cls._callbacks[type(link)]
            if ((src is None or ref not in getattr(src, '_models', [ref])) or
                (getattr(link, '_requires_target', False) and tgt is None)
                    or (tgt is not None
                        and ref not in getattr(tgt, '_models', [ref]))):
                continue
            overrides = arg_overrides.get(id(link), {})
            callbacks.append(
                cb(root_model, link, src, tgt, arg_overrides=overrides))
        return callbacks
Ejemplo n.º 27
0
class Sheet(EventProcessor,
            SheetCoordinateSystem):  # pylint: disable-msg=W0223
    """
    The generic base class for neural sheets.

    See SheetCoordinateSystem for how Sheet represents space, and
    EventProcessor for how Sheet handles time.

    output_fns are functions that take an activity matrix and produce
    an identically shaped output matrix. The default is having no
    output_fns.
    """
    __abstract = True

    nominal_bounds = BoundingRegionParameter(BoundingBox(radius=0.5),
                                             constant=True,
                                             doc="""
            User-specified BoundingBox of the Sheet coordinate area
            covered by this Sheet.  The left and right bounds--if
            specified--will always be observed, but the top and bottom
            bounds may be adjusted to ensure the density in the y
            direction is the same as the density in the x direction.
            In such a case, the top and bottom bounds are adjusted
            so that the center y point remains the same, and each
            bound is as close as possible to its specified value. The
            actual value of this Parameter is not adjusted, but the
            true bounds may be found from the 'bounds' attribute
            of this object.
            """)

    nominal_density = param.Number(default=10,
                                   constant=True,
                                   doc="""
            User-specified number of processing units per 1.0 distance
            horizontally or vertically in Sheet coordinates. The actual
            number may be different because of discretization; the matrix
            needs to tile the plane exactly, and for that to work the
            density might need to be adjusted.  For instance, an area of 3x2
            cannot have a density of 2 in each direction. The true density
            may be obtained from either the xdensity or ydensity attribute
            (since these are identical for a Sheet).
            """)

    plastic = param.Boolean(True,
                            doc="""
            Setting this to False tells the Sheet not to change its
            permanent state (e.g. any connection weights) based on
            incoming events.
            """)

    precedence = param.Number(default=0.1,
                              softbounds=(0.0, 1.0),
                              doc="""
            Allows a sorting order for Sheets, e.g. in the GUI.""")

    row_precedence = param.Number(default=0.5,
                                  softbounds=(0.0, 1.0),
                                  doc="""
            Allows grouping of Sheets before sorting precedence is
            applied, e.g. for two-dimensional plots in the GUI.""")

    layout_location = param.NumericTuple(default=(-1, -1),
                                         precedence=-1,
                                         doc="""
            Location for this Sheet in an arbitrary pixel-based space
            in which Sheets can be laid out for visualization.""")

    output_fns = param.HookList(
        default=[],
        class_=TransferFn,
        doc=
        "Output function(s) to apply (if apply_output_fns is true) to this Sheet's activity."
    )

    apply_output_fns = param.Boolean(
        default=True,
        doc="Whether to apply the output_fn after computing an Activity matrix."
    )

    properties = param.Dict(default={},
                            doc="""
       A dictionary of property values associated with the Sheet
       object.  For instance, the dictionary:

       {'polarity':'ON', 'eye':'Left'}

       could be used to indicate a left, LGN Sheet with ON-surround
       receptive fields.""")

    def _get_density(self):
        return self.xdensity

    density = property(_get_density,
                       doc="""The sheet's true density (i.e. the
        xdensity, which is equal to the ydensity for a Sheet.)""")

    def __init__(self, **params):
        """
        Initialize this object as an EventProcessor, then also as
        a SheetCoordinateSystem with equal xdensity and ydensity.

        views is an AttrTree, which stores associated measurements,
        i.e. representations of the sheet for use by analysis or plotting
        code.
        """
        EventProcessor.__init__(self, **params)

        # Initialize this object as a SheetCoordinateSystem, with
        # the same density along y as along x.
        SheetCoordinateSystem.__init__(self, self.nominal_bounds,
                                       self.nominal_density)

        n_units = round((self.lbrt[2] - self.lbrt[0]) * self.xdensity, 0)
        if n_units < 1:            raise ValueError(
                "Sheet bounds and density must be specified such that the "+ \
 "sheet has at least one unit in each direction; " \
 +self.name+ " does not.")

        # setup the activity matrix
        self.activity = zeros(self.shape, activity_type)

        # For non-plastic inputs
        self.__saved_activity = []
        self._plasticity_setting_stack = []

        self.views = AttrTree()
        self.views.Maps = AttrTree()
        self.views.Curves = AttrTree()

    ### JABALERT: This should be deleted now that sheet_views is public
    ### JC: shouldn't we keep that, or at least write a function in
    ### utils that deletes a value in a dictinnary without returning an
    ### error if the key is not in the dict?  I leave for the moment,
    ### and have to ask Jim to advise.
    def release_sheet_view(self, view_name):
        """
        Delete the dictionary entry with key entry 'view_name' to save
        memory.
        """
        if view_name in self.views.Maps:
            del self.views.Maps[view_name]

    # CB: what to call this? sheetcoords()? sheetcoords_of_grid()? idxsheetcoords()?
    def sheetcoords_of_idx_grid(self):
        """
        Return an array of x-coordinates and an array of y-coordinates
        corresponding to the activity matrix of the sheet.
        """
        nrows, ncols = self.activity.shape

        C, R = meshgrid(arange(ncols), arange(nrows))

        X, Y = self.matrixidx2sheet(R, C)
        return X, Y

    # CB: check whether we need this function any more.
    def row_col_sheetcoords(self):
        """
        Return an array of Y-coordinates corresponding to the rows of
        the activity matrix of the sheet, and an array of
        X-coordinates corresponding to the columns.
        """
        # The row and column centers are returned in matrix (not
        # sheet) order (hence the reversals below).
        nrows, ncols = self.activity.shape
        return self.matrixidx2sheet(arange(nrows - 1, -1, -1),
                                    arange(ncols))[::-1]

    # CBALERT: to be removed once other code uses
    # row_col_sheetcoords() or sheetcoords_of_idx_grid().
    def sheet_rows(self):
        return self.row_col_sheetcoords()[0]

    def sheet_cols(self):
        return self.row_col_sheetcoords()[1]

    # CEBALERT: haven't really thought about what to put in this. The
    # way it is now, subclasses could make a super.activate() call to
    # avoid repeating some stuff.
    def activate(self):
        """
        Collect activity from each projection, combine it to calculate
        the activity for this sheet, and send the result out.

        Subclasses will need to override this method to whatever it
        means to calculate activity in that subclass.
        """
        if self.apply_output_fns:
            for of in self.output_fns:
                of(self.activity)

        self.send_output(src_port='Activity', data=self.activity)

    def state_push(self):
        """
        Save the current state of this sheet to an internal stack.

        This method is used by operations that need to test the
        response of the sheet without permanently altering its state,
        e.g. for measuring maps or probing the current behavior
        non-invasively.  By default, only the activity pattern of this
        sheet is saved, but subclasses should add saving for any
        additional state that they maintain, or strange bugs are
        likely to occur.  The state can be restored using state_pop().

        Note that Sheets that do learning need not save the
        values of all connection weights, if any, because
        plasticity can be turned off explicitly.  Thus this method
        is intended only for shorter-term state.
        """
        self.__saved_activity.append(array(self.activity))
        EventProcessor.state_push(self)
        for of in self.output_fns:
            if hasattr(of, 'state_push'):
                of.state_push()

    def state_pop(self):
        """
        Pop the most recently saved state off the stack.

        See state_push() for more details.
        """
        self.activity = self.__saved_activity.pop()
        EventProcessor.state_pop(self)
        for of in self.output_fns:
            if hasattr(of, 'state_pop'):
                of.state_pop()

    def activity_len(self):
        """Return the number of items that have been saved by state_push()."""
        return len(self.__saved_activity)

    def override_plasticity_state(self, new_plasticity_state):
        """
        Temporarily override plasticity of medium and long term internal state.

        This function should be implemented by all subclasses so that
        it preserves the ability of the Sheet to compute activity,
        i.e. to operate over a short time scale, while preventing any
        lasting changes to the state (if new_plasticity_state=False).

        Any operation that does not have any lasting state, such as
        those affecting only the current activity level, should not
        be affected by this call.

        By default, simply saves a copy of the plastic flag to an
        internal stack (so that it can be restored by
        restore_plasticity_state()), and then sets plastic to
        new_plasticity_state.
        """
        self._plasticity_setting_stack.append(self.plastic)
        self.plastic = new_plasticity_state

    def restore_plasticity_state(self):
        """
        Restores plasticity of medium and long term internal state after
        a override_plasticity_state call.

        This function should be implemented by all subclasses to
        remove the effect of the most recent override_plasticity_state call,
        i.e. to restore plasticity of any type that was overridden.
        """
        self.plastic = self._plasticity_setting_stack.pop()

    def n_bytes(self):
        """
        Return a lower bound for the memory taken by this sheet, in bytes.

        Typically, this number will include the activity array and any
        similar arrays, plus any other significant data owned (in some
        sense) by this Sheet.  It will not usually include memory
        taken by the Python dictionary or various "housekeeping"
        attributes, which usually contribute only a small amount to
        the memory requirements.

        Subclasses should reimplement this method if they store a
        significant amount of data other than in the activity array.
        """
        return self.activity.nbytes

    def __getitem__(self, coords):
        metadata = AttrDict(precedence=self.precedence,
                            row_precedence=self.row_precedence,
                            timestamp=self.simulation.time())

        sv = Matrix(self.activity.copy(),
                    self.bounds,
                    label=self.name + ' Activity',
                    value='Activity')[coords]
        sv.metadata = metadata
        return sv
Ejemplo n.º 28
0
class ReactTemplate(BasicTemplate):
    """
    ReactTemplate is built on top of React Grid Layout web components.
    """

    compact = param.ObjectSelector(
        default=None, objects=[None, 'vertical', 'horizontal', 'both'])

    cols = param.Dict(default={'lg': 12, 'md': 10, 'sm': 6, 'xs': 4, 'xxs': 2})

    breakpoints = param.Dict(default={
        'lg': 1200,
        'md': 996,
        'sm': 768,
        'xs': 480,
        'xxs': 0
    })

    main = param.ClassSelector(class_=GridSpec,
                               constant=True,
                               doc="""
        A list-like container which populates the main area.""")

    row_height = param.Integer(default=150)

    dimensions = param.Dict(
        default={
            'minW': 0,
            'maxW': 'Infinity',
            'minH': 0,
            'maxH': 'Infinity'
        },
        doc="""A dictonary of minimum/maximum width/height in grid units.""")

    prevent_collision = param.Boolean(default=False,
                                      doc="Prevent collisions between items.")

    _css = pathlib.Path(__file__).parent / 'react.css'

    _template = pathlib.Path(__file__).parent / 'react.html'

    _modifiers = {Card: {'children': {'margin': (20, 20)}, 'margin': (10, 5)}}

    _resources = {
        'js': {
            'react':
            "https://unpkg.com/react@16/umd/react.development.js",
            'react-dom':
            "https://unpkg.com/react-dom@16/umd/react-dom.development.js",
            'babel':
            "https://unpkg.com/babel-standalone@latest/babel.min.js",
            'react-grid':
            "https://cdnjs.cloudflare.com/ajax/libs/react-grid-layout/1.1.1/react-grid-layout.min.js"
        },
        'css': {
            'bootstrap':
            "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css",
            'font-awesome':
            "https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
        }
    }

    def __init__(self, **params):
        if 'main' not in params:
            params['main'] = GridSpec(ncols=12, mode='override')
        super().__init__(**params)
        self._update_render_vars()

    def _update_render_items(self, event):
        super()._update_render_items(event)
        if event.obj is not self.main:
            return
        layouts = []
        for i, ((y0, x0, y1, x1), v) in enumerate(self.main.objects.items()):
            if x0 is None: x0 = 0
            if x1 is None: x1 = 12
            if y0 is None: y0 = 0
            if y1 is None: y1 = self.main.nrows
            elem = {
                'x': x0,
                'y': y0,
                'w': x1 - x0,
                'h': y1 - y0,
                'i': str(i + 1)
            }
            elem.update(self.dimensions)
            layouts.append(elem)
        self._render_variables['layouts'] = {'lg': layouts, 'md': layouts}

    @depends('cols',
             'breakpoints',
             'row_height',
             'compact',
             'dimensions',
             'prevent_collision',
             watch=True)
    def _update_render_vars(self):
        self._render_variables['breakpoints'] = self.breakpoints
        self._render_variables['cols'] = self.cols
        self._render_variables['rowHeight'] = self.row_height
        self._render_variables['compact'] = self.compact
        self._render_variables['dimensions'] = self.dimensions
        self._render_variables['preventCollision'] = self.prevent_collision
Ejemplo n.º 29
0
class overlaid_plot(PylabPlotCommand):
   """
    Use matplotlib to make a plot combining a bitmap and line-based
    overlays for a single plot template and sheet.
    """

   plot_template = param.Dict(default={'Hue': 'OrientationPreference'}, doc="""
        Template for the underlying bitmap plot.""")

   overlay = param.List(default=[('contours', 'OcularPreference', 0.5, 'black'),
                                 ('arrows', 'DirectionPreference',
                                  'DirectionSelectivity', 'white')],
                        doc="""
        List of overlaid plots, where each list item may be a 4-tuple
        specifying either a contour line or a field of arrows::

          ('contours',map-name,contour-value,line-color)

          ('arrows',arrow-location-map-name,arrow-size-map-name,arrow-color)

        Any number or combination of contours and arrows may be supplied.""")

   normalize = param.Boolean(default='Individually', doc="""
        Type of normalization, if any, to use. Options include 'None',
        'Individually', and 'AllTogether'. See
        topo.plotting.plotgroup.TemplatePlotGroup.normalize for more
        details.""")

   sheet = param.ClassSelector(class_=topo.base.sheet.Sheet, doc="""
        The sheet from which sheetViews are to be obtained for plotting.""")

   def __call__(self, **params):

       p=ParamOverrides(self,params)
       name=p.plot_template.keys().pop(0)
       plot=make_template_plot(p.plot_template,
                               p.sheet.views.Maps, p.sheet.xdensity,p.sheet.bounds,
                               p.normalize,name=p.plot_template[name])
       fig = plt.figure(figsize=(5,5))
       if plot:
           bitmap=plot.bitmap
           isint=plt.isinteractive() # Temporarily make non-interactive for plotting
           plt.ioff()                                         # Turn interactive mode off

           plt.imshow(bitmap.image,origin='lower',interpolation='nearest')
           plt.axis('off')

           for (t,pref,sel,c) in p.overlay:
               v = plt.flipud(p.sheet.views.Maps[pref].view()[0])
               if (t=='contours'):
                   plt.contour(v,[sel,sel],colors=c,linewidths=2)

               if (t=='arrows'):
                   s = plt.flipud(p.sheet.views.Maps[sel].view()[0])
                   scale = int(np.ceil(np.log10(len(v))))
                   X = np.array([x for x in xrange(len(v)/scale)])
                   v_sc = np.zeros((len(v)/scale,len(v)/scale))
                   s_sc = np.zeros((len(v)/scale,len(v)/scale))
                   for i in X:
                       for j in X:
                           v_sc[i][j] = v[scale*i][scale*j]
                           s_sc[i][j] = s[scale*i][scale*j]
                   plt.quiver(scale*X, scale*X, -np.cos(2*np.pi*v_sc)*s_sc,
                              -np.sin(2*np.pi*v_sc)*s_sc, color=c,
                              edgecolors=c, minshaft=3, linewidths=1)

           p.title='%s overlaid with %s at time %s' %(plot.name,pref,topo.sim.timestr())
           if isint: plt.ion()
           p.filename_suffix="_"+p.sheet.name
           self._generate_figure(p)
           return fig
Ejemplo n.º 30
0
class DeckGL(PaneBase):
    """
    DeckGL panes allow rendering Deck.Gl/ PyDeck plots in Panel.
    """

    mapbox_api_key = param.String(default=None,
                                  doc="""
        The MapBox API key if not supplied by a PyDeck object.""")

    tooltips = param.ClassSelector(default=True,
                                   class_=(bool, dict),
                                   doc="""
        Whether to enable tooltips""")

    click_state = param.Dict(default={},
                             doc="""
        Contains the last click event on the DeckGL plot.""")

    hover_state = param.Dict(default={},
                             doc="""
        The current hover state of the DeckGL plot.""")

    view_state = param.Dict(default={},
                            doc="""
        The current view state of the DeckGL plot.""")

    _rename = {
        'click_state': 'clickState',
        'hover_state': 'hoverState',
        'view_state': 'viewState',
        'tooltips': 'tooltip'
    }

    _updates = True

    priority = None

    @classmethod
    def applies(cls, obj):
        if (hasattr(obj, "to_json") and hasattr(obj, "mapbox_key")
                and hasattr(obj, "deck_widget")):
            return 0.8
        elif isinstance(obj, (dict, string_types)):
            return 0
        return False

    def _get_properties(self, layout=True):
        if self.object is None:
            data, mapbox_api_key, tooltip = {}, self.mapbox_api_key, self.tooltips
        elif isinstance(self.object, (string_types, dict)):
            if isinstance(self.object, string_types):
                data = json.loads(self.object)
            else:
                data = dict(self.object)
                data['layers'] = [
                    dict(layer) for layer in data.get('layers', [])
                ]
            mapbox_api_key = self.mapbox_api_key
            tooltip = self.tooltips
        else:
            data = dict(self.object.__dict__)
            mapbox_api_key = data.pop('mapbox_key', self.mapbox_api_key)
            deck_widget = data.pop('deck_widget', None)
            tooltip = deck_widget.tooltip
            data = recurse_data(data)

        if layout:
            properties = {
                p: getattr(self, p)
                for p in Layoutable.param if getattr(self, p) is not None
            }
        else:
            properties = {}
        return data, dict(properties,
                          tooltip=tooltip,
                          mapbox_api_key=mapbox_api_key or "")

    @classmethod
    def _process_data(cls, data):
        columns = defaultdict(list)
        for d in data:
            for col, val in d.items():
                columns[col].append(val)
        return {col: np.asarray(vals) for col, vals in columns.items()}

    @classmethod
    def _update_sources(cls, json_data, sources):
        layers = json_data.get('layers', [])

        # Create index of sources by columns
        source_columns = defaultdict(list)
        for i, source in enumerate(sources):
            key = tuple(sorted(source.data.keys()))
            source_columns[key].append((i, source))

        # Process
        unprocessed, unused = [], list(sources)
        for layer in layers:
            data = layer.get('data')
            if is_dataframe(data):
                data = ColumnDataSource.from_df(data)
            elif (isinstance(data, list) and data
                  and isinstance(data[0], dict)):
                data = cls._process_data(data)
            else:
                continue

            key = tuple(sorted(data.keys()))
            existing = source_columns.get(key)
            if existing:
                index, cds = existing.pop()
                layer['data'] = index
                updates = {}
                for col, values in data.items():
                    if not np.array_equal(data[col], cds.data[col]):
                        updates[col] = values
                if updates:
                    cds.data.update(updates)
                unused.remove(cds)
            else:
                unprocessed.append((layer, data))

        for layer, data in unprocessed:
            if unused:
                cds = unused.pop()
                cds.data = data
            else:
                cds = ColumnDataSource(data)
                sources.append(cds)
            layer['data'] = sources.index(cds)

    def _get_model(self, doc, root=None, parent=None, comm=None):
        if "panel.models.deckgl" not in sys.modules:
            if isinstance(comm, JupyterComm):
                self.param.warning(
                    "DeckGLPlot was not imported on instantiation "
                    "and may not render in a notebook. Restart "
                    "the notebook kernel and ensure you load "
                    "it as part of the extension using:"
                    "\n\npn.extension('deckgl')\n")
            from ..models.deckgl import DeckGLPlot
        else:
            DeckGLPlot = getattr(sys.modules["panel.models.deckgl"],
                                 "DeckGLPlot")
        data, properties = self._get_properties()
        properties['data_sources'] = sources = []
        self._update_sources(data, sources)
        properties['layers'] = data.pop('layers', [])
        properties['initialViewState'] = data.pop('initialViewState', {})
        model = DeckGLPlot(data=data, **properties)
        root = root or model
        self._link_props(model, ['clickState', 'hoverState', 'viewState'], doc,
                         root, comm)
        self._models[root.ref["id"]] = (model, parent)
        return model

    def _update(self, model):
        data, properties = self._get_properties(layout=False)
        self._update_sources(data, model.data_sources)
        properties['data'] = data
        properties['layers'] = data.pop('layers', [])
        properties['initialViewState'] = data.pop('initialViewState', {})
        model.update(**properties)