Exemplo n.º 1
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'}""")

    merge_tools = param.Boolean(default=True,
                                doc="""
        Whether to merge all the tools into a single toolbar""")

    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.""")

    show_legend = param.Boolean(default=False,
                                doc="""
        Adds a legend based on the entries of the middle-right plot""")

    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.ClassSelector(default=120,
                                    class_=(int, tuple),
                                    doc="""
        Defines the width and height of each plot in the grid, either
        as a tuple specifying width and height or an integer for a
        square plot.""")

    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.traverse(lambda x: attach_streams(self, x.hmap, 2),
                          [GenericElementPlot])
        if 'axis_offset' in params:
            self.param.warning("GridPlot axis_offset option is deprecated "
                               "since 1.12.0 since subplots are now sized "
                               "correctly and therefore no longer require "
                               "an offset.")

    def _create_subplots(self, layout, ranges):
        if isinstance(self.plot_size, tuple):
            width, height = self.plot_size
        else:
            width, height = self.plot_size, self.plot_size

        subplots = OrderedDict()
        frame_ranges = self.compute_ranges(layout, None, ranges)
        keys = self.keys[:1] if self.dynamic else self.keys
        frame_ranges = OrderedDict([
            (key, self.compute_ranges(layout, key, frame_ranges))
            for key in 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

            if type(view) in (Layout, NdLayout):
                raise SkipRendering("Cannot plot nested Layouts.")
            if not displayable(view):
                view = collate(view)

            # Create axes
            kwargs = {}
            if width is not None:
                kwargs['frame_width'] = width
            if height is not None:
                kwargs['frame_height'] = height
            if c == 0:
                kwargs['align'] = 'end'
            if c == 0 and r != 0:
                kwargs['xaxis'] = None
            if c != 0 and r == 0:
                kwargs['yaxis'] = None
            if r != 0 and c != 0:
                kwargs['xaxis'] = None
                kwargs['yaxis'] = None

            if 'border' not in kwargs:
                kwargs['border'] = 3

            if self.show_legend and c == (self.cols - 1) and r == (self.rows -
                                                                   1):
                kwargs['show_legend'] = True
                kwargs['legend_position'] = 'right'
            else:
                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.param.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],
                        merge_tools=self.merge_tools,
                        sizing_mode=self.sizing_mode,
                        toolbar_location=self.toolbar)
        plot = self._make_axes(plot)

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

        self.handles['plot'] = plot
        self.handles['plots'] = plots

        self._update_callbacks(plot)
        if self.shared_datasource:
            self.sync_sources()

        if self.top_level:
            self.init_links()

        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
        keys = self.layout.keys(full_grid=True)
        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([wrap_tuple(k)[0] for k in keys]))
            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([k[1] for k in keys]))
            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:
                x_axis.margin = (0, 0, 0, 50)
                r1, r2 = r1[::-1], r2[::-1]
            plot = gridplot([r1, r2])
        elif y_axis:
            models = [y_axis, plot]
            if self.shared_yaxis: models = models[::-1]
            plot = Row(*models)
        elif x_axis:
            models = [plot, x_axis]
            if self.shared_xaxis: models = models[::-1]
            plot = Column(*models)
        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_div(key)
        if title:
            self.handles['title']
Exemplo n.º 2
0
class MPLPlot(DimensionedPlot):
    """
    An MPLPlot object draws a matplotlib figure object when called or
    indexed but can also return a matplotlib animation object as
    appropriate. MPLPlots take element objects such as Image, Contours
    or Points as inputs and plots them in the appropriate format using
    matplotlib. As HoloMaps are supported, all plots support animation
    via the anim() method.
    """

    renderer = MPLRenderer
    sideplots = {}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        return fig, axis

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

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

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

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

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

    def update(self, key):
        rc_params = self.fig_rcparams
        if self.fig_latex:
            rc_params['text.usetex'] = True
        mpl.rcParams.update(rc_params)
        if len(self) == 1 and key == 0 and not self.drawn:
            return self.initialize_plot()
        return self.__getitem__(key)
Exemplo n.º 3
0
class LayoutPlot(GenericLayoutPlot, CompositePlot):
    """
    A LayoutPlot accepts either a Layout or a NdLayout and
    displays the elements in a cartesian grid in scanline order.
    """

    aspect_weight = param.Number(default=0,
                                 doc="""
      Weighting of the individual aspects when computing the Layout
      grid aspects and overall figure size.""")

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

    tight = param.Boolean(default=False,
                          doc="""
      Tightly fit the axes in the layout within the fig_bounds
      and tight_padding.""")

    tight_padding = param.Parameter(default=3,
                                    doc="""
      Integer or tuple specifying the padding in inches in a tight layout.""")

    hspace = param.Number(default=0.5,
                          doc="""
      Specifies the space between horizontally adjacent elements in the grid.
      Default value is set conservatively to avoid overlap of subplots.""")

    vspace = param.Number(default=0.1,
                          doc="""
      Specifies the space between vertically adjacent elements in the grid.
      Default value is set conservatively to avoid overlap of subplots.""")

    fontsize = param.Parameter(default={'title': 16}, allow_None=True)

    def __init__(self, layout, **params):
        super(LayoutPlot, self).__init__(layout=layout, **params)
        self.subplots, self.subaxes, self.layout = self._compute_gridspec(
            layout)

    def _compute_gridspec(self, layout):
        """
        Computes the tallest and widest cell for each row and column
        by examining the Layouts in the GridSpace. The GridSpec is then
        instantiated and the LayoutPlots are configured with the
        appropriate embedded layout_types. The first element of the
        returned tuple is a dictionary of all the LayoutPlots indexed
        by row and column. The second dictionary in the tuple supplies
        the grid indicies needed to instantiate the axes for each
        LayoutPlot.
        """
        layout_items = layout.grid_items()
        layout_dimensions = layout.kdims if isinstance(layout,
                                                       NdLayout) else None

        layouts = {}
        row_heightratios, col_widthratios = {}, {}
        col_aspects, row_aspects = defaultdict(lambda: [0, 0]), defaultdict(
            lambda: [0, 0])
        for (r, c) in self.coords:
            # Get view at layout position and wrap in AdjointLayout
            _, view = layout_items.get((r, c), (None, None))
            layout_view = view if isinstance(
                view, AdjointLayout) else AdjointLayout([view])
            layouts[(r, c)] = layout_view

            # Compute shape of AdjointLayout element
            layout_lens = {1: 'Single', 2: 'Dual', 3: 'Triple'}
            layout_type = layout_lens[len(layout_view)]
            hidx = 0

            # Get aspects
            main = layout_view.main
            main = main.last if isinstance(main, HoloMap) else main
            main_options = self.lookup_options(main,
                                               'plot').options if main else {}
            if main and not isinstance(main_options.get('aspect', 1),
                                       basestring):
                main_aspect = main_options.get('aspect', 1)
                main_aspect = self.aspect_weight * main_aspect + 1 - self.aspect_weight
            else:
                main_aspect = 1
            if layout_type == 'Triple':
                row_aspect = [0.25, 1. / main_aspect]
            else:
                row_aspect = [1. / main_aspect, 0]
            if layout_type in ['Dual', 'Triple']:
                col_aspect = [main_aspect, 0.25]
            else:
                col_aspect = [main_aspect, 0]

            # Compute width and height ratios
            width_ratios = AdjointLayoutPlot.layout_dict[layout_type][
                'width_ratios'][:]
            height_ratios = AdjointLayoutPlot.layout_dict[layout_type][
                'height_ratios'][:]
            if not isinstance(main_aspect, (basestring, type(None))):
                width_ratios[0] = (width_ratios[0] * main_aspect)
                height_ratios[0] = (height_ratios[hidx] * 1. / main_aspect)
            layout_shape = (len(width_ratios), len(height_ratios))

            # For each row and column record the width and height ratios
            # of the LayoutPlot with the most horizontal or vertical splits
            # and largest aspect
            if layout_shape[1] > row_heightratios.get(r, (0, None))[0]:
                row_heightratios[r] = [layout_shape[1], height_ratios]
            if height_ratios[hidx] > row_heightratios[r][1][hidx]:
                row_heightratios[r][1][hidx] = height_ratios[hidx]

            if layout_shape[0] > col_widthratios.get(c, (0, None))[0]:
                col_widthratios[c] = (layout_shape[0], width_ratios)
            if width_ratios[0] > col_widthratios[c][1][0]:
                col_widthratios[c][1][0] = width_ratios[0]

            for i in range(2):
                if col_aspect[i] > col_aspects.get(c, [0, 0])[i]:
                    col_aspects[c][i] = col_aspect[i]
                if row_aspect[i] > row_aspects.get(r, [0, 0])[i]:
                    row_aspects[r][i] = row_aspect[i]

        # In order of row/column collect the largest width and height ratios
        height_ratios = [v[1] for k, v in sorted(row_heightratios.items())]
        width_ratios = [v[1] for k, v in sorted(col_widthratios.items())]
        col_aspect_ratios = [v for k, v in sorted(col_aspects.items())]
        row_aspect_ratios = [v for k, v in sorted(row_aspects.items())]

        # Compute the number of rows and cols
        cols = np.sum([len(wr) for wr in width_ratios])
        rows = np.sum([len(hr) for hr in height_ratios])

        # Flatten the width and height ratio lists
        wr_list = [wr for wrs in width_ratios for wr in wrs]
        hr_list = [hr for hrs in height_ratios for hr in hrs]

        # Compute and set the plot size if not explicitly supplied
        col_ars = [ar for ars in col_aspect_ratios for ar in ars]
        row_ars = [ar for ars in row_aspect_ratios for ar in ars]
        width = len(col_ars[::2]) + sum(col_ars[1::2])
        yscale = sum(col_ars) / sum(row_ars)
        xinches, yinches = None, None
        if not isinstance(self.fig_inches, (tuple, list)):
            xinches = self.fig_inches * width
            yinches = xinches / yscale
        elif self.fig_inches[0] is None:
            xinches = self.fig_inches[1] * yscale
            yinches = self.fig_inches[1]
        elif self.fig_inches[1] is None:
            xinches = self.fig_inches[0]
            yinches = self.fig_inches[0] / yscale
        if xinches and yinches:
            self.handles['fig'].set_size_inches([xinches, yinches])

        self.gs = gridspec.GridSpec(rows,
                                    cols,
                                    width_ratios=wr_list,
                                    height_ratios=hr_list,
                                    wspace=self.hspace,
                                    hspace=self.vspace)

        # Situate all the Layouts in the grid and compute the gridspec
        # indices for all the axes required by each LayoutPlot.
        gidx = 0
        layout_count = 0
        tight = self.tight
        collapsed_layout = layout.clone(shared_data=False, id=layout.id)
        frame_ranges = self.compute_ranges(layout, None, None)
        frame_ranges = OrderedDict([
            (key, self.compute_ranges(layout, key, frame_ranges))
            for key in self.keys
        ])
        layout_subplots, layout_axes = {}, {}
        for r, c in self.coords:
            # Compute the layout type from shape
            wsplits = len(width_ratios[c])
            hsplits = len(height_ratios[r])
            if (wsplits, hsplits) == (1, 1):
                layout_type = 'Single'
            elif (wsplits, hsplits) == (2, 1):
                layout_type = 'Dual'
            elif (wsplits, hsplits) == (1, 2):
                layout_type = 'Embedded Dual'
            elif (wsplits, hsplits) == (2, 2):
                layout_type = 'Triple'

            # Get the AdjoinLayout at the specified coordinate
            view = layouts[(r, c)]
            positions = AdjointLayoutPlot.layout_dict[layout_type]['positions']

            # Create temporary subplots to get projections types
            # to create the correct subaxes for all plots in the layout
            _, _, projs = self._create_subplots(layouts[(r, c)],
                                                positions,
                                                None,
                                                frame_ranges,
                                                create=False)
            gidx, gsinds = self.grid_situate(gidx, layout_type, cols)

            layout_key, _ = layout_items.get((r, c), (None, None))
            if isinstance(layout, NdLayout) and layout_key:
                layout_dimensions = OrderedDict(
                    zip(layout_dimensions, layout_key))

            # Generate the axes and create the subplots with the appropriate
            # axis objects, handling any Empty objects.
            obj = layouts[(r, c)]
            empty = isinstance(obj.main, Empty)
            if empty:
                obj = AdjointLayout([])
            else:
                layout_count += 1
            subaxes = [
                plt.subplot(self.gs[ind], projection=proj)
                for ind, proj in zip(gsinds, projs)
            ]
            subplot_data = self._create_subplots(
                obj,
                positions,
                layout_dimensions,
                frame_ranges,
                dict(zip(positions, subaxes)),
                num=0 if empty else layout_count)
            subplots, adjoint_layout, _ = subplot_data
            layout_axes[(r, c)] = subaxes

            # Generate the AdjointLayoutsPlot which will coordinate
            # plotting of AdjointLayouts in the larger grid
            plotopts = self.lookup_options(view, 'plot').options
            layout_plot = AdjointLayoutPlot(adjoint_layout,
                                            layout_type,
                                            subaxes,
                                            subplots,
                                            fig=self.handles['fig'],
                                            **plotopts)
            layout_subplots[(r, c)] = layout_plot
            tight = not any(
                type(p) is GridPlot
                for p in layout_plot.subplots.values()) and tight
            if layout_key:
                collapsed_layout[layout_key] = adjoint_layout

        # Apply tight layout if enabled and incompatible
        # GridPlot isn't present.
        if tight:
            if isinstance(self.tight_padding, (tuple, list)):
                wpad, hpad = self.tight_padding
                padding = dict(w_pad=wpad, h_pad=hpad)
            else:
                padding = dict(w_pad=self.tight_padding,
                               h_pad=self.tight_padding)
            self.gs.tight_layout(self.handles['fig'],
                                 rect=self.fig_bounds,
                                 **padding)

        # Create title handle
        if self.show_title and len(self.coords) > 1:
            title = self.handles['fig'].suptitle('', **self._fontsize('title'))
            self.handles['title'] = title

        return layout_subplots, layout_axes, collapsed_layout

    def grid_situate(self, current_idx, layout_type, subgrid_width):
        """
        Situate the current AdjointLayoutPlot in a LayoutPlot. The
        LayoutPlot specifies a layout_type into which the AdjointLayoutPlot
        must be embedded. This enclosing layout is guaranteed to have
        enough cells to display all the views.

        Based on this enforced layout format, a starting index
        supplied by LayoutPlot (indexing into a large gridspec
        arrangement) is updated to the appropriate embedded value. It
        will also return a list of gridspec indices associated with
        the all the required layout axes.
        """
        # Set the layout configuration as situated in a NdLayout

        if layout_type == 'Single':
            start, inds = current_idx + 1, [current_idx]
        elif layout_type == 'Dual':
            start, inds = current_idx + 2, [current_idx, current_idx + 1]

        bottom_idx = current_idx + subgrid_width
        if layout_type == 'Embedded Dual':
            bottom = ((current_idx + 1) % subgrid_width) == 0
            grid_idx = (bottom_idx if bottom else current_idx) + 1
            start, inds = grid_idx, [current_idx, bottom_idx]
        elif layout_type == 'Triple':
            bottom = ((current_idx + 2) % subgrid_width) == 0
            grid_idx = (bottom_idx if bottom else current_idx) + 2
            start, inds = grid_idx, [
                current_idx, current_idx + 1, bottom_idx, bottom_idx + 1
            ]

        return start, inds

    def _create_subplots(self,
                         layout,
                         positions,
                         layout_dimensions,
                         ranges,
                         axes={},
                         num=1,
                         create=True):
        """
        Plot all the views contained in the AdjointLayout Object using axes
        appropriate to the layout configuration. All the axes are
        supplied by LayoutPlot - the purpose of the call is to
        invoke subplots with correct options and styles and hide any
        empty axes as necessary.
        """
        subplots = {}
        projections = []
        adjoint_clone = layout.clone(shared_data=False, id=layout.id)
        subplot_opts = dict(show_title=False, adjoined=layout)
        for pos in positions:
            # Pos will be one of 'main', 'top' or 'right' or None
            view = layout.get(pos, None)
            ax = axes.get(pos, None)
            if view is None:
                projections.append(None)
                continue

            # Determine projection type for plot
            components = view.traverse(lambda x: x)
            projs = [
                '3d' if isinstance(c, Element3D) else self.lookup_options(
                    c, 'plot').options.get('projection', None)
                for c in components
            ]
            projs = [p for p in projs if p is not None]
            if len(set(projs)) > 1:
                raise Exception(
                    "A single axis may only be assigned one projection type")
            elif projs:
                projections.append(projs[0])
            else:
                projections.append(None)

            if not create:
                continue

            # Customize plotopts depending on position.
            plotopts = self.lookup_options(view, 'plot').options

            # Options common for any subplot
            override_opts = {}
            sublabel_opts = {}
            if pos == 'main':
                own_params = self.get_param_values(onlychanged=True)
                sublabel_opts = {
                    k: v
                    for k, v in own_params if 'sublabel_' in k
                }
                if not isinstance(view, GridSpace):
                    override_opts = dict(aspect='square')
            elif pos == 'right':
                right_opts = dict(orientation='vertical',
                                  xaxis=None,
                                  yaxis='left')
                override_opts = dict(subplot_opts, **right_opts)
            elif pos == 'top':
                top_opts = dict(xaxis='bottom', yaxis=None)
                override_opts = dict(subplot_opts, **top_opts)

            # Override the plotopts as required
            plotopts = dict(sublabel_opts, **plotopts)
            plotopts.update(override_opts, fig=self.handles['fig'])
            vtype = view.type if isinstance(view, HoloMap) else view.__class__
            if isinstance(view, GridSpace):
                plotopts['create_axes'] = ax is not None
            if pos == 'main':
                plot_type = Store.registry['matplotlib'][vtype]
            else:
                plot_type = MPLPlot.sideplots[vtype]
            num = num if len(self.coords) > 1 else 0
            subplots[pos] = plot_type(view,
                                      axis=ax,
                                      keys=self.keys,
                                      dimensions=self.dimensions,
                                      layout_dimensions=layout_dimensions,
                                      ranges=ranges,
                                      subplot=True,
                                      uniform=self.uniform,
                                      layout_num=num,
                                      **plotopts)
            if isinstance(view, (Element, HoloMap, CompositeOverlay)):
                adjoint_clone[pos] = subplots[pos].map
            else:
                adjoint_clone[pos] = subplots[pos].layout
        return subplots, adjoint_clone, projections

    def update_handles(self, axis, view, key, ranges=None):
        """
        Should be called by the update_frame class to update
        any handles on the plot.
        """
        if self.show_title and 'title' in self.handles and len(
                self.coords) > 1:
            self.handles['title'].set_text(self._format_title(key))

    def initialize_plot(self):
        axis = self.handles['axis']
        self.update_handles(axis, None, self.keys[-1])

        ranges = self.compute_ranges(self.layout, self.keys[-1], None)
        for subplot in self.subplots.values():
            subplot.initialize_plot(ranges=ranges)

        return self._finalize_axis(None)
Exemplo n.º 4
0
class Dimension(param.Parameterized):
    """
    Dimension objects are used to specify some important general
    features that may be associated with a collection of values.

    For instance, a Dimension may specify that a set of numeric values
    actually correspond to 'Height' (dimension name), in units of
    meters, with a descriptive label 'Height of adult males'.

    All dimensions object have a name that identifies them and a label
    containing a suitable description. If the label is not explicitly
    specified it matches the name.

    These two parameters define the core identity of the dimension
    object and must match if two dimension objects are to be considered
    equivalent. All other parameters are considered optional metadata
    and are not used when testing for equality.

    Unlike all the other parameters, these core parameters can be used
    to construct a Dimension object from a tuple. This format is
    sufficient to define an identical Dimension:

    Dimension('a', label='Dimension A') == Dimension(('a', 'Dimension A'))

    Everything else about a dimension is considered to reflect
    non-semantic preferences. Examples include the default value (which
    may be used in a visualization to set an initial slider position),
    how the value is to rendered as text (which may be used to specify
    the printed floating point precision) or a suitable range of values
    to consider for a particular analysis.

    Units
    -----

    Full unit support with automated conversions are on the HoloViews
    roadmap. Once rich unit objects are supported, the unit (or more
    specifically the type of unit) will be part of the core dimension
    specification used to establish equality.

    Until this feature is implemented, there are two auxiliary
    parameters that hold some partial information about the unit: the
    name of the unit and whether or not it is cyclic. The name of the
    unit is used as part of the pretty-printed representation and
    knowing whether it is cyclic is important for certain operations.
    """

    name = param.String(doc="""
       Short name associated with the Dimension, such as 'height' or
       'weight'. Valid Python identifiers make good names, because they
       can be used conveniently as a keyword in many contexts.""")

    label = param.String(default=None, doc="""
        Unrestricted label used to describe the dimension. A label
        should succinctly describe the dimension and may contain any
        characters, including Unicode and LaTeX expression.""")

    cyclic = param.Boolean(default=False, doc="""
        Whether the range of this feature is cyclic such that the
        maximum allowed value (defined by the range parameter) is
        continuous with the minimum allowed value.""")

    value_format = param.Callable(default=None, doc="""
        Formatting function applied to each value before display.""")

    range = param.Tuple(default=(None, None), doc="""
        Specifies the minimum and maximum allowed values for a
        Dimension. None is used to represent an unlimited bound.""")

    soft_range = param.Tuple(default=(None, None), doc="""
        Specifies a minimum and maximum reference value, which
        may be overridden by the data.""")

    type = param.Parameter(default=None, doc="""
        Optional type associated with the Dimension values. The type
        may be an inbuilt constructor (such as int, str, float) or a
        custom class object.""")

    step = param.Number(default=None, doc="""
        Optional floating point step specifying how frequently the
        underlying space should be sampled. May be used to define a
        discrete sampling of over the range.""")

    unit = param.String(default=None, allow_None=True, doc="""
        Optional unit string associated with the Dimension. For
        instance, the string 'm' may be used represent units of meters
        and 's' to represent units of seconds.""")

    values = param.List(default=[], doc="""
        Optional specification of the allowed value set for the
        dimension that may also be used to retain a categorical
        ordering.""")

    # Defines default formatting by type
    type_formatters = {}
    unit_format = ' ({unit})'
    presets = {} # A dictionary-like mapping name, (name,) or
                 # (name, unit) to a preset Dimension object

    def __init__(self, spec, **params):
        """
        Initializes the Dimension object with the given name.
        """
        if 'name' in params:
            raise KeyError('Dimension name must only be passed as the positional argument')

        if isinstance(spec, Dimension):
            existing_params = dict(spec.get_param_values())
        elif (spec, params.get('unit', None)) in self.presets.keys():
            preset = self.presets[(str(spec), str(params['unit']))]
            existing_params = dict(preset.get_param_values())
        elif spec in self.presets:
            existing_params = dict(self.presets[spec].get_param_values())
        elif (spec,) in self.presets:
            existing_params = dict(self.presets[(spec,)].get_param_values())
        else:
            existing_params = {}

        all_params = dict(existing_params, **params)
        if isinstance(spec, tuple):
            name, label = spec
            all_params['name'] = name
            all_params['label'] = label
            if 'label' in params and (label != params['label']):
                if params['label'] != label:
                    self.warning('Using label as supplied by keyword ({!r}), ignoring '
                                 'tuple value {!r}'.format(params['label'], label))
                all_params['label'] = params['label']
        elif isinstance(spec, basestring):
            all_params['name'] = spec
            all_params['label'] = params.get('label', spec)

        if all_params['name'] == '':
            raise ValueError('Dimension name cannot be the empty string')
        if all_params['label'] in ['', None]:
            raise ValueError('Dimension label cannot be None or the empty string')

        values = params.get('values', [])
        if isinstance(values, basestring) and values == 'initial':
            self.warning("The 'initial' string for dimension values is no longer supported.")
            values = []

        all_params['values'] = list(unique_array(values))
        super(Dimension, self).__init__(**all_params)


    @property
    def spec(self):
        "Returns the corresponding tuple specification"
        return (self.name, self.label)


    def __call__(self, spec=None, **overrides):
        "Aliased to clone method. To be deprecated in 2.0"
        return self.clone(spec=spec, **overrides)


    def clone(self, spec=None, **overrides):
        """
        Derive a new Dimension that inherits existing parameters
        except for the supplied, explicit overrides
        """
        settings = dict(self.get_param_values(onlychanged=True), **overrides)

        if spec is None:
            spec = (self.name, overrides.get('label', self.label))
        if 'label' in overrides and isinstance(spec, basestring) :
            spec = (spec, overrides['label'])
        elif 'label' in overrides and isinstance(spec, tuple) :
            if overrides['label'] != spec[1]:
                self.warning('Using label as supplied by keyword ({!r}), ignoring '
                             'tuple value {!r}'.format(overrides['label'], spec[1]))
            spec = (spec[0],  overrides['label'])

        return self.__class__(spec, **{k:v for k,v in settings.items()
                                       if k not in ['name', 'label']})

    def __hash__(self):
        """
        The hash allows Dimension objects to be used as dictionary keys in Python 3.
        """
        return hash(self.spec)

    def __setstate__(self, d):
        """
        Compatibility for pickles before alias attribute was introduced.
        """
        super(Dimension, self).__setstate__(d)
        self.label = self.name

    def __eq__(self, other):
        "Implements equals operator including sanitized comparison."

        if isinstance(other, Dimension):
            return self.spec == other.spec

        # For comparison to strings. Name may be sanitized.
        return other in [self.name, self.label,  dimension_sanitizer(self.name)]

    def __ne__(self, other):
        "Implements not equal operator including sanitized comparison."
        return not self.__eq__(other)

    def __lt__(self, other):
        "Dimensions are sorted alphanumerically by name"
        return self.name < other.name if isinstance(other, Dimension) else self.name < other

    def __str__(self):
        return self.name

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

    @property
    def pprint_label(self):
        "The pretty-printed label string for the Dimension"
        unit = ('' if self.unit is None
                else type(self.unit)(self.unit_format).format(unit=self.unit))
        return bytes_to_unicode(self.label) + bytes_to_unicode(unit)

    def pprint(self):
        changed = dict(self.get_param_values(onlychanged=True))
        if len(set([changed.get(k, k) for k in ['name','label']])) == 1:
            return 'Dimension({spec})'.format(spec=repr(self.name))

        ordering = sorted( sorted(changed.keys()),
                           key=lambda k: (- float('inf')
                                          if self.params(k).precedence is None
                                          else self.params(k).precedence))
        kws = ", ".join('%s=%r' % (k, changed[k]) for k in ordering if k != 'name')
        return 'Dimension({spec}, {kws})'.format(spec=repr(self.name), kws=kws)


    def pprint_value(self, value):
        """
        Applies the defined formatting to the value.
        """
        own_type = type(value) if self.type is None else self.type
        formatter = (self.value_format if self.value_format
                     else self.type_formatters.get(own_type))
        if formatter:
            if callable(formatter):
                return formatter(value)
            elif isinstance(formatter, basestring):
                if isinstance(value, dt.datetime):
                    return value.strftime(formatter)
                elif isinstance(value, np.datetime64):
                    return dt64_to_dt(value).strftime(formatter)
                elif re.findall(r"\{(\w+)\}", formatter):
                    return formatter.format(value)
                else:
                    return formatter % value
        return unicode(bytes_to_unicode(value))

    def pprint_value_string(self, value):
        """
        Pretty prints the dimension name and value using the global
        title_format variable, including the unit string (if
        set). Numeric types are printed to the stated rounding level.
        """
        unit = '' if self.unit is None else ' ' + bytes_to_unicode(self.unit)
        value = self.pprint_value(value)
        return title_format.format(name=bytes_to_unicode(self.label), val=value, unit=unit)
Exemplo n.º 5
0
class GeoPlot(ProjectionPlot, ElementPlot):
    """
    Plotting baseclass for geographic plots with a cartopy projection.
    """

    apply_ranges = param.Boolean(default=False,
                                 doc="""
        Do not use ranges to compute plot extents by default.""")

    projection = param.Parameter(default=ccrs.PlateCarree())

    def __init__(self, element, **params):
        if 'projection' not in params:
            el = element.last if isinstance(element, HoloMap) else element
            params['projection'] = el.crs
        super(GeoPlot, self).__init__(element, **params)
        plot_opts = self.lookup_options(self.hmap.last, 'plot').options
        self.geographic = is_geographic(self.hmap.last)
        if 'aspect' not in plot_opts:
            self.aspect = 'equal' if self.geographic else 'square'

    def get_extents(self, element, ranges):
        """
        Subclasses the get_extents method using the GeoAxes
        set_extent method to project the extents to the
        Elements coordinate reference system.
        """
        ax = self.handles['axis']
        extents = super(GeoPlot, self).get_extents(element, ranges)
        x0, y0, x1, y1 = extents
        if not self.geographic:
            return extents

        # If extent can't be determined autoscale, otherwise use
        # set_extent to convert coordinates to native coordinate
        # system
        if (None in extents or any(not np.isfinite(e) for e in extents)
                or x0 == x1 or y0 == y1):
            extents = None
        else:
            try:
                extents = project_extents((x0, y0, x1, y1), element.crs,
                                          self.projection)
            except:
                extents = (np.NaN, ) * 4

        if extents:
            l, b, r, t = extents
        else:
            ax.autoscale_view()
            (l, r), (b, t) = ax.get_xlim(), ax.get_ylim()

        return l, b, r, t

    def teardown_handles(self):
        """
        Delete artist handle so it can be redrawn.
        """
        try:
            self.handles['artist'].remove()
        except ValueError:
            pass
Exemplo n.º 6
0
class GenesVsCounts(OperationInterface):
    hue = param.Parameter(default=None)
    mode = param.ObjectSelector(default="scatter", objects=["scatter"])

    @staticmethod
    def genewise_scatter(data,
                         hue=None,
                         gene_dim="Gene",
                         sample_dim="Sample",
                         count_dim="counts"):

        hvds = hv.Dataset(data.to_dataframe().reset_index())

        kdims = [gene_dim, count_dim]
        vdims = [sample_dim]

        if hue:
            vdims += [hue]

        scatter = hv.Scatter(hvds, kdims=kdims, vdims=vdims)

        if hue is not None:
            overlays = {
                key: scatter.select(**{hue: key})
                for key in scatter.data[hue].unique()
            }
            return hv.NdOverlay(overlays)

        return scatter

    # @staticmethod
    # def genewise_violin(data, hue=None,
    #                     gene_dim="Gene", sample_dim="Sample", count_dim="counts"):
    #     hvds = hv.Dataset(data.to_dataframe().reset_index())
    #
    #     kdims = [gene_dim]
    #     vdims = [count_dim]
    #
    #     if hue:
    #         kdims += [hue]
    #
    #     violin = hv.Violin(hvds, kdims=kdims, vdims=vdims)
    #
    #     if hue is not None:
    #         overlays = {key: violin.select(**{hue: key})
    #                     for key in violin.data[hue].unique()}
    #         return hv.NdOverlay(overlays)
    #
    #     return violin

    def process(self):

        self.set_param(gene_set_mode="intersection")

        if self.gene_set_collection is not None:
            self.set_param(selected_gene_sets=list(
                self.gene_set_collection.gene_sets.keys()))

        plotting_dims = [self.count_variable]
        if self.hue:
            plotting_dims += [self.hue]

        selected_subset = self.gem.data[plotting_dims].sel(
            self.get_selection_indexes())

        modes = {
            "scatter": self.genewise_scatter,
            # "violin": self.genewise_violin,
        }

        return modes[self.mode](selected_subset, self.hue)
Exemplo n.º 7
0
class DETRApp(param.Parameterized):
    "A Panel App for object detection using DE:TR:"
    title = param.String("DE:TR: Object Detection App")

    progress = param.Parameter()

    input_image_url = param.String(config.DEFAULT_URL, label="Input Image URL")
    run_detr = param.Action(label="Run DE:TR:")
    set_random_image = param.Action(label="Random Image")

    plot = param.Parameter()

    suppression_enabled = param.Boolean(config.SUPPRESSION_ENABLED,
                                        label="Enabled")
    suppression = param.Number(
        config.SUPPRESSION_DEFAULT,
        bounds=config.SUPPRESSION_BOUNDS,
        label="Non-maximum suppression (IoU)",
    )
    confidence = param.Number(config.CONFIDENCE_DEFAULT,
                              bounds=config.CONFIDENCE_BOUNDS,
                              label="Confidence Treshold")

    view = param.Parameter()

    def __init__(self, **params):
        params["progress"], params["plot"], params["view"] = self._get_view()
        params["set_random_image"] = self._set_random_image
        params["run_detr"] = self._update_plot

        super().__init__(**params)

        self.transform, self.detr, self.device = get_transform_detr_and_device(
        )

        self.run_detr()  # pylint: disable=not-callable

    def _get_view(self):
        style = pn.pane.HTML(config.STYLE,
                             width=0,
                             height=0,
                             margin=0,
                             sizing_mode="fixed")

        description = pn.pane.Markdown(__doc__)
        progress = pn.widgets.Progress(bar_color="secondary",
                                       width=285,
                                       sizing_mode="fixed",
                                       margin=(0, 5, 10, 5))
        progress.active = False
        app_bar = pn.Row(
            pn.pane.Markdown("# " + self.title,
                             sizing_mode="stretch_width",
                             margin=(None, None, None, 25)),
            sizing_mode="stretch_width",
            margin=(25, 5, 0, 5),
            css_classes=["app-bar"],
        )

        top_selections = pn.Row(
            pn.Param(
                self,
                parameters=["input_image_url"],
                default_layout=pn.Row,
                show_name=False,
            ),
            pn.Param(
                self,
                parameters=["run_detr", "set_random_image"],
                widgets={"set_random_image": {
                    "button_type": "success"
                }},
                default_layout=pn.Row,
                show_name=False,
                width=300,
                margin=(14, 5, 5, 5),
                sizing_mode="fixed",
            ),
        )
        top_selections = pn.Row(
            pn.Param(
                self,
                parameters=["input_image_url", "run_detr", "set_random_image"],
                widgets={
                    "set_random_image": {
                        "button_type": "success",
                        "align": "end",
                        "width": 125,
                        "sizing_mode": "fixed",
                    },
                    "run_detr": {
                        "align": "end",
                        "width": 125,
                        "sizing_mode": "fixed"
                    },
                },
                default_layout=pn.Row,
                show_name=False,
                width=900,
            ))
        bottom_selections = pn.Column(
            pn.Param(
                self,
                parameters=["suppression", "confidence"],
                default_layout=pn.Row,
                show_name=False,
            ),
            pn.Param(
                self,
                parameters=["suppression_enabled"],
                show_name=False,
            ),
        )
        plot = pn.pane.Plotly(height=600, config={"responsive": True})
        app_view = pn.Column(
            style,
            description,
            app_bar,
            pn.Row(pn.Spacer(), progress),
            top_selections,
            plot,
            bottom_selections,
        )
        return progress, plot, app_view

    def _set_random_image(self, _=None):
        urls = config.RANDOM_URLS
        current_url = self.input_image_url
        new_url = current_url

        while current_url == new_url:
            new_url = random.choice(urls)
        self.input_image_url = new_url
        self._update_plot()

    def _update_plot(self, _=None):
        self.progress.active = True
        self.plot.object = get_figure(
            apply_nms=self.suppression_enabled,
            iou=self.suppression,
            confidence=self.confidence,
            url=self.input_image_url,
            transform=self.transform,
            detr=self.detr,
            device=self.device,
        )
        self.progress.active = False
class FileSelector(CompositeWidget):

    directory = param.String(default=os.getcwd(),
                             doc="""
        The directory to explore.""")

    file_pattern = param.String(default='*',
                                doc="""
        A glob-like pattern to filter the files.""")

    only_files = param.Boolean(default=False,
                               doc="""
        Whether to only allow selecting files.""")

    margin = param.Parameter(default=(5, 10, 20, 10),
                             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).""")

    show_hidden = param.Boolean(default=False,
                                doc="""
        Whether to show hidden files and directories (starting with
        a period).""")

    size = param.Integer(default=10,
                         doc="""
        The number of options shown at once (note this is the only
        way to control the height of this widget)""")

    refresh_period = param.Integer(default=None,
                                   doc="""
        If set to non-None value indicates how frequently to refresh
        the directory contents in milliseconds.""")

    root_directory = param.String(default=None,
                                  doc="""
        If set, overrides directory parameter as the root directory
        beyond which users cannot navigate.""")

    value = param.List(default=[], doc="""
        List of selected files.""")

    _composite_type = Column

    def __init__(self, directory=None, **params):
        from ..pane import Markdown
        if directory is not None:
            params['directory'] = os.path.abspath(
                os.path.expanduser(directory))
        if 'root_directory' in params:
            root = params['root_directory']
            params['root_directory'] = os.path.abspath(
                os.path.expanduser(root))
        if params.get('width') and params.get(
                'height') and 'sizing_mode' not in params:
            params['sizing_mode'] = None

        super().__init__(**params)

        # Set up layout
        layout = {
            p: getattr(self, p)
            for p in Layoutable.param
            if p not in ('name', 'height',
                         'margin') and getattr(self, p) is not None
        }
        sel_layout = dict(layout,
                          sizing_mode='stretch_both',
                          height=None,
                          margin=0)
        self._selector = CrossSelector(filter_fn=lambda p, f: fnmatch(f, p),
                                       size=self.size,
                                       **sel_layout)
        self._back = Button(name='◀',
                            width=25,
                            margin=(5, 10, 0, 0),
                            disabled=True)
        self._forward = Button(name='▶',
                               width=25,
                               margin=(5, 10),
                               disabled=True)
        self._up = Button(name='⬆', width=25, margin=(5, 10), disabled=True)
        self._directory = TextInput(value=self.directory,
                                    margin=(5, 10),
                                    width_policy='max')
        self._go = Button(name='⬇',
                          disabled=True,
                          width=25,
                          margin=(5, 10, 0, 0))
        self._reload = Button(name='↻', width=25, margin=(5, 15, 0, 10))
        self._nav_bar = Row(
            self._back, self._forward, self._up, self._directory, self._go,
            self._reload,
            **dict(layout, width=None, margin=0, width_policy='max'))
        self._composite[:] = [self._nav_bar, Divider(margin=0), self._selector]
        self._selector._selected.insert(
            0, Markdown('### Selected files', margin=0))
        self._selector._unselected.insert(
            0, Markdown('### File Browser', margin=0))
        self.link(self._selector, size='size')

        # Set up state
        self._stack = []
        self._cwd = None
        self._position = -1
        self._update_files(True)

        # Set up callback
        self.link(self._directory, directory='value')
        self._selector.param.watch(self._update_value, 'value')
        self._go.on_click(self._update_files)
        self._reload.on_click(self._update_files)
        self._up.on_click(self._go_up)
        self._back.on_click(self._go_back)
        self._forward.on_click(self._go_forward)
        self._directory.param.watch(self._dir_change, 'value')
        self._selector._lists[False].param.watch(self._select, 'value')
        self._selector._lists[False].param.watch(self._filter_blacklist,
                                                 'options')
        self._periodic = PeriodicCallback(callback=self._refresh,
                                          period=self.refresh_period or 0)
        self.param.watch(self._update_periodic, 'refresh_period')
        if self.refresh_period:
            self._periodic.start()

    def _update_periodic(self, event):
        if event.new:
            self._periodic.period = event.new
            if not self._periodic.running:
                self._periodic.start()
        elif self._periodic.running:
            self._periodic.stop()

    @property
    def _root_directory(self):
        return self.root_directory or self.directory

    def _update_value(self, event):
        value = [
            v for v in event.new if not self.only_files or os.path.isfile(v)
        ]
        self._selector.value = value
        self.value = value

    def _dir_change(self, event):
        path = os.path.abspath(os.path.expanduser(self._directory.value))
        if not path.startswith(self._root_directory):
            self._directory.value = self._root_directory
            return
        elif path != self._directory.value:
            self._directory.value = path
        self._go.disabled = path == self._cwd

    def _refresh(self):
        self._update_files(refresh=True)

    def _update_files(self, event=None, refresh=False):
        path = os.path.abspath(self._directory.value)
        refresh = refresh or (event
                              and getattr(event, 'obj', None) is self._reload)
        if refresh:
            path = self._cwd
        elif not os.path.isdir(path):
            self._selector.options = ['Entered path is not valid']
            self._selector.disabled = True
            return
        elif event is not None and (not self._stack
                                    or path != self._stack[-1]):
            self._stack.append(path)
            self._position += 1

        self._cwd = path
        if not refresh:
            self._go.disabled = True
        self._up.disabled = path == self._root_directory
        if self._position == len(self._stack) - 1:
            self._forward.disabled = True
        if 0 <= self._position and len(self._stack) > 1:
            self._back.disabled = False

        selected = self.value
        dirs, files = scan_path(path, self.file_pattern)
        for s in selected:
            check = os.path.realpath(s) if os.path.islink(s) else s
            if os.path.isdir(check):
                dirs.append(s)
            elif os.path.isfile(check):
                files.append(s)

        paths = [
            p for p in sorted(dirs) + sorted(files)
            if self.show_hidden or not os.path.basename(p).startswith('.')
        ]
        abbreviated = [
            ('📁' if f in dirs else '') + os.path.relpath(f, self._cwd)
            for f in paths
        ]
        options = OrderedDict(zip(abbreviated, paths))
        self._selector.options = options
        self._selector.value = selected

    def _filter_blacklist(self, event):
        """
        Ensure that if unselecting a currently selected path and it
        is not in the current working directory then it is removed
        from the blacklist.
        """
        dirs, files = scan_path(self._cwd, self.file_pattern)
        paths = [('📁' if p in dirs else '') + os.path.relpath(p, self._cwd)
                 for p in dirs + files]
        blacklist = self._selector._lists[False]
        options = OrderedDict(self._selector._items)
        self._selector.options.clear()
        self._selector.options.update([(k, v) for k, v in options.items()
                                       if k in paths or v in self.value])
        blacklist.options = [o for o in blacklist.options if o in paths]

    def _select(self, event):
        if len(event.new) != 1:
            self._directory.value = self._cwd
            return

        relpath = event.new[0].replace('📁', '')
        sel = os.path.abspath(os.path.join(self._cwd, relpath))
        if os.path.isdir(sel):
            self._directory.value = sel
        else:
            self._directory.value = self._cwd

    def _go_back(self, event):
        self._position -= 1
        self._directory.value = self._stack[self._position]
        self._update_files()
        self._forward.disabled = False
        if self._position == 0:
            self._back.disabled = True

    def _go_forward(self, event):
        self._position += 1
        self._directory.value = self._stack[self._position]
        self._update_files()

    def _go_up(self, event=None):
        path = self._cwd.split(os.path.sep)
        self._directory.value = os.path.sep.join(path[:-1]) or os.path.sep
        self._update_files(True)
Exemplo n.º 9
0
class Layoutable(param.Parameterized):

    align = param.ObjectSelector(default='start',
                                 objects=['start', 'end', 'center'],
                                 doc="""
        Whether the object should be aligned with the start, end or
        center of its container""")

    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.Parameter(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=5,
                             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 ``height`` pixels. Component will overflow if
            it can't fit in the available vertical space.

        ``"fit"``
            Use component's preferred height (if set) and allow to fit
            into the available vertical space within the minimum and
            maximum height bounds (if set). Component's height neither
            will be aggressively minimized nor maximized.

        ``"min"``
            Use as little vertical space as possible, not less than
            the minimum height (if set).  The starting point is the
            preferred height (if set). The height of the component may
            shrink or grow depending on the parent layout, aspect
            management and other factors.

        ``"max"``
            Use as much vertical space as possible, not more than the
            maximum height (if set).  The starting point is the
            preferred height (if set). The height 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 params.get('width_policy') is None
                and params.get('height_policy') is None
                and 'sizing_mode' not in params):
            params['sizing_mode'] = 'fixed'
        elif (not (self.param.sizing_mode.constant
                   or self.param.sizing_mode.readonly)
              and type(self).sizing_mode is None):
            params['sizing_mode'] = params.get('sizing_mode',
                                               config.sizing_mode)
        super(Layoutable, self).__init__(**params)
class annotate(param.ParameterizedFunction):
    """
    The annotate function allows drawing, editing and annotating any
    given Element (if it is supported). The annotate function returns
    a Layout of the editable plot and an Overlay of table(s), which
    allow editing the data of the element. The edited and annotated
    data may be accessed using the element and selected properties.
    """

    annotator = param.Parameter(doc="""The current Annotator instance.""")

    annotations = param.ClassSelector(default=[],
                                      class_=(dict, list),
                                      doc="""
        Annotations to associate with each object.""")

    edit_vertices = param.Boolean(default=True,
                                  doc="""
        Whether to add tool to edit vertices.""")

    empty_value = param.Parameter(default=None,
                                  doc="""
        The value to insert on annotation columns when drawing a new
        element.""")

    num_objects = param.Integer(default=None,
                                bounds=(0, None),
                                doc="""
        The maximum number of objects to draw.""")

    show_vertices = param.Boolean(default=True,
                                  doc="""
        Whether to show vertices when drawing the Path.""")

    table_transforms = param.HookList(default=[],
                                      doc="""
        Transform(s) to apply to element when converting data to Table.
        The functions should accept the Annotator and the transformed
        element as input.""")

    table_opts = param.Dict(default={
        'editable': True,
        'width': 400
    },
                            doc="""
        Opts to apply to the editor table(s).""")

    vertex_annotations = param.ClassSelector(default=[],
                                             class_=(dict, list),
                                             doc="""
        Columns to annotate the Polygons with.""")

    vertex_style = param.Dict(default={'nonselection_alpha': 0.5},
                              doc="""
        Options to apply to vertices during drawing and editing.""")

    _annotator_types = OrderedDict()

    @property
    def annotated(self):
        annotated = self.annotator.object
        if Store.current_backend == 'bokeh':
            return annotated.opts(clone=True, tools=['hover'])

    @property
    def selected(self):
        selected = self.annotator.selected
        if Store.current_backend == 'bokeh':
            return selected.opts(clone=True, tools=['hover'])

    @classmethod
    def compose(cls, *annotators):
        """Composes multiple annotator layouts and elements

        The composed Layout will contain all the elements in the
        supplied annotators and an overlay of all editor tables.

        Args:
            annotators: Annotator layouts or elements to compose

        Returns:
            A new layout consisting of the overlaid plots and tables
        """
        layers = []
        tables = []
        for annotator in annotators:
            if isinstance(annotator, Layout):
                l, ts = annotator
                layers.append(l)
                tables += ts
            elif isinstance(annotator, annotate):
                layers.append(annotator.plot)
                tables += [t[0].object for t in annotator.editor]
            elif isinstance(annotator, (HoloMap, ViewableElement)):
                layers.append(annotator)
            else:
                raise ValueError("Cannot compose %s type with annotators." %
                                 type(annotator).__name__)
        tables = Overlay(tables, group='Annotator')
        return (Overlay(layers).collate() + tables)

    def __call__(self, element, **params):
        overlay = element if isinstance(element, Overlay) else [element]

        layers = []
        annotator_type = None
        for element in overlay:
            matches = []
            for eltype, atype in self._annotator_types.items():
                if isinstance(element, eltype):
                    matches.append(
                        (getmro(type(element)).index(eltype), atype))
            if matches:
                if annotator_type is not None:
                    msg = (
                        'An annotate call may only annotate a single element. '
                        'If you want to annotate multiple elements call annotate '
                        'on each one separately and then use the annotate.compose '
                        'method to combine them into a single layout.')
                    raise ValueError(msg)
                annotator_type = sorted(matches)[0][1]
                self.annotator = annotator_type(element, **params)
                tables = Overlay([t[0].object for t in self.annotator.editor],
                                 group='Annotator')
                layout = (self.annotator.plot + tables)
                layers.append(layout)
            else:
                layers.append(element)

        if annotator_type is None:
            obj = overlay if isinstance(overlay, Overlay) else element
            raise ValueError('Could not find an Element to annotate on'
                             '%s object.' % type(obj).__name__)

        if len(layers) == 1:
            return layers[0]
        return self.compose(*layers)
Exemplo n.º 11
0
Arquivo: vega.py Projeto: yuttie/panel
class Vega(PaneBase):
    """
    Vega panes allow rendering Vega plots and traces.

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

    margin = param.Parameter(default=(5, 5, 30, 5),
                             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).""")

    priority = 0.8

    _updates = True

    @classmethod
    def is_altair(cls, obj):
        if 'altair' in sys.modules:
            import altair as alt
            return isinstance(obj, alt.api.TopLevelMixin)
        return False

    @classmethod
    def applies(cls, obj):
        if isinstance(obj, dict) and 'vega' in obj.get('$schema', '').lower():
            return True
        return cls.is_altair(obj)

    @classmethod
    def _to_json(cls, obj):
        if isinstance(obj, dict):
            json = dict(obj)
            if 'data' in json:
                data = json['data']
                if isinstance(data, dict):
                    json['data'] = dict(data)
                elif isinstance(data, list):
                    json['data'] = [dict(d) for d in data]
            return json
        return obj.to_dict()

    def _get_sources(self, json, sources):
        datasets = json.get('datasets', {})
        for name in list(datasets):
            if name in sources or isinstance(datasets[name], dict):
                continue
            data = datasets.pop(name)
            if isinstance(data, list) and any(
                    isinstance(d, dict) and 'geometry' in d for d in data):
                # Handle geometry records types
                datasets[name] = data
                continue
            columns = set(data[0]) if data else []
            if self.is_altair(self.object):
                import altair as alt
                if (not isinstance(
                        self.object.data,
                    (alt.Data, alt.UrlData, type(alt.Undefined)))
                        and columns == set(self.object.data)):
                    data = ColumnDataSource.from_df(self.object.data)
                else:
                    data = ds_as_cds(data)
                sources[name] = ColumnDataSource(data=data)
            else:
                sources[name] = ColumnDataSource(data=ds_as_cds(data))
        data = json.get('data', {})
        if isinstance(data, dict):
            data = data.pop('values', {})
            if data:
                sources['data'] = ColumnDataSource(data=ds_as_cds(data))
        elif isinstance(data, list):
            for d in data:
                if 'values' in d:
                    sources[d['name']] = ColumnDataSource(
                        data=ds_as_cds(d.pop('values')))

    @classmethod
    def _get_dimensions(cls, json, props):
        if json is None:
            return

        if 'config' in json and 'view' in json['config']:
            size_config = json['config']['view']
        else:
            size_config = json

        view = {}
        for w in ('width', 'continuousWidth'):
            if w in size_config:
                view['width'] = size_config[w]
        for h in ('height', 'continuousHeight'):
            if h in size_config:
                view['height'] = size_config[h]

        for p in ('width', 'height'):
            if p not in view or isinstance(view[p], string_types):
                continue
            if props.get(p) is None or p in view and props.get(p) < view[p]:
                v = view[p]
                props[p] = v + 22 if isinstance(v, int) else v

        responsive_height = json.get('height') == 'container'
        responsive_width = json.get('width') == 'container'
        if responsive_height and responsive_width:
            props['sizing_mode'] = 'stretch_both'
        elif responsive_width:
            props['sizing_mode'] = 'stretch_width'
        elif responsive_height:
            props['sizing_mode'] = 'stretch_height'

    def _get_model(self, doc, root=None, parent=None, comm=None):
        VegaPlot = lazy_load('panel.models.vega', 'VegaPlot',
                             isinstance(comm, JupyterComm))
        sources = {}
        if self.object is None:
            json = None
        else:
            json = self._to_json(self.object)
            self._get_sources(json, sources)
        props = self._process_param_change(self._init_params())
        self._get_dimensions(json, props)
        model = VegaPlot(data=json, data_sources=sources, **props)
        if root is None:
            root = model
        self._models[root.ref['id']] = (model, parent)
        return model

    def _update(self, ref=None, model=None):
        if self.object is None:
            json = None
        else:
            json = self._to_json(self.object)
            self._get_sources(json, model.data_sources)
        props = {
            p: getattr(self, p)
            for p in list(Layoutable.param) if getattr(self, p) is not None
        }
        self._get_dimensions(json, props)
        props['data'] = json
        model.update(**props)
class Annotator(PaneBase):
    """
    An Annotator allows drawing, editing and annotating a specific
    type of element. Each Annotator consists of the `plot` to draw and
    edit the element and the `editor`, which contains a list of tables,
    which make it possible to annotate each object in the element with
    additional properties defined in the `annotations`.
    """

    annotations = param.ClassSelector(default=[],
                                      class_=(dict, list),
                                      doc="""
        Annotations to associate with each object.""")

    default_opts = param.Dict(default={
        'responsive': True,
        'min_height': 400,
        'padding': 0.1,
        'framewise': True
    },
                              doc="""
        Opts to apply to the element.""")

    empty_value = param.Parameter(default=None,
                                  doc="""
        The value to insert on annotation columns when drawing a new
        element.""")

    object = param.ClassSelector(class_=Element,
                                 doc="""
        The Element to edit and annotate.""")

    num_objects = param.Integer(default=None,
                                bounds=(0, None),
                                doc="""
        The maximum number of objects to draw.""")

    table_transforms = param.HookList(default=[],
                                      doc="""
        Transform(s) to apply to element when converting data to Table.
        The functions should accept the Annotator and the transformed
        element as input.""")

    table_opts = param.Dict(default={
        'editable': True,
        'width': 400
    },
                            doc="""
        Opts to apply to the editor table(s).""")

    # Once generic editing tools are merged into bokeh this could
    # include snapshot, restore and clear tools
    _tools = []

    # Allows patching on custom behavior
    _extra_opts = {}

    # Triggers for updates to the table
    _triggers = ['annotations', 'object', 'table_opts']

    # Links between plot and table
    _link_type = DataLink
    _selection_link_type = SelectionLink

    priority = 0.7

    @classmethod
    def applies(cls, obj):
        if 'holoviews' not in sys.modules:
            return False
        return isinstance(obj, cls.param.object.class_)

    @property
    def _element_type(self):
        return self.param.object.class_

    @property
    def _object_name(self):
        return self._element_type.__name__

    def __init__(self, object=None, **params):
        super(Annotator, self).__init__(None, **params)
        self.object = self._process_element(object)
        self._table_row = Row()
        self.editor = Tabs(('%s' % param_name(self.name), self._table_row))
        self.plot = DynamicMap(self._get_plot)
        self.plot.callback.inputs[:] = [self.object]
        self._tables = []
        self._init_stream()
        self._stream.add_subscriber(self._update_object, precedence=0.1)
        self._selection = Selection1D(source=self.plot)
        self._update_table()
        self._update_links()
        self.param.watch(self._update, self._triggers)
        self.layout[:] = [self.plot, self.editor]

    @param.depends('annotations', 'object', 'default_opts')
    def _get_plot(self):
        return self._process_element(self.object)

    def _get_model(self, doc, root=None, parent=None, comm=None):
        return self.layout._get_model(doc, root, parent, comm)

    @preprocess
    def _update(self, event=None):
        if event and event.name == 'object':
            with param.discard_events(self):
                self.object = self._process_element(event.new)
        self._update_table()

    def _update_links(self):
        if hasattr(self, '_link'): self._link.unlink()
        self._link = self._link_type(self.plot, self._table)
        if self._selection_link_type:
            if hasattr(self, '_selection_link'): self._selection_link.unlink()
            self._selection_link = SelectionLink(self.plot, self._table)

    def _update_object(self, data=None):
        with param.discard_events(self):
            if len(self._stream.source) == 0:
                self.plot[()]
            self.object = self._stream.element

    def _update_table(self):
        object = self.object
        for transform in self.table_transforms:
            object = transform(object)
        self._table = Table(object, label=param_name(self.name)).opts(
            show_title=False, **self.table_opts)
        self._update_links()
        self._table_row[:] = [self._table]

    def select(self, selector=None):
        return self.layout.select(selector)

    @classmethod
    def compose(cls, *annotators):
        """Composes multiple Annotator instances and elements

        The composed Panel will contain all the elements in the
        supplied Annotators and Tabs containing all editors.

        Args:
            annotators: Annotator objects or elements to compose

        Returns:
            A new Panel consisting of the overlaid plots and tables
        """
        layers, tables = [], []
        for a in annotators:
            if isinstance(a, Annotator):
                layers.append(a.plot)
                tables += a.tables
            elif isinstance(a, Element):
                layers.append(a)
        return Row(Overlay(layers).collate(), Tabs(*tables))

    @property
    def tables(self):
        return list(zip(self.editor._names, self.editor))

    @property
    def selected(self):
        return self.object.iloc[self._selection.index]
Exemplo n.º 13
0
class Param(PaneBase):
    """
    Param panes render a Parameterized class to a set of widgets which
    are linked to the parameter values on the class.
    """

    display_threshold = param.Number(default=0, precedence=-10, doc="""
        Parameters with precedence below this value are not displayed.""")

    default_layout = param.ClassSelector(default=Column, class_=Panel,
                                         is_instance=False)

    default_precedence = param.Number(default=1e-8, precedence=-10, doc="""
        Precedence value to use for parameters with no declared
        precedence.  By default, zero predecence is available for
        forcing some parameters to the top of the list, and other
        values above the default_precedence values can be used to sort
        or group parameters arbitrarily.""")

    expand = param.Boolean(default=False, doc="""
        Whether parameterized subobjects are expanded or collapsed on
        instantiation.""")

    expand_button = param.Boolean(default=None, doc="""
        Whether to add buttons to expand and collapse sub-objects.""")

    expand_layout = param.Parameter(default=Column, doc="""
        Layout to expand sub-objects into.""")

    height = param.Integer(default=None, bounds=(0, None), doc="""
        Height of widgetbox the parameter widgets are displayed in.""")

    initializer = param.Callable(default=None, doc="""
        User-supplied function that will be called on initialization,
        usually to update the default Parameter values of the
        underlying parameterized object.""")

    name = param.String(default='', doc="""
        Title of the pane.""")

    parameters = param.List(default=[], allow_None=True, doc="""
        If set this serves as a whitelist of parameters to display on
        the supplied Parameterized object.""")

    show_labels = param.Boolean(default=True, doc="""
        Whether to show labels for each widget""")

    show_name = param.Boolean(default=True, doc="""
        Whether to show the parameterized object's name""")

    width = param.Integer(default=300, allow_None=True, bounds=(0, None), doc="""
        Width of widgetbox the parameter widgets are displayed in.""")

    widgets = param.Dict(doc="""
        Dictionary of widget overrides, mapping from parameter name
        to widget class.""")

    priority = 0.1

    _unpack = True

    _mapping = {
        param.Action:            Button,
        param.Boolean:           Checkbox,
        param.CalendarDate:      DatePicker,
        param.Color:             ColorPicker,
        param.Date:              DatetimeInput,
        param.DateRange:         DateRangeSlider,
        param.CalendarDateRange: DateRangeSlider,
        param.DataFrame:         DataFrame,
        param.Dict:              LiteralInputTyped,
        param.FileSelector:      SingleFileSelector,
        param.Filename:          TextInput,
        param.Foldername:        TextInput,
        param.Integer:           IntSlider,
        param.List:              LiteralInputTyped,
        param.MultiFileSelector: FileSelector,
        param.ListSelector:      MultiSelect,
        param.Number:            FloatSlider,
        param.ObjectSelector:    Select,
        param.Parameter:         LiteralInputTyped,
        param.Range:             RangeSlider,
        param.Selector:          Select,
        param.String:            TextInput,
    }

    if hasattr(param, 'Event'):
        _mapping[param.Event] = Button

    _rerender_params = []

    def __init__(self, object=None, **params):
        if isinstance(object, param.Parameter):
            if not 'show_name' in params:
                params['show_name'] = False
            params['parameters'] = [object.name]
            object = object.owner
        if isinstance(object, param.parameterized.Parameters):
            object = object.cls if object.self is None else object.self

        if 'parameters' not in params and object is not None:
            params['parameters'] = [p for p in object.param if p != 'name']
            self._explicit_parameters = False
        else:
            self._explicit_parameters = object is not None

        if object and 'name' not in params:
            params['name'] = param_name(object.name)
        super().__init__(object, **params)
        self._updating = []

        # Construct Layout
        kwargs = {p: v for p, v in self.param.get_param_values()
                  if p in Layoutable.param and v is not None}
        self._widget_box = self.default_layout(**kwargs)

        layout = self.expand_layout
        if isinstance(layout, Panel):
            self._expand_layout = layout
            self.layout = self._widget_box
        elif isinstance(self._widget_box, layout):
            self.layout = self._expand_layout = self._widget_box
        elif isinstance(layout, type) and issubclass(layout, Panel):
            self.layout = self._expand_layout = layout(self._widget_box, **kwargs)
        else:
            raise ValueError('expand_layout expected to be a panel.layout.Panel'
                             'type or instance, found %s type.' %
                             type(layout).__name__)
        self.param.watch(self._update_widgets, [
            'object', 'parameters', 'name', 'display_threshold', 'expand_button',
            'expand', 'expand_layout', 'widgets', 'show_labels', 'show_name'])
        self._update_widgets()

    def __repr__(self, depth=0):
        cls = type(self).__name__
        obj_cls = type(self.object).__name__
        params = [] if self.object is None else list(self.object.param)
        parameters = [k for k in params if k != 'name']
        params = []
        for p, v in sorted(self.param.get_param_values()):
            if v is self.param[p].default: continue
            elif v is None: continue
            elif isinstance(v, string_types) and v == '': continue
            elif p == 'object' or (p == 'name' and (v.startswith(obj_cls) or v.startswith(cls))): continue
            elif p == 'parameters' and v == parameters: continue
            try:
                params.append('%s=%s' % (p, abbreviated_repr(v)))
            except RuntimeError:
                params.append('%s=%s' % (p, '...'))
        obj = 'None' if self.object is None else '%s' % type(self.object).__name__
        template = '{cls}({obj}, {params})' if params else '{cls}({obj})'
        return template.format(cls=cls, params=', '.join(params), obj=obj)

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

    @property
    def _synced_params(self):
        ignored_params = ['default_layout', 'loading']
        return [p for p in Layoutable.param if p not in ignored_params]

    def _update_widgets(self, *events):
        parameters = []
        for event in sorted(events, key=lambda x: x.name):
            if event.name == 'object':
                if isinstance(event.new, param.parameterized.Parameters):
                    # Setting object will trigger this method a second time
                    self.object = event.new.cls if event.new.self is None else event.new.self
                    return
                
                if self._explicit_parameters:
                    parameters = self.parameters
                elif event.new is None:
                    parameters = []
                else:
                    parameters = [p for p in event.new.param if p != 'name']
                    self.name = param_name(event.new.name)
            if event.name == 'parameters':
                if event.new is None:
                    self._explicit_parameters = False
                    if self.object is not None:
                        parameters = [p for p in self.object.param if p != 'name']
                else:
                    self._explicit_parameters = True
                    parameters = [] if event.new == [] else event.new

        if parameters != [] and parameters != self.parameters:
            # Setting parameters will trigger this method a second time
            self.parameters = parameters
            return

        for cb in list(self._callbacks):
            if cb.inst in self._widget_box.objects:
                cb.inst.param.unwatch(cb)
                self._callbacks.remove(cb)

        # Construct widgets
        if self.object is None:
            self._widgets = {}
        else:
            self._widgets = self._get_widgets()

        alias = {'_title': 'name'}
        widgets = [widget for p, widget in self._widgets.items()
                   if (self.object.param[alias.get(p, p)].precedence is None)
                   or (self.object.param[alias.get(p, p)].precedence >= self.display_threshold)]
        self._widget_box.objects = widgets
        if not (self.expand_button == False and not self.expand):
            self._link_subobjects()

    def _link_subobjects(self):
        for pname, widget in self._widgets.items():
            widgets = [widget] if isinstance(widget, Widget) else widget
            if not any(is_parameterized(getattr(w, 'value', None)) or
                       any(is_parameterized(o) for o in getattr(w, 'options', []))
                       for w in widgets):
                continue
            if (isinstance(widgets, Row) and isinstance(widgets[1], Toggle)):
                selector, toggle = (widgets[0], widgets[1])
            else:
                selector, toggle = (widget, None)

            def toggle_pane(change, parameter=pname):
                "Adds or removes subpanel from layout"
                parameterized = getattr(self.object, parameter)
                existing = [p for p in self._expand_layout.objects
                            if isinstance(p, Param) and
                            p.object in recursive_parameterized(parameterized)]
                if not change.new:
                    self._expand_layout[:] = [
                        e for e in self._expand_layout.objects
                        if e not in existing
                    ]
                elif change.new:
                    kwargs = {k: v for k, v in self.param.get_param_values()
                              if k not in ['name', 'object', 'parameters']}
                    pane = Param(parameterized, name=parameterized.name,
                                 **kwargs)
                    if isinstance(self._expand_layout, Tabs):
                        title = self.object.param[pname].label
                        pane = (title, pane)
                    self._expand_layout.append(pane)

            def update_pane(change, parameter=pname):
                "Adds or removes subpanel from layout"
                layout = self._expand_layout
                existing = [p for p in layout.objects if isinstance(p, Param)
                            and p.object is change.old]

                if toggle:
                    toggle.disabled = not is_parameterized(change.new)
                if not existing:
                    return
                elif is_parameterized(change.new):
                    parameterized = change.new
                    kwargs = {k: v for k, v in self.param.get_param_values()
                              if k not in ['name', 'object', 'parameters']}
                    pane = Param(parameterized, name=parameterized.name,
                                 **kwargs)
                    layout[layout.objects.index(existing[0])] = pane
                else:
                    layout.pop(existing[0])

            watchers = [selector.param.watch(update_pane, 'value')]
            if toggle:
                watchers.append(toggle.param.watch(toggle_pane, 'value'))
            self._callbacks += watchers

            if self.expand:
                if self.expand_button:
                    toggle.value = True
                else:
                    toggle_pane(namedtuple('Change', 'new')(True))

    def widget(self, p_name):
        """Get widget for param_name"""
        p_obj = self.object.param[p_name]
        kw_widget = {}

        widget_class_overridden = True
        if self.widgets is None or p_name not in self.widgets:
            widget_class_overridden = False
            widget_class = self.widget_type(p_obj)
        elif isinstance(self.widgets[p_name], dict):
            kw_widget = dict(self.widgets[p_name])
            if 'widget_type' in self.widgets[p_name]:
                widget_class = kw_widget.pop('widget_type')
            elif 'type' in self.widgets[p_name]:
                widget_class = kw_widget.pop('type')
            else:
                widget_class_overridden = False
                widget_class = self.widget_type(p_obj)
        else:
            widget_class = self.widgets[p_name]

        if not self.show_labels and not issubclass(widget_class, _ButtonBase):
            label = ''
        else:
            label = p_obj.label
        kw = dict(disabled=p_obj.constant, name=label)

        value = getattr(self.object, p_name)
        if value is not None:
            kw['value'] = value

        # Update kwargs
        kw.update(kw_widget)

        if hasattr(p_obj, 'get_range'):
            options = p_obj.get_range()
            if not options and value is not None:
                options = [value]
            kw['options'] = options
        if hasattr(p_obj, 'get_soft_bounds'):
            bounds = p_obj.get_soft_bounds()
            if bounds[0] is not None:
                kw['start'] = bounds[0]
            if bounds[1] is not None:
                kw['end'] = bounds[1]
            if ('start' not in kw or 'end' not in kw):
                # Do not change widget class if _mapping was overridden
                if not widget_class_overridden:
                    if (isinstance(p_obj, param.Number) and
                        not isinstance(p_obj, (param.Date, param.CalendarDate))):
                        widget_class = FloatInput
                        if isinstance(p_obj, param.Integer):
                            widget_class = IntInput
                    elif not issubclass(widget_class, LiteralInput):
                        widget_class = LiteralInput
            if hasattr(widget_class, 'step') and getattr(p_obj, 'step', None):
                kw['step'] = p_obj.step

        kwargs = {k: v for k, v in kw.items() if k in widget_class.param}

        if isinstance(widget_class, type) and issubclass(widget_class, Button):
            kwargs.pop('value', None)

        if isinstance(widget_class, Widget):
            widget = widget_class
        else:
            widget = widget_class(**kwargs)
        widget._param_pane = self
        widget._param_name = p_name

        watchers = self._callbacks

        def link_widget(change):
            if p_name in self._updating:
                return
            try:
                self._updating.append(p_name)
                self.object.param.set_param(**{p_name: change.new})
            finally:
                self._updating.remove(p_name)

        if hasattr(param, 'Event') and isinstance(p_obj, param.Event):
            def event(change):
                self.object.param.trigger(p_name)
            watcher = widget.param.watch(event, 'clicks')
        elif isinstance(p_obj, param.Action):
            def action(change):
                value(self.object)
            watcher = widget.param.watch(action, 'clicks')
        elif kw_widget.get('throttled', False) and hasattr(widget, 'value_throttled'):
            watcher = widget.param.watch(link_widget, 'value_throttled')
        else:
            watcher = widget.param.watch(link_widget, 'value')
        watchers.append(watcher)

        def link(change, watchers=[watcher]):
            updates = {}
            if p_name not in self._widgets:
                return
            widget = self._widgets[p_name]
            if change.what == 'constant':
                updates['disabled'] = change.new
            elif change.what == 'precedence':
                if change.new is change.old:
                    return
                elif change.new is None:
                    self._rerender()
                elif (change.new < self.display_threshold and
                      widget in self._widget_box.objects):
                    self._widget_box.pop(widget)
                elif change.new >= self.display_threshold:
                    self._rerender()
                return
            elif change.what == 'objects':
                updates['options'] = p_obj.get_range()
            elif change.what == 'bounds':
                start, end = p_obj.get_soft_bounds()
                supports_bounds = hasattr(widget, 'start')
                if start is None or end is None:
                    rerender = supports_bounds
                else:
                    rerender = not supports_bounds
                if supports_bounds:
                    updates['start'] = start
                    updates['end'] = end
                if rerender:
                    self._rerender_widget(p_name)
                    return
            elif change.what == 'step':
                updates['step'] = p_obj.step
            elif change.what == 'label':
                updates['name'] = p_obj.label
            elif p_name in self._updating:
                return
            elif hasattr(param, 'Event') and isinstance(p_obj, param.Event):
                return
            elif isinstance(p_obj, param.Action):
                prev_watcher = watchers[0]
                widget.param.unwatch(prev_watcher)
                def action(event):
                    change.new(self.object)
                watchers[0] = widget.param.watch(action, 'clicks')
                idx = self._callbacks.index(prev_watcher)
                self._callbacks[idx] = watchers[0]
                return
            elif kw_widget.get('throttled', False) and hasattr(widget, 'value_throttled'):
                updates['value_throttled'] = change.new
            elif isinstance(widget, Row) and len(widget) == 2:
                updates['value'] = change.new
                widget = widget[0]
            else:
                updates['value'] = change.new

            try:
                self._updating.append(p_name)
                if change.type == 'triggered':
                    with discard_events(widget):
                        widget.param.set_param(**updates)
                    widget.param.trigger(*updates)
                else:
                    widget.param.set_param(**updates)
            finally:
                self._updating.remove(p_name)

        # Set up links to parameterized object
        watchers.append(self.object.param.watch(link, p_name, 'constant'))
        watchers.append(self.object.param.watch(link, p_name, 'precedence'))
        watchers.append(self.object.param.watch(link, p_name, 'label'))
        if hasattr(p_obj, 'get_range'):
            watchers.append(self.object.param.watch(link, p_name, 'objects'))
        if hasattr(p_obj, 'get_soft_bounds'):
            watchers.append(self.object.param.watch(link, p_name, 'bounds'))
        if 'step' in kw:
            watchers.append(self.object.param.watch(link, p_name, 'step'))
        watchers.append(self.object.param.watch(link, p_name))

        options = kwargs.get('options', [])
        if isinstance(options, dict):
            options = options.values()
        if ((is_parameterized(value) or any(is_parameterized(o) for o in options))
            and (self.expand_button or (self.expand_button is None and not self.expand))):
            widget.margin = (5, 0, 5, 10)
            toggle = Toggle(name='\u22EE', button_type='primary',
                            disabled=not is_parameterized(value), max_height=30,
                            max_width=20, height_policy='fit', align='end',
                            margin=(0, 0, 5, 10))
            widget.width = self._widget_box.width-60
            return Row(widget, toggle, width_policy='max', margin=0)
        else:
            return widget

    @property
    def _ordered_params(self):
        params = [(p, pobj) for p, pobj in self.object.param.objects('existing').items()
                  if p in self.parameters or p == 'name']
        key_fn = lambda x: x[1].precedence if x[1].precedence is not None else self.default_precedence
        sorted_precedence = sorted(params, key=key_fn)
        filtered = [(k, p) for k, p in sorted_precedence]
        groups = itertools.groupby(filtered, key=key_fn)
        # Params preserve definition order in Python 3.6+
        dict_ordered_py3 = (sys.version_info.major == 3 and sys.version_info.minor >= 6)
        dict_ordered = dict_ordered_py3 or (sys.version_info.major > 3)
        ordered_groups = [list(grp) if dict_ordered else sorted(grp) for (_, grp) in groups]
        ordered_params = [el[0] for group in ordered_groups for el in group
                          if (el[0] != 'name' or el[0] in self.parameters)]
        return ordered_params

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

    def _rerender(self):
        precedence = lambda k: self.object.param['name' if k == '_title' else k].precedence
        params = self._ordered_params
        if self.show_name:
            params.insert(0, '_title')
        widgets = []
        for k in params:
            if precedence(k) is None or precedence(k) >= self.display_threshold:
                widgets.append(self._widgets[k])
        self._widget_box.objects = widgets

    def _rerender_widget(self, p_name):
        watchers = []
        for w in self._callbacks:
            if w.inst is self._widgets[p_name]:
                w.inst.param.unwatch(w)
            else:
                watchers.append(w)
        self._widgets[p_name] = self.widget(p_name)
        self._rerender()
    
    def _get_widgets(self):
        """Return name,widget boxes for all parameters (i.e., a property sheet)"""
        # Format name specially
        if self.expand_layout is Tabs:
            widgets = []
        elif self.show_name:
            widgets = [('_title', StaticText(value='<b>{0}</b>'.format(self.name)))]
        else:
            widgets = []
        widgets += [(pname, self.widget(pname)) for pname in self._ordered_params]
        return OrderedDict(widgets)

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

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

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

    @classmethod
    def applies(cls, obj):
        return (is_parameterized(obj) or
                isinstance(obj, param.parameterized.Parameters) or
                (isinstance(obj, param.Parameter) and obj.owner is not None))

    @classmethod
    def widget_type(cls, pobj):
        ptype = type(pobj)
        for t in classlist(ptype)[::-1]:
            if t in cls._mapping:
                if isinstance(cls._mapping[t], types.FunctionType):
                    return cls._mapping[t](pobj)
                return cls._mapping[t]

    def select(self, selector=None):
        """
        Iterates over the Viewable and any potential children in the
        applying the Selector.

        Arguments
        ---------
        selector: type or callable or None
          The selector allows selecting a subset of Viewables by
          declaring a type or callable function to filter by.

        Returns
        -------
        viewables: list(Viewable)
        """
        return super().select(selector) + self.layout.select(selector)

    def get_root(self, doc=None, comm=None, preprocess=True):
        """
        Returns the root model and applies pre-processing hooks

        Arguments
        ---------
        doc: bokeh.Document
          Bokeh document the bokeh model will be attached to.
        comm: pyviz_comms.Comm
          Optional pyviz_comms when working in notebook
        preprocess: boolean (default=True)
          Whether to run preprocessing hooks

        Returns
        -------
        Returns the bokeh model corresponding to this panel object
        """
        doc = init_doc(doc)
        root = self.layout.get_root(doc, comm, preprocess)
        ref = root.ref['id']
        self._models[ref] = (root, None)
        state._views[ref] = (self, root, doc, comm)
        return root
Exemplo n.º 14
0
class DiscreteSlider(CompositeWidget, _SliderBase):

    options = param.ClassSelector(default=[], class_=(dict, list))

    value = param.Parameter()

    formatter = param.String(default='%.3g')

    _rename = {'formatter': None}

    _supports_embed = True

    _text_link = """
    var labels = {labels}
    target.text = labels[source.value]
    """

    def __init__(self, **params):
        self._syncing = False
        super(DiscreteSlider, self).__init__(**params)
        if 'formatter' not in params and all(
                isinstance(v, (int, np.int_)) for v in self.values):
            self.formatter = '%d'
        if self.value is None and None not in self.values and self.options:
            self.value = self.values[0]
        elif self.value not in self.values:
            raise ValueError('Value %s not a valid option, '
                             'ensure that the supplied value '
                             'is one of the declared options.' % self.value)

        self._text = StaticText(margin=(5, 0, 0, 5),
                                style={'white-space': 'nowrap'})
        self._slider = IntSlider()
        self._composite = Column(self._text, self._slider)
        self._update_options()
        self.param.watch(self._update_options, ['options', 'formatter'])
        self.param.watch(self._update_value, ['value'])

    def _update_options(self, *events):
        values, labels = self.values, self.labels
        if self.value not in values:
            value = 0
            self.value = values[0]
        else:
            value = values.index(self.value)
        self._slider = IntSlider(start=0,
                                 end=len(self.options) - 1,
                                 value=value,
                                 tooltips=False,
                                 show_value=False,
                                 margin=(0, 5, 5, 5),
                                 _supports_embed=False)
        js_code = self._text_link.format(labels=repr(self.labels))
        self._jslink = self._slider.jslink(self._text, code={'value': js_code})
        self._slider.param.watch(self._sync_value, 'value')
        self._text.value = labels[value]
        self._composite[1] = self._slider

    def _update_value(self, event):
        values = self.values
        if self.value not in values:
            self.value = values[0]
            return
        index = self.values.index(self.value)
        if self._syncing:
            return
        try:
            self._syncing = True
            self._slider.value = index
        finally:
            self._syncing = False

    def _sync_value(self, event):
        if self._syncing:
            return
        try:
            self._syncing = True
            self.value = self.values[event.new]
        finally:
            self._syncing = False

    def _get_embed_state(self, root, max_opts=3):
        model = self._composite[1]._models[root.ref['id']][0]
        return self, model, self.values, lambda x: x.value, 'value', 'cb_obj.value'

    @property
    def labels(self):
        title = (self.name + ': ' if self.name else '')
        if isinstance(self.options, dict):
            return [title + ('<b>%s</b>' % o) for o in self.options]
        else:
            return [
                title + ('<b>%s</b>' % (o if isinstance(o, string_types) else
                                        (self.formatter % o)))
                for o in self.options
            ]

    @property
    def values(self):
        return list(self.options.values()) if isinstance(
            self.options, dict) else self.options
Exemplo n.º 15
0
class DataFrame(HTML):
    """
    DataFrame renders pandas, dask and streamz DataFrame types using
    their custom HTML repr. In the case of a streamz DataFrame the
    rendered data will update periodically.

    """

    bold_rows = param.Boolean(default=True, doc="""
        Make the row labels bold in the output.""")

    border = param.Integer(default=0, doc="""
        A ``border=border`` attribute is included in the opening
        `<table>` tag.""")

    classes = param.List(default=['panel-df'], doc="""
        CSS class(es) to apply to the resulting html table.""")

    col_space = param.ClassSelector(default=None, class_=(str, int), doc="""
        The minimum width of each column in CSS length units. An int
        is assumed to be px units.""")

    decimal = param.String(default='.', doc="""
        Character recognized as decimal separator, e.g. ',' in Europe.""")

    float_format = param.Callable(default=None, doc="""
        Formatter function to apply to columns' elements if they are
        floats. The result of this function must be a unicode string.""")

    formatters = param.ClassSelector(default=None, class_=(dict, list), doc="""
        Formatter functions to apply to columns' elements by position
        or name. The result of each function must be a unicode string.""")

    header = param.Boolean(default=True, doc="""
        Whether to print column labels.""")

    index = param.Boolean(default=True, doc="""
        Whether to print index (row) labels.""")

    index_names = param.Boolean(default=True, doc="""
        Prints the names of the indexes.""")

    justify = param.ObjectSelector(default=None, allow_None=True, objects=[
        'left', 'right', 'center', 'justify', 'justify-all', 'start',
        'end', 'inherit', 'match-parent', 'initial', 'unset'], doc="""
        How to justify the column labels.""")

    max_rows = param.Integer(default=None, doc="""
        Maximum number of rows to display.""")

    max_cols = param.Integer(default=None, doc="""
        Maximum number of columns to display.""")

    na_rep = param.String(default='NaN', doc="""
        String representation of NAN to use.""")

    render_links = param.Boolean(default=False, doc="""
        Convert URLs to HTML links.""")

    show_dimensions = param.Boolean(default=False, doc="""
        Display DataFrame dimensions (number of rows by number of
        columns).""")

    sparsify = param.Boolean(default=True, doc="""
        Set to False for a DataFrame with a hierarchical index to
        print every multi-index key at each row.""")

    _object = param.Parameter(default=None, doc="""Hidden parameter.""")

    _rerender_params = [
        'object', '_object', 'bold_rows', 'border', 'classes',
        'col_space', 'decimal', 'float_format', 'formatters',
        'header', 'index', 'index_names', 'justify', 'max_rows',
        'max_cols', 'na_rep', 'render_links', 'show_dimensions',
        'sparsify', 'sizing_mode'
    ]

    _dask_params = ['max_rows']

    def __init__(self, object=None, **params):
        super(DataFrame, self).__init__(object, **params)
        self._stream = None
        self._setup_stream()

    @classmethod
    def applies(cls, obj):
        module = getattr(obj, '__module__', '')
        name = type(obj).__name__
        if (any(m in module for m in ('pandas', 'dask', 'streamz')) and
            name in ('DataFrame', 'Series', 'Random', 'DataFrames', 'Seriess')):
            return 0.3
        else:
            return False

    def _set_object(self, object):
        self._object = object

    @param.depends('object', watch=True)
    def _setup_stream(self):
        if not self._models or not hasattr(self.object, 'stream'):
            return
        elif self._stream:
            self._stream.destroy()
            self._stream = None
        self._stream = self.object.stream.latest().rate_limit(0.5).gather()
        self._stream.sink(self._set_object)

    def _get_model(self, doc, root=None, parent=None, comm=None):
        model = super(DataFrame, self)._get_model(doc, root, parent, comm)
        self._setup_stream()
        return model

    def _cleanup(self, model):
        super(DataFrame, self)._cleanup(model)
        if not self._models and self._stream:
            self._stream.destroy()
            self._stream = None

    def _get_properties(self):
        properties = DivPaneBase._get_properties(self)
        if self._stream:
            df = self._object
        else:
            df = self.object
        if hasattr(df, 'to_frame'):
            df = df.to_frame()

        module = getattr(df, '__module__', '')
        if hasattr(df, 'to_html'):
            if 'dask' in module:
                html = df.to_html(max_rows=self.max_rows).replace('border="1"', '')
            else:
                kwargs = {p: getattr(self, p) for p in self._rerender_params
                          if p not in DivPaneBase.param and p != '_object'}
                html = df.to_html(**kwargs)
        else:
            html = ''
        return dict(properties, text=escape(html))
Exemplo n.º 16
0
class LiteralInput(Widget):
    """
    LiteralInput allows declaring Python literals using a text
    input widget. Optionally a type may be declared.
    """

    serializer = param.ObjectSelector(default='ast',
                                      objects=['ast', 'json'],
                                      doc="""
       The serialization (and deserialization) method to use. 'ast'
       uses ast.literal_eval and 'json' uses json.loads and json.dumps.
    """)

    type = param.ClassSelector(default=None,
                               class_=(type, tuple),
                               is_instance=True)

    value = param.Parameter(default=None)

    _rename = {'name': 'title', 'type': None, 'serializer': None}

    _source_transforms = {'value': """JSON.parse(value.replace(/'/g, '"'))"""}

    _target_transforms = {
        'value':
        """JSON.stringify(value).replace(/,/g, ", ").replace(/:/g, ": ")"""
    }

    _widget_type = _BkTextInput

    def __init__(self, **params):
        super(LiteralInput, self).__init__(**params)
        self._state = ''
        self._validate(None)
        self._callbacks.append(self.param.watch(self._validate, 'value'))

    def _validate(self, event):
        if self.type is None: return
        new = self.value
        if not isinstance(new, self.type) and new is not None:
            if event:
                self.value = event.old
            types = repr(self.type) if isinstance(
                self.type, tuple) else self.type.__name__
            raise ValueError('LiteralInput expected %s type but value %s '
                             'is of type %s.' %
                             (types, new, type(new).__name__))

    def _process_property_change(self, msg):
        msg = super(LiteralInput, self)._process_property_change(msg)
        new_state = ''
        if 'value' in msg:
            value = msg.pop('value')
            try:
                if self.serializer == 'json':
                    value = json.loads(value)
                else:
                    value = ast.literal_eval(value)
            except Exception:
                new_state = ' (invalid)'
                value = self.value
            else:
                if self.type and not isinstance(value, self.type):
                    vtypes = self.type if isinstance(self.type,
                                                     tuple) else (self.type, )
                    typed_value = None
                    for vtype in vtypes:
                        try:
                            typed_value = vtype(value)
                        except Exception:
                            pass
                        else:
                            break
                    if typed_value is None and value is not None:
                        new_state = ' (wrong type)'
                        value = self.value
                    else:
                        value = typed_value
            msg['value'] = value
        msg['name'] = msg.get('title', self.name).replace(self._state,
                                                          '') + new_state
        self._state = new_state
        self.param.trigger('name')
        return msg

    def _process_param_change(self, msg):
        msg = super(LiteralInput, self)._process_param_change(msg)
        if 'value' in msg:
            value = '' if msg['value'] is None else msg['value']
            if isinstance(value, string_types):
                value = repr(value)
            elif self.serializer == 'json':
                value = json.dumps(value, sort_keys=True)
            else:
                value = as_unicode(value)
            msg['value'] = value
        msg['title'] = self.name
        return msg
Exemplo n.º 17
0
class HoloViews(PaneBase):
    """
    HoloViews panes render any HoloViews object to a corresponding
    Bokeh model while respecting the currently selected backend.
    """

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

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

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

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

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

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

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

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

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

    priority = 0.8

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

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

    _rerender_params = ['object', 'backend']

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

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

        center = self.center and not self._responsive_content

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        if widget_types is None:
            widget_types = {}

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

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

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

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

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

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

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

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

    aspect = param.Parameter(default='square',
                             doc="""
        The aspect ratio mode of the plot. Allows setting an
        explicit aspect ratio as width/height as well as
        'square' and 'equal' options.""")

    color_index = param.ClassSelector(default=None,
                                      allow_None=True,
                                      class_=(str, int),
                                      doc="""
      Index of the dimension from which the color will the drawn""")

    spike_length = param.Number(default=0.1,
                                doc="""
      The length of each spike if Spikes object is one dimensional.""")

    padding = param.ClassSelector(default=(0, 0.1), class_=(int, float, tuple))

    position = param.Number(default=0.,
                            doc="""
      The position of the lower end of each spike.""")

    style_opts = PathPlot.style_opts + ['cmap']

    def init_artists(self, ax, plot_args, plot_kwargs):
        if 'c' in plot_kwargs:
            plot_kwargs['array'] = plot_kwargs.pop('c')
        if 'vmin' in plot_kwargs and 'vmax' in plot_kwargs:
            plot_kwargs['clim'] = plot_kwargs.pop('vmin'), plot_kwargs.pop(
                'vmax')
        if not 'array' in plot_kwargs and 'cmap' in plot_kwargs:
            del plot_kwargs['cmap']
        line_segments = LineCollection(*plot_args, **plot_kwargs)
        ax.add_collection(line_segments)
        return {'artist': line_segments}

    def get_data(self, element, ranges, style):
        dimensions = element.dimensions(label=True)
        ndims = len(dimensions)
        opts = self.lookup_options(element, 'plot').options

        pos = self.position
        if ndims > 1 and 'spike_length' not in opts:
            data = element.columns([0, 1])
            xs, ys = data[dimensions[0]], data[dimensions[1]]
            data = [[(x, pos), (x, pos + y)] for x, y in zip(xs, ys)]
        else:
            xs = element.array([0])
            height = self.spike_length
            data = [[(x[0], pos), (x[0], pos + height)] for x in xs]

        if self.invert_axes:
            data = [(line[0][::-1], line[1][::-1]) for line in data]

        dims = element.dimensions()
        clean_spikes = []
        for spike in data:
            xs, ys = zip(*spike)
            cols = []
            for i, vs in enumerate((xs, ys)):
                vs = np.array(vs)
                if isdatetime(vs):
                    dt_format = Dimension.type_formatters.get(
                        type(vs[0]), Dimension.type_formatters[np.datetime64])
                    vs = date2num(vs)
                    dims[i] = dims[i].clone(
                        value_format=DateFormatter(dt_format))
                cols.append(vs)
            clean_spikes.append(np.column_stack(cols))

        cdim = element.get_dimension(self.color_index)
        color = style.get('color', None)
        if cdim and ((isinstance(color, str) and color in element)
                     or isinstance(color, dim)):
            self.param.warning(
                "Cannot declare style mapping for 'color' option and "
                "declare a color_index; ignoring the color_index.")
            cdim = None
        if cdim:
            style['array'] = element.dimension_values(cdim)
            self._norm_kwargs(element, ranges, style, cdim)

        if 'spike_length' in opts:
            axis_dims = (element.dimensions()[0], None)
        elif len(element.dimensions()) == 1:
            axis_dims = (element.dimensions()[0], None)
        else:
            axis_dims = (element.dimensions()[0], element.dimensions()[1])
        with abbreviated_exception():
            style = self._apply_transforms(element, ranges, style)

        return (clean_spikes, ), style, {'dimensions': axis_dims}

    def update_handles(self, key, axis, element, ranges, style):
        artist = self.handles['artist']
        (data, ), kwargs, axis_kwargs = self.get_data(element, ranges, style)
        artist.set_paths(data)
        artist.set_visible(style.get('visible', True))
        if 'color' in kwargs:
            artist.set_edgecolors(kwargs['color'])
        if 'array' in kwargs or 'c' in kwargs:
            artist.set_array(kwargs.get('array', kwargs.get('c')))
        if 'vmin' in kwargs:
            artist.set_clim((kwargs['vmin'], kwargs['vmax']))
        if 'norm' in kwargs:
            artist.norm = kwargs['norm']
        if 'linewidth' in kwargs:
            artist.set_linewidths(kwargs['linewidth'])
        return axis_kwargs
Exemplo n.º 19
0
class Vega(PaneBase):
    """
    Vega panes allow rendering Vega plots and traces.

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

    debounce = param.ClassSelector(default=20,
                                   class_=(int, dict),
                                   doc="""
        Declares the debounce time in milliseconds either for all
        events or if a dictionary is provided for individual events.""")

    margin = param.Parameter(default=(5, 5, 30, 5),
                             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).""")

    selection = param.ClassSelector(class_=Selection,
                                    doc="""
        The Selection object reflects any selections available on the
        supplied vega plot into Python.""")

    show_actions = param.Boolean(default=False,
                                 doc="""
        Whether to show Vega actions.""")

    theme = param.ObjectSelector(default=None,
                                 allow_None=True,
                                 objects=[
                                     'excel', 'ggplot2', 'quartz', 'vox',
                                     'fivethirtyeight', 'dark', 'latimes',
                                     'urbaninstitute', 'googlecharts'
                                 ])

    priority = 0.8

    _rename = {'selection': None, 'debounce': None}

    _updates = True

    def __init__(self, object=None, **params):
        super().__init__(object, **params)
        self.param.watch(self._update_selections, ['object'])
        self._update_selections()

    @property
    def _selections(self):
        return _get_selections(self.object)

    @property
    def _throttle(self):
        default = self.param.debounce.default
        if isinstance(self.debounce, dict):
            throttle = {
                sel: self.debounce.get(sel, default)
                for sel in self._selections
            }
        else:
            throttle = {
                sel: self.debounce or default
                for sel in self._selections
            }
        return throttle

    def _update_selections(self, *args):
        self.selection = Selection()
        for e in self._selections:
            self.selection.param._add_parameter(e, param.Dict())

    @classmethod
    def is_altair(cls, obj):
        if 'altair' in sys.modules:
            import altair as alt
            return isinstance(obj, alt.api.TopLevelMixin)
        return False

    @classmethod
    def applies(cls, obj):
        if isinstance(obj, dict) and 'vega' in obj.get('$schema', '').lower():
            return True
        return cls.is_altair(obj)

    @classmethod
    def _to_json(cls, obj):
        if isinstance(obj, dict):
            json = dict(obj)
            if 'data' in json:
                data = json['data']
                if isinstance(data, dict):
                    json['data'] = dict(data)
                elif isinstance(data, list):
                    json['data'] = [dict(d) for d in data]
            return json
        return obj.to_dict()

    def _get_sources(self, json, sources):
        datasets = json.get('datasets', {})
        for name in list(datasets):
            if name in sources or isinstance(datasets[name], dict):
                continue
            data = datasets.pop(name)
            if isinstance(data, list) and any(
                    isinstance(d, dict) and 'geometry' in d for d in data):
                # Handle geometry records types
                datasets[name] = data
                continue
            columns = set(data[0]) if data else []
            if self.is_altair(self.object):
                import altair as alt
                if (not isinstance(
                        self.object.data,
                    (alt.Data, alt.UrlData, type(alt.Undefined)))
                        and columns == set(self.object.data)):
                    data = ColumnDataSource.from_df(self.object.data)
                else:
                    data = ds_as_cds(data)
                sources[name] = ColumnDataSource(data=data)
            else:
                sources[name] = ColumnDataSource(data=ds_as_cds(data))
        data = json.get('data', {})
        if isinstance(data, dict):
            data = data.pop('values', {})
            if data:
                sources['data'] = ColumnDataSource(data=ds_as_cds(data))
        elif isinstance(data, list):
            for d in data:
                if 'values' in d:
                    sources[d['name']] = ColumnDataSource(
                        data=ds_as_cds(d.pop('values')))

    @classmethod
    def _get_dimensions(cls, json, props):
        if json is None:
            return

        if 'config' in json and 'view' in json['config']:
            size_config = json['config']['view']
        else:
            size_config = json

        view = {}
        for w in ('width', 'continuousWidth'):
            if w in size_config:
                view['width'] = size_config[w]
        for h in ('height', 'continuousHeight'):
            if h in size_config:
                view['height'] = size_config[h]

        for p in ('width', 'height'):
            if p not in view or isinstance(view[p], str):
                continue
            if props.get(p) is None or p in view and props.get(p) < view[p]:
                v = view[p]
                props[p] = v + 22 if isinstance(v, int) else v

        responsive_height = json.get('height') == 'container'
        responsive_width = json.get('width') == 'container'
        if responsive_height and responsive_width:
            props['sizing_mode'] = 'stretch_both'
        elif responsive_width:
            props['sizing_mode'] = 'stretch_width'
        elif responsive_height:
            props['sizing_mode'] = 'stretch_height'

    def _process_event(self, event):
        self.selection.param.update(
            **{event.data['type']: event.data['value']})

    def _get_model(self, doc, root=None, parent=None, comm=None):
        VegaPlot = lazy_load('panel.models.vega', 'VegaPlot',
                             isinstance(comm, JupyterComm), root)
        sources = {}
        if self.object is None:
            json = None
        else:
            json = self._to_json(self.object)
            self._get_sources(json, sources)
        props = self._process_param_change(self._init_params())
        self._get_dimensions(json, props)
        model = VegaPlot(data=json,
                         data_sources=sources,
                         events=self._selections,
                         throttle=self._throttle,
                         **props)
        if comm:
            model.on_event('vega_event', self._comm_event)
        else:
            model.on_event('vega_event', partial(self._server_event, doc))
        if root is None:
            root = model
        self._models[root.ref['id']] = (model, parent)
        return model

    def _update(self, ref=None, model=None):
        if self.object is None:
            json = None
        else:
            json = self._to_json(self.object)
            self._get_sources(json, model.data_sources)
        props = {
            p: getattr(self, p)
            for p in list(Layoutable.param) if getattr(self, p) is not None
        }
        props['throttle'] = self._throttle
        props['events'] = self._selections
        self._get_dimensions(json, props)
        props['data'] = json
        model.update(**props)
Exemplo n.º 20
0
class SideHistogramPlot(AdjoinedPlot, HistogramPlot):

    bgcolor = param.Parameter(default=(1, 1, 1, 0),
                              doc="""
        Make plot background invisible.""")

    offset = param.Number(default=0.2,
                          bounds=(0, 1),
                          doc="""
        Histogram value offset for a colorbar.""")

    show_grid = param.Boolean(default=False,
                              doc="""
        Whether to overlay a grid on the axis.""")

    def _process_hist(self, hist):
        """
        Subclassed to offset histogram by defined amount.
        """
        edges, hvals, widths, lims, isdatetime = super()._process_hist(hist)
        offset = self.offset * lims[3]
        hvals = hvals * (1 - self.offset)
        hvals += offset
        lims = lims[0:3] + (lims[3] + offset, )
        return edges, hvals, widths, lims, isdatetime

    def _update_artists(self, n, element, edges, hvals, widths, lims, ranges):
        super()._update_artists(n, element, edges, hvals, widths, lims, ranges)
        self._update_plot(n, element, self.handles['artist'], lims, ranges)

    def _update_plot(self, key, element, bars, lims, ranges):
        """
        Process the bars and draw the offset line as necessary. If a
        color map is set in the style of the 'main' ViewableElement object, color
        the bars appropriately, respecting the required normalization
        settings.
        """
        main = self.adjoined.main
        _, y1 = element.range(1)
        offset = self.offset * y1
        range_item, main_range, dim = get_sideplot_ranges(
            self, element, main, ranges)

        # Check if plot is colormapped
        plot_type = Store.registry['matplotlib'].get(type(range_item))
        if isinstance(plot_type, PlotSelector):
            plot_type = plot_type.get_plot_class(range_item)
        opts = self.lookup_options(range_item, 'plot')
        if plot_type and issubclass(plot_type, ColorbarPlot):
            cidx = opts.options.get('color_index', None)
            if cidx is None:
                opts = self.lookup_options(range_item, 'style')
                cidx = opts.kwargs.get('color', None)
                if cidx not in range_item:
                    cidx = None
            cdim = None if cidx is None else range_item.get_dimension(cidx)
        else:
            cdim = None

        # Get colormapping options
        if isinstance(range_item,
                      (HeatMap, Raster)) or (cdim and cdim in element):
            style = self.lookup_options(range_item, 'style')[self.cyclic_index]
            cmap = cm.get_cmap(style.get('cmap'))
            main_range = style.get('clims', main_range)
        else:
            cmap = None

        if offset and ('offset_line' not in self.handles):
            self.handles['offset_line'] = self.offset_linefn(offset,
                                                             linewidth=1.0,
                                                             color='k')
        elif offset:
            self._update_separator(offset)

        if cmap is not None and main_range and (None not in main_range):
            self._colorize_bars(cmap, bars, element, main_range, dim)
        return bars

    def _colorize_bars(self, cmap, bars, element, main_range, dim):
        """
        Use the given cmap to color the bars, applying the correct
        color ranges as necessary.
        """
        cmap_range = main_range[1] - main_range[0]
        lower_bound = main_range[0]
        colors = np.array(element.dimension_values(dim))
        colors = (colors - lower_bound) / (cmap_range)
        for c, bar in zip(colors, bars):
            bar.set_facecolor(cmap(c))
            bar.set_clip_on(False)

    def _update_separator(self, offset):
        """
        Compute colorbar offset and update separator line
        if map is non-zero.
        """
        offset_line = self.handles['offset_line']
        if offset == 0:
            offset_line.set_visible(False)
        else:
            offset_line.set_visible(True)
            if self.invert_axes:
                offset_line.set_xdata(offset)
            else:
                offset_line.set_ydata(offset)
Exemplo n.º 21
0
class PaneBase(Reactive):
    """
    PaneBase is the abstract baseclass for all atomic displayable units
    in the panel library. Pane defines an extensible interface for
    wrapping arbitrary objects and transforming them into Bokeh models.

    Panes are reactive in the sense that when the object they are
    wrapping is changed any dashboard containing the pane will update
    in response.

    To define a concrete Pane type subclass this class and implement
    the applies classmethod and the _get_model private method.
    """

    default_layout = param.ClassSelector(default=Row,
                                         class_=(Panel),
                                         is_instance=False,
                                         doc="""
        Defines the layout the model(s) returned by the pane will
        be placed in.""")

    object = param.Parameter(default=None,
                             doc="""
        The object being wrapped, which will be converted to a
        Bokeh model.""")

    # When multiple Panes apply to an object, the one with the highest
    # numerical priority is selected. The default is an intermediate value.
    # If set to None, applies method will be called to get a priority
    # value for a specific object type.
    priority = 0.5

    # Whether applies requires full set of keywords
    _applies_kw = False

    # Whether the Pane layout can be safely unpacked
    _unpack = True

    # Declares whether Pane supports updates to the Bokeh model
    _updates = False

    # List of parameters that trigger a rerender of the Bokeh model
    _rerender_params = ['object']

    __abstract = True

    def __init__(self, object=None, **params):
        applies = self.applies(object, **(params if self._applies_kw else {}))
        if (isinstance(applies, bool) and not applies) and object is not None:
            self._type_error(object)

        super(PaneBase, self).__init__(object=object, **params)
        kwargs = {k: v for k, v in params.items() if k in Layoutable.param}
        self.layout = self.default_layout(self, **kwargs)
        watcher = self.param.watch(self._update_pane, self._rerender_params)
        self._callbacks.append(watcher)

    def _type_error(self, object):
        raise ValueError("%s pane does not support objects of type '%s'." %
                         (type(self).__name__, type(object).__name__))

    def __repr__(self, depth=0):
        cls = type(self).__name__
        params = param_reprs(self, ['object'])
        obj = 'None' if self.object is None else type(self.object).__name__
        template = '{cls}({obj}, {params})' if params else '{cls}({obj})'
        return template.format(cls=cls, params=', '.join(params), obj=obj)

    def __getitem__(self, index):
        """
        Allows pane objects to behave like the underlying layout
        """
        return self.layout[index]

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

    @property
    def _linkable_params(self):
        return [
            p for p in self._synced_params()
            if self._rename.get(p, False) is not None
        ]

    def _synced_params(self):
        ignored_params = ['name', 'default_layout'] + self._rerender_params
        return [p for p in self.param if p not in ignored_params]

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

    def _update_object(self, ref, doc, root, parent, comm):
        old_model = self._models[ref][0]
        if self._updates:
            self._update(ref, old_model)
        else:
            new_model = self._get_model(doc, root, parent, comm)
            try:
                if isinstance(parent, _BkGridBox):
                    indexes = [
                        i for i, child in enumerate(parent.children)
                        if child[0] is old_model
                    ]
                    if indexes:
                        index = indexes[0]
                    else:
                        raise ValueError
                    new_model = (new_model, ) + parent.children[index][1:]
                else:
                    index = parent.children.index(old_model)
            except ValueError:
                self.warning('%s pane model %s could not be replaced '
                             'with new model %s, ensure that the '
                             'parent is not modified at the same '
                             'time the panel is being updated.' %
                             (type(self).__name__, old_model, new_model))
            else:
                parent.children[index] = new_model

        from ..io import state
        ref = root.ref['id']
        if ref in state._views:
            state._views[ref][0]._preprocess(root)

    def _update_pane(self, *events):
        for ref, (_, parent) in self._models.items():
            if ref not in state._views or ref in state._fake_roots:
                continue
            viewable, root, doc, comm = state._views[ref]
            if comm or state._unblocked(doc):
                with unlocked():
                    self._update_object(ref, doc, root, parent, comm)
                if comm and 'embedded' not in root.tags:
                    push(doc, comm)
            else:
                cb = partial(self._update_object, ref, doc, root, parent, comm)
                if doc.session_context:
                    doc.add_next_tick_callback(cb)
                else:
                    cb()

    def _update(self, ref=None, model=None):
        """
        If _updates=True this method is used to update an existing
        Bokeh model instead of replacing the model entirely. The
        supplied model should be updated with the current state.
        """
        raise NotImplementedError

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

    @classmethod
    def applies(cls, obj):
        """
        Given the object return a boolean indicating whether the Pane
        can render the object. If the priority of the pane is set to
        None, this method may also be used to define a priority
        depending on the object being rendered.
        """
        return None

    def clone(self, object=None, **params):
        """
        Makes a copy of the Pane sharing the same parameters.

        Arguments
        ---------
        params: Keyword arguments override the parameters on the clone.

        Returns
        -------
        Cloned Pane object
        """
        params = dict(self.param.get_param_values(), **params)
        old_object = params.pop('object')
        if object is None:
            object = old_object
        return type(self)(object, **params)

    def get_root(self, doc=None, comm=None):
        """
        Returns the root model and applies pre-processing hooks

        Arguments
        ---------
        doc: bokeh.Document
          Bokeh document the bokeh model will be attached to.
        comm: pyviz_comms.Comm
          Optional pyviz_comms when working in notebook

        Returns
        -------
        Returns the bokeh model corresponding to this panel object
        """
        doc = doc or _curdoc()
        if self._updates:
            root = self._get_model(doc, comm=comm)
        else:
            root = self.layout._get_model(doc, comm=comm)
        self._preprocess(root)
        ref = root.ref['id']
        state._views[ref] = (self, root, doc, comm)
        return root

    @classmethod
    def get_pane_type(cls, obj, **kwargs):
        """
        Returns the applicable Pane type given an object by resolving
        the precedence of all types whose applies method declares that
        the object is supported.

        Arguments
        ---------
        obj (object): The object type to return a Pane for

        Returns
        -------
        The applicable Pane type with the highest precedence.
        """
        if isinstance(obj, Viewable):
            return type(obj)
        descendents = []
        for p in param.concrete_descendents(PaneBase).values():
            if p.priority is None:
                applies = True
                try:
                    priority = p.applies(obj,
                                         **(kwargs if p._applies_kw else {}))
                except Exception:
                    priority = False
            else:
                applies = None
                priority = p.priority
            if isinstance(priority, bool) and priority:
                raise ValueError('If a Pane declares no priority '
                                 'the applies method should return a '
                                 'priority value specific to the '
                                 'object type or False, but the %s pane '
                                 'declares no priority.' % p.__name__)
            elif priority is None or priority is False:
                continue
            descendents.append((priority, applies, p))
        pane_types = reversed(sorted(descendents, key=lambda x: x[0]))
        for _, applies, pane_type in pane_types:
            if applies is None:
                try:
                    applies = pane_type.applies(
                        obj, **(kwargs if pane_type._applies_kw else {}))
                except Exception:
                    applies = False
            if not applies:
                continue
            return pane_type
        raise TypeError('%s type could not be rendered.' % type(obj).__name__)
Exemplo n.º 22
0
class PatternGenerator(param.Parameterized):
    """
    A class hierarchy for callable objects that can generate 2D patterns.

    Once initialized, PatternGenerators can be called to generate a
    value or a matrix of values from a 2D function, typically
    accepting at least x and y.
    
    A PatternGenerator's Parameters can make use of Parameter's
    precedence attribute to specify the order in which they should
    appear, e.g. in a GUI. The precedence attribute has a nominal
    range of 0.0 to 1.0, with ordering going from 0.0 (first) to 1.0
    (last), but any value is allowed.

    The orientation and layout of the pattern matrices is defined by
    the SheetCoordinateSystem class, which see.

    Note that not every parameter defined for a PatternGenerator will
    be used by every subclass.  For instance, a Constant pattern will
    ignore the x, y, orientation, and size parameters, because the
    pattern does not vary with any of those parameters.  However,
    those parameters are still defined for all PatternGenerators, even
    Constant patterns, to allow PatternGenerators to be scaled, rotated,
    translated, etc. uniformly.
    """
    __abstract = True

    bounds = BoundingRegionParameter(
        default=BoundingBox(points=((-0.5, -0.5), (0.5, 0.5))),
        precedence=-1,
        doc="BoundingBox of the area in which the pattern is generated.")

    xdensity = param.Number(default=10,
                            bounds=(0, None),
                            precedence=-1,
                            doc="""
        Density (number of samples per 1.0 length) in the x direction.""")

    ydensity = param.Number(default=10,
                            bounds=(0, None),
                            precedence=-1,
                            doc="""
        Density (number of samples per 1.0 length) in the y direction.
        Typically the same as the xdensity.""")

    x = param.Number(default=0.0,
                     softbounds=(-1.0, 1.0),
                     precedence=0.20,
                     doc="""
        X-coordinate location of pattern center.""")

    y = param.Number(default=0.0,
                     softbounds=(-1.0, 1.0),
                     precedence=0.21,
                     doc="""
        Y-coordinate location of pattern center.""")

    position = param.Composite(attribs=['x', 'y'],
                               precedence=-1,
                               doc="""
        Coordinates of location of pattern center.
        Provides a convenient way to set the x and y parameters together
        as a tuple (x,y), but shares the same actual storage as x and y
        (and thus only position OR x and y need to be specified).""")

    orientation = param.Number(default=0.0,
                               softbounds=(0.0, 2 * pi),
                               precedence=0.40,
                               doc="""
        Polar angle of pattern, i.e., the orientation in the Cartesian coordinate
        system, with zero at 3 o'clock and increasing counterclockwise.""")

    size = param.Number(default=1.0,
                        bounds=(0.0, None),
                        softbounds=(0.0, 6.0),
                        precedence=0.30,
                        doc="""Determines the overall size of the pattern.""")

    scale = param.Number(default=1.0,
                         softbounds=(0.0, 2.0),
                         precedence=0.10,
                         doc="""
        Multiplicative strength of input pattern, defaulting to 1.0""")

    offset = param.Number(default=0.0,
                          softbounds=(-1.0, 1.0),
                          precedence=0.11,
                          doc="""
        Additive offset to input pattern, defaulting to 0.0""")

    mask = param.Parameter(default=None,
                           precedence=-1,
                           doc="""
        Optional object (expected to be an array) with which to multiply the
        pattern array after it has been created, before any output_fns are
        applied. This can be used to shape the pattern.""")

    # Note that the class type is overridden to PatternGenerator below
    mask_shape = param.ClassSelector(param.Parameterized,
                                     default=None,
                                     precedence=0.06,
                                     doc="""
        Optional PatternGenerator used to construct a mask to be applied to
        the pattern.""")

    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.""")

    def __init__(self, **params):
        super(PatternGenerator, self).__init__(**params)
        self.set_matrix_dimensions(self.bounds, self.xdensity, self.ydensity)

    def __call__(self, **params_to_override):
        """
        Call the subclass's 'function' method on a rotated and scaled coordinate system.

        Creates and fills an array with the requested pattern.  If
        called without any params, uses the values for the Parameters
        as currently set on the object. Otherwise, any params
        specified override those currently set on the object.
        """
        p = ParamOverrides(self, params_to_override)

        # CEBERRORALERT: position parameter is not currently
        # supported. We should delete the position parameter or fix
        # this.
        #
        # position=params_to_override.get('position',None) if position
        # is not None: x,y = position

        self._setup_xy(p.bounds, p.xdensity, p.ydensity, p.x, p.y,
                       p.orientation)
        fn_result = self.function(p)
        self._apply_mask(p, fn_result)
        result = p.scale * fn_result + p.offset

        for of in p.output_fns:
            of(result)

        return result

    def _setup_xy(self, bounds, xdensity, ydensity, x, y, orientation):
        """
        Produce pattern coordinate matrices from the bounds and
        density (or rows and cols), and transforms them according to
        x, y, and orientation.
        """
        self.debug(
            lambda:
            "bounds=%s, xdensity=%s, ydensity=%s, x=%s, y=%s, orientation=%s" %
            (bounds, xdensity, ydensity, x, y, orientation))
        # Generate vectors representing coordinates at which the pattern
        # will be sampled.

        # CB: note to myself - use slice_._scs if supplied?
        x_points, y_points = SheetCoordinateSystem(
            bounds, xdensity, ydensity).sheetcoordinates_of_matrixidx()

        # Generate matrices of x and y sheet coordinates at which to
        # sample pattern, at the correct orientation
        self.pattern_x, self.pattern_y = self._create_and_rotate_coordinate_arrays(
            x_points - x, y_points - y, orientation)

    def function(self, p):
        """
        Function to draw a pattern that will then be scaled and rotated.

        Instead of implementing __call__ directly, PatternGenerator
        subclasses will typically implement this helper function used
        by __call__, because that way they can let __call__ handle the
        scaling and rotation for them.  Alternatively, __call__ itself
        can be reimplemented entirely by a subclass (e.g. if it does
        not need to do any scaling or rotation), in which case this
        function will be ignored.
        """
        raise NotImplementedError

    def _create_and_rotate_coordinate_arrays(self, x, y, orientation):
        """
        Create pattern matrices from x and y vectors, and rotate
        them to the specified orientation.
        """
        # Using this two-liner requires that x increase from left to
        # right and y decrease from left to right; I don't think it
        # can be rewritten in so little code otherwise - but please
        # prove me wrong.
        pattern_y = subtract.outer(cos(orientation) * y, sin(orientation) * x)
        pattern_x = add.outer(sin(orientation) * y, cos(orientation) * x)
        return pattern_x, pattern_y

    def _apply_mask(self, p, mat):
        """Create (if necessary) and apply the mask to the given matrix mat."""
        mask = p.mask
        ms = p.mask_shape
        if ms is not None:
            mask = ms(x=p.x + p.size *
                      (ms.x * cos(p.orientation) - ms.y * sin(p.orientation)),
                      y=p.y + p.size *
                      (ms.x * sin(p.orientation) + ms.y * cos(p.orientation)),
                      orientation=ms.orientation + p.orientation,
                      size=ms.size * p.size,
                      bounds=p.bounds,
                      ydensity=p.ydensity,
                      xdensity=p.xdensity)
        if mask is not None:
            mat *= mask

    def set_matrix_dimensions(self, bounds, xdensity, ydensity):
        """
        Change the dimensions of the matrix into which the pattern will be drawn.
        Users of this class should call this method rather than changing
        the bounds, xdensity, and ydensity parameters directly.  Subclasses
        can override this method to update any internal data structures that
        may depend on the matrix dimensions.
        """
        self.bounds = bounds
        self.xdensity = xdensity
        self.ydensity = ydensity
Exemplo n.º 23
0
class Card(Column):
    """
    A Card layout allows arranging multiple panel objects in a
    collapsible, vertical container with a header bar.
    """

    active_header_background = param.String(doc="""
        A valid CSS color for the header background when not collapsed.""")

    button_css_classes = param.List(['card-button'],
                                    doc="""
        CSS classes to apply to the button element.""")

    collapsible = param.Boolean(default=True,
                                doc="""
        Whether the Card should be expandable and collapsible.""")

    collapsed = param.Boolean(default=False,
                              doc="""
        Whether the contents of the Card are collapsed.""")

    css_classes = param.List(['card'],
                             doc="""
        CSS classes to apply to the overall Card.""")

    header = param.Parameter(doc="""
        A Panel component to display in the header bar of the Card.
        Will override the given title if defined.""")

    header_background = param.String(doc="""
        A valid CSS color for the header background.""")

    header_color = param.String(doc="""
        A valid CSS color to apply to the header text.""")

    header_css_classes = param.List(['card-header'],
                                    doc="""
        CSS classes to apply to the header element.""")

    title_css_classes = param.List(['card-title'],
                                   doc="""
        CSS classes to apply to the header title.""")

    margin = param.Parameter(default=5)

    title = param.String(doc="""
        A title to be displayed in the Card header, will be overridden
        by the header if defined.""")

    _bokeh_model = BkCard

    _rename = dict(Column._rename,
                   title=None,
                   header=None,
                   title_css_classes=None)

    def __init__(self, *objects, **params):
        self._header_layout = Row(css_classes=['card-header-row'],
                                  sizing_mode='stretch_width')
        super(Card, self).__init__(*objects, **params)
        self.param.watch(self._update_header,
                         ['title', 'header', 'title_css_classes'])
        self._update_header()

    def _cleanup(self, root):
        super(Card, self)._cleanup(root)
        self._header_layout._cleanup(root)

    def _process_param_change(self, params):
        scroll = params.pop('scroll', None)
        css_classes = self.css_classes or []
        if scroll:
            params['css_classes'] = css_classes + ['scrollable']
        elif scroll == False:
            params['css_classes'] = css_classes
        return super(ListPanel, self)._process_param_change(params)

    def _update_header(self, *events):
        from ..pane import HTML, panel
        if self.header is None:
            item = HTML('%s' % (self.title or "&#8203;"),
                        css_classes=self.title_css_classes,
                        sizing_mode='stretch_width',
                        margin=(2, 5))
        else:
            item = panel(self.header)
        self._header_layout[:] = [item]

    def _get_objects(self, model, old_objects, doc, root, comm=None):
        ref = root.ref['id']
        if ref in self._header_layout._models:
            header = self._header_layout._models[ref][0]
        else:
            header = self._header_layout._get_model(doc, root, model, comm)
        objects = super(Card, self)._get_objects(model, old_objects, doc, root)
        return [header] + objects
Exemplo n.º 24
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.""")

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

    labelled = param.List(default=['x', 'y'], doc="""
        Whether to plot the 'x' and 'y' labels.""")

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

    xformatter = param.ClassSelector(
        default=None, class_=(util.basestring, ticker.Formatter, FunctionType), doc="""
        Formatter for ticks along the x-axis.""")

    yformatter = param.ClassSelector(
        default=None, class_=(util.basestring, ticker.Formatter, FunctionType), doc="""
        Formatter for ticks along the y-axis.""")

    zformatter = param.ClassSelector(
        default=None, class_=(util.basestring, ticker.Formatter, FunctionType), doc="""
        Formatter for ticks along the z-axis.""")

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

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

    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 = []

    # Whether plot has axes, disables setting axis limits, labels and ticks
    _has_axes = True

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

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


    def _finalize_axis(self, key, element=None, title=None, dimensions=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.
        """
        if element is None:
            element = self._get_frame(key)
        self.current_frame = element
        if not dimensions and element and not self.subplots:
            el = element.traverse(lambda x: x, [Element])
            if el: dimensions = el[0].dimensions()
        axis = self.handles['axis']

        subplots = list(self.subplots.values()) if self.subplots else []
        if self.zorder == 0 and key is not None:
            if self.bgcolor:
                if LooseVersion(mpl.__version__) <= '1.5.9':
                    axis.set_axis_bgcolor(self.bgcolor)
                else:
                    axis.set_facecolor(self.bgcolor)

            # Apply title
            title = self._format_title(key)
            if self.show_title and title is not None:
                fontsize = self._fontsize('title')
                if 'title' in self.handles:
                    self.handles['title'].set_text(title)
                else:
                    self.handles['title'] = axis.set_title(title, **fontsize)

            # Apply subplot label
            self._subplot_label(axis)

            # Apply axis options if axes are enabled
            if element is not None and not any(not sp._has_axes for sp in [self] + subplots):
                # Set axis labels
                if dimensions:
                    self._set_labels(axis, dimensions, xlabel, ylabel, zlabel)

                if not subplots:
                    legend = axis.get_legend()
                    if legend:
                        legend.set_visible(self.show_legend)
                        self.handles["bbox_extra_artists"] += [legend]
                    axis.xaxis.grid(self.show_grid)
                    axis.yaxis.grid(self.show_grid)

                # Apply log axes
                if self.logx:
                    axis.set_xscale('log')
                if self.logy:
                    axis.set_yscale('log')

                if not self.projection == '3d':
                    self._set_axis_position(axis, 'x', self.xaxis)
                    self._set_axis_position(axis, 'y', self.yaxis)

                # Apply ticks
                if self.apply_ticks:
                    self._finalize_ticks(axis, dimensions, xticks, yticks, zticks)

                # Set axes limits
                self._set_axis_limits(axis, element, subplots, ranges)

            # Apply aspects
            if self.aspect is not None and self.projection != 'polar' and not self.adjoined:
                self._set_aspect(axis, self.aspect)

        if not subplots and not self.drawn:
            self._finalize_artist(element)

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


    def _finalize_ticks(self, axis, dimensions, xticks, yticks, zticks):
        """
        Finalizes the ticks on the axes based on the supplied ticks
        and Elements. Sets the axes position as well as tick positions,
        labels and fontsize.
        """
        ndims = len(dimensions) if dimensions else 0
        xdim = dimensions[0] if ndims else None
        ydim = dimensions[1] if ndims > 1 else None

        # Tick formatting
        if xdim:
            self._set_axis_formatter(axis.xaxis, xdim, self.xformatter)
        if ydim:
            self._set_axis_formatter(axis.yaxis, ydim, self.yformatter)
        if self.projection == '3d':
            zdim = dimensions[2] if ndims > 2 else None
            if zdim or self.zformatter is not None:
                self._set_axis_formatter(axis.zaxis, zdim, self.zformatter)

        xticks = xticks if xticks else self.xticks
        self._set_axis_ticks(axis.xaxis, xticks, log=self.logx,
                             rotation=self.xrotation)

        yticks = yticks if yticks else self.yticks
        self._set_axis_ticks(axis.yaxis, yticks, log=self.logy,
                             rotation=self.yrotation)

        if self.projection == '3d':
            zticks = zticks if zticks else self.zticks
            self._set_axis_ticks(axis.zaxis, zticks, log=self.logz,
                                 rotation=self.zrotation)

        axes_str = 'xy'
        axes_list = [axis.xaxis, axis.yaxis]

        if hasattr(axis, 'zaxis'):
            axes_str += 'z'
            axes_list.append(axis.zaxis)

        for ax, ax_obj in zip(axes_str, axes_list):
            tick_fontsize = self._fontsize('%sticks' % ax,'labelsize',common=False)
            if tick_fontsize: ax_obj.set_tick_params(**tick_fontsize)


    def _finalize_artist(self, element):
        """
        Allows extending the _finalize_axis method with Element
        specific options.
        """
        pass


    def _set_labels(self, axes, dimensions, xlabel=None, ylabel=None, zlabel=None):
        """
        Sets the labels of the axes using the supplied list of dimensions.
        Optionally explicit labels may be supplied to override the dimension
        label.
        """
        xlabel, ylabel, zlabel = self._get_axis_labels(dimensions, xlabel, ylabel, zlabel)
        if self.invert_axes:
            xlabel, ylabel = ylabel, xlabel
        if xlabel and self.xaxis and 'x' in self.labelled:
            axes.set_xlabel(xlabel, **self._fontsize('xlabel'))
        if ylabel and self.yaxis and 'y' in self.labelled:
            axes.set_ylabel(ylabel, **self._fontsize('ylabel'))
        if zlabel and self.zaxis and 'z' in self.labelled:
            axes.set_zlabel(zlabel, **self._fontsize('zlabel'))


    def _set_axis_formatter(self, axis, dim, formatter):
        """
        Set axis formatter based on dimension formatter.
        """
        if isinstance(dim, list): dim = dim[0]
        if formatter is not None:
            pass
        elif dim.value_format:
            formatter = dim.value_format
        elif dim.type in dim.type_formatters:
            formatter = dim.type_formatters[dim.type]
        if formatter:
            axis.set_major_formatter(wrap_formatter(formatter))


    def get_aspect(self, xspan, yspan):
        """
        Computes the aspect ratio of the plot
        """
        if isinstance(self.aspect, (int, float)):
            return self.aspect
        elif self.aspect == 'square':
            return 1
        elif self.aspect == 'equal':
            return xspan/yspan
        return 1


    def _set_aspect(self, axes, aspect):
        """
        Set the aspect on the axes based on the aspect setting.
        """
        if isinstance(aspect, util.basestring) and aspect != 'square':
            axes.set_aspect(aspect)
            return

        (x0, x1), (y0, y1) = axes.get_xlim(), axes.get_ylim()
        xsize = np.log(x1) - np.log(x0) if self.logx else x1-x0
        ysize = np.log(y1) - np.log(y0) if self.logy else y1-y0
        xsize = max(abs(xsize), 1e-30)
        ysize = max(abs(ysize), 1e-30)
        data_ratio = 1./(ysize/xsize)
        if aspect != 'square':
            data_ratio = data_ratio/aspect
        axes.set_aspect(float(data_ratio))


    def _set_axis_limits(self, axis, view, subplots, ranges):
        """
        Compute extents for current view and apply as axis limits
        """
        # Extents
        scalex, scaley = True, True
        extents = self.get_extents(view, ranges)
        if extents and not self.overlaid:
            coords = [coord if np.isreal(coord) or isinstance(coord, np.datetime64) else np.NaN for coord in extents]
            coords = [date2num(util.dt64_to_dt(c)) if isinstance(c, np.datetime64) else c
                      for c in coords]
            valid_lim = lambda c: util.isnumeric(c) and not np.isnan(c)
            if self.projection == '3d' or len(extents) == 6:
                l, b, zmin, r, t, zmax = coords
                if self.invert_zaxis or any(p.invert_zaxis for p in subplots):
                    zmin, zmax = zmax, zmin
                if zmin != zmax:
                    if valid_lim(zmin):
                        axis.set_zlim(bottom=zmin)
                    if valid_lim(zmax):
                        axis.set_zlim(top=zmax)
            else:
                l, b, r, t = coords

            if self.invert_axes:
                l, b, r, t = b, l, t, r

            if self.invert_xaxis or any(p.invert_xaxis for p in subplots):
                r, l = l, r
            if l != r:
                lims = {}
                if valid_lim(l):
                    lims['left'] = l
                    scalex = False
                if valid_lim(r):
                    lims['right'] = r
                    scalex = False
                if lims:
                    axis.set_xlim(**lims)
            if self.invert_yaxis or any(p.invert_yaxis for p in subplots):
                t, b = b, t
            if b != t:
                lims = {}
                if valid_lim(b):
                    lims['bottom'] = b
                    scaley = False
                if valid_lim(t):
                    lims['top'] = t
                    scaley = False
                if lims:
                    axis.set_ylim(**lims)
        axis.autoscale_view(scalex=scalex, scaley=scaley)


    def _set_axis_position(self, axes, axis, option):
        """
        Set the position and visibility of the xaxis or yaxis by
        supplying the axes object, the axis to set, i.e. 'x' or 'y'
        and an option to specify the position and visibility of the axis.
        The option may be None, 'bare' or positional, i.e. 'left' and
        'right' for the yaxis and 'top' and 'bottom' for the xaxis.
        May also combine positional and 'bare' into for example 'left-bare'.
        """
        positions = {'x': ['bottom', 'top'], 'y': ['left', 'right']}[axis]
        axis = axes.xaxis if axis == 'x' else axes.yaxis
        if option in [None, False]:
            axis.set_visible(False)
            for pos in positions:
                axes.spines[pos].set_visible(False)
        else:
            if option is True:
                option = positions[0]
            if 'bare' in option:
                axis.set_ticklabels([])
                axis.set_label_text('')
            if option != 'bare':
                option = option.split('-')[0]
                axis.set_ticks_position(option)
                axis.set_label_position(option)
        if not self.overlaid and not self.show_frame and self.projection != 'polar':
            pos = (positions[1] if (option and (option == 'bare' or positions[0] in option))
                   else positions[0])
            axes.spines[pos].set_visible(False)


    def _set_axis_ticks(self, axis, ticks, log=False, rotation=0):
        """
        Allows setting the ticks for a particular axis either with
        a tuple of ticks, a tick locator object, an integer number
        of ticks, a list of tuples containing positions and labels
        or a list of positions. Also supports enabling log ticking
        if an integer number of ticks is supplied and setting a
        rotation for the ticks.
        """
        if isinstance(ticks, (list, tuple)) and all(isinstance(l, list) for l in ticks):
            axis.set_ticks(ticks[0])
            axis.set_ticklabels(ticks[1])
        elif isinstance(ticks, ticker.Locator):
            axis.set_major_locator(ticks)
        elif not ticks and ticks is not None:
            axis.set_ticks([])
        elif isinstance(ticks, int):
            if log:
                locator = ticker.LogLocator(numticks=ticks,
                                            subs=range(1,10))
            else:
                locator = ticker.MaxNLocator(ticks)
            axis.set_major_locator(locator)
        elif isinstance(ticks, (list, tuple)):
            labels = None
            if all(isinstance(t, tuple) for t in ticks):
                ticks, labels = zip(*ticks)
            axis.set_ticks(ticks)
            if labels:
                axis.set_ticklabels(labels)
        for tick in axis.get_ticklabels():
            tick.set_rotation(rotation)


    @mpl_rc_context
    def update_frame(self, key, ranges=None, element=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.
        """
        reused = isinstance(self.hmap, DynamicMap) and self.overlaid
        if not reused and element is None:
            element = self._get_frame(key)
        elif element is not None:
            self.current_key = key
            self.current_frame = element

        if element is not None:
            self.set_param(**self.lookup_options(element, 'plot').options)
        axis = self.handles['axis']

        axes_visible = element 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(element is not None)
        if element is None:
            return

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

        label = element.label if self.show_legend else ''
        style = dict(label=label, zorder=self.zorder, **self.style[self.cyclic_index])
        axis_kwargs = self.update_handles(key, axis, element, ranges, style)
        self._finalize_axis(key, element=element, ranges=ranges,
                            **(axis_kwargs if axis_kwargs else {}))


    @mpl_rc_context
    def initialize_plot(self, ranges=None):
        element = self.hmap.last
        ax = self.handles['axis']
        key = list(self.hmap.data.keys())[-1]
        dim_map = dict(zip((d.name for d in self.hmap.kdims), key))
        key = tuple(dim_map.get(d.name, None) for d in self.dimensions)
        ranges = self.compute_ranges(self.hmap, key, ranges)
        self.current_ranges = ranges
        self.current_frame = element
        self.current_key = key

        ranges = util.match_spec(element, ranges)

        style = dict(zorder=self.zorder, **self.style[self.cyclic_index])
        if self.show_legend:
            style['label'] = element.label

        plot_data, plot_kwargs, axis_kwargs = self.get_data(element, ranges, style)

        with abbreviated_exception():
            handles = self.init_artists(ax, plot_data, plot_kwargs)
        self.handles.update(handles)

        return self._finalize_axis(self.keys[-1], element=element, ranges=ranges,
                                   **axis_kwargs)


    def init_artists(self, ax, plot_args, plot_kwargs):
        """
        Initializes the artist based on the plot method declared on
        the plot.
        """
        plot_method = self._plot_methods.get('batched' if self.batched else 'single')
        plot_fn = getattr(ax, plot_method)
        artist = plot_fn(*plot_args, **plot_kwargs)
        return {'artist': artist[0] if isinstance(artist, list) and
                len(artist) == 1 else artist}


    def update_handles(self, key, axis, element, ranges, style):
        """
        Update the elements of the plot.
        """
        self.teardown_handles()
        plot_data, plot_kwargs, axis_kwargs = self.get_data(element, ranges, style)

        with abbreviated_exception():
            handles = self.init_artists(axis, plot_data, plot_kwargs)
        self.handles.update(handles)
        return axis_kwargs

    def teardown_handles(self):
        """
        If no custom update_handles method is supplied this method
        is called to tear down any previous handles before replacing
        them.
        """
        if 'artist' in self.handles:
            self.handles['artist'].remove()
Exemplo n.º 25
0
Arquivo: misc.py Projeto: yuttie/panel
class FileDownload(Widget):

    auto = param.Boolean(default=True,
                         doc="""
        Whether to download on the initial click or allow for
        right-click save as.""")

    button_type = param.ObjectSelector(
        default='default',
        objects=['default', 'primary', 'success', 'warning', 'danger'])

    callback = param.Callable(default=None,
                              doc="""
        A callable that returns the file path or file-like object.""")

    data = param.String(default=None,
                        doc="""
        The data being transferred.""")

    embed = param.Boolean(default=False,
                          doc="""
        Whether to embed the file on initialization.""")

    file = param.Parameter(default=None,
                           doc="""
        The file, file-like object or file contents to transfer.  If
        the file is not pointing to a file on disk a filename must
        also be provided.""")

    filename = param.String(default=None,
                            doc="""
        A filename which will also be the default name when downloading
        the file.""")

    label = param.String(default="Download file",
                         doc="""
        The label of the download button""")

    _clicks = param.Integer(default=0)

    _transfers = param.Integer(default=0)

    _mime_types = {
        'application': {
            'pdf': 'pdf',
            'zip': 'zip'
        },
        'audio': {
            'mp3': 'mp3',
            'ogg': 'ogg',
            'wav': 'wav',
            'webm': 'webm'
        },
        'image': {
            'apng': 'apng',
            'bmp': 'bmp',
            'gif': 'gif',
            'ico': 'x-icon',
            'cur': 'x-icon',
            'jpg': 'jpeg',
            'jpeg': 'jpeg',
            'png': 'png',
            'svg': 'svg+xml',
            'tif': 'tiff',
            'tiff': 'tiff',
            'webp': 'webp'
        },
        'text': {
            'css': 'css',
            'csv': 'plain;charset=UTF-8',
            'js': 'javascript',
            'html': 'html',
            'txt': 'plain;charset=UTF-8'
        },
        'video': {
            'mp4': 'mp4',
            'ogg': 'ogg',
            'webm': 'webm'
        }
    }

    _widget_type = _BkFileDownload

    _rename = {
        'callback': None,
        'embed': None,
        'file': None,
        '_clicks': 'clicks',
        'name': 'title'
    }

    def __init__(self, file=None, **params):
        self._default_label = 'label' not in params
        self._synced = False
        super().__init__(file=file, **params)
        if self.embed:
            self._transfer()
        self._update_label()

    @param.depends('label', watch=True)
    def _update_default(self):
        self._default_label = False

    @param.depends('file', watch=True)
    def _update_filename(self):
        if isinstance(self.file, str):
            self.filename = os.path.basename(self.file)

    @param.depends('auto', 'file', 'filename', watch=True)
    def _update_label(self):
        label = 'Download' if self._synced or self.auto else 'Transfer'
        if self._default_label:
            if self.file is None and self.callback is None:
                label = 'No file set'
            else:
                try:
                    filename = self.filename or os.path.basename(self.file)
                except TypeError:
                    raise ValueError('Must provide filename if file-like '
                                     'object is provided.')
                label = '%s %s' % (label, filename)
            self.label = label
            self._default_label = True

    @param.depends('embed', 'file', 'callback', watch=True)
    def _update_embed(self):
        if self.embed:
            self._transfer()

    @param.depends('_clicks', watch=True)
    def _transfer(self):
        if self.file is None and self.callback is None:
            if self.embed:
                raise ValueError('Must provide a file or a callback '
                                 'if it is to be embedded.')
            return

        from ..param import ParamFunction
        if self.callback is None:
            fileobj = self.file
        else:
            fileobj = ParamFunction.eval(self.callback)
        filename = self.filename
        if isinstance(fileobj, str):
            if not os.path.isfile(fileobj):
                raise FileNotFoundError('File "%s" not found.' % fileobj)
            with open(fileobj, 'rb') as f:
                b64 = b64encode(f.read()).decode("utf-8")
            if filename is None:
                filename = os.path.basename(fileobj)
        elif hasattr(fileobj, 'read'):
            bdata = fileobj.read()
            if not isinstance(bdata, bytes):
                bdata = bdata.encode("utf-8")
            b64 = b64encode(bdata).decode("utf-8")
            if filename is None:
                raise ValueError('Must provide filename if file-like '
                                 'object is provided.')
        else:
            raise ValueError('Cannot transfer unknown object of type %s' %
                             type(fileobj).__name__)

        ext = filename.split('.')[-1]
        for mtype, subtypes in self._mime_types.items():
            stype = None
            if ext in subtypes:
                stype = subtypes[ext]
                break
        if stype is None:
            mime = 'application/octet-stream'
        else:
            mime = '{type}/{subtype}'.format(type=mtype, subtype=stype)

        data = "data:{mime};base64,{b64}".format(mime=mime, b64=b64)
        self._synced = True

        self.param.set_param(data=data, filename=filename)
        self._update_label()
        self._transfers += 1
Exemplo n.º 26
0
class ColorbarPlot(ElementPlot):

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

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

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

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

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

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

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

    _colorbars = {}

    _default_nan = '#8b8b8b'

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

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


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


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

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

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

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

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

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


    def _norm_kwargs(self, element, ranges, opts, vdim, prefix=''):
        """
        Returns valid color normalization kwargs
        to be passed to matplotlib plot function.
        """
        clim = opts.pop(prefix+'clims', None)
        values = np.asarray(element.dimension_values(vdim))
        if clim is None:
            if not len(values):
                clim = (0, 0)
                categorical = False
            elif values.dtype.kind in 'uif':
                if vdim.name in ranges:
                    clim = ranges[vdim.name]['combined']
                else:
                    clim = element.range(vdim)
                if self.logz:
                    # Lower clim must be >0 when logz=True
                    # Choose the maximum between the lowest non-zero value
                    # and the overall range
                    if clim[0] == 0:
                        clim = (values[values!=0].min(), clim[1])
                if self.symmetric:
                    clim = -np.abs(clim).max(), np.abs(clim).max()
                categorical = False
            else:
                clim = (0, len(np.unique(values))-1)
                categorical = True
        else:
            categorical = values.dtype.kind not in 'uif'

        if self.logz:
            if self.symmetric:
                norm = mpl_colors.SymLogNorm(vmin=clim[0], vmax=clim[1],
                                             linthresh=clim[1]/np.e)
            else:
                norm = mpl_colors.LogNorm(vmin=clim[0], vmax=clim[1])
            opts[prefix+'norm'] = norm
        opts[prefix+'vmin'] = clim[0]
        opts[prefix+'vmax'] = clim[1]

        cmap = opts.get(prefix+'cmap', 'viridis')
        if values.dtype.kind not in 'OSUM':
            ncolors = None
            if isinstance(self.color_levels, int):
                ncolors = self.color_levels
            elif isinstance(self.color_levels, list):
                ncolors = len(self.color_levels) - 1
                if isinstance(cmap, list) and len(cmap) != ncolors:
                    raise ValueError('The number of colors in the colormap '
                                     'must match the intervals defined in the '
                                     'color_levels, expected %d colors found %d.'
                                     % (ncolors, len(cmap)))
            try:
                el_min, el_max = np.nanmin(values), np.nanmax(values)
            except ValueError:
                el_min, el_max = -np.inf, np.inf
        else:
            ncolors = clim[-1]+1
            el_min, el_max = -np.inf, np.inf
        vmin = -np.inf if opts[prefix+'vmin'] is None else opts[prefix+'vmin']
        vmax = np.inf if opts[prefix+'vmax'] is None else opts[prefix+'vmax']
        if el_min < vmin and el_max > vmax:
            self._cbar_extend = 'both'
        elif el_min < vmin:
            self._cbar_extend = 'min'
        elif el_max > vmax:
            self._cbar_extend = 'max'

        # Define special out-of-range colors on colormap
        colors = {}
        for k, val in self.clipping_colors.items():
            if val == 'transparent':
                colors[k] = {'color': 'w', 'alpha': 0}
            elif isinstance(val, tuple):
                colors[k] = {'color': val[:3],
                             'alpha': val[3] if len(val) > 3 else 1}
            elif isinstance(val, util.basestring):
                color = val
                alpha = 1
                if color.startswith('#') and len(color) == 9:
                    alpha = int(color[-2:], 16)/255.
                    color = color[:-2]
                colors[k] = {'color': color, 'alpha': alpha}

        if not isinstance(cmap, mpl_colors.Colormap):
            if isinstance(cmap, dict):
                factors = np.unique(values)
                palette = [cmap.get(f, colors.get('NaN', {'color': self._default_nan})['color'])
                           for f in factors]
            else:
                palette = process_cmap(cmap, ncolors, categorical=categorical)
                if isinstance(self.color_levels, list):
                    palette, (vmin, vmax) = color_intervals(palette, self.color_levels, clip=(vmin, vmax))
            cmap = mpl_colors.ListedColormap(palette)
        if 'max' in colors: cmap.set_over(**colors['max'])
        if 'min' in colors: cmap.set_under(**colors['min'])
        if 'NaN' in colors: cmap.set_bad(**colors['NaN'])
        opts[prefix+'cmap'] = cmap
Exemplo n.º 27
0
class GridPlot(CompositePlot):
    """
    Plot a group of elements in a grid layout based on a GridSpace element
    object.
    """

    aspect = param.Parameter(default='equal',
                             doc="""
        Aspect ratios on GridPlot should be automatically determined.""")

    padding = param.Number(default=0.1,
                           doc="""
        The amount of padding as a fraction of the total Grid size""")

    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.""")

    show_frame = param.Boolean(default=False,
                               doc="""
        Whether to draw a frame around the Grid.""")

    show_legend = param.Boolean(default=False,
                                doc="""
        Legends add to much clutter in a grid and are disabled by default.""")

    tick_format = param.String(default="%.2f",
                               doc="""
        Formatting string for the GridPlot ticklabels.""")

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

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

    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 xticks.""")

    def __init__(self,
                 layout,
                 axis=None,
                 create_axes=True,
                 ranges=None,
                 keys=None,
                 dimensions=None,
                 layout_num=1,
                 **params):
        if not isinstance(layout, GridSpace):
            raise Exception("GridPlot only accepts GridSpace.")
        self.layout = layout
        self.cols, self.rows = layout.shape
        self.layout_num = layout_num
        extra_opts = self.lookup_options(layout, 'plot').options
        if not keys or not dimensions:
            dimensions, keys = traversal.unique_dimkeys(layout)
        if 'uniform' not in params:
            params['uniform'] = traversal.uniform(layout)

        super(GridPlot, self).__init__(keys=keys,
                                       dimensions=dimensions,
                                       **dict(extra_opts, **params))
        # Compute ranges layoutwise
        grid_kwargs = {}
        if axis is not None:
            bbox = axis.get_position()
            l, b, w, h = bbox.x0, bbox.y0, bbox.width, bbox.height
            grid_kwargs = {
                'left': l,
                'right': l + w,
                'bottom': b,
                'top': b + h
            }
            self.position = (l, b, w, h)

        self.fig_inches = self._get_size()
        self._layoutspec = gridspec.GridSpec(self.rows, self.cols,
                                             **grid_kwargs)
        self.subplots, self.subaxes, self.layout = self._create_subplots(
            layout, axis, ranges, create_axes)

    def _get_size(self):
        max_dim = max(self.layout.shape)
        # Reduce plot size as GridSpace gets larger
        shape_factor = 1. / max_dim
        # Expand small grids to a sensible viewing size
        expand_factor = 1 + (max_dim - 1) * 0.1
        scale_factor = expand_factor * shape_factor
        cols, rows = self.layout.shape
        if isinstance(self.fig_inches, (tuple, list)):
            fig_inches = list(self.fig_inches)
            if fig_inches[0] is None:
                fig_inches[0] = fig_inches[1] * (cols / rows)
            if fig_inches[1] is None:
                fig_inches[1] = fig_inches[0] * (rows / cols)
            return fig_inches
        else:
            fig_inches = (self.fig_inches, ) * 2
            return (scale_factor * cols * fig_inches[0],
                    scale_factor * rows * fig_inches[1])

    def _create_subplots(self, layout, axis, ranges, create_axes):
        layout = layout.map(Compositor.collapse_element, [CompositeOverlay],
                            clone=False)
        norm_opts = self._deep_options(layout, 'norm', ['axiswise'], [Element])
        axiswise = all(v.get('axiswise', False) for v in norm_opts.values())

        if not ranges:
            self.handles['fig'].set_size_inches(self.fig_inches)
        subplots, subaxes = OrderedDict(), 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)
        r, c = (0, 0)
        for coord in layout.keys(full_grid=True):
            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

            # Create axes
            kwargs = {}
            if create_axes:
                threed = issubclass(vtype, Element3D)
                subax = plt.subplot(self._layoutspec[r, c],
                                    projection='3d' if threed else None)

                if not axiswise and self.shared_xaxis and self.xaxis is not None:
                    self.xaxis = 'top'
                if not axiswise and self.shared_yaxis and self.yaxis is not None:
                    self.yaxis = 'right'

                # Disable subplot axes depending on shared axis options
                # and the position in the grid
                if (self.shared_xaxis or self.shared_yaxis) and not axiswise:
                    if c == 0 and r != 0:
                        subax.xaxis.set_ticks_position('none')
                        kwargs['xaxis'] = 'bottom-bare'
                    if c != 0 and r == 0 and not layout.ndims == 1:
                        subax.yaxis.set_ticks_position('none')
                        kwargs['yaxis'] = 'left-bare'
                    if r != 0 and c != 0:
                        kwargs['xaxis'] = 'bottom-bare'
                        kwargs['yaxis'] = 'left-bare'
                    if not self.shared_xaxis:
                        kwargs['xaxis'] = 'bottom-bare'
                    if not self.shared_yaxis:
                        kwargs['yaxis'] = 'left-bare'
                else:
                    kwargs['xaxis'] = 'bottom-bare'
                    kwargs['yaxis'] = 'left-bare'
                subaxes[(r, c)] = subax
            else:
                subax = None

            # Create subplot
            if view is not None:
                plotting_class = Store.registry['matplotlib'][vtype]
                subplot = plotting_class(view,
                                         fig=self.handles['fig'],
                                         axis=subax,
                                         dimensions=self.dimensions,
                                         show_title=False,
                                         subplot=not create_axes,
                                         ranges=frame_ranges,
                                         uniform=self.uniform,
                                         keys=self.keys,
                                         show_legend=False,
                                         **dict(opts, **kwargs))
                collapsed_layout[coord] = subplot.layout if isinstance(
                    subplot, CompositePlot) else subplot.map
                subplots[(r, c)] = subplot
            else:
                subax.set_visible(False)
            if r != self.rows - 1:
                r += 1
            else:
                r = 0
                c += 1
        if create_axes:
            self.handles['axis'] = self._layout_axis(layout, axis)
            self._adjust_subplots(self.handles['axis'], subaxes)

        return subplots, subaxes, collapsed_layout

    def initialize_plot(self, ranges=None):
        # Get the extent of the layout elements (not the whole layout)
        key = self.keys[-1]
        axis = self.handles['axis']
        subplot_kwargs = dict()
        ranges = self.compute_ranges(self.layout, key, ranges)
        for subplot in self.subplots.values():
            subplot.initialize_plot(ranges=ranges, **subplot_kwargs)

        if self.show_title:
            title = axis.set_title(self._format_title(key),
                                   **self._fontsize('title'))
            self.handles['title'] = title

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

    def _readjust_axes(self, axis):
        if self.subplot:
            axis.set_position(self.position)
            if self.aspect == 'equal':
                axis.set_aspect(float(self.rows) / self.cols)
            self.handles['fig'].canvas.draw()
            self._adjust_subplots(self.handles['axis'], self.subaxes)

    def update_handles(self, axis, view, key, ranges=None):
        """
        Should be called by the update_frame class to update
        any handles on the plot.
        """
        if self.show_title:
            title = axis.set_title(self._format_title(key),
                                   **self._fontsize('title'))
            self.handles['title'] = title

    def _layout_axis(self, layout, axis):
        fig = self.handles['fig']
        axkwargs = {'gid': str(self.position)} if axis else {}
        layout_axis = fig.add_subplot(1, 1, 1, **axkwargs)

        if axis:
            axis.set_visible(False)
            layout_axis.set_position(self.position)
        layout_axis.patch.set_visible(False)

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

        # Set labels
        layout_axis.set_xlabel(str(layout.kdims[0]),
                               **self._fontsize('xlabel'))
        if layout.ndims == 2:
            layout_axis.set_ylabel(str(layout.kdims[1]),
                                   **self._fontsize('ylabel'))

        # Compute and set x- and y-ticks
        dims = layout.kdims
        keys = layout.keys()
        if layout.ndims == 1:
            dim1_keys = keys
            dim2_keys = [0]
            layout_axis.get_yaxis().set_visible(False)
        else:
            dim1_keys, dim2_keys = zip(*keys)
            layout_axis.set_ylabel(str(dims[1]))
            layout_axis.set_aspect(float(self.rows) / self.cols)

        # Process ticks
        plot_width = (1.0 - self.padding) / self.cols
        border_width = self.padding / (self.cols - 1)
        xticks = [(plot_width / 2) + (r * (plot_width + border_width))
                  for r in range(self.cols)]
        plot_height = (1.0 - self.padding) / self.rows
        border_height = self.padding / (self.rows -
                                        1) if layout.ndims > 1 else 0
        yticks = [(plot_height / 2) + (r * (plot_height + border_height))
                  for r in range(self.rows)]

        layout_axis.set_xticks(xticks)
        layout_axis.set_xticklabels(
            self._process_ticklabels(sorted(set(dim1_keys)), dims[0]))
        for tick in layout_axis.get_xticklabels():
            tick.set_rotation(self.xrotation)

        ydim = dims[1] if layout.ndims > 1 else None
        layout_axis.set_yticks(yticks)
        layout_axis.set_yticklabels(
            self._process_ticklabels(sorted(set(dim2_keys)), ydim))
        for tick in layout_axis.get_yticklabels():
            tick.set_rotation(self.yrotation)

        if not self.show_frame:
            layout_axis.spines['right' if self.yaxis ==
                               'left' else 'left'].set_visible(False)
            layout_axis.spines['bottom' if self.xaxis ==
                               'top' else 'top'].set_visible(False)

        axis = layout_axis
        if self.xaxis is not None:
            axis.xaxis.set_ticks_position(self.xaxis)
            axis.xaxis.set_label_position(self.xaxis)
        else:
            axis.xaxis.set_visible(False)

        if self.yaxis is not None:
            axis.yaxis.set_ticks_position(self.yaxis)
            axis.yaxis.set_label_position(self.yaxis)
        else:
            axis.yaxis.set_visible(False)

        for pos in ['left', 'right', 'top', 'bottom']:
            axis.spines[pos].set_visible(False)

        return layout_axis

    def _process_ticklabels(self, labels, dim):
        formatted_labels = []
        for k in labels:
            if dim and dim.formatter:
                k = dim.formatter(k)
            elif not isinstance(k, (str, type(None))):
                k = self.tick_format % k
            elif k is None:
                k = ''
            formatted_labels.append(k)
        return formatted_labels

    def _adjust_subplots(self, axis, subaxes):
        bbox = axis.get_position()
        l, b, w, h = bbox.x0, bbox.y0, bbox.width, bbox.height

        if self.padding:
            width_padding = w / (1. / self.padding)
            height_padding = h / (1. / self.padding)
        else:
            width_padding, height_padding = 0, 0

        if self.cols == 1:
            b_w = 0
        else:
            b_w = width_padding / (self.cols - 1)

        if self.rows == 1:
            b_h = 0
        else:
            b_h = height_padding / (self.rows - 1)
        ax_w = (w - (width_padding if self.cols > 1 else 0)) / self.cols
        ax_h = (h - (height_padding if self.rows > 1 else 0)) / self.rows

        r, c = (0, 0)
        for ax in subaxes.values():
            xpos = l + (c * ax_w) + (c * b_w)
            ypos = b + (r * ax_h) + (r * b_h)
            if r != self.rows - 1:
                r += 1
            else:
                r = 0
                c += 1
            if not ax is None:
                ax.set_position([xpos, ypos, ax_w, ax_h])
Exemplo n.º 28
0
class DynamicMap(HoloMap):
    """
    A DynamicMap is a type of HoloMap where the elements are dynamically
    generated by a callback which may be either a callable or a
    generator. A DynamicMap supports two different modes depending on
    the type of callable supplied and the dimension declarations.

    The 'closed' mode is used when the limits of the parameter space are
    known upon declaration (as specified by the ranges on the key
    dimensions) or 'open' which allows the continual generation of
    elements (e.g as data output by a simulator over an unbounded
    simulated time dimension).

    Generators always imply open mode but a callable that has any key
    dimension unbounded in any direction will also be in open
    mode. Closed mode only applied to callables where all the key
    dimensions are fully bounded.
    """
    _sorted = False
    # Declare that callback is a positional parameter (used in clone)
    __pos_params = ['callback']

    callback = param.Parameter(doc="""
        The callable or generator used to generate the elements. In the
        simplest case where all key dimensions are bounded, this can be
        a callable that accepts the key dimension values as arguments
        (in the declared order) and returns the corresponding element.

        For open mode where there is an unbounded key dimension, the
        return type can specify a key as well as element as the tuple
        (key, element). If no key is supplied, a simple counter is used
        instead.

        If the callback is a generator, open mode is used and next() is
        simply called. If the callback is callable and in open mode, the
        element counter value will be supplied as the single
        argument. This can be used to avoid issues where multiple
        elements in a Layout each call next() leading to uncontrolled
        changes in simulator state (the counter can be used to indicate
        simulation time across the layout).
    """)

    cache_size = param.Integer(default=500,
                               doc="""
       The number of entries to cache for fast access. This is an LRU
       cache where the least recently used item is overwritten once
       the cache is full.""")

    cache_interval = param.Integer(default=1,
                                   doc="""
       When the element counter modulo the cache_interval is zero, the
       element will be cached and therefore accessible when casting to a
       HoloMap.  Applicable in open mode only.""")

    sampled = param.Boolean(default=False,
                            doc="""
       Allows defining a DynamicMap in closed mode without defining the
       dimension bounds or values. The DynamicMap may then be explicitly
       sampled via getitem or the sampling is determined during plotting
       by a HoloMap with fixed sampling.
       """)

    def __init__(self, callback, initial_items=None, **params):
        super(DynamicMap, self).__init__(initial_items,
                                         callback=callback,
                                         **params)
        self.counter = 0
        if self.callback is None:
            raise Exception("A suitable callback must be "
                            "declared to create a DynamicMap")

        self.call_mode = self._validate_mode()
        self.mode = 'closed' if self.call_mode == 'key' else 'open'

    def _initial_key(self):
        """
        Construct an initial key for closed mode based on the lower
        range bounds or values on the key dimensions.
        """
        key = []
        for kdim in self.kdims:
            if kdim.values:
                key.append(kdim.values[0])
            elif kdim.range:
                key.append(kdim.range[0])
        return tuple(key)

    def _validate_mode(self):
        """
        Check the key dimensions and callback to determine the calling mode.
        """
        isgenerator = isinstance(self.callback, types.GeneratorType)
        if isgenerator:
            if self.sampled:
                raise ValueError("Cannot set DynamicMap containing generator "
                                 "to sampled")
            return 'generator'
        if self.sampled:
            return 'key'
        # Any unbounded kdim (any direction) implies open mode
        for kdim in self.kdims:
            if (kdim.values) and kdim.range != (None, None):
                raise Exception(
                    'Dimension cannot have both values and ranges.')
            elif kdim.values:
                continue
            if None in kdim.range:
                return 'counter'
        return 'key'

    def _validate_key(self, key):
        """
        Make sure the supplied key values are within the bounds
        specified by the corresponding dimension range and soft_range.
        """
        key = util.wrap_tuple(key)
        assert len(key) == len(self.kdims)
        for ind, val in enumerate(key):
            kdim = self.kdims[ind]
            low, high = util.max_range([kdim.range, kdim.soft_range])
            if low is not np.NaN:
                if val < low:
                    raise StopIteration("Key value %s below lower bound %s" %
                                        (val, low))
            if high is not np.NaN:
                if val > high:
                    raise StopIteration("Key value %s above upper bound %s" %
                                        (val, high))

    def _style(self, retval):
        """
        Use any applicable OptionTree of the DynamicMap to apply options
        to the return values of the callback.
        """
        if self.id not in Store.custom_options():
            return retval
        spec = StoreOptions.tree_to_dict(Store.custom_options()[self.id])
        return retval(spec)

    def _execute_callback(self, *args):
        """
        Execute the callback, validating both the input key and output
        key where applicable.
        """
        if self.call_mode == 'key':
            self._validate_key(args)  # Validate input key

        if self.call_mode == 'generator':
            retval = next(self.callback)
        else:
            retval = self.callback(*args)

        if self.call_mode == 'key':
            return self._style(retval)

        if isinstance(retval, tuple):
            self._validate_key(retval[0])  # Validated output key
            return self._style(retval)
        else:
            self._validate_key((self.counter, ))
            return (self.counter, self._style(retval))

    def clone(self,
              data=None,
              shared_data=True,
              new_type=None,
              *args,
              **overrides):
        """
        Clone method to adapt the slightly different signature of
        DynamicMap that also overrides Dimensioned clone to avoid
        checking items if data is unchanged.
        """
        if data is None and shared_data:
            data = self.data
        return super(UniformNdMapping,
                     self).clone(overrides.pop('callback', self.callback),
                                 shared_data, new_type,
                                 *(data, ) + args, **overrides)

    def reset(self):
        """
        Return a cleared dynamic map with a cleared cached
        and a reset counter.
        """
        if self.call_mode == 'generator':
            raise Exception("Cannot reset generators.")
        self.counter = 0
        self.data = OrderedDict()
        return self

    def _cross_product(self, tuple_key, cache):
        """
        Returns a new DynamicMap if the key (tuple form) expresses a
        cross product, otherwise returns None. The cache argument is a
        dictionary (key:element pairs) of all the data found in the
        cache for this key.

        Each key inside the cross product is looked up in the cache
        (self.data) to check if the appropriate element is
        available. Oherwise the element is computed accordingly.
        """
        if self.mode != 'closed': return None
        if not any(isinstance(el, (list, set)) for el in tuple_key):
            return None
        if len(tuple_key) == 1:
            product = tuple_key[0]
        else:
            args = [
                set(el) if isinstance(el, (list, set)) else set([el])
                for el in tuple_key
            ]
            product = itertools.product(*args)

        data = []
        for inner_key in product:
            key = util.wrap_tuple(inner_key)
            if key in cache:
                val = cache[key]
            else:
                val = self._execute_callback(*key)
            data.append((key, val))
        return self.clone(data)

    def __getitem__(self, key):
        """
        Return an element for any key chosen key (in'closed mode') or
        for a previously generated key that is still in the cache
        (for one of the 'open' modes)
        """
        tuple_key = util.wrap_tuple(key)

        # Validation for closed mode
        if self.mode == 'closed':
            # DynamicMap(...)[:] returns a new DynamicMap with the same cache
            if key == slice(None, None, None):
                return self.clone(self)

            if any(isinstance(el, slice) for el in tuple_key):
                raise Exception(
                    "Slices not supported by DynamicMap in closed mode "
                    "except for the global slice [:] to create a clone.")

        # Cache lookup
        try:
            cache = super(DynamicMap, self).__getitem__(key)
            # Return selected cache items in a new DynamicMap
            if isinstance(cache, DynamicMap) and self.mode == 'open':
                cache = self.clone(cache)
        except KeyError as e:
            cache = None
            if self.mode == 'open' and len(self.data) > 0:
                raise KeyError(
                    str(e) + " Note: Cannot index outside "
                    "available cache in open interval mode.")

        # If the key expresses a cross product, compute the elements and return
        product = self._cross_product(tuple_key, cache.data if cache else {})
        if product is not None:
            return product

        # Not a cross product and nothing cached so compute element.
        if cache: return cache
        val = self._execute_callback(*tuple_key)
        if self.call_mode == 'counter':
            val = val[1]

        self._cache(tuple_key, val)
        return val

    def _cache(self, key, val):
        """
        Request that a key/value pair be considered for caching.
        """
        if self.mode == 'open' and (self.counter % self.cache_interval) != 0:
            return
        if len(self) >= self.cache_size:
            first_key = next(self.data.iterkeys())
            self.data.pop(first_key)
        self.data[key] = val

    def next(self):
        """
        Interface for 'open' mode. For generators, this simply calls the
        next() method. For callables callback, the counter is supplied
        as a single argument.
        """
        if self.mode == 'closed':
            raise Exception("The next() method should only be called in "
                            "one of the open modes.")

        args = () if self.call_mode == 'generator' else (self.counter, )
        retval = self._execute_callback(*args)

        (key, val) = (retval if isinstance(retval, tuple) else
                      (self.counter, retval))

        key = util.wrap_tuple(key)
        if len(key) != len(self.key_dimensions):
            raise Exception(
                "Generated key does not match the number of key dimensions")

        self._cache(key, val)
        self.counter += 1
        return val

    # For Python 2 and 3 compatibility
    __next__ = next
Exemplo n.º 29
0
class DataFrame(Widget):

    aggregators = param.Dict(default={},
                             doc="""
        A dictionary mapping from index name to an aggregator to
        be used for hierarchical multi-indexes (valid aggregators
        include 'min', 'max', 'mean' and 'sum'). If separate
        aggregators for different columns are required the dictionary
        may be nested as `{index_name: {column_name: aggregator}}`""")

    auto_edit = param.Boolean(default=False,
                              doc="""
        Whether clicking on a table cell automatically starts edit mode.""")

    autosize_mode = param.ObjectSelector(
        default='force_fit',
        objects=["none", "fit_columns", "fit_viewport", "force_fit"],
        doc="""

        Describes the column autosizing mode with one of the following options:

        ``"fit_columns"``
          Compute columns widths based on cell contents but ensure the
          table fits into the available viewport. This results in no
          horizontal scrollbar showing up, but data can get unreadable
          if there is not enough space available.

        ``"fit_viewport"``
          Adjust the viewport size after computing columns widths based
          on cell contents.

        ``"force_fit"``
          Fit columns into available space dividing the table width across
          the columns equally (equivalent to `fit_columns=True`).
          This results in no horizontal scrollbar showing up, but data
          can get unreadable if there is not enough space available.

        ``"none"``
          Do not automatically compute column widths.""")

    editors = param.Dict(default={},
                         doc="""
        Bokeh CellEditor to use for a particular column
        (overrides the default chosen based on the type).""")

    hierarchical = param.Boolean(default=False,
                                 constant=True,
                                 doc="""
        Whether to generate a hierachical index.""")

    formatters = param.Dict(default={},
                            doc="""
        Bokeh CellFormatter to use for a particular column
        (overrides the default chosen based on the type).""")

    fit_columns = param.Boolean(default=None,
                                doc="""
        Whether columns should expand to the available width. This
        results in no horizontal scrollbar showing up, but data can
        get unreadable if there is no enough space available.""")

    frozen_columns = param.Integer(default=None,
                                   doc="""
        Integer indicating the number of columns to freeze. If set the
        first N columns will be frozen which prevents them from
        scrolling out of frame.""")

    frozen_rows = param.Integer(default=None,
                                doc="""
       Integer indicating the number of rows to freeze. If set the
       first N rows will be frozen which prevents them from scrolling
       out of frame, if set to a negative value last N rows will be
       frozen.""")

    reorderable = param.Boolean(default=True,
                                doc="""
        Allows the reordering of a table's columns. To reorder a
        column, click and drag a table's header to the desired
        location in the table.  The columns on either side will remain
        in their previous order.""")

    selection = param.List(default=[],
                           doc="""
        The currently selected rows of the table.""")

    show_index = param.Boolean(default=True,
                               doc="""
        Whether to show the index column.""")

    sortable = param.Boolean(default=True,
                             doc="""
        Allows to sort table's contents. By default natural order is
        preserved.  To sort a column, click on it's header. Clicking
        one more time changes sort direction. Use Ctrl + click to
        return to natural order. Use Shift + click to sort multiple
        columns simultaneously.""")

    row_height = param.Integer(default=25,
                               doc="""
        The height of each table row.""")

    widths = param.ClassSelector(default={},
                                 class_=(dict, int),
                                 doc="""
        A mapping from column name to column width or a fixed column
        width.""")

    value = param.Parameter(default=None)

    _rename = {
        'editors': None,
        'formatters': None,
        'widths': None,
        'disabled': None,
        'show_index': None
    }

    _manual_params = [
        'value', 'editors', 'formatters', 'selection', 'widths', 'aggregators',
        'show_index'
    ]

    _aggregators = {
        'sum': SumAggregator,
        'max': MaxAggregator,
        'min': MinAggregator,
        'mean': AvgAggregator
    }

    def __init__(self, value=None, **params):
        super(DataFrame, self).__init__(value=value, **params)
        self.param.watch(self._validate, 'value')
        self._validate(None)
        self._renamed_cols = {}
        self._updating = False

    def _validate(self, event):
        if self.value is None:
            return
        cols = self.value.columns
        if len(cols) != len(cols.drop_duplicates()):
            raise ValueError('Cannot display a pandas.DataFrame with '
                             'duplicate column names.')

    @property
    def indexes(self):
        import pandas as pd
        if self.value is None or not self.show_index:
            return []
        elif isinstance(self.value.index, pd.MultiIndex):
            return list(self.value.index.names)
        return [self.value.index.name or 'index']

    def _get_columns(self):
        if self.value is None:
            return []

        indexes = self.indexes
        col_names = list(self.value.columns)
        if not self.hierarchical or len(indexes) == 1:
            col_names = indexes + col_names
        else:
            col_names = indexes[-1:] + col_names
        df = self.value.reset_index() if len(indexes) > 1 else self.value
        columns = []
        for col in col_names:
            if col in df.columns:
                data = df[col]
            else:
                data = df.index

            kind = data.dtype.kind
            if kind == 'i':
                formatter = NumberFormatter()
                editor = IntEditor()
            elif kind == 'f':
                formatter = NumberFormatter(format='0,0.0[00000]')
                editor = NumberEditor()
            elif isdatetime(data) or kind == 'M':
                formatter = DateFormatter(format='%Y-%m-%d %H:%M:%S')
                editor = DateEditor()
            else:
                formatter = StringFormatter()
                editor = StringEditor()

            if col in self.editors:
                editor = self.editors[col]

            if col in indexes or editor is None:
                editor = CellEditor()

            if col in self.formatters:
                formatter = self.formatters[col]
            if str(col) != col:
                self._renamed_cols[str(col)] = col
            if isinstance(self.widths, int):
                width = self.widths
            else:
                width = self.widths.get(str(col))

            title = str(col)
            if col in indexes and len(indexes) > 1 and self.hierarchical:
                title = 'Index: %s' % ' | '.join(indexes)
            column = TableColumn(field=str(col),
                                 title=title,
                                 editor=editor,
                                 formatter=formatter,
                                 width=width)
            columns.append(column)
        return columns

    def _get_groupings(self):
        if self.value is None:
            return []

        groups = []
        for group, agg_group in zip(self.indexes[:-1], self.indexes[1:]):
            if str(group) != group:
                self._renamed_cols[str(group)] = group
            aggs = self._get_aggregators(agg_group)
            groups.append(GroupingInfo(getter=str(group), aggregators=aggs))
        return groups

    def _get_aggregators(self, group):
        numeric_cols = list(self.value.select_dtypes(include='number').columns)
        aggs = self.aggregators.get(group, [])
        if not isinstance(aggs, list):
            aggs = [aggs]
        expanded_aggs = []
        for col_aggs in aggs:
            if not isinstance(col_aggs, dict):
                col_aggs = {col: col_aggs for col in numeric_cols}
            for col, agg in col_aggs.items():
                if isinstance(agg, str):
                    agg = self._aggregators.get(agg)
                if issubclass(agg, RowAggregator):
                    expanded_aggs.append(agg(field_=str(col)))
        return expanded_aggs

    def _get_properties(self):
        props = {
            p: getattr(self, p)
            for p in list(Layoutable.param) if getattr(self, p) is not None
        }
        df = self.value.reset_index() if len(self.indexes) > 1 else self.value
        if df is None:
            data = {}
        else:
            data = {
                k if isinstance(k, str) else str(k): v
                for k, v in ColumnDataSource.from_df(df).items()
            }
        if props.get('height', None) is None:
            length = max([len(v) for v in data.values()]) if data else 0
            props['height'] = length * self.row_height + 30
        if self.hierarchical:
            props['target'] = ColumnDataSource(
                data=dict(row_indices=[], labels=[]))
            props['grouping'] = self._get_groupings()
        props['source'] = ColumnDataSource(data=data)
        props['columns'] = self._get_columns()
        props['index_position'] = None
        props['fit_columns'] = self.fit_columns
        if 'autosize_mode' in DataTable.properties():
            props['frozen_columns'] = self.frozen_columns
            props['frozen_rows'] = self.frozen_rows
            props['autosize_mode'] = self.autosize_mode
            props['auto_edit'] = self.auto_edit
        props['row_height'] = self.row_height
        props['editable'] = not self.disabled and len(self.indexes) <= 1
        props['sortable'] = self.sortable
        props['reorderable'] = self.reorderable
        return props

    def _process_param_change(self, msg):
        if 'disabled' in msg:
            msg['editable'] = not msg.pop('disabled') and len(
                self.indexes) <= 1
        return super(DataFrame, self)._process_param_change(msg)

    def _get_model(self, doc, root=None, parent=None, comm=None):
        model_type = DataCube if self.hierarchical else DataTable
        model = model_type(**self._get_properties())
        if root is None:
            root = model
        self._link_props(model.source, ['data', ('patching', 'data')], doc,
                         root, comm)
        self._link_props(model.source.selected, ['indices'], doc, root, comm)
        self._models[root.ref['id']] = (model, parent)
        return model

    def _manual_update(self, events, model, doc, root, parent, comm):
        self._validate(None)
        for event in events:
            if event.type == 'triggered' and self._updating:
                continue
            elif event.name in ('value', 'show_index'):
                cds = model.source
                data = {
                    k if isinstance(k, str) else str(k): v
                    for k, v in ColumnDataSource.from_df(self.value).items()
                }
                cds.data = data
                model.columns = self._get_columns()
                if isinstance(model, DataCube):
                    model.groupings = self._get_groupings()
            elif event.name == 'selection':
                model.source.selected.indices = self.selection
            elif event.name == 'aggregators':
                for g in model.grouping:
                    group = self._renamed_cols.get(g.getter, g.getter)
                    index = self.indexes[self.indexes.index(group) + 1]
                    g.aggregators = self._get_aggregators(index)
            else:
                for col in model.columns:
                    if col.name in self.editors:
                        col.editor = self.editors[col.name]
                    if col.name in self.formatters:
                        col.formatter = self.formatters[col.name]
                    if col.name in self.widths:
                        col.width = self.widths[col.name]

    def _process_events(self, events):
        if 'data' in events:
            data = events.pop('data')
            updated = False
            for k, v in data.items():
                if k in self.indexes:
                    continue
                k = self._renamed_cols.get(k, k)
                if isinstance(v, dict):
                    v = [
                        v for _, v in sorted(v.items(),
                                             key=lambda it: int(it[0]))
                    ]
                try:
                    isequal = (self.value[k].values == np.asarray(v)).all()
                except Exception:
                    isequal = False
                if not isequal:
                    self.value[k] = v
                    updated = True
            if updated:
                self._updating = True
                try:
                    self.param.trigger('value')
                finally:
                    self._updating = False
        if 'indices' in events:
            self.selection = events.pop('indices')
        super(DataFrame, self)._process_events(events)

    @property
    def selected_dataframe(self):
        """
        Returns a DataFrame of the currently selected rows.
        """
        if not self.selection:
            return self.value
        return self.value.iloc[self.selection]
Exemplo n.º 30
0
class CompositePlot(BokehPlot):
    """
    CompositePlot is an abstract baseclass for plot types that draw
    render multiple axes. It implements methods to add an overall title
    to such a plot.
    """

    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.

        * "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.
    """)

    fontsize = param.Parameter(default={'title': '15pt'},
                               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'}""")

    def _link_dimensioned_streams(self):
        """
        Should perform any linking required to update titles when dimensioned
        streams change.
        """
        streams = [
            s for s in self.streams
            if any(k in self.dimensions for k in s.contents)
        ]
        for s in streams:
            s.add_subscriber(self._stream_update, 1)

    def _stream_update(self, **kwargs):
        contents = [k for s in self.streams for k in s.contents]
        key = tuple(None if d in contents else k
                    for d, k in zip(self.dimensions, self.current_key))
        key = wrap_tuple_streams(key, self.dimensions, self.streams)
        self._get_title_div(key)

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