Beispiel #1
0
class Pickler(Exporter):
    """
    The recommended pickler for serializing HoloViews object to a .hvz
    file (a simple zip archive of pickle files). In addition to the
    functionality offered by Store.dump and Store.load, this file
    format offers three additional features:

    1. Optional (zip) compression.
    2. Ability to save and load components of a Layout independently.
    3. Support for metadata per saved component.

    The output file with the .hvz file extension is simply a zip
    archive containing pickled HoloViews objects.
    """

    protocol = param.Integer(default=2,
                             doc="""
        The pickling protocol where 0 is ASCII, 1 supports old Python
        versions and 2 is efficient for new style classes.""")

    compress = param.Boolean(default=True,
                             doc="""
        Whether compression is enabled or not""")

    mime_type = 'application/zip'
    file_ext = 'hvz'

    def __call__(self, obj, key={}, info={}, **kwargs):
        buff = BytesIO()
        self.save(obj, buff, key=key, info=info, **kwargs)
        buff.seek(0)
        return buff.read(), {'file-ext': 'hvz', 'mime_type': self.mime_type}

    @bothmethod
    def save(self_or_cls, obj, filename, key={}, info={}, **kwargs):
        base_info = {'file-ext': 'hvz', 'mime_type': self_or_cls.mime_type}
        key = self_or_cls._merge_metadata(obj, self_or_cls.key_fn, key)
        info = self_or_cls._merge_metadata(obj, self_or_cls.info_fn, info,
                                           base_info)
        compression = zipfile.ZIP_STORED if self_or_cls.compress else zipfile.ZIP_DEFLATED

        filename = self_or_cls._filename(filename) if isinstance(
            filename, str) else filename
        with zipfile.ZipFile(filename, 'w', compression=compression) as f:

            if isinstance(obj, Layout):
                entries = ['.'.join(k) for k in obj.data.keys()]
                components = list(obj.data.values())
                entries = entries if len(entries) > 1 else [entries[0] + '(L)']
            else:
                entries = [
                    '%s.%s' % (group_sanitizer(
                        obj.group, False), label_sanitizer(obj.label, False))
                ]
                components = [obj]

            for component, entry in zip(components, entries):
                f.writestr(
                    entry, Store.dumps(component,
                                       protocol=self_or_cls.protocol))
            f.writestr('metadata', pickle.dumps({'info': info, 'key': key}))
Beispiel #2
0
class ElementPlot(GenericElementPlot, MPLPlot):

    apply_ticks = param.Boolean(default=True,
                                doc="""
        Whether to apply custom ticks.""")

    aspect = param.Parameter(default='square',
                             doc="""
        The aspect ratio mode of the plot. By default, a plot may
        select its own appropriate aspect ratio but sometimes it may
        be necessary to force a square aspect ratio (e.g. to display
        the plot as an element of a grid). The modes 'auto' and
        'equal' correspond to the axis modes of the same name in
        matplotlib, a numeric value may also be passed.""")

    bgcolor = param.ClassSelector(class_=(str, tuple),
                                  default=None,
                                  doc="""
        If set bgcolor overrides the background color of the axis.""")

    invert_xaxis = param.Boolean(default=False,
                                 doc="""
        Whether to invert the plot x-axis.""")

    invert_yaxis = param.Boolean(default=False,
                                 doc="""
        Whether to invert the plot y-axis.""")

    logx = param.Boolean(default=False,
                         doc="""
         Whether to apply log scaling to the x-axis of the Chart.""")

    logy = param.Boolean(default=False,
                         doc="""
         Whether to apply log scaling to the y-axis of the Chart.""")

    logz = param.Boolean(default=False,
                         doc="""
         Whether to apply log scaling to the y-axis of the Chart.""")

    orientation = param.ObjectSelector(default='horizontal',
                                       objects=['horizontal', 'vertical'],
                                       doc="""
        The orientation of the plot. Note that this parameter may not
        always be respected by all plots but should be respected by
        adjoined plots when appropriate.""")

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

    show_grid = param.Boolean(default=False,
                              doc="""
        Whether to show a Cartesian grid on the plot.""")

    xaxis = param.ObjectSelector(
        default='bottom',
        objects=['top', 'bottom', 'bare', 'top-bare', 'bottom-bare', None],
        doc="""
        Whether and where to display the xaxis, bare options allow suppressing
        all axis labels including ticks and xlabel.""")

    yaxis = param.ObjectSelector(
        default='left',
        objects=['left', 'right', 'bare', 'left-bare', 'right-bare', None],
        doc="""
        Whether and where to display the yaxis, bare options allow suppressing
        all axis labels including ticks and ylabel.""")

    zaxis = param.Boolean(default=True,
                          doc="""
        Whether to display the z-axis.""")

    xticks = param.Parameter(default=None,
                             doc="""
        Ticks along x-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.""")

    xrotation = param.Integer(default=0,
                              bounds=(0, 360),
                              doc="""
        Rotation angle of the xticks.""")

    yticks = param.Parameter(default=None,
                             doc="""
        Ticks along y-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.""")

    yrotation = param.Integer(default=0,
                              bounds=(0, 360),
                              doc="""
        Rotation angle of the xticks.""")

    zrotation = param.Integer(default=0,
                              bounds=(0, 360),
                              doc="""
        Rotation angle of the xticks.""")

    zticks = param.Parameter(default=None,
                             doc="""
        Ticks along z-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.""")

    # Element Plots should declare the valid style options for matplotlib call
    style_opts = []

    _suppressed = [Table, ItemTable]
    _colorbars = {}

    def __init__(self, element, **params):
        super(ElementPlot, self).__init__(element, **params)
        check = self.map.last
        if isinstance(check, CompositeOverlay):
            check = check.values()[0]  # Should check if any are 3D plots
        if isinstance(check, Element3D):
            self.projection = '3d'

    def _create_cbar(self, artist, cax):
        self.handles['fig'].add_axes(cax)
        cbar = plt.colorbar(artist, cax=cax)
        if math.floor(self.style[self.cyclic_index].get('alpha', 1)) == 1:
            cbar.solids.set_edgecolor("face")

    def _draw_colorbar(self, artist):
        axis = self.handles['axis']
        ax_colorbars = ElementPlot._colorbars.get(id(axis), [])

        create = 'cax' not in self.handles
        colorbars = []
        if create:
            divider = make_axes_locatable(axis)
            cax = divider.new_horizontal(pad=0.05, size='5%')
            self.handles['cax'] = cax
            self._create_cbar(artist, cax)
            colorbars.append((artist, cax))
            if ax_colorbars:
                if not create: divider = make_axes_locatable(axis)
                for artist, cax in ax_colorbars:
                    self.handles['fig'].delaxes(cax)
                    cax = divider.new_horizontal(pad='20%', size='5%')
                    self._create_cbar(artist, cax)
                    colorbars.append((artist, cax))
        ElementPlot._colorbars[id(axis)] = colorbars

    def _finalize_axis(self,
                       key,
                       title=None,
                       ranges=None,
                       xticks=None,
                       yticks=None,
                       zticks=None,
                       xlabel=None,
                       ylabel=None,
                       zlabel=None):
        """
        Applies all the axis settings before the axis or figure is returned.
        Only plots with zorder 0 get to apply their settings.

        When the number of the frame is supplied as n, this method looks
        up and computes the appropriate title, axis labels and axis bounds.
        """

        axis = self.handles['axis']
        if self.bgcolor:
            axis.set_axis_bgcolor(self.bgcolor)

        view = self._get_frame(key)
        subplots = list(self.subplots.values()) if self.subplots else []
        if self.zorder == 0 and key is not None:
            title = None if self.zorder > 0 else self._format_title(key)
            suppress = any(sp.map.type in self._suppressed
                           for sp in [self] + subplots
                           if isinstance(sp.map, HoloMap))
            if view is not None and not suppress:
                xlabel, ylabel, zlabel = self._axis_labels(
                    view, subplots, xlabel, ylabel, zlabel)
                self._finalize_limits(axis, view, subplots, ranges)

                # Tick formatting
                xdim, ydim = view.get_dimension(0), view.get_dimension(1)
                xformat, yformat = None, None
                if xdim is None:
                    pass
                elif xdim.formatter:
                    xformat = xdim.formatter
                elif xdim.type_formatters.get(xdim.type):
                    xformat = xdim.type_formatters[xdim.type]
                if xformat:
                    axis.xaxis.set_major_formatter(xformat)

                if ydim is None:
                    pass
                elif ydim.formatter:
                    yformat = ydim.formatter
                elif ydim.type_formatters.get(ydim.type):
                    yformat = ydim.type_formatters[ydim.type]
                if yformat:
                    axis.yaxis.set_major_formatter(yformat)

            if self.zorder == 0 and not subplots:
                legend = axis.get_legend()
                if legend: legend.set_visible(self.show_legend)

                axis.get_xaxis().grid(self.show_grid)
                axis.get_yaxis().grid(self.show_grid)

            if xlabel and self.xaxis:
                axis.set_xlabel(xlabel, **self._fontsize('xlabel'))
            if ylabel and self.yaxis:
                axis.set_ylabel(ylabel, **self._fontsize('ylabel'))
            if zlabel and self.zaxis:
                axis.set_zlabel(zlabel, **self._fontsize('ylabel'))

            self._apply_aspect(axis)
            self._subplot_label(axis)
            if self.apply_ticks:
                self._finalize_ticks(axis, view, xticks, yticks, zticks)

            if self.show_title and title is not None:
                self.handles['title'] = axis.set_title(
                    title, **self._fontsize('title'))
        # Always called to ensure log and inverted axes are applied
        self._finalize_axes(axis)

        for hook in self.finalize_hooks:
            try:
                hook(self, view)
            except Exception as e:
                self.warning("Plotting hook %r could not be applied:\n\n %s" %
                             (hook, e))

        return super(ElementPlot, self)._finalize_axis(key)

    def _apply_aspect(self, axis):
        if self.logx or self.logy:
            pass
        elif self.aspect == 'square':
            axis.set_aspect((1. / axis.get_data_ratio()))
        elif self.aspect not in [None, 'square']:
            if isinstance(self.aspect, util.basestring):
                axis.set_aspect(self.aspect)
            else:
                axis.set_aspect(((1. / axis.get_data_ratio())) / self.aspect)

    def _finalize_limits(self, axis, view, subplots, ranges):
        # Extents
        extents = self.get_extents(view, ranges)
        if extents and not self.overlaid:
            coords = [
                coord if np.isreal(coord) else np.NaN for coord in extents
            ]
            if isinstance(view, Element3D) or self.projection == '3d':
                l, b, zmin, r, t, zmax = coords
                zmin, zmax = (c if np.isfinite(c) else None
                              for c in (zmin, zmax))
                if not zmin == zmax:
                    axis.set_zlim((zmin, zmax))
            else:
                l, b, r, t = [
                    coord if np.isreal(coord) else np.NaN for coord in extents
                ]
            l, r = (c if np.isfinite(c) else None for c in (l, r))
            if self.invert_xaxis or any(p.invert_xaxis for p in subplots):
                r, l = l, r
            if not l == r:
                axis.set_xlim((l, r))
            b, t = (c if np.isfinite(c) else None for c in (b, t))
            if self.invert_yaxis or any(p.invert_yaxis for p in subplots):
                t, b = b, t
            if not b == t:
                axis.set_ylim((b, t))

    def _finalize_axes(self, axis):
        if self.logx:
            axis.set_xscale('log')
        elif self.logy:
            axis.set_yscale('log')

    def _finalize_ticks(self, axis, view, xticks, yticks, zticks):
        if not self.projection == '3d':
            disabled_spines = []
            if self.xaxis is not None:
                if 'bare' in self.xaxis:
                    axis.set_xticklabels([])
                    axis.xaxis.set_ticks_position('none')
                    axis.set_xlabel('')
                if 'top' in self.xaxis:
                    axis.xaxis.set_ticks_position("top")
                    axis.xaxis.set_label_position("top")
                elif 'bottom' in self.xaxis:
                    axis.xaxis.set_ticks_position("bottom")
            else:
                axis.xaxis.set_visible(False)
                disabled_spines.extend(['top', 'bottom'])

            if self.yaxis is not None:
                if 'bare' in self.yaxis:
                    axis.set_yticklabels([])
                    axis.yaxis.set_ticks_position('none')
                    axis.set_ylabel('')
                if 'left' in self.yaxis:
                    axis.yaxis.set_ticks_position("left")
                elif 'right' in self.yaxis:
                    axis.yaxis.set_ticks_position("right")
                    axis.yaxis.set_label_position("right")
            else:
                axis.yaxis.set_visible(False)
                disabled_spines.extend(['left', 'right'])

            for pos in disabled_spines:
                axis.spines[pos].set_visible(False)

        if not self.overlaid and not self.show_frame and self.projection != 'polar':
            xaxis = self.xaxis if self.xaxis else ''
            yaxis = self.yaxis if self.yaxis else ''
            axis.spines['top' if self.xaxis == 'bare' or 'bottom' in
                        xaxis else 'bottom'].set_visible(False)
            axis.spines['right' if self.yaxis == 'bare' or 'left' in
                        yaxis else 'left'].set_visible(False)

        if xticks:
            axis.set_xticks(xticks[0])
            axis.set_xticklabels(xticks[1])
        elif self.xticks is not None:
            if isinstance(self.xticks, ticker.Locator):
                axis.xaxis.set_major_locator(self.xticks)
            elif self.xticks == 0:
                axis.set_xticks([])
            elif isinstance(self.xticks, int):
                if self.logx:
                    locator = ticker.LogLocator(numticks=self.xticks,
                                                subs=range(1, 10))
                else:
                    locator = ticker.MaxNLocator(self.xticks)
                axis.xaxis.set_major_locator(locator)
            elif isinstance(self.xticks, list):
                if all(isinstance(t, tuple) for t in self.xticks):
                    xticks, xlabels = zip(*self.xticks)
                else:
                    xdim = view.get_dimension(0)
                    xticks, xlabels = zip(*[(t, xdim.pprint_value(t))
                                            for t in self.xticks])
                axis.set_xticks(xticks)
                axis.set_xticklabels(xlabels)

        if self.xticks != 0:
            for tick in axis.get_xticklabels():
                tick.set_rotation(self.xrotation)

        if yticks:
            axis.set_yticks(yticks[0])
            axis.set_yticklabels(yticks[1])
        elif self.yticks is not None:
            if isinstance(self.yticks, ticker.Locator):
                axis.yaxis.set_major_locator(self.yticks)
            elif self.yticks == 0:
                axis.set_yticks([])
            elif isinstance(self.yticks, int):
                if self.logy:
                    locator = ticker.LogLocator(numticks=self.yticks,
                                                subs=range(1, 10))
                else:
                    locator = ticker.MaxNLocator(self.yticks)
                axis.yaxis.set_major_locator(locator)
            elif isinstance(self.yticks, list):
                if all(isinstance(t, tuple) for t in self.yticks):
                    yticks, ylabels = zip(*self.yticks)
                else:
                    ydim = view.get_dimension(1)
                    yticks, ylabels = zip(*[(t, ydim.pprint_value(t))
                                            for t in self.yticks])
                axis.set_yticks(yticks)
                axis.set_yticklabels(ylabels)

        if self.yticks != 0:
            for tick in axis.get_yticklabels():
                tick.set_rotation(self.yrotation)

        if not self.projection == '3d':
            pass
        elif zticks:
            axis.set_zticks(zticks[0])
            axis.set_zticklabels(zticks[1])
        elif self.zticks is not None:
            if isinstance(self.zticks, ticker.Locator):
                axis.zaxis.set_major_locator(self.zticks)
            elif self.zticks == 0:
                axis.set_zticks([])
            elif isinstance(self.zticks, int):
                if self.logz:
                    locator = ticker.LogLocator(numticks=self.zticks,
                                                subs=range(1, 10))
                else:
                    locator = ticker.MaxNLocator(self.zticks)
                axis.zaxis.set_major_locator(locator)
            elif isinstance(self.zticks, list):
                if all(isinstance(t, tuple) for t in self.zticks):
                    zticks, zlabels = zip(*self.zticks)
                else:
                    zdim = view.get_dimension(2)
                    zticks, zlabels = zip(*[(t, zdim.pprint_value(t))
                                            for t in self.zticks])
                axis.set_zticks(zticks)
                axis.set_zticklabels(zlabels)

        if self.projection == '3d' and self.zticks != 0:
            for tick in axis.get_zticklabels():
                tick.set_rotation(self.zrotation)

        tick_fontsize = self._fontsize('ticks', 'labelsize', common=False)
        if tick_fontsize: axis.tick_params(**tick_fontsize)

    def update_frame(self, key, ranges=None):
        """
        Set the plot(s) to the given frame number.  Operates by
        manipulating the matplotlib objects held in the self._handles
        dictionary.

        If n is greater than the number of available frames, update
        using the last available frame.
        """
        view = self._get_frame(key)
        if view is not None:
            self.set_param(**self.lookup_options(view, 'plot').options)
        axis = self.handles['axis']

        axes_visible = view is not None or self.overlaid
        axis.xaxis.set_visible(axes_visible and self.xaxis)
        axis.yaxis.set_visible(axes_visible and self.yaxis)
        axis.patch.set_alpha(np.min([int(axes_visible), 1]))

        for hname, handle in self.handles.items():
            hideable = hasattr(handle, 'set_visible')
            if hname not in ['axis', 'fig'] and hideable:
                handle.set_visible(view is not None)
        if view is None:
            return
        ranges = self.compute_ranges(self.map, key, ranges)
        if not self.adjoined:
            ranges = util.match_spec(view, ranges)
        axis_kwargs = self.update_handles(axis, view,
                                          key if view is not None else {},
                                          ranges)
        self._finalize_axis(key,
                            ranges=ranges,
                            **(axis_kwargs if axis_kwargs else {}))

    def update_handles(self, axis, view, key, ranges=None):
        """
        Update the elements of the plot.
        :param axis:
        """
        raise NotImplementedError
Beispiel #3
0
class GenericElementPlot(DimensionedPlot):
    """
    Plotting baseclass to render contents of an Element. Implements
    methods to get the correct frame given a HoloMap, axis labels and
    extents and titles.
    """

    apply_ranges = param.Boolean(default=True, doc="""
        Whether to compute the plot bounds from the data itself.""")

    apply_extents = param.Boolean(default=True, doc="""
        Whether to apply extent overrides on the Elements""")

    bgcolor = param.ClassSelector(class_=(str, tuple), default=None, doc="""
        If set bgcolor overrides the background color of the axis.""")

    invert_axes = param.Boolean(default=False, doc="""
        Whether to invert the x- and y-axis""")

    invert_xaxis = param.Boolean(default=False, doc="""
        Whether to invert the plot x-axis.""")

    invert_yaxis = param.Boolean(default=False, doc="""
        Whether to invert the plot y-axis.""")

    logx = param.Boolean(default=False, doc="""
        Whether the x-axis of the plot will be a log axis.""")

    logy = param.Boolean(default=False, doc="""
        Whether the y-axis of the plot will be a log axis.""")

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

    show_grid = param.Boolean(default=False, doc="""
        Whether to show a Cartesian grid on the plot.""")

    xaxis = param.ObjectSelector(default='bottom',
                                 objects=['top', 'bottom', 'bare', 'top-bare',
                                          'bottom-bare', None, True, False], doc="""
        Whether and where to display the xaxis, bare options allow suppressing
        all axis labels including ticks and xlabel. Valid options are 'top',
        'bottom', 'bare', 'top-bare' and 'bottom-bare'.""")

    yaxis = param.ObjectSelector(default='left',
                                      objects=['left', 'right', 'bare', 'left-bare',
                                               'right-bare', None, True, False], doc="""
        Whether and where to display the yaxis, bare options allow suppressing
        all axis labels including ticks and ylabel. Valid options are 'left',
        'right', 'bare' 'left-bare' and 'right-bare'.""")

    xrotation = param.Integer(default=None, bounds=(0, 360), doc="""
        Rotation angle of the xticks.""")

    yrotation = param.Integer(default=None, bounds=(0, 360), doc="""
        Rotation angle of the yticks.""")

    xticks = param.Parameter(default=None, doc="""
        Ticks along x-axis specified as an integer, explicit list of
        tick locations or bokeh Ticker object. If set to None default
        bokeh ticking behavior is applied.""")

    yticks = param.Parameter(default=None, doc="""
        Ticks along y-axis specified as an integer, explicit list of
        tick locations or bokeh Ticker object. If set to None
        default bokeh ticking behavior is applied.""")

    # A dictionary mapping of the plot methods used to draw the
    # glyphs corresponding to the ElementPlot, can support two
    # keyword arguments a 'single' implementation to draw an individual
    # plot and a 'batched' method to draw multiple Elements at once
    _plot_methods = {}

    # Declares the options that are propagated from sub-elements of the
    # plot, mostly useful for inheriting options from individual
    # Elements on an OverlayPlot. Enabled by default in v1.7.
    _propagate_options = []
    v17_option_propagation = True

    def __init__(self, element, keys=None, ranges=None, dimensions=None,
                 batched=False, overlaid=0, cyclic_index=0, zorder=0, style=None,
                 overlay_dims={}, stream_sources=[], streams=None, **params):
        self.zorder = zorder
        self.cyclic_index = cyclic_index
        self.overlaid = overlaid
        self.batched = batched
        self.overlay_dims = overlay_dims

        if not isinstance(element, (HoloMap, DynamicMap)):
            self.hmap = HoloMap(initial_items=(0, element),
                               kdims=['Frame'], id=element.id)
        else:
            self.hmap = element

        if overlaid:
            self.stream_sources = stream_sources
        else:
            self.stream_sources = compute_overlayable_zorders(self.hmap)

        plot_element = self.hmap.last
        if self.batched and not isinstance(self, GenericOverlayPlot):
            plot_element = plot_element.last

        dynamic = isinstance(element, DynamicMap) and not element.unbounded
        self.top_level = keys is None
        if self.top_level:
            dimensions = self.hmap.kdims
            keys = list(self.hmap.data.keys())

        self.style = self.lookup_options(plot_element, 'style') if style is None else style
        plot_opts = self.lookup_options(plot_element, 'plot').options
        if self.v17_option_propagation:
            inherited = self._traverse_options(plot_element, 'plot',
                                               self._propagate_options,
                                               defaults=False)
            plot_opts.update(**{k: v[0] for k, v in inherited.items()})

        super(GenericElementPlot, self).__init__(keys=keys, dimensions=dimensions,
                                                 dynamic=dynamic,
                                                 **dict(params, **plot_opts))
        self.streams = get_nested_streams(self.hmap) if streams is None else streams
        if self.top_level:
            self.comm = self.init_comm()
            self.traverse(lambda x: setattr(x, 'comm', self.comm))

        # Attach streams if not overlaid and not a batched ElementPlot
        if not (self.overlaid or (self.batched and not isinstance(self, GenericOverlayPlot))):
            attach_streams(self, self.hmap)

        # Update plot and style options for batched plots
        if self.batched:
            self.ordering = util.layer_sort(self.hmap)
            overlay_opts = self.lookup_options(self.hmap.last, 'plot').options.items()
            opts = {k: v for k, v in overlay_opts if k in self.params()}
            self.set_param(**opts)
            self.style = self.lookup_options(plot_element, 'style').max_cycles(len(self.ordering))
        else:
            self.ordering = []


    def get_zorder(self, overlay, key, el):
        """
        Computes the z-order of element in the NdOverlay
        taking into account possible batching of elements.
        """
        spec = util.get_overlay_spec(overlay, key, el)
        return self.ordering.index(spec)


    def _updated_zorders(self, overlay):
        specs = [util.get_overlay_spec(overlay, key, el)
                 for key, el in overlay.data.items()]
        self.ordering = sorted(set(self.ordering+specs))
        return [self.ordering.index(spec) for spec in specs]


    def _get_frame(self, key):
        if isinstance(self.hmap, DynamicMap) and self.overlaid and self.current_frame:
            self.current_key = key
            return self.current_frame
        elif key == self.current_key and not self._force:
            return self.current_frame

        cached = self.current_key is None
        key_map = dict(zip([d.name for d in self.dimensions], key))
        frame = get_plot_frame(self.hmap, key_map, cached)
        traverse_setter(self, '_force', False)

        if not key in self.keys and self.dynamic:
            self.keys.append(key)
        self.current_frame = frame
        self.current_key = key
        return frame


    def _execute_hooks(self, element):
        """
        Executes finalize hooks
        """
        for hook in self.finalize_hooks:
            try:
                hook(self, element)
            except Exception as e:
                self.warning("Plotting hook %r could not be applied:\n\n %s" % (hook, e))


    def get_extents(self, view, ranges):
        """
        Gets the extents for the axes from the current View. The globally
        computed ranges can optionally override the extents.
        """
        ndims = len(view.dimensions())
        num = 6 if self.projection == '3d' else 4
        if self.apply_ranges:
            if ranges:
                dims = view.dimensions()
                x0, x1 = ranges[dims[0].name]
                if ndims > 1:
                    y0, y1 = ranges[dims[1].name]
                else:
                    y0, y1 = (np.NaN, np.NaN)
                if self.projection == '3d':
                    if len(dims) > 2:
                        z0, z1 = ranges[dims[2].name]
                    else:
                        z0, z1 = np.NaN, np.NaN
            else:
                x0, x1 = view.range(0)
                y0, y1 = view.range(1) if ndims > 1 else (np.NaN, np.NaN)
                if self.projection == '3d':
                    z0, z1 = view.range(2)
            if self.projection == '3d':
                range_extents = (x0, y0, z0, x1, y1, z1)
            else:
                range_extents = (x0, y0, x1, y1)
        else:
            range_extents = (np.NaN,) * num

        if self.apply_extents:
            norm_opts = self.lookup_options(view, 'norm').options
            if norm_opts.get('framewise', False) or self.dynamic:
                extents = view.extents
            else:
                extent_list = self.hmap.traverse(lambda x: x.extents, [Element])
                extents = util.max_extents(extent_list, self.projection == '3d')
        else:
            extents = (np.NaN,) * num

        if getattr(self, 'shared_axes', False) and self.subplot:
            return util.max_extents([range_extents, extents], self.projection == '3d')
        else:
            max_extent = []
            for l1, l2 in zip(range_extents, extents):
                if (isinstance(l2, util.datetime_types)
                    or (l2 is not None and np.isfinite(l2))):
                    max_extent.append(l2)
                else:
                    max_extent.append(l1)
            return tuple(max_extent)


    def _get_axis_labels(self, dimensions, xlabel=None, ylabel=None, zlabel=None):
        if dimensions and xlabel is None:
            xlabel = dim_axis_label(dimensions[0]) if dimensions[0] else ''
        if len(dimensions) >= 2 and ylabel is None:
            ylabel = dim_axis_label(dimensions[1]) if dimensions[1] else ''
        if self.projection == '3d' and len(dimensions) >= 3 and zlabel is None:
            zlabel = dim_axis_label(dimensions[2]) if dimensions[2] else ''
        return xlabel, ylabel, zlabel


    def _format_title(self, key, dimensions=True, separator='\n'):
        frame = self._get_frame(key)
        if frame is None: return None
        type_name = type(frame).__name__
        group = frame.group if frame.group != type_name else ''
        label = frame.label

        if self.layout_dimensions:
            dim_title = self._frame_title(key, separator=separator)
            title = dim_title
        else:
            if dimensions:
                dim_title = self._frame_title(key, separator=separator)
            else:
                dim_title = ''
            title_format = util.bytes_to_unicode(self.title_format)
            title = title_format.format(label=util.bytes_to_unicode(label),
                                        group=util.bytes_to_unicode(group),
                                        type=type_name,
                                        dimensions=dim_title)
        return title.strip(' \n')


    def update_frame(self, key, ranges=None):
        """
Beispiel #4
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.
    """

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

    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)
            plot_opts = dict(self_or_cls.plot_options(obj, self_or_cls.size),
                             **kwargs)
            if isinstance(obj, AdjointLayout):
                obj = Layout(obj)
            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':
            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 = HoloViewsPane(obj,
                                 center=True,
                                 backend=self.backend,
                                 renderer=self)
        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 isinstance(resources, basestring):
            resources = resources.lower()
        if css is None: css = self.css

        if isinstance(plot, Viewable):
            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):
            registry = list(Stream.registry.items())
            objects = plot.object.traverse(lambda x: x)
            dynamic, streams = False, False
            for source in objects:
                dynamic |= isinstance(source, DynamicMap)
                streams |= any(
                    src is source or (src._plot_id is not None
                                      and src._plot_id == source._plot_id)
                    for src, streams in registry for s in streams)
            embed = (not (dynamic or streams or self.widget_mode == 'live')
                     or config.embed)

            # This part should be factored out in Panel and then imported
            # here for HoloViews 2.0, which will be able to require a
            # recent Panel version.
            if embed or config.comms == 'default':
                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)
                if embed:
                    return render_model(model, comm)
                args = (model, doc, comm)
                if panel_version > '0.9.3':
                    from panel.models.comm_manager import CommManager
                    ref = model.ref['id']
                    manager = CommManager(comm_id=comm.id, plot_id=ref)
                    client_comm = self.comm_manager.get_client_comm(
                        on_msg=partial(plot._on_msg, ref, manager),
                        on_error=partial(plot._on_error, ref),
                        on_stdout=partial(plot._on_stdout, ref))
                    manager.client_comm_id = client_comm.id
                    args = args + (manager, )
                return render_mimebundle(*args)

            # Handle rendering object as ipywidget
            widget = ipywidget(plot, combine_events=True)
            if hasattr(widget, '_repr_mimebundle_'):
                return widget._repr_mimebundle()
            plaintext = repr(widget)
            if len(plaintext) > 110:
                plaintext = plaintext[:110] + '…'
            data = {
                'text/plain': plaintext,
            }
            if widget._view_name is not None:
                data['application/vnd.jupyter.widget-view+json'] = {
                    'version_major': 2,
                    'version_minor': 0,
                    'model_id': widget._model_id
                }
            if config.comms == 'vscode':
                # Unfortunately VSCode does not yet handle _repr_mimebundle_
                from IPython.display import display
                display(data, raw=True)
                return {'text/html': '<div style="display: none"></div>'}, {}
            return data, {}
        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=self_or_cls.center,
                               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',
             title=None,
             **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.')

        if kwargs:
            param.main.warning("Supplying plot, style or norm options "
                               "as keyword arguments to the Renderer.save "
                               "method is deprecated and will error in "
                               "the next minor release.")

        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
            if isinstance(basename, basestring):
                if title is None:
                    title = os.path.basename(basename)
                if fmt in MIME_TYPES:
                    basename = '.'.join([basename, fmt])
            plot.layout.save(basename,
                             embed=True,
                             resources=resources,
                             title=title)
            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]
Beispiel #5
0
class Layoutable(param.Parameterized):

    aspect_ratio = param.Parameter(default=None,
                                   doc="""
        Describes the proportional relationship between component's
        width and height.  This works if any of component's dimensions
        are flexible in size. If set to a number, ``width / height =
        aspect_ratio`` relationship will be maintained.  Otherwise, if
        set to ``"auto"``, component's preferred width and height will
        be used to determine the aspect (if not set, no aspect will be
        preserved).
    """)

    background = param.Color(default=None,
                             doc="""
        Background color of the component.""")

    css_classes = param.List(default=None,
                             doc="""
        CSS classes to apply to the layout.""")

    width = param.Integer(default=None,
                          bounds=(0, None),
                          doc="""
        The width of the component (in pixels). This can be either
        fixed or preferred width, depending on width sizing policy.""")

    height = param.Integer(default=None,
                           bounds=(0, None),
                           doc="""
        The height of the component (in pixels).  This can be either
        fixed or preferred height, depending on height sizing policy.""")

    min_width = param.Integer(default=None,
                              bounds=(0, None),
                              doc="""
        Minimal width of the component (in pixels) if width is adjustable.""")

    min_height = param.Integer(default=None,
                               bounds=(0, None),
                               doc="""
        Minimal height of the component (in pixels) if height is adjustable."""
                               )

    max_width = param.Integer(default=None,
                              bounds=(0, None),
                              doc="""
        Minimal width of the component (in pixels) if width is adjustable.""")

    max_height = param.Integer(default=None,
                               bounds=(0, None),
                               doc="""
        Minimal height of the component (in pixels) if height is adjustable."""
                               )

    margin = param.Parameter(default=None,
                             doc="""
        Allows to create additional space around the component. May
        be specified as a two-tuple of the form (vertical, horizontal)
        or a four-tuple (top, right, bottom, left).""")

    width_policy = param.ObjectSelector(
        default="auto",
        objects=['auto', 'fixed', 'fit', 'min', 'max'],
        doc="""
        Describes how the component should maintain its width.

        * "auto"
          Use component's preferred sizing policy.
        * "fixed"
          Use exactly ``width`` pixels. Component will overflow if it
          can't fit in the available horizontal space.
        * "fit"
          Use component's preferred width (if set) and allow it to fit
          into the available horizontal space within the minimum and
          maximum width bounds (if set). Component's width neither
          will be aggressively minimized nor maximized.
        * "min"
          Use as little horizontal space as possible, not less than
          the minimum width (if set).  The starting point is the
          preferred width (if set). The width of the component may
          shrink or grow depending on the parent layout, aspect
          management and other factors.
        * "max"
          Use as much horizontal space as possible, not more than the
          maximum width (if set).  The starting point is the preferred
          width (if set). The width of the component may shrink or
          grow depending on the parent layout, aspect management and
          other factors.
    """)

    height_policy = param.ObjectSelector(
        default="auto",
        objects=['auto', 'fixed', 'fit', 'min', 'max'],
        doc="""
        Describes how the component should maintain its height.

        * "auto"
          Use component's preferred sizing policy.
        * "fixed"
          Use exactly ``width`` pixels. Component will overflow if it
          can't fit in the available horizontal space.
        * "fit"
          Use component's preferred width (if set) and allow it to fit
          into the available horizontal space within the minimum and
          maximum width bounds (if set). Component's width neither
          will be aggressively minimized nor maximized.
        * "min"
          Use as little horizontal space as possible, not less than
          the minimum width (if set).  The starting point is the
          preferred width (if set). The width of the component may
          shrink or grow depending on the parent layout, aspect
          management and other factors.
        * "max"
          Use as much horizontal space as possible, not more than the
          maximum width (if set).  The starting point is the preferred
          width (if set). The width of the component may shrink or
          grow depending on the parent layout, aspect management and
          other factors.
    """)

    sizing_mode = param.ObjectSelector(default=None,
                                       objects=[
                                           'fixed', 'stretch_width',
                                           'stretch_height', 'stretch_both',
                                           'scale_width', 'scale_height',
                                           'scale_both', None
                                       ],
                                       doc="""

        How the component should size itself.

        This is a high-level setting for maintaining width and height
        of the component. To gain more fine grained control over
        sizing, use ``width_policy``, ``height_policy`` and
        ``aspect_ratio`` instead (those take precedence over
        ``sizing_mode``).

        * "fixed"
          Component is not responsive. It will retain its original
          width and height regardless of any subsequent browser window
          resize events.
        * "stretch_width"
          Component will responsively resize to stretch to the
          available width, without maintaining any aspect ratio. The
          height of the component depends on the type of the component
          and may be fixed or fit to component's contents.
        * "stretch_height"
          Component will responsively resize to stretch to the
          available height, without maintaining any aspect ratio. The
          width of the component depends on the type of the component
          and may be fixed or fit to component's contents.
        * "stretch_both"
          Component is completely responsive, independently in width
          and height, and will occupy all the available horizontal and
          vertical space, even if this changes the aspect ratio of the
          component.
        * "scale_width"
          Component will responsively resize to stretch to the
          available width, while maintaining the original or provided
          aspect ratio.
        * "scale_height"
          Component will responsively resize to stretch to the
          available height, while maintaining the original or provided
          aspect ratio.
        * "scale_both"
          Component will responsively resize to both the available
          width and height, while maintaining the original or provided
          aspect ratio.
    """)

    def __init__(self, **params):
        if (params.get('width', None) is not None
                and params.get('height', None) is not None
                and 'sizing_mode' not in params):
            params['sizing_mode'] = 'fixed'
        super(Layoutable, self).__init__(**params)
Beispiel #6
0
class PanelDeck(param.Parameterized):
    """
    PanelDeck class for panel.pane.DeckGL + multi_select(Boolean) parameter
    """

    x = param.String("x")
    data = param.DataFrame()
    colors = param.DataFrame()
    indices = set()
    multi_select = param.Boolean(False, doc="multi-select")
    callback = param.Callable()
    spec = param.Dict()
    default_color = param.List([211, 211, 211, 50])
    sizing_mode = param.String("stretch_both")
    height = param.Integer(400)
    width = param.Integer(400)
    tooltip_include_cols = param.List(
        [], doc="list of columns to include in tooltip")

    def get_tooltip_html(self):
        """
        get tooltip info from dataframe columns, if not already present
        """
        html_str = ""
        tooltip_columns = (list(
            set(self.data.columns) -
            set(["index", "coordinates"] + list(self.colors.columns))) if len(
                self.tooltip_include_cols) == 0 else self.tooltip_include_cols)

        for i in tooltip_columns:
            html_str += f"<b> {i} </b>: {{{i}}} <br>"
        return html_str

    def __init__(self, **params):
        """
        initialize pydeck object, and set a listener on self.data
        """
        super(PanelDeck, self).__init__(**params)
        self._view_state = pdk.ViewState(**self.spec["initialViewState"],
                                         bearing=0.45)
        self._layers = pdk.Layer("PolygonLayer",
                                 data=self.data,
                                 **self.spec["layers"][0])
        self._tooltip = {"html": self.get_tooltip_html()}
        self._deck = pdk.Deck(
            mapbox_key=self.spec["mapboxApiAccessToken"],
            views=[
                pdk.View(
                    type="MapView",
                    controller=True,
                    height="100%",
                    width="100%",
                )
            ],
            layers=[self._layers],
            initial_view_state=self._view_state,
            tooltip=self._tooltip,
        )
        if self.spec["map_style"]:
            self._deck.map_style = self.spec["map_style"]
        self.pane = pn.pane.DeckGL(
            self._deck,
            sizing_mode=self.sizing_mode,
            height=self.height,
            css_classes=["deck-chart"],
        )
        self.param.watch(self._update, ["data"])

    def selected_points(self):
        """
        returns a list of currently selected column_x values as a list
        """
        return self.data[self.x].loc[self.indices].tolist()

    @pn.depends("pane.click_state")
    def click_event(self):
        """
        callback for click events, highlights the selected indices
        (single_select/multi_select) and sets the color of
        unselected indices to default_color
        """
        index = self.pane.click_state.get("index", -1)
        old_indices = list(self.indices)
        if index == -1:
            index = slice(0, 0)
            self.indices = set()
            self.data[self.colors.columns] = self.colors
        else:
            if self.multi_select:
                if index not in self.indices:
                    self.indices.add(index)
                else:
                    self.indices.remove(index)
            else:
                if index in self.indices:
                    self.indices.clear()
                else:
                    self.indices.clear()
                    self.indices.add(index)
            temp_colors = self.colors.copy()
            if len(self.indices) > 0:
                temp_colors.loc[set(self.data.index) - self.indices,
                                self.colors.columns] = self.default_color
            self.data[self.colors.columns] = temp_colors
        self._layers.data = self.data
        self.pane.param.trigger("object")
        self.callback(
            self.data[self.x].loc[old_indices].tolist(),
            self.data[self.x].loc[list(self.indices)].tolist(),
        )

    def _update(self, event):
        """
        trigger deck_gl pane when layer data is updated
        """
        if event.name == "data":
            self._layers.data = self.data
        self.pane.param.trigger("object")

    def view(self):
        """
        view object
        """
        x = pn.Column(
            self.param.multi_select,
            sizing_mode=self.sizing_mode,
            css_classes=["multi-select"],
        )

        return pn.Column(
            x,
            self.click_event,
            self.pane,
            width=self.width,
            height=self.height,
            sizing_mode=self.sizing_mode,
        )
Beispiel #7
0
class BokehPlot(DimensionedPlot):
    """
    Plotting baseclass for the Bokeh backends, implementing the basic
    plotting interface for Bokeh based plots.
    """

    width = param.Integer(default=300,
                          doc="""
        Width of the plot in pixels""")

    height = param.Integer(default=300,
                           doc="""
        Height of the plot in pixels""")

    sizing_mode = param.ObjectSelector(default='fixed',
                                       objects=[
                                           'fixed', 'stretch_both',
                                           'scale_width', 'scale_height',
                                           'scale_both'
                                       ],
                                       doc="""
        How the item being displayed should size itself.

        "stretch_both" plots will resize to occupy all available
        space, even if this changes the aspect ratio of the element.

        "fixed" plots are not responsive and will retain their
        original width and height regardless of any subsequent browser
        window resize events.

        "scale_width" elements will responsively resize to fit to the
        width available, while maintaining the original aspect ratio.

        "scale_height" elements will responsively resize to fit to the
        height available, while maintaining the original aspect ratio.

        "scale_both" elements will responsively resize to for both the
        width and height available, while maintaining the original
        aspect ratio.""")

    shared_datasource = param.Boolean(default=True,
                                      doc="""
        Whether Elements drawing the data from the same object should
        share their Bokeh data source allowing for linked brushing
        and other linked behaviors.""")

    title_format = param.String(default="{label} {group} {dimensions}",
                                doc="""
        The formatting string for the title of this plot, allows defining
        a label group separator and dimension labels.""")

    backend = 'bokeh'

    @property
    def document(self):
        return self._document

    @document.setter
    def document(self, doc):
        self._document = doc
        if self.subplots:
            for plot in self.subplots.values():
                if plot is not None:
                    plot.document = doc

    def __init__(self, *args, **params):
        super(BokehPlot, self).__init__(*args, **params)
        self._document = None
        self.root = None

    def get_data(self, element, ranges, style):
        """
        Returns the data from an element in the appropriate format for
        initializing or updating a ColumnDataSource and a dictionary
        which maps the expected keywords arguments of a glyph to
        the column in the datasource.
        """
        raise NotImplementedError

    def _construct_callbacks(self):
        """
        Initializes any callbacks for streams which have defined
        the plotted object as a source.
        """
        if isinstance(self, GenericOverlayPlot):
            zorders = []
        elif self.batched:
            zorders = list(
                range(self.zorder, self.zorder + len(self.hmap.last)))
        else:
            zorders = [self.zorder]

        if isinstance(self, GenericOverlayPlot) and not self.batched:
            sources = []
        elif not self.static or isinstance(self.hmap, DynamicMap):
            sources = [(i, o) for i, inputs in self.stream_sources.items()
                       for o in inputs if i in zorders]
        else:
            sources = [(self.zorder, self.hmap.last)]

        cb_classes = set()
        for _, source in sources:
            streams = Stream.registry.get(id(source), [])
            registry = Stream._callbacks['bokeh']
            cb_classes |= {(registry[type(stream)], stream)
                           for stream in streams
                           if type(stream) in registry and stream.linked}
        cbs = []
        sorted_cbs = sorted(cb_classes, key=lambda x: id(x[0]))
        for cb, group in groupby(sorted_cbs, lambda x: x[0]):
            cb_streams = [s for _, s in group]
            cbs.append(cb(self, cb_streams, source))
        return cbs

    def push(self):
        """
        Pushes updated plot data via the Comm.
        """
        if self.renderer.mode == 'server':
            return
        if self.comm is None:
            raise Exception('Renderer does not have a comm.')

        msg = self.renderer.diff(self, binary=True)
        if msg is None:
            return
        self.comm.send(msg.header_json)
        self.comm.send(msg.metadata_json)
        self.comm.send(msg.content_json)
        for header, payload in msg.buffers:
            self.comm.send(json.dumps(header))
            self.comm.send(buffers=[payload])

    def set_root(self, root):
        """
        Sets the current document on all subplots.
        """
        for plot in self.traverse(lambda x: x):
            plot.root = root

    def _init_datasource(self, data):
        """
        Initializes a data source to be passed into the bokeh glyph.
        """
        return ColumnDataSource(data=data)

    def _update_datasource(self, source, data):
        """
        Update datasource with data for a new frame.
        """
        if (self.streaming
                and self.streaming[0].data is self.current_frame.data
                and self._stream_data):
            stream = self.streaming[0]
            if stream._triggering:
                data = {k: v[-stream._chunk_length:] for k, v in data.items()}
                source.stream(data, stream.length)
        else:
            source.data.update(data)

    @property
    def state(self):
        """
        The plotting state that gets updated via the update method and
        used by the renderer to generate output.
        """
        return self.handles['plot']

    @property
    def current_handles(self):
        """
        Should return a list of plot objects that have changed and
        should be updated.
        """
        return []

    def _fontsize(self, key, label='fontsize', common=True):
        """
        Converts integer fontsizes to a string specifying
        fontsize in pt.
        """
        size = super(BokehPlot, self)._fontsize(key, label, common)
        return {
            k: v if isinstance(v, basestring) else '%spt' % v
            for k, v in size.items()
        }

    def sync_sources(self):
        """
        Syncs data sources between Elements, which draw data
        from the same object.
        """
        get_sources = lambda x: (id(x.current_frame.data), x)
        filter_fn = lambda x: (x.shared_datasource and x.current_frame is
                               not None and not isinstance(
                                   x.current_frame.data, np.ndarray
                               ) and 'source' in x.handles)
        data_sources = self.traverse(get_sources, [filter_fn])
        grouped_sources = groupby(sorted(data_sources, key=lambda x: x[0]),
                                  lambda x: x[0])
        shared_sources = []
        source_cols = {}
        for _, group in grouped_sources:
            group = list(group)
            if len(group) > 1:
                source_data = {}
                for _, plot in group:
                    source_data.update(plot.handles['source'].data)
                new_source = ColumnDataSource(source_data)
                for _, plot in group:
                    renderer = plot.handles.get('glyph_renderer')
                    if renderer is None:
                        continue
                    elif 'data_source' in renderer.properties():
                        renderer.update(data_source=new_source)
                    else:
                        renderer.update(source=new_source)
                    plot.handles['source'] = new_source
                    for callback in plot.callbacks:
                        callback.reset()
                        callback.initialize()
                shared_sources.append(new_source)
                source_cols[id(new_source)] = [c for c in new_source.data]
        self.handles['shared_sources'] = shared_sources
        self.handles['source_cols'] = source_cols
Beispiel #8
0
class Button(_ButtonBase):

    clicks = param.Integer(default=0)

    _widget_type = _BkButton
Beispiel #9
0
class SagSwellExplorer(hv.streams.Stream):

    alpha = param.Magnitude(default=0.75,
                            doc="Alpha value for the map opacity")
    plot = param.ObjectSelector(default="Sag", objects=["Sag", "Swell"])
    colormap = param.ObjectSelector(default=cm["fire"], objects=cm.values())
    numEvents = param.Range(default=(1, 300),
                            bounds=(1, 300),
                            doc="""Filter for event count""")
    ByDay = param.Boolean(False, doc="Filter By Day")
    DayNum = param.Integer(
        1,
        bounds=(1, 15))  # Stop at 15 since that's all the data we have loaded
    ByFeeder = param.Boolean(False, doc="Filter By Feeder")
    Feeder = param.ObjectSelector(
        default="28GM012002",
        objects=df.FEEDER_ID.sort_values().unique().tolist())
    BySUB = param.Boolean(False, doc="Filter By SUB")
    Substations = param.ObjectSelector(
        default="28GM", objects=df.SUB.sort_values().unique().tolist())
    maxpix = param.Integer(12)
    threshhold = param.Number(0.6, bounds=(0.1, 1.0))

    def make_view(self, x_range=None, y_range=None, **kwargs):
        #map_tiles = tiles.opts(style=dict(alpha=self.alpha), plot=options)

        points = hv.Points(
            df,
            kdims=['X_CORD', 'Y_CORD'],
            vdims=['EVENT_COUNT', 'EventType', 'SUB', 'day', 'FEEDER_ID'])

        if (self.BySUB & self.ByFeeder & self.ByDay):
            selected = points.select(EventType=self.plot,
                                     EVENT_COUNT=self.numEvents,
                                     SUB=self.Substations,
                                     day=self.DayNum,
                                     FEEDER_ID=self.Feeder)
        elif (self.BySUB & self.ByFeeder & (not self.ByDay)):
            selected = points.select(EventType=self.plot,
                                     EVENT_COUNT=self.numEvents,
                                     SUB=self.Substations,
                                     FEEDER_ID=self.Feeder)
        elif (self.BySUB & (not self.ByFeeder) & self.ByDay):
            selected = points.select(EventType=self.plot,
                                     EVENT_COUNT=self.numEvents,
                                     SUB=self.Substations,
                                     day=self.DayNum)
        elif (self.BySUB & (not self.ByFeeder) & (not self.ByDay)):
            selected = points.select(EventType=self.plot,
                                     EVENT_COUNT=self.numEvents,
                                     SUB=self.Substations)
        elif ((not self.BySUB) & self.ByFeeder & self.ByDay):
            selected = points.select(EventType=self.plot,
                                     EVENT_COUNT=self.numEvents,
                                     day=self.DayNum,
                                     FEEDER_ID=self.Feeder)
        elif ((not self.BySUB) & self.ByFeeder & (not self.ByDay)):
            selected = points.select(EventType=self.plot,
                                     EVENT_COUNT=self.numEvents,
                                     FEEDER_ID=self.Feeder)
        elif ((not self.BySUB) & (not self.ByFeeder) & self.ByDay):
            selected = points.select(EventType=self.plot,
                                     EVENT_COUNT=self.numEvents,
                                     day=self.DayNum)
        else:
            selected = points.select(EventType=self.plot,
                                     EVENT_COUNT=self.numEvents)

        SagSwellPts = datashade(selected,
                                x_sampling=1,
                                y_sampling=1,
                                cmap=self.colormap,
                                dynamic=False,
                                x_range=x_range,
                                y_range=y_range,
                                width=640,
                                height=380)
        dsss = dynspread(SagSwellPts,
                         shape='circle',
                         max_px=self.maxpix,
                         threshold=self.threshhold)
        #return map_tiles * dsss
        return dsss

    def jtdp(self, x_range, y_range, **kwargs):

        pointdec = hv.Points(df,
                             kdims=['X_CORD', 'Y_CORD'],
                             vdims=['EVENT_COUNT', 'FEEDER_ID'])
        selecteddec = pointdec.select(EventType=self.plot,
                                      EVENT_COUNT=self.numEvents)
        dm2 = decimate(
            selecteddec, x_range=x_range, y_range=y_range, dynamic=False
        ).opts(
            style={'Points': dict(alpha=0.0, color='blue', size=self.maxpix)})
        return dm2

    def dec_tab(self, x_range, y_range, bounds, **kwargs):

        #%opts Table [ fig_size=550 width=600 height=380]

        b0 = bounds[0]
        b2 = bounds[2]
        b1 = bounds[1]
        b3 = bounds[3]

        xr = bounds[2] - bounds[0]
        yr = bounds[3] - bounds[1]

        if (not ((xr < 50000) & (yr < 50000))):
            b0 = b2 = b1 = b3 = 0.0
            win32api.MessageBox(0, "SELECTED AREA TOO LARGE! ", 'dec_tab',
                                0x00001000)

        pointdec = hv.Points(df,
                             kdims=['X_CORD', 'Y_CORD'],
                             vdims=[
                                 'EVENT_COUNT', 'EventType', 'SUB', 'day',
                                 'FEEDER_ID', 'XFMR', 'Phase'
                             ])

        if (self.BySUB & self.ByFeeder & self.ByDay):
            selected = pointdec.select(X_CORD=(b0, b2),
                                       Y_CORD=(b1, b3),
                                       EventType=self.plot,
                                       EVENT_COUNT=self.numEvents,
                                       SUB=self.Substations,
                                       day=self.DayNum,
                                       FEEDER_ID=self.Feeder)
        elif (self.BySUB & self.ByFeeder & (not self.ByDay)):
            selected = pointdec.select(X_CORD=(b0, b2),
                                       Y_CORD=(b1, b3),
                                       EventType=self.plot,
                                       EVENT_COUNT=self.numEvents,
                                       SUB=self.Substations,
                                       FEEDER_ID=self.Feeder)
        elif (self.BySUB & (not self.ByFeeder) & self.ByDay):
            selected = pointdec.select(X_CORD=(b0, b2),
                                       Y_CORD=(b1, b3),
                                       EventType=self.plot,
                                       EVENT_COUNT=self.numEvents,
                                       SUB=self.Substations,
                                       day=self.DayNum)
        elif (self.BySUB & (not self.ByFeeder) & (not self.ByDay)):
            selected = pointdec.select(X_CORD=(b0, b2),
                                       Y_CORD=(b1, b3),
                                       EventType=self.plot,
                                       EVENT_COUNT=self.numEvents,
                                       SUB=self.Substations)
        elif ((not self.BySUB) & self.ByFeeder & self.ByDay):
            selected = pointdec.select(X_CORD=(b0, b2),
                                       Y_CORD=(b1, b3),
                                       EventType=self.plot,
                                       EVENT_COUNT=self.numEvents,
                                       day=self.DayNum,
                                       FEEDER_ID=self.Feeder)
        elif ((not self.BySUB) & self.ByFeeder & (not self.ByDay)):
            selected = pointdec.select(X_CORD=(b0, b2),
                                       Y_CORD=(b1, b3),
                                       EventType=self.plot,
                                       EVENT_COUNT=self.numEvents,
                                       FEEDER_ID=self.Feeder)
        elif ((not self.BySUB) & (not self.ByFeeder) & self.ByDay):
            selected = pointdec.select(X_CORD=(b0, b2),
                                       Y_CORD=(b1, b3),
                                       EventType=self.plot,
                                       EVENT_COUNT=self.numEvents,
                                       day=self.DayNum)
        else:
            selected = pointdec.select(X_CORD=(b0, b2),
                                       Y_CORD=(b1, b3),
                                       EventType=self.plot,
                                       EVENT_COUNT=self.numEvents)

        #bp=selected.select( X_CORD=(b0, b2),Y_CORD=(b1, b3)  )
        tab = hv.Table(
            selected,
            kdims=[],
            vdims=['EventType', 'SUB', 'FEEDER_ID', 'XFMR', 'Phase'])

        return tab
Beispiel #10
0
class Dial(ValueIndicator):
    """
    A `Dial` represents a value in some range as a position on an
    annular dial. It is similar to a `Gauge` but more minimal
    visually.

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

    :Example:

    >>> Dial(name='Speed', value=79, format="{value} km/h", bounds=(0, 200), colors=[(0.4, 'green'), (1, 'red')])
    """

    annulus_width = param.Number(default=0.2, doc="""
      Width of the radial annulus as a fraction of the total.""")

    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 Dial, specified as a list of tuples
      of the fractional threshold and the color to switch to.""")

    default_color = param.String(default='lightblue', doc="""
      Color of the radial annulus if not color thresholds are supplied.""")

    end_angle = param.Number(default=25, doc="""
      Angle at which the dial ends.""")

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

    height = param.Integer(default=250, bounds=(1, None))

    nan_format = param.String(default='-', doc="""
      How to format nan values.""")

    needle_color = param.String(default='black', doc="""
      Color of the Dial needle.""")

    needle_width = param.Number(default=0.1, doc="""
      Radial width of the needle.""")

    start_angle = param.Number(default=-205, doc="""
      Angle at which the dial starts.""")

    tick_size = param.String(default=None, doc="""
      Font size of the Dial min/max labels.""")

    title_size = param.String(default=None, doc="""
      Font size of the Dial title.""")

    unfilled_color = param.String(default='whitesmoke', doc="""
      Color of the unfilled region of the Dial.""")

    value_size = param.String(default=None, doc="""
      Font size of the Dial value label.""")

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

    width = param.Integer(default=250, bounds=(1, None))

    _manual_params = [
        'value', 'start_angle', 'end_angle', 'bounds',
        'annulus_width', 'format', 'background', 'needle_width',
        'tick_size', 'title_size', 'value_size', 'colors',
        'default_color', 'unfilled_color', 'height',
        'width', 'nan_format', 'needle_color'
    ]

    _data_params = _manual_params

    _rename = {'background': 'background_fill_color'}

    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 _get_data(self):
        vmin, vmax = self.bounds
        value = self.value
        if value is None:
            value = float('nan')
        fraction = (value-vmin)/(vmax-vmin)
        start = (np.radians(360-self.start_angle) - pi % (2*pi)) + pi
        end = (np.radians(360-self.end_angle) - pi % (2*pi)) + pi
        distance = (abs(end-start) % (pi*2))
        if end>start:
            distance = (pi*2)-distance
        radial_fraction = distance*fraction
        angle = start if np.isnan(fraction) else (start-radial_fraction)
        inner_radius = 1-self.annulus_width

        color = self.default_color
        for val, clr in (self.colors or [])[::-1]:
            if fraction <= val:
                color = clr

        annulus_data = {
            'starts': np.array([start, angle]),
            'ends' :  np.array([angle, end]),
            'color':  [color, self.unfilled_color],
            'radius': np.array([inner_radius, inner_radius])
        }

        x0s, y0s, x1s, y1s, clrs = [], [], [], [], []
        colors = self.colors or []
        for (val, _), (_, clr) in zip(colors[:-1], colors[1:]):
            tangle = start-(distance*val)
            if (vmin + val * (vmax-vmin)) <= value:
                continue
            x0, y0 = np.cos(tangle), np.sin(tangle)
            x1, y1 = x0*inner_radius, y0*inner_radius
            x0s.append(x0)
            y0s.append(y0)
            x1s.append(x1)
            y1s.append(y1)
            clrs.append(clr)

        threshold_data = {
            'x0': x0s, 'y0': y0s, 'x1': x1s, 'y1': y1s, 'color': clrs
        }

        center_radius = 1-self.annulus_width/2.
        x, y = np.cos(angle)*center_radius, np.sin(angle)*center_radius
        needle_start = pi+angle-(self.needle_width/2.)
        needle_end = pi+angle+(self.needle_width/2.)
        needle_data = {
            'x':      np.array([x]),
            'y':      np.array([y]),
            'start':  np.array([needle_start]),
            'end':    np.array([needle_end]),
            'radius': np.array([center_radius])
        }

        value = self.format.format(value=value).replace('nan', self.nan_format)
        min_value = self.format.format(value=vmin)
        max_value = self.format.format(value=vmax)
        tminx, tminy = np.cos(start)*center_radius, np.sin(start)*center_radius
        tmaxx, tmaxy = np.cos(end)*center_radius, np.sin(end)*center_radius
        tmin_angle, tmax_angle = start+pi, end+pi % pi
        scale = (self.height/400)
        title_size = self.title_size if self.title_size else '%spt' % (scale*32)
        value_size = self.value_size if self.value_size else '%spt' % (scale*48)
        tick_size = self.tick_size if self.tick_size else '%spt' % (scale*18)

        text_data= {
            'x':    np.array([0, 0, tminx, tmaxx]),
            'y':    np.array([-.2, -.5, tminy, tmaxy]),
            'text': [self.name, value, min_value, max_value],
            'rot':  np.array([0, 0, tmin_angle, tmax_angle]),
            'size': [title_size, value_size, tick_size, tick_size],
            'color': ['black', color, 'black', 'black']
        }
        return annulus_data, needle_data, threshold_data, text_data

    def _get_model(self, doc, root=None, parent=None, comm=None):
        params = self._process_param_change(self._init_params())
        model = figure(
            x_range=(-1,1), y_range=(-1,1), tools=[],
            outline_line_color=None, toolbar_location=None,
            width=self.width, height=self.height, **params
        )
        model.xaxis.visible = False
        model.yaxis.visible = False
        model.grid.visible = False

        annulus, needle, threshold, text = self._get_data()

        # Draw annulus
        annulus_source = ColumnDataSource(data=annulus, name='annulus_source')
        model.annular_wedge(
            x=0, y=0, inner_radius='radius', outer_radius=1, start_angle='starts',
            end_angle='ends', line_color='gray', color='color', direction='clock',
            source=annulus_source
        )

        # Draw needle
        needle_source = ColumnDataSource(data=needle, name='needle_source')
        model.wedge(
            x='x', y='y', radius='radius', start_angle='start', end_angle='end',
            fill_color=self.needle_color, line_color=self.needle_color,
            source=needle_source, name='needle_renderer'
        )

        # Draw thresholds
        threshold_source = ColumnDataSource(data=threshold, name='threshold_source')
        model.segment(
            x0='x0', x1='x1', y0='y0', y1='y1', line_color='color', source=threshold_source,
            line_width=2
        )

        # Draw labels
        text_source = ColumnDataSource(data=text, name='label_source')
        model.text(
            x='x', y='y', text='text', font_size='size', text_align='center',
            text_color='color', source=text_source, text_baseline='top',
            angle='rot'
        )

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

    def _manual_update(self, events, model, doc, root, parent, comm):
        update_data = False
        for event in events:
            if event.name in ('width', 'height'):
                model.update(**{event.name: event.new})
            if event.name in self._data_params:
                update_data = True
            elif event.name == 'needle_color':
                needle_r = model.select(name='needle_renderer')
                needle_r.glyph.line_color = event.new
                needle_r.glyph.fill_color = event.new
        if not update_data:
            return
        annulus, needle, threshold, labels = self._get_data()
        model.select(name='annulus_source').data.update(annulus)
        model.select(name='needle_source').data.update(needle)
        model.select(name='threshold_source').data.update(threshold)
        model.select(name='label_source').data.update(labels)
Beispiel #11
0
class LinearGauge(ValueIndicator):
    """
    A LinearGauge represents a value in some range as a position on an
    linear scale. It is similar to a Dial/Gauge but visually more
    compact.

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

    :Example:

    >>> LinearGauge(value=30, default_color='red', bounds=(0, 100))
    """

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

    default_color = param.String(default='lightblue', doc="""
      Color of the radial annulus if not color thresholds are supplied.""")

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

    format = param.String(default='{value:.2f}%', doc="""
      Formatting string for the value indicator and lower/upper bounds.""")

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

    horizontal = param.Boolean(default=False, doc="""
      Whether to display the linear gauge horizontally.""")

    nan_format = param.String(default='-', doc="""
      How to format nan values.""")

    needle_color = param.String(default='black', doc="""
      Color of the gauge needle.""")

    show_boundaries = param.Boolean(default=False, doc="""
      Whether to show the boundaries between colored regions.""")

    unfilled_color = param.String(default='whitesmoke', doc="""
      Color of the unfilled region of the LinearGauge.""")

    title_size = param.String(default=None, doc="""
      Font size of the gauge title.""")

    tick_size = param.String(default=None, doc="""
      Font size of the gauge tick labels.""")

    value_size = param.String(default=None, doc="""
      Font size of the gauge value label.""")

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

    width = param.Integer(default=125, bounds=(1, None))

    _manual_params = [
        'value', 'bounds', 'format', 'title_size', 'value_size',
        'horizontal', 'height', 'colors', 'tick_size',
        'unfilled_color', 'width', 'nan_format', 'needle_color'
    ]

    _data_params = [
        'value', 'bounds', 'format', 'nan_format', 'needle_color',
        'colors'
    ]

    _rerender_params = ['horizontal']

    _rename = {
        'background': 'background_fill_color', 'show_boundaries': None,
        'default_color': None
    }

    _updates = False

    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

    @property
    def _color_intervals(self):
        vmin, vmax = self.bounds
        value = self.value
        ncolors = len(self.colors) if self.colors else 1
        interval = (vmax-vmin)
        if math.isfinite(value):
            fraction = value / interval
            idx = round(fraction * (ncolors-1))
        else:
            fraction = 0
            idx = 0
        if not self.colors:
            intervals = [
                (fraction, self.default_color)
            ]
            intervals.append((1, self.unfilled_color))
        elif self.show_boundaries:
            intervals = [
                c if isinstance(c, tuple) else ((i+1)/(ncolors), c)
                for i, c in enumerate(self.colors)
            ]
        else:
            intervals = [
                self.colors[idx] if isinstance(self.colors[0], tuple)
                else (fraction, self.colors[idx])
            ]
            intervals.append((1, self.unfilled_color))
        return intervals

    def _get_data(self):
        vmin, vmax = self.bounds
        value = self.value
        interval = (vmax-vmin)
        colors, values = [], [vmin]
        above = False
        prev = None
        for (v, color) in self._color_intervals:
            val = v*interval
            if val == prev:
                continue
            elif val > value:
                if not above:
                    colors.append(color)
                    values.append(value)
                    above = True
                color = self.unfilled_color
            colors.append(color)
            values.append(val)
            prev = val
        value = self.format.format(value=value).replace('nan', self.nan_format)
        return (
            {'y0': values[:-1], 'y1': values[1:], 'color': colors},
            {'y': [self.value], 'text': [value]}
        )

    def _get_model(self, doc, root=None, parent=None, comm=None):
        params = self._process_param_change(self._init_params())
        model = figure(
            outline_line_color=None, toolbar_location=None, tools=[],
            x_axis_location='above', y_axis_location='right', **params
        )
        model.grid.visible = False
        model.xaxis.major_label_standoff = 2
        model.yaxis.major_label_standoff = 2
        model.xaxis.axis_label_standoff = 2
        model.yaxis.axis_label_standoff = 2
        self._update_name(model)
        self._update_title_size(model)
        self._update_tick_size(model)
        self._update_figure(model)
        self._update_axes(model)
        self._update_renderers(model)
        self._update_bounds(model)
        if root is None:
            root = model
        self._models[root.ref['id']] = (model, parent)
        return model

    def _update_name(self, model):
        model.xaxis.axis_label = self.name
        model.yaxis.axis_label = self.name

    def _update_title_size(self, model):
        title_size = self.title_size or f'{self.width/6}px'
        model.xaxis.axis_label_text_font_size = title_size
        model.yaxis.axis_label_text_font_size = title_size

    def _update_tick_size(self, model):
        tick_size = self.tick_size or f'{self.width/9}px'
        model.xaxis.major_label_text_font_size = tick_size
        model.yaxis.major_label_text_font_size = tick_size

    def _update_renderers(self, model):
        model.renderers = []
        data, needle_data = self._get_data()
        bar_source = ColumnDataSource(data=data, name='bar_source')
        needle_source = ColumnDataSource(data=needle_data, name='needle_source')
        if self.horizontal:
            model.hbar(
                y=0.1, left='y0', right='y1', height=1, color='color',
                source=bar_source
            )
            wedge_params = {'y': 0.5, 'x': 'y', 'angle': np.deg2rad(180)}
            text_params = {
                'y': -0.4, 'x': 0, 'text_align': 'left',
                'text_baseline': 'top'
            }
        else:
            model.vbar(
                x=0.1, bottom='y0', top='y1', width=0.9, color='color',
                source=bar_source
            )
            wedge_params = {'x': 0.5, 'y': 'y', 'angle': np.deg2rad(90)}
            text_params = {
                'x': -0.4, 'y': 0, 'text_align': 'left',
                'text_baseline': 'bottom', 'angle': np.deg2rad(90)
            }
        model.scatter(
            fill_color=self.needle_color, line_color=self.needle_color,
            source=needle_source, name='needle_renderer', marker='triangle',
            size=int(self.width/8), level='overlay', **wedge_params
        )
        value_size = self.value_size or f'{self.width/8}px'
        model.text(
            text='text', source=needle_source, text_font_size=value_size,
            **text_params
        )

    def _update_bounds(self, model):
        if self.horizontal:
            x_range, y_range = tuple(self.bounds), (-0.8, 0.5)
        else:
            x_range, y_range = (-0.8, 0.5), tuple(self.bounds)
        model.x_range.update(start=x_range[0], end=x_range[1])
        model.y_range.update(start=y_range[0], end=y_range[1])

    def _update_axes(self, model):
        vmin, vmax = self.bounds
        interval = (vmax-vmin)
        if self.show_boundaries:
            ticks = [vmin] + [v*interval for (v, _) in self._color_intervals]
        else:
            ticks = [vmin, vmax]
        ticker = FixedTicker(ticks=ticks)
        if self.horizontal:
            model.xaxis.visible = True
            model.xaxis.ticker = ticker
            model.yaxis.visible = False
        else:
            model.xaxis.visible = False
            model.yaxis.visible = True
            model.yaxis.ticker = ticker

    def _update_figure(self, model):
        params = self._process_param_change(self._init_params())
        if self.horizontal:
            params.update(width=self.height, height=self.width)
        else:
            params.update(width=self.width, height=self.height)
        model.update(**params)

    def _manual_update(self, events, model, doc, root, parent, comm):
        update_data = False
        for event in events:
            if event.name in ('width', 'height'):
                self._update_figure(model)
            elif event.name == 'bounds':
                self._update_bounds(model)
                self._update_renderers(model)
            elif event.name in self._data_params:
                update_data = True
            elif event.name == 'needle_color':
                needle_r = model.select(name='needle_renderer')
                needle_r.glyph.line_color = event.new
                needle_r.glyph.fill_color = event.new
            elif event.name == 'horizontal':
                self._update_bounds(model)
                self._update_figure(model)
                self._update_axes(model)
                self._update_renderers(model)
            elif event.name == 'name':
                self._update_name(model)
            elif event.name == 'tick_size':
                self._update_tick_size(model)
            elif event.name == 'title_size':
                self._update_title_size(model)
        if not update_data:
            return
        data, needle_data = self._get_data()
        model.select(name='bar_source').data.update(data)
        model.select(name='needle_source').data.update(needle_data)
Beispiel #12
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
Beispiel #13
0
class Tqdm(Indicator):
    """
    The `Tqdm` indicator wraps the well known `tqdm` progress
    indicator and displays the progress towards some target in your
    Panel app.

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

    :Example:

    >>> tqdm = Tqdm()
    >>> for i in tqdm(range(0,10), desc="My loop", leave=True, colour='#666666'):
    ...     time.sleep(timeout)
    """

    value = param.Integer(default=0, bounds=(-1, None), doc="""
        The current value of the progress bar. If set to -1 the progress
        bar will be indeterminate and animate depending on the active
        parameter.""")

    layout = param.ClassSelector(class_=(Column, Row), precedence=-1, constant=True, doc="""
        The layout for the text and progress indicator.""",)

    max = param.Integer(default=100, doc="The maximum value of the progress bar.")

    progress = param.ClassSelector(class_=Progress, precedence=-1, doc="""
        The Progress indicator used to display the progress.""",)

    text = param.String(default='', doc="""
        The current tqdm style progress text.""")

    text_pane = param.ClassSelector(class_=Str, precedence=-1, doc="""
        The pane to display the text to.""")

    margin = param.Parameter(default=0, doc="""
        Allows to create additional space around the component. May
        be specified as a two-tuple of the form (vertical, horizontal)
        or a four-tuple (top, right, bottom, left).""")

    width = param.Integer(default=400, bounds=(0, None), doc="""
        The width of the component (in pixels). This can be either
        fixed or preferred width, depending on width sizing policy.""")

    write_to_console = param.Boolean(default=False, doc="""
        Whether or not to also write to the console.""")

    _layouts = {Row: 'row', Column: 'column'}

    _rename = {'value': None, 'min': None, 'max': None, 'text': None}

    def __init__(self, **params):
        layout = params.pop('layout', 'column')
        layout = self._layouts.get(layout, layout)
        if "text_pane" not in params:
            sizing_mode = 'stretch_width' if layout == 'column' else 'fixed'
            params["text_pane"] = Str(
                None, min_height=20, min_width=280, sizing_mode=sizing_mode,
                margin=MARGIN["text_pane"][layout],
            )
        if "progress" not in params:
            params["progress"] = Progress(
                active=False,
                sizing_mode="stretch_width",
                min_width=100,
                margin=MARGIN["progress"][layout],
            )

        layout_params = {p: params.get(p, getattr(self, p))  for p in Viewable.param}
        if layout == 'row' or layout is Row:
            params['layout'] = Row(
                params['progress'], params['text_pane'], **layout_params
            )
        else:
            params['layout'] = Column(
                params['text_pane'], params['progress'], **layout_params
            )
        super().__init__(**params)

        self.param.watch(self._update_layout, list(Viewable.param))

        self.progress.max = self.max
        self.progress.value = self.value
        self.text_pane.object = self.text

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

    def _cleanup(self, root):
        super()._cleanup(root)
        self.layout._cleanup(root)

    def _update_layout(self, *events):
        self.layout.param.update(**{event.name: event.new for event in events})

    @param.depends("text", watch=True)
    def _update_text(self):
        if self.text_pane:
            self.text_pane.object = self.text

    @param.depends("value", watch=True)
    def _update_value(self):
        if self.progress:
            self.progress.value = self.value

    @param.depends("max", watch=True)
    def _update_max(self):
        if self.progress:
            self.progress.max = self.max

    def __call__(self, *args, **kwargs):
        kwargs['indicator'] = self
        if not self.write_to_console:
            f = open(os.devnull, 'w')
            kwargs['file'] = f
        return ptqdm(*args, **kwargs)

    __call__.__doc__ = ptqdm.__doc__

    def pandas(self, *args, **kwargs):
        kwargs['indicator'] = self
        if not self.write_to_console and 'file' not in kwargs:
            f = open(os.devnull, 'w')
            kwargs['file'] = f
        return ptqdm.pandas(*args, **kwargs)

    def reset(self):
        """Resets the parameters"""
        self.value = self.param.value.default
        self.text = self.param.text.default
Beispiel #14
0
class FileArchive(Archive):
    """
    A file archive stores files on disk, either unpacked in a
    directory or in an archive format (e.g. a zip file).
    """

    exporters = param.List(default=[Pickler],
                           doc="""
        The exporter functions used to convert HoloViews objects into
        the appropriate format(s).""")

    dimension_formatter = param.String("{name}_{range}",
                                       doc="""
        A string formatter for the output file based on the
        supplied HoloViews objects dimension names and values.
        Valid fields are the {name}, {range} and {unit} of the
        dimensions.""")

    object_formatter = param.Callable(default=simple_name_generator,
                                      doc="""
        Callable that given an object returns a string suitable for
        inclusion in file and directory names. This is what generates
        the value used in the {obj} field of the filename
        formatter.""")

    filename_formatter = param.String('{dimensions},{obj}',
                                      doc="""
        A string formatter for output filename based on the HoloViews
        object that is being rendered to disk.

        The available fields are the {type}, {group}, {label}, {obj}
        of the holoviews object added to the archive as well as
        {timestamp}, {obj} and {SHA}. The {timestamp} is the export
        timestamp using timestamp_format, {obj} is the object
        representation as returned by object_formatter and {SHA} is
        the SHA of the {obj} value used to compress it into a shorter
        string.""")

    timestamp_format = param.String("%Y_%m_%d-%H_%M_%S",
                                    doc="""
        The timestamp format that will be substituted for the
        {timestamp} field in the export name.""")

    root = param.String('.',
                        doc="""
        The root directory in which the output directory is
        located. May be an absolute or relative path.""")

    archive_format = param.ObjectSelector('zip',
                                          objects=['zip', 'tar'],
                                          doc="""
        The archive format to use if there are multiple files and pack
        is set to True. Supported formats include 'zip' and 'tar'.""")

    pack = param.Boolean(default=False,
                         doc="""
        Whether or not to pack to contents into the specified archive
        format. If pack is False, the contents will be output to a
        directory.

        Note that if there is only a single file in the archive, no
        packing will occur and no directory is created. Instead, the
        file is treated as a single-file archive.""")

    export_name = param.String(default='{timestamp}',
                               doc="""
        The name assigned to the overall export. If an archive file is
        used, this is the correspond filename (e.g of the exporter zip
        file). Alternatively, if unpack=False, this is the name of the
        output directory. Lastly, for archives of a single file, this
        is the basename of the output file.

        The {timestamp} field is available to include the timestamp at
        the time of export in the chosen timestamp format.""")

    unique_name = param.Boolean(default=False,
                                doc="""
       Whether the export name should be made unique with a numeric
       suffix. If set to False, any existing export of the same name
       will be removed and replaced.""")

    max_filename = param.Integer(default=100,
                                 bounds=(0, None),
                                 doc="""
       Maximum length to enforce on generated filenames.  100 is the
       practical maximum for zip and tar file generation, but you may
       wish to use a lower value to avoid long filenames.""")

    flush_archive = param.Boolean(default=True,
                                  doc="""
       Flushed the contents of the archive after export.
       """)

    ffields = {
        'type', 'group', 'label', 'obj', 'SHA', 'timestamp', 'dimensions'
    }
    efields = {'timestamp'}

    @classmethod
    def parse_fields(cls, formatter):
        "Returns the format fields otherwise raise exception"
        if formatter is None: return []
        try:
            parse = list(string.Formatter().parse(formatter))
            return set(f for f in list(zip(*parse))[1] if f is not None)
        except:
            raise SyntaxError("Could not parse formatter %r" % formatter)

    def __init__(self, **params):
        super(FileArchive, self).__init__(**params)
        #  Items with key: (basename,ext) and value: (data, info)
        self._files = OrderedDict()
        self._validate_formatters()

    def _dim_formatter(self, obj):
        if not obj: return ''
        key_dims = obj.traverse(lambda x: x.kdims, [UniformNdMapping])
        constant_dims = obj.traverse(lambda x: x.cdims)
        dims = []
        map(dims.extend, key_dims + constant_dims)
        dims = unique_iterator(dims)
        dim_strings = []
        for dim in dims:
            lower, upper = obj.range(dim.name)
            lower, upper = (dim.pprint_value(lower), dim.pprint_value(upper))
            if lower == upper:
                range = dim.pprint_value(lower)
            else:
                range = "%s-%s" % (lower, upper)
            formatters = {'name': dim.name, 'range': range, 'unit': dim.unit}
            dim_strings.append(self.dimension_formatter.format(**formatters))
        return '_'.join(dim_strings)

    def _validate_formatters(self):
        if not self.parse_fields(self.filename_formatter).issubset(
                self.ffields):
            raise Exception("Valid filename fields are: %s" %
                            ','.join(sorted(self.ffields)))
        elif not self.parse_fields(self.export_name).issubset(self.efields):
            raise Exception("Valid export fields are: %s" %
                            ','.join(sorted(self.efields)))
        try:
            time.strftime(self.timestamp_format, tuple(time.localtime()))
        except:
            raise Exception("Timestamp format invalid")

    def add(self, obj=None, filename=None, data=None, info={}):
        """
        If a filename is supplied, it will be used. Otherwise, a
        filename will be generated from the supplied object. Note that
        if the explicit filename uses the {timestamp} field, it will
        be formatted upon export.

        The data to be archived is either supplied explicitly as
        'data' or automatically rendered from the object.
        """
        if [filename, obj] == [None, None]:
            raise Exception("Either filename or a HoloViews object is "
                            "needed to create an entry in the archive.")
        elif obj is None and not self.parse_fields(filename).issubset(
            {'timestamp'}):
            raise Exception(
                "Only the {timestamp} formatter may be used unless an object is supplied."
            )
        elif [obj, data] == [None, None]:
            raise Exception("Either an object or explicit data must be "
                            "supplied to create an entry in the archive.")
        elif data and 'mime_type' not in info:
            raise Exception(
                "The mime-type must be supplied in the info dictionary "
                "when supplying data directly")

        self._validate_formatters()

        entries = []
        if data is None:
            for exporter in self.exporters:
                rendered = exporter(obj)
                if rendered is None: continue
                (data, new_info) = rendered
                info = dict(info, **new_info)
                entries.append((data, info))
        else:
            entries.append((data, info))

        for (data, info) in entries:
            self._add_content(obj, data, info, filename=filename)

    def _add_content(self, obj, data, info, filename=None):
        (unique_key, ext) = self._compute_filename(obj,
                                                   info,
                                                   filename=filename)
        self._files[(unique_key, ext)] = (data, info)

    def _compute_filename(self, obj, info, filename=None):
        if filename is None:
            hashfn = sha256()
            obj_str = 'None' if obj is None else self.object_formatter(obj)
            dimensions = self._dim_formatter(obj)
            dimensions = dimensions if dimensions else ''

            hashfn.update(obj_str.encode('utf-8'))
            format_values = {
                'timestamp': '{timestamp}',
                'dimensions': dimensions,
                'group': getattr(obj, 'group', 'no-group'),
                'label': getattr(obj, 'label', 'no-label'),
                'type': obj.__class__.__name__,
                'obj': obj_str,
                'SHA': hashfn.hexdigest()
            }

            filename = self._format(self.filename_formatter,
                                    dict(info, **format_values))

        filename = self._normalize_name(filename)
        ext = info.get('file-ext', '')
        (unique_key, ext) = self._unique_name(filename,
                                              ext,
                                              self._files.keys(),
                                              force=True)
        return (unique_key, ext)

    def _zip_archive(self, export_name, files, root):
        archname = '.'.join(self._unique_name(export_name, 'zip', root))
        with zipfile.ZipFile(os.path.join(root, archname), 'w') as zipf:
            for (basename, ext), entry in files:
                filename = self._truncate_name(basename, ext)
                zipf.writestr(('%s/%s' % (export_name, filename)),
                              Exporter.encode(entry))

    def _tar_archive(self, export_name, files, root):
        archname = '.'.join(self._unique_name(export_name, 'tar', root))
        with tarfile.TarFile(os.path.join(root, archname), 'w') as tarf:
            for (basename, ext), entry in files:
                filename = self._truncate_name(basename, ext)
                tarinfo = tarfile.TarInfo('%s/%s' % (export_name, filename))
                filedata = Exporter.encode(entry)
                tarinfo.size = len(filedata)
                tarf.addfile(tarinfo, BytesIO(filedata))

    def _single_file_archive(self, export_name, files, root):
        ((basename, ext), entry) = files[0]
        full_fname = '%s_%s' % (export_name, basename)
        (unique_name, ext) = self._unique_name(full_fname, ext, root)
        filename = self._truncate_name(self._normalize_name(unique_name),
                                       ext=ext)
        fpath = os.path.join(root, filename)
        with open(fpath, 'wb') as f:
            f.write(Exporter.encode(entry))

    def _directory_archive(self, export_name, files, root):
        output_dir = os.path.join(root,
                                  self._unique_name(export_name, '', root)[0])
        if os.path.isdir(output_dir):
            shutil.rmtree(output_dir)
        os.makedirs(output_dir)

        for (basename, ext), entry in files:
            filename = self._truncate_name(basename, ext)
            fpath = os.path.join(output_dir, filename)
            with open(fpath, 'wb') as f:
                f.write(Exporter.encode(entry))

    def _unique_name(self, basename, ext, existing, force=False):
        """
        Find a unique basename for a new file/key where existing is
        either a list of (basename, ext) pairs or an absolute path to
        a directory.

        By default, uniqueness is enforced dependning on the state of
        the unique_name parameter (for export names). If force is
        True, this parameter is ignored and uniqueness is guaranteed.
        """
        skip = False if force else (not self.unique_name)
        if skip: return (basename, ext)
        ext = '' if ext is None else ext
        if isinstance(existing, str):
            split = [
                os.path.splitext(el)
                for el in os.listdir(os.path.abspath(existing))
            ]
            existing = [(n, ex if not ex else ex[1:]) for (n, ex) in split]
        new_name, counter = basename, 1
        while (new_name, ext) in existing:
            new_name = basename + '-' + str(counter)
            counter += 1
        return (new_name, ext)

    def _truncate_name(self,
                       basename,
                       ext='',
                       tail=10,
                       join='...',
                       maxlen=None):
        maxlen = self.max_filename if maxlen is None else maxlen
        max_len = maxlen - len(ext)
        if len(basename) > max_len:
            start = basename[:max_len - (tail + len(join))]
            end = basename[-tail:]
            basename = start + join + end
        filename = '%s.%s' % (basename, ext) if ext else basename

        return filename

    def _normalize_name(self, basename):
        basename = re.sub('-+', '-', basename)
        basename = re.sub('^[-,_]', '', basename)
        return basename.replace(' ', '_')

    def export(self, timestamp=None, info={}):
        """
        Export the archive, directory or file.
        """
        tval = tuple(time.localtime()) if timestamp is None else timestamp
        tstamp = time.strftime(self.timestamp_format, tval)

        info = dict(info, timestamp=tstamp)
        export_name = self._format(self.export_name, info)
        files = [((self._format(base, info), ext), val)
                 for ((base, ext), val) in self._files.items()]
        root = os.path.abspath(self.root)
        # Make directory and populate if multiple files and not packed
        if len(self) > 1 and not self.pack:
            self._directory_archive(export_name, files, root)
        elif len(files) == 1:
            self._single_file_archive(export_name, files, root)
        elif self.archive_format == 'zip':
            self._zip_archive(export_name, files, root)
        elif self.archive_format == 'tar':
            self._tar_archive(export_name, files, root)
        if self.flush_archive:
            self._files = OrderedDict()

    def _format(self, formatter, info):
        filtered = {
            k: v
            for k, v in info.items() if k in self.parse_fields(formatter)
        }
        return formatter.format(**filtered)

    def __len__(self):
        "The number of files currently specified in the archive"
        return len(self._files)

    def __repr__(self):
        return self.pprint()

    def contents(self, maxlen=70):
        "Print the current (unexported) contents of the archive"
        lines = []
        if len(self._files) == 0:
            print("Empty %s" % self.__class__.__name__)
            return

        fnames = [self._truncate_name(maxlen=maxlen, *k) for k in self._files]
        max_len = max([len(f) for f in fnames])
        for name, v in zip(fnames, self._files.values()):
            mime_type = v[1].get('mime_type', 'no mime type')
            lines.append('%s : %s' % (name.ljust(max_len), mime_type))
        print('\n'.join(lines))

    def listing(self):
        "Return a list of filename entries currently in the archive"
        return [
            '.'.join([f, ext]) if ext else f
            for (f, ext) in self._files.keys()
        ]
Beispiel #15
0
class Matplotlib(PNG, IPyWidget):
    """
    A Matplotlib pane renders a matplotlib figure to png and wraps the
    base64 encoded data in a bokeh Div model. The size of the image in
    pixels is determined by scaling the size of the figure in inches
    by a dpi of 72, increasing the dpi therefore controls the
    resolution of the image not the displayed size.
    """

    dpi = param.Integer(default=144,
                        bounds=(1, None),
                        doc="""
        Scales the dpi of the matplotlib figure.""")

    interactive = param.Boolean(default=False, constant=True, doc="""
    """)

    tight = param.Boolean(default=False,
                          doc="""
        Automatically adjust the figure size to fit the
        subplots and other artist elements.""")

    _rerender_params = PNG._rerender_params + ['object', 'dpi', 'tight']

    @classmethod
    def applies(cls, obj):
        if 'matplotlib' not in sys.modules:
            return False
        from matplotlib.figure import Figure
        is_fig = isinstance(obj, Figure)
        if is_fig and obj.canvas is None:
            raise ValueError('Matplotlib figure has no canvas and '
                             'cannot be rendered.')
        return is_fig

    def __init__(self, object=None, **params):
        super(Matplotlib, self).__init__(object, **params)
        self._managers = {}

    def _get_widget(self, fig):
        import matplotlib
        old_backend = getattr(matplotlib.backends, 'backend', 'agg')

        from ipympl.backend_nbagg import FigureManager, Canvas, is_interactive
        from matplotlib._pylab_helpers import Gcf

        matplotlib.use(old_backend)

        def closer(event):
            Gcf.destroy(0)

        canvas = Canvas(fig)
        fig.patch.set_alpha(0)
        manager = FigureManager(canvas, 0)

        if is_interactive():
            fig.canvas.draw_idle()

        canvas.mpl_connect('close_event', closer)
        return manager

    def _get_model(self, doc, root=None, parent=None, comm=None):
        if not self.interactive:
            return PNG._get_model(self, doc, root, parent, comm)
        self.object.set_dpi(self.dpi)
        manager = self._get_widget(self.object)
        props = self._process_param_change(self._init_properties())
        kwargs = {
            k: v
            for k, v in props.items()
            if k not in self._rerender_params + ['interactive']
        }
        model = self._get_ipywidget(manager.canvas, doc, root, comm, **kwargs)
        if root is None:
            root = model
        self._models[root.ref['id']] = (model, parent)
        self._managers[root.ref['id']] = manager
        return model

    def _update(self, ref=None, model=None):
        if not self.interactive:
            model.update(**self._get_properties())
            return
        manager = self._managers[ref]
        if self.object is not manager.canvas.figure:
            self.object.set_dpi(self.dpi)
            self.object.patch.set_alpha(0)
            manager.canvas.figure = self.object
            self.object.set_canvas(manager.canvas)
            event = {
                'width': manager.canvas._width,
                'height': manager.canvas._height
            }
            manager.canvas.handle_resize(event)
        manager.canvas.draw_idle()

    def _imgshape(self, data):
        """Calculate and return image width,height"""
        w, h = self.object.get_size_inches()
        return int(w * 72), int(h * 72)

    def _img(self):
        self.object.set_dpi(self.dpi)
        b = BytesIO()

        if self.tight:
            bbox_inches = 'tight'
        else:
            bbox_inches = None

        self.object.canvas.print_figure(b, bbox_inches=bbox_inches)
        return b.getvalue()
Beispiel #16
0
class histogram(Operation):
    """
    Returns a Histogram of the input element data, binned into
    num_bins over the bin_range (if specified) along the specified
    dimension.
    """

    bin_range = param.NumericTuple(default=None,
                                   length=2,
                                   doc="""
      Specifies the range within which to compute the bins.""")

    bins = param.ClassSelector(default=None,
                               class_=(np.ndarray, list, tuple, str),
                               doc="""
      An explicit set of bin edges or a method to find the optimal
      set of bin edges, e.g. 'auto', 'fd', 'scott' etc. For more
      documentation on these approaches see the np.histogram_bin_edges
      documentation.""")

    cumulative = param.Boolean(default=False,
                               doc="""
      Whether to compute the cumulative histogram""")

    dimension = param.String(default=None,
                             doc="""
      Along which dimension of the Element to compute the histogram.""")

    frequency_label = param.String(default=None,
                                   doc="""
      Format string defining the label of the frequency dimension of the Histogram."""
                                   )

    groupby = param.ClassSelector(default=None,
                                  class_=(basestring, Dimension),
                                  doc="""
      Defines a dimension to group the Histogram returning an NdOverlay of Histograms."""
                                  )

    log = param.Boolean(default=False,
                        doc="""
      Whether to use base 10 logarithmic samples for the bin edges.""")

    mean_weighted = param.Boolean(default=False,
                                  doc="""
      Whether the weighted frequencies are averaged.""")

    normed = param.ObjectSelector(default=True,
                                  objects=[True, False, 'integral', 'height'],
                                  doc="""
      Controls normalization behavior.  If `True` or `'integral'`, then
      `density=True` is passed to np.histogram, and the distribution
      is normalized such that the integral is unity.  If `False`,
      then the frequencies will be raw counts. If `'height'`, then the
      frequencies are normalized such that the max bin height is unity.""")

    nonzero = param.Boolean(default=False,
                            doc="""
      Whether to use only nonzero values when computing the histogram""")

    num_bins = param.Integer(default=20,
                             doc="""
      Number of bins in the histogram .""")

    weight_dimension = param.String(default=None,
                                    doc="""
       Name of the dimension the weighting should be drawn from""")

    style_prefix = param.String(default=None,
                                allow_None=None,
                                doc="""
      Used for setting a common style for histograms in a HoloMap or AdjointLayout."""
                                )

    def _process(self, element, key=None):
        if self.p.groupby:
            if not isinstance(element, Dataset):
                raise ValueError(
                    'Cannot use histogram groupby on non-Dataset Element')
            grouped = element.groupby(self.p.groupby,
                                      group_type=Dataset,
                                      container_type=NdOverlay)
            self.p.groupby = None
            return grouped.map(self._process, Dataset)

        normed = False if self.p.mean_weighted and self.p.weight_dimension else self.p.normed
        if self.p.dimension:
            selected_dim = self.p.dimension
        else:
            selected_dim = [d.name for d in element.vdims + element.kdims][0]
        dim = element.get_dimension(selected_dim)

        if hasattr(element, 'interface'):
            data = element.interface.values(element,
                                            selected_dim,
                                            compute=False)
        else:
            data = element.dimension_values(selected_dim)

        is_datetime = isdatetime(data)
        if is_datetime:
            data = data.astype('datetime64[ns]').astype('int64')

        # Handle different datatypes
        is_finite = isfinite
        is_cupy = is_cupy_array(data)
        if is_cupy:
            import cupy
            full_cupy_support = LooseVersion(cupy.__version__) > '8.0'
            if not full_cupy_support and (normed or self.p.weight_dimension):
                data = cupy.asnumpy(data)
                is_cupy = False
            else:
                is_finite = cupy.isfinite

        # Mask data
        if is_ibis_expr(data):
            mask = data.notnull()
            if self.p.nonzero:
                mask = mask & (data != 0)
            data = data.to_projection()
            data = data[mask]
            no_data = not len(data.head(1).execute())
            data = data[dim.name]
        else:
            mask = is_finite(data)
            if self.p.nonzero:
                mask = mask & (data != 0)
            data = data[mask]
            da = dask_array_module()
            no_data = False if da and isinstance(data,
                                                 da.Array) else not len(data)

        # Compute weights
        if self.p.weight_dimension:
            if hasattr(element, 'interface'):
                weights = element.interface.values(element,
                                                   self.p.weight_dimension,
                                                   compute=False)
            else:
                weights = element.dimension_values(self.p.weight_dimension)
            weights = weights[mask]
        else:
            weights = None

        # Compute bins
        if isinstance(self.p.bins, str):
            bin_data = cupy.asnumpy(data) if is_cupy else data
            edges = np.histogram_bin_edges(bin_data, bins=self.p.bins)
        elif isinstance(self.p.bins, (list, np.ndarray)):
            edges = self.p.bins
            if isdatetime(edges):
                edges = edges.astype('datetime64[ns]').astype('int64')
        else:
            hist_range = self.p.bin_range or element.range(selected_dim)
            # Avoids range issues including zero bin range and empty bins
            if hist_range == (0, 0) or any(not isfinite(r)
                                           for r in hist_range):
                hist_range = (0, 1)
            steps = self.p.num_bins + 1
            start, end = hist_range
            if is_datetime:
                start, end = dt_to_int(start, 'ns'), dt_to_int(end, 'ns')
            if self.p.log:
                bin_min = max([abs(start), data[data > 0].min()])
                edges = np.logspace(np.log10(bin_min), np.log10(end), steps)
            else:
                edges = np.linspace(start, end, steps)
        if is_cupy:
            edges = cupy.asarray(edges)

        if not is_dask_array(data) and no_data:
            nbins = self.p.num_bins if self.p.bins is None else len(
                self.p.bins) - 1
            hist = np.zeros(nbins)
        elif hasattr(element, 'interface'):
            density = True if normed else False
            hist, edges = element.interface.histogram(data,
                                                      edges,
                                                      density=density,
                                                      weights=weights)
            if normed == 'height':
                hist /= hist.max()
            if self.p.weight_dimension and self.p.mean_weighted:
                hist_mean, _ = element.interface.histogram(data,
                                                           density=False,
                                                           bins=edges)
                hist /= hist_mean
        elif normed:
            # This covers True, 'height', 'integral'
            hist, edges = np.histogram(data,
                                       density=True,
                                       weights=weights,
                                       bins=edges)
            if normed == 'height':
                hist /= hist.max()
        else:
            hist, edges = np.histogram(data,
                                       normed=normed,
                                       weights=weights,
                                       bins=edges)
            if self.p.weight_dimension and self.p.mean_weighted:
                hist_mean, _ = np.histogram(data,
                                            density=False,
                                            bins=self.p.num_bins)
                hist /= hist_mean

        hist[np.isnan(hist)] = 0
        if is_datetime:
            edges = (edges / 1e3).astype('datetime64[us]')

        params = {}
        if self.p.weight_dimension:
            params['vdims'] = [element.get_dimension(self.p.weight_dimension)]
        elif self.p.frequency_label:
            label = self.p.frequency_label.format(dim=dim.pprint_label)
            params['vdims'] = [Dimension('Frequency', label=label)]
        else:
            label = 'Frequency' if normed else 'Count'
            params['vdims'] = [
                Dimension('{0}_{1}'.format(dim.name, label.lower()),
                          label=label)
            ]

        if element.group != element.__class__.__name__:
            params['group'] = element.group

        if self.p.cumulative:
            hist = np.cumsum(hist)
            if self.p.normed in (True, 'integral'):
                hist *= edges[1] - edges[0]

        # Save off the computed bin edges so that if this operation instance
        # is used to compute another histogram, it will default to the same
        # bin edges.
        self.bins = list(edges)
        return Histogram((edges, hist),
                         kdims=[element.get_dimension(selected_dim)],
                         label=element.label,
                         **params)
Beispiel #17
0
class layout_chords(Operation):
    """
    layout_chords computes the locations of each node on a circle and
    the chords connecting them. The amount of radial angle devoted to
    each node and the number of chords are scaled by the value
    dimension of the Chord element. If the values are integers then
    the number of chords is directly scaled by the value, if the
    values are floats then the number of chords are apportioned such
    that the lowest value edge is given one chord and all other nodes
    are given nodes proportional to their weight. The max_chords
    parameter scales the number of chords to be assigned to an edge.

    The chords are computed by interpolating a cubic spline from the
    source to the target node in the graph, the number of samples to
    interpolate the spline with is given by the chord_samples
    parameter.
    """

    chord_samples = param.Integer(default=50, bounds=(0, None), doc="""
        Number of samples per chord for the spline interpolation.""")

    max_chords = param.Integer(default=500, doc="""
        Maximum number of chords to render.""")

    def _process(self, element, key=None):
        nodes_el = element._nodes
        if nodes_el:
            idx_dim = nodes_el.kdims[-1]
            nodes = nodes_el.dimension_values(idx_dim, expanded=False)
        else:
            source = element.dimension_values(0, expanded=False)
            target = element.dimension_values(1, expanded=False)
            nodes = np.unique(np.concatenate([source, target]))

        # Compute indices and values for connectivity matrix
        max_chords = self.p.max_chords
        src, tgt = (element.dimension_values(i) for i in range(2))
        src_idx = search_indices(src, nodes)
        tgt_idx = search_indices(tgt, nodes)
        if element.vdims:
            values = element.dimension_values(2)
            if values.dtype.kind not in 'uif':
                values = np.ones(len(element), dtype='int')
            else:
                if values.dtype.kind == 'f':
                    values = np.ceil(values*(1./values.min()))
                if values.sum() > max_chords:
                    values = np.ceil((values/float(values.sum()))*max_chords)
                    values = values.astype('int64')
        else:
            values = np.ones(len(element), dtype='int')

        # Compute connectivity matrix
        matrix = np.zeros((len(nodes), len(nodes)))
        for s, t, v in zip(src_idx, tgt_idx, values):
            matrix[s, t] += v

        # Compute weighted angular slice for each connection
        weights_of_areas = (matrix.sum(axis=0) + matrix.sum(axis=1))
        areas_in_radians = (weights_of_areas / weights_of_areas.sum()) * (2 * np.pi)

        # We add a zero in the begging for the cumulative sum
        points = np.zeros((areas_in_radians.shape[0] + 1))
        points[1:] = areas_in_radians
        points = points.cumsum()

        # Compute mid-points for node positions
        midpoints = np.convolve(points, [0.5, 0.5], mode='valid')
        mxs = np.cos(midpoints)
        mys = np.sin(midpoints)

        # Compute angles of chords in each edge
        all_areas = []
        for i in range(areas_in_radians.shape[0]):
            n_conn = weights_of_areas[i]
            p0, p1 = points[i], points[i+1]
            angles = np.linspace(p0, p1, n_conn)
            coords = list(zip(np.cos(angles), np.sin(angles)))
            all_areas.append(coords)

        # Draw each chord by interpolating quadratic splines
        # Separate chords in each edge by NaNs
        empty = np.array([[np.NaN, np.NaN]])
        paths = []
        for i in range(len(element)):
            sidx, tidx = src_idx[i], tgt_idx[i]
            src_area, tgt_area = all_areas[sidx], all_areas[tidx]
            n_conns = matrix[sidx, tidx]
            subpaths = []
            for _ in range(int(n_conns)):
                if not src_area or not tgt_area:
                    continue
                x0, y0 = src_area.pop()
                if not tgt_area:
                    continue
                x1, y1 = tgt_area.pop()
                b = quadratic_bezier((x0, y0), (x1, y1), (x0/2., y0/2.),
                                     (x1/2., y1/2.), steps=self.p.chord_samples)
                subpaths.append(b)
                subpaths.append(empty)
            subpaths = [p for p in subpaths[:-1] if len(p)]
            if subpaths:
                paths.append(np.concatenate(subpaths))
            else:
                paths.append(np.empty((0, 2)))

        # Construct Chord element from components
        if nodes_el:
            if isinstance(nodes_el, Nodes):
                kdims = nodes_el.kdims
            else:
                kdims = Nodes.kdims[:2]+[idx_dim]
            vdims = [vd for vd in nodes_el.vdims if vd not in kdims]
            values = tuple(nodes_el.dimension_values(vd) for vd in vdims)
        else:
            kdims = Nodes.kdims
            values, vdims = (), []
        nodes = Nodes((mxs, mys, nodes)+values, kdims=kdims, vdims=vdims)
        edges = EdgePaths(paths)
        chord = Chord((element.data, nodes, edges), compute=False)
        chord._angles = points
        return chord
Beispiel #18
0
class decimate(Operation):
    """
    Decimates any column based Element to a specified number of random
    rows if the current element defined by the x_range and y_range
    contains more than max_samples. By default the operation returns a
    DynamicMap with a RangeXY stream allowing dynamic downsampling.
    """

    dynamic = param.Boolean(default=True,
                            doc="""
       Enables dynamic processing by default.""")

    link_inputs = param.Boolean(default=True,
                                doc="""
         By default, the link_inputs parameter is set to True so that
         when applying shade, backends that support linked streams
         update RangeXY streams on the inputs of the shade operation.""")

    max_samples = param.Integer(default=5000,
                                doc="""
        Maximum number of samples to display at the same time.""")

    random_seed = param.Integer(default=42,
                                doc="""
        Seed used to initialize randomization.""")

    streams = param.List(default=[RangeXY],
                         doc="""
        List of streams that are applied if dynamic=True, allowing
        for dynamic interaction with the plot.""")

    x_range = param.NumericTuple(default=None,
                                 length=2,
                                 doc="""
       The x_range as a tuple of min and max x-value. Auto-ranges
       if set to None.""")

    y_range = param.NumericTuple(default=None,
                                 length=2,
                                 doc="""
       The x_range as a tuple of min and max y-value. Auto-ranges
       if set to None.""")

    _per_element = True

    def _process_layer(self, element, key=None):
        if not isinstance(element, Dataset):
            raise ValueError("Cannot downsample non-Dataset types.")
        if element.interface not in column_interfaces:
            element = element.clone(tuple(element.columns().values()))

        xstart, xend = self.p.x_range if self.p.x_range else element.range(0)
        ystart, yend = self.p.y_range if self.p.y_range else element.range(1)

        # Slice element to current ranges
        xdim, ydim = element.dimensions(label=True)[0:2]
        sliced = element.select(**{xdim: (xstart, xend), ydim: (ystart, yend)})

        if len(sliced) > self.p.max_samples:
            prng = np.random.RandomState(self.p.random_seed)
            return sliced.iloc[prng.choice(len(sliced), self.p.max_samples,
                                           False)]
        return sliced

    def _process(self, element, key=None):
        return element.map(self._process_layer, Element)
Beispiel #19
0
class TrainingStateParams(param.Parameterized):
    """Parameters controlling a TrainingStateController

    This class implements the :class:`pydrobert.param.optuna.TunableParameterized`
    interface
    """

    num_epochs = param.Integer(
        None,
        bounds=(1, None),
        softbounds=(10, 100),
        doc="Total number of epochs to run for. If unspecified, runs "
        "until the early stopping criterion (or infinitely if disabled) ",
    )
    log10_learning_rate = param.Number(
        None,
        softbounds=(-10, -2),
        doc="Initial optimizer log-learning rate. If unspecified, the initial "
        "learning rate of the optimizer instance remains unchanged",
    )
    early_stopping_threshold = param.Number(
        0.0,
        bounds=(0, None),
        softbounds=(0, 1.0),
        doc="Minimum magnitude decrease in validation metric from the last "
        "best that resets the early stopping clock. If zero, early stopping "
        "will never be performed",
    )
    early_stopping_patience = param.Integer(
        1,
        bounds=(1, None),
        softbounds=(1, 30),
        doc="Number of epochs after which, if the classifier has failed to "
        "decrease its validation metric by a threshold, training is "
        "halted",
    )
    early_stopping_burnin = param.Integer(
        0,
        bounds=(0, None),
        softbounds=(0, 10),
        doc="Number of epochs before the early stopping criterion kicks in",
    )
    reduce_lr_threshold = param.Number(
        0.0,
        bounds=(0, None),
        softbounds=(0, 1.0),
        doc="Minimum magnitude decrease in validation metric from the last "
        "best that resets the clock for reducing the learning rate. If zero, "
        "the learning rate will never be reduced",
    )
    reduce_lr_factor = param.Magnitude(
        0.1,
        softbounds=(0.1, 0.5),
        inclusive_bounds=(False, False),
        doc="Factor by which to multiply the learning rate if there has "
        'been no improvement in the  after "reduce_lr_patience" '
        "epochs",
    )
    reduce_lr_patience = param.Integer(
        1,
        bounds=(1, None),
        softbounds=(1, 30),
        doc="Number of epochs after which, if the classifier has failed to "
        "decrease its validation metric by a threshold, the learning rate is "
        "reduced",
    )
    reduce_lr_cooldown = param.Integer(
        0,
        bounds=(0, None),
        softbounds=(0, 10),
        doc="Number of epochs after reducing the learning rate before we "
        "resume checking improvements",
    )
    reduce_lr_log10_epsilon = param.Number(
        -8,
        bounds=(None, 0),
        doc="The log10 absolute difference between learning rates that, "
        "below which, reducing the learning rate is considered meaningless",
    )
    reduce_lr_burnin = param.Integer(
        0,
        bounds=(0, None),
        softbounds=(0, 10),
        doc="Number of epochs before the criterion for reducing the learning "
        "rate kicks in",
    )
    seed = param.Integer(
        None,
        doc="Seed used for training procedures (e.g. dropout). If "
        "unset, will not touch torch's seeding",
    )
    keep_last_and_best_only = param.Boolean(
        True,
        doc="If the model is being saved, keep only the model and optimizer "
        "parameters for the last and best epoch (in terms of validation loss)."
        ' If False, save every epoch. See also "saved_model_fmt" and '
        '"saved_optimizer_fmt"',
    )
    saved_model_fmt = param.String(
        "model_{epoch:03d}.pt",
        doc="The file name format string used to save model state information."
        " Entries from the state csv are used to format this string (see "
        "TrainingStateController)",
    )
    saved_optimizer_fmt = param.String(
        "optim_{epoch:03d}.pt",
        doc="The file name format string used to save optimizer state "
        "information. Entries from the state csv are used to format this "
        "string (see TrainingStateController)",
    )

    @classmethod
    def get_tunable(cls):
        """Returns a set of tunable parameters"""
        return {
            "num_epochs",
            "log10_learning_rate",
            "early_stopping_threshold",
            "early_stopping_patience",
            "early_stopping_burnin",
            "reduce_lr_factor",
            "reduce_lr_threshold",
            "reduce_lr_patience",
            "reduce_lr_cooldown",
            "reduce_lr_burnin",
        }

    @classmethod
    def suggest_params(cls, trial, base=None, only=None, prefix=""):
        """Populate a parameterized instance with values from trial"""
        if only is None:
            only = cls.get_tunable()
        params = cls() if base is None else base
        pdict = params.param.params()
        if "log10_learning_rate" in only:
            softbounds = pdict["log10_learning_rate"].get_soft_bounds()
            params.log10_learning_rate = trial.suggest_uniform(
                prefix + "log10_learning_rate", *softbounds)
        if "num_epochs" in only:
            softbounds = pdict["num_epochs"].get_soft_bounds()
            params.num_epochs = trial.suggest_int(prefix + "num_epochs",
                                                  *softbounds)
        if params.num_epochs is None:
            num_epochs = float("inf")
        else:
            num_epochs = params.num_epochs
        # if we sample patience and burnin so that their collective total
        # reaches or exceeds the number of epochs, they are effectively
        # disabled. Rather than allowing vast sums above the number of epochs,
        # we only allow the sum to reach the remaining epochs
        remaining_epochs = num_epochs
        if "early_stopping_patience" not in only:
            remaining_epochs -= params.early_stopping_patience
        if "early_stopping_burnin" not in only:
            remaining_epochs -= params.early_stopping_burnin
        remaining_epochs = max(0, remaining_epochs)
        if remaining_epochs and "early_stopping_threshold" in only:
            softbounds = pdict["early_stopping_threshold"].get_soft_bounds()
            params.early_stopping_threshold = trial.suggest_uniform(
                prefix + "early_stopping_threshold", *softbounds)
        if not params.early_stopping_threshold:
            remaining_epochs = 0
        if remaining_epochs and "early_stopping_patience" in only:
            softbounds = pdict["early_stopping_patience"].get_soft_bounds()
            softbounds = tuple(min(x, remaining_epochs) for x in softbounds)
            params.early_stopping_patience = trial.suggest_int(
                prefix + "early_stopping_patience", *softbounds)
            remaining_epochs -= params.early_stopping_patience
            assert remaining_epochs >= 0
        if remaining_epochs and "early_stopping_burnin" in only:
            softbounds = pdict["early_stopping_burnin"].get_soft_bounds()
            softbounds = tuple(min(x, remaining_epochs) for x in softbounds)
            params.early_stopping_burnin = trial.suggest_int(
                prefix + "early_stopping_burnin", *softbounds)
            remaining_epochs -= params.early_stopping_burnin
            assert remaining_epochs >= 0
        # we do the same thing, but for the learning rate scheduler
        remaining_epochs = num_epochs
        if "reduce_lr_patience" not in only:
            remaining_epochs -= params.reduce_lr_patience
        if "reduce_lr_burnin" not in only:
            remaining_epochs -= params.reduce_lr_burnin
        remaining_epochs = max(0, remaining_epochs)
        if remaining_epochs and "reduce_lr_threshold" in only:
            softbounds = pdict["reduce_lr_threshold"].get_soft_bounds()
            params.reduce_lr_threshold = trial.suggest_uniform(
                prefix + "reduce_lr_threshold", *softbounds)
        if not params.reduce_lr_threshold:
            remaining_epochs = 0
        if remaining_epochs and "reduce_lr_patience" in only:
            softbounds = pdict["reduce_lr_patience"].get_soft_bounds()
            softbounds = tuple(min(x, remaining_epochs) for x in softbounds)
            params.reduce_lr_patience = trial.suggest_int(
                prefix + "reduce_lr_patience", *softbounds)
            remaining_epochs -= params.reduce_lr_patience
        if remaining_epochs and "reduce_lr_burnin" in only:
            softbounds = pdict["reduce_lr_burnin"].get_soft_bounds()
            softbounds = tuple(min(x, remaining_epochs) for x in softbounds)
            params.reduce_lr_burnin = trial.suggest_int(
                prefix + "reduce_lr_burnin", *softbounds)
        if remaining_epochs and "reduce_lr_factor" in only:
            softbounds = pdict["reduce_lr_factor"].get_soft_bounds()
            params.reduce_lr_factor = trial.suggest_uniform(
                prefix + "reduce_lr_factor", *softbounds)
        if remaining_epochs and "reduce_lr_cooldown" in only:
            softbounds = pdict["reduce_lr_cooldown"].get_soft_bounds()
            params.reduce_lr_cooldown = trial.suggest_int(
                prefix + "reduce_lr_cooldown", *softbounds)
        return params
Beispiel #20
0
class forceatlas2_layout(LayoutAlgorithm):
    """
    Assign coordinates to the nodes using force-directed algorithm.

    This is a force-directed graph layout algorithm called
    `ForceAtlas2`.

    Timothee Poisot's `nxfa2` is the original implementation of this
    algorithm.

    .. _ForceAtlas2:
       http://journals.plos.org/plosone/article/file?id=10.1371/journal.pone.0098679&type=printable
    .. _nxfa2:
       https://github.com/tpoisot/nxfa2
    """

    iterations = param.Integer(default=10,
                               bounds=(1, None),
                               doc="""
        Number of passes for the layout algorithm""")

    linlog = param.Boolean(False,
                           doc="""
        Whether to use logarithmic attraction force""")

    nohubs = param.Boolean(False,
                           doc="""
        Whether to grant authorities (nodes with a high indegree) a
        more central position than hubs (nodes with a high outdegree)""")

    k = param.Number(default=None,
                     doc="""
        Compensates for the repulsion for nodes that are far away
        from the center. Defaults to the inverse of the number of
        nodes.""")

    dim = param.Integer(default=2,
                        bounds=(1, None),
                        doc="""
        Coordinate dimensions of each node""")

    seed = param.Integer(default=None,
                         bounds=(0, 2**32 - 1),
                         doc="""
        Random seed used to initialize the pseudo-random number
        generator.""")

    def __call__(self, nodes, edges, **params):
        p = param.ParamOverrides(self, params)

        np.random.seed(p.seed)

        # Convert graph into sparse adjacency matrix and array of points
        points = _extract_points_from_nodes(nodes)
        matrix = _convert_graph_to_sparse_matrix(nodes, edges)

        if p.k is None:
            p.k = np.sqrt(1.0 / len(points))

        # the initial "temperature" is about .1 of domain area (=1x1)
        # this is the largest step allowed in the dynamics.
        temperature = 0.1

        # simple cooling scheme.
        # linearly step down by dt on each iteration so last iteration is size dt.
        cooling(matrix, points, temperature, p)

        # Return the nodes with updated positions
        return _merge_points_with_nodes(nodes, points)
Beispiel #21
0
class GridPlot(CompositePlot, GenericCompositePlot):
    """
    Plot a group of elements in a grid layout based on a GridSpace element
    object.
    """

    axis_offset = param.Integer(default=50,
                                doc="""
        Number of pixels to adjust row and column widths and height by
        to compensate for shared axes.""")

    fontsize = param.Parameter(default={'title': '16pt'},
                               allow_None=True,
                               doc="""
       Specifies various fontsizes of the displayed text.

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

          {'title': '15pt'}""")

    shared_xaxis = param.Boolean(default=False,
                                 doc="""
        If enabled the x-axes of the GridSpace will be drawn from the
        objects inside the Grid rather than the GridSpace dimensions.""")

    shared_yaxis = param.Boolean(default=False,
                                 doc="""
        If enabled the x-axes of the GridSpace will be drawn from the
        objects inside the Grid rather than the GridSpace dimensions.""")

    xaxis = param.ObjectSelector(default=True,
                                 objects=['bottom', 'top', None, True, False],
                                 doc="""
        Whether and where to display the xaxis, supported options are
        'bottom', 'top' and None.""")

    yaxis = param.ObjectSelector(default=True,
                                 objects=['left', 'right', None, True, False],
                                 doc="""
        Whether and where to display the yaxis, supported options are
        'left', 'right' and None.""")

    xrotation = param.Integer(default=0,
                              bounds=(0, 360),
                              doc="""
        Rotation angle of the xticks.""")

    yrotation = param.Integer(default=0,
                              bounds=(0, 360),
                              doc="""
        Rotation angle of the yticks.""")

    plot_size = param.Integer(default=120,
                              doc="""
        Defines the width and height of each plot in the grid""")

    def __init__(self, layout, ranges=None, layout_num=1, keys=None, **params):
        if not isinstance(layout, GridSpace):
            raise Exception("GridPlot only accepts GridSpace.")
        super(GridPlot, self).__init__(layout=layout,
                                       layout_num=layout_num,
                                       ranges=ranges,
                                       keys=keys,
                                       **params)
        self.cols, self.rows = layout.shape
        self.subplots, self.layout = self._create_subplots(layout, ranges)
        if self.top_level:
            self.comm = self.init_comm()
            self.traverse(lambda x: setattr(x, 'comm', self.comm))
            self.traverse(lambda x: attach_streams(self, x.hmap, 2),
                          [GenericElementPlot])

    def _create_subplots(self, layout, ranges):
        subplots = OrderedDict()
        frame_ranges = self.compute_ranges(layout, None, ranges)
        frame_ranges = OrderedDict([
            (key, self.compute_ranges(layout, key, frame_ranges))
            for key in self.keys
        ])
        collapsed_layout = layout.clone(shared_data=False, id=layout.id)
        for i, coord in enumerate(layout.keys(full_grid=True)):
            r = i % self.rows
            c = i // self.rows

            if not isinstance(coord, tuple): coord = (coord, )
            view = layout.data.get(coord, None)
            # Create subplot
            if view is not None:
                vtype = view.type if isinstance(view,
                                                HoloMap) else view.__class__
                opts = self.lookup_options(view, 'plot').options
            else:
                vtype = None

            # Create axes
            offset = self.axis_offset
            kwargs = {}
            if c == 0 and r != 0:
                kwargs['xaxis'] = None
                kwargs['width'] = self.plot_size + offset
            if c != 0 and r == 0:
                kwargs['yaxis'] = None
                kwargs['height'] = self.plot_size + offset
            if c == 0 and r == 0:
                kwargs['width'] = self.plot_size + offset
                kwargs['height'] = self.plot_size + offset
            if r != 0 and c != 0:
                kwargs['xaxis'] = None
                kwargs['yaxis'] = None

            if 'width' not in kwargs or not self.shared_yaxis:
                kwargs['width'] = self.plot_size
            if 'height' not in kwargs or not self.shared_xaxis:
                kwargs['height'] = self.plot_size
            if 'border' not in kwargs:
                kwargs['border'] = 3

            kwargs['show_legend'] = False

            if not self.shared_xaxis:
                kwargs['xaxis'] = None

            if not self.shared_yaxis:
                kwargs['yaxis'] = None

            # Create subplot
            plotting_class = Store.registry[self.renderer.backend].get(
                vtype, None)
            if plotting_class is None:
                if view is not None:
                    self.warning("Bokeh plotting class for %s type not found, "
                                 "object will not be rendered." %
                                 vtype.__name__)
            else:
                subplot = plotting_class(view,
                                         dimensions=self.dimensions,
                                         show_title=False,
                                         subplot=True,
                                         renderer=self.renderer,
                                         ranges=frame_ranges,
                                         uniform=self.uniform,
                                         keys=self.keys,
                                         **dict(opts, **kwargs))
                collapsed_layout[coord] = (subplot.layout if isinstance(
                    subplot, GenericCompositePlot) else subplot.hmap)
                subplots[coord] = subplot
        return subplots, collapsed_layout

    def initialize_plot(self, ranges=None, plots=[]):
        ranges = self.compute_ranges(self.layout, self.keys[-1], None)
        passed_plots = list(plots)
        plots = [[None for c in range(self.cols)] for r in range(self.rows)]
        for i, coord in enumerate(self.layout.keys(full_grid=True)):
            r = i % self.rows
            c = i // self.rows
            subplot = self.subplots.get(wrap_tuple(coord), None)
            if subplot is not None:
                plot = subplot.initialize_plot(ranges=ranges,
                                               plots=passed_plots)
                plots[r][c] = plot
                passed_plots.append(plot)
            else:
                passed_plots.append(None)

        plot = gridplot(plots[::-1])
        plot = self._make_axes(plot)

        title = self._get_title(self.keys[-1])
        if title:
            plot = Column(title, plot)
            self.handles['title'] = title

        self._update_callbacks(plot)
        self.handles['plot'] = plot
        self.handles['plots'] = plots
        if self.shared_datasource:
            self.sync_sources()
        self.drawn = True

        return self.handles['plot']

    def _make_axes(self, plot):
        width, height = self.renderer.get_size(plot)
        x_axis, y_axis = None, None
        kwargs = dict(sizing_mode=self.sizing_mode)
        if self.xaxis:
            flip = self.shared_xaxis
            rotation = self.xrotation
            lsize = self._fontsize('xlabel').get('fontsize')
            tsize = self._fontsize('xticks', common=False).get('fontsize')
            xfactors = list(unique_iterator(self.layout.dimension_values(0)))
            x_axis = make_axis('x',
                               width,
                               xfactors,
                               self.layout.kdims[0],
                               flip=flip,
                               rotation=rotation,
                               label_size=lsize,
                               tick_size=tsize)
        if self.yaxis and self.layout.ndims > 1:
            flip = self.shared_yaxis
            rotation = self.yrotation
            lsize = self._fontsize('ylabel').get('fontsize')
            tsize = self._fontsize('yticks', common=False).get('fontsize')
            yfactors = list(unique_iterator(self.layout.dimension_values(1)))
            y_axis = make_axis('y',
                               height,
                               yfactors,
                               self.layout.kdims[1],
                               flip=flip,
                               rotation=rotation,
                               label_size=lsize,
                               tick_size=tsize)
        if x_axis and y_axis:
            plot = filter_toolboxes(plot)
            r1, r2 = ([y_axis, plot], [None, x_axis])
            if self.shared_xaxis:
                r1, r2 = r2, r1
            if self.shared_yaxis:
                r1, r2 = r1[::-1], r2[::-1]
            models = layout_padding([r1, r2], self.renderer)
            plot = gridplot(models, **kwargs)
        elif y_axis:
            models = [y_axis, plot]
            if self.shared_yaxis: models = models[::-1]
            plot = Row(*models, **kwargs)
        elif x_axis:
            models = [plot, x_axis]
            if self.shared_xaxis: models = models[::-1]
            plot = Column(*models, **kwargs)
        return plot

    @update_shared_sources
    def update_frame(self, key, ranges=None):
        """
        Update the internal state of the Plot to represent the given
        key tuple (where integers represent frames). Returns this
        state.
        """
        ranges = self.compute_ranges(self.layout, key, ranges)
        for coord in self.layout.keys(full_grid=True):
            subplot = self.subplots.get(wrap_tuple(coord), None)
            if subplot is not None:
                subplot.update_frame(key, ranges)
        title = self._get_title(key)
        if title:
            self.handles['title']
Beispiel #22
0
class SparseConnectionField(param.Parameterized):
    """
    A set of weights on one input Sheet.

    Each ConnectionField contributes to the activity of one unit on
    the output sheet, and is normally used as part of a Projection
    including many other ConnectionFields.
    """

    # ALERT: need bounds, more docs
    x = param.Number(default=0.0,doc="Sheet X coordinate of CF")

    y = param.Number(default=0.0,doc="Sheet Y coordinate of CF")

    weights_generator = param.ClassSelector(PatternGenerator,
        default=patterngenerator.Constant(),constant=True,doc="""
        Generates initial weights values.""")

    min_matrix_radius=param.Integer(default=1)

    output_fns = param.HookList(default=[],class_=TransferFn,precedence=0.08,doc="""
        Optional function(s) to apply to the pattern array after it has been created.
        Can be used for normalization, thresholding, etc.""")


    # Class attribute to switch to legacy weight generation if False
    independent_weight_generation = True

    def get_bounds(self,input_sheet=None):
        if not input_sheet == None:
            return self.input_sheet_slice.compute_bounds(input_sheet)
        else:
            return self.input_sheet_slice.compute_bounds(self.input_sheet)


    def __get_shape_mask(self):
        cf_shape = self.projection.cf_shape
        bounds = self.projection.bounds_template
        xdensity = self.projection.src.xdensity
        ydensity = self.projection.src.xdensity
        center_r,center_c = self.projection.src.sheet2matrixidx(0,0)
        center_x,center_y = self.projection.src.matrixidx2sheet(center_r,center_c)
        cf_mask = cf_shape(x=center_x,y=center_y,bounds=bounds,xdensity=xdensity,ydensity=ydensity)
        return cf_mask

    shape_mask = property(__get_shape_mask)


    def __get_norm_total(self):
        return self.projection.norm_total[self.matrix_idx[0],self.matrix_idx[1]]

    def __set_norm_total(self,new_norm_total):
        self.projection.norm_total[self.matrix_idx[0],self.matrix_idx[1]] = new_norm_total

    def __del_norm_total(self):
        self.projection.norm_total[self.matrix_idx[0],self.matrix_idx[1]] = 0.0

    norm_total = property(__get_norm_total,__set_norm_total,__del_norm_total)


    def __get_mask(self):
        x1,x2,y1,y2 = self.input_sheet_slice.tolist()
        mask = np.zeros((x2-x1,y2-y1),dtype=np.bool)
        inds = np.ravel_multi_index(np.mgrid[x1:x2,y1:y2],self.projection.src.shape).flatten()
        nz_flat = self.projection.weights[inds,self.oned_idx].toarray()
        nz_inds = nz_flat.reshape(x2-x1,y2-y1).nonzero()
        mask[nz_inds] = True
        return mask

    mask = property(__get_mask,
        """
        The mask property returns an array of bools representing the
        zero weights in the CF weights array.

        It is useful when applying additive functions on the weights
        array, to ensure zero values are not accidentally overwritten.

        The mask cannot be changed via the property, only by changing
        the weights directly.
        """)


    def __get_weights(self):
        """
        get_weights accesses the sparse CF matrix and returns the CF
        in dense form.
        """

        x1,x2,y1,y2 = self.src_slice
        inds = np.ravel_multi_index(np.mgrid[x1:x2,y1:y2],self.projection.src.shape).flatten()
        return self.projection.weights[inds,self.oned_idx].toarray().reshape(x2-x1,y2-y1)

    def __set_weights(self,arr):
        """
        Takes an input array, which has to match the CF shape, and
        creates an mgrid of the appropriate size, adds the proper
        offsets and passes the values and indices to the sparse matrix
        representation.
        """

        x1,x2,y1,y2 = self.src_slice
        (dim1,dim2) = arr.shape
        assert (dim1,dim2) == (x2-x1,y2-y1), "Array does not match CF shape."
        (x,y) = np.mgrid[0:dim1,0:dim2] # Create mgrid of CF size
        x_ind = np.array(x)+x1; y_ind = np.array(y) + y1; # Add slice offsets
        row_inds = np.ravel_multi_index((x_ind,y_ind),self.projection.src.shape).flatten().astype(np.int32)
        col_inds = np.array([self.oned_idx]*len(row_inds),dtype=np.int32)
        self.projection.weights.put(arr[x,y].flatten(),row_inds,col_inds)

    weights = property(__get_weights,__set_weights)


    def __init__(self,template,input_sheet,projection,label=None,**params):
        """
        Initializes the CF object and stores meta information about the CF's
        shape and position in the SparseCFProjection to allow for easier
        initialization.
        """

        super(SparseConnectionField,self).__init__(**params)

        self.input_sheet = input_sheet
        self.projection = projection
        self.label = label

        self.matrix_idx = self.projection.dest.sheet2matrixidx(self.x,self.y)
        self.oned_idx = self.matrix_idx[0] * self.projection.dest.shape[1] + self.matrix_idx[1]

        template = copy(template)

        if not isinstance(template,Slice):
            template = Slice(template,self.input_sheet,force_odd=True,
                             min_matrix_radius=self.min_matrix_radius)
        self.weights_slice = self._create_input_sheet_slice(template)

        self.src_slice = tuple(self.input_sheet_slice.tolist())


    def _init_weights(self,mask_template):

        if not hasattr(mask_template,'view'):
            mask = _create_mask(mask_template,
                                self.weights_slice.compute_bounds(
                                    self.input_sheet),
                                self.input_sheet,True,0.5)

        mask = self.weights_slice.submatrix(mask_template)
        mask = np.array(mask,copy=1)



        pattern_params = dict(x=self.x,y=self.y,
                              bounds=self.get_bounds(self.input_sheet),
                              xdensity=self.input_sheet.xdensity,
                              ydensity=self.input_sheet.ydensity,
                              mask=mask)

        controlled_weights = (param.Dynamic.time_dependent
                              and isinstance(param.Dynamic.time_fn,
                                             param.Time)
                              and self.independent_weight_generation)

        if controlled_weights:
            with param.Dynamic.time_fn as t:
                t(0)                        # Initialize at time zero.
                # Controls random streams
                label = '' if self.label is None else self.label
                name = "%s_CF (%.5f, %.5f)" % (label, self.x, self.y)
                w = self.weights_generator(**dict(pattern_params,
                                                  name=name))
        else:
            w = self.weights_generator(**pattern_params)

        w = w.astype(sparse_type)

        for of in self.output_fns:
            of(w)

        return w


    def _create_input_sheet_slice(self,template):
        """
        Create the input_sheet_slice, which provides the appropriate
        Slice for this CF on the input_sheet (as well as providing
        this CF's exact bounds).

        Also creates the weights_slice, which provides the Slice for
        this weights matrix (in case it must be cropped at an edge).
        """
        # copy required because the template gets modified here but
        # needs to be used again
        input_sheet_slice = copy(template)
        input_sheet_slice.positionedcrop(self.x,self.y,self.input_sheet)
        input_sheet_slice.crop_to_sheet(self.input_sheet)

        # weights matrix cannot have a zero-sized dimension (could
        # happen at this stage because of cropping)
        nrows,ncols = input_sheet_slice.shape_on_sheet()
        if nrows<1 or ncols<1:
            raise NullCFError(self.x,self.y,self.input_sheet,nrows,ncols)

        self.input_sheet_slice = input_sheet_slice

        # not copied because we don't use again
        template.positionlesscrop(self.x,self.y,self.input_sheet)
        return template


    def get_input_matrix(self, activity):
        return self.input_sheet_slice.submatrix(activity)
Beispiel #23
0
class BarPlot(LegendPlot):

    group_index = param.Integer(default=0,
                                doc="""
       Index of the dimension in the supplied Bars
       Element, which will be laid out into groups.""")

    category_index = param.Integer(default=1,
                                   doc="""
       Index of the dimension in the supplied Bars
       Element, which will be laid out into categories.""")

    stack_index = param.Integer(default=2,
                                doc="""
       Index of the dimension in the supplied Bars
       Element, which will stacked.""")

    padding = param.Number(default=0.2,
                           doc="""
       Defines the padding between groups.""")

    color_by = param.List(default=['category'],
                          doc="""
       Defines how the Bar elements colored. Valid options include
       any permutation of 'group', 'category' and 'stack'.""")

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

    xticks = param.Integer(0, precedence=-1)

    style_opts = [
        'alpha', 'color', 'align', 'visible', 'edgecolor', 'log', 'facecolor',
        'capsize', 'error_kw', 'hatch'
    ]

    legend_specs = dict(
        LegendPlot.legend_specs, **{
            'top':
            dict(bbox_to_anchor=(0., 1.02, 1., .102),
                 ncol=3,
                 loc=3,
                 mode="expand",
                 borderaxespad=0.),
            'bottom':
            dict(ncol=3,
                 mode="expand",
                 loc=2,
                 bbox_to_anchor=(0., -0.4, 1., .102),
                 borderaxespad=0.1)
        })

    _dimensions = OrderedDict([('group', 0), ('category', 1), ('stack', 2)])

    def __init__(self, element, **params):
        super(BarPlot, self).__init__(element, **params)
        self.values, self.bar_dimensions = self._get_values()

    def _get_values(self):
        """
        Get unique index value for each bar
        """
        gi, ci, si = self.group_index, self.category_index, self.stack_index
        ndims = self.hmap.last.ndims
        dims = self.hmap.last.kdims
        dimensions = []
        values = {}
        for vidx, vtype in zip([gi, ci, si], self._dimensions):
            if vidx < ndims:
                dim = dims[vidx]
                dimensions.append(dim)
                vals = self.hmap.dimension_values(dim.name)
            else:
                dimensions.append(None)
                vals = [None]
            values[vtype] = list(unique_iterator(vals))
        return values, dimensions

    def _compute_styles(self, element, style_groups):
        """
        Computes color and hatch combinations by
        any combination of the 'group', 'category'
        and 'stack'.
        """
        style = self.lookup_options(element, 'style')[0]
        sopts = []
        for sopt in ['color', 'hatch']:
            if sopt in style:
                sopts.append(sopt)
                style.pop(sopt, None)
        color_groups = []
        for sg in style_groups:
            color_groups.append(self.values[sg])
        style_product = list(product(*color_groups))
        wrapped_style = self.lookup_options(element, 'style').max_cycles(
            len(style_product))
        color_groups = {
            k: tuple(wrapped_style[n][sopt] for sopt in sopts)
            for n, k in enumerate(style_product)
        }
        return style, color_groups, sopts

    def get_extents(self, element, ranges):
        ngroups = len(self.values['group'])
        vdim = element.vdims[0].name
        if self.stack_index in range(element.ndims):
            return 0, 0, ngroups, np.NaN
        else:
            vrange = ranges[vdim]
            return 0, np.nanmin([vrange[0], 0]), ngroups, vrange[1]

    @mpl_rc_context
    def initialize_plot(self, ranges=None):
        element = self.hmap.last
        vdim = element.vdims[0]
        axis = self.handles['axis']
        key = self.keys[-1]

        ranges = self.compute_ranges(self.hmap, key, ranges)
        ranges = match_spec(element, ranges)

        self.handles['artist'], self.handles[
            'xticks'], xdims = self._create_bars(axis, element)
        return self._finalize_axis(key,
                                   ranges=ranges,
                                   xticks=self.handles['xticks'],
                                   element=element,
                                   dimensions=[xdims, vdim])

    def _finalize_ticks(self, axis, element, xticks, yticks, zticks):
        """
        Apply ticks with appropriate offsets.
        """
        yalignments = None
        if xticks is not None:
            ticks, labels, yalignments = zip(
                *sorted(xticks, key=lambda x: x[0]))
            xticks = (list(ticks), list(labels))
        super(BarPlot, self)._finalize_ticks(axis, element, xticks, yticks,
                                             zticks)
        if yalignments:
            for t, y in zip(axis.get_xticklabels(), yalignments):
                t.set_y(y)

    def _create_bars(self, axis, element):
        # Get style and dimension information
        values = self.values
        gi, ci, si = self.group_index, self.category_index, self.stack_index
        gdim, cdim, sdim = [
            element.kdims[i] if i < element.ndims else None
            for i in (gi, ci, si)
        ]
        indices = dict(zip(self._dimensions, (gi, ci, si)))
        style_groups = [
            sg for sg in self.color_by if indices[sg] < element.ndims
        ]
        style_opts, color_groups, sopts = self._compute_styles(
            element, style_groups)
        dims = element.dimensions('key', label=True)
        ndims = len(dims)
        xdims = [d for d in [cdim, gdim] if d is not None]

        # Compute widths
        width = (1 - (2. * self.padding)) / len(values['category'])

        # Initialize variables
        xticks = []
        val_key = [None] * ndims
        style_key = [None] * len(style_groups)
        label_key = [None] * len(style_groups)
        labels = []
        bars = {}

        # Iterate over group, category and stack dimension values
        # computing xticks and drawing bars and applying styles
        for gidx, grp_name in enumerate(values['group']):
            if grp_name is not None:
                grp = gdim.pprint_value(grp_name)
                if 'group' in style_groups:
                    idx = style_groups.index('group')
                    label_key[idx] = str(grp)
                    style_key[idx] = grp_name
                val_key[gi] = grp_name
                if ci < ndims:
                    yalign = -0.04
                else:
                    yalign = 0
                xticks.append((gidx + 0.5, grp, yalign))
            for cidx, cat_name in enumerate(values['category']):
                xpos = gidx + self.padding + (cidx * width)
                if cat_name is not None:
                    cat = gdim.pprint_value(cat_name)
                    if 'category' in style_groups:
                        idx = style_groups.index('category')
                        label_key[idx] = str(cat)
                        style_key[idx] = cat_name
                    val_key[ci] = cat_name
                    xticks.append((xpos + width / 2., cat, 0))
                prev = 0
                for stk_name in values['stack']:
                    if stk_name is not None:
                        if 'stack' in style_groups:
                            idx = style_groups.index('stack')
                            stk = gdim.pprint_value(stk_name)
                            label_key[idx] = str(stk)
                            style_key[idx] = stk_name
                        val_key[si] = stk_name
                    vals = element.sample([tuple(val_key)]).dimension_values(
                        element.vdims[0].name)
                    val = float(vals[0]) if len(vals) else np.NaN
                    label = ', '.join(label_key)
                    style = dict(style_opts,
                                 label='' if label in labels else label,
                                 **dict(
                                     zip(sopts,
                                         color_groups[tuple(style_key)])))
                    bar = axis.bar([xpos], [val],
                                   width=width,
                                   bottom=prev,
                                   **style)

                    # Update variables
                    bars[tuple(val_key)] = bar
                    prev += val if isfinite(val) else 0
                    labels.append(label)
        title = [
            element.kdims[indices[cg]].pprint_label for cg in self.color_by
            if indices[cg] < ndims
        ]

        if self.show_legend and any(len(l) for l in labels):
            leg_spec = self.legend_specs[self.legend_position]
            if self.legend_cols: leg_spec['ncol'] = self.legend_cols
            axis.legend(title=', '.join(title), **leg_spec)
        return bars, xticks, xdims

    def update_handles(self, key, axis, element, ranges, style):
        dims = element.dimensions('key', label=True)
        ndims = len(dims)
        ci, gi, si = self.category_index, self.group_index, self.stack_index
        val_key = [None] * ndims
        for g in self.values['group']:
            if g is not None: val_key[gi] = g
            for c in self.values['category']:
                if c is not None: val_key[ci] = c
                prev = 0
                for s in self.values['stack']:
                    if s is not None: val_key[si] = s
                    bar = self.handles['artist'].get(tuple(val_key))
                    if bar:
                        vals = element.sample([
                            tuple(val_key)
                        ]).dimension_values(element.vdims[0].name)
                        height = float(vals[0]) if len(vals) else np.NaN
                        bar[0].set_height(height)
                        bar[0].set_y(prev)
                        prev += height if isfinite(height) else 0
        return {'xticks': self.handles['xticks']}
Beispiel #24
0
class DownsampleColumns(DownsampleCallback):
    """
    Downsamples any column based Element by randomizing
    the rows and updating the ColumnDataSource with
    up to max_samples.
    """

    compute_ranges = param.Boolean(default=False,
                                   doc="""
        Whether the ranges are recomputed for the sliced region""")

    max_samples = param.Integer(default=800,
                                doc="""
        Maximum number of samples to display at the same time.""")

    random_seed = param.Integer(default=42,
                                doc="""
        Seed used to initialize randomization.""")

    plot_attributes = param.Dict(default={
        'x_range': ['start', 'end'],
        'y_range': ['start', 'end']
    })

    def initialize(self, data):
        plot = self.plots[0]
        maxn = np.max([len(el) for el in plot.hmap])
        np.random.seed(self.random_seed)
        self.random_index = np.random.choice(maxn, maxn, False)

    def __call__(self, data):
        xstart, xend = data['x_range']
        ystart, yend = data['y_range']

        plot = self.plots[0]
        element = plot.current_frame
        if element.interface is not ArrayColumns:
            element = plot.current_frame.clone(datatype=['array'])

        # Slice element to current ranges
        xdim, ydim = element.dimensions(label=True)[0:2]
        sliced = element.select(**{xdim: (xstart, xend), ydim: (ystart, yend)})

        if self.compute_ranges:
            ranges = {d: element.range(d) for d in element.dimensions()}
        else:
            ranges = plot.current_ranges

        # Avoid randomizing if possible (expensive)
        if len(sliced) > self.max_samples:
            # Randomize element samples and slice to region
            # Randomization consistent to avoid "flicker".
            length = len(element)
            inds = self.random_index[self.random_index < length]
            data = element.data[inds, :]
            randomized = element.clone(data, datatype=['array'])
            sliced = randomized.select(**{
                xdim: (xstart, xend),
                ydim: (ystart, yend)
            })
            sliced = sliced.clone(sliced.data[:self.max_samples, :])

        # Update data source
        new_data = plot.get_data(sliced, ranges)[0]
        source = plot.handles['source']
        source.data.update(new_data)
        return [source]
Beispiel #25
0
class BinderButton(pn.pane.Markdown):
    """The BinderButton displayes the Binder badge and if clicked opens the Notebook on Binder
    in a new tab"""

    repository = param.String()
    branch = param.String()
    folder = param.String()
    notebook = param.String()

    # In order to not be selected by the `pn.panel` selection process
    # Cf. https://github.com/holoviz/panel/issues/1494#issuecomment-663219654
    priority = 0

    width = param.Integer(
        default=200,
        bounds=(0, None),
        doc="""
        The width of the component (in pixels). This can be either
        fixed or preferred width, depending on width sizing policy.""",
    )

    # The _rename dict is used to keep track of Panel parameters to sync to Bokeh properties.
    # As value is not a property on the Bokeh model we should set it to None
    _rename = dict(pn.pane.Markdown._rename,
                   repository=None,
                   branch=None,
                   folder=None,
                   notebook=None)

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

        self._update_object_from_parameters()

    # Note:
    # Don't name the function
    # `_update`, `_update_object`, `_update_model` or `_update_pane`
    # as this will override a function in the parent class.
    @param.depends("repository",
                   "branch",
                   "folder",
                   "notebook",
                   "height",
                   "width",
                   "sizing_mode",
                   watch=True)
    def _update_object_from_parameters(self, *events):
        if self.sizing_mode == "fixed":
            style = f"height:{self.height}px;width:{self.width}px;"
        elif self.sizing_mode == "stretch_width":
            style = f"width:{self.width}px;"
        elif self.sizing_mode == "stretch_height":
            style = f"height:{self.height}px;"
        else:
            style = f"height:100%;width:100%;"

        self.object = self.to_markdown(
            repository=self.repository,
            branch=self.branch,
            folder=self.folder,
            notebook=self.notebook,
            style=style,
        )

    @classmethod
    def to_markdown(self,
                    repository: str,
                    branch: str,
                    folder: str,
                    notebook: str,
                    style: str = None):
        folder = folder.replace("/", "%2F").replace("\\", "%2F")
        url = f"https://mybinder.org/v2/gh/{repository}/{branch}?filepath={folder}%2F{notebook}"
        if style:
            image = f'<img src="https://mybinder.org/badge_logo.svg" style="{style}">'
        else:
            image = f'<img src="https://mybinder.org/badge_logo.svg">'
        markdown = f"[{image}]({url})"
        return markdown
Beispiel #26
0
class dynspread(ElementOperation):
    """
    Spreading expands each pixel in an Image based Element a certain
    number of pixels on all sides according to a given shape, merging
    pixels using a specified compositing operator. This can be useful
    to make sparse plots more visible. Dynamic spreading determines
    how many pixels to spread based on a density heuristic.

    See the datashader documentation for more detail:

    http://datashader.readthedocs.io/en/latest/api.html#datashader.transfer_functions.dynspread
    """

    how = param.ObjectSelector(default='source',
                               objects=['source', 'over', 'saturate', 'add'],
                               doc="""
        The name of the compositing operator to use when combining
        pixels.""")

    max_px = param.Integer(default=3,
                           doc="""
        Maximum number of pixels to spread on all sides.""")

    shape = param.ObjectSelector(default='circle',
                                 objects=['circle', 'square'],
                                 doc="""
        The shape to spread by. Options are 'circle' [default] or 'square'.""")

    threshold = param.Number(default=0.5,
                             bounds=(0, 1),
                             doc="""
        When spreading, determines how far to spread.
        Spreading starts at 1 pixel, and stops when the fraction
        of adjacent non-empty pixels reaches this threshold.
        Higher values give more spreading, up to the max_px
        allowed.""")

    @classmethod
    def uint8_to_uint32(cls, img):
        shape = img.shape
        flat_shape = np.multiply.reduce(shape[:2])
        rgb = img.reshape((flat_shape, 4)).view('uint32').reshape(shape[:2])
        return rgb

    def _apply_dynspread(self, array):
        img = tf.Image(array)
        return tf.dynspread(img,
                            max_px=self.p.max_px,
                            threshold=self.p.threshold,
                            how=self.p.how,
                            shape=self.p.shape).data

    def _process(self, element, key=None):
        if not isinstance(element, (Image, GridImage)):
            raise ValueError(
                'dynspread can only be applied to Image Elements.')

        if isinstance(element, GridImage):
            new_data = {
                kd.name: element.dimension_values(kd, expanded=False)
                for kd in element.kdims
            }
            for vd in element.vdims:
                array = element.dimension_values(vd, flat=False)
                new_data[vd.name] = self._apply_dynspread(array)
            return element.clone(element.data)
        else:
            img = np.flipud(element.data)
            isrgb = isinstance(element, RGB)
            data = self.uint8_to_uint32(img) if isrgb else img
            array = self._apply_dynspread(data)
            img = datashade.uint32_to_uint8(array) if isrgb else np.flipud(
                array)
            return element.clone(img)
Beispiel #27
0
class NamedListPanel(ListPanel):

    active = param.Integer(default=0,
                           bounds=(0, None),
                           doc="""
        Index of the currently displayed objects.""")

    objects = param.List(default=[],
                         doc="""
        The list of child objects that make up the tabs.""")

    def __init__(self, *items, **params):
        if 'objects' in params:
            if items:
                raise ValueError('%s objects should be supplied either '
                                 'as positional arguments or as a keyword, '
                                 'not both.' % type(self).__name__)
            items = params['objects']
        objects, self._names = self._to_objects_and_names(items)
        super(NamedListPanel, self).__init__(*objects, **params)
        self._panels = defaultdict(dict)
        self.param.watch(self._update_names, 'objects')
        # ALERT: Ensure that name update happens first, should be
        #        replaced by watch precedence support in param
        self._param_watchers['objects']['value'].reverse()

    def _to_object_and_name(self, item):
        from ..pane import panel
        if isinstance(item, tuple):
            name, item = item
        else:
            name = getattr(item, 'name', None)
        pane = panel(item, name=name)
        name = param_name(pane.name) if name is None else name
        return pane, name

    def _to_objects_and_names(self, items):
        objects, names = [], []
        for item in items:
            pane, name = self._to_object_and_name(item)
            objects.append(pane)
            names.append(name)
        return objects, names

    def _update_names(self, event):
        if len(event.new) == len(self._names):
            return
        names = []
        for obj in event.new:
            if obj in event.old:
                index = event.old.index(obj)
                name = self._names[index]
            else:
                name = obj.name
            names.append(name)
        self._names = names

    def _update_active(self, *events):
        pass

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

    def __add__(self, other):
        if isinstance(other, NamedListPanel):
            other = list(zip(other._names, other.objects))
        elif isinstance(other, ListLike):
            other = other.objects
        if not isinstance(other, list):
            stype = type(self).__name__
            otype = type(other).__name__
            raise ValueError(
                "Cannot add items of type %s and %s, can only "
                "combine %s.objects with list or ListLike object." %
                (stype, otype, stype))
        objects = list(zip(self._names, self.objects))
        return self.clone(*(objects + other))

    def __radd__(self, other):
        if isinstance(other, NamedListPanel):
            other = list(zip(other._names, other.objects))
        elif isinstance(other, ListLike):
            other = other.objects
        if not isinstance(other, list):
            stype = type(self).__name__
            otype = type(other).__name__
            raise ValueError(
                "Cannot add items of type %s and %s, can only "
                "combine %s.objects with list or ListLike object." %
                (otype, stype, stype))
        objects = list(zip(self._names, self.objects))
        return self.clone(*(other + objects))

    def __setitem__(self, index, panes):
        new_objects = list(self)
        if not isinstance(index, slice):
            if index > len(self.objects):
                raise IndexError(
                    'Index %d out of bounds on %s '
                    'containing %d objects.' %
                    (index, type(self).__name__, len(self.objects)))
            start, end = index, index + 1
            panes = [panes]
        else:
            start = index.start or 0
            end = len(self.objects) if index.stop is None else index.stop
            if index.start is None and index.stop is None:
                if not isinstance(panes, list):
                    raise IndexError(
                        'Expected a list of objects to '
                        'replace the objects in the %s, '
                        'got a %s type.' %
                        (type(self).__name__, type(panes).__name__))
                expected = len(panes)
                new_objects = [None] * expected
                self._names = [None] * len(panes)
                end = expected
            else:
                expected = end - start
                if end > len(self.objects):
                    raise IndexError(
                        'Index %d out of bounds on %s '
                        'containing %d objects.' %
                        (end, type(self).__name__, len(self.objects)))
            if not isinstance(panes, list) or len(panes) != expected:
                raise IndexError('Expected a list of %d objects to set '
                                 'on the %s to match the supplied slice.' %
                                 (expected, type(self).__name__))
        for i, pane in zip(range(start, end), panes):
            new_objects[i], self._names[i] = self._to_object_and_name(pane)
        self.objects = new_objects

    def clone(self, *objects, **params):
        """
        Makes a copy of the Tabs sharing the same parameters.

        Arguments
        ---------
        objects: Objects to add to the cloned Tabs object.
        params: Keyword arguments override the parameters on the clone.

        Returns
        -------
        Cloned Tabs object
        """
        if not objects:
            if 'objects' in params:
                objects = params.pop('objects')
            else:
                objects = zip(self._names, self.objects)
        elif 'objects' in params:
            raise ValueError('Tabs objects should be supplied either '
                             'as positional arguments or as a keyword, '
                             'not both.')
        p = dict(self.param.get_param_values(), **params)
        del p['objects']
        return type(self)(*objects, **params)

    def append(self, pane):
        """
        Appends an object to the tabs.

        Arguments
        ---------
        obj (object): Panel component to add as a tab.
        """
        new_object, new_name = self._to_object_and_name(pane)
        new_objects = list(self)
        new_objects.append(new_object)
        self._names.append(new_name)
        self.objects = new_objects

    def clear(self):
        """
        Clears the tabs.
        """
        self._names = []
        self.objects = []

    def extend(self, panes):
        """
        Extends the the tabs with a list.

        Arguments
        ---------
        objects (list): List of panel components to add as tabs.
        """
        new_objects, new_names = self._to_objects_and_names(panes)
        objects = list(self)
        objects.extend(new_objects)
        self._names.extend(new_names)
        self.objects = objects

    def insert(self, index, pane):
        """
        Inserts an object in the tabs at the specified index.

        Arguments
        ---------
        index (int): Index at which to insert the object.
        object (object): Panel components to insert as tabs.
        """
        new_object, new_name = self._to_object_and_name(pane)
        new_objects = list(self.objects)
        new_objects.insert(index, new_object)
        self._names.insert(index, new_name)
        self.objects = new_objects

    def pop(self, index):
        """
        Pops an item from the tabs by index.

        Arguments
        ---------
        index (int): The index of the item to pop from the tabs.
        """
        new_objects = list(self)
        if index in new_objects:
            index = new_objects.index(index)
        new_objects.pop(index)
        self._names.pop(index)
        self.objects = new_objects

    def remove(self, pane):
        """
        Removes an object from the tabs.

        Arguments
        ---------
        obj (object): The object to remove from the tabs.
        """
        new_objects = list(self)
        if pane in new_objects:
            index = new_objects.index(pane)
        new_objects.remove(pane)
        self._names.pop(index)
        self.objects = new_objects

    def reverse(self):
        """
        Reverses the tabs.
        """
        new_objects = list(self)
        new_objects.reverse()
        self._names.reverse()
        self.objects = new_objects
Beispiel #28
0
class aggregate(ElementOperation):
    """
    aggregate implements 2D binning for any valid HoloViews Element
    type using datashader. I.e., this operation turns a HoloViews
    Element or overlay of Elements into an hv.Image or an overlay of
    hv.Images by rasterizing it, which provides a fixed-sized
    representation independent of the original dataset size.

    By default it will simply count the number of values in each bin
    but other aggregators can be supplied implementing mean, max, min
    and other reduction operations.

    The bins of the aggregate are defined by the width and height and
    the x_range and y_range. If x_sampling or y_sampling are supplied
    the operation will ensure that a bin is no smaller than theminimum
    sampling distance by reducing the width and height when the zoomed
    in beyond the minimum sampling distance.
    """

    aggregator = param.ClassSelector(class_=ds.reductions.Reduction,
                                     default=ds.count())

    dynamic = param.Boolean(default=True,
                            doc="""
       Enables dynamic processing by default.""")

    height = param.Integer(default=400,
                           doc="""
       The height of the aggregated image in pixels.""")

    width = param.Integer(default=400,
                          doc="""
       The width of the aggregated image in pixels.""")

    x_range = param.NumericTuple(default=None,
                                 length=2,
                                 doc="""
       The x_range as a tuple of min and max x-value. Auto-ranges
       if set to None.""")

    y_range = param.NumericTuple(default=None,
                                 length=2,
                                 doc="""
       The x_range as a tuple of min and max y-value. Auto-ranges
       if set to None.""")

    x_sampling = param.Number(default=None,
                              doc="""
        Specifies the smallest allowed sampling interval along the y-axis.""")

    y_sampling = param.Number(default=None,
                              doc="""
        Specifies the smallest allowed sampling interval along the y-axis.""")

    streams = param.List(default=[RangeXY],
                         doc="""
        List of streams that are applied if dynamic=True, allowing
        for dynamic interaction with the plot.""")

    element_type = param.ClassSelector(class_=(Dataset, ),
                                       instantiate=False,
                                       is_instance=False,
                                       default=GridImage,
                                       doc="""
        The type of the returned Elements, must be a 2D Dataset type.""")

    @classmethod
    def get_agg_data(cls, obj, category=None):
        """
        Reduces any Overlay or NdOverlay of Elements into a single
        xarray Dataset that can be aggregated.
        """
        paths = []
        kdims = obj.kdims
        vdims = obj.vdims
        x, y = obj.dimensions(label=True)[:2]
        if isinstance(obj, Path):
            glyph = 'line'
            for p in obj.data:
                df = pd.DataFrame(p, columns=obj.dimensions('key', True))
                if isinstance(obj, Contours) and obj.vdims and obj.level:
                    df[obj.vdims[0].name] = p.level
                paths.append(df)
        elif isinstance(obj, CompositeOverlay):
            for key, el in obj.data.items():
                x, y, element, glyph = cls.get_agg_data(el)
                df = PandasInterface.as_dframe(element)
                if isinstance(obj, NdOverlay):
                    df = df.assign(
                        **dict(zip(obj.dimensions('key', True), key)))
                paths.append(df)
            kdims += element.kdims
            vdims = element.vdims
        elif isinstance(obj, Element):
            glyph = 'line' if isinstance(obj, Curve) else 'points'
            paths.append(PandasInterface.as_dframe(obj))
        if len(paths) > 1:
            if glyph == 'line':
                path = paths[0][:1]
                if isinstance(path, dd.DataFrame):
                    path = path.compute()
                empty = path.copy()
                empty.iloc[0, :] = (np.NaN, ) * empty.shape[1]
                paths = [elem for path in paths for elem in (path, empty)][:-1]
            if all(isinstance(path, dd.DataFrame) for path in paths):
                df = dd.concat(paths)
            else:
                paths = [
                    path.compute() if isinstance(path, dd.DataFrame) else path
                    for path in paths
                ]
                df = pd.concat(paths)
        else:
            df = paths[0]
        if category and df[category].dtype.name != 'category':
            df[category] = df[category].astype('category')
        return x, y, Dataset(df, kdims=kdims, vdims=vdims), glyph

    def _process(self, element, key=None):
        agg_fn = self.p.aggregator
        category = agg_fn.column if isinstance(agg_fn, ds.count_cat) else None
        x, y, data, glyph = self.get_agg_data(element, category)

        xstart, xend = self.p.x_range if self.p.x_range else data.range(x)
        ystart, yend = self.p.y_range if self.p.y_range else data.range(y)

        # Compute highest allowed sampling density
        width, height = self.p.width, self.p.height
        if self.p.x_sampling:
            x_range = xend - xstart
            width = int(min([(x_range / self.p.x_sampling), width]))
        if self.p.y_sampling:
            y_range = yend - ystart
            height = int(min([(y_range / self.p.y_sampling), height]))

        cvs = ds.Canvas(plot_width=width,
                        plot_height=height,
                        x_range=(xstart, xend),
                        y_range=(ystart, yend))

        column = agg_fn.column
        if column and isinstance(agg_fn, ds.count_cat):
            name = '%s Count' % agg_fn.column
        else:
            name = column
        vdims = [
            element.get_dimension(column)(name)
            if column else Dimension('Count')
        ]
        params = dict(get_param_values(element),
                      kdims=element.dimensions()[:2],
                      datatype=['xarray'],
                      vdims=vdims)

        agg = getattr(cvs, glyph)(data, x, y, self.p.aggregator)
        if agg.ndim == 2:
            return self.p.element_type(agg, **params)
        else:
            return NdOverlay(
                {
                    c: self.p.element_type(agg.sel(**{column: c}), **params)
                    for c in agg.coords[column].data
                },
                kdims=[data.get_dimension(column)])
Beispiel #29
0
class GenericOverlayPlot(GenericElementPlot):
    """
    Plotting baseclass to render (Nd)Overlay objects. It implements
    methods to handle the creation of ElementPlots, coordinating style
    groupings and zorder for all layers across a HoloMap. It also
    allows collapsing of layers via the Compositor.
    """

    batched = param.Boolean(default=True, doc="""
        Whether to plot Elements NdOverlay in a batched plotting call
        if possible. Disables legends and zorder may not be preserved.""")

    legend_limit = param.Integer(default=25, doc="""
        Number of rendered glyphs before legends are disabled.""")

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

    style_grouping = param.Integer(default=2,
                                   doc="""The length of the type.group.label
        spec that will be used to group Elements into style groups, i.e.
        a style_grouping value of 1 will group just by type, a value of 2
        will group by type and group and a value of 3 will group by the
        full specification.""")

    _passed_handles = []

    def __init__(self, overlay, ranges=None, batched=True, keys=None, **params):
        super(GenericOverlayPlot, self).__init__(overlay, ranges=ranges, keys=keys,
                                                 batched=batched, **params)

        # Apply data collapse
        self.hmap = self._apply_compositor(self.hmap, ranges, self.keys)
        self.map_lengths = Counter()
        self.group_counter = Counter()
        self.zoffset = 0
        self.subplots = self._create_subplots(ranges)
        self.traverse(lambda x: setattr(x, 'comm', self.comm))
        self.top_level = keys is None
        if self.top_level:
            self.comm = self.init_comm()
            self.traverse(lambda x: setattr(x, 'comm', self.comm))
            self.traverse(lambda x: attach_streams(self, x.hmap, 2),
                          [GenericElementPlot])


    def _apply_compositor(self, holomap, ranges=None, keys=None, dimensions=None):
        """
        Given a HoloMap compute the appropriate (mapwise or framewise)
        ranges in order to apply the Compositor collapse operations in
        display mode (data collapse should already have happened).
        """
        # Compute framewise normalization
        defaultdim = holomap.ndims == 1 and holomap.kdims[0].name != 'Frame'

        if keys and ranges and dimensions and not defaultdim:
            dim_inds = [dimensions.index(d) for d in holomap.kdims]
            sliced_keys = [tuple(k[i] for i in dim_inds) for k in keys]
            frame_ranges = OrderedDict([(slckey, self.compute_ranges(holomap, key, ranges[key]))
                                        for key, slckey in zip(keys, sliced_keys) if slckey in holomap.data.keys()])
        else:
            mapwise_ranges = self.compute_ranges(holomap, None, None)
            frame_ranges = OrderedDict([(key, self.compute_ranges(holomap, key, mapwise_ranges))
                                        for key in holomap.data.keys()])
        ranges = frame_ranges.values()

        return Compositor.collapse(holomap, (ranges, frame_ranges.keys()), mode='display')


    def _create_subplots(self, ranges):
        # Check if plot should be batched
        ordering = util.layer_sort(self.hmap)
        batched = self.batched and type(self.hmap.last) is NdOverlay
        if batched:
            backend = self.renderer.backend
            batchedplot = Store.registry[backend].get(self.hmap.last.type)
        if (batched and batchedplot and 'batched' in batchedplot._plot_methods and
            (not self.show_legend or len(ordering) > self.legend_limit)):
            self.batched = True
            keys, vmaps = [()], [self.hmap]
        else:
            self.batched = False
            keys, vmaps = self.hmap.split_overlays()

        if isinstance(self.hmap, DynamicMap):
            dmap_streams = [get_nested_streams(layer) for layer in
                            split_dmap_overlay(self.hmap)]
        else:
            dmap_streams = [None]*len(keys)

        # Compute global ordering
        length = self.style_grouping
        group_fn = lambda x: (x.type.__name__, x.last.group, x.last.label)
        for m in vmaps:
            self.map_lengths[group_fn(m)[:length]] += 1

        subplots = OrderedDict()
        for (key, vmap, streams) in zip(keys, vmaps, dmap_streams):
            subplot = self._create_subplot(key, vmap, streams, ranges)
            if subplot is None:
                continue
            if not isinstance(key, tuple): key = (key,)
            subplots[key] = subplot
            if isinstance(subplot, GenericOverlayPlot):
                self.zoffset += len(subplot.subplots.keys()) - 1

        if not subplots:
            raise SkipRendering("%s backend could not plot any Elements "
                                "in the Overlay." % self.renderer.backend)
        return subplots


    def _create_subplot(self, key, obj, streams, ranges):
        registry = Store.registry[self.renderer.backend]
        ordering = util.layer_sort(self.hmap)
        overlay_type = 1 if self.hmap.type == Overlay else 2
        group_fn = lambda x: (x.type.__name__, x.last.group, x.last.label)

        opts = {'overlaid': overlay_type}
        if self.hmap.type == Overlay:
            style_key = (obj.type.__name__,) + key
        else:
            if not isinstance(key, tuple): key = (key,)
            style_key = group_fn(obj) + key
            opts['overlay_dims'] = OrderedDict(zip(self.hmap.last.kdims, key))

        if self.batched:
            vtype = type(obj.last.last)
            oidx = 0
        else:
            vtype = type(obj.last)
            oidx = ordering.index(style_key)

        plottype = registry.get(vtype, None)
        if plottype is None:
            self.warning("No plotting class for %s type and %s backend "
                         "found. " % (vtype.__name__, self.renderer.backend))
            return None

        # Get zorder and style counter
        length = self.style_grouping
        group_key = style_key[:length]
        zorder = self.zorder + oidx + self.zoffset
        cyclic_index = self.group_counter[group_key]
        self.group_counter[group_key] += 1
        group_length = self.map_lengths[group_key]

        if not isinstance(plottype, PlotSelector) and issubclass(plottype, GenericOverlayPlot):
            opts['show_legend'] = self.show_legend
            if not any(len(frame) for frame in obj):
                self.warning('%s is empty and will be skipped during plotting'
                             % obj.last)
                return None
        elif self.batched and 'batched' in plottype._plot_methods:
            param_vals = dict(self.get_param_values())
            propagate = {opt: param_vals[opt] for opt in self._propagate_options
                         if opt in param_vals}
            opts['batched'] = self.batched
            opts['overlaid'] = self.overlaid
            opts.update(propagate)
        if len(ordering) > self.legend_limit:
            opts['show_legend'] = False
        style = self.lookup_options(obj.last, 'style').max_cycles(group_length)
        passed_handles = {k: v for k, v in self.handles.items()
                          if k in self._passed_handles}
        plotopts = dict(opts, cyclic_index=cyclic_index,
                        invert_axes=self.invert_axes,
                        dimensions=self.dimensions, keys=self.keys,
                        layout_dimensions=self.layout_dimensions,
                        ranges=ranges, show_title=self.show_title,
                        style=style, uniform=self.uniform,
                        fontsize=self.fontsize, streams=streams,
                        renderer=self.renderer, adjoined=self.adjoined,
                        stream_sources=self.stream_sources,
                        zorder=zorder, **passed_handles)
        return plottype(obj, **plotopts)


    def _create_dynamic_subplots(self, key, items, ranges, **init_kwargs):
        """
        Handles the creation of new subplots when a DynamicMap returns
        a changing set of elements in an Overlay.
        """
        length = self.style_grouping
        group_fn = lambda x: (x.type.__name__, x.last.group, x.last.label)
        keys, vmaps = self.hmap.split_overlays()
        for k, m in items:
            self.map_lengths[group_fn(vmaps[keys.index(k)])[:length]] += 1

        for k, obj in items:
            subplot = self._create_subplot(k, vmaps[keys.index(k)], [], ranges)
            if subplot is None:
                continue
            self.subplots[k] = subplot
            subplot.initialize_plot(ranges, **init_kwargs)
            subplot.update_frame(key, ranges, element=obj)


    def _update_subplot(self, subplot, spec):
        """
        Updates existing subplots when the subplot has been assigned
        to plot an element that is not an exact match to the object
        it was initially assigned.
        """
        # Handle reused plot updating plot values
        group_key = spec[:self.style_grouping]
        self.group_counter[group_key] += 1
        cyclic_index = self.group_counter[group_key]
        subplot.cyclic_index = cyclic_index
        if subplot.overlay_dims:
            odim_key = util.wrap_tuple(spec[-1])
            new_dims = zip(subplot.overlay_dims, odim_key)
            subplot.overlay_dims = util.OrderedDict(new_dims)


    def get_extents(self, overlay, ranges):
        extents = []
        items = overlay.items()
        if self.batched and self.subplots:
            subplot = list(self.subplots.values())[0]
            subplots = [(k, subplot) for k in overlay.data.keys()]
        else:
            subplots = self.subplots.items()
        for key, subplot in subplots:
            found = False
            if subplot is None:
                continue
            layer = overlay.data.get(key, None)
            if isinstance(self.hmap, DynamicMap) and layer is None:
                for _, layer in items:
                    if isinstance(layer, subplot.hmap.type):
                        found = True
                        break
                if not found:
                    layer = None
            if layer is not None and subplot.apply_ranges:
                if isinstance(layer, CompositeOverlay):
                    sp_ranges = ranges
                else:
                    sp_ranges = util.match_spec(layer, ranges) if ranges else {}
                extents.append(subplot.get_extents(layer, sp_ranges))
        return util.max_extents(extents, self.projection == '3d')
Beispiel #30
0
class Interact(param.Parameterized):
    '''

    '''
    def __init__(self):
        super(Interact, self).__init__()
        self.filelist = os.listdir(name + '/data')
        self.filelist.sort()
        self.dates = [dt.strptime(fn[:8], '%Y%m%d') for fn in self.filelist]
        self.dict = {
            d.strftime('%Y-%m-%d'): i
            for i, d in enumerate(self.dates)
        }

    nums = [i for i in range(74)]  #update this
    date = param.Selector(default=70, objects=nums)
    res = param.Integer(30, bounds=(10, 300), step=10)
    sigma = param.Number(1.0, bounds=(0.0, 3.0))

    @staticmethod
    def _format_plt(fig,
                    ax,
                    title,
                    m=0.02,
                    bgc='#292929',
                    axc='#eee',
                    lblc='#fff'):
        ax.margins(m)
        ax.set_aspect(aspect=1)
        ax.set_xlabel('Easting')
        ax.set_ylabel('Northing')
        ax.set_title(title, color=axc, loc='left', pad=20)
        ax.xaxis.label.set_color(lblc)
        ax.yaxis.label.set_color(lblc)
        ax.tick_params(axis='x', colors=lblc)
        ax.tick_params(axis='y', colors=lblc)
        ax.spines['bottom'].set_color(axc)
        ax.spines['top'].set_color(axc)
        ax.spines['right'].set_color(axc)
        ax.spines['left'].set_color(axc)
        ax.set_facecolor(bgc)
        fig.patch.set_facecolor(bgc)

    @staticmethod
    def _set_title(fn, opt='scatter'):
        '''
        '''
        date = fn[:4] + '-' + fn[4:6] + '-' + fn[6:8]

        if opt == 'scatter':
            return date + ': LiDAR Pointcloud'
        if opt == 'grid':
            return date + ': Interpolation'

    @param.depends('date')
    def input(self):
        '''
        '''
        fig, ax = plt.subplots(1)

        self.filename = self.filelist[self.date]
        self.data = Interpolate(filename=self.filename, bounds=c.BOUNDS)
        xyz = self.data.xyz

        ax.scatter(x=xyz[:, 0], y=xyz[:, 1], c=xyz[:, 2], cmap='viridis')

        title = self._set_title(fn=self.filename)
        self._format_plt(fig=fig, ax=ax, title=title)

        plt.close('all')

        return fig

    @param.depends('res', 'sigma')
    def output(self):
        '''
        '''
        fig, ax = plt.subplots(1)

        grid = self.data.interpolate_grid(xyz=self.data.xyz, res=self.res)
        self.array = ndimage.gaussian_filter(grid, sigma=self.sigma)

        ax.imshow(self.array, origin='lower')

        title = self._set_title(fn=self.data.filename, opt='grid')
        self._format_plt(fig=fig, ax=ax, title=title)

        plt.close('all')

        return fig

    def export(self):
        '''
        '''
        outfile = TemporaryFile()
        np.save(outfile, self.array)
        _ = outfile.seek(0)

        res = str(self.res)
        res = res if len(res) == 3 else '0' + res
        sigma = str(self.sigma).replace(".", "")
        sigma = sigma if len(sigma) == 2 else sigma + '0'

        params = f'R{res}S{sigma}'
        name = f'{self.filename[:8]}_NN{params}.npy'
        return pn.widgets.FileDownload(file=outfile, filename=name)