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))
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
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)
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()
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
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)
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() ]
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
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()
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)
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)]
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))
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
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'])
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
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
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}
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.")
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]
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
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
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
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
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']
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))
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
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
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
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()