Beispiel #1
0
class collapse_curve(ElementOperation):
    """
    Given an overlay of Curves, compute a new curve which is collapsed
    for each x-value given a specified function.

    This is an example of an ElementOperation that does not involve
    any Raster types.
    """

    output_type = Curve

    fn = param.Callable(default=np.mean,
                        doc="""
        The function that is used to collapse the curve y-values for
        each x-value.""")

    group = param.String(default='Collapses',
                         doc="""
       The group assigned to the collapsed curve output.""")

    def _process(self, overlay, key=None):

        for curve in overlay:
            if not isinstance(curve, Curve):
                raise ValueError(
                    "The collapse_curve operation requires Curves as input.")
            if not all(curve.data[:, 0] == overlay[0].data[:, 0]):
                raise ValueError(
                    "All input curves must have same x-axis values.")

        data = []
        for i, xval in enumerate(overlay[0].data[:, 0]):
            yval = self.p.fn([c.data[i, 1] for c in overlay])
            data.append((xval, yval))

        return Curve(np.array(data),
                     group=self.p.group,
                     label=self.get_overlay_label(overlay))
Beispiel #2
0
class BoundedNumber(NumberGenerator):
    """
    Function object that silently enforces numeric bounds on values
    returned by a callable object.
    """

    generator = param.Callable(None, doc="Object to call to generate values.")

    bounds = param.Parameter((None, None),
                             doc="""
        Legal range for the value returned, as a pair.

        The default bounds are (None,None), meaning there are actually
        no bounds.  One or both bounds can be set by specifying a
        value.  For instance, bounds=(None,10) means there is no lower
        bound, and an upper bound of 10.""")

    def __call__(self):
        val = self.generator()
        min_, max_ = self.bounds
        if min_ != None and val < min_: return min_
        elif max_ != None and val > max_: return max_
        else: return val
Beispiel #3
0
class Serializer(Exporter):
    "A generic exporter that supports any arbitrary serializer"

    serializer = param.Callable(Store.dumps,
                                doc="""
       The serializer function, set to Store.dumps by default. The
       serializer should take an object and output a serialization as
       a string or byte stream.

       Any suitable serializer may be used. For instance, pickle.dumps
       may be used although this will not save customized options.""")

    mime_type = param.String('application/python-pickle',
                             allow_None=True,
                             doc="""
       The mime-type associated with the serializer (if applicable).""")

    file_ext = param.String('pkl',
                            doc="""
       The file extension associated with the corresponding file
       format (if applicable).""")

    def __call__(self, obj, **kwargs):
        data = self.serializer(obj)
        return data, {'file-ext': self.file_ext, 'mime_type': self.mime_type}

    @bothmethod
    def save(self_or_cls, obj, filename, info={}, key={}, **kwargs):
        data, base_info = self_or_cls(obj, **kwargs)
        key = self_or_cls._merge_metadata(obj, self_or_cls.key_fn, key)
        info = self_or_cls._merge_metadata(obj, self_or_cls.info_fn, info,
                                           base_info)
        metadata, _ = self_or_cls({'info': info, 'key': key}, **kwargs)
        filename = self_or_cls._filename(filename)
        with open(filename, 'ab') as f:
            f.write(metadata)
            f.write(data)
Beispiel #4
0
class DataLoader(MultiCollectionHandler):
    '''Extension of MultiCollectionHandler that automatically load files of selected part of the data collection'''

    loaded_objects = param.Dict()
    loading_fun = param.Callable(
        doc=
        'callable loading function accepting a path as argument. If None the pandas DC accessor is used'
    )
    loading_off = param.Boolean(False)

    @param.depends('subdf', watch=True)
    def _load_files(self):

        if not self.loading_off:
            if self.loading_fun is not None:
                # read files in parallel
                with ThreadPoolExecutor() as threads:
                    files = threads.map(self.loading_fun, self.subdf.dc.path)
            else:
                files = self.subdf.dc.read()

            # set keys using levels that can have multiple selection
            if len(self.multi_select_levels) == 0:
                keys = self.subdf.index
            elif len(self.multi_select_levels) == 1:
                keys = self.subdf.index.get_level_values(
                    self.multi_select_levels[0])
            else:
                keys = zip(*[
                    self.subdf.index.get_level_values(l)
                    for l in self.multi_select_levels
                ])

            self.loaded_objects = {k: f for k, f in zip(keys, files)}

            # force garbage collection
            gc.collect()
Beispiel #5
0
class matrixplot(PylabPlotCommand):
    """
    Simple plotting for any matrix as a bitmap with axes.

    Like MatLab's imagesc, scales the values to fit in the range 0 to 1.0.
    Intended for interactive debugging or analyzing from the command
    prompt.  See MatPlotLib's pylab functions to create more elaborate
    or customized plots; this is just a simple example.
    """

    plot_type = param.Callable(default=plt.gray,
                               doc="""
        Matplotlib command to generate the plot, e.g. plt.gray or plt.hsv.""")

    extent = param.Parameter(default=None,
                             doc="""
        Subregion of the matrix to plot, as a tuple (l,b,r,t).""")

    # JABALERT: All but the first two should probably be Parameters
    def __call__(self, mat, aspect=None, colorbar=True, **params):
        p = ParamOverrides(self, params)

        fig = plt.figure(figsize=(5, 5))
        p.plot_type()

        # Swap lbrt to lrbt to match pylab
        if p.extent is None:
            extent = None
        else:
            (l, b, r, t) = p.extent
            extent = (l, r, b, t)

        plt.imshow(mat, interpolation='nearest', aspect=aspect, extent=extent)
        if colorbar and (mat.min() != mat.max()): plt.colorbar()
        self._generate_figure(p)
        return fig
Beispiel #6
0
class function(Operation):

    output_type = param.ClassSelector(class_=type,
                                      doc="""
        The output type of the method operation""")

    input_type = param.ClassSelector(class_=type,
                                     doc="""
        The object type the method is defined on""")

    fn = param.Callable(default=lambda el, *args, **kwargs: el,
                        doc="""
        The function to apply.""")

    args = param.List(default=[],
                      doc="""
        The list of positional argument to pass to the method""")

    kwargs = param.Dict(default={},
                        doc="""
        The dict of keyword arguments to pass to the method""")

    def _process(self, element, key=None):
        return self.p.fn(element, *self.p.args, **self.p.kwargs)
Beispiel #7
0
class FileArchive(Archive):
    """
    A file archive stores files on disk, either unpacked in a
    directory or in an archive format (e.g. a zip file).
    """

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        self._validate_formatters()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        return filename

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

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

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

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

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

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

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

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

    def listing(self):
        "Return a list of filename entries currently in the archive"
        return [
            '.'.join([f, ext]) if ext else f
            for (f, ext) in self._files.keys()
        ]
Beispiel #8
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, and that allowed values must be floats greater than zero.

    In addition, Dimensions can be declared as cyclic, support
    categorical data using a finite set of allowed, ordered values and
    support a custom, pretty-printed representation.
    """

    name = param.String(doc="""
        Optional name associated with the Dimension. For instance,
        'height' or 'weight'.""")

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

    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.ClassSelector(class_=(str, list),
                                 default=[],
                                 doc="""
        Optional set of allowed values for the dimension that can also
        be used to retain a categorical ordering. Setting values to
        'initial' indicates that the values will be added during construction."""
                                 )

    # 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, name, **params):
        """
        Initializes the Dimension object with the given name.
        """
        if isinstance(name, Dimension):
            existing_params = dict(name.get_param_values())
        elif (name, params.get('unit', None)) in self.presets.keys():
            preset = self.presets[(str(name), str(params['unit']))]
            existing_params = dict(preset.get_param_values())
        elif name in self.presets.keys():
            existing_params = dict(self.presets[str(name)].get_param_values())
        elif (name, ) in self.presets.keys():
            existing_params = dict(
                self.presets[(str(name), )].get_param_values())
        else:
            existing_params = {'name': name}

        all_params = dict(existing_params, **params)
        name = all_params['name']
        label = name
        if isinstance(name, tuple):
            name, label = name
            all_params['name'] = name
        self.label = label

        if not isinstance(params.get('values', None), basestring):
            all_params['values'] = sorted(
                list(unique_array(params.get('values', []))))
        elif params['values'] != 'initial':
            raise Exception(
                "Values argument can only be set with the string 'initial'.")
        super(Dimension, self).__init__(**all_params)

    def __call__(self, name=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 name is not None: settings['name'] = name
        return self.__class__(**settings)

    @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 safe_unicode(self.label) + safe_unicode(unit)

    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 value

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

    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 ' ' + safe_unicode(self.unit)
        value = self.pprint_value(value)
        return title_format.format(name=safe_unicode(self.label),
                                   val=value,
                                   unit=unit)

    def __hash__(self):
        """
        The hash allows two Dimension objects to be compared; if the
        hashes are equal, all the parameters of the Dimensions are
        also equal.
        """
        return sum([
            hash(value) for _, value in self.get_param_values()
            if not isinstance(value, list)
        ])

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

    def __str__(self):
        return self.pprint_label

    def __eq__(self, other):
        "Implements equals operator including sanitized comparison."
        dim_matches = [self.name, self.label, dimension_sanitizer(self.label)]
        if self is other:
            return True
        elif isinstance(other, Dimension):
            return bool({other.name, other.label} & set(dim_matches))
        else:
            return other in dim_matches

    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
Beispiel #9
0
class JointNormalizingCFSheet(CFSheet):
    """
    A type of CFSheet extended to support joint sum-based normalization.

    For L1 normalization, joint normalization means normalizing the
    sum of (the absolute values of) all weights in a set of
    corresponding CFs in different Projections, rather than only
    considering weights in the same CF.

    This class provides a mechanism for grouping Projections (see
    _port_match and _grouped_in_projections) and a learn() function
    that computes the joint sums.  Joint normalization also requires
    having ConnectionField store and return a norm_total for each
    neuron, and having an TransferFn that will respect this norm_total
    rather than the strict total of the ConnectionField's weights.  At
    present, CFPOF_DivisiveNormalizeL1 and
    CFPOF_DivisiveNormalizeL1_opt do use norm_total; others can be
    extended to do something similar if necessary.

    To enable joint normalization, you can declare that all the
    incoming connections that should be normalized together each
    have a dest_port of:

    dest_port=('Activity','JointNormalize', 'AfferentGroup1'),

    Then all those that have this dest_port will be normalized
    together, as long as an appropriate TransferFn is being used.
    """

    joint_norm_fn = param.Callable(default=compute_joint_norm_totals,doc="""
        Function to use to compute the norm_total for each CF in each
        projection from a group to be normalized jointly.""")

    # JABALERT: Should check that whenever a connection is added to a
    # group, it has the same no of cfs as the existing connections.
    def start(self):
        self._normalize_weights(active_units_mask=False)


    # CEBALERT: rename active_units_mask and default to False
    def _normalize_weights(self,active_units_mask=True):
        """
        Apply the weights_output_fns for every group of Projections.

        If active_units_mask is True, only active units will have
        their weights normalized.
        """
        for key,projlist in self._grouped_in_projections('JointNormalize'):
            if key == None:
                normtype='Individually'
            else:
                normtype='Jointly'
                self.joint_norm_fn(projlist,active_units_mask)

            self.debug(normtype + " normalizing:")

            for p in projlist:
                p.apply_learn_output_fns(active_units_mask=active_units_mask)
                self.debug('  ',p.name)


    def learn(self):
        """
        Call the learn() method on every Projection to the Sheet, and
        call the output functions (jointly if necessary).
        """
        # Ask all projections to learn independently
        for proj in self.in_connections:
            if not isinstance(proj,Projection):
                self.debug("Skipping non-Projection "+proj.name)
            else:
                proj.learn()

        # Apply output function in groups determined by dest_port
        self._normalize_weights()
Beispiel #10
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 auxillary
    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)
Beispiel #11
0
class TopoCommand(Command):
    """
   TopoCommand is designed to to format Lancet Args objects into
   run_batch commands in a general way. Note that Topographica is
   always invoked with the -a flag so all of topo.command is imported.

   Some of the parameters duplicate those in run_batch to ensure
   consistency with previous run_batch usage in Topographica. As a
   consequence, this class sets all the necessary options for
   run_batch except the 'times' parameter which may vary specified
   arbitrarily by the Lancet Args object.
   """

    tyfile = param.String(doc="The Topographica model file to run.")

    analysis_fn = param.String(default="default_analysis_function",
                               doc="""
       The name of the analysis_fn to run. If modified from the
       default, the named callable will need to be imported into the
       namespace using a '-c' command in topo_flag_options.""")

    tag = param.Boolean(default=False,
                        doc="""
       Whether to label the run_batch generated directory with the
       batch name and batch tag.""")

    topo_switches = param.List(default=['-a'],
                               doc="""
          Specifies the Topographica qsub switches (flags without
          arguments) as a list of strings. Note the that the -a switch
          is always used to auto import commands.""")

    topo_flag_options = param.Dict(default={},
                                   doc="""
          Specifies Topographica flags and their corresponding options
          as a dictionary.  This parameter is suitable for setting -c
          and -p flags for Topographica. This parameter is important
          for introducing the callable named by the analysis_fn
          parameter into the namespace.

          Tuples can be used to indicate groups of options using the
          same flag:
          {'-p':'retina_density=5'} => -p retina_density=5
          {'-p':('retina_density=5', 'scale=2') => -p retina_density=5 -p scale=2

          If a plain Python dictionary is used, the keys are
          alphanumerically sorted, otherwise the dictionary is assumed
          to be an OrderedDict (Python 2.7+, Python3 or
          param.external.OrderedDict) and the key ordering will be
          preserved. Note that the '-' is prefixed to the key if
          missing (to ensure a valid flag). This allows keywords to be
          specified with the dict constructor eg.. dict(key1=value1,
          key2=value2).""")

    param_formatter = param.Callable(
        param_formatter.instance(),
        doc="""Used to specify run_batch formatting.""")

    max_name_length = param.Number(
        default=200, doc="Matches run_batch parameter of same name.")

    snapshot = param.Boolean(default=True,
                             doc="Matches run_batch parameter of same name.")

    vc_info = param.Boolean(default=True,
                            doc="Matches run_batch parameter of same name.")

    save_global_params = param.Boolean(
        default=True, doc="Matches run_batch parameter of same name.")

    progress_bar = param.String(
        default='disabled', doc="Matches run_batch parameter of same name.")

    progress_interval = param.Number(
        default=100, doc="Matches run_batch parameter of same name.")

    def __init__(self, tyfile, executable=None, **params):

        auto_executable = os.path.realpath(
            os.path.join(topo.__file__, '..', '..', 'topographica'))

        executable = executable if executable else auto_executable
        super(TopoCommand, self).__init__(tyfile=tyfile,
                                          executable=executable,
                                          **params)
        self.pprint_args(['executable', 'tyfile', 'analysis_fn'],
                         ['topo_switches', 'snapshot'])
        self._typath = os.path.abspath(self.tyfile)

        if not os.path.isfile(self.executable):
            raise Exception(
                'Cannot find the topographica script relative to topo/__init__.py.'
            )
        if not os.path.exists(self._typath):
            raise Exception("Tyfile doesn't exist! Cannot proceed.")

        if ((self.analysis_fn.strip() != "default_analysis_function")
                and (type(self) == TopoCommand)
                and ('-c' not in self.topo_flag_options)):
            raise Exception, 'Please use -c option to introduce the appropriate analysis into the namespace.'

    def _topo_args(self, switch_override=[]):
        """
      Method to generate Popen style argument list for Topographica
      using the topo_switches and topo_flag_options
      parameters. Switches are returned first, sorted
      alphanumerically.  The qsub_flag_options follow in the order
      given by keys() which may be controlled if an OrderedDict is
      used (eg. in Python 2.7+ or using param.external
      OrderedDict). Otherwise the keys are sorted alphanumerically.
      """
        opt_dict = type(self.topo_flag_options)()
        opt_dict.update(self.topo_flag_options)

        # Alphanumeric sort if vanilla Python dictionary
        if type(self.topo_flag_options) == dict:
            ordered_options = [(k, opt_dict[k]) for k in sorted(opt_dict)]
        else:
            ordered_options = list(opt_dict.items())

        # Unpack tuple values so flag:(v1, v2,...)) => ..., flag:v1, flag:v2, ...
        unpacked_groups = [[(k, v)
                            for v in val] if type(val) == tuple else [(k, val)]
                           for (k, val) in ordered_options]
        unpacked_kvs = [el for group in unpacked_groups for el in group]

        # Adds '-' if missing (eg, keywords in dict constructor) and flattens lists.
        ordered_pairs = [(k, v) if (k[0] == '-') else ('-%s' % (k), v)
                         for (k, v) in unpacked_kvs]
        ordered_options = [[k] + ([v] if type(v) == str else v)
                           for (k, v) in ordered_pairs]
        flattened_options = [el for kvs in ordered_options for el in kvs]
        switches = [
            s for s in switch_override if (s not in self.topo_switches)
        ] + self.topo_switches
        return sorted(switches) + flattened_options

    def _run_batch_kwargs(self, spec, tid, info):
        """
      Defines the keywords accepted by run_batch and so specifies
      run_batch behaviour. These keywords are those consumed by
      run_batch for controlling run_batch behaviour.
      """
        # Direct options for controlling run_batch.
        options = {
            'name_time_format': repr(info['timestamp_format']),
            'max_name_length': self.max_name_length,
            'snapshot': self.snapshot,
            'vc_info': self.vc_info,
            'save_global_params': self.save_global_params,
            'progress_interval': self.progress_interval,
            'progress_bar': repr(self.progress_bar),
            'metadata_dir': repr('metadata'),
            'compress_metadata': repr('zip'),
            'save_script_repr': repr('first')
        }

        # Settings inferred using information from launcher ('info')
        tag_info = (info['batch_name'], info['batch_tag'])
        tag = '[%s]_' % ':'.join(el
                                 for el in tag_info if el) if self.tag else ''

        derived_options = {
            'dirname_prefix': repr(''),
            'tag': repr('%st%s_' % (tag, tid)),
            'output_directory': repr(info['root_directory'])
        }

        # Use fixed timestamp argument to run_batch if available.
        if info['timestamp'] is not None:
            derived_options['timestamp'] = info['timestamp']

        # The analysis_fn is set my self.analysis_fn
        derived_options['analysis_fn'] = self.analysis_fn

        # Use the specified param_formatter to create the suitably named
        # lambda (returning the desired string) in run_batch.
        dir_format = self.param_formatter(info['constant_keys'],
                                          info['varying_keys'], spec)

        dir_formatter = 'lambda p: %s' % repr(dir_format)
        derived_options['dirname_params_filter'] = dir_formatter

        return dict(options.items() + derived_options.items())

    def __call__(self, spec, tid=None, info={}):
        """
      Returns a Popen argument list to invoke Topographica and execute
      run_batch with all options appropriately set (in alphabetical
      order). Keywords that are not run_batch options are also in
      alphabetical order at the end of the keyword list.
      """

        kwarg_opts = self._run_batch_kwargs(spec, tid, info)
        # Override spec values if mistakenly included.
        allopts = dict(spec, **kwarg_opts)

        keywords = ', '.join([
            '%s=%s' % (k, allopts[k])
            for k in sorted(kwarg_opts.keys()) + sorted(spec.keys())
        ])

        run_batch_list = ["run_batch(%s,%s)" % (repr(self._typath), keywords)]
        topo_args = self._topo_args(['-a'])
        return [self.executable
                ] + topo_args + ['-c', '; '.join(run_batch_list)]
Beispiel #12
0
class Dynamic(param.ParameterizedFunction):
    """
    Dynamically applies a callable to the Elements in any HoloViews
    object. Will return a DynamicMap wrapping the original map object,
    which will lazily evaluate when a key is requested. By default
    Dynamic applies a no-op, making it useful for converting HoloMaps
    to a DynamicMap.

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

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

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

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

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

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

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

    def __call__(self, map_obj, **params):
        watch = params.pop('watch', True)
        self.p = param.ParamOverrides(self, params)
        callback = self._dynamic_operation(map_obj)
        streams = self._get_streams(map_obj, watch)
        if isinstance(map_obj, DynamicMap):
            dmap = map_obj.clone(callback=callback,
                                 shared_data=self.p.shared_data,
                                 streams=streams)
        else:
            dmap = self._make_dynamic(map_obj, callback, streams)
        return dmap

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

        # If callback is a parameterized method and watch is disabled add as stream
        param_watch_support = util.param_version >= '1.8.0' and watch
        if util.is_param_method(self.p.operation) and param_watch_support:
            streams.append(self.p.operation)
        valid, invalid = Stream._process_streams(streams)
        if invalid:
            msg = ('The supplied streams list contains objects that '
                   'are not Stream instances: {objs}')
            raise TypeError(
                msg.format(objs=', '.join('%r' % el for el in invalid)))
        return valid

    def _process(self, element, key=None):
        if isinstance(self.p.operation, Operation):
            kwargs = {
                k: v
                for k, v in self.p.kwargs.items()
                if k in self.p.operation.params()
            }
            return self.p.operation.process_element(element, key, **kwargs)
        else:
            return self.p.operation(element, **self.p.kwargs)

    def _dynamic_operation(self, map_obj):
        """
        Generate function to dynamically apply the operation.
        Wraps an existing HoloMap or DynamicMap.
        """
        if not isinstance(map_obj, DynamicMap):

            def dynamic_operation(*key, **kwargs):
                self.p.kwargs.update(kwargs)
                obj = map_obj[key] if isinstance(map_obj, HoloMap) else map_obj
                return self._process(obj, key)
        else:

            def dynamic_operation(*key, **kwargs):
                self.p.kwargs.update(kwargs)
                return self._process(map_obj[key], key)

        if isinstance(self.p.operation, Operation):
            return OperationCallable(dynamic_operation,
                                     inputs=[map_obj],
                                     link_inputs=self.p.link_inputs,
                                     operation=self.p.operation)
        else:
            return Callable(dynamic_operation,
                            inputs=[map_obj],
                            link_inputs=self.p.link_inputs)

    def _make_dynamic(self, hmap, dynamic_fn, streams):
        """
        Accepts a HoloMap and a dynamic callback function creating
        an equivalent DynamicMap from the HoloMap.
        """
        if isinstance(hmap, ViewableElement):
            return DynamicMap(dynamic_fn, streams=streams)
        dim_values = zip(*hmap.data.keys())
        params = util.get_param_values(hmap)
        kdims = [
            d(values=list(util.unique_iterator(values)))
            for d, values in zip(hmap.kdims, dim_values)
        ]
        return DynamicMap(dynamic_fn,
                          streams=streams,
                          **dict(params, kdims=kdims))
Beispiel #13
0
class PeriodicCallback(param.Parameterized):
    """
    Periodic encapsulates a periodic callback which will run both
    in tornado based notebook environments and on bokeh server. By
    default the callback will run until the stop method is called,
    but count and timeout values can be set to limit the number of
    executions or the maximum length of time for which the callback
    will run.
    """

    callback = param.Callable(doc="""
        The callback to execute periodically.""")

    count = param.Integer(default=None,
                          doc="""
        Number of times the callback will be executed, by default
        this is unlimited.""")

    period = param.Integer(default=500,
                           doc="""
        Period in milliseconds at which the callback is executed.""")

    timeout = param.Integer(default=None,
                            doc="""
        Timeout in seconds from the start time at which the callback
        expires""")

    def __init__(self, **params):
        super(PeriodicCallback, self).__init__(**params)
        self._counter = 0
        self._start_time = None
        self._timeout = None
        self._cb = None
        self._doc = None

    def start(self):
        if self._cb is not None:
            raise RuntimeError('Periodic callback has already started.')
        self._start_time = time.time()
        if _curdoc().session_context:
            self._doc = _curdoc()
            self._cb = self._doc.add_periodic_callback(self._periodic_callback,
                                                       self.period)
        else:
            from tornado.ioloop import PeriodicCallback
            self._cb = PeriodicCallback(self._periodic_callback, self.period)
            self._cb.start()

    @param.depends('period', watch=True)
    def _update_period(self):
        if self._cb:
            self.stop()
            self.start()

    def _periodic_callback(self):
        self.callback()
        self._counter += 1
        if self._timeout is not None:
            dt = (time.time() - self._start_time)
            if dt > self._timeout:
                self.stop()
        if self._counter == self.count:
            self.stop()

    def stop(self):
        self._counter = 0
        self._timeout = None
        if self._doc:
            self._doc.remove_periodic_callback(self._cb)
        else:
            self._cb.stop()
        self._cb = None
Beispiel #14
0
class PointPlot(ChartPlot, ColorbarPlot):
    """
    Note that the 'cmap', 'vmin' and 'vmax' style arguments control
    how point magnitudes are rendered to different colors.
    """

    show_grid = param.Boolean(default=False, doc="""
      Whether to draw grid lines at the tick positions.""")

    # Deprecated parameters

    color_index = param.ClassSelector(default=None, class_=(basestring, int),
                                      allow_None=True, doc="""
        Deprecated in favor of color style mapping, e.g. `color=dim('color')`""")

    size_index = param.ClassSelector(default=None, class_=(basestring, int),
                                     allow_None=True, doc="""
        Deprecated in favor of size style mapping, e.g. `size=dim('size')`""")

    scaling_method = param.ObjectSelector(default="area",
                                          objects=["width", "area"],
                                          doc="""
        Deprecated in favor of size style mapping, e.g.
        size=dim('size')**2.""")

    scaling_factor = param.Number(default=1, bounds=(0, None), doc="""
      Scaling factor which is applied to either the width or area
      of each point, depending on the value of `scaling_method`.""")

    size_fn = param.Callable(default=np.abs, doc="""
      Function applied to size values before applying scaling,
      to remove values lower than zero.""")

    style_opts = ['alpha', 'color', 'edgecolors', 'facecolors',
                  'linewidth', 'marker', 'size', 'visible',
                  'cmap', 'vmin', 'vmax', 'norm']

    _nonvectorized_styles = ['alpha', 'marker', 'cmap', 'vmin', 'vmax',
                      'norm', 'visible']

    _disabled_opts = ['size']
    _plot_methods = dict(single='scatter')

    def get_data(self, element, ranges, style):
        xs, ys = (element.dimension_values(i) for i in range(2))
        self._compute_styles(element, ranges, style)
        with abbreviated_exception():
            style = self._apply_transforms(element, ranges, style)
        return (ys, xs) if self.invert_axes else (xs, ys), style, {}


    def _compute_styles(self, element, ranges, style):
        cdim = element.get_dimension(self.color_index)
        color = style.pop('color', None)
        cmap = style.get('cmap', None)

        if cdim and ((isinstance(color, basestring) 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 and cmap:
            cs = element.dimension_values(self.color_index)
            # Check if numeric otherwise treat as categorical
            if cs.dtype.kind in 'uif':
                style['c'] = cs
            else:
                style['c'] = search_indices(cs, unique_array(cs))
            self._norm_kwargs(element, ranges, style, cdim)
        elif color is not None:
            style['color'] = color
        style['edgecolors'] = style.pop('edgecolors', style.pop('edgecolor', 'none'))

        ms = style.get('s', mpl.rcParams['lines.markersize'])
        sdim = element.get_dimension(self.size_index)
        if sdim and ((isinstance(ms, basestring) and ms in element) or isinstance(ms, dim)):
            self.param.warning(
                "Cannot declare style mapping for 's' option and "
                "declare a size_index; ignoring the size_index.")
            sdim = None
        if sdim:
            sizes = element.dimension_values(self.size_index)
            sizes = compute_sizes(sizes, self.size_fn, self.scaling_factor,
                                  self.scaling_method, ms)
            if sizes is None:
                eltype = type(element).__name__
                self.param.warning(
                    '%s dimension is not numeric, cannot use to '
                    'scale %s size.' % (sdim.pprint_label, eltype))
            else:
                style['s'] = sizes
        style['edgecolors'] = style.pop('edgecolors', 'none')


    def update_handles(self, key, axis, element, ranges, style):
        paths = self.handles['artist']
        (xs, ys), style, _ = self.get_data(element, ranges, style)
        xdim, ydim = element.dimensions()[:2]
        if 'factors' in ranges.get(xdim.name, {}):
            factors = list(ranges[xdim.name]['factors'])
            xs = [factors.index(x) for x in xs if x in factors]
        if 'factors' in ranges.get(ydim.name, {}):
            factors = list(ranges[ydim.name]['factors'])
            ys = [factors.index(y) for y in ys if y in factors]
        paths.set_offsets(np.column_stack([xs, ys]))
        if 's' in style:
            sizes = style['s']
            if isscalar(sizes):
                sizes = [sizes]
            paths.set_sizes(sizes)
        if 'vmin' in style:
            paths.set_clim((style['vmin'], style['vmax']))
        if 'c' in style:
            paths.set_array(style['c'])
        if 'norm' in style:
            paths.norm = style['norm']
        if 'linewidth' in style:
            paths.set_linewidths(style['linewidth'])
        if 'edgecolors' in style:
            paths.set_edgecolors(style['edgecolors'])
        if 'facecolors' in style:
            paths.set_edgecolors(style['facecolors'])
Beispiel #15
0
class PeriodicCallback(param.Parameterized):
    """
    Periodic encapsulates a periodic callback which will run both
    in tornado based notebook environments and on bokeh server. By
    default the callback will run until the stop method is called,
    but count and timeout values can be set to limit the number of
    executions or the maximum length of time for which the callback
    will run. The callback may also be started and stopped by setting
    the running parameter to True or False respectively.
    """

    callback = param.Callable(doc="""
        The callback to execute periodically.""")

    count = param.Integer(default=None, doc="""
        Number of times the callback will be executed, by default
        this is unlimited.""")

    period = param.Integer(default=500, doc="""
        Period in milliseconds at which the callback is executed.""")

    timeout = param.Integer(default=None, doc="""
        Timeout in milliseconds from the start time at which the callback
        expires.""")

    running = param.Boolean(default=False, doc="""
        Toggles whether the periodic callback is currently running.""")

    def __init__(self, **params):
        super().__init__(**params)
        self._counter = 0
        self._start_time = None
        self._cb = None
        self._updating = False
        self._doc = None

    @param.depends('running', watch=True)
    def _start(self):
        if not self.running or self._updating:
            return
        self.start()

    @param.depends('running', watch=True)
    def _stop(self):
        if self.running or self._updating:
            return
        self.stop()

    @param.depends('period', watch=True)
    def _update_period(self):
        if self._cb:
            self.stop()
            self.start()

    def _periodic_callback(self):
        with edit_readonly(state):
            state.busy = True
        try:
            self.callback()
        finally:
            with edit_readonly(state):
                state.busy = False
        self._counter += 1
        if self.timeout is not None:
            dt = (time.time() - self._start_time) * 1000
            if dt > self.timeout:
                self.stop()
        if self._counter == self.count:
            self.stop()

    @property
    def counter(self):
        """
        Returns the execution count of the periodic callback.
        """
        return self._counter

    def _cleanup(self, session_context):
        self.stop()

    def start(self):
        """
        Starts running the periodic callback.
        """
        if self._cb is not None:
            raise RuntimeError('Periodic callback has already started.')
        if not self.running:
            try:
                self._updating = True
                self.running = True
            finally:
                self._updating = False
        self._start_time = time.time()
        if state.curdoc:
            self._doc = state.curdoc
            self._cb = self._doc.add_periodic_callback(self._periodic_callback, self.period)
        else:
            from tornado.ioloop import PeriodicCallback
            self._cb = PeriodicCallback(self._periodic_callback, self.period)
            self._cb.start()
        try:
            state.on_session_destroyed(self._cleanup)
        except Exception:
            pass

    def stop(self):
        """
        Stops running the periodic callback.
        """
        if self.running:
            try:
                self._updating = True
                self.running = False
            finally:
                self._updating = False
        self._counter = 0
        self._timeout = None
        if self._doc:
            self._doc.remove_periodic_callback(self._cb)
        elif self._cb:
            self._cb.stop()
        self._cb = None
        doc = self._doc or _curdoc()
        if doc:
            doc.session_destroyed_callbacks = {
                cb for cb in doc.session_destroyed_callbacks
                if cb is not self._cleanup
            }
            self._doc = None
Beispiel #16
0
class SaccadeController(CFSheet):
    """
    Sheet that decodes activity on CFProjections into a saccade command.

    This class accepts CF-projected input and computes its activity
    like a normal CFSheet, then decodes that activity into a saccade
    amplitude and direction as would be specified by activity in the
    superior colliculi.  The X dimension of activity corresponds to
    amplitude, the Y dimension to direction.  The activity is decoded
    to a single (x,y) point according to the parameter decode_method.

    From this (x,y) point an (amplitude,direction) pair, specified in
    degrees, is computed using the parameters amplitude_scale and
    direction scale.  That pair is then sent out on the 'Saccade'
    output port.

    NOTE: Non-linear mappings for saccade commands, as in Ottes, et
    al (below), are assumed to be provided using the coord_mapperg
    parameter of the incoming CFProjection.

    References:
    Ottes, van Gisbergen, Egglermont. 1986.  Visuomotor fields of the
    superior colliculus: a quantitative model.  Vision Research;
    26(6): 857-73.
    """

    # JPALERT: amplitude_scale and direction scale can be implemented as
    # part of self.command_mapper, so these should probably be removed.

    amplitude_scale = param.Number(default=120,
                                   doc="""
        Scale factor for saccade command amplitude, expressed in
        degrees per unit of sheet.  Indicates how large a saccade is
        represented by the x-component of the command input.""")

    direction_scale = param.Number(default=180,
                                   doc="""
        Scale factor for saccade command direction, expressed in
        degrees per unit of sheet.  Indicates what direction of saccade
        is represented by the y-component of the command input.""")

    decode_fn = param.Callable(default=activity_centroid,
                               instantiate=False,
                               doc="""
        The function for extracting a single point from sheet activity.
        Should take a sheet as the first argument, and return (x,y).""")

    command_mapper = param.ClassSelector(CoordinateMapperFn,
                                         default=IdentityMF(),
                                         doc="""
        A CoordinateMapperFn that will be applied to the command vector extracted
        from the sheet activity.""")

    src_ports = ['Activity', 'Saccade']

    def activate(self):
        super(SaccadeController, self).activate()

        # get the input projection activity
        # decode the input projection activity as a command
        xa, ya = self.decode_fn(self)
        self.verbose("Saccade command centroid = (%.2f,%.2f)" % (xa, ya))

        xa, ya = self.command_mapper(xa, ya)

        amplitude = xa * self.amplitude_scale
        direction = ya * self.direction_scale

        self.verbose("Saccade amplitute = %.2f." % amplitude)
        self.verbose("Saccade direction = %.2f." % direction)

        self.send_output(src_port='Saccade', data=(amplitude, direction))
class PatternSampler(ImageSampler):
    """
    When called, resamples - according to the size_normalization
    parameter - an image at the supplied (x,y) sheet coordinates.

    (x,y) coordinates outside the image are returned as the background
    value.
    """
    whole_pattern_output_fns = param.HookList(class_=TransferFn,default=[],doc="""
        Functions to apply to the whole image before any sampling is done.""")

    background_value_fn = param.Callable(default=None,doc="""
        Function to compute an appropriate background value. Must accept
        an array and return a scalar.""")

    size_normalization = param.ObjectSelector(default='original',
        objects=['original','stretch_to_fit','fit_shortest','fit_longest'],
        doc="""
        Determines how the pattern is scaled initially, relative to the
        default retinal dimension of 1.0 in sheet coordinates:

        'stretch_to_fit': scale both dimensions of the pattern so they
        would fill a Sheet with bounds=BoundingBox(radius=0.5) (disregards
        the original's aspect ratio).

        'fit_shortest': scale the pattern so that its shortest dimension
        is made to fill the corresponding dimension on a Sheet with
        bounds=BoundingBox(radius=0.5) (maintains the original's aspect
        ratio, filling the entire bounding box).

        'fit_longest': scale the pattern so that its longest dimension is
        made to fill the corresponding dimension on a Sheet with
        bounds=BoundingBox(radius=0.5) (maintains the original's
        aspect ratio, fitting the image into the bounding box but not
        necessarily filling it).

        'original': no scaling is applied; each pixel of the pattern
        corresponds to one matrix unit of the Sheet on which the
        pattern being displayed.""")

    def _get_image(self):
        return self.scs.activity

    def _set_image(self,image):
        # Stores a SheetCoordinateSystem with an activity matrix
        # representing the image
        if not isinstance(image,numpy.ndarray):
            image = array(image,Float)

        rows,cols = image.shape
        self.scs = SheetCoordinateSystem(xdensity=1.0,ydensity=1.0,
                                         bounds=BoundingBox(points=((-cols/2.0,-rows/2.0),
                                                                    ( cols/2.0, rows/2.0))))
        self.scs.activity=image

    def _del_image(self):
        self.scs = None


    def __call__(self, image, x, y, sheet_xdensity, sheet_ydensity, width=1.0, height=1.0):
        """
        Return pixels from the supplied image at the given Sheet (x,y)
        coordinates.

        The image is assumed to be a NumPy array or other object that
        exports the NumPy buffer interface (i.e. can be converted to a
        NumPy array by passing it to numpy.array(), e.g. Image.Image).
        The whole_pattern_output_fns are applied to the image before
        any sampling is done.

        To calculate the sample, the image is scaled according to the
        size_normalization parameter, and any supplied width and
        height. sheet_xdensity and sheet_ydensity are the xdensity and
        ydensity of the sheet on which the pattern is to be drawn.
        """
        # CEB: could allow image=None in args and have 'if image is
        # not None: self.image=image' here to avoid re-initializing the
        # image.
        self.image=image

        for wpof in self.whole_pattern_output_fns:
            wpof(self.image)
        if not self.background_value_fn:
            self.background_value = 0.0
        else:
            self.background_value = self.background_value_fn(self.image)

        pattern_rows,pattern_cols = self.image.shape

        if width==0 or height==0 or pattern_cols==0 or pattern_rows==0:
            return ones(x.shape, Float)*self.background_value

        # scale the supplied coordinates to match the pattern being at density=1
        x=x*sheet_xdensity # deliberately don't operate in place (so as not to change supplied x & y)
        y=y*sheet_ydensity

        # scale according to initial pattern size_normalization selected (size_normalization)
        self.__apply_size_normalization(x,y,sheet_xdensity,sheet_ydensity,self.size_normalization)

        # scale according to user-specified width and height
        x/=width
        y/=height

        # now sample pattern at the (r,c) corresponding to the supplied (x,y)
        r,c = self.scs.sheet2matrixidx(x,y)
        # (where(cond,x,y) evaluates x whether cond is True or False)
        r.clip(0,pattern_rows-1,out=r)
        c.clip(0,pattern_cols-1,out=c)
        left,bottom,right,top = self.scs.bounds.lbrt()
        return numpy.where((x>=left) & (x<right) & (y>bottom) & (y<=top),
                           self.image[r,c],
                           self.background_value)


    def __apply_size_normalization(self,x,y,sheet_xdensity,sheet_ydensity,size_normalization):
        pattern_rows,pattern_cols = self.image.shape

        # Instead of an if-test, could have a class of this type of
        # function (c.f. OutputFunctions, etc)...
        if size_normalization=='original':
            return

        elif size_normalization=='stretch_to_fit':
            x_sf,y_sf = pattern_cols/sheet_xdensity, pattern_rows/sheet_ydensity
            x*=x_sf; y*=y_sf

        elif size_normalization=='fit_shortest':
            if pattern_rows<pattern_cols:
                sf = pattern_rows/sheet_ydensity
            else:
                sf = pattern_cols/sheet_xdensity
            x*=sf;y*=sf

        elif size_normalization=='fit_longest':
            if pattern_rows<pattern_cols:
                sf = pattern_cols/sheet_xdensity
            else:
                sf = pattern_rows/sheet_ydensity
            x*=sf;y*=sf
Beispiel #18
0
class measure_cog(ParameterizedFunction):
    """
    Calculate center of gravity (CoG) for each CF of each unit in each CFSheet.

    Unlike measure_position_pref and other measure commands, this one
    does not work by collating the responses to a set of input patterns.
    Instead, the CoG is calculated directly from each set of incoming
    weights.  The CoG value thus is an indirect estimate of what
    patterns the neuron will prefer, but is not limited by the finite
    number of test patterns as the other measure commands are.

    Measures only one projection for each sheet, as specified by the
    proj_name parameter.  The default proj_name of '' selects the
    first non-self connection, which is usually useful to examine for
    simple feedforward networks, but will not necessarily be useful in
    other cases.
    """

    proj_name = param.String(default='',
                             doc="""
        Name of the projection to measure; the empty string means 'the first
        non-self connection available'.""")

    stride = param.Integer(default=1,
                           doc="Stride by which to skip grid lines"
                           "in the CoG Wireframe.")

    measurement_storage_hook = param.Callable(default=None,
                                              instantiate=True,
                                              doc="""
        Interface to store measurements after they have been completed.""")

    def __call__(self, **params):
        p = ParamOverrides(self, params)

        measured_sheets = [
            s for s in topo.sim.objects(CFSheet).values()
            if hasattr(s, 'measure_maps') and s.measure_maps
        ]

        results = AttrTree()

        # Could easily be extended to measure CoG of all projections
        # and e.g. register them using different names (e.g. "Afferent
        # XCoG"), but then it's not clear how the PlotGroup would be
        # able to find them automatically (as it currently supports
        # only a fixed-named plot).
        requested_proj = p.proj_name
        for sheet in measured_sheets:
            for proj in sheet.in_connections:
                if (proj.name == requested_proj) or \
                   (requested_proj == '' and (proj.src != sheet)):
                    cog_data = self._update_proj_cog(p, proj)
                    for key, data in cog_data.items():
                        name = proj.name[0].upper() + proj.name[1:]
                        results.set_path((key, name), data)

        if p.measurement_storage_hook:
            p.measurement_storage_hook(results)

        return results

    def _update_proj_cog(self, p, proj):
        """Measure the CoG of the specified projection and register corresponding SheetViews."""

        sheet = proj.dest
        rows, cols = sheet.activity.shape
        xcog = np.zeros((rows, cols), np.float64)
        ycog = np.zeros((rows, cols), np.float64)

        for r in xrange(rows):
            for c in xrange(cols):
                cf = proj.cfs[r, c]
                r1, r2, c1, c2 = cf.input_sheet_slice
                row_centroid, col_centroid = centroid(cf.weights)
                xcentroid, ycentroid = proj.src.matrix2sheet(
                    r1 + row_centroid + 0.5, c1 + col_centroid + 0.5)

                xcog[r][c] = xcentroid
                ycog[r][c] = ycentroid

        metadata = AttrDict(precedence=sheet.precedence,
                            row_precedence=sheet.row_precedence,
                            src_name=sheet.name)

        timestamp = topo.sim.time()
        xsv = Matrix(xcog,
                     sheet.bounds,
                     label='X CoG',
                     title='%s {label}' % proj.name)
        ysv = Matrix(ycog,
                     sheet.bounds,
                     label='Y CoG',
                     title='%s {label}' % proj.name)

        lines = []
        hlines, vlines = xsv.data.shape
        for hind in range(hlines)[::p.stride]:
            lines.append(np.vstack([xsv.data[hind, :].T, ysv.data[hind, :]]).T)
        for vind in range(vlines)[::p.stride]:
            lines.append(np.vstack([xsv.data[:, vind].T, ysv.data[:, vind]]).T)
        cogmesh = Contours(lines,
                           lbrt=sheet.bounds.lbrt(),
                           label='Center of Gravity',
                           title='%s {label}' % proj.name)

        xcog_map = ViewMap((timestamp, xsv), dimensions=[features.Time])
        xcog_map.metadata = metadata
        ycog_map = ViewMap((timestamp, ysv), dimensions=[features.Time])
        ycog_map.metadata = metadata

        contour_map = ViewMap((timestamp, cogmesh), dimensions=[features.Time])
        contour_map.metadata = metadata

        return {'XCoG': xcog_map, 'YCoG': ycog_map, 'CoG': contour_map}
Beispiel #19
0
class Exporter(param.ParameterizedFunction):
    """
    An Exporter is a parameterized function that accepts a HoloViews
    object and converts it to a new some new format. This mechanism is
    designed to be very general so here are a few examples:

    Pickling:   Native Python, supported by HoloViews.
    Rendering:  Any plotting backend may be used (default uses matplotlib)
    Storage:    Saving to a database (e.g SQL), HDF5 etc.
    """

    # Mime-types that need encoding as utf-8 upon export
    utf8_mime_types = ['image/svg+xml', 'text/html', 'text/json']

    key_fn = param.Callable(doc="""
      Function that generates the metadata key from the HoloViews
      object being saved. The metadata key is a single
      high-dimensional key of values associated with dimension labels.

      The returned dictionary must have string keys and simple
      literals that may be conviently used for dictionary-style
      indexing. Returns an empty dictionary by default.""")

    info_fn = param.Callable(lambda x: {'repr': repr(x)},
                             doc="""
      Function that generates additional metadata information from the
      HoloViews object being saved.

      Unlike metadata keys, the information returned may be unsuitable
      for use as a key index and may include entries such as the
      object's repr. Regardless, the info metadata should still only
      contain items that will be quick to load and inspect. """)

    @classmethod
    def encode(cls, entry):
        """
        Classmethod that applies conditional encoding based on
        mime-type. Given an entry as returned by __call__ return the
        data in the appropriate encoding.
        """
        (data, info) = entry
        if info['mime_type'] in cls.utf8_mime_types:
            return data.encode('utf-8')
        else:
            return data

    @bothmethod
    def _filename(self_or_cls, filename):
        "Add the file extension if not already present"
        if not filename.endswith(self_or_cls.file_ext):
            return '%s.%s' % (filename, self_or_cls.file_ext)
        else:
            return filename

    @bothmethod
    def _merge_metadata(self_or_cls, obj, fn, *dicts):
        """
        Returns a merged metadata info dictionary from the supplied
        function and additional dictionaries
        """
        merged = dict([(k, v) for d in dicts for (k, v) in d.items()])
        return dict(merged, **fn(obj)) if fn else merged

    def __call__(self, obj, fmt=None):
        """
        Given a HoloViews object return the raw exported data and
        corresponding metadata as the tuple (data, metadata). The
        metadata should include:

        'file-ext' : The file extension if applicable (else empty string)
        'mime_type': The mime-type of the data.

        The fmt argument may be used with exporters that support multiple
        output formats. If not supplied, the exporter is to pick an
        appropriate format automatically.
        """
        raise NotImplementedError("Exporter not implemented.")

    @bothmethod
    def save(self_or_cls, obj, basename, fmt=None, key={}, info={}, **kwargs):
        """
        Similar to the call method except saves exporter data to disk
        into a file with specified basename. For exporters that
        support multiple formats, the fmt argument may also be
        supplied (which typically corresponds to the file-extension).

        The supplied metadata key and info dictionaries will be used
        to update the output of the relevant key and info functions
        which is then saved (if supported).
        """
        raise NotImplementedError("Exporter save method not implemented.")
Beispiel #20
0
class Renderer(Exporter):
    """
    The job of a Renderer is to turn the plotting state held within
    Plot classes into concrete, visual output in the form of the PNG,
    SVG, MP4 or WebM formats (among others). Note that a Renderer is a
    type of Exporter and must therefore follow the Exporter interface.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    # Plot registry
    _plots = {}

    # Whether to render plots with Panel
    _render_with_panel = False

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


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

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


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

        # Initialize DynamicMaps with first data item
        initialize_dynamic(obj)

        if not isinstance(obj, Plot):
            if not displayable(obj):
                obj = collate(obj)
                initialize_dynamic(obj)
            obj = Compositor.map(obj, mode='data', backend=self_or_cls.backend)

        if not renderer:
            renderer = self_or_cls
            if not isinstance(self_or_cls, Renderer):
                renderer = self_or_cls.instance()
        if not isinstance(obj, Plot):
            obj = Layout.from_values(obj) if isinstance(obj, AdjointLayout) else obj
            plot_opts = dict(self_or_cls.plot_options(obj, self_or_cls.size),
                             **kwargs)
            plot = self_or_cls.plotting_class(obj)(obj, renderer=renderer,
                                                   **plot_opts)
            defaults = [kd.default for kd in plot.dimensions]
            init_key = tuple(v if d is None else d for v, d in
                             zip(plot.keys[0], defaults))
            plot.update(init_key)
        else:
            plot = obj

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

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


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

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

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

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

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

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


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


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

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

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

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


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

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

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


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


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

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


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


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


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


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


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


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

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

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


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

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

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

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

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


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

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

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


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


    @classmethod
    def load_nb(cls, inline=True):
        """
        Loads any resources required for display of plots
        in the Jupyter notebook
        """
        load_notebook(inline)
        with param.logging_level('ERROR'):
            cls.notebook_context = True
            cls.comm_manager = JupyterCommManager


    @classmethod
    def _delete_plot(cls, plot_id):
        """
        Deletes registered plots and calls Plot.cleanup
        """
        plot = cls._plots.get(plot_id)
        if plot is None:
            return
        plot.cleanup()
        del cls._plots[plot_id]
Beispiel #21
0
class Widgets(param.ParameterizedFunction):

    callback = param.Callable(default=None, doc="""
        Custom callable to execute on button press
        (if `button`) else whenever a widget is changed,
        Should accept a Parameterized object argument.""")

    view_position = param.ObjectSelector(default='below',
                                         objects=['below', 'right', 'left', 'above'],
                                         doc="""
        Layout position of any View parameter widgets.""")

    next_n = param.Parameter(default=0, doc="""
        When executing cells, integer number to execute (or 'all').
        A value of zero means not to control cell execution.""")

    on_init = param.Boolean(default=False, doc="""
        Whether to do the action normally taken (executing cells
        and/or calling a callable) when first instantiating this
        object.""")

    close_button = param.Boolean(default=False, doc="""
        Whether to show a button allowing the Widgets to be closed.""")

    button = param.Boolean(default=False, doc="""
        Whether to show a button to control cell execution.
        If false, will execute `next` cells on any widget
        value change.""")

    label_width = param.Parameter(default=estimate_label_width, doc="""
        Width of the description for parameters in the list, using any
        string specification accepted by CSS (e.g. "100px" or "50%").
        If set to a callable, will call that function using the list of
        all labels to get the value.""")

    tooltips = param.Boolean(default=True, doc="""
        Whether to add tooltips to the parameter names to show their
        docstrings.""")

    show_labels = param.Boolean(default=True)

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

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

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

    layout = param.ObjectSelector(default='column',
                                  objects=['row','column'],doc="""
        Whether to lay out the buttons as a row or a column.""")

    continuous_update = param.Boolean(default=False, doc="""
        If true, will continuously update the next_n and/or callback,
        if any, as a slider widget is dragged.""")

    def __call__(self, parameterized, **params):
        self.p = param.ParamOverrides(self, params)
        if self.p.initializer:
            self.p.initializer(parameterized)

        self._widgets = {}
        self.parameterized = parameterized

        widgets, views = self.widgets()
        layout = ipywidgets.Layout(display='flex', flex_flow=self.p.layout)
        if self.p.close_button:
            layout.border = 'solid 1px'

        widget_box = ipywidgets.VBox(children=widgets, layout=layout)
        if views:
            view_box = ipywidgets.VBox(children=views, layout=layout)
            layout = self.p.view_position
            if layout in ['below', 'right']:
                children = [widget_box, view_box]
            else:
                children = [view_box, widget_box]
            box = ipywidgets.VBox if layout in ['below', 'above'] else ipywidgets.HBox
            widget_box = box(children=children)

        display(Javascript(WIDGET_JS))
        display(widget_box)
        self._widget_box = widget_box

        for view in views:
            p_obj = self.parameterized.params(view.name)
            value = getattr(self.parameterized, view.name)
            if value is not None:
                self._update_trait(view.name, p_obj.renderer(value))

        # Keeps track of changes between button presses
        self._changed = {}

        if self.p.on_init:
            self.execute()


    def _update_trait(self, p_name, p_value, widget=None):
        p_obj = self.parameterized.params(p_name)
        widget = self._widgets[p_name] if widget is None else widget
        if isinstance(p_value, tuple):
            p_value, size = p_value
            if isinstance(widget, ipywidgets.Image):
                widget.width = size[0]
                widget.height = size[1]
            elif isinstance(size, tuple) and len(size) == 2:
                widget.layout.min_width = '%dpx' % size[0]
                widget.layout.min_height = '%dpx' % size[1]
        widget.value = p_value


    def _make_widget(self, p_name):
        p_obj = self.parameterized.params(p_name)
        widget_class = wtype(p_obj)

        value = getattr(self.parameterized, p_name)

        # For ObjectSelector, pick first from objects if no default;
        # see https://github.com/ioam/param/issues/164
        if hasattr(p_obj,'objects') and len(p_obj.objects)>0 and value is None:
            value = p_obj.objects[0]
            if isinstance(p_obj,param.ListSelector):
                value = [value]
            setattr(self.parameterized, p_name, value)

        kw = dict(value=value)
        if p_obj.doc:
            kw['tooltip'] = p_obj.doc

        if isinstance(p_obj, param.Action):
            def action_cb(button):
                getattr(self.parameterized, p_name)(self.parameterized)
            kw['value'] = action_cb

        kw['name'] = p_name

        kw['continuous_update']=self.p.continuous_update

        if hasattr(p_obj, 'callbacks'):
            kw.pop('value', None)

        if hasattr(p_obj, 'get_range'):
            kw['options'] = named_objs(p_obj.get_range().items())

        if hasattr(p_obj, 'get_soft_bounds'):
            kw['min'], kw['max'] = p_obj.get_soft_bounds()

        if hasattr(p_obj,'is_instance') and p_obj.is_instance:
            kw['options'][kw['value'].__class__.__name__]=kw['value']

        w = widget_class(**kw)

        if hasattr(p_obj, 'callbacks') and value is not None:
            self._update_trait(p_name, p_obj.renderer(value), w)

        def change_event(event):
            new_values = event['new']
            error = False
            # Apply literal evaluation to values
            if (isinstance(w, ipywidgets.Text) and isinstance(p_obj, literal_params)):
                try:
                    new_values = ast.literal_eval(new_values)
                except:
                    error = 'eval'
            elif hasattr(p_obj,'is_instance') and p_obj.is_instance and isinstance(new_values,type):
                # results in new instance each time non-default option
                # is selected; could consider caching.
                try:
                    # awkward: support ParameterizedFunction
                    new_values = new_values.instance() if hasattr(new_values,'instance') else new_values()
                except:
                    error = 'instantiate'

            # If no error during evaluation try to set parameter
            if not error:
                try:
                    setattr(self.parameterized, p_name, new_values)
                except ValueError:
                    error = 'validation'

            # Style widget to denote error state
            apply_error_style(w, error)

            if not error and not self.p.button:
                self.execute({p_name: new_values})
            else:
                self._changed[p_name] = new_values

        if hasattr(p_obj, 'callbacks'):
            p_obj.callbacks[id(self.parameterized)] = functools.partial(self._update_trait, p_name)
        else:
            w.observe(change_event, 'value')

        # Hack ; should be part of Widget classes
        if hasattr(p_obj,"path"):
            def path_change_event(event):
                new_values = event['new']
                p_obj = self.parameterized.params(p_name)
                p_obj.path = new_values
                p_obj.update()

                # Update default value in widget, ensuring it's always a legal option
                selector = self._widgets[p_name].children[1]
                defaults = p_obj.default
                if not issubclass(type(defaults),list):
                    defaults = [defaults]
                selector.options.update(named_objs(zip(defaults,defaults)))
                selector.value=p_obj.default
                selector.options=named_objs(p_obj.get_range().items())

                if p_obj.objects and not self.p.button:
                    self.execute({p_name:selector.value})

            path_w = ipywidgets.Text(value=p_obj.path)
            path_w.observe(path_change_event, 'value')
            w = ipywidgets.VBox(children=[path_w,w],
                                layout=ipywidgets.Layout(margin='0'))

        return w


    def widget(self, param_name):
        """Get widget for param_name"""
        if param_name not in self._widgets:
            self._widgets[param_name] = self._make_widget(param_name)
        return self._widgets[param_name]


    def execute(self, changed={}):
        run_next_cells(self.p.next_n)
        if self.p.callback is not None:
            if get_method_owner(self.p.callback) is self.parameterized:
                self.p.callback(**changed)
            else:
                self.p.callback(self.parameterized, **changed)


    # Define some settings :)
    preamble = """
        <style>
          .widget-dropdown .dropdown-menu { width: 100% }
          .widget-select-multiple select { min-height: 100px; min-width: 300px;}
        </style>
        """

    label_format = """<div title="{2}" style="padding: 5px; width: {0};
                      text-align: right;">{1}</div>"""

    def helptip(self,obj):
        """Return HTML code formatting a tooltip if help is available"""
        helptext = obj.__doc__
        return "" if (not self.p.tooltips or not helptext) else helptext


    def widgets(self):
        """Return name,widget boxes for all parameters (i.e., a property sheet)"""

        params = self.parameterized.params().items()
        key_fn = lambda x: x[1].precedence if x[1].precedence is not None else self.p.default_precedence
        sorted_precedence = sorted(params, key=key_fn)
        outputs = [k for k, p in sorted_precedence if isinstance(p, _View)]
        filtered = [(k,p) for (k,p) in sorted_precedence
                    if ((p.precedence is None) or (p.precedence >= self.p.display_threshold))
                    and k not in outputs]
        groups = itertools.groupby(filtered, key=key_fn)
        sorted_groups = [sorted(grp) for (k,grp) in groups]
        ordered_params = [el[0] for group in sorted_groups for el in group]

        # Format name specially
        name = ordered_params.pop(ordered_params.index('name'))
        widgets = [ipywidgets.HTML(self.preamble +
            '<div class="ttip"><b>{0}</b>'.format(self.parameterized.name)+"</div>")]

        label_width=self.p.label_width
        if callable(label_width):
            label_width = label_width(self.parameterized.params().keys())

        def format_name(pname):
            p = self.parameterized.params(pname)
            # omit name for buttons, which already show the name on the button
            name = "" if issubclass(type(p),param.Action) else pname
            return ipywidgets.HTML(self.label_format.format(label_width, name, self.helptip(p)))

        if self.p.show_labels:
            widgets += [ipywidgets.HBox(children=[format_name(pname),self.widget(pname)])
                        for pname in ordered_params]
        else:
            widgets += [self.widget(pname) for pname in ordered_params]

        if self.p.close_button:
            close_button = ipywidgets.Button(description="Close")
            # TODO: what other cleanup should be done?
            close_button.on_click(lambda _: self._widget_box.close())
            widgets.append(close_button)


        if self.p.button and not (self.p.callback is None and self.p.next_n==0):
            label = 'Run %s' % self.p.next_n if self.p.next_n != 'all' else "Run"
            display_button = ipywidgets.Button(description=label)
            def click_cb(button):
                # Execute and clear changes since last button press
                try:
                    self.execute(self._changed)
                except Exception as e:
                    self._changed.clear()
                    raise e
                self._changed.clear()
            display_button.on_click(click_cb)
            widgets.append(display_button)

        outputs = [self.widget(pname) for pname in outputs]
        return widgets, outputs
Beispiel #22
0
class PointPlot(LegendPlot, ColorbarPlot):

    jitter = param.Number(default=None,
                          bounds=(0, None),
                          doc="""
      The amount of jitter to apply to offset the points along the x-axis.""")

    # Deprecated parameters

    color_index = param.ClassSelector(default=None,
                                      class_=(basestring, int),
                                      allow_None=True,
                                      doc="""
        Deprecated in favor of color style mapping, e.g. `color=dim('color')`"""
                                      )

    size_index = param.ClassSelector(default=None,
                                     class_=(basestring, int),
                                     allow_None=True,
                                     doc="""
        Deprecated in favor of size style mapping, e.g. `size=dim('size')`""")

    scaling_method = param.ObjectSelector(default="area",
                                          objects=["width", "area"],
                                          doc="""
        Deprecated in favor of size style mapping, e.g.
        size=dim('size')**2.""")

    scaling_factor = param.Number(default=1,
                                  bounds=(0, None),
                                  doc="""
      Scaling factor which is applied to either the width or area
      of each point, depending on the value of `scaling_method`.""")

    size_fn = param.Callable(default=np.abs,
                             doc="""
      Function applied to size values before applying scaling,
      to remove values lower than zero.""")

    style_opts = (['cmap', 'palette', 'marker', 'size', 'angle'] +
                  line_properties + fill_properties)

    _plot_methods = dict(single='scatter', batched='scatter')
    _batched_style_opts = line_properties + fill_properties + ['size']

    def _get_size_data(self, element, ranges, style):
        data, mapping = {}, {}
        sdim = element.get_dimension(self.size_index)
        ms = style.get('size', np.sqrt(6))
        if sdim and ((isinstance(ms, basestring) and ms in element)
                     or isinstance(ms, dim)):
            self.warning("Cannot declare style mapping for 'size' option "
                         "and declare a size_index; ignoring the size_index.")
            sdim = None
        if not sdim or self.static_source:
            return data, mapping

        map_key = 'size_' + sdim.name
        ms = ms**2
        sizes = element.dimension_values(self.size_index)
        sizes = compute_sizes(sizes, self.size_fn, self.scaling_factor,
                              self.scaling_method, ms)
        if sizes is None:
            eltype = type(element).__name__
            self.warning('%s dimension is not numeric, cannot '
                         'use to scale %s size.' % (sdim.pprint_label, eltype))
        else:
            data[map_key] = np.sqrt(sizes)
            mapping['size'] = map_key
        return data, mapping

    def get_data(self, element, ranges, style):
        dims = element.dimensions(label=True)

        xidx, yidx = (1, 0) if self.invert_axes else (0, 1)
        mapping = dict(x=dims[xidx], y=dims[yidx])
        data = {}

        if not self.static_source or self.batched:
            xdim, ydim = dims[xidx], dims[yidx]
            data[xdim] = element.dimension_values(xidx)
            data[ydim] = element.dimension_values(yidx)
            self._categorize_data(data, (xdim, ydim), element.dimensions())

        cdata, cmapping = self._get_color_data(element, ranges, style)
        data.update(cdata)
        mapping.update(cmapping)

        sdata, smapping = self._get_size_data(element, ranges, style)
        data.update(sdata)
        mapping.update(smapping)

        if 'angle' in style and isinstance(style['angle'], (int, float)):
            style['angle'] = np.deg2rad(style['angle'])

        if self.jitter:
            axrange = 'y_range' if self.invert_axes else 'x_range'
            mapping['x'] = jitter(dims[xidx],
                                  self.jitter,
                                  range=self.handles[axrange])

        self._get_hover_data(data, element)
        return data, mapping, style

    def get_batched_data(self, element, ranges):
        data = defaultdict(list)
        zorders = self._updated_zorders(element)
        for (key, el), zorder in zip(element.data.items(), zorders):
            self.set_param(**self.lookup_options(el, 'plot').options)
            style = self.lookup_options(element.last, 'style')
            style = style.max_cycles(len(self.ordering))[zorder]
            eldata, elmapping, style = self.get_data(el, ranges, style)
            for k, eld in eldata.items():
                data[k].append(eld)

            # Skip if data is empty
            if not eldata:
                continue

            # Apply static styles
            nvals = len(list(eldata.values())[0])
            sdata, smapping = expand_batched_style(style,
                                                   self._batched_style_opts,
                                                   elmapping, nvals)
            elmapping.update(smapping)
            for k, v in sdata.items():
                data[k].append(v)

            if 'hover' in self.handles:
                for d, k in zip(element.dimensions(), key):
                    sanitized = dimension_sanitizer(d.name)
                    data[sanitized].append([k] * nvals)

        data = {k: np.concatenate(v) for k, v in data.items()}
        return data, elmapping, style
Beispiel #23
0
class Collator(NdElement):
    """
    Collator is an NdMapping type which can merge any number
    of HoloViews components with whatever level of nesting
    by inserting the Collators key dimensions on the HoloMaps.
    If the items in the Collator do not contain HoloMaps
    they will be created. Collator also supports filtering
    of Tree structures and dropping of constant dimensions.
    """

    drop = param.List(default=[],
                      doc="""
        List of dimensions to drop when collating data, specified
        as strings.""")

    drop_constant = param.Boolean(default=False,
                                  doc="""
        Whether to demote any non-varying key dimensions to
        constant dimensions.""")

    filters = param.List(default=[],
                         doc="""
        List of paths to drop when collating data, specified
        as strings or tuples.""")

    group = param.String(default='Collator')

    progress_bar = param.Parameter(default=None,
                                   doc="""
         The progress bar instance used to report progress. Set to
         None to disable progress bars.""")

    merge_type = param.ClassSelector(class_=NdMapping,
                                     default=HoloMap,
                                     is_instance=False,
                                     instantiate=False)

    value_transform = param.Callable(default=None,
                                     doc="""
        If supplied the function will be applied on each Collator
        value during collation. This may be used to apply an operation
        to the data or load references from disk before they are collated
        into a displayable HoloViews object.""")

    vdims = param.List(default=[],
                       doc="""
         Collator operates on HoloViews objects, if vdims are specified
         a value_transform function must also be supplied.""")

    _deep_indexable = False
    _auxiliary_component = False

    _nest_order = {
        HoloMap: ViewableElement,
        GridSpace: (HoloMap, CompositeOverlay, ViewableElement),
        NdLayout: (GridSpace, HoloMap, ViewableElement),
        NdOverlay: Element
    }

    def __call__(self):
        """
        Filter each Layout in the Collator with the supplied
        path_filters. If merge is set to True all Layouts are
        merged, otherwise an NdMapping containing all the
        Layouts is returned. Optionally a list of dimensions
        to be ignored can be supplied.
        """
        constant_dims = self.static_dimensions
        ndmapping = NdMapping(kdims=self.kdims)

        num_elements = len(self)
        for idx, (key, data) in enumerate(self.data.items()):
            if isinstance(data, AttrTree):
                data = data.filter(self.filters)
            if len(self.vdims):
                vargs = dict(zip(self.dimensions('value', label=True), data))
                data = self.value_transform(vargs)
            if not isinstance(data, Dimensioned):
                raise ValueError("Collator values must be Dimensioned objects "
                                 "before collation.")

            dim_keys = zip(self.kdims, key)
            varying_keys = [(d, k) for d, k in dim_keys
                            if not self.drop_constant or (
                                d not in constant_dims and d not in self.drop)]
            constant_keys = [(d, k) for d, k in dim_keys if d in constant_dims
                             and d not in self.drop and self.drop_constant]
            if varying_keys or constant_keys:
                data = self._add_dimensions(data, varying_keys,
                                            dict(constant_keys))
            ndmapping[key] = data
            if self.progress_bar is not None:
                self.progress_bar(float(idx + 1) / num_elements * 100)

        components = ndmapping.values()
        accumulator = ndmapping.last.clone(components[0].data)
        for component in components:
            accumulator.update(component)
        return accumulator

    def _add_item(self, key, value, sort=True, update=True):
        NdMapping._add_item(self, key, value, sort, update)

    @property
    def static_dimensions(self):
        """
        Return all constant dimensions.
        """
        dimensions = []
        for dim in self.kdims:
            if len(set(self[dim.name])) == 1:
                dimensions.append(dim)
        return dimensions

    def _add_dimensions(self, item, dims, constant_keys):
        """
        Recursively descend through an Layout and NdMapping objects
        in order to add the supplied dimension values to all contained
        HoloMaps.
        """
        if isinstance(item, Layout):
            item.fixed = False

        dim_vals = [(dim, val) for dim, val in dims[::-1]
                    if dim not in self.drop]
        if isinstance(item, self.merge_type):
            new_item = item.clone(cdims=constant_keys)
            for dim, val in dim_vals:
                dim = dim if isinstance(dim, Dimension) else Dimension(dim)
                if dim not in new_item.kdims:
                    new_item = new_item.add_dimension(dim, 0, val)
        elif isinstance(item, self._nest_order[self.merge_type]):
            if len(dim_vals):
                dimensions, key = zip(*dim_vals)
                new_item = self.merge_type({key: item},
                                           kdims=dimensions,
                                           cdims=constant_keys)
            else:
                new_item = item
        else:
            new_item = item.clone(shared_data=False, cdims=constant_keys)
            for k, v in item.items():
                new_item[k] = self._add_dimensions(v, dims[::-1],
                                                   constant_keys)
        if isinstance(new_item, Layout):
            new_item.fixed = True

        return new_item
Beispiel #24
0
class Palette(Cycle):
    """
    Palettes allow easy specifying a discrete sampling
    of an existing colormap. Palettes may be supplied a key
    to look up a function function in the colormap class
    attribute. The function should accept a float scalar
    in the specified range and return a RGB(A) tuple.
    The number of samples may also be specified as a
    parameter.

    The range and samples may conveniently be overridden
    with the __getitem__ method.
    """

    range = param.NumericTuple(default=(0, 1),
                               doc="""
        The range from which the Palette values are sampled.""")

    samples = param.Integer(default=32,
                            doc="""
        The number of samples in the given range to supply to
        the sample_fn.""")

    sample_fn = param.Callable(default=np.linspace,
                               doc="""
        The function to generate the samples, by default linear.""")

    reverse = param.Boolean(default=False,
                            doc="""
        Whether to reverse the palette.""")

    # A list of available colormaps
    colormaps = {'grayscale': grayscale}

    def __init__(self, key, **params):
        super(Cycle, self).__init__(key=key, **params)
        self.values = self._get_values()

    def __getitem__(self, slc):
        """
        Provides a convenient interface to override the
        range and samples parameters of the Cycle.
        Supplying a slice step or index overrides the
        number of samples. Unsupplied slice values will be
        inherited.
        """
        (start, stop), step = self.range, self.samples
        if isinstance(slc, slice):
            if slc.start is not None:
                start = slc.start
            if slc.stop is not None:
                stop = slc.stop
            if slc.step is not None:
                step = slc.step
        else:
            step = slc
        return self(range=(start, stop), samples=step)

    def _get_values(self):
        cmap = self.colormaps[self.key]
        (start, stop), steps = self.range, self.samples
        samples = [cmap(n) for n in self.sample_fn(start, stop, steps)]
        return samples[::-1] if self.reverse else samples
Beispiel #25
0
class PointPlot(ChartPlot, ColorbarPlot):
    """
    Note that the 'cmap', 'vmin' and 'vmax' style arguments control
    how point magnitudes are rendered to different colors.
    """

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

    size_index = param.ClassSelector(default=None,
                                     class_=(basestring, int),
                                     allow_None=True,
                                     doc="""
      Index of the dimension from which the sizes will the drawn.""")

    scaling_method = param.ObjectSelector(default="area",
                                          objects=["width", "area"],
                                          doc="""
      Determines whether the `scaling_factor` should be applied to
      the width or area of each point (default: "area").""")

    scaling_factor = param.Number(default=1,
                                  bounds=(0, None),
                                  doc="""
      Scaling factor which is applied to either the width or area
      of each point, depending on the value of `scaling_method`.""")

    show_grid = param.Boolean(default=False,
                              doc="""
      Whether to draw grid lines at the tick positions.""")

    size_fn = param.Callable(default=np.abs,
                             doc="""
      Function applied to size values before applying scaling,
      to remove values lower than zero.""")

    style_opts = [
        'alpha', 'color', 'edgecolors', 'facecolors', 'linewidth', 'marker',
        'size', 'visible', 'cmap', 'vmin', 'vmax', 'norm'
    ]

    _disabled_opts = ['size']
    _plot_methods = dict(single='scatter')

    def get_data(self, element, ranges, style):
        xs, ys = (element.dimension_values(i) for i in range(2))
        self._compute_styles(element, ranges, style)
        return (ys, xs) if self.invert_axes else (xs, ys), style, {}

    def _compute_styles(self, element, ranges, style):
        cdim = element.get_dimension(self.color_index)
        color = style.pop('color', None)
        cmap = style.get('cmap', None)
        if cdim and cmap:
            cs = element.dimension_values(self.color_index)
            # Check if numeric otherwise treat as categorical
            if cs.dtype.kind in 'if':
                style['c'] = cs
            else:
                categories = np.unique(cs)
                xsorted = np.argsort(categories)
                ypos = np.searchsorted(categories[xsorted], cs)
                style['c'] = xsorted[ypos]
            self._norm_kwargs(element, ranges, style, cdim)
        elif color:
            style['c'] = color
        style['edgecolors'] = style.pop('edgecolors',
                                        style.pop('edgecolor', 'none'))

        sdim = element.get_dimension(self.size_index)
        if sdim:
            sizes = element.dimension_values(self.size_index)
            ms = style['s'] if 's' in style else mpl.rcParams[
                'lines.markersize']
            sizes = compute_sizes(sizes, self.size_fn, self.scaling_factor,
                                  self.scaling_method, ms)
            if sizes is None:
                eltype = type(element).__name__
                self.warning('%s dimension is not numeric, cannot '
                             'use to scale %s size.' %
                             (sdim.pprint_label, eltype))
            else:
                style['s'] = sizes
        style['edgecolors'] = style.pop('edgecolors', 'none')

    def update_handles(self, key, axis, element, ranges, style):
        paths = self.handles['artist']
        (xs, ys), style, _ = self.get_data(element, ranges, style)
        paths.set_offsets(np.column_stack([xs, ys]))
        sdim = element.get_dimension(self.size_index)
        if sdim:
            paths.set_sizes(style['s'])

        cdim = element.get_dimension(self.color_index)
        if cdim:
            paths.set_clim((style['vmin'], style['vmax']))
            paths.set_array(style['c'])
            if 'norm' in style:
                paths.norm = style['norm']
Beispiel #26
0
class Dynamic(param.ParameterizedFunction):
    """
    Dynamically applies a callable to the Elements in any HoloViews
    object. Will return a DynamicMap wrapping the original map object,
    which will lazily evaluate when a key is requested. By default
    Dynamic applies a no-op, making it useful for converting HoloMaps
    to a DynamicMap.

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

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

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

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

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

    link_dataset = param.Boolean(default=True,
                                 doc="""
         Determines whether the output of the operation should inherit
         the .dataset property of the input to the operation. Helpful
         for tracking data providence for user supplied functions,
         which do not make use of the clone method. Should be disabled
         for operations where the output is not derived from the input
         and instead depends on some external state.""")

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

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

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

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

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

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

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

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

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

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

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

        def apply(element, *key, **kwargs):
            kwargs = dict(util.resolve_dependent_kwargs(self.p.kwargs),
                          **kwargs)
            processed = self._process(element, key, kwargs)
            if (self.p.link_dataset and isinstance(element, Dataset)
                    and isinstance(processed, Dataset)
                    and processed._dataset is None):
                processed._dataset = element.dataset
            return processed

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

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

    def _make_dynamic(self, hmap, dynamic_fn, streams):
        """
        Accepts a HoloMap and a dynamic callback function creating
        an equivalent DynamicMap from the HoloMap.
        """
        if isinstance(hmap, ViewableElement):
            dmap = DynamicMap(dynamic_fn, streams=streams)
            if isinstance(hmap, Overlay):
                dmap.callback.inputs[:] = list(hmap)
            return dmap
        dim_values = zip(*hmap.data.keys())
        params = util.get_param_values(hmap)
        kdims = [
            d.clone(values=list(util.unique_iterator(values)))
            for d, values in zip(hmap.kdims, dim_values)
        ]
        return DynamicMap(dynamic_fn,
                          streams=streams,
                          **dict(params, kdims=kdims))
Beispiel #27
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=[],
                            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.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,
    }

    _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']
        if object and 'name' not in params:
            params['name'] = param_name(object.name)
        super(Param, self).__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
    #----------------------------------------------------------------

    def _synced_params(self):
        ignored_params = ['default_layout']
        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):
                    self.object = event.new.cls if event.new.self is None else event.new.self
                    return
                if 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':
                parameters = [] if event.new == [] else event.new

        if parameters != [] and parameters != self.parameters:
            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):
            if 'type' in self.widgets[p_name]:
                widget_class = self.widgets[p_name].pop('type')
            else:
                widget_class_overridden = False
                widget_class = self.widget_type(p_obj)
            kw_widget = self.widgets[p_name]
        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):
                        widget_class = Spinner
                        if isinstance(p_obj, param.Integer):
                            kw['step'] = 1
                    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, Widget):
            widget = widget_class
        else:
            widget = widget_class(**kwargs)
        widget._param_pane = self

        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 isinstance(p_obj, param.Action):

            def action(change):
                value(self.object)

            watcher = widget.param.watch(action, 'clicks')
        else:
            watcher = widget.param.watch(link_widget, 'value')
        watchers.append(watcher)

        def link(change, watchers=[watcher]):
            updates = {}
            if change.what == 'constant':
                updates['disabled'] = change.new
            elif change.what == 'precedence':
                if (change.new < self.display_threshold
                        and widget in self._widget_box.objects):
                    self._widget_box.pop(widget)
                elif change.new >= self.display_threshold:
                    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
                return
            elif change.what == 'objects':
                updates['options'] = p_obj.get_range()
            elif change.what == 'bounds':
                start, end = p_obj.get_soft_bounds()
                updates['start'] = start
                updates['end'] = end
            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 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
            else:
                updates['value'] = change.new

            try:
                self._updating.append(p_name)
                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 _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(Param, self)._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 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()
        root = self.layout.get_root(doc, comm)
        ref = root.ref['id']
        self._models[ref] = (root, None)
        state._views[ref] = (self, root, doc, comm)
        return root
Beispiel #28
0
class Range(Args):
    """
    Range generates an argument from a numerically interpolated range
    which is linear by default. An optional function can be specified
    to sample a numeric range with regular intervals.
    """

    key = param.String(default='',
                       constant=True,
                       doc='''
         The key assigned to the values computed over the numeric range.''')

    start_value = param.Number(
        default=None,
        allow_None=True,
        constant=True,
        doc='''The starting numeric value of the range.''')

    end_value = param.Number(
        default=None,
        allow_None=True,
        constant=True,
        doc='''The ending numeric value of the range (inclusive).''')

    steps = param.Integer(
        default=2,
        constant=True,
        bounds=(1, None),
        doc='''The number of steps to interpolate over. Default is 2
         which returns the start and end values without interpolation.''')

    # Can't this be a lambda?
    mapfn = param.Callable(default=identityfn,
                           constant=True,
                           doc='''
         The function to be mapped across the linear range. The
         identity function is used by by default''')

    def __init__(self,
                 key,
                 start_value,
                 end_value,
                 steps=2,
                 mapfn=identityfn,
                 **params):

        values = self.linspace(start_value, end_value, steps)
        specs = [{key: mapfn(val)} for val in values]

        super(Range, self).__init__(specs,
                                    key=key,
                                    start_value=start_value,
                                    end_value=end_value,
                                    steps=steps,
                                    mapfn=mapfn,
                                    **params)
        self.pprint_args(['key', 'start_value'], ['end_value', 'steps'])

    def linspace(self, start, stop, n):
        """ Simple replacement for numpy linspace"""
        if n == 1: return [start]
        L = [0.0] * n
        nm1 = n - 1
        nm1inv = 1.0 / nm1
        for i in range(n):
            L[i] = nm1inv * (start * (nm1 - i) + stop * i)
        return L
Beispiel #29
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_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.""")

    label_formatter = param.Callable(
        default=default_label_formatter,
        allow_None=True,
        doc="Callable used to format the parameter names into widget labels.")

    parameters = param.List(default=None,
                            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,
                          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.""")

    precedence = 0.1

    _mapping = {
        param.Action: Button,
        param.Parameter: LiteralInput,
        param.Dict: LiteralInput,
        param.Selector: Select,
        param.ObjectSelector: ObjectSelector,
        param.FileSelector: FileSelector,
        param.Boolean: Checkbox,
        param.Number: FloatSlider,
        param.Integer: IntSlider,
        param.Range: RangeSlider,
        param.String: TextInput,
        param.ListSelector: MultiSelect,
        param.Date: DatetimeInput,
    }

    def __init__(self, object, **params):
        if isinstance(object, param.parameterized.Parameters):
            object = object.cls if object.self is None else object.self
        if 'name' not in params:
            params['name'] = object.name
        if 'parameters' not in params:
            params['parameters'] = [p for p in object.params() if p != 'name']
        super(Param, self).__init__(object, **params)

        # Construct widgets
        self._widgets = self._get_widgets()
        widgets = [
            widget for widgets in self._widgets.values() for widget in widgets
        ]
        self._widget_box = WidgetBox(*widgets,
                                     height=self.height,
                                     width=self.width,
                                     name=self.name)

        # Construct Layout
        kwargs = {'name': self.name}
        if self.expand_layout is Tabs:
            kwargs['width'] = self.width

        layout = self.expand_layout
        if isinstance(layout, Panel):
            self._expand_layout = layout
            self.layout = self._widget_box
        elif isinstance(layout, type) and issubclass(layout, Panel):
            self.layout = layout(self._widget_box, **kwargs)
            self._expand_layout = self.layout
        else:
            raise ValueError(
                'expand_layout expected to be a panel.layout.Panel'
                'type or instance, found %s type.' % type(layout).__name__)

        if not (self.expand_button == False and not self.expand):
            self._link_subobjects()

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

    def _link_subobjects(self):
        for pname, widgets in self._widgets.items():
            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
            selector, toggle = widgets if len(widgets) == 2 else (widgets[0],
                                                                  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 is parameterized
                ]
                if existing:
                    old_panel = existing[0]
                    if not change.new:
                        old_panel._cleanup(final=old_panel._temporary)
                        self._expand_layout.pop(old_panel)
                elif change.new:
                    kwargs = {
                        k: v
                        for k, v in self.get_param_values()
                        if k not in ['name', 'object', 'parameters']
                    }
                    pane = Param(parameterized,
                                 name=parameterized.name,
                                 _temporary=True,
                                 **kwargs)
                    if isinstance(self._expand_layout, Tabs):
                        title = pname if self.label_formatter is None else self.label_formatter(
                            pname)
                        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.get_param_values()
                        if k not in ['name', 'object', 'parameters']
                    }
                    pane = Param(parameterized,
                                 name=parameterized.name,
                                 _temporary=True,
                                 **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, 'active'))
            self._callbacks['instance'] += watchers

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

    @classmethod
    def applies(cls, obj):
        return (is_parameterized(obj)
                or isinstance(obj, param.parameterized.Parameters))

    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 widget(self, p_name):
        """Get widget for param_name"""
        p_obj = self.object.params(p_name)

        if self.widgets is None or p_name not in self.widgets:
            widget_class = self.widget_type(p_obj)
        else:
            widget_class = self.widgets[p_name]
        value = getattr(self.object, p_name)

        kw = dict(value=value, disabled=p_obj.constant)

        if self.label_formatter is not None:
            kw['name'] = self.label_formatter(p_name)
        else:
            kw['name'] = p_name

        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) and not issubclass(widget_class, LiteralInput):
                widget_class = LiteralInput

        kwargs = {k: v for k, v in kw.items() if k in widget_class.params()}
        widget = widget_class(**kwargs)
        watchers = self._callbacks['instance']
        if isinstance(p_obj, param.Action):
            widget.button_type = 'success'

            def action(change):
                value(self.object)

            watchers.append(widget.param.watch(action, 'clicks'))
        elif isinstance(widget, Toggle):
            pass
        else:
            widget.link(self.object, **{'value': p_name})

            def link(change, _updating=[]):
                key = (change.name, change.what)
                if key in _updating:
                    return

                _updating.append(key)
                updates = {}
                if change.what == 'constant':
                    updates['disabled'] = change.new
                elif change.what == 'precedence':
                    if change.new < 0 and widget in self._widget_box.objects:
                        self._widget_box.pop(widget)
                    elif change.new >= 0 and widget not in self._widget_box.objects:
                        precedence = lambda k: self.object.params(k).precedence
                        widgets = []
                        for k, ws in self._widgets.items():
                            if precedence(k) is None or precedence(
                                    k) >= self.display_threshold:
                                widgets += ws
                        self._widget_box.objects = widgets
                elif change.what == 'objects':
                    updates['options'] = p_obj.get_range()
                elif change.what == 'bounds':
                    start, end = p_obj.get_soft_bounds()
                    updates['start'] = start
                    updates['end'] = end
                else:
                    updates['value'] = change.new
                try:
                    widget.set_param(**updates)
                except:
                    raise
                finally:
                    _updating.pop(_updating.index(key))

            # 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))
            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'))

        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))):
            toggle = Toggle(name='...',
                            button_type='primary',
                            disabled=not is_parameterized(value))
            return [widget, toggle]
        else:
            return [widget]

    def _cleanup(self, root=None, final=False):
        self.layout._cleanup(root, final)
        super(Param, self)._cleanup(root, final)

    def _get_widgets(self):
        """Return name,widget boxes for all parameters (i.e., a property sheet)"""
        params = [(p, pobj) for p, pobj in self.object.params().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 if (
            (p.precedence is None) or (p.precedence >= self.display_threshold))
                    ]
        groups = itertools.groupby(filtered, key=key_fn)
        sorted_groups = [sorted(grp) for (k, grp) in groups]
        ordered_params = [el[0] for group in sorted_groups for el in group]

        # Format name specially
        ordered_params.pop(ordered_params.index('name'))
        if self.expand_layout is Tabs:
            widgets = []
        elif self.show_name:
            name = param_name(self.object.name)
            widgets = [('name', [StaticText(value='<b>{0}</b>'.format(name))])]
        else:
            widgets = []
        widgets += [(pname, self.widget(pname)) for pname in 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)
        if root:
            self._models[root.ref['id']] = model
        return model
Beispiel #30
0
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)

    _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(FileDownload, self).__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('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:
            return

        from ..param import ParamFunction
        filename = self.filename
        if self.callback is None:
            fileobj = self.file
        else:
            fileobj = ParamFunction.eval(self.callback)
        if isinstance(fileobj, str) and os.path.isfile(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 file 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()