class HeatMapPlot(ColorbarPlot): clipping_colors = param.Dict(default={'NaN': 'white'}, doc=""" Dictionary to specify colors for clipped values, allows setting color for NaN values and for values above and below the min and max value. The min, max or NaN color may specify an RGB(A) color as a color hex string of the form #FFFFFF or #FFFFFFFF or a length 3 or length 4 tuple specifying values in the range 0-1 or a named HTML color.""") show_legend = param.Boolean(default=False, doc=""" Whether to show legend for the plot.""") _plot_methods = dict(single='rect') style_opts = ['cmap', 'color'] + line_properties + fill_properties _update_handles = ['color_mapper', 'source', 'glyph', 'colorbar'] _categorical = True def _get_factors(self, element): return super(HeatMapPlot, self)._get_factors(element.gridded) def get_data(self, element, ranges, style): x, y, z = [ dimension_sanitizer(d) for d in element.dimensions(label=True)[:3] ] if self.invert_axes: x, y = y, x cmapper = self._get_colormapper(element.vdims[0], element, ranges, style) if self.static_source: return {}, { 'x': x, 'y': y, 'fill_color': { 'field': 'zvalues', 'transform': cmapper } }, style aggregate = element.gridded xdim, ydim = aggregate.dimensions()[:2] xvals, yvals = (aggregate.dimension_values(x), aggregate.dimension_values(y)) zvals = aggregate.dimension_values(2, flat=False) if self.invert_axes: xdim, ydim = ydim, xdim zvals = zvals.T.flatten() else: zvals = zvals.T.flatten() if xvals.dtype.kind not in 'SU': xvals = [xdim.pprint_value(xv) for xv in xvals] if yvals.dtype.kind not in 'SU': yvals = [ydim.pprint_value(yv) for yv in yvals] data = {x: xvals, y: yvals, 'zvalues': zvals} if any(isinstance(t, HoverTool) for t in self.state.tools) and not self.static_source: for vdim in element.vdims: sanitized = dimension_sanitizer(vdim.name) data[sanitized] = [ '-' if is_nan(v) else vdim.pprint_value(v) for v in aggregate.dimension_values(vdim) ] return (data, { 'x': x, 'y': y, 'fill_color': { 'field': 'zvalues', 'transform': cmapper }, 'height': 1, 'width': 1 }, style)
class ReloadService(param.Parameterized): # pylint: disable=too-many-instance-attributes """The ReloadService is used by the Designer. For each component you want access to in the Designer you should provide a seperate Reload Service Args: component ([type]): For now the components that are know to be supported are - subclasses of `pn.reactive.Reactive` - subclasses of `param.Parameterized` with a `view` parameter which is a subclass of `pn.reactive.Reactive` Please NOTE that in order for the reload service to be able to reload the compoonent, the component specified cannot be defined in the __main__ file. Example ------- ```python TITLE_COMPONENT = ReloadService( component=components.TitleComponent, css_path=COMPONENT_CSS, js_path=COMPONENT_JS, ) EMPTY_COMPONENT = ReloadService( component=components.EmptyComponent, css_path=COMPONENT_CSS, js_path=COMPONENT2_JS, ) ```""" component = param.Parameter(allow_None=False) component_parameters = param.Dict() component_instance = param.Parameter() css_path = param.Parameter(constant=True) js_path = param.Parameter(constant=True) modules_to_reload = param.List() reload_component = param.Action(label="RELOAD COMPONENT") reload_css_file = param.Action(label="RELOAD CSS") reload_js_file = param.Action(label="RELOAD JS") css_text = param.String() js_text = param.String() reloading = param.Boolean(default=False) last_reload = param.String(constant=True) error_message = param.String() def __init__(self, component, **params): if not isinstance(params, dict): params = {} params["component"] = component super().__init__(**params) try: name = self.component.name except AttributeError: name = self.component.__name__ with param.edit_constant(self): self.name = name self.reload_component = self._reload_component self.reload_css_file = self._reload_css_file self.reload_js_file = self._reload_js_file def __repr__(self): return f"ReloadService({self.name})" def __str__(self): return f"ReloadService({self.name})" def _reload_component(self, _=None): try: self._signal_reload_start() if self.component_instance is not None: for mod in self.modules_to_reload: # pylint: disable=not-an-iterable importlib.reload(mod) mod = sys.modules[self.component.__module__] importlib.reload(mod) with param.edit_constant(self): self.component = getattr(mod, self.component.__name__) if self.component_parameters: # pylint: disable=not-a-mapping self.component_instance = self.component( **self.component_parameters) else: self.component_instance = self.component() self._reset_error_message() except Exception as ex: # pylint: disable=broad-except self._report_exception(ex) finally: self._signal_reload_end() def _reload_css_file(self, _=None): try: self._signal_reload_start() if not self.css_path: pass elif isinstance(self.css_path, pathlib.Path): self.css_text = self.css_path.read_text() else: raise NotImplementedError self._reset_error_message() except Exception as ex: # pylint: disable=broad-except self._report_exception(ex) finally: self._signal_reload_end() def _reload_js_file(self, _=None): try: self._signal_reload_start() if not self.js_path: pass elif isinstance(self.js_path, pathlib.Path): self.js_text = self.js_path.read_text() else: raise NotImplementedError self._reset_error_message() except Exception as ex: # pylint: disable=broad-except self._report_exception(ex) finally: self._signal_reload_end() def _signal_reload_start(self): self.reloading = True print("reload start", self.name, datetime.datetime.now()) def _report_exception(self, ex): # pylint: disable=unused-argument self.error_message = traceback.format_exc() self.component_instance = ErrorView(error_message=self.error_message) print(self.name, self.error_message) def _signal_reload_end(self): self.reloading = False with param.edit_constant(self): self.last_reload = str(datetime.datetime.now()) print("reload end", self.name, datetime.datetime.now()) def _reset_error_message(self): self.error_message = ""
class Gauge(ValueIndicator): """ A `Gauge` represents a value in some range as a position on speedometer or gauge. It is similar to a `Dial` but visually a lot busier. Reference: https://panel.holoviz.org/reference/indicators/Gauge.html :Example: >>> Gauge(name='Speed', value=79, bounds=(0, 200), colors=[(0.4, 'green'), (1, 'red')]) """ annulus_width = param.Integer(default=10, doc=""" Width of the gauge annulus.""") bounds = param.Range(default=(0, 100), doc=""" The upper and lower bound of the dial.""") colors = param.List(default=None, doc=""" Color thresholds for the Gauge, specified as a list of tuples of the fractional threshold and the color to switch to.""") custom_opts = param.Dict(doc=""" Additional options to pass to the ECharts Gauge definition.""") height = param.Integer(default=300, bounds=(0, None)) end_angle = param.Number(default=-45, doc=""" Angle at which the gauge ends.""") format = param.String(default='{value}%', doc=""" Formatting string for the value indicator.""") num_splits = param.Integer(default=10, doc=""" Number of splits along the gauge.""") show_ticks = param.Boolean(default=True, doc=""" Whether to show ticks along the dials.""") show_labels = param.Boolean(default=True, doc=""" Whether to show tick labels along the dials.""") start_angle = param.Number(default=225, doc=""" Angle at which the gauge starts.""") tooltip_format = param.String(default='{b} : {c}%', doc=""" Formatting string for the hover tooltip.""") title_size = param.Integer(default=18, doc=""" Size of title font.""") value = param.Number(default=25, doc=""" Value to indicate on the gauge a value within the declared bounds.""") width = param.Integer(default=300, bounds=(0, None)) _rename = {} _source_transforms = { 'annulus_width': None, 'bounds': None, 'colors': None, 'custom_opts': None, 'end_angle': None, 'format': None, 'num_splits': None, 'show_ticks': None, 'show_labels': None, 'start_angle': None, 'tooltip_format': None, 'title_size': None, 'value': None } @property def _widget_type(self): if 'panel.models.echarts' not in sys.modules: from ..models.echarts import ECharts else: ECharts = getattr(sys.modules['panel.models.echarts'], 'ECharts') return ECharts def __init__(self, **params): super().__init__(**params) self._update_value_bounds() @param.depends('bounds', watch=True) def _update_value_bounds(self): self.param.value.bounds = self.bounds def _process_param_change(self, msg): msg = super()._process_param_change(msg) vmin, vmax = msg.pop('bounds', self.bounds) msg['data'] = { 'tooltip': { 'formatter': msg.pop('tooltip_format', self.tooltip_format) }, 'series': [{ 'name': 'Gauge', 'type': 'gauge', 'axisTick': {'show': msg.pop('show_ticks', self.show_ticks)}, 'axisLabel': {'show': msg.pop('show_labels', self.show_labels)}, 'title': {'fontWeight': 'bold', 'fontSize': msg.pop('title_size', self.title_size)}, 'splitLine': {'show': True}, 'radius': '100%', 'detail': {'formatter': msg.pop('format', self.format)}, 'min': vmin, 'max': vmax, 'startAngle': msg.pop('start_angle', self.start_angle), 'endAngle': msg.pop('end_angle', self.end_angle), 'splitNumber': msg.pop('num_splits', self.num_splits), 'data': [{'value': msg.pop('value', self.value), 'name': self.name}], 'axisLine': { 'lineStyle': { 'width': msg.pop('annulus_width', self.annulus_width), } } }] } colors = msg.pop('colors', self.colors) if colors: msg['data']['series'][0]['axisLine']['lineStyle']['color'] = colors custom_opts = msg.pop('custom_opts', self.custom_opts) if custom_opts: gauge = msg['data']['series'][0] for k, v in custom_opts.items(): if k not in gauge or not isinstance(gauge[k], dict): gauge[k] = v else: gauge[k].update(v) return msg
class ColorbarPlot(ElementPlot): clim = param.NumericTuple(default=(np.nan, np.nan), length=2, doc=""" User-specified colorbar axis range limits for the plot, as a tuple (low,high). If specified, takes precedence over data and dimension ranges.""") colorbar = param.Boolean(default=False, doc=""" Whether to display a colorbar.""") color_levels = param.ClassSelector(default=None, class_=(int, list), doc=""" Number of discrete colors to use when colormapping or a set of color intervals defining the range of values to map each color to.""") colorbar_opts = param.Dict(default={}, doc=""" Allows setting including borderwidth, showexponent, nticks, outlinecolor, thickness, bgcolor, outlinewidth, bordercolor, ticklen, xpad, ypad, tickangle...""") symmetric = param.Boolean(default=False, doc=""" Whether to make the colormap symmetric around zero.""") def get_color_opts(self, eldim, element, ranges, style): opts = {} dim_name = dim_range_key(eldim) if self.colorbar: if isinstance(eldim, dim): title = str(eldim) if eldim.ops else str(eldim)[1:-1] else: title = eldim.pprint_label opts['colorbar'] = dict(title=title, **self.colorbar_opts) opts['showscale'] = True else: opts['showscale'] = False if eldim: auto = False if util.isfinite(self.clim).all(): cmin, cmax = self.clim elif dim_name in ranges: cmin, cmax = ranges[dim_name]['combined'] elif isinstance(eldim, dim): cmin, cmax = np.nan, np.nan auto = True else: cmin, cmax = element.range(dim_name) if self.symmetric: cabs = np.abs([cmin, cmax]) cmin, cmax = -cabs.max(), cabs.max() else: auto = True cmin, cmax = None, None cmap = style.pop('cmap', 'viridis') colorscale = get_colorscale(cmap, self.color_levels, cmin, cmax) # Reduce colorscale length to <= 255 to work around # https://github.com/plotly/plotly.js/issues/3699. Plotly.js performs # colorscale interpolation internally so reducing the number of colors # here makes very little difference to the displayed colorscale. # # Note that we need to be careful to make sure the first and last # colorscale pairs, colorscale[0] and colorscale[-1], are preserved # as the first and last in the subsampled colorscale if isinstance(colorscale, list) and len(colorscale) > 255: last_clr_pair = colorscale[-1] step = int(np.ceil(len(colorscale) / 255)) colorscale = colorscale[0::step] colorscale[-1] = last_clr_pair if cmin is not None: opts['cmin'] = cmin if cmax is not None: opts['cmax'] = cmax opts['cauto'] = auto opts['colorscale'] = colorscale return opts
class NdWidget(param.Parameterized): """ NdWidget is an abstract base class implementing a method to find the dimensions and keys of any ViewableElement, GridSpace or UniformNdMapping type. In the process it creates a mock_obj to hold the dimensions and keys. """ display_options = param.Dict(default={}, doc=""" The display options used to generate individual frames""") embed = param.Boolean(default=True, doc=""" Whether to embed all plots in the Javascript, generating a static widget not dependent on the IPython server.""") ####################### # JSON export options # ####################### export_json = param.Boolean(default=False, doc=""" Whether to export plots as JSON files, which can be dynamically loaded through a callback from the slider.""") json_save_path = param.String(default='./json_figures', doc=""" If export_json is enabled the widget will save the JSON data to this path. If None data will be accessible via the json_data attribute.""") json_load_path = param.String(default=None, doc=""" If export_json is enabled the widget JS code will load the data from this path, if None defaults to json_save_path. For loading the data from within the notebook the path must be relative, when exporting the notebook the path can be set to another location like a webserver where the JSON files can be uploaded to.""") ############################## # Javascript include options # ############################## css = param.String(default=None, doc=""" Defines the local CSS file to be loaded for this widget.""") basejs = param.String(default='widgets.js', doc=""" JS file containing javascript baseclasses for the widget.""") extensionjs = param.String(default=None, doc=""" Optional javascript extension file for a particular backend.""") widgets = {} counter = 0 def __init__(self, plot, renderer=None, **params): super(NdWidget, self).__init__(**params) self.id = plot.comm.id if plot.comm else uuid.uuid4().hex self.plot = plot self.plot_id = plot.id streams = [] for stream in plot.streams: if any(k in plot.dimensions for k in stream.contents): streams.append(stream) keys = plot.keys[:1] if self.plot.dynamic else plot.keys self.dimensions, self.keys = drop_streams(streams, plot.dimensions, keys) defaults = [kd.default for kd in self.dimensions] self.init_key = tuple(v if d is None else d for v, d in zip(self.keys[0], defaults)) self.json_data = {} if self.plot.dynamic: self.embed = False if renderer is None: backend = Store.current_backend self.renderer = Store.renderers[backend] else: self.renderer = renderer # Create mock NdMapping to hold the common dimensions and keys sorted_dims = [] for dim in self.dimensions: if dim.values and all(isnumeric(v) for v in dim.values): dim = dim.clone(values=sorted(dim.values)) sorted_dims.append(dim) if self.plot.dynamic: self.length = np.product( [len(d.values) for d in sorted_dims if d.values]) else: self.length = len(self.plot) with item_check(False): self.mock_obj = NdMapping([(k, None) for k in self.keys], kdims=sorted_dims, sort=False) NdWidget.widgets[self.id] = self # Set up jinja2 templating import jinja2 templateLoader = jinja2.FileSystemLoader(subdirs) self.jinjaEnv = jinja2.Environment(loader=templateLoader) if not self.embed: comm_manager = self.renderer.comm_manager self.comm = comm_manager.get_client_comm( id=self.id + '_client', on_msg=self._process_update) def cleanup(self): self.plot.cleanup() del NdWidget.widgets[self.id] def _process_update(self, msg): if 'content' not in msg: raise ValueError('Received widget comm message has no content.') self.update(msg['content']) def __call__(self, as_script=False): data = self._get_data() html = self.render_html(data) js = self.render_js(data) if as_script: return js, html js = '<script type="text/javascript">%s</script>' % js html = '\n'.join([html, js]) return html def _get_data(self): delay = int(1000. / self.display_options.get('fps', 5)) CDN = {} for name, resources in self.plot.renderer.core_dependencies.items(): if 'js' in resources: CDN[name] = resources['js'][0] for name, resources in self.plot.renderer.extra_dependencies.items(): if 'js' in resources: CDN[name] = resources['js'][0] name = type(self).__name__ cached = str(self.embed).lower() load_json = str(self.export_json).lower() mode = str(self.renderer.mode) json_path = (self.json_save_path if self.json_load_path is None else self.json_load_path) if json_path and json_path[-1] != '/': json_path = json_path + '/' dynamic = json.dumps( self.plot.dynamic) if self.plot.dynamic else 'false' return dict(CDN=CDN, frames=self.get_frames(), delay=delay, cached=cached, load_json=load_json, mode=mode, id=self.id, Nframes=self.length, widget_name=name, json_path=json_path, dynamic=dynamic, plot_id=self.plot_id) def render_html(self, data): template = self.jinjaEnv.get_template(self.html_template) return template.render(**data) def render_js(self, data): template = self.jinjaEnv.get_template(self.js_template) return template.render(**data) def get_frames(self): if self.embed: frames = OrderedDict([(idx, self._plot_figure(idx)) for idx in range(len(self.plot))]) else: frames = {} return self.encode_frames(frames) def encode_frames(self, frames): if isinstance(frames, dict): frames = dict(frames) return json.dumps(frames) def save_json(self, frames): """ Saves frames data into a json file at the specified json_path, named with the widget uuid. """ if self.json_save_path is None: return path = os.path.join(self.json_save_path, '%s.json' % self.id) if not os.path.isdir(self.json_save_path): os.mkdir(self.json_save_path) with open(path, 'w') as f: json.dump(frames, f) self.json_data = frames def _plot_figure(self, idx): with self.renderer.state(): self.plot.update(idx) css = self.display_options.get('css', {}) figure_format = self.display_options.get('figure_format', self.renderer.fig) return self.renderer.html(self.plot, figure_format, css=css) def update(self, key): pass
class Renderer(Exporter): """ The job of a Renderer is to turn the plotting state held within Plot classes into concrete, visual output in the form of the PNG, SVG, MP4 or WebM formats (among others). Note that a Renderer is a type of Exporter and must therefore follow the Exporter interface. The Renderer needs to be able to use the .state property of the appropriate Plot classes associated with that renderer in order to generate output. The process of 'drawing' is execute by the Plots and the Renderer turns the final plotting state into output. """ backend = param.String(doc=""" The full, lowercase name of the rendering backend or third part plotting package used e.g 'matplotlib' or 'cairo'.""") dpi = param.Integer(None, doc=""" The render resolution in dpi (dots per inch)""") fig = param.ObjectSelector(default='auto', objects=['auto'], doc=""" Output render format for static figures. If None, no figure rendering will occur. """) fps = param.Number(20, doc=""" Rendered fps (frames per second) for animated formats.""") holomap = param.ObjectSelector( default='auto', objects=['scrubber', 'widgets', None, 'auto'], doc=""" Output render multi-frame (typically animated) format. If None, no multi-frame rendering will occur.""") mode = param.ObjectSelector(default='default', objects=['default', 'server'], doc=""" Whether to render the object in regular or server mode. In server mode a bokeh Document will be returned which can be served as a bokeh server app. By default renders all output is rendered to HTML.""" ) size = param.Integer(100, doc=""" The rendered size as a percentage size""") widget_location = param.ObjectSelector(default=None, allow_None=True, objects=[ 'left', 'bottom', 'right', 'top', 'top_left', 'top_right', 'bottom_left', 'bottom_right', 'left_top', 'left_bottom', 'right_top', 'right_bottom' ], doc=""" The position of the widgets relative to the plot.""") widget_mode = param.ObjectSelector(default='embed', objects=['embed', 'live'], doc=""" The widget mode determining whether frames are embedded or generated 'live' when interacting with the widget.""") css = param.Dict(default={}, doc=""" Dictionary of CSS attributes and values to apply to HTML output.""") info_fn = param.Callable(None, allow_None=True, constant=True, doc=""" Renderers do not support the saving of object info metadata""") key_fn = param.Callable(None, allow_None=True, constant=True, doc=""" Renderers do not support the saving of object key metadata""") post_render_hooks = param.Dict(default={ 'svg': [], 'png': [] }, doc=""" Optional dictionary of hooks that are applied to the rendered data (according to the output format) before it is returned. Each hook is passed the rendered data and the object that is being rendered. These hooks allow post-processing of rendered data before output is saved to file or displayed.""") # Defines the valid output formats for each mode. mode_formats = {'fig': [None, 'auto'], 'holomap': [None, 'auto']} # The comm_manager handles the creation and registering of client, # and server side comms comm_manager = CommManager # Define appropriate widget classes widgets = ['scrubber', 'widgets'] # Whether in a notebook context, set when running Renderer.load_nb notebook_context = False # Plot registry _plots = {} # Whether to render plots with Panel _render_with_panel = False def __init__(self, **params): self.last_plot = None super(Renderer, self).__init__(**params) def __call__(self, obj, fmt='auto', **kwargs): plot, fmt = self._validate(obj, fmt) info = {'file-ext': fmt, 'mime_type': MIME_TYPES[fmt]} if plot is None: return None, info elif self.mode == 'server': return self.server_doc(plot, doc=kwargs.get('doc')), info elif isinstance(plot, Viewable): return self.static_html(plot), info else: data = self._figure_data(plot, fmt, **kwargs) data = self._apply_post_render_hooks(data, obj, fmt) return data, info @bothmethod def get_plot(self_or_cls, obj, doc=None, renderer=None, comm=None, **kwargs): """ Given a HoloViews Viewable return a corresponding plot instance. """ if isinstance(obj, DynamicMap) and obj.unbounded: dims = ', '.join('%r' % dim for dim in obj.unbounded) msg = ('DynamicMap cannot be displayed without explicit indexing ' 'as {dims} dimension(s) are unbounded. ' '\nSet dimensions bounds with the DynamicMap redim.range ' 'or redim.values methods.') raise SkipRendering(msg.format(dims=dims)) # Initialize DynamicMaps with first data item initialize_dynamic(obj) if not renderer: renderer = self_or_cls if not isinstance(self_or_cls, Renderer): renderer = self_or_cls.instance() if not isinstance(obj, Plot): if not displayable(obj): obj = collate(obj) initialize_dynamic(obj) obj = Compositor.map(obj, mode='data', backend=self_or_cls.backend) obj = Layout(obj) if isinstance(obj, AdjointLayout) else obj plot_opts = dict(self_or_cls.plot_options(obj, self_or_cls.size), **kwargs) plot = self_or_cls.plotting_class(obj)(obj, renderer=renderer, **plot_opts) defaults = [kd.default for kd in plot.dimensions] init_key = tuple(v if d is None else d for v, d in zip(plot.keys[0], defaults)) plot.update(init_key) else: plot = obj if isinstance(self_or_cls, Renderer): self_or_cls.last_plot = plot if comm: plot.comm = comm if comm or self_or_cls.mode == 'server': from bokeh.document import Document from bokeh.io import curdoc if doc is None: doc = Document() if self_or_cls.notebook_context else curdoc() plot.document = doc return plot @bothmethod def get_plot_state(self_or_cls, obj, renderer=None, **kwargs): """ Given a HoloViews Viewable return a corresponding plot state. """ if not isinstance(obj, Plot): obj = self_or_cls.get_plot(obj, renderer, **kwargs) return obj.state def _validate(self, obj, fmt, **kwargs): """ Helper method to be used in the __call__ method to get a suitable plot or widget object and the appropriate format. """ if isinstance(obj, Viewable): return obj, 'html' fig_formats = self.mode_formats['fig'] holomap_formats = self.mode_formats['holomap'] holomaps = obj.traverse(lambda x: x, [HoloMap]) dynamic = any(isinstance(m, DynamicMap) for m in holomaps) if fmt in ['auto', None]: if any( len(o) > 1 or (isinstance(o, DynamicMap) and unbound_dimensions(o.streams, o.kdims)) for o in holomaps): fmt = holomap_formats[0] if self.holomap in [ 'auto', None ] else self.holomap else: fmt = fig_formats[0] if self.fig == 'auto' else self.fig if fmt in self.widgets: plot = self.get_widget(obj, fmt) fmt = 'html' elif dynamic or (self._render_with_panel and fmt == 'html'): plot, fmt = HoloViewsPane(obj, center=True, backend=self.backend, renderer=self), fmt else: plot = self.get_plot(obj, renderer=self, **kwargs) all_formats = set(fig_formats + holomap_formats) if fmt not in all_formats: raise Exception( "Format %r not supported by mode %r. Allowed formats: %r" % (fmt, self.mode, fig_formats + holomap_formats)) self.last_plot = plot return plot, fmt def _apply_post_render_hooks(self, data, obj, fmt): """ Apply the post-render hooks to the data. """ hooks = self.post_render_hooks.get(fmt, []) for hook in hooks: try: data = hook(data, obj) except Exception as e: self.param.warning("The post_render_hook %r could not " "be applied:\n\n %s" % (hook, e)) return data def html(self, obj, fmt=None, css=None, resources='CDN', **kwargs): """ Renders plot or data structure and wraps the output in HTML. The comm argument defines whether the HTML output includes code to initialize a Comm, if the plot supplies one. """ plot, fmt = self._validate(obj, fmt) figdata, _ = self(plot, fmt, **kwargs) if css is None: css = self.css if isinstance(plot, Viewable): from bokeh.document import Document from bokeh.embed import file_html from bokeh.resources import CDN, INLINE doc = Document() plot._render_model(doc) if resources == 'cdn': resources = CDN elif resources == 'inline': resources = INLINE return file_html(doc, resources) elif fmt in ['html', 'json']: return figdata else: if fmt == 'svg': figdata = figdata.encode("utf-8") elif fmt == 'pdf' and 'height' not in css: _, h = self.get_size(plot) css['height'] = '%dpx' % (h * self.dpi * 1.15) if isinstance(css, dict): css = '; '.join("%s: %s" % (k, v) for k, v in css.items()) else: raise ValueError("CSS must be supplied as Python dictionary") b64 = base64.b64encode(figdata).decode("utf-8") (mime_type, tag) = MIME_TYPES[fmt], HTML_TAGS[fmt] src = HTML_TAGS['base64'].format(mime_type=mime_type, b64=b64) html = tag.format(src=src, mime_type=mime_type, css=css) return html def components(self, obj, fmt=None, comm=True, **kwargs): """ Returns data and metadata dictionaries containing HTML and JS components to include render in app, notebook, or standalone document. """ if isinstance(obj, Plot): plot = obj else: plot, fmt = self._validate(obj, fmt) data, metadata = {}, {} if isinstance(plot, Viewable): from bokeh.document import Document dynamic = bool(plot.object.traverse(lambda x: x, [DynamicMap])) embed = (not (dynamic or self.widget_mode == 'live') or config.embed) comm = self.comm_manager.get_server_comm() if comm else None doc = Document() with config.set(embed=embed): model = plot.layout._render_model(doc, comm) return render_model(model, comm) if embed else render_mimebundle( model, doc, comm) else: html = self._figure_data(plot, fmt, as_script=True, **kwargs) data['text/html'] = html return (data, {MIME_TYPES['jlab-hv-exec']: metadata}) def static_html(self, obj, fmt=None, template=None): """ Generates a static HTML with the rendered object in the supplied format. Allows supplying a template formatting string with fields to interpolate 'js', 'css' and the main 'html'. """ html_bytes = StringIO() self.save(obj, html_bytes, fmt) html_bytes.seek(0) return html_bytes.read() @bothmethod def get_widget(self_or_cls, plot, widget_type, **kwargs): if widget_type == 'scrubber': widget_location = self_or_cls.widget_location or 'bottom' else: widget_type = 'individual' widget_location = self_or_cls.widget_location or 'right' layout = HoloViewsPane(plot, widget_type=widget_type, center=True, widget_location=widget_location, renderer=self_or_cls) interval = int((1. / self_or_cls.fps) * 1000) for player in layout.layout.select(PlayerBase): player.interval = interval return layout @bothmethod def export_widgets(self_or_cls, obj, filename, fmt=None, template=None, json=False, json_path='', **kwargs): """ Render and export object as a widget to a static HTML file. Allows supplying a custom template formatting string with fields to interpolate 'js', 'css' and the main 'html' containing the widget. Also provides options to export widget data to a json file in the supplied json_path (defaults to current path). """ if fmt not in self_or_cls.widgets + ['auto', None]: raise ValueError("Renderer.export_widget may only export " "registered widget types.") self_or_cls.get_widget(obj, fmt).save(filename) @bothmethod def _widget_kwargs(self_or_cls): if self_or_cls.holomap in ('auto', 'widgets'): widget_type = 'individual' loc = self_or_cls.widget_location or 'right' else: widget_type = 'scrubber' loc = self_or_cls.widget_location or 'bottom' return { 'widget_location': loc, 'widget_type': widget_type, 'center': True } @bothmethod def app(self_or_cls, plot, show=False, new_window=False, websocket_origin=None, port=0): """ Creates a bokeh app from a HoloViews object or plot. By default simply attaches the plot to bokeh's curdoc and returns the Document, if show option is supplied creates an Application instance and displays it either in a browser window or inline if notebook extension has been loaded. Using the new_window option the app may be displayed in a new browser tab once the notebook extension has been loaded. A websocket origin is required when launching from an existing tornado server (such as the notebook) and it is not on the default port ('localhost:8888'). """ if isinstance(plot, HoloViewsPane): pane = plot else: pane = HoloViewsPane(plot, backend=self_or_cls.backend, renderer=self_or_cls, **self_or_cls._widget_kwargs()) if new_window: return pane._get_server(port, websocket_origin, show=show) else: kwargs = { 'notebook_url': websocket_origin } if websocket_origin else {} return pane.app(port=port, **kwargs) @bothmethod def server_doc(self_or_cls, obj, doc=None): """ Get a bokeh Document with the plot attached. May supply an existing doc, otherwise bokeh.io.curdoc() is used to attach the plot to the global document instance. """ if not isinstance(obj, HoloViewsPane): obj = HoloViewsPane(obj, renderer=self_or_cls, backend=self_or_cls.backend, **self_or_cls._widget_kwargs()) return obj.layout.server_doc(doc) @classmethod def plotting_class(cls, obj): """ Given an object or Element class, return the suitable plotting class needed to render it with the current renderer. """ if isinstance(obj, AdjointLayout) or obj is AdjointLayout: obj = Layout if isinstance(obj, type): element_type = obj else: element_type = obj.type if isinstance(obj, HoloMap) else type(obj) try: plotclass = Store.registry[cls.backend][element_type] except KeyError: raise SkipRendering("No plotting class for {0} " "found".format(element_type.__name__)) return plotclass @classmethod def html_assets(cls, core=True, extras=True, backends=None, script=False): """ Deprecated: No longer needed """ param.main.warning("Renderer.html_assets is deprecated as all " "JS and CSS dependencies are now handled by " "Panel.") @classmethod def plot_options(cls, obj, percent_size): """ Given an object and a percentage size (as supplied by the %output magic) return all the appropriate plot options that would be used to instantiate a plot class for that element. Default plot sizes at the plotting class level should be taken into account. """ raise NotImplementedError @bothmethod def save(self_or_cls, obj, basename, fmt='auto', key={}, info={}, options=None, resources='inline', **kwargs): """ Save a HoloViews object to file, either using an explicitly supplied format or to the appropriate default. """ if info or key: raise Exception( 'Renderer does not support saving metadata to file.') with StoreOptions.options(obj, options, **kwargs): plot, fmt = self_or_cls._validate(obj, fmt) if isinstance(plot, Viewable): from bokeh.resources import CDN, INLINE, Resources if isinstance(resources, Resources): pass elif resources.lower() == 'cdn': resources = CDN elif resources.lower() == 'inline': resources = INLINE plot.layout.save(basename, embed=True, resources=resources) return rendered = self_or_cls(plot, fmt) if rendered is None: return (data, info) = rendered encoded = self_or_cls.encode(rendered) prefix = self_or_cls._save_prefix(info['file-ext']) if prefix: encoded = prefix + encoded if isinstance(basename, (BytesIO, StringIO)): basename.write(encoded) basename.seek(0) else: filename = '%s.%s' % (basename, info['file-ext']) with open(filename, 'wb') as f: f.write(encoded) @bothmethod def _save_prefix(self_or_cls, ext): "Hook to prefix content for instance JS when saving HTML" return @bothmethod def get_size(self_or_cls, plot): """ Return the display size associated with a plot before rendering to any particular format. Used to generate appropriate HTML display. Returns a tuple of (width, height) in pixels. """ raise NotImplementedError @classmethod @contextmanager def state(cls): """ Context manager to handle global state for a backend, allowing Plot classes to temporarily override that state. """ yield @classmethod def validate(cls, options): """ Validate an options dictionary for the renderer. """ return options @classmethod def load_nb(cls, inline=True): """ Loads any resources required for display of plots in the Jupyter notebook """ load_notebook(inline) with param.logging_level('ERROR'): try: ip = get_ipython() # noqa except: ip = None if not ip or not hasattr(ip, 'kernel'): return cls.notebook_context = True cls.comm_manager = JupyterCommManager state._comm_manager = JupyterCommManager @classmethod def _delete_plot(cls, plot_id): """ Deletes registered plots and calls Plot.cleanup """ plot = cls._plots.get(plot_id) if plot is None: return plot.cleanup() del cls._plots[plot_id]
class HttpxClient(param.Parameterized): _app_settings = param.Dict(default=None, allow_None=True) _self_serve = param.Boolean(False) server_url = param.String( default="http://localhost:5000/v1", regex= r"(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'\".,<>?«»“”‘’]))" ) server_urls = param.List(default=[], class_=str) auth = param.ClassSelector(Oauth2DeviceFlow, default=Oauth2DeviceFlow()) _log = param.String() _messages = param.List(default=[]) _busy = param.Boolean(False) _client_ttl = param.Integer(600) _client_created = param.Number(0) _client = None @property def app(self): if not self._self_serve: return None if self.name not in APPS and self._app_settings: APPS[self.name] = eve.Eve(settings=self._app_settings) return APPS.get(self.name, None) @property def client(self): if self._client is None or (time.time() - self._client_created) > self._client_ttl: if self._client: self._client.close() self._client = httpx.Client(app=self.app, base_url=self.server_url) self._client_created = time.time() return self._client def get_client_kwargs(self): kwargs = { "headers": self.headers(), "base_url": self.server_url, "app": None, } if self._self_serve: kwargs["app"] = self._app_settings return kwargs def headers(self): headers = self.auth.get_headers() headers["Accept"] = "application/json" return headers def upload_file(self, url, _file, name=None, **kwargs): if isinstance(_file, BufferedIOBase): if name is None and hasattr(_file, "name"): name = _file.name.rpartition(".")[0] f = _file elif isinstance(_file, str): if not os.path.isfile(str): raise ValueError("File does not exist.") f = open(_file, "rb") else: raise TypeError("_file parameter must be a string or File object.") with httpx.Client(app=self.app, base_url=self.server_url) as client: try: httpx.post(url, data={"name": name}, files={"data": f}**kwargs) finally: f.close() def download_file(self, url, f): with httpx.Client(app=self.app, base_url=self.server_url) as client: with client.stream("GET", url, headers=self.auth.get_headers()) as response: total = int(response.headers["Content-Length"]) with tqdm(total=total, unit_scale=True, unit_divisor=1024, unit="B") as progress: num_bytes_downloaded = response.num_bytes_downloaded for chunk in response.iter_bytes(): f.write(chunk) progress.update(response.num_bytes_downloaded - num_bytes_downloaded) num_bytes_downloaded = response.num_bytes_downloaded def get(self, url, timeout=10, **params): with httpx.Client(app=self.app, base_url=self.server_url) as client: self._busy = True try: resp = client.get(url, params=params, headers=self.headers(), timeout=timeout) self._busy = False if resp.is_error: self.log_error(resp.text) else: self.clear_messages() return resp.json() except Exception as e: self.log_error(e) self._busy = False return {} def post(self, url, data=None, json=None, files=None, timeout=10, **kwargs): with httpx.Client(app=self.app, base_url=self.server_url) as client: self._busy = True headers = self.headers() if files is None: headers["Content-Type"] = "application/json" headers.update(kwargs.get("headers", {})) try: resp = client.post(url, data=data, json=json, headers=headers, files=files, timeout=timeout, **kwargs) self._busy = False if resp.is_error: self.log_error(resp.text) return False else: self.clear_messages() return True except Exception as e: self.log_error(e) self._busy = False def put(self, url, data=None, json=None, files=None, etag=None, timeout=10, **kwargs): with httpx.Client(app=self.app, base_url=self.server_url) as client: self._busy = True headers = self.headers() if files is None: headers["Content-Type"] = "application/json" if etag: headers["If-Match"] = etag try: resp = client.put(url, data=data, json=json, files=files, headers=headers, timeout=timeout, **kwargs) self._busy = False if resp.is_error: self.log_error(resp.text) return False else: self.clear_messages() return True except Exception as e: self.log_error(e) self._busy = False def patch(self, url, data, json=None, files=None, etag=None, timeout=10, **kwargs): with httpx.Client(app=self.app, base_url=self.server_url) as client: self._busy = True headers = self.headers() if files is None: headers["Content-Type"] = "application/json" if etag: headers["If-Match"] = etag try: resp = client.patch(url, data=data, json=json, files=files, headers=headers, timeout=timeout, **kwargs) self._busy = False if resp.is_error or settings.DEBUG: self.log_error(resp.text) return False else: self.clear_messages() return True except Exception as e: self.log_error(e) self._busy = False def delete(self, url, etag="", timeout=10): with httpx.Client(app=self.app, base_url=self.server_url) as client: self._busy = True headers = self.headers() if etag: headers["If-Match"] = etag try: resp = client.delete(url, headers=headers, timeout=timeout) self._busy = False if resp.is_error: self.log_error(resp.text) return False else: self.clear_messages() return True except Exception as e: self.log_error(e) self._busy = False def log_error(self, e): try: e = str(e) log = [e] + self._log.split("\n") self._log = "\n".join(log[:settings.MAX_LOG_SIZE]) self._messages = (e.split("\n") + self._messages)[:settings.MAX_MESSAGES] except: pass def clear_messages(self): self._messages = [] def set_token(self, token): self.auth.set_token(token) def login(self, webbrowser=True): self.auth.login(webbrowser) def __getstate__(self): state = super().__getstate__() state.pop("_client", None) return state
class _config(_base_config): """ Holds global configuration options for Panel. The options can be set directly on the global config instance, via keyword arguments in the extension or via environment variables. For example to set the embed option the following approaches can be used: pn.config.embed = True pn.extension(embed=True) os.environ['PANEL_EMBED'] = 'True' """ apply_signatures = param.Boolean(default=True, doc=""" Whether to set custom Signature which allows tab-completion in some IDEs and environments.""") autoreload = param.Boolean(default=False, doc=""" Whether to autoreload server when script changes.""") loading_spinner = param.Selector(default='arcs', objects=[ 'arcs', 'bars', 'dots', 'petals'], doc=""" Loading indicator to use when component loading parameter is set.""") loading_color = param.Color(default='#c3c3c3', doc=""" Color of the loading indicator.""") safe_embed = param.Boolean(default=False, doc=""" Ensure all bokeh property changes trigger events which are embedded. Useful when only partial updates are made in an app, e.g. when working with HoloViews.""") session_history = param.Integer(default=0, bounds=(-1, None), doc=""" If set to a non-negative value this determines the maximum length of the pn.state.session_info dictionary, which tracks information about user sessions. A value of -1 indicates an unlimited history.""") sizing_mode = param.ObjectSelector(default=None, objects=[ 'fixed', 'stretch_width', 'stretch_height', 'stretch_both', 'scale_width', 'scale_height', 'scale_both', None], doc=""" Specify the default sizing mode behavior of panels.""") _comms = param.ObjectSelector( default='default', objects=['default', 'ipywidgets', 'vscode', 'colab'], doc=""" Whether to render output in Jupyter with the default Jupyter extension or use the jupyter_bokeh ipywidget model.""") _console_output = param.ObjectSelector(default='accumulate', allow_None=True, objects=['accumulate', 'replace', 'disable', False], doc=""" How to log errors and stdout output triggered by callbacks from Javascript in the notebook.""") _cookie_secret = param.String(default=None, doc=""" Configure to enable getting/setting secure cookies.""") _embed = param.Boolean(default=False, allow_None=True, doc=""" Whether plot data will be embedded.""") _embed_json = param.Boolean(default=False, doc=""" Whether to save embedded state to json files.""") _embed_json_prefix = param.String(default='', doc=""" Prefix for randomly generated json directories.""") _embed_load_path = param.String(default=None, doc=""" Where to load json files for embedded state.""") _embed_save_path = param.String(default='./', doc=""" Where to save json files for embedded state.""") _log_level = param.Selector( default=None, objects=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], doc="Log level of Panel loggers") _oauth_provider = param.ObjectSelector( default=None, allow_None=True, objects=[], doc=""" Select between a list of authentification providers.""") _oauth_key = param.String(default=None, doc=""" A client key to provide to the OAuth provider.""") _oauth_secret = param.String(default=None, doc=""" A client secret to provide to the OAuth provider.""") _oauth_jwt_user = param.String(default=None, doc=""" The key in the ID JWT token to consider the user.""") _oauth_redirect_uri = param.String(default=None, doc=""" A redirect URI to provide to the OAuth provider.""") _oauth_encryption_key = param.ClassSelector(default=None, class_=bytes, doc=""" A random string used to encode OAuth related user information.""") _oauth_extra_params = param.Dict(default={}, doc=""" Additional parameters required for OAuth provider.""") _inline = param.Boolean(default=_LOCAL_DEV_VERSION, allow_None=True, doc=""" Whether to inline JS and CSS resources. If disabled, resources are loaded from CDN if one is available.""") _truthy = ['True', 'true', '1', True, 1] def __init__(self, **params): super().__init__(**params) for p in self.param: if p.startswith('_'): setattr(self, p+'_', None) @contextmanager def set(self, **kwargs): values = [(k, v) for k, v in self.param.get_param_values() if k != 'name'] overrides = [(k, getattr(self, k+'_')) for k in self.param if k.startswith('_')] for k, v in kwargs.items(): setattr(self, k, v) try: yield finally: self.param.set_param(**dict(values)) for k, v in overrides: setattr(self, k+'_', v) @property def _doc_build(self): return os.environ.get('PANEL_DOC_BUILD') @property def console_output(self): if self._console_output_ is not None: return 'disable' if not self._console_output_ else self._console_output_ elif self._doc_build: return 'disable' else: return os.environ.get('PANEL_CONSOLE_OUTPUT', _config._console_output) @console_output.setter def console_output(self, value): validate_config(self, '_console_output', value) self._console_output_ = value @property def embed(self): if self._embed_ is not None: return self._embed_ else: return os.environ.get('PANEL_EMBED', _config._embed) in self._truthy @embed.setter def embed(self, value): validate_config(self, '_embed', value) self._embed_ = value @property def comms(self): if self._comms_ is not None: return self._comms_ else: return os.environ.get('PANEL_COMMS', _config._comms) @comms.setter def comms(self, value): validate_config(self, '_comms', value) self._comms_ = value @property def embed_json(self): if self._embed_json_ is not None: return self._embed_json_ else: return os.environ.get('PANEL_EMBED_JSON', _config._embed_json) in self._truthy @embed_json.setter def embed_json(self, value): validate_config(self, '_embed_json', value) self._embed_json_ = value @property def embed_json_prefix(self): if self._embed_json_prefix_ is not None: return self._embed_json_prefix_ else: return os.environ.get('PANEL_EMBED_JSON_PREFIX', _config._embed_json_prefix) @embed_json_prefix.setter def embed_json_prefix(self, value): validate_config(self, '_embed_json_prefix', value) self._embed_json_prefix_ = value @property def embed_save_path(self): if self._embed_save_path_ is not None: return self._embed_save_path_ else: return os.environ.get('PANEL_EMBED_SAVE_PATH', _config._embed_save_path) @embed_save_path.setter def embed_save_path(self, value): validate_config(self, '_embed_save_path', value) self._embed_save_path_ = value @property def embed_load_path(self): if self._embed_load_path_ is not None: return self._embed_load_path_ else: return os.environ.get('PANEL_EMBED_LOAD_PATH', _config._embed_load_path) @embed_load_path.setter def embed_load_path(self, value): validate_config(self, '_embed_load_path', value) self._embed_load_path_ = value @property def inline(self): if self._inline_ is not None: return self._inline_ else: return os.environ.get('PANEL_INLINE', _config._inline) in self._truthy @inline.setter def inline(self, value): validate_config(self, '_inline', value) self._inline_ = value @property def log_level(self): if self._log_level_ is not None: return self._log_level_ elif 'PANEL_LOG_LEVEL' in os.environ: return os.environ['PANEL_LOG_LEVEL'].upper() else: return self._log_level @log_level.setter def log_level(self, value): validate_config(self, '_log_level', value) self._log_level_ = value @property def oauth_provider(self): if self._oauth_provider_ is not None: return self._oauth_provider_ else: provider = os.environ.get('PANEL_OAUTH_PROVIDER', _config._oauth_provider) return provider.lower() if provider else None @oauth_provider.setter def oauth_provider(self, value): validate_config(self, '_oauth_provider', value.lower()) self._oauth_provider_ = value.lower() @property def oauth_key(self): if self._oauth_key_ is not None: return self._oauth_key_ else: return os.environ.get('PANEL_OAUTH_KEY', _config._oauth_key) @oauth_key.setter def oauth_key(self, value): validate_config(self, '_oauth_key', value) self._oauth_key_ = value @property def cookie_secret(self): if self._cookie_secret_ is not None: return self._cookie_secret_ else: return os.environ.get( 'PANEL_COOKIE_SECRET', os.environ.get('BOKEH_COOKIE_SECRET', _config._cookie_secret) ) @cookie_secret.setter def cookie_secret(self, value): validate_config(self, '_cookie_secret', value) self._cookie_secret_ = value @property def oauth_secret(self): if self._oauth_secret_ is not None: return self._oauth_secret_ else: return os.environ.get('PANEL_OAUTH_SECRET', _config._oauth_secret) @oauth_secret.setter def oauth_secret(self, value): validate_config(self, '_oauth_secret', value) self._oauth_secret_ = value @property def oauth_redirect_uri(self): if self._oauth_redirect_uri_ is not None: return self._oauth_redirect_uri_ else: return os.environ.get('PANEL_OAUTH_REDIRECT_URI', _config._oauth_redirect_uri) @oauth_redirect_uri.setter def oauth_redirect_uri(self, value): validate_config(self, '_oauth_redirect_uri', value) self._oauth_redirect_uri_ = value @property def oauth_jwt_user(self): if self._oauth_jwt_user_ is not None: return self._oauth_jwt_user_ else: return os.environ.get('PANEL_OAUTH_JWT_USER', _config._oauth_jwt_user) @oauth_jwt_user.setter def oauth_jwt_user(self, value): validate_config(self, '_oauth_jwt_user', value) self._oauth_jwt_user_ = value @property def oauth_encryption_key(self): if self._oauth_encryption_key_ is not None: return self._oauth_encryption_key_ else: return os.environ.get('PANEL_OAUTH_ENCRYPTION', _config._oauth_encryption_key) @oauth_encryption_key.setter def oauth_encryption_key(self, value): validate_config(self, '_oauth_encryption_key', value) self._oauth_encryption_key_ = value @property def oauth_extra_params(self): if self._oauth_extra_params_ is not None: return self._oauth_extra_params_ else: if 'PANEL_OAUTH_EXTRA_PARAMS' in os.environ: return ast.literal_eval(os.environ['PANEL_OAUTH_EXTRA_PARAMS']) else: return _config._oauth_extra_params @oauth_extra_params.setter def oauth_extra_params(self, value): validate_config(self, '_oauth_extra_params', value) self._oauth_extra_params_ = value
class HoloViews(PaneBase): """ HoloViews panes render any HoloViews object to a corresponding Bokeh model while respecting the currently selected backend. """ backend = param.ObjectSelector(default=None, objects=['bokeh', 'plotly', 'matplotlib'], doc=""" The HoloViews backend used to render the plot (if None defaults to the currently selected renderer).""") center = param.Boolean(default=False, doc=""" Whether to center the plot.""") linked_axes = param.Boolean(default=True, doc=""" Whether to use link the axes of bokeh plots inside this pane across a panel layout.""") renderer = param.Parameter(default=None, doc=""" Explicit renderer instance to use for rendering the HoloViews plot. Overrides the backend.""") theme = param.ClassSelector(default=None, class_=(Theme, str), allow_None=True, doc=""" Bokeh theme to apply to the HoloViews plot.""") widget_location = param.ObjectSelector(default='right_top', objects=[ 'left', 'bottom', 'right', 'top', 'top_left', 'top_right', 'bottom_left', 'bottom_right', 'left_top', 'left_bottom', 'right_top', 'right_bottom' ], doc=""" The layout of the plot and the widgets. The value refers to the position of the widgets relative to the plot.""") widget_layout = param.ObjectSelector(objects=[WidgetBox, Row, Column], constant=True, default=WidgetBox, doc=""" The layout object to display the widgets in.""") widget_type = param.ObjectSelector(default='individual', objects=['individual', 'scrubber'], doc=""") Whether to generate individual widgets for each dimension or on global scrubber.""") widgets = param.Dict(default={}, doc=""" A mapping from dimension name to a widget instance which will be used to override the default widgets.""") priority = 0.8 _panes = {'bokeh': Bokeh, 'matplotlib': Matplotlib, 'plotly': Plotly} _rename = { 'backend': None, 'center': None, 'linked_axes': None, 'renderer': None, 'theme': None, 'widgets': None, 'widget_layout': None, 'widget_location': None, 'widget_type': None } _rerender_params = ['object', 'backend'] def __init__(self, object=None, **params): super(HoloViews, self).__init__(object, **params) self._initialized = False self._responsive_content = False self._restore_plot = None self.widget_box = self.widget_layout() self._widget_container = [] self._update_widgets() self._plots = {} self.param.watch(self._update_widgets, self._rerender_params) self._initialized = True @param.depends('center', 'widget_location', watch=True) def _update_layout(self): loc = self.widget_location if not len(self.widget_box): widgets = [] elif loc in ('left', 'right'): widgets = Column(VSpacer(), self.widget_box, VSpacer()) elif loc in ('top', 'bottom'): widgets = Row(HSpacer(), self.widget_box, HSpacer()) elif loc in ('top_left', 'bottom_left'): widgets = Row(self.widget_box, HSpacer()) elif loc in ('top_right', 'bottom_right'): widgets = Row(HSpacer(), self.widget_box) elif loc in ('left_top', 'right_top'): widgets = Column(self.widget_box, VSpacer()) elif loc in ('left_bottom', 'right_bottom'): widgets = Column(VSpacer(), self.widget_box) center = self.center and not self._responsive_content self._widget_container = widgets if not widgets: if center: components = [HSpacer(), self, HSpacer()] else: components = [self] elif center: if loc.startswith('left'): components = [widgets, HSpacer(), self, HSpacer()] elif loc.startswith('right'): components = [HSpacer(), self, HSpacer(), widgets] elif loc.startswith('top'): components = [ HSpacer(), Column(widgets, Row(HSpacer(), self, HSpacer())), HSpacer() ] elif loc.startswith('bottom'): components = [ HSpacer(), Column(Row(HSpacer(), self, HSpacer()), widgets), HSpacer() ] else: if loc.startswith('left'): components = [widgets, self] elif loc.startswith('right'): components = [self, widgets] elif loc.startswith('top'): components = [Column(widgets, self)] elif loc.startswith('bottom'): components = [Column(self, widgets)] self.layout[:] = components #---------------------------------------------------------------- # Callback API #---------------------------------------------------------------- @param.depends('theme', watch=True) def _update_theme(self, *events): if self.theme is None: return for (model, _) in self._models.values(): if model.document: model.document.theme = self.theme @param.depends('widget_type', 'widgets', watch=True) def _update_widgets(self, *events): if self.object is None: widgets, values = [], [] else: widgets, values = self.widgets_from_dimensions( self.object, self.widgets, self.widget_type) self._values = values # Clean up anything models listening to the previous widgets for cb in list(self._callbacks): if cb.inst in self.widget_box.objects: cb.inst.param.unwatch(cb) self._callbacks.remove(cb) # Add new widget callbacks for widget in widgets: watcher = widget.param.watch(self._widget_callback, 'value') self._callbacks.append(watcher) self.widget_box[:] = widgets if ((widgets and self.widget_box not in self._widget_container) or (not widgets and self.widget_box in self._widget_container) or not self._initialized): self._update_layout() def _update_plot(self, plot, pane): from holoviews.core.util import cross_index, wrap_tuple_streams widgets = self.widget_box.objects if not widgets: return elif self.widget_type == 'scrubber': key = cross_index([v for v in self._values.values()], widgets[0].value) else: key = tuple(w.value for w in widgets) if plot.dynamic: widget_dims = [w.name for w in widgets] key = [ key[widget_dims.index(kdim)] if kdim in widget_dims else None for kdim in plot.dimensions ] key = wrap_tuple_streams(tuple(key), plot.dimensions, plot.streams) if plot.backend == 'bokeh': if plot.comm or state._unblocked(plot.document): with unlocked(): plot.update(key) if plot.comm and 'embedded' not in plot.root.tags: plot.push() else: if plot.document.session_context: plot.document.add_next_tick_callback( partial(plot.update, key)) else: plot.update(key) else: plot.update(key) if hasattr(plot.renderer, 'get_plot_state'): pane.object = plot.renderer.get_plot_state(plot) else: # Compatibility with holoviews<1.13.0 pane.object = plot.state def _widget_callback(self, event): for _, (plot, pane) in self._plots.items(): self._update_plot(plot, pane) #---------------------------------------------------------------- # Model API #---------------------------------------------------------------- def _get_model(self, doc, root=None, parent=None, comm=None): from holoviews.plotting.plot import Plot if root is None: return self.get_root(doc, comm) ref = root.ref['id'] if self.object is None: model = _BkSpacer() self._models[ref] = (model, parent) return model if self._restore_plot is not None: plot = self._restore_plot self._restore_plot = None elif isinstance(self.object, Plot): plot = self.object else: plot = self._render(doc, comm, root) plot.pane = self backend = plot.renderer.backend if hasattr(plot.renderer, 'get_plot_state'): state = plot.renderer.get_plot_state(plot) else: # Compatibility with holoviews<1.13.0 state = plot.state # Ensure rerender if content is responsive but layout is centered if (backend == 'bokeh' and self.center and state.sizing_mode not in ('fixed', None) and not self._responsive_content): self._responsive_content = True self._update_layout() self._restore_plot = plot raise RerenderError() else: self._responsive_content = False kwargs = { p: v for p, v in self.param.get_param_values() if p in Layoutable.param and p != 'name' } child_pane = self._panes.get(backend, Pane)(state, **kwargs) self._update_plot(plot, child_pane) model = child_pane._get_model(doc, root, parent, comm) if ref in self._plots: old_plot, old_pane = self._plots[ref] old_plot.comm = None # Ensures comm does not get cleaned up old_plot.cleanup() self._plots[ref] = (plot, child_pane) self._models[ref] = (model, parent) return model def _render(self, doc, comm, root): import holoviews as hv from holoviews import Store, renderer as load_renderer if self.renderer: renderer = self.renderer backend = renderer.backend else: if not Store.renderers: loaded_backend = (self.backend or 'bokeh') load_renderer(loaded_backend) Store.current_backend = loaded_backend backend = self.backend or Store.current_backend renderer = Store.renderers[backend] mode = 'server' if comm is None else 'default' if backend == 'bokeh': params = {} if self.theme is not None: params['theme'] = self.theme if mode != renderer.mode: params['mode'] = mode if params: renderer = renderer.instance(**params) kwargs = {'margin': self.margin} if backend == 'bokeh' or LooseVersion(str( hv.__version__)) >= str('1.13.0'): kwargs['doc'] = doc kwargs['root'] = root if comm: kwargs['comm'] = comm return renderer.get_plot(self.object, **kwargs) def _cleanup(self, root): """ Traverses HoloViews object to find and clean up any streams connected to existing plots. """ old_plot, old_pane = self._plots.pop(root.ref['id'], (None, None)) if old_plot: old_plot.cleanup() if old_pane: old_pane._cleanup(root) super(HoloViews, self)._cleanup(root) #---------------------------------------------------------------- # Public API #---------------------------------------------------------------- @classmethod def applies(cls, obj): if 'holoviews' not in sys.modules: return False from holoviews.core.dimension import Dimensioned from holoviews.plotting.plot import Plot return isinstance(obj, Dimensioned) or isinstance(obj, Plot) @classmethod def widgets_from_dimensions(cls, object, widget_types=None, widgets_type='individual'): from holoviews.core import Dimension, DynamicMap from holoviews.core.options import SkipRendering from holoviews.core.util import isnumeric, unicode, datetime_types, unique_iterator from holoviews.core.traversal import unique_dimkeys from holoviews.plotting.plot import Plot, GenericCompositePlot from holoviews.plotting.util import get_dynamic_mode from ..widgets import Widget, DiscreteSlider, Select, FloatSlider, DatetimeInput, IntSlider if widget_types is None: widget_types = {} if isinstance(object, GenericCompositePlot): object = object.layout elif isinstance(object, Plot): object = object.hmap if isinstance(object, DynamicMap) and object.unbounded: dims = ', '.join('%r' % dim for dim in object.unbounded) msg = ('DynamicMap cannot be displayed without explicit indexing ' 'as {dims} dimension(s) are unbounded. ' '\nSet dimensions bounds with the DynamicMap redim.range ' 'or redim.values methods.') raise SkipRendering(msg.format(dims=dims)) dynamic, bounded = get_dynamic_mode(object) dims, keys = unique_dimkeys(object) if ((dims == [Dimension('Frame')] and keys == [(0, )]) or (not dynamic and len(keys) == 1)): return [], {} nframes = 1 values = dict() if dynamic else dict(zip(dims, zip(*keys))) dim_values = OrderedDict() widgets = [] dims = [ d for d in dims if values.get(d) is not None or d.values or d.range != (None, None) ] for i, dim in enumerate(dims): widget_type, widget, widget_kwargs = None, None, {} if widgets_type == 'individual': if i == 0 and i == (len(dims) - 1): margin = (20, 20, 20, 20) elif i == 0: margin = (20, 20, 5, 20) elif i == (len(dims) - 1): margin = (5, 20, 20, 20) else: margin = (0, 20, 5, 20) kwargs = {'margin': margin, 'width': 250} else: kwargs = {} vals = dim.values or values.get(dim, None) if vals is not None: vals = list(unique_iterator(vals)) dim_values[dim.name] = vals if widgets_type == 'scrubber': if not vals: raise ValueError( 'Scrubber widget may only be used if all dimensions define values.' ) nframes *= len(vals) elif dim.name in widget_types: widget = widget_types[dim.name] if isinstance(widget, Widget): widget.param.set_param(**kwargs) if not widget.name: widget.name = dim.label widgets.append(widget) continue elif isinstance(widget, dict): widget_type = widget.get('type', widget_type) widget_kwargs = dict(widget) elif isinstance(widget, type) and issubclass(widget, Widget): widget_type = widget else: raise ValueError('Explicit widget definitions expected ' 'to be a widget instance or type, %s ' 'dimension widget declared as %s.' % (dim, widget)) widget_kwargs.update(kwargs) if vals: if all( isnumeric(v) or isinstance(v, datetime_types) for v in vals) and len(vals) > 1: vals = sorted(vals) labels = [unicode(dim.pprint_value(v)) for v in vals] options = OrderedDict(zip(labels, vals)) widget_type = widget_type or DiscreteSlider else: options = list(vals) widget_type = widget_type or Select default = vals[0] if dim.default is None else dim.default widget_kwargs = dict( dict(name=dim.label, options=options, value=default), **widget_kwargs) widget = widget_type(**widget_kwargs) elif dim.range != (None, None): start, end = dim.range if start == end: continue default = start if dim.default is None else dim.default if widget_type is not None: pass elif all(isinstance(v, int) for v in (start, end, default)): widget_type = IntSlider step = 1 if dim.step is None else dim.step elif isinstance(default, datetime_types): widget_type = DatetimeInput else: widget_type = FloatSlider step = 0.1 if dim.step is None else dim.step widget_kwargs = dict( dict(step=step, name=dim.label, start=dim.range[0], end=dim.range[1], value=default), **widget_kwargs) widget = widget_type(**widget_kwargs) if widget is not None: widgets.append(widget) if widgets_type == 'scrubber': widgets = [Player(length=nframes, width=550)] return widgets, dim_values
class Model(param.Parameterized): """Data defining this stage""" # Values set by previous stage report_from_composition = param.Dict() # Parameters for the current stage ready = param.Boolean(default=False) net_gross = param.Number(label="Net/Gross", softbounds=(0, 100)) scenario_name = param.String(label="Scenario Name") porosity_modifier = param.Number( default=0, label="Porosity Modifier", bounds=(0, 100) ) net_gross_modified = param.Number(label="Modified N/G", bounds=(0, 100)) def __init__(self, report_from_composition, net_gross): """Set initial values based on previous stage""" super().__init__() self._state = state.get_user_state().setdefault(APP, {}) self._state.setdefault("scenarios", {}) self._current_scenario_name = None self.net_gross = net_gross self.report_from_composition = report_from_composition self.scenario_name = f"Scenario {len(self._state['scenarios']) + 1}" self.data = charts.data_as_dataframe(report_from_composition, CFG.columns) try: session_id = pn.state.curdoc.session_context.id logger.insights(f"New result: {self.net_gross}, SessionID: {session_id}") logger.insights( f"SessionID: {session_id}, Choices: {report_from_composition}" ) except AttributeError as e: logger.error(f"SessionID not available: {e}") logger.insights(f"New result: {self.net_gross}") logger.insights(f"Choices: {report_from_composition}") @param.depends("net_gross", watch=True) def update_porosity_bounds(self): net_gross = dict(self.param.get_param_values())["net_gross"] if self.porosity_modifier > net_gross: self.porosity_modifier = self.net_gross self.param.porosity_modifier.bounds = (0, round(net_gross)) @param.depends("net_gross", "porosity_modifier", watch=True) def update_ng_from_porosity(self): """Calculate modified net gross based on porosity""" if self.porosity_modifier > self.net_gross: self.porosity_modifier = self.net_gross self.net_gross_modified = self.net_gross - self.porosity_modifier def _store_scenario(self): """Store results for the current scenario""" scenarios = self._state["scenarios"] # Make sure name does not overwrite previous scenarios if ( self.scenario_name != self._current_scenario_name and self.scenario_name in scenarios ): for suffix in itertools.count(start=2): name = f"{self.scenario_name} ({suffix})" if name not in scenarios: break self.scenario_name = name # Get information about scenario if self._current_scenario_name in scenarios: scenario_info = scenarios.pop(self._current_scenario_name) else: scenario_info = { "net_gross": self.net_gross, **self.report_from_composition, } # Update scenario information scenario_info["net_gross_modified"] = self.net_gross_modified scenario_info["porosity_modifier"] = self.porosity_modifier # Store scenario information scenarios[self.scenario_name] = scenario_info self._current_scenario_name = self.scenario_name @pn.depends("scenario_name", "porosity_modifier") def scenario_table(self): """Table showing current scenarios""" self._store_scenario() scenarios = self._state["scenarios"] table_data = pd.DataFrame( { "Scenario Name": [s for s in scenarios.keys()], "Net/Gross": [round(s["net_gross"]) for s in scenarios.values()], "Modified N/G": [ round(s["net_gross_modified"]) for s in scenarios.values() ], } ).set_index("Scenario Name") return pn.pane.DataFrame(table_data, sizing_mode="stretch_width") def new_scenario_button(self): """Button for starting new scenario""" reset_button = self._state["update_view"]["workflow"] def restart_scenario(event): """Start a new scenario by restarting the workflow""" reset_button.clicks += 1 button = pn.widgets.Button( name="New Scenario", button_type="success", width=125 ) button.on_click(restart_scenario) return button @param.output(param.List) def scenario_names(self): """Pass on names of scenarios to next stage""" return [sn for sn in self._state["scenarios"].keys()]
class Perspective(PaneBase, ReactiveData): """ The Perspective widget enables exploring large tables of data. """ aggregates = param.Dict(None, doc=""" How to aggregate. For example {"x": "distinct count"}""") columns = param.List(default=None, doc=""" A list of source columns to show as columns. For example ["x", "y"]""") computed_columns = param.List(default=None, doc=""" A list of computed columns. For example [""x"+"index""]""") column_pivots = param.List(None, doc=""" A list of source columns to pivot by. For example ["x", "y"]""") filters = param.List(default=None, doc=""" How to filter. For example [["x", "<", 3],["y", "contains", "abc"]]""") object = param.Parameter(doc=""" The plot data declared as a dictionary of arrays or a DataFrame.""") row_pivots = param.List(default=None, doc=""" A list of source columns to group by. For example ["x", "y"]""") selectable = param.Boolean(default=True, allow_None=True, doc=""" Whether items are selectable.""") sort = param.List(default=None, doc=""" How to sort. For example[["x","desc"]]""") plugin = param.ObjectSelector(default=Plugin.GRID.value, objects=Plugin.options(), doc=""" The name of a plugin to display the data. For example hypergrid or d3_xy_scatter.""" ) toggle_config = param.Boolean(default=True, doc=""" Whether to show the config menu.""") theme = param.ObjectSelector(default=DEFAULT_THEME, objects=THEMES, doc=""" The style of the PerspectiveViewer. For example material-dark""") _data_params = ['object'] _rerender_params = ['object'] _updates = True def applies(cls, object): if isinstance(object, dict): return True elif 'pandas' in sys.modules: import pandas as pd return isinstance(object, pd.DataFrame) return False def _get_data(self): if self.object is None: return {}, {} if isinstance(self.object, dict): ncols = len(self.object) data = self.object else: ncols = len(self.object.columns) data = ColumnDataSource.from_df(self.object) cols = set(self._as_digit(c) for c in self.object) if len(cols) != ncols: raise ValueError("Integer columns must be unique when " "converted to strings.") return self.object, {str(k): v for k, v in data.items()} def _filter_properties(self, properties): ignored = list(Viewable.param) return [p for p in properties if p not in ignored] def _init_params(self): props = super()._init_params() props['source'] = ColumnDataSource(data=self._data) return props def _process_param_change(self, msg): msg = super()._process_param_change(msg) for p in ('columns', 'row_pivots', 'column_pivots'): if msg.get(p): msg[p] = [str(col) for col in msg[p]] if msg.get('sort'): msg['sort'] = [[str(col), d] for col, d in msg['sort']] if msg.get('filters'): msg['filters'] = [[str(col), e, val] for col, e, val in msg['filters']] if msg.get('aggregates'): msg['aggregates'] = { str(col): agg for col, agg in msg['aggregates'].items() } return msg def _as_digit(self, col): if self._processed is None: return col elif col in self._processed: return col elif col.isdigit() and int(col) in self._processed: return int(col) return col def _process_property_change(self, msg): msg = super()._process_property_change(msg) for prop in ('columns', 'row_pivots', 'column_pivots'): if msg.get(prop): msg[prop] = [self._as_digit(col) for col in msg[prop]] if msg.get('sort'): msg['sort'] = [[self._as_digit(col), d] for col, d in msg['sort']] if msg.get('filters'): msg['filters'] = [[self._as_digit(col), e, val] for col, e, val in msg['filters']] if msg.get('aggregates'): msg['aggregates'] = { self._as_digit(col): agg for col, agg in msg['aggregates'].items() } return msg def _get_model(self, doc, root=None, parent=None, comm=None): Perspective = lazy_load('panel.models.perspective', 'Perspective', isinstance(comm, JupyterComm)) properties = self._process_param_change(self._init_params()) if properties.get('toggle_config'): properties['height'] = self.height or 300 else: properties['height'] = self.height or 150 model = Perspective(**properties) if root is None: root = model synced = list( set(self.param) ^ (set(PaneBase.param) | set(ReactiveData.param))) self._link_props(model, synced, doc, root, comm) self._models[root.ref['id']] = (model, parent) return model def _update(self, ref, model): pass
class QuickLookComponent(Component): data_repository = param.String(default=sample_data_directory, label=None, allow_None=True) query_filter = param.String(label="Query Expression") new_column_expr = param.String(label="Data Column Expression") tract_count = param.Number(default=0) status_message_queue = param.List(default=[]) patch_count = param.Number(default=0) visit_count = param.Number(default=0) filter_count = param.Number(default=0) unique_object_count = param.Number(default=0) comparison = param.String() selected = param.Tuple(default=(None, None, None, None), length=4) selected_metrics_by_filter = param.Dict( default={f: [] for f in store.active_dataset.filters}) selected_flag_filters = param.Dict(default={}) view_mode = ['Skyplot View', 'Detail View'] data_stack = ['Forced Coadd', 'Unforced Coadd'] plot_top = None plots_list = [] skyplot_list = [] label = param.String(default='Quick Look') def __init__(self, store, **param): super().__init__(**param) self.store = store self._clear_metrics_button = pn.widgets.Button(name='Clear', width=30, align='end') self._clear_metrics_button.on_click(self._on_clear_metrics) self._submit_repository = pn.widgets.Button(name='Load Data', width=50, align='end') self._submit_repository.on_click(self._on_load_data_repository) self._submit_comparison = pn.widgets.Button(name='Submit', width=50, align='end') self._submit_comparison.on_click(self._update) self.flag_filter_select = pn.widgets.Select( name='Add Flag Filter', width=160, options=self.store.active_dataset.flags) self.flag_state_select = pn.widgets.Select(name='Flag State', width=75, options=['True', 'False']) self.flag_submit = pn.widgets.Button(name='Add Flag Filter', width=10, height=30, align='end') self.flag_submit.on_click(self.on_flag_submit_click) self.flag_filter_selected = pn.widgets.Select( name='Active Flag Filters', width=250) self.flag_remove = pn.widgets.Button(name='Remove Flag Filter', width=50, height=30, align='end') self.flag_remove.on_click(self.on_flag_remove_click) self.query_filter_submit = pn.widgets.Button(name='Run Query Filter', width=100, align='end') self.query_filter_submit.on_click(self.on_run_query_filter_click) self.query_filter_clear = pn.widgets.Button(name='Clear', width=50, align='end') self.query_filter_clear.on_click(self.on_query_filter_clear) self.new_column_submit = pn.widgets.Button(name='Define New Column', width=100, align='end') self.new_column_submit.on_click(self.on_define_new_column_click) self.status_message = pn.pane.HTML(sizing_mode='stretch_width', max_height=10) self.adhoc_js = pn.pane.HTML(sizing_mode='stretch_width', max_height=10) self._info = pn.pane.HTML(sizing_mode='stretch_width', max_height=10) self._flags = pn.pane.HTML(sizing_mode='stretch_width', max_height=10) self._metric_panels = [] self._metric_layout = pn.Column() self._switch_view = self._create_switch_view_buttons() self._switch_stack = self._create_switch_datastack_buttons() self._plot_top = pn.Row(sizing_mode='stretch_width', margin=(10, 10, 10, 10)) self._plot_layout = pn.Column(sizing_mode='stretch_width', margin=(10, 10, 10, 10)) self.skyplot_layout = pn.Column(sizing_mode='stretch_width', margin=(10, 10, 10, 10)) self.list_layout = pn.Column(sizing_mode='stretch_width') self._update(None) def _on_load_data_repository(self, event): global datasets global datavisits global filtered_datavisits self.store.active_dataset = Dataset('') self.skyplot_list = [] self.plots_list = [] self.plot_top = None datasets = {} filtered_datasets = {} datavisits = {} filtered_datavisits = {} self._load_metrics() self._switch_view_mode() self.update_display() data_repo_path = self.data_repository self.add_status_message('Load Data Start...', data_repo_path, level='info') dstack_switch_val = self._switch_stack.value.lower() datastack = 'unforced' if 'unforced' in dstack_switch_val else 'forced' try: self.store.active_dataset = load_data(data_repo_path, datastack) except Exception as e: self.update_display() self.add_message_from_error('Data Loading Error', data_repo_path, e) raise self.add_status_message('Data Ready', data_repo_path, level='success', duration=3) # update ui self.flag_filter_select.options = self.store.active_dataset.flags for f in self.store.active_dataset.filters: self.selected_metrics_by_filter[f] = [] self._load_metrics() self._switch_view_mode() self.update_display() def update_display(self): self.set_checkbox_style() def set_checkbox_style(self): code = '''$("input[type='checkbox']").addClass("metric-checkbox");''' self.execute_js_script(code) global store for filter_type, fails in store.active_dataset.failures.items(): error_metrics = json.dumps(fails) code = '$(".' + filter_type + '-checkboxes .metric-checkbox").siblings().filter(function () { return ' + error_metrics + '.indexOf($(this).text()) > -1;}).css("color", "orange");' self.execute_js_script(code) def add_status_message(self, title, body, level='info', duration=5): msg = {'title': title, 'body': body} msg_args = dict(msg=msg, level=level, duration=duration) self.status_message_queue.append(msg_args) self.param.trigger('status_message_queue') # to work with panel 0.7 # Drop message in terminal/logger too try: # temporary try/except until 'level' values are all checked getattr(logger, level)(msg) except: pass def on_flag_submit_click(self, event): flag_name = self.flag_filter_select.value flag_state = self.flag_state_select.value == 'True' self.selected_flag_filters.update({flag_name: flag_state}) self.param.trigger('selected_flag_filters') self.add_status_message('Added Flag Filter', '{} : {}'.format(flag_name, flag_state), level='info') def on_flag_remove_click(self, event): flag_name = self.flag_filter_selected.value.split()[0] del self.selected_flag_filters[flag_name] self.param.trigger('selected_flag_filters') self.add_status_message('Removed Flag Filter', flag_name, level='info') def _on_clear_metrics(self, event): for k in self.selected_metrics_by_filter.keys(): self.selected_metrics_by_filter[k] = [] self.param.trigger('selected_metrics_by_filter') code = '''$("input[type='checkbox']").prop("checked", false);''' self.execute_js_script(code) def on_run_query_filter_click(self, event): pass def on_query_filter_clear(self, event): self.query_filter = '' pass def on_define_new_column_click(self, event): new_column_expr = self.new_column_expr logger.info("NEW COLUMN EXPRESSION: '{!s}'".format(new_column_expr)) def _create_switch_view_buttons(self): radio_group = pn.widgets.RadioBoxGroup(name='SwitchView', options=self.view_mode, align='center', value=self.view_mode[0], inline=True) radio_group.param.watch(self._switch_view_mode, ['value']) return radio_group def _create_switch_datastack_buttons(self): radio_group = pn.widgets.RadioBoxGroup(name='SwitchDataStack', options=self.data_stack, align='center', value=self.data_stack[0], inline=True) radio_group.param.watch(self._switch_data_stack, ['value']) return radio_group def update_selected_by_filter(self, filter_type, selected_values): self.selected_metrics_by_filter.update({filter_type: selected_values}) self.param.trigger('selected_metrics_by_filter') def _update(self, event): self._update_info() self._load_metrics() def create_info_element(self, name, value): box_css = """ background-color: #EEEEEE; border: 1px solid #777777; display: inline-block; padding-left: 5px; padding-right: 5px; margin-left:7px; """ fval = format(value, ',') outel = '<li><span style="{}"><b>{}</b> {}</span></li>' return outel.format(box_css, fval, name) @param.depends('tract_count', 'patch_count', 'visit_count', 'filter_count', 'unique_object_count', watch=True) def _update_info(self): """ Updates the _info HTML pane with info loaded from the current repository. """ html = '' html += self.create_info_element('Tracts', self.tract_count) html += self.create_info_element('Patches', self.patch_count) html += self.create_info_element('Visits', self.visit_count) # html += self.create_info_element('Unique Objects', # self.unique_object_count) self._info.object = '<ul class="list-group list-group-horizontal" style="list-style: none;">{}</ul>'.format( html) def create_status_message(self, msg, level='info', duration=5): import uuid msg_id = str(uuid.uuid1()) color_levels = dict(info='rgba(0,191,255, .8)', error='rgba(249, 180, 45, .8)', warning='rgba(240, 255, 0, .8)', success='rgba(3, 201, 169, .8)') box_css = """ width: 15rem; background-color: {}; border: 1px solid #CCCCCC; display: inline-block; color: white; padding: 5px; margin-top: 1rem; """.format(color_levels.get(level, 'rgba(0,0,0,0)')) remove_msg_func = ('<script>(function() { ' 'setTimeout(function(){ document.getElementById("' + msg_id + '").outerHTML = ""; }, ' + str(duration * 1000) + ')})()' '</script>') text = '<span style="{}"><h5>{}</h5><hr/><p>{}</p></span></li>'.format( box_css, msg.get('title'), msg.get('body')) return ('<li id="{}" class="status-message nav-item">' '{}' '{}' '</lil>').format(msg_id, remove_msg_func, text) def gen_clear_func(self, msg): async def clear_message(): try: if msg in self.status_message_queue: self.status_message_queue.remove(msg) except ValueError: pass return clear_message @param.depends('status_message_queue', watch=True) def _update_status_message(self): queue_css = """ list-style-type: none; position: fixed; bottom: 2rem; right: 2rem; background-color: rgba(0,0,0,0); border: none; display: inline-block; margin-left: 7px; """ html = '' for msg in self.status_message_queue: html += self.create_status_message(**msg) set_timeout(msg.get('duration', 5), self.gen_clear_func(msg)) self.status_message.object = '<ul style="{}">{}</ul>'.format( queue_css, html) def execute_js_script(self, js_body): script = '<script>(function() { ' + js_body + '})()</script>' # to work with panel 0.7 self.adhoc_js.object = script def get_patch_count(self): return 1 patchs = set() for filt, _ in self.selected_metrics_by_filter.items(): dset = self.get_dataset_by_filter(filt) patchs = patchs.union(set(dset.df['patch'].unique())) return len(patchs) def get_tract_count(self): return 1 tracts = set() for filt, _ in self.selected_metrics_by_filter.items(): dset = self.get_dataset_by_filter(filt) tracts = tracts.union(set(dset.df['tract'].unique())) return len(tracts) def get_visit_count(self): return 1 dvisits = self.get_datavisits() visits = set() for filt, metrics in self.selected_metrics_by_filter.items(): for metric in metrics: df = dvisits[filt][metric].compute() visits = visits.union(set(df['visit'].unique())) return len(visits) def update_info_counts(self): self.tract_count = self.get_tract_count() self.patch_count = self.get_patch_count() self.visit_count = self.get_visit_count() self.unique_object_count = get_unique_object_count() def _load_metrics(self): """ Populates the _metrics Row with metrics loaded from the repository """ panels = [ MetricPanel(metric='LSST', filters=self.store.active_dataset.filters, parent=self) ] self._metric_panels = panels self._metric_layout.objects = [p.panel() for p in panels] self.update_display() @param.depends('query_filter', watch=True) def _update_query_filter(self): self.filter_main_dataframe() @param.depends('selected_flag_filters', watch=True) def _update_selected_flags(self): selected_flags = [ '{} : {}'.format(f, v) for f, v in self.selected_flag_filters.items() ] self.flag_filter_selected.options = selected_flags self.filter_main_dataframe() def filter_main_dataframe(self): global filtered_datasets global datasets for filt, qa_dataset in datasets.items(): try: query_expr = self._assemble_query_expression() if query_expr: filtered_datasets[filt] = QADataset( datasets[filt].df.query(query_expr)) except Exception as e: self.add_message_from_error('Filtering Error', '', e) raise return #self.filter_visits_dataframe() self._update_selected_metrics_by_filter() def filter_visits_dataframe(self): global filtered_datavisits global datavisits for filt, metrics in datavisits.items(): for metric, df in metrics.items(): try: query_expr = self._assemble_query_expression( ignore_query_expr=True) if query_expr and datavisits[filt][metric] is not None: filtered_datavisits[filt][metric] = datavisits[filt][ metric].query(query_expr) except Exception as e: self.add_message_from_error('Filtering Visits Error', '', e) return def _assemble_query_expression(self, ignore_query_expr=False): query_expr = '' flags_query = [] for flag, state in self.selected_flag_filters.items(): flags_query.append('{}=={}'.format(flag, state)) if flags_query: query_expr += ' & '.join(flags_query) if ignore_query_expr: return query_expr query_filter = self.query_filter.strip() if query_filter: if query_expr: query_expr += ' & {!s}'.format(query_filter) else: query_expr = '{!s}'.format(query_filter) return query_expr def get_dataset_by_filter(self, filter_type): global datasets global filtered_datasets if self.query_filter == '' and len(self.selected_flag_filters) == 0: return datasets[filter_type] else: return filtered_datasets[filter_type] def get_datavisits(self): global datavisits global filtered_datavisits # if self.query_filter == '' and len(self.selected_flag_filters) == 0: if len(self.selected_flag_filters) == 0: return datavisits else: return filtered_datavisits def add_message_from_error(self, title, info, exception_obj, level='error'): tb = traceback.format_exception_only(type(exception_obj), exception_obj)[0] msg_body = '<b>Info:</b> ' + info + '<br />' msg_body += '<b>Cause:</b> ' + tb logger.error(title) logger.error(msg_body) self.add_status_message(title, msg_body, level=level, duration=10) @param.depends('selected_metrics_by_filter', watch=True) def _update_selected_metrics_by_filter(self): plots_list = [] skyplot_list = [] top_plot = None dvisits = self.get_datavisits() try: top_plot = visits_plot(dvisits, self.selected_metrics_by_filter) except Exception as e: self.add_message_from_error('Visits Plot Error', '', e) self.plot_top = top_plot filter_stream_scatter = FilterStream() for filt, plots in self.selected_metrics_by_filter.items(): filter_stream = FilterStream() dset = self.get_dataset_by_filter(filt) for i, p in enumerate(plots): # skyplots plot_sky = skyplot(dset.ds, filter_stream=filter_stream, vdim=p) skyplot_list.append((filt + ' - ' + p, plot_sky)) plots_ss = scattersky(dset.ds, xdim='psfMag', ydim=p, filter_stream=filter_stream_scatter) plot = plots_ss plots_list.append((p, plot)) self.skyplot_list = skyplot_list self.plots_list = plots_list self.update_display() self._switch_view_mode() def linked_tab_plots(self): tabs = [(name, pn.panel(plot)) for name, plot in self.skyplot_list] return pn.Tabs(*tabs, sizing_mode='stretch_both') def attempt_to_clear(self, obj): try: obj.clear() except: pass def _switch_data_stack(self, *events): # clear existing plot layouts self.attempt_to_clear(self._plot_top) self.attempt_to_clear(self._plot_layout) self.attempt_to_clear(self.skyplot_layout) self.attempt_to_clear(self.list_layout) self._on_clear_metrics(event=None) self._on_load_data_repository(None) def _switch_view_mode(self, *events): # clear existing plot layouts self.attempt_to_clear(self._plot_top) self.attempt_to_clear(self._plot_layout) self.attempt_to_clear(self.skyplot_layout) self.attempt_to_clear(self.list_layout) if self._switch_view.value == 'Skyplot View': self.execute_js_script( '''$( ".skyplot-plot-area" ).show(); $( ".metrics-plot-area" ).hide();''' ) self.skyplot_layout.append(self.linked_tab_plots()) else: self.execute_js_script( '''$( ".skyplot-plot-area" ).hide(); $( ".metrics-plot-area" ).show();''' ) logger.info(self.plot_top) self._plot_top.append(self.plot_top) for i, p in self.plots_list: self.list_layout.append(p) self._plot_layout.append(self.list_layout) def jinja(self): from ._jinja2_templates import quicklook import holoviews as hv tmpl = pn.Template(dashboard_html_template) data_repo_widget = pn.panel(self.param.data_repository, show_labels=False) data_repo_widget.width = 300 # data_repo_row = pn.Row(pn.panel('Data Repository', align='end'), # data_repo_widget, self._submit_repository) data_repo_row = pn.Row(data_repo_widget, self._submit_repository) data_repo_row.css_classes = ['data-repo-input'] query_filter_widget = pn.panel(self.param.query_filter) query_filter_widget.width = 260 new_column_widget = pn.panel(self.param.new_column_expr) new_column_widget.width = 260 datastack_switcher = pn.Row(self._switch_stack) datastack_switcher.css_classes = ['stack-switcher'] view_switcher = pn.Row(self._switch_view) view_switcher.css_classes = ['view-switcher'] clear_button_row = pn.Row(self._clear_metrics_button) components = [ ('metrics_clear_button', clear_button_row), ('data_repo_path', data_repo_row), ('status_message_queue', self.status_message), ('adhoc_js', self.adhoc_js), ('infobar', self._info), # ('view_switcher', switcher_row), ('stack_switcher', datastack_switcher), ('view_switcher', view_switcher), ('metrics_selectors', self._metric_layout), ('metrics_plots', self._plot_layout), ('skyplot_metrics_plots', self.skyplot_layout), ('plot_top', self._plot_top), ('flags', pn.Column(pn.Row(self.flag_filter_select, self.flag_state_select), pn.Row(self.flag_submit), pn.Row(self.flag_filter_selected), pn.Row(self.flag_remove))), ( 'query_filter', pn.Column( query_filter_widget, pn.Row(self.query_filter_submit, self.query_filter_clear)), ), ( 'new_column', pn.Column(new_column_widget, pn.Row(self.new_column_submit)), ), ] for l, c in components: tmpl.add_panel(l, c) return tmpl
class MedNumApp(TopIndicators): score_widget = pn.widgets.IntRangeSlider _layout = pn.Column() top_params = param.Dict(default={}) def __init__(self, **params): super(MedNumApp, self).__init__(**params) self.catch_request() def catch_request(self): try: self.localisation = str( pn.state.session_args.get("localisation")[0].decode()) except Exception as e: self.localisation = "Auch" try: self.level_1_column = str( pn.state.session_args.get("level_1_column")[0].decode()) except Exception: self.level_1_column = "EPCI" try: self.level_0_column = str( pn.state.session_args.get("level_0_column")[0].decode()) except Exception: self.level_0_column = "insee_com" pass try: self.level_0_column_names = str( pn.state.session_args.get("level_0_column_names")[0].decode()) except Exception: self.level_0_column_names = "nom_com" pass def lat_widgets(self): self.score_controls = pn.Param( self.param.score, widgets={ "score": { "type": pn.widgets.IntRangeSlider, "bar_color": "#000000", "throttled": True, }, }, ) score_panel = pn.Column("## Score", self.score_controls) point_ref_panel = pn.Column( "## Point de reference", pn.Param( self.param.point_ref, widgets={"point_ref": pn.widgets.RadioBoxGroup}, ), ) niveau_observation_panel = pn.Column( "## " + self.param.niveau_observation.label, pn.Param( self.param.niveau_observation, widgets={"niveau_observation": pn.widgets.RadioBoxGroup}, ), ) # niveau_details_panel = pn.Column( # "## " + self.param.niveau_details.label, # pn.Param( # self.param.niveau_details, # widgets={"niveau_details": pn.widgets.RadioBoxGroup}, # ), # ) export_panel = pn.Column( """## Aller plus loin""", pn.pane.HTML(""" <a href="https://lamednum.coop/actions/indice-de-fragilite-numerique/" title="En savoir plus sur la méthode" class="link2"> > En savoir plus sur la méthode</a> """) # self.param.export_data, # self.param.edit_report ) localisation_panel = pn.Column( "## Localisation", pn.Param( self.param.localisation, widgets={ "localisation": { "type": pn.widgets.AutocompleteInput, "options": self.seachable_localisation, "value": self.localisation, "case_sensitive": False, } }, ), css_classes=['blc-search']) indicateurs = pn.Column("## Indicateurs", *self.g_params) ordered_panel = pn.Column( localisation_panel, score_panel, indicateurs, point_ref_panel, niveau_observation_panel, # niveau_details_panel, export_panel, ) return ordered_panel @pn.depends("score", "localisation", "point_ref", "df_score") # ,watch=True) def update_map_values(self): try: # Selection par localisation # http://holoviews.org/user_guide/Plotting_with_Bokeh.html # https://docs.bokeh.org/en/latest/docs/user_guide/tools.html#custom-tooltip map_info = ["tout_axes", "nom_com"] vdims = (map_info + [k + "_SCORE" for k in self.selected_indices_level_0] + list(AXES_INDICES.keys())) TOOLTIPS_HTML = """<span style="font-size: 22px; font-weight: bold;"> @nom_com : @tout_axes</span> <div> """ for k, indicators in AXES_INDICES.items(): display_name = indicators["nom"] vdim_name = k if vdim_name in vdims: TOOLTIPS_HTML += """<div class="mednum-hover"> <span style="font-size: 18px; font-weight: bold;"> {display_name} :</span> <span style=""> @{vdim_name}</span> </div>""".format(display_name=display_name, vdim_name=vdim_name) else: TOOLTIPS_HTML += """<div class="mednum-hover"> <span style="font-size: 18px; font-weight: bold;"> {display_name} :</span> <span style="color:orange"> N/A </span> </div>""".format(display_name=display_name, vdim_name=vdim_name) for indic in indicators: if indic not in ["desc", "nom"]: display_name = CATEGORIES_INDICES[indic] vdim_name = indic + "_SCORE" if vdim_name in vdims: TOOLTIPS_HTML += """<div class="mednum-hover"> <span style="font-size: 12px; "> {display_name} :</span> <span style="">@{vdim_name}</span> </div>""".format(display_name=display_name, vdim_name=vdim_name) else: TOOLTIPS_HTML += """<div class="mednum-hover"> <span style="font-size: 12px;"> {display_name} :</span> <span style="color:orange">N/A</span> </div>""".format(display_name=display_name) TOOLTIPS_HTML += """ </div> """ hover_custom = HoverTool(tooltips=TOOLTIPS_HTML) self.maps = gv.Polygons(self.df_score, vdims=vdims) return self.maps.opts( tools=[hover_custom], color="tout_axes", colorbar=True, toolbar="above", xaxis=None, yaxis=None, fill_alpha=0.5, ) except Exception as e: print(e) pass @pn.depends("localisation") # , watch=True) def map_view(self): return self.tiles * gv.DynamicMap(self.update_map_values) @pn.depends("tout_axes", watch=True) def selection_indicateurs(self): for par in self.g_params: indic_name = next(iter(par.widgets)) if "tout_axes" != indic_name: widg = par.widgets[indic_name].get("type", None) widg.param.select_all = self.tout_axes def table_view(self): script = """ <script> if (document.readyState === "complete") { $('.mednum-df').DataTable(); } else { $(document).ready(function () { $('.mednum-df').DataTable(); }) } </script> """ df = self.df_score[self.selected_indices_level_0] df.index.names = [ MAP_COL_WIDGETS_REV[name] if name in MAP_COL_WIDGETS_REV else name for name in df.index.names ] df.columns = [ CATEGORIES_INDICES[name] if name in CATEGORIES_INDICES else name for name in df.columns ] html = df.to_html(classes=["mednum-df", "panel-df"]) return pn.Column(self.download, pn.pane.HTML(html + script, sizing_mode="stretch_width"), sizing_mode="stretch_width") @pn.depends("localisation") def tabs_view(self): return pn.Tabs( ("Carte", self.map_view), ("Tableau", self.table_view), css_classes=[ re.sub(r"(?<!^)(?=[A-Z])", "-", self.get_name() + "Tabs").lower() ], )
class Compositor(param.Parameterized): """ A Compositor is a way of specifying an operation to be automatically applied to Overlays that match a specified pattern upon display. Any ElementOperation that takes an Overlay as input may be used to define a compositor. For instance, a compositor may be defined to automatically display three overlaid monochrome matrices as an RGB image as long as the values names of those matrices match 'R', 'G' and 'B'. """ mode = param.ObjectSelector(default='data', objects=['data', 'display'], doc=""" The mode of the Compositor object which may be either 'data' or 'display'.""") operation = param.Parameter(doc=""" The ElementOperation to apply when collapsing overlays.""") pattern = param.String(doc=""" The overlay pattern to be processed. An overlay pattern is a sequence of elements specified by dotted paths separated by * . For instance the following pattern specifies three overlayed matrices with values of 'RedChannel', 'GreenChannel' and 'BlueChannel' respectively: 'Image.RedChannel * Image.GreenChannel * Image.BlueChannel. This pattern specification could then be associated with the RGB operation that returns a single RGB matrix for display.""") group = param.String(doc=""" The group identifier for the output of this particular compositor""") kwargs = param.Dict(doc=""" Optional set of parameters to pass to the operation.""") operations = [] # The operations that can be used to define compositors. definitions = [] # The set of all the compositor instances @classmethod def strongest_match(cls, overlay, mode): """ Returns the single strongest matching compositor operation given an overlay. If no matches are found, None is returned. The best match is defined as the compositor operation with the highest match value as returned by the match_level method. """ match_strength = [(op.match_level(overlay), op) for op in cls.definitions if op.mode == mode] matches = [(match[0], op, match[1]) for (match, op) in match_strength if match is not None] if matches == []: return None else: return sorted(matches)[0] @classmethod def collapse_element(cls, overlay, key=None, ranges=None, mode='data'): """ Finds any applicable compositor and applies it. """ from .overlay import Overlay match = cls.strongest_match(overlay, mode) if match is None: return overlay (_, applicable_op, (start, stop)) = match values = overlay.values() sliced = Overlay.from_values(values[start:stop]) result = applicable_op.apply(sliced, ranges, key=key) result = result.relabel(group=applicable_op.group) output = Overlay.from_values(values[:start] + [result] + values[stop:]) output.id = overlay.id return output @classmethod def collapse(cls, holomap, ranges=None, mode='data'): """ Given a map of Overlays, apply all applicable compositors. """ # No potential compositors if cls.definitions == []: return holomap # Apply compositors clone = holomap.clone(shared_data=False) data = zip(ranges[1], holomap.data.values()) if ranges else holomap.data.items() for key, overlay in data: clone[key] = cls.collapse_element(overlay, key, ranges, mode) return clone @classmethod def register(cls, compositor): defined_groups = [op.group for op in cls.definitions] if compositor.group in defined_groups: cls.definitions.pop(defined_groups.index(compositor.group)) cls.definitions.append(compositor) if compositor.operation not in cls.operations: cls.operations.append(compositor.operation) def __init__(self, pattern, operation, group, mode, **kwargs): self._pattern_spec, labels = [], [] for path in pattern.split('*'): path_tuple = tuple(el.strip() for el in path.strip().split('.')) self._pattern_spec.append(path_tuple) if len(path_tuple) == 3: labels.append(path_tuple[2]) if len(labels) > 1 and not all(l == labels[0] for l in labels): raise KeyError( "Mismatched labels not allowed in compositor patterns") elif len(labels) == 1: self.label = labels[0] else: self.label = '' super(Compositor, self).__init__(group=group, pattern=pattern, operation=operation, mode=mode, kwargs=kwargs) @property def output_type(self): """ Returns the operation output_type unless explicitly overridden in the kwargs. """ if 'output_type' in self.kwargs: return self.kwargs['output_type'] else: return self.operation.output_type def _slice_match_level(self, overlay_items): """ Find the match strength for a list of overlay items that must be exactly the same length as the pattern specification. """ level = 0 for spec, el in zip(self._pattern_spec, overlay_items): if spec[0] != type(el).__name__: return None level += 1 # Types match if len(spec) == 1: continue group = [el.group, sanitize_identifier(el.group, escape=False)] if spec[1] in group: level += 1 # Values match else: return None if len(spec) == 3: group = [el.label, sanitize_identifier(el.label, escape=False)] if (spec[2] in group): level += 1 # Labels match else: return None return level def match_level(self, overlay): """ Given an overlay, return the match level and applicable slice of the overall overlay. The level an integer if there is a match or None if there is no match. The level integer is the number of matching components. Higher values indicate a stronger match. """ slice_width = len(self._pattern_spec) if slice_width > len(overlay): return None # Check all the possible slices and return the best matching one best_lvl, match_slice = (0, None) for i in range(len(overlay) - slice_width + 1): overlay_slice = overlay.values()[i:i + slice_width] lvl = self._slice_match_level(overlay_slice) if lvl is None: continue if lvl > best_lvl: best_lvl = lvl match_slice = (i, i + slice_width) return (best_lvl, match_slice) if best_lvl != 0 else None def apply(self, value, input_ranges, key=None): """ Apply the compositor on the input with the given input ranges. """ from .overlay import CompositeOverlay if isinstance(value, CompositeOverlay) and len(value) == 1: value = value.values()[0] if key is None: return self.operation(value, input_ranges=input_ranges, **self.kwargs) return self.operation.instance(input_ranges=input_ranges, **self.kwargs).process_element( value, key)
class Dynamic(param.ParameterizedFunction): """ Dynamically applies a callable to the Elements in any HoloViews object. Will return a DynamicMap wrapping the original map object, which will lazily evaluate when a key is requested. By default Dynamic applies a no-op, making it useful for converting HoloMaps to a DynamicMap. Any supplied kwargs will be passed to the callable and any streams will be instantiated on the returned DynamicMap. If the supplied operation is a method on a parameterized object which was decorated with parameter dependencies Dynamic will automatically create a stream to watch the parameter changes. This default behavior may be disabled by setting watch=False. """ operation = param.Callable(default=lambda x: x, doc=""" Operation or user-defined callable to apply dynamically""") kwargs = param.Dict(default={}, doc=""" Keyword arguments passed to the function.""") link_inputs = param.Boolean(default=True, doc=""" If Dynamic is applied to another DynamicMap, determines whether linked streams attached to its Callable inputs are transferred to the output of the utility. For example if the Dynamic utility is applied to a DynamicMap with an RangeXY, this switch determines whether the corresponding visualization should update this stream with range changes originating from the newly generated axes.""") shared_data = param.Boolean(default=False, doc=""" Whether the cloned DynamicMap will share the same cache.""") streams = param.List(default=[], doc=""" List of streams to attach to the returned DynamicMap""") def __call__(self, map_obj, **params): watch = params.pop('watch', True) self.p = param.ParamOverrides(self, params) callback = self._dynamic_operation(map_obj) streams = self._get_streams(map_obj, watch) if isinstance(map_obj, DynamicMap): dmap = map_obj.clone(callback=callback, shared_data=self.p.shared_data, streams=streams) if self.p.shared_data: dmap.data = OrderedDict([(k, callback.callable(*k)) for k, v in dmap.data]) else: dmap = self._make_dynamic(map_obj, callback, streams) return dmap def _get_streams(self, map_obj, watch=True): """ Generates a list of streams to attach to the returned DynamicMap. If the input is a DynamicMap any streams that are supplying values for the key dimension of the input are inherited. And the list of supplied stream classes and instances are processed and added to the list. """ streams = [] op = self.p.operation for stream in self.p.streams: if inspect.isclass(stream) and issubclass(stream, Stream): stream = stream() elif not (isinstance(stream, Stream) or util.is_param_method(stream)): raise ValueError( 'Streams must be Stream classes or instances, found %s type' % type(stream).__name__) if isinstance(op, Operation): updates = { k: op.p.get(k) for k, v in stream.contents.items() if v is None and k in op.p } if updates: reverse = {v: k for k, v in stream._rename.items()} stream.update( **{reverse.get(k, k): v for k, v in updates.items()}) streams.append(stream) params = {} for k, v in self.p.kwargs.items(): if 'panel' in sys.modules: from panel.widgets.base import Widget if isinstance(v, Widget): v = v.param.value if isinstance(v, param.Parameter) and isinstance( v.owner, param.Parameterized): params[k] = v streams += Params.from_params(params) # Inherit dimensioned streams if isinstance(map_obj, DynamicMap): dim_streams = util.dimensioned_streams(map_obj) streams = list(util.unique_iterator(streams + dim_streams)) # If callback is a parameterized method and watch is disabled add as stream has_dependencies = (util.is_param_method(op, has_deps=True) or isinstance(op, FunctionType) and hasattr(op, '_dinfo')) if has_dependencies and watch: streams.append(op) # Add any keyword arguments which are parameterized methods # with dependencies as streams for value in self.p.kwargs.values(): if util.is_param_method(value, has_deps=True): streams.append(value) elif isinstance(value, FunctionType) and hasattr(value, '_dinfo'): dependencies = list(value._dinfo.get('dependencies', [])) dependencies += list(value._dinfo.get('kw', {}).values()) params = [ d for d in dependencies if isinstance(d, param.Parameter) and isinstance(d.owner, param.Parameterized) ] streams.append(Params(parameters=params, watch_only=True)) valid, invalid = Stream._process_streams(streams) if invalid: msg = ('The supplied streams list contains objects that ' 'are not Stream instances: {objs}') raise TypeError( msg.format(objs=', '.join('%r' % el for el in invalid))) return valid def _process(self, element, key=None, kwargs={}): if util.is_param_method(self.p.operation) and util.get_method_owner( self.p.operation) is element: return self.p.operation(**kwargs) elif isinstance(self.p.operation, Operation): kwargs = { k: v for k, v in kwargs.items() if k in self.p.operation.param } return self.p.operation.process_element(element, key, **kwargs) else: return self.p.operation(element, **kwargs) def _dynamic_operation(self, map_obj): """ Generate function to dynamically apply the operation. Wraps an existing HoloMap or DynamicMap. """ def resolve(key, kwargs): if not isinstance(map_obj, HoloMap): return key, map_obj elif isinstance(map_obj, DynamicMap) and map_obj._posarg_keys and not key: key = tuple(kwargs[k] for k in map_obj._posarg_keys) return key, map_obj[key] def apply(element, *key, **kwargs): kwargs = dict(util.resolve_dependent_kwargs(self.p.kwargs), **kwargs) return self._process(element, key, kwargs) def dynamic_operation(*key, **kwargs): key, obj = resolve(key, kwargs) return apply(obj, *key, **kwargs) operation = self.p.operation if not isinstance(operation, Operation): operation = function.instance(fn=apply) return OperationCallable(dynamic_operation, inputs=[map_obj], link_inputs=self.p.link_inputs, operation=operation, operation_kwargs=self.p.kwargs) def _make_dynamic(self, hmap, dynamic_fn, streams): """ Accepts a HoloMap and a dynamic callback function creating an equivalent DynamicMap from the HoloMap. """ if isinstance(hmap, ViewableElement): return DynamicMap(dynamic_fn, streams=streams) dim_values = zip(*hmap.data.keys()) params = util.get_param_values(hmap) kdims = [ d.clone(values=list(util.unique_iterator(values))) for d, values in zip(hmap.kdims, dim_values) ] return DynamicMap(dynamic_fn, streams=streams, **dict(params, kdims=kdims))
class VTKVolume(AbstractVTK): ambient = param.Number(default=0.2, step=1e-2, doc=""" Value to control the ambient lighting. It is the light an object gives even in the absence of strong light. It is constant in all directions.""") controller_expanded = param.Boolean(default=True, doc=""" If True the volume controller panel options is expanded in the view""") colormap = param.Selector(default='erdc_rainbow_bright', objects=PRESET_CMAPS, doc=""" Name of the colormap used to transform pixel value in color.""") diffuse = param.Number(default=0.7, step=1e-2, doc=""" Value to control the diffuse Lighting. It relies on both the light direction and the object surface normal.""") display_volume = param.Boolean(default=True, doc=""" If set to True, the 3D respresentation of the volume is displayed using ray casting.""") display_slices = param.Boolean(default=False, doc=""" If set to true, the orthgonal slices in the three (X, Y, Z) directions are displayed. Position of each slice can be controlled using slice_(i,j,k) parameters.""") edge_gradient = param.Number(default=0.4, bounds=(0, 1), step=1e-2, doc=""" Parameter to adjust the opacity of the volume based on the gradient between voxels.""") interpolation = param.Selector( default='fast_linear', objects=['fast_linear', 'linear', 'nearest'], doc=""" interpolation type for sampling a volume. `nearest` interpolation will snap to the closest voxel, `linear` will perform trilinear interpolation to compute a scalar value from surrounding voxels. `fast_linear` under WebGL 1 will perform bilinear interpolation on X and Y but use nearest for Z. This is slightly faster than full linear at the cost of no Z axis linear interpolation.""") mapper = param.Dict(doc="Lookup Table in format {low, high, palette}") max_data_size = param.Number(default=(256**3) * 2 / 1e6, doc=""" Maximum data size transfert allowed without subsampling""") origin = param.Tuple(default=None, length=3, allow_None=True) render_background = param.Color(default='#52576e', doc=""" Allows to specify the background color of the 3D rendering. The value must be specified as an hexadecimal color string.""") rescale = param.Boolean(default=False, doc=""" If set to True the colormap is rescaled beween min and max value of the non-transparent pixel, otherwise the full range of the pixel values are used.""") shadow = param.Boolean(default=True, doc=""" If set to False, then the mapper for the volume will not perform shading computations, it is the same as setting ambient=1, diffuse=0, specular=0.""") sampling = param.Number(default=0.4, bounds=(0, 1), step=1e-2, doc=""" Parameter to adjust the distance between samples used for rendering. The lower the value is the more precise is the representation but it is more computationally intensive.""") spacing = param.Tuple(default=(1, 1, 1), length=3, doc=""" Distance between voxel in each direction""") specular = param.Number(default=0.3, step=1e-2, doc=""" Value to control specular lighting. It is the light reflects back toward the camera when hitting the object.""") specular_power = param.Number(default=8., doc=""" Specular power refers to how much light is reflected in a mirror like fashion, rather than scattered randomly in a diffuse manner.""") slice_i = param.Integer(per_instance=True, doc=""" Integer parameter to control the position of the slice normal to the X direction.""") slice_j = param.Integer(per_instance=True, doc=""" Integer parameter to control the position of the slice normal to the Y direction.""") slice_k = param.Integer(per_instance=True, doc=""" Integer parameter to control the position of the slice normal to the Z direction.""") _serializers = {} _rename = {'max_data_size': None, 'spacing': None, 'origin': None} _updates = True def __init__(self, object=None, **params): super(VTKVolume, self).__init__(object, **params) self._sub_spacing = self.spacing self._update() @classmethod def applies(cls, obj): if ((isinstance(obj, np.ndarray) and obj.ndim == 3) or any([isinstance(obj, k) for k in cls._serializers.keys()])): return True elif 'vtk' not in sys.modules: return False else: import vtk return isinstance(obj, vtk.vtkImageData) def _get_model(self, doc, root=None, parent=None, comm=None): """ Should return the bokeh model to be rendered. """ if 'panel.models.vtk' not in sys.modules: if isinstance(comm, JupyterComm): self.param.warning( 'VTKVolumePlot was not imported on instantiation ' 'and may not render in a notebook. Restart ' 'the notebook kernel and ensure you load ' 'it as part of the extension using:' '\n\npn.extension(\'vtk\')\n') from ...models.vtk import VTKVolumePlot else: VTKVolumePlot = getattr(sys.modules['panel.models.vtk'], 'VTKVolumePlot') props = self._process_param_change(self._init_properties()) volume_data = self._volume_data model = VTKVolumePlot(data=volume_data, **props) if root is None: root = model self._link_props(model, [ 'colormap', 'orientation_widget', 'camera', 'mapper', 'controller_expanded' ], doc, root, comm) self._models[root.ref['id']] = (model, parent) return model def _update_object(self, ref, doc, root, parent, comm): self._legend = None super(VTKVolume, self)._update_object(ref, doc, root, parent, comm) def _init_properties(self): return { k: v for k, v in self.param.get_param_values() if v is not None and k not in ['default_layout', 'object', 'max_data_size', 'spacing', 'origin'] } def _get_object_dimensions(self): if isinstance(self.object, np.ndarray): return self.object.shape else: return self.object.GetDimensions() def _process_param_change(self, msg): msg = super(VTKVolume, self)._process_param_change(msg) if self.object is not None: slice_params = {'slice_i': 0, 'slice_j': 1, 'slice_k': 2} for k, v in msg.items(): sub_dim = self._subsample_dimensions ori_dim = self._orginal_dimensions if k in slice_params: index = slice_params[k] msg[k] = int(np.round(v * sub_dim[index] / ori_dim[index])) return msg def _process_property_change(self, msg): msg = super(VTKVolume, self)._process_property_change(msg) if self.object is not None: slice_params = {'slice_i': 0, 'slice_j': 1, 'slice_k': 2} for k, v in msg.items(): sub_dim = self._subsample_dimensions ori_dim = self._orginal_dimensions if k in slice_params: index = slice_params[k] msg[k] = int(np.round(v * ori_dim[index] / sub_dim[index])) return msg def _update(self, ref=None, model=None): self._volume_data = self._get_volume_data() if self._volume_data is not None: self._orginal_dimensions = self._get_object_dimensions() self._subsample_dimensions = self._volume_data['dims'] self.param.slice_i.bounds = (0, self._orginal_dimensions[0] - 1) self.slice_i = (self._orginal_dimensions[0] - 1) // 2 self.param.slice_j.bounds = (0, self._orginal_dimensions[1] - 1) self.slice_j = (self._orginal_dimensions[1] - 1) // 2 self.param.slice_k.bounds = (0, self._orginal_dimensions[2] - 1) self.slice_k = (self._orginal_dimensions[2] - 1) // 2 if model is not None: model.data = self._volume_data @classmethod def register_serializer(cls, class_type, serializer): """ Register a seriliazer for a given type of class. A serializer is a function which take an instance of `class_type` (like a vtk.vtkImageData) as input and return a numpy array of the data """ cls._serializers.update({class_type: serializer}) def _volume_from_array(self, sub_array): return dict( buffer=base64encode( sub_array.ravel( order='F' if sub_array.flags['F_CONTIGUOUS'] else 'C')), dims=sub_array.shape if sub_array.flags['F_CONTIGUOUS'] else sub_array.shape[::-1], spacing=self._sub_spacing if sub_array.flags['F_CONTIGUOUS'] else self._sub_spacing[::-1], origin=self.origin, data_range=(sub_array.min(), sub_array.max()), dtype=sub_array.dtype.name) def _get_volume_data(self): if self.object is None: return None elif isinstance(self.object, np.ndarray): return self._volume_from_array(self._subsample_array(self.object)) else: available_serializer = [ v for k, v in VTKVolume._serializers.items() if isinstance(self.object, k) ] if not available_serializer: import vtk from vtk.util import numpy_support def volume_serializer(inst): imageData = inst.object array = numpy_support.vtk_to_numpy( imageData.GetPointData().GetScalars()) dims = imageData.GetDimensions()[::-1] inst.spacing = imageData.GetSpacing()[::-1] inst.origin = imageData.GetOrigin() return inst._volume_from_array( inst._subsample_array(array.reshape(dims, order='C'))) VTKVolume.register_serializer(vtk.vtkImageData, volume_serializer) serializer = volume_serializer else: serializer = available_serializer[0] return serializer(self) def _subsample_array(self, array): original_shape = array.shape spacing = self.spacing extent = tuple( (o_s - 1) * s for o_s, s in zip(original_shape, spacing)) dim_ratio = np.cbrt((array.nbytes / 1e6) / self.max_data_size) max_shape = tuple(int(o_s / dim_ratio) for o_s in original_shape) dowsnscale_factor = [ max(o_s, m_s) / m_s for m_s, o_s in zip(max_shape, original_shape) ] if any([d_f > 1 for d_f in dowsnscale_factor]): try: import scipy.ndimage as nd sub_array = nd.interpolation.zoom( array, zoom=[1 / d_f for d_f in dowsnscale_factor], order=0) except ImportError: sub_array = array[::int(np.ceil(dowsnscale_factor[0])), ::int( np.ceil(dowsnscale_factor[1]) ), ::int(np.ceil(dowsnscale_factor[2]))] self._sub_spacing = tuple(e / (s - 1) for e, s in zip(extent, sub_array.shape)) else: sub_array = array self._sub_spacing = self.spacing return sub_array
class _config(param.Parameterized): """ Holds global configuration options for Panel. The options can be set directly on the global config instance, via keyword arguments in the extension or via environment variables. For example to set the embed option the following approaches can be used: pn.config.embed = True pn.extension(embed=True) os.environ['PANEL_EMBED'] = 'True' """ apply_signatures = param.Boolean(default=True, doc=""" Whether to set custom Signature which allows tab-completion in some IDEs and environments.""") css_files = param.List(default=_CSS_FILES, doc=""" External CSS files to load.""") js_files = param.Dict(default={}, doc=""" External JS files to load. Dictionary should map from exported name to the URL of the JS file.""") raw_css = param.List(default=[], doc=""" List of raw CSS strings to add to load.""") safe_embed = param.Boolean(default=False, doc=""" Ensure all bokeh property changes trigger events which are embedded. Useful when only partial updates are made in an app, e.g. when working with HoloViews.""") sizing_mode = param.ObjectSelector(default=None, objects=[ 'fixed', 'stretch_width', 'stretch_height', 'stretch_both', 'scale_width', 'scale_height', 'scale_both', None ], doc=""" Specify the default sizing mode behavior of panels.""") _comms = param.ObjectSelector(default='default', objects=['default', 'ipywidgets'], doc=""" Whether to render output in Jupyter with the default Jupyter extension or use the jupyter_bokeh ipywidget model.""") _console_output = param.ObjectSelector( default='accumulate', allow_None=True, objects=['accumulate', 'replace', 'disable', False], doc=""" How to log errors and stdout output triggered by callbacks from Javascript in the notebook.""") _embed = param.Boolean(default=False, allow_None=True, doc=""" Whether plot data will be embedded.""") _embed_json = param.Boolean(default=False, doc=""" Whether to save embedded state to json files.""") _embed_json_prefix = param.String(default='', doc=""" Prefix for randomly generated json directories.""") _embed_load_path = param.String(default=None, doc=""" Where to load json files for embedded state.""") _embed_save_path = param.String(default='./', doc=""" Where to save json files for embedded state.""") _inline = param.Boolean(default=True, allow_None=True, doc=""" Whether to inline JS and CSS resources. If disabled, resources are loaded from CDN if one is available.""") _truthy = ['True', 'true', '1', True, 1] def __init__(self, **params): super(_config, self).__init__(**params) for p in self.param: if p.startswith('_'): setattr(self, p + '_', None) @contextmanager def set(self, **kwargs): values = [(k, v) for k, v in self.param.get_param_values() if k != 'name'] overrides = [(k, getattr(self, k + '_')) for k in self.param if k.startswith('_')] for k, v in kwargs.items(): setattr(self, k, v) try: yield finally: self.param.set_param(**dict(values)) for k, v in overrides: setattr(self, k + '_', v) @property def _doc_build(self): return os.environ.get('PANEL_DOC_BUILD') @property def console_output(self): if self._console_output_ is not None: return 'disable' if not self._console_output_ else self._console_output_ elif self._doc_build: return 'disable' else: return os.environ.get('PANEL_CONSOLE_OUTPUT', _config._console_output) @console_output.setter def console_output(self, value): validate_config(self, '_console_output', value) self._console_output_ = value @property def embed(self): if self._embed_ is not None: return self._embed_ else: return os.environ.get('PANEL_EMBED', _config._embed) in self._truthy @embed.setter def embed(self, value): validate_config(self, '_embed', value) self._embed_ = value @property def comms(self): if self._comms_ is not None: return self._comms_ else: return os.environ.get('PANEL_COMMS', _config._comms) @comms.setter def comms(self, value): validate_config(self, '_comms', value) self._comms_ = value @property def embed_json(self): if self._embed_json_ is not None: return self._embed_json_ else: return os.environ.get('PANEL_EMBED_JSON', _config._embed_json) in self._truthy @embed_json.setter def embed_json(self, value): validate_config(self, '_embed_json', value) self._embed_json_ = value @property def embed_json_prefix(self): if self._embed_json_prefix_ is not None: return self._embed_json_prefix_ else: return os.environ.get('PANEL_EMBED_JSON_PREFIX', _config._embed_json_prefix) @embed_json_prefix.setter def embed_json_prefix(self, value): validate_config(self, '_embed_json_prefix', value) self._embed_json_prefix_ = value @property def embed_save_path(self): if self._embed_save_path_ is not None: return self._embed_save_path_ else: return os.environ.get('PANEL_EMBED_SAVE_PATH', _config._embed_save_path) @embed_save_path.setter def embed_save_path(self, value): validate_config(self, '_embed_save_path', value) self._embed_save_path_ = value @property def embed_load_path(self): if self._embed_load_path_ is not None: return self._embed_load_path_ else: return os.environ.get('PANEL_EMBED_LOAD_PATH', _config._embed_load_path) @embed_load_path.setter def embed_load_path(self, value): validate_config(self, '_embed_load_path', value) self._embed_load_path_ = value @property def inline(self): if self._inline_ is not None: return self._inline_ else: return os.environ.get('PANEL_INLINE', _config._inline) in self._truthy @inline.setter def inline(self, value): validate_config(self, '_inline', value) self._inline_ = value
class RasterPlot(ColorbarPlot): aspect = param.Parameter(default='equal', doc=""" Raster elements respect the aspect ratio of the Images by default but may be set to an explicit aspect ratio or to 'square'.""") clipping_colors = param.Dict(default={'NaN': 'transparent'}) colorbar = param.Boolean(default=False, doc=""" Whether to add a colorbar to the plot.""") show_legend = param.Boolean(default=False, doc=""" Whether to show legend for the plot.""") situate_axes = param.Boolean(default=True, doc=""" Whether to situate the image relative to other plots. """) style_opts = [ 'alpha', 'cmap', 'interpolation', 'visible', 'filterrad', 'clims', 'norm' ] _plot_methods = dict(single='imshow') def __init__(self, *args, **kwargs): super(RasterPlot, self).__init__(*args, **kwargs) if self.hmap.type == Raster: self.invert_yaxis = not self.invert_yaxis def get_extents(self, element, ranges, range_type='combined'): extents = super(RasterPlot, self).get_extents(element, ranges, range_type) if self.situate_axes or range_type not in ('combined', 'data'): return extents else: if isinstance(element, Image): return element.bounds.lbrt() else: return element.extents def _compute_ticks(self, element, ranges): return None, None def get_data(self, element, ranges, style): xticks, yticks = self._compute_ticks(element, ranges) if isinstance(element, RGB): style.pop('cmap', None) data = get_raster_array(element) if type(element) is Raster: l, b, r, t = element.extents if self.invert_axes: data = data[:, ::-1] else: data = data[::-1] else: l, b, r, t = element.bounds.lbrt() if self.invert_axes: data = data[::-1, ::-1] if self.invert_axes: data = data.transpose([1, 0, 2]) if isinstance(element, RGB) else data.T l, b, r, t = b, l, t, r vdim = element.vdims[0] self._norm_kwargs(element, ranges, style, vdim) style['extent'] = [l, r, b, t] style['origin'] = 'upper' return [data], style, {'xticks': xticks, 'yticks': yticks} def update_handles(self, key, axis, element, ranges, style): im = self.handles['artist'] data, style, axis_kwargs = self.get_data(element, ranges, style) l, r, b, t = style['extent'] im.set_data(data[0]) im.set_extent((l, r, b, t)) im.set_clim((style['vmin'], style['vmax'])) if 'norm' in style: im.norm = style['norm'] return axis_kwargs
class MPLPlot(DimensionedPlot): """ An MPLPlot object draws a matplotlib figure object when called or indexed but can also return a matplotlib animation object as appropriate. MPLPlots take element objects such as Image, Contours or Points as inputs and plots them in the appropriate format using matplotlib. As HoloMaps are supported, all plots support animation via the anim() method. """ renderer = MPLRenderer sideplots = {} fig_alpha = param.Number(default=1.0, bounds=(0, 1), doc=""" Alpha of the overall figure background.""") fig_bounds = param.NumericTuple(default=(0.15, 0.15, 0.85, 0.85), doc=""" The bounds of the overall figure as a 4-tuple of the form (left, bottom, right, top), defining the size of the border around the subplots.""") fig_inches = param.Parameter(default=4, doc=""" The overall matplotlib figure size in inches. May be set as an integer in which case it will be used to autocompute a size. Alternatively may be set with an explicit tuple or list, in which case it will be applied directly after being scaled by fig_size. If either the width or height is set to None, it will be computed automatically.""") fig_latex = param.Boolean(default=False, doc=""" Whether to use LaTeX text in the overall figure.""") fig_rcparams = param.Dict(default={}, doc=""" matplotlib rc parameters to apply to the overall figure.""") fig_size = param.Integer(default=100, bounds=(1, None), doc=""" Size relative to the supplied overall fig_inches in percent.""") finalize_hooks = param.HookList(default=[], doc=""" Optional list of hooks called when finalizing an axis. The hook is passed the full set of plot handles and the displayed object.""") sublabel_format = param.String(default=None, allow_None=True, doc=""" Allows labeling the subaxes in each plot with various formatters including {Alpha}, {alpha}, {numeric} and {roman}.""") sublabel_position = param.NumericTuple(default=(-0.35, 0.85), doc=""" Position relative to the plot for placing the optional subfigure label.""" ) sublabel_size = param.Number(default=18, doc=""" Size of optional subfigure label.""") projection = param.ObjectSelector(default=None, objects=['3d', 'polar', None], doc=""" The projection of the plot axis, default of None is equivalent to 2D plot, 3D and polar plots are also supported.""") show_frame = param.Boolean(default=True, doc=""" Whether or not to show a complete frame around the plot.""") fontsize = param.Parameter(default=None, allow_None=True, doc=""" Specifies various fontsizes of the displayed text. By default, the fontsize is determined by matplotlib (via rcparams) but if set to an integer, this is the fontsize of all text except for tick labels (and subfigure labels in Layouts). Finer control is available by supplying a dictionary where any unmentioned keys reverts to the default sizes, e.g: {'ticks':20, 'title':15, 'ylabel':5, 'xlabel':5}""") def __init__(self, fig=None, axis=None, **params): self._create_fig = True super(MPLPlot, self).__init__(**params) # List of handles to matplotlib objects for animation update scale = self.fig_size / 100. if isinstance(self.fig_inches, (tuple, list)): self.fig_inches = [ None if i is None else i * scale for i in self.fig_inches ] else: self.fig_inches *= scale fig, axis = self._init_axis(fig, axis) self.handles['fig'] = fig self.handles['axis'] = axis def _fontsize(self, key, label='fontsize', common=True): """ To be used as kwargs e.g: **self._fontsize('title') """ if not self.fontsize: return {} if isinstance(self.fontsize, dict): if key not in self.fontsize: return {} else: return {label: self.fontsize[key]} return {label: self.fontsize} if common else {} def _init_axis(self, fig, axis): """ Return an axis which may need to be initialized from a new figure. """ if not fig and self._create_fig: rc_params = self.fig_rcparams if self.fig_latex: rc_params['text.usetex'] = True with mpl.rc_context(rc=rc_params): fig = plt.figure() l, b, r, t = self.fig_bounds inches = self.fig_inches fig.subplots_adjust(left=l, bottom=b, right=r, top=t) fig.patch.set_alpha(self.fig_alpha) if isinstance(inches, (tuple, list)): inches = list(inches) if inches[0] is None: inches[0] = inches[1] elif inches[1] is None: inches[1] = inches[0] fig.set_size_inches(list(inches)) else: fig.set_size_inches([inches, inches]) axis = fig.add_subplot(111, projection=self.projection) axis.set_aspect('auto') return fig, axis def _subplot_label(self, axis): layout_num = self.layout_num if self.subplot else 1 if self.sublabel_format and not self.adjoined and layout_num > 0: from mpl_toolkits.axes_grid1.anchored_artists import AnchoredText labels = {} if '{Alpha}' in self.sublabel_format: labels['Alpha'] = int_to_alpha(layout_num - 1) elif '{alpha}' in self.sublabel_format: labels['alpha'] = int_to_alpha(layout_num - 1, upper=False) elif '{numeric}' in self.sublabel_format: labels['numeric'] = self.layout_num elif '{Roman}' in self.sublabel_format: labels['Roman'] = int_to_roman(layout_num) elif '{roman}' in self.sublabel_format: labels['roman'] = int_to_roman(layout_num).lower() at = AnchoredText(self.sublabel_format.format(**labels), loc=3, bbox_to_anchor=self.sublabel_position, frameon=False, prop=dict(size=self.sublabel_size, weight='bold'), bbox_transform=axis.transAxes) at.patch.set_visible(False) axis.add_artist(at) def _finalize_axis(self, key): """ General method to finalize the axis and plot. """ if 'title' in self.handles: self.handles['title'].set_visible(self.show_title) self.drawn = True if self.subplot: return self.handles['axis'] else: fig = self.handles['fig'] plt.close(fig) return fig @property def state(self): return self.handles['fig'] def anim(self, start=0, stop=None, fps=30): """ Method to return a matplotlib animation. The start and stop frames may be specified as well as the fps. """ figure = self.initialize_plot() anim = animation.FuncAnimation(figure, self.update_frame, frames=self.keys, interval=1000.0 / fps) # Close the figure handle plt.close(figure) return anim def update(self, key): rc_params = self.fig_rcparams if self.fig_latex: rc_params['text.usetex'] = True mpl.rcParams.update(rc_params) if len(self) == 1 and key == 0 and not self.drawn: return self.initialize_plot() return self.__getitem__(key)
class MontageBitmap(Bitmap): """ A bitmap composed of tiles containing other bitmaps. Bitmaps are scaled to fit in the given tile size, and tiled right-to-left, top-to-bottom into the given number of rows and columns. """ bitmaps = param.List(class_=Bitmap, doc=""" The list of bitmaps to compose.""") rows = param.Integer(default=2, doc=""" The number of rows in the montage.""") cols = param.Integer(default=2, doc=""" The number of columns in the montage.""") shape = param.Composite(attribs=['rows', 'cols'], doc=""" The shape of the montage. Same as (self.rows,self.cols).""") margin = param.Integer(default=5, doc=""" The size in pixels of the margin to put around each tile in the montage.""") tile_size = param.NumericTuple(default=(100, 100), doc=""" The size in pixels of a tile in the montage.""") titles = param.List(class_=str, default=[], doc=""" A list of titles to overlay on the tiles.""") title_pos = param.NumericTuple(default=(10, 10), doc=""" The position of the upper left corner of the title in each tile.""") title_options = param.Dict(default={}, doc=""" Dictionary of options for drawing the titles. Dict should contain keyword options for the PIL draw.text method. Possible options include 'fill' (fill color), 'outline' (outline color), and 'font' (an ImageFont font instance). The PIL defaults will be used for any omitted options.""", instantiate=False) hooks = param.List(default=[], doc=""" A list of functions, one per tile, that take a PIL image as input and return a PIL image as output. The hooks are applied to the tile images before resizing. The value None can be inserted as a placeholder where no hook function is needed.""") resize_filter = param.Integer(default=Image.NEAREST, doc=""" The filter used for resizing the images. Defaults to NEAREST. See PIL Image module documentation for other options and their meanings.""") bg_color = param.NumericTuple(default=(0, 0, 0), doc=""" The background color for the montage, as (r,g,b).""") def __init__(self, **params): ## JPALERT: The Bitmap class is a Parameterized object,but its ## __init__ doesn't take **params and doesn't call super.__init__, ## so we have to skip it. ## JAB: Good point; Bitmap should be modified to be more like ## other PO classes. param.Parameterized.__init__(self, **params) rows, cols = self.shape tilew, tileh = self.tile_size bgr, bgg, bgb = self.bg_color width = tilew * cols + self.margin * (cols * 2) height = tileh * rows + self.margin * (rows * 2) self.image = Image.new('RGB', (width, height), (bgr * 255, bgg * 255, bgb * 255)) self.title_options.setdefault('font', TITLE_FONT) for r in xrange(rows): for c in xrange(cols): i = r * self.cols + c if i < len(self.bitmaps): bm = self.bitmaps[i] bmw, bmh = bm.image.size if bmw > bmh: bmh = int(float(tilew) / bmw * bmh) bmw = tilew else: bmw = int(float(tileh) / bmh * bmw) bmh = tileh if self.hooks and self.hooks[i]: f = self.hooks[i] else: f = lambda x: x new_bm = Bitmap(f(bm.image).resize((bmw, bmh))) if self.titles: draw = ImageDraw.Draw(new_bm.image) draw.text(self.title_pos, self.titles[i], **self.title_options) self.image.paste(new_bm.image, (c * width / cols + tilew / 2 - bmw / 2 + self.margin, r * height / rows + tileh / 2 - bmh / 2 + self.margin)) else: break
class Plotly(PaneBase): """ Plotly panes allow rendering plotly Figures and traces. For efficiency any array objects found inside a Figure are added to a ColumnDataSource which allows using binary transport to sync the figure on bokeh server and via Comms. """ click_data = param.Dict(doc="Click callback data") clickannotation_data = param.Dict(doc="Clickannotation callback data") config = param.Dict(doc="Config data") hover_data = param.Dict(doc="Hover callback data") relayout_data = param.Dict(doc="Relayout callback data") restyle_data = param.List(doc="Restyle callback data") selected_data = param.Dict(doc="Selected callback data") viewport = param.Dict(doc="Current viewport state") viewport_update_policy = param.Selector( default="mouseup", doc=""" Policy by which the viewport parameter is updated during user interactions. * "mouseup": updates are synchronized when mouse button is released after panning * "continuous": updates are synchronized continually while panning * "throttle": updates are synchronized while panning, at intervals determined by the viewport_update_throttle parameter """, objects=["mouseup", "continuous", "throttle"]) viewport_update_throttle = param.Integer(default=200, bounds=(0, None), doc=""" Time interval in milliseconds at which viewport updates are synchronized when viewport_update_policy is "throttle".""") _render_count = param.Integer(default=0, doc=""" Number of renders, increment to trigger re-render""") priority = 0.8 _updates = True @classmethod def applies(cls, obj): return ((isinstance(obj, list) and obj and all(cls.applies(o) for o in obj)) or hasattr(obj, 'to_plotly_json') or (isinstance(obj, dict) and 'data' in obj and 'layout' in obj)) def __init__(self, object=None, **params): super().__init__(object, **params) self._figure = None self._update_figure() def _to_figure(self, obj): import plotly.graph_objs as go if isinstance(obj, go.Figure): return obj elif isinstance(obj, dict): data, layout = obj['data'], obj['layout'] elif isinstance(obj, tuple): data, layout = obj else: data, layout = obj, {} data = data if isinstance(data, list) else [data] return go.Figure(data=data, layout=layout) @staticmethod def _get_sources(json): sources = [] traces = json.get('data', []) for trace in traces: data = {} Plotly._get_sources_for_trace(trace, data) sources.append(ColumnDataSource(data)) return sources @staticmethod def _get_sources_for_trace(json, data, parent_path=''): for key, value in list(json.items()): full_path = key if not parent_path else (parent_path + '.' + key) if isinstance(value, np.ndarray): # Extract numpy array data[full_path] = [json.pop(key)] elif isinstance(value, dict): # Recurse into dictionaries: Plotly._get_sources_for_trace(value, data=data, parent_path=full_path) elif isinstance(value, list) and value and isinstance( value[0], dict): # recurse into object arrays: for i, element in enumerate(value): element_path = full_path + '.' + str(i) Plotly._get_sources_for_trace(element, data=data, parent_path=element_path) @param.depends('object', watch=True) def _update_figure(self): import plotly.graph_objs as go if (self.object is None or type(self.object) is not go.Figure or self.object is self._figure): return # Monkey patch the message stubs used by FigureWidget. # We only patch `Figure` objects (not subclasses like FigureWidget) so # we don't interfere with subclasses that override these methods. fig = self.object fig._send_addTraces_msg = lambda *_, **__: self.param.trigger('object') fig._send_moveTraces_msg = lambda *_, **__: self.param.trigger('object' ) fig._send_deleteTraces_msg = lambda *_, **__: self.param.trigger( 'object') fig._send_restyle_msg = lambda *_, **__: self.param.trigger('object') fig._send_relayout_msg = lambda *_, **__: self.param.trigger('object') fig._send_update_msg = lambda *_, **__: self.param.trigger('object') fig._send_animate_msg = lambda *_, **__: self.param.trigger('object') self._figure = fig def _update_data_sources(self, cds, trace): trace_arrays = {} Plotly._get_sources_for_trace(trace, trace_arrays) update_sources = False for key, new_col in trace_arrays.items(): new = new_col[0] try: old = cds.data.get(key)[0] update_array = ((type(old) != type(new)) or (new.shape != old.shape) or (new != old).any()) except Exception: update_array = True if update_array: update_sources = True cds.data[key] = [new] return update_sources @staticmethod def _plotly_json_wrapper(fig): """Wraps around to_plotly_json and applies necessary fixes. For #382: Map datetime elements to strings. """ json = fig.to_plotly_json() data = json['data'] for idx in range(len(data)): for key in data[idx]: if isdatetime(data[idx][key]): arr = data[idx][key] if isinstance(arr, np.ndarray): arr = arr.astype(str) else: arr = [str(v) for v in arr] data[idx][key] = arr return json def _get_model(self, doc, root=None, parent=None, comm=None): """ Should return the bokeh model to be rendered. """ PlotlyPlot = lazy_load('panel.models.plotly', 'PlotlyPlot', isinstance(comm, JupyterComm)) viewport_params = [p for p in self.param if 'viewport' in p] params = list(Layoutable.param) + viewport_params properties = { p: getattr(self, p) for p in params if getattr(self, p) is not None } if self.object is None: json, sources = {}, [] else: fig = self._to_figure(self.object) json = self._plotly_json_wrapper(fig) sources = Plotly._get_sources(json) data = json.get('data', []) layout = json.get('layout', {}) if layout.get('autosize' ) and self.sizing_mode is self.param.sizing_mode.default: properties['sizing_mode'] = 'stretch_both' model = PlotlyPlot(data=data, layout=layout, config=self.config or {}, data_sources=sources, _render_count=self._render_count, **properties) if root is None: root = model self._link_props(model, [ 'config', 'relayout_data', 'restyle_data', 'click_data', 'hover_data', 'clickannotation_data', 'selected_data', 'viewport', 'viewport_update_policy', 'viewport_update_throttle', '_render_count' ], doc, root, comm) if root is None: root = model self._models[root.ref['id']] = (model, parent) return model def _update(self, ref=None, model=None): if self.object is None: model.update(data=[], layout={}) model._render_count += 1 return fig = self._to_figure(self.object) json = self._plotly_json_wrapper(fig) layout = json.get('layout') traces = json['data'] new_sources = [] update_sources = False for i, trace in enumerate(traces): if i < len(model.data_sources): cds = model.data_sources[i] else: cds = ColumnDataSource() new_sources.append(cds) update_sources = self._update_data_sources(cds, trace) or update_sources try: update_layout = model.layout != layout except Exception: update_layout = True # Determine if model needs updates if (len(model.data) != len(traces)): update_data = True else: update_data = False for new, old in zip(traces, model.data): try: update_data = ({ k: v for k, v in new.items() if k != 'uid' } != {k: v for k, v in old.items() if k != 'uid'}) except Exception: update_data = True if update_data: break if self.sizing_mode is self.param.sizing_mode.default and 'autosize' in layout: autosize = layout.get('autosize') if autosize and model.sizing_mode != 'stretch_both': model.sizing_mode = 'stretch_both' elif not autosize and model.sizing_mode != 'fixed': model.sizing_mode = 'fixed' if new_sources: model.data_sources += new_sources if update_data: model.data = json.get('data') if update_layout: model.layout = layout # Check if we should trigger rendering if new_sources or update_sources or update_data or update_layout: model._render_count += 1
class NdWidget(param.Parameterized): """ NdWidget is an abstract base class implementing a method to find the dimensions and keys of any ViewableElement, GridSpace or UniformNdMapping type. In the process it creates a mock_obj to hold the dimensions and keys. """ display_options = param.Dict(default={}, doc=""" The display options used to generate individual frames""") embed = param.Boolean(default=True, doc=""" Whether to embed all plots in the Javascript, generating a static widget not dependent on the IPython server.""") ####################### # JSON export options # ####################### export_json = param.Boolean(default=False, doc="""Whether to export plots as json files, which can be dynamically loaded through a callback from the slider.""") json_save_path = param.String(default='./json_figures', doc=""" If export_json is enabled the widget will save the json data to this path. If None data will be accessible via the json_data attribute.""") json_load_path = param.String(default=None, doc=""" If export_json is enabled the widget JS code will load the data from this path, if None defaults to json_save_path. For loading the data from within the notebook the path must be relative, when exporting the notebook the path can be set to another location like a webserver where the json files can be uploaded to.""") ############################## # Javascript include options # ############################## CDN = param.Dict( default={ 'underscore': 'https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js', 'jQueryUI': 'https://code.jquery.com/ui/1.10.4/jquery-ui.min.js' }) css = param.String(default=None, doc=""" Defines the local CSS file to be loaded for this widget.""") basejs = param.String(default='widgets.js', doc=""" JS file containing javascript baseclasses for the widget.""") extensionjs = param.String(default=None, doc=""" Optional javascript extension file for a particular backend.""") widgets = {} counter = 0 def __init__(self, plot, renderer=None, **params): super(NdWidget, self).__init__(**params) self.id = uuid.uuid4().hex self.plot = plot self.dimensions = plot.dimensions self.keys = plot.keys self.json_data = {} if self.plot.dynamic: self.embed = False if renderer is None: backend = Store.current_backend self.renderer = Store.renderers[backend] else: self.renderer = renderer # Create mock NdMapping to hold the common dimensions and keys self.mock_obj = NdMapping([(k, None) for k in self.keys], kdims=self.dimensions) NdWidget.widgets[self.id] = self # Set up jinja2 templating import jinja2 templateLoader = jinja2.FileSystemLoader(subdirs) self.jinjaEnv = jinja2.Environment(loader=templateLoader) def __call__(self): return self.render_html(self._get_data()) def _get_data(self): delay = int(1000. / self.display_options.get('fps', 5)) CDN = {k: v[:-3] for k, v in self.CDN.items()} template = self.jinjaEnv.get_template(self.base_template) name = type(self).__name__ cached = str(self.embed).lower() load_json = str(self.export_json).lower() mode = repr(self.renderer.mode) json_path = (self.json_save_path if self.json_load_path is None else self.json_load_path) if json_path and json_path[-1] != '/': json_path = json_path + '/' dynamic = json.dumps( self.plot.dynamic) if self.plot.dynamic else 'false' return dict(CDN=CDN, frames=self.get_frames(), delay=delay, cached=cached, load_json=load_json, mode=mode, id=self.id, Nframes=len(self.plot), widget_name=name, json_path=json_path, widget_template=template, dynamic=dynamic) def render_html(self, data): template = self.jinjaEnv.get_template(self.template) return template.render(**data) def get_frames(self): if self.embed: frames = OrderedDict([(idx, self._plot_figure(idx)) for idx in range(len(self.plot))]) else: frames = {} return self.encode_frames(frames) def encode_frames(self, frames): if isinstance(frames, dict): frames = {idx: frame for idx, frame in frames.items()} return frames def save_json(self, frames): """ Saves frames data into a json file at the specified json_path, named with the widget uuid. """ if self.json_save_path is None: return path = os.path.join(self.json_save_path, '%s.json' % self.id) if not os.path.isdir(self.json_save_path): os.mkdir(self.json_save_path) with open(path, 'w') as f: json.dump(frames, f) self.json_data = frames def _plot_figure(self, idx): with self.renderer.state(): self.plot.update(idx) css = self.display_options.get('css', {}) figure_format = self.display_options.get('figure_format', self.renderer.fig) return self.renderer.html(self.plot, figure_format, css=css) def update(self, key): return self._plot_figure(key)
class pattern_present(PatternPresentingCommand): """ Given a set of input patterns, installs them into the specified GeneratorSheets, runs the simulation for the specified length of time, then restores the original patterns and the original simulation time. Thus this input is not considered part of the regular simulation, and is usually for testing purposes. May also be used to measure the response to a pattern by calling it with restore_events disabled and restore_state and install_sheetview enabled, which will push and pop the simulation state and install the response in the sheets views dictionary. The update_activity command implements this functionality. As a special case, if 'inputs' is just a single pattern, and not a dictionary, it is presented to all GeneratorSheets. If this process is interrupted by the user, the temporary patterns may still be installed on the retina. If overwrite_previous is true, the given inputs overwrite those previously defined. If plastic is False, overwrites the existing values of Sheet.plastic to disable plasticity, then re-enables plasticity. If this process is interrupted by the user, the temporary patterns may still be installed on the retina. In order to to see the sequence of values presented, you may use the back arrow history mechanism in the GUI. Note that the GUI's Activity window must be open. Alternatively or access the activities through the Activity entry in the views.maps dictionary on the specified sheets. """ apply_output_fns = param.Boolean(default=True, doc=""" Determines whether sheet output functions will be applied. """) inputs = param.Dict(default={}, doc=""" A dictionary of GeneratorSheetName:PatternGenerator pairs to be installed into the specified GeneratorSheets""") install_sheetview = param.Boolean(default=False, doc="""Determines whether to install a sheet view in the global storage dictionary.""") plastic = param.Boolean(default=False, doc=""" If plastic is False, overwrites the existing values of Sheet.plastic to disable plasticity, then reenables plasticity.""") overwrite_previous = param.Boolean(default=False, doc=""" If overwrite_previous is true, the given inputs overwrite those previously defined.""") restore_events = param.Boolean(default=True, doc=""" If True, restore simulation events after the response has been measured, so that no simulation time will have elapsed. Implied by restore_state=True.""") restore_state = param.Boolean(default=False, doc=""" If True, restore the state of both sheet activities and simulation events after the response has been measured. Implies restore_events.""") return_responses = param.Boolean(default=False, doc=""" If True, return a dictionary of the responses.""") __abstract = True def __call__(self, inputs={}, outputs=[], **params_to_override): p = ParamOverrides(self, dict(params_to_override, inputs=inputs)) # ensure EPs get started (if pattern_response is called before the # simulation is run()) topo.sim.run(0.0) if p.restore_state: topo.sim.state_push() if not p.overwrite_previous: save_input_generators() if not p.plastic: # turn off plasticity everywhere for sheet in topo.sim.objects(Sheet).values(): sheet.override_plasticity_state(new_plasticity_state=False) if not p.apply_output_fns: for each in topo.sim.objects(Sheet).values(): if hasattr(each, 'measure_maps'): if each.measure_maps: each.apply_output_fns = False # Register the inputs on each input sheet generatorsheets = topo.sim.objects(GeneratorSheet) if not isinstance(p.inputs, dict): for g in generatorsheets.values(): g.set_input_generator(p.inputs) else: for each in p.inputs.keys(): if generatorsheets.has_key(each): generatorsheets[each].set_input_generator(p.inputs[each]) else: param.Parameterized().warning( '%s not a valid Sheet name for pattern_present.' % each) if p.restore_events: topo.sim.event_push() durations = np.diff([0] + p.durations) projection_dict = dict( (conn.name, conn) for conn in topo.sim.connections()) outputs = outputs if len(outputs) > 0 else topo.sim.objects( Sheet).keys() + projection_dict.keys() responses = defaultdict(dict) for i, d in enumerate(durations): topo.sim.run(d) time = p.durations[i] if hasattr(topo, 'guimain'): update_activity(p.install_sheetview) topo.guimain.refresh_activity_windows() if p.return_responses: for output in outputs: if output in topo.sim.objects(Sheet).keys(): responses[(output, time)] = topo.sim[output].activity.copy() elif output in projection_dict: responses[( output, time)] = projection_dict[output].activity.copy() if p.restore_events: topo.sim.event_pop() # turn sheets' plasticity and output_fn plasticity back on if we # turned it off before if not p.plastic: for sheet in topo.sim.objects(Sheet).values(): sheet.restore_plasticity_state() if not p.apply_output_fns: for each in topo.sim.objects(Sheet).values(): each.apply_output_fns = True if not p.overwrite_previous: restore_input_generators() if p.restore_state: topo.sim.state_pop() return responses
class Link(Callback): """ A Link defines some connection between a source and target model. It allows defining callbacks in response to some change or event on the source object. Instead a Link directly causes some action to occur on the target, for JS based backends this usually means that a corresponding JS callback will effect some change on the target in response to a change on the source. A Link must define a source object which is what triggers events, but must not define a target. It is also possible to define bi- directional links between the source and target object. """ bidirectional = param.Boolean(default=False, doc=""" Whether to link source and target in both directions.""") properties = param.Dict(default={}, doc=""" A dictionary mapping between source specification to target specification.""") # Whether the link requires a target _requires_target = True def __init__(self, source, target=None, **params): if self._requires_target and target is None: raise ValueError('%s must define a target.' % type(self).__name__) # Source is stored as a weakref to allow it to be garbage collected self._target = None if target is None else weakref.ref(target) super().__init__(source, **params) @property def target(self): return self._target() if self._target else None def link(self): """ Registers the Link """ self.init() if self.source in self.registry: links = self.registry[self.source] params = { k: v for k, v in self.param.get_param_values() if k != 'name' } for link in links: link_params = { k: v for k, v in link.param.get_param_values() if k != 'name' } if (type(link) is type(self) and link.source is self.source and link.target is self.target and params == link_params): return self.registry[self.source].append(self) else: self.registry[self.source] = [self] def unlink(self): """ Unregisters the Link """ links = self.registry.get(self.source) if self in links: links.pop(links.index(self))
class ColorbarPlot(ElementPlot): colorbar = param.Boolean(default=False, doc=""" Whether to draw a colorbar.""") clipping_colors = param.Dict(default={}, doc=""" Dictionary to specify colors for clipped values, allows setting color for NaN values and for values above and below the min and max value. The min, max or NaN color may specify an RGB(A) color as a color hex string of the form #FFFFFF or #FFFFFFFF or a length 3 or length 4 tuple specifying values in the range 0-1 or a named HTML color.""") cbar_padding = param.Number(default=0.01, doc=""" Padding between colorbar and other plots.""") cbar_ticks = param.Parameter(default=None, doc=""" Ticks along colorbar-axis specified as an integer, explicit list of tick locations, list of tuples containing the locations and labels or a matplotlib tick locator object. If set to None default matplotlib ticking behavior is applied.""") cbar_width = param.Number(default=0.05, doc=""" Width of the colorbar as a fraction of the main plot""") symmetric = param.Boolean(default=False, doc=""" Whether to make the colormap symmetric around zero.""") _colorbars = {} def __init__(self, *args, **kwargs): super(ColorbarPlot, self).__init__(*args, **kwargs) self._cbar_extend = 'neither' def _adjust_cbar(self, cbar, label, dim): noalpha = math.floor(self.style[self.cyclic_index].get('alpha', 1)) == 1 if (cbar.solids and noalpha): cbar.solids.set_edgecolor("face") cbar.set_label(label) if isinstance(self.cbar_ticks, ticker.Locator): cbar.ax.yaxis.set_major_locator(self.cbar_ticks) elif self.cbar_ticks == 0: cbar.set_ticks([]) elif isinstance(self.cbar_ticks, int): locator = ticker.MaxNLocator(self.cbar_ticks) cbar.ax.yaxis.set_major_locator(locator) elif isinstance(self.cbar_ticks, list): if all(isinstance(t, tuple) for t in self.cbar_ticks): ticks, labels = zip(*self.cbar_ticks) else: ticks, labels = zip(*[(t, dim.pprint_value(t)) for t in self.cbar_ticks]) cbar.set_ticks(ticks) cbar.set_ticklabels(labels) def _finalize_artist(self, element): artist = self.handles.get('artist', None) if artist and self.colorbar: self._draw_colorbar() def _draw_colorbar(self, dim=None, redraw=True): element = self.hmap.last artist = self.handles.get('artist', None) fig = self.handles['fig'] axis = self.handles['axis'] ax_colorbars, position = ColorbarPlot._colorbars.get(id(axis), ([], None)) specs = [spec[:2] for _, _, spec, _ in ax_colorbars] spec = util.get_spec(element) if position is None or not redraw: if redraw: fig.canvas.draw() bbox = axis.get_position() l, b, w, h = bbox.x0, bbox.y0, bbox.width, bbox.height else: l, b, w, h = position # Get colorbar label dim = element.get_dimension(dim) if dim: label = dim.pprint_label elif element.vdims: label = element.vdims[0].pprint_label elif dim is None: label = '' padding = self.cbar_padding width = self.cbar_width if spec[:2] not in specs: offset = len(ax_colorbars) scaled_w = w*width cax = fig.add_axes([l+w+padding+(scaled_w+padding+w*0.15)*offset, b, scaled_w, h]) cbar = fig.colorbar(artist, cax=cax, ax=axis, extend=self._cbar_extend) self._adjust_cbar(cbar, label, dim) self.handles['cax'] = cax self.handles['cbar'] = cbar ylabel = cax.yaxis.get_label() self.handles['bbox_extra_artists'] += [cax, ylabel] ax_colorbars.append((artist, cax, spec, label)) for i, (artist, cax, spec, label) in enumerate(ax_colorbars): scaled_w = w*width cax.set_position([l+w+padding+(scaled_w+padding+w*0.15)*i, b, scaled_w, h]) ColorbarPlot._colorbars[id(axis)] = (ax_colorbars, (l, b, w, h)) def _norm_kwargs(self, element, ranges, opts, vdim): """ Returns valid color normalization kwargs to be passed to matplotlib plot function. """ clim = opts.pop('clims', None) if clim is None: cs = element.dimension_values(vdim) if not isinstance(cs, np.ndarray): cs = np.array(cs) if len(cs) and cs.dtype.kind in 'if': clim = ranges[vdim.name] if vdim.name in ranges else element.range(vdim) if self.logz: # Lower clim must be >0 when logz=True # Choose the maximum between the lowest non-zero value # and the overall range if clim[0] == 0: vals = element.dimension_values(vdim) clim = (vals[vals!=0].min(), clim[1]) if self.symmetric: clim = -np.abs(clim).max(), np.abs(clim).max() else: clim = (0, len(np.unique(cs))) if self.logz: if self.symmetric: norm = mpl_colors.SymLogNorm(vmin=clim[0], vmax=clim[1], linthresh=clim[1]/np.e) else: norm = mpl_colors.LogNorm(vmin=clim[0], vmax=clim[1]) opts['norm'] = norm opts['vmin'] = clim[0] opts['vmax'] = clim[1] # Check whether the colorbar should indicate clipping values = np.asarray(element.dimension_values(vdim)) if values.dtype.kind not in 'OSUM': try: el_min, el_max = np.nanmin(values), np.nanmax(values) except ValueError: el_min, el_max = -np.inf, np.inf else: el_min, el_max = -np.inf, np.inf vmin = -np.inf if opts['vmin'] is None else opts['vmin'] vmax = np.inf if opts['vmax'] is None else opts['vmax'] if el_min < vmin and el_max > vmax: self._cbar_extend = 'both' elif el_min < vmin: self._cbar_extend = 'min' elif el_max > vmax: self._cbar_extend = 'max' # Define special out-of-range colors on colormap cmap = opts.get('cmap') if isinstance(cmap, list): cmap = mpl_colors.ListedColormap(cmap) elif isinstance(cmap, util.basestring): cmap = copy.copy(plt.cm.get_cmap(cmap)) else: cmap = copy.copy(cmap) colors = {} for k, val in self.clipping_colors.items(): if isinstance(val, tuple): colors[k] = {'color': val[:3], 'alpha': val[3] if len(val) > 3 else 1} elif isinstance(val, util.basestring): color = val alpha = 1 if color.startswith('#') and len(color) == 9: alpha = int(color[-2:], 16)/255. color = color[:-2] colors[k] = {'color': color, 'alpha': alpha} if 'max' in colors: cmap.set_over(**colors['max']) if 'min' in colors: cmap.set_under(**colors['min']) if 'NaN' in colors: cmap.set_bad(**colors['NaN']) opts['cmap'] = cmap
class Callback(param.Parameterized): """ A Callback defines some callback to be triggered when a property changes on the source object. A Callback can execute arbitrary Javascript code and will make all objects referenced in the args available in the JS namespace. """ args = param.Dict(default={}, allow_None=True, doc=""" A mapping of names to Python objects. These objects are made available to the callback's code snippet as the values of named parameters to the callback.""") code = param.Dict(default=None, doc=""" A dictionary mapping from a source specication to a JS code snippet to be executed if the source property changes.""") # Mapping from a source id to a Link instance registry = weakref.WeakKeyDictionary() # Mapping to define callbacks by backend and Link type. # e.g. Callback._callbacks[Link] = Callback _callbacks = {} # Whether the link requires a target _requires_target = False def __init__(self, source, target=None, **params): if source is None: raise ValueError('%s must define a source' % type(self).__name__) # Source is stored as a weakref to allow it to be garbage collected self._source = None if source is None else weakref.ref(source) super().__init__(**params) self.init() def init(self): """ Registers the Callback """ if self.source in self.registry: links = self.registry[self.source] params = { k: v for k, v in self.param.get_param_values() if k != 'name' } for link in links: link_params = { k: v for k, v in link.param.get_param_values() if k != 'name' } if not hasattr(link, 'target'): pass elif (type(link) is type(self) and link.source is self.source and link.target is self.target and params == link_params): return self.registry[self.source].append(self) else: self.registry[self.source] = [self] @classmethod def register_callback(cls, callback): """ Register a LinkCallback providing the implementation for the Link for a particular backend. """ cls._callbacks[cls] = callback @property def source(self): return self._source() if self._source else None @classmethod def _process_callbacks(cls, root_view, root_model): if not root_model: return linkable = root_view.select(Viewable) linkable += root_model.select({'type': BkModel}) if not linkable: return found = [(link, src, getattr(link, 'target', None)) for src in linkable for link in cls.registry.get(src, []) if not link._requires_target or link.target in linkable] arg_overrides = {} if 'holoviews' in sys.modules: from .pane.holoviews import HoloViews, generate_panel_bokeh_map hv_views = root_view.select(HoloViews) map_hve_bk = generate_panel_bokeh_map(root_model, hv_views) for src in linkable: for link in cls.registry.get(src, []): if hasattr(link, 'target'): for tgt in map_hve_bk.get(link.target, []): found.append((link, src, tgt)) arg_overrides[id(link)] = {} for k, v in link.args.items(): # Not all args are hashable try: hv_objs = map_hve_bk.get(v, []) except Exception: continue for tgt in hv_objs: arg_overrides[id(link)][k] = tgt ref = root_model.ref['id'] callbacks = [] for (link, src, tgt) in found: cb = cls._callbacks[type(link)] if ((src is None or ref not in getattr(src, '_models', [ref])) or (getattr(link, '_requires_target', False) and tgt is None) or (tgt is not None and ref not in getattr(tgt, '_models', [ref]))): continue overrides = arg_overrides.get(id(link), {}) callbacks.append( cb(root_model, link, src, tgt, arg_overrides=overrides)) return callbacks
class Sheet(EventProcessor, SheetCoordinateSystem): # pylint: disable-msg=W0223 """ The generic base class for neural sheets. See SheetCoordinateSystem for how Sheet represents space, and EventProcessor for how Sheet handles time. output_fns are functions that take an activity matrix and produce an identically shaped output matrix. The default is having no output_fns. """ __abstract = True nominal_bounds = BoundingRegionParameter(BoundingBox(radius=0.5), constant=True, doc=""" User-specified BoundingBox of the Sheet coordinate area covered by this Sheet. The left and right bounds--if specified--will always be observed, but the top and bottom bounds may be adjusted to ensure the density in the y direction is the same as the density in the x direction. In such a case, the top and bottom bounds are adjusted so that the center y point remains the same, and each bound is as close as possible to its specified value. The actual value of this Parameter is not adjusted, but the true bounds may be found from the 'bounds' attribute of this object. """) nominal_density = param.Number(default=10, constant=True, doc=""" User-specified number of processing units per 1.0 distance horizontally or vertically in Sheet coordinates. The actual number may be different because of discretization; the matrix needs to tile the plane exactly, and for that to work the density might need to be adjusted. For instance, an area of 3x2 cannot have a density of 2 in each direction. The true density may be obtained from either the xdensity or ydensity attribute (since these are identical for a Sheet). """) plastic = param.Boolean(True, doc=""" Setting this to False tells the Sheet not to change its permanent state (e.g. any connection weights) based on incoming events. """) precedence = param.Number(default=0.1, softbounds=(0.0, 1.0), doc=""" Allows a sorting order for Sheets, e.g. in the GUI.""") row_precedence = param.Number(default=0.5, softbounds=(0.0, 1.0), doc=""" Allows grouping of Sheets before sorting precedence is applied, e.g. for two-dimensional plots in the GUI.""") layout_location = param.NumericTuple(default=(-1, -1), precedence=-1, doc=""" Location for this Sheet in an arbitrary pixel-based space in which Sheets can be laid out for visualization.""") output_fns = param.HookList( default=[], class_=TransferFn, doc= "Output function(s) to apply (if apply_output_fns is true) to this Sheet's activity." ) apply_output_fns = param.Boolean( default=True, doc="Whether to apply the output_fn after computing an Activity matrix." ) properties = param.Dict(default={}, doc=""" A dictionary of property values associated with the Sheet object. For instance, the dictionary: {'polarity':'ON', 'eye':'Left'} could be used to indicate a left, LGN Sheet with ON-surround receptive fields.""") def _get_density(self): return self.xdensity density = property(_get_density, doc="""The sheet's true density (i.e. the xdensity, which is equal to the ydensity for a Sheet.)""") def __init__(self, **params): """ Initialize this object as an EventProcessor, then also as a SheetCoordinateSystem with equal xdensity and ydensity. views is an AttrTree, which stores associated measurements, i.e. representations of the sheet for use by analysis or plotting code. """ EventProcessor.__init__(self, **params) # Initialize this object as a SheetCoordinateSystem, with # the same density along y as along x. SheetCoordinateSystem.__init__(self, self.nominal_bounds, self.nominal_density) n_units = round((self.lbrt[2] - self.lbrt[0]) * self.xdensity, 0) if n_units < 1: raise ValueError( "Sheet bounds and density must be specified such that the "+ \ "sheet has at least one unit in each direction; " \ +self.name+ " does not.") # setup the activity matrix self.activity = zeros(self.shape, activity_type) # For non-plastic inputs self.__saved_activity = [] self._plasticity_setting_stack = [] self.views = AttrTree() self.views.Maps = AttrTree() self.views.Curves = AttrTree() ### JABALERT: This should be deleted now that sheet_views is public ### JC: shouldn't we keep that, or at least write a function in ### utils that deletes a value in a dictinnary without returning an ### error if the key is not in the dict? I leave for the moment, ### and have to ask Jim to advise. def release_sheet_view(self, view_name): """ Delete the dictionary entry with key entry 'view_name' to save memory. """ if view_name in self.views.Maps: del self.views.Maps[view_name] # CB: what to call this? sheetcoords()? sheetcoords_of_grid()? idxsheetcoords()? def sheetcoords_of_idx_grid(self): """ Return an array of x-coordinates and an array of y-coordinates corresponding to the activity matrix of the sheet. """ nrows, ncols = self.activity.shape C, R = meshgrid(arange(ncols), arange(nrows)) X, Y = self.matrixidx2sheet(R, C) return X, Y # CB: check whether we need this function any more. def row_col_sheetcoords(self): """ Return an array of Y-coordinates corresponding to the rows of the activity matrix of the sheet, and an array of X-coordinates corresponding to the columns. """ # The row and column centers are returned in matrix (not # sheet) order (hence the reversals below). nrows, ncols = self.activity.shape return self.matrixidx2sheet(arange(nrows - 1, -1, -1), arange(ncols))[::-1] # CBALERT: to be removed once other code uses # row_col_sheetcoords() or sheetcoords_of_idx_grid(). def sheet_rows(self): return self.row_col_sheetcoords()[0] def sheet_cols(self): return self.row_col_sheetcoords()[1] # CEBALERT: haven't really thought about what to put in this. The # way it is now, subclasses could make a super.activate() call to # avoid repeating some stuff. def activate(self): """ Collect activity from each projection, combine it to calculate the activity for this sheet, and send the result out. Subclasses will need to override this method to whatever it means to calculate activity in that subclass. """ if self.apply_output_fns: for of in self.output_fns: of(self.activity) self.send_output(src_port='Activity', data=self.activity) def state_push(self): """ Save the current state of this sheet to an internal stack. This method is used by operations that need to test the response of the sheet without permanently altering its state, e.g. for measuring maps or probing the current behavior non-invasively. By default, only the activity pattern of this sheet is saved, but subclasses should add saving for any additional state that they maintain, or strange bugs are likely to occur. The state can be restored using state_pop(). Note that Sheets that do learning need not save the values of all connection weights, if any, because plasticity can be turned off explicitly. Thus this method is intended only for shorter-term state. """ self.__saved_activity.append(array(self.activity)) EventProcessor.state_push(self) for of in self.output_fns: if hasattr(of, 'state_push'): of.state_push() def state_pop(self): """ Pop the most recently saved state off the stack. See state_push() for more details. """ self.activity = self.__saved_activity.pop() EventProcessor.state_pop(self) for of in self.output_fns: if hasattr(of, 'state_pop'): of.state_pop() def activity_len(self): """Return the number of items that have been saved by state_push().""" return len(self.__saved_activity) def override_plasticity_state(self, new_plasticity_state): """ Temporarily override plasticity of medium and long term internal state. This function should be implemented by all subclasses so that it preserves the ability of the Sheet to compute activity, i.e. to operate over a short time scale, while preventing any lasting changes to the state (if new_plasticity_state=False). Any operation that does not have any lasting state, such as those affecting only the current activity level, should not be affected by this call. By default, simply saves a copy of the plastic flag to an internal stack (so that it can be restored by restore_plasticity_state()), and then sets plastic to new_plasticity_state. """ self._plasticity_setting_stack.append(self.plastic) self.plastic = new_plasticity_state def restore_plasticity_state(self): """ Restores plasticity of medium and long term internal state after a override_plasticity_state call. This function should be implemented by all subclasses to remove the effect of the most recent override_plasticity_state call, i.e. to restore plasticity of any type that was overridden. """ self.plastic = self._plasticity_setting_stack.pop() def n_bytes(self): """ Return a lower bound for the memory taken by this sheet, in bytes. Typically, this number will include the activity array and any similar arrays, plus any other significant data owned (in some sense) by this Sheet. It will not usually include memory taken by the Python dictionary or various "housekeeping" attributes, which usually contribute only a small amount to the memory requirements. Subclasses should reimplement this method if they store a significant amount of data other than in the activity array. """ return self.activity.nbytes def __getitem__(self, coords): metadata = AttrDict(precedence=self.precedence, row_precedence=self.row_precedence, timestamp=self.simulation.time()) sv = Matrix(self.activity.copy(), self.bounds, label=self.name + ' Activity', value='Activity')[coords] sv.metadata = metadata return sv
class ReactTemplate(BasicTemplate): """ ReactTemplate is built on top of React Grid Layout web components. """ compact = param.ObjectSelector( default=None, objects=[None, 'vertical', 'horizontal', 'both']) cols = param.Dict(default={'lg': 12, 'md': 10, 'sm': 6, 'xs': 4, 'xxs': 2}) breakpoints = param.Dict(default={ 'lg': 1200, 'md': 996, 'sm': 768, 'xs': 480, 'xxs': 0 }) main = param.ClassSelector(class_=GridSpec, constant=True, doc=""" A list-like container which populates the main area.""") row_height = param.Integer(default=150) dimensions = param.Dict( default={ 'minW': 0, 'maxW': 'Infinity', 'minH': 0, 'maxH': 'Infinity' }, doc="""A dictonary of minimum/maximum width/height in grid units.""") prevent_collision = param.Boolean(default=False, doc="Prevent collisions between items.") _css = pathlib.Path(__file__).parent / 'react.css' _template = pathlib.Path(__file__).parent / 'react.html' _modifiers = {Card: {'children': {'margin': (20, 20)}, 'margin': (10, 5)}} _resources = { 'js': { 'react': "https://unpkg.com/react@16/umd/react.development.js", 'react-dom': "https://unpkg.com/react-dom@16/umd/react-dom.development.js", 'babel': "https://unpkg.com/babel-standalone@latest/babel.min.js", 'react-grid': "https://cdnjs.cloudflare.com/ajax/libs/react-grid-layout/1.1.1/react-grid-layout.min.js" }, 'css': { 'bootstrap': "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css", 'font-awesome': "https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" } } def __init__(self, **params): if 'main' not in params: params['main'] = GridSpec(ncols=12, mode='override') super().__init__(**params) self._update_render_vars() def _update_render_items(self, event): super()._update_render_items(event) if event.obj is not self.main: return layouts = [] for i, ((y0, x0, y1, x1), v) in enumerate(self.main.objects.items()): if x0 is None: x0 = 0 if x1 is None: x1 = 12 if y0 is None: y0 = 0 if y1 is None: y1 = self.main.nrows elem = { 'x': x0, 'y': y0, 'w': x1 - x0, 'h': y1 - y0, 'i': str(i + 1) } elem.update(self.dimensions) layouts.append(elem) self._render_variables['layouts'] = {'lg': layouts, 'md': layouts} @depends('cols', 'breakpoints', 'row_height', 'compact', 'dimensions', 'prevent_collision', watch=True) def _update_render_vars(self): self._render_variables['breakpoints'] = self.breakpoints self._render_variables['cols'] = self.cols self._render_variables['rowHeight'] = self.row_height self._render_variables['compact'] = self.compact self._render_variables['dimensions'] = self.dimensions self._render_variables['preventCollision'] = self.prevent_collision
class overlaid_plot(PylabPlotCommand): """ Use matplotlib to make a plot combining a bitmap and line-based overlays for a single plot template and sheet. """ plot_template = param.Dict(default={'Hue': 'OrientationPreference'}, doc=""" Template for the underlying bitmap plot.""") overlay = param.List(default=[('contours', 'OcularPreference', 0.5, 'black'), ('arrows', 'DirectionPreference', 'DirectionSelectivity', 'white')], doc=""" List of overlaid plots, where each list item may be a 4-tuple specifying either a contour line or a field of arrows:: ('contours',map-name,contour-value,line-color) ('arrows',arrow-location-map-name,arrow-size-map-name,arrow-color) Any number or combination of contours and arrows may be supplied.""") normalize = param.Boolean(default='Individually', doc=""" Type of normalization, if any, to use. Options include 'None', 'Individually', and 'AllTogether'. See topo.plotting.plotgroup.TemplatePlotGroup.normalize for more details.""") sheet = param.ClassSelector(class_=topo.base.sheet.Sheet, doc=""" The sheet from which sheetViews are to be obtained for plotting.""") def __call__(self, **params): p=ParamOverrides(self,params) name=p.plot_template.keys().pop(0) plot=make_template_plot(p.plot_template, p.sheet.views.Maps, p.sheet.xdensity,p.sheet.bounds, p.normalize,name=p.plot_template[name]) fig = plt.figure(figsize=(5,5)) if plot: bitmap=plot.bitmap isint=plt.isinteractive() # Temporarily make non-interactive for plotting plt.ioff() # Turn interactive mode off plt.imshow(bitmap.image,origin='lower',interpolation='nearest') plt.axis('off') for (t,pref,sel,c) in p.overlay: v = plt.flipud(p.sheet.views.Maps[pref].view()[0]) if (t=='contours'): plt.contour(v,[sel,sel],colors=c,linewidths=2) if (t=='arrows'): s = plt.flipud(p.sheet.views.Maps[sel].view()[0]) scale = int(np.ceil(np.log10(len(v)))) X = np.array([x for x in xrange(len(v)/scale)]) v_sc = np.zeros((len(v)/scale,len(v)/scale)) s_sc = np.zeros((len(v)/scale,len(v)/scale)) for i in X: for j in X: v_sc[i][j] = v[scale*i][scale*j] s_sc[i][j] = s[scale*i][scale*j] plt.quiver(scale*X, scale*X, -np.cos(2*np.pi*v_sc)*s_sc, -np.sin(2*np.pi*v_sc)*s_sc, color=c, edgecolors=c, minshaft=3, linewidths=1) p.title='%s overlaid with %s at time %s' %(plot.name,pref,topo.sim.timestr()) if isint: plt.ion() p.filename_suffix="_"+p.sheet.name self._generate_figure(p) return fig
class DeckGL(PaneBase): """ DeckGL panes allow rendering Deck.Gl/ PyDeck plots in Panel. """ mapbox_api_key = param.String(default=None, doc=""" The MapBox API key if not supplied by a PyDeck object.""") tooltips = param.ClassSelector(default=True, class_=(bool, dict), doc=""" Whether to enable tooltips""") click_state = param.Dict(default={}, doc=""" Contains the last click event on the DeckGL plot.""") hover_state = param.Dict(default={}, doc=""" The current hover state of the DeckGL plot.""") view_state = param.Dict(default={}, doc=""" The current view state of the DeckGL plot.""") _rename = { 'click_state': 'clickState', 'hover_state': 'hoverState', 'view_state': 'viewState', 'tooltips': 'tooltip' } _updates = True priority = None @classmethod def applies(cls, obj): if (hasattr(obj, "to_json") and hasattr(obj, "mapbox_key") and hasattr(obj, "deck_widget")): return 0.8 elif isinstance(obj, (dict, string_types)): return 0 return False def _get_properties(self, layout=True): if self.object is None: data, mapbox_api_key, tooltip = {}, self.mapbox_api_key, self.tooltips elif isinstance(self.object, (string_types, dict)): if isinstance(self.object, string_types): data = json.loads(self.object) else: data = dict(self.object) data['layers'] = [ dict(layer) for layer in data.get('layers', []) ] mapbox_api_key = self.mapbox_api_key tooltip = self.tooltips else: data = dict(self.object.__dict__) mapbox_api_key = data.pop('mapbox_key', self.mapbox_api_key) deck_widget = data.pop('deck_widget', None) tooltip = deck_widget.tooltip data = recurse_data(data) if layout: properties = { p: getattr(self, p) for p in Layoutable.param if getattr(self, p) is not None } else: properties = {} return data, dict(properties, tooltip=tooltip, mapbox_api_key=mapbox_api_key or "") @classmethod def _process_data(cls, data): columns = defaultdict(list) for d in data: for col, val in d.items(): columns[col].append(val) return {col: np.asarray(vals) for col, vals in columns.items()} @classmethod def _update_sources(cls, json_data, sources): layers = json_data.get('layers', []) # Create index of sources by columns source_columns = defaultdict(list) for i, source in enumerate(sources): key = tuple(sorted(source.data.keys())) source_columns[key].append((i, source)) # Process unprocessed, unused = [], list(sources) for layer in layers: data = layer.get('data') if is_dataframe(data): data = ColumnDataSource.from_df(data) elif (isinstance(data, list) and data and isinstance(data[0], dict)): data = cls._process_data(data) else: continue key = tuple(sorted(data.keys())) existing = source_columns.get(key) if existing: index, cds = existing.pop() layer['data'] = index updates = {} for col, values in data.items(): if not np.array_equal(data[col], cds.data[col]): updates[col] = values if updates: cds.data.update(updates) unused.remove(cds) else: unprocessed.append((layer, data)) for layer, data in unprocessed: if unused: cds = unused.pop() cds.data = data else: cds = ColumnDataSource(data) sources.append(cds) layer['data'] = sources.index(cds) def _get_model(self, doc, root=None, parent=None, comm=None): if "panel.models.deckgl" not in sys.modules: if isinstance(comm, JupyterComm): self.param.warning( "DeckGLPlot was not imported on instantiation " "and may not render in a notebook. Restart " "the notebook kernel and ensure you load " "it as part of the extension using:" "\n\npn.extension('deckgl')\n") from ..models.deckgl import DeckGLPlot else: DeckGLPlot = getattr(sys.modules["panel.models.deckgl"], "DeckGLPlot") data, properties = self._get_properties() properties['data_sources'] = sources = [] self._update_sources(data, sources) properties['layers'] = data.pop('layers', []) properties['initialViewState'] = data.pop('initialViewState', {}) model = DeckGLPlot(data=data, **properties) root = root or model self._link_props(model, ['clickState', 'hoverState', 'viewState'], doc, root, comm) self._models[root.ref["id"]] = (model, parent) return model def _update(self, model): data, properties = self._get_properties(layout=False) self._update_sources(data, model.data_sources) properties['data'] = data properties['layers'] = data.pop('layers', []) properties['initialViewState'] = data.pop('initialViewState', {}) model.update(**properties)