class SimpleWatchExample(param.Parameterized): a = param.Parameter(default=0) b = param.Parameter(default=0) c = param.Parameter(default=0) d = param.Integer(default=0) e = param.Event() f = param.Event() def method(self, event): self.b = self.a * 2
class Transform(param.Parameterized): """Gets data and applies transform""" _type = "base" widgets = param.Dict(default={}) updated = param.Event() redrawn = param.Event( doc="event gets triggered when widgets are changed and the controller needs to redraw them" ) _hash = param.Integer(doc="Hash of current transform state") _cache = param.ClassSelector(default=Cache(), class_=Cache) def __init__(self, **params): super().__init__(**params) # perhaps htey should all be private to prevent namespace collision with filter options @property def source_hash(self): return self.source.hash @property def hash_key(self): """hashable key describing the transform""" excluded = ["updated", "source", "widgets"] return tuple( (item, make_tuple(val)) for item, val in self.param.get_param_values() if not (item.startswith("_") or item in excluded) ) @property def hash(self): tup = (*self.hash_key, self.source_hash) return hash(tup) def update_hash(self): if self.hash == self._hash: return False else: self._hash = self.hash return True def update(self): if self.update_hash(): self._update_options() # todo this shouldnt be here self.updated = True
class Button(_ClickButton): clicks = param.Integer(default=0) value = param.Event() _rename = {'clicks': None, 'name': 'label', 'value': None} _widget_type = _BkButton def _server_click(self, doc, ref, event): processing = bool(self._events) self._events.update({"clicks": self.clicks + 1}) self.param.trigger('value') if not processing: if doc.session_context: doc.add_timeout_callback(partial(self._change_coroutine, doc), self._debounce) else: self._change_event(doc) def _process_property_change(self, msg): msg = super()._process_property_change(msg) if 'clicks' in msg: msg['clicks'] = self.clicks + 1 return msg def on_click(self, callback): self.param.watch(callback, 'clicks', onlychanged=False)
class WebAppTransform(param.Parameterized): #todo subclass from Transform? updated = param.Event() def __init__(self, **params): super().__init__(**params) self.widgets = self.generate_widgets() @property def panel(self): return pn.Column(*self.widgets.values()) # widget = self.widget.clone() # self.widget.link(widget, value='value', bidirectional=True) # return widget def generate_widgets(self, **kwargs): """returns a dict with keys parameter names and values default mapped widgets""" names = [ p for p in self.param if self.param[p].precedence is None or self.param[p].precedence > 1 ] widgets = pn.Param(self.param, show_name=False, show_labels=True, widgets=kwargs) return {k: v for k, v in zip(names[1:], widgets)}
class Source(param.Parameterized): """Base class for sources""" _type = "base" updated = param.Event() def get(self): raise NotImplementedError()
class Button(_ClickButton): clicks = param.Integer(default=0) value = param.Event() _rename = {'clicks': None, 'name': 'label', 'value': None} _target_transforms = {'event:button_click': None, 'value': None} _widget_type = _BkButton @property def _linkable_params(self): return super()._linkable_params + ['value'] def jslink(self, target, code=None, args=None, bidirectional=False, **links): links = { 'event:' + self._event if p == 'value' else p: v for p, v in links.items() } super().jslink(target, code, args, bidirectional, **links) jslink.__doc__ = Widget.jslink.__doc__ def _server_click(self, doc, ref, event): processing = bool(self._events) self._events.update({"clicks": self.clicks + 1}) self.param.trigger('value') if not processing: if doc.session_context: doc.add_timeout_callback(partial(self._change_coroutine, doc), self._debounce) else: self._change_event(doc) def _process_property_change(self, msg): msg = super()._process_property_change(msg) if 'clicks' in msg: msg['clicks'] = self.clicks + 1 return msg def on_click(self, callback): self.param.watch(callback, 'clicks', onlychanged=False)
class TestButton(param.Parameterized): action = param.Action(lambda x: x._print_something()) event = param.Event() updating = param.Boolean() view = param.Parameter(precedence=-1) def __init__(self, **params): super().__init__(**params) self.view = self._create_view() @param.depends("event", watch=True) def _print_something(self): if self.updating: return self.updating = True print(f"event: {self.event}, action: {self.action}") time.sleep(1) self.updating = False @param.depends("updating", watch=True) def toggle_loading(self): self._action_button.loading = self.updating self._event_button.loading = self.updating def _create_view(self): widgets = pn.Param( self, parameters=["action", "event", "updating"], widgets={ "action": { "button_type": "primary" }, "event": { "button_type": "success" }, "updating": { "disabled": True }, }, ) self._action_button = widgets[1] self._event_button = widgets[2] self._updating_checkbox = widgets[3] return widgets
class TestButton(param.Parameterized): action = param.Action(lambda x: x._print_something()) event = param.Event() updating = param.Boolean() @param.depends("event", watch=True) def _print_something(self): if self.updating: return self.updating = True print(f"event: {self.event}, action: {self.action}") time.sleep(1) self.updating = False
class OptsBase(param.Parameterized): _type = None updated = param.Event() def __init__(self, **params): super().__init__(**params) self._excluded_from_opts = ["name" ] # todo remove this and opts property self.widgets = self.generate_widgets() @property def panel(self): return pn.Column(*self.widgets.values()) @property def opts(self): opts = { name: self.param[name] for name in self.param if name not in self._excluded_from_opts } return opts def generate_widgets(self, **kwargs): """returns a dict with keys parameter names and values default mapped widgets""" # todo base class? names = [ p for p in self.param if self.param[p].precedence is None or self.param[p].precedence > 1 ] widgets = pn.Param(self.param, show_name=False, show_labels=True, widgets=kwargs) return {k: v for k, v in zip(names[1:], widgets)}
class WebAppFilter(Filter): """ """ source = param.ClassSelector(Source) # maybe instead of making filters co-dependent the second filter should have a DerivedSource # but we'll deal with this later filters = param.List() updated = param.Event() def __init__(self, **params): super().__init__(**params) if self.source: self.source.param.watch(self.update, 'updated') for filt in self.filters: filt.param.watch(self.update, 'updated') def get_data(self): """Equivalent to view's get_data method""" query = { filt.field: filt.query for filt in self.filters if filt.query is not None and ( filt.table is None or filt.table == self.table) } data = self.source.get(self.table, **query) return data @param.depends('value', watch=True) def update(self, *events): """gets called when the source event triggers""" self.updated = True
class SpeechToText(Widget): """ The SpeechToText widget controls the speech recognition service of the browser. It wraps the HTML5 SpeechRecognition API. See https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition This functionality is **experimental** and only supported by Chrome and a few other browsers. Checkout https://caniuse.com/speech-recognition for a up to date list of browsers supporting the SpeechRecognition Api. Or alternatively https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition#Browser_compatibility On some browsers, like Chrome, using Speech Recognition on a web page involves a server-based recognition engine. Your audio is sent to a web service for recognition processing, so it won't work offline. Whether this is secure and confidential enough for your use case is up to you to evaluate. """ abort = param.Event(doc=""" Stops the speech recognition service from listening to incoming audio, and doesn't attempt to return a RecognitionResult.""") start = param.Event(doc=""" Starts the speech recognition service listening to incoming audio with intent to recognize grammars associated with the current SpeechRecognition.""") stop = param.Event(doc=""" Stops the speech recognition service from listening to incoming audio, and attempts to return a RecognitionResult using the audio captured so far.""") lang = param.ObjectSelector(default="", objects=[""] + LANGUAGE_CODES, allow_None=True, label="Language", doc=""" The language of the current SpeechRecognition in BCP 47 format. For example 'en-US'. If not specified, this defaults to the HTML lang attribute value, or the user agent's language setting if that isn't set either. """) continuous = param.Boolean(default=False, doc=""" Controls whether continuous results are returned for each recognition, or only a single result. Defaults to False""") interim_results = param.Boolean(default=False, doc=""" Controls whether interim results should be returned (True) or not (False.) Interim results are results that are not yet final (e.g. the RecognitionResult.is_final property is False).""") max_alternatives = param.Integer(default=1, bounds=(1, 5), doc=""" Sets the maximum number of RecognitionAlternatives provided per result. A number between 1 and 5. The default value is 1.""") service_uri = param.String(doc=""" Specifies the location of the speech recognition service used by the current SpeechRecognition to handle the actual recognition. The default is the user agent's default speech service.""") grammars = param.ClassSelector(class_=GrammarList, doc=""" A GrammarList object that represents the grammars that will be understood by the current SpeechRecognition service""") button_hide = param.Boolean(default=False, label="Hide the Button", doc=""" If True no button is shown. If False a toggle Start/ Stop button is shown.""" ) button_type = param.ObjectSelector(default="light", objects=BUTTON_TYPES, doc=""" The button styling.""") button_not_started = param.String(label="Button Text when not started", doc=""" The text to show on the button when the SpeechRecognition service is NOT started. If '' a *muted microphone* icon is shown.""") button_started = param.String(label="Button Text when started", doc=""" The text to show on the button when the SpeechRecognition service is started. If '' a *muted microphone* icon is shown.""") started = param.Boolean(constant=True, doc=""" Returns True if the Speech Recognition Service is started and False otherwise.""") audio_started = param.Boolean(constant=True, doc=""" Returns True if the Audio is started and False otherwise.""") sound_started = param.Boolean(constant=True, doc=""" Returns True if the Sound is started and False otherwise.""") speech_started = param.Boolean(constant=True, doc=""" Returns True if the the User has started speaking and False otherwise.""" ) results = param.List(constant=True, doc=""" The `results` as a list of Dictionaries.""") value = param.String(constant=True, label="Last Result", doc=""" The transcipt of the highest confidence RecognitionAlternative of the last RecognitionResult. Please note we strip the transcript for leading spaces.""") _grammars = param.List(constant=True, doc=""" List used to transfer the serialized grammars from server to browser.""") _widget_type = _BkSpeechToText _rename = { "value": None, "grammars": None, "_grammars": "grammars", } def __init__(self, **params): super().__init__(**params) if self.grammars: self._update_grammars() def __repr__(self, depth=None): # Custom repr needed to avoid infinite recursion because this Parameterized class has # multiple actions return f"SpeechToText(name='{self.name}')" @param.depends("grammars", watch=True) def _update_grammars(self): with param.edit_constant(self): if self.grammars: self._grammars = self.grammars.serialize() # pylint: disable=no-member else: self._grammars = [] @param.depends("results", watch=True) def _update_results(self): # pylint: disable=unsubscriptable-object with param.edit_constant(self): if self.results and "alternatives" in self.results[-1]: self.value = (self.results[-1]["alternatives"][0]["transcript"] ).lstrip() else: self.value = "" @property def results_deserialized(self): """ Returns the results as a List of RecognitionResults """ return RecognitionResult.create_from_list(self.results) @property def results_as_html(self) -> str: """ Returns the `results` formatted as html Convenience method for ease of use """ if not self.results: return "No results" html = "<div class='pn-speech-recognition-result'>" total = len(self.results) - 1 for index, result in enumerate(reversed(self.results_deserialized)): if len(self.results) > 1: html += f"<h3>Result {total-index}</h3>" html += f"<span>Is Final: {result.is_final}</span><br/>" for index2, alternative in enumerate(result.alternatives): if len(result.alternatives) > 1: html += f"<h4>Alternative {index2}</h4>" html += f""" <span>Confidence: {alternative.confidence:.2f}</span> </br> <p> <strong>{alternative.transcript}</strong> </p> """ html += "</div>" return html
class GridEditor(param.Parameterized): """ Interactive boundary editor for previewing and generating pygridgen grids. Core features: * Add, drag and delete nodes interactively with the PointsDraw tool * Toggle their polarity (beta) value with the Tap Tool * Insert nodes into selected edges after selecting them with the Tap Tool. * Set a focus function and update the grid * Pythonic access to the geopandas boundary DataFrame. * Serializable state to capture editor state between sessions * Customizable background tile sources or background tile elements. For more information please visit https://github.com/pygridgen/hologridgen """ # Algorithmic parameters hidden from the GUI max_nodes = param.Integer(default=1000, precedence=-1, doc = "Maximum number of nodes in a boundary") ul_idx=param.Integer(default=0, precedence=-1, doc='Upper left index: parameter of grid generation') polarity_value = param.Dict(default={'+':1, '0':0, '-':-1}, precedence=-1, doc=""" Beta values to associate with the three node polarities (positive '+', neutral '0' and negative '-'). Setting '+':4/3 for instance enables grid generation with only three positive nodes.""") # Parameters not in the GUI for Pythonic data access focus = param.ClassSelector(default=None, class_=pgg.Focus, allow_None=True, precedence=-1, doc=""" Parameter that can be set to a pygridgen Focus object (or None). When set, mesh generation will apply the specified Focus function.""") grid = param.ClassSelector(default=None, class_=pgg.grid.Gridgen, allow_None=True, precedence=-1, doc=""" Parameter that exposes the pygridgen Gridgen object that will be set after mesh generation """) ready = param.Boolean(default=False, precedence=-1, doc=""" Boolean predicate indicating readiness for mesh generation: mesh generation can be executed when the sum of the polarity in the boundary is 4.""") # User settings hidden from the GUI, settable in the constructor (precedence= -1) width = param.Integer(default=600, precedence=-1, bounds=(200, 1000), doc="Width of the HoloViews object corresponding to the editor view area") height = param.Integer(default=600, precedence=-1, bounds=(200, 1000), doc="Height of the HoloViews object corresponding to the editor view area") custom_background = param.Parameter(default=None, precedence=-1, doc=""" Custom HoloViews element to use as the background when the background parameter is set to 'Custom'.""") background = param.ObjectSelector('None', objects=TILE_SOURCES.keys(), doc=""" Selector of available default tile sources which can also be set to 'None' for no background or 'Custom' in which case the HoloViews/GeoViews element set in the custom_background parameter (if any) is used as the background.""") # Customizable HoloViews styles (hidden from the GUI, settable in the constructor) node_style = param.Dict(dict(cmap={'+': 'red', '-': 'blue', '0':'black'}), precedence=-1, doc=""" Style options for nodes. Note that the size is overidden by the node_size param controllable in the GUI and the polarity colors can be changed by setting the cmap dictionary.""") mesh_style = param.Dict(dict(line_width=2, line_alpha=1, line_color='blue'), precedence=-1, doc="Style options for displayed mesh.") edge_style = param.Dict(dict(line_width=2, line_alpha=1, line_color='green', nonselection_color='green', nonselection_alpha=0.5), precedence=-1, doc=""" Style options for displayed boundary edges. The nonselection_* options set how deselected edges appear when the Tap tool is used to insert new nodes into edges""") start_indicator_style = param.Dict( dict(marker='triangle', angle=30, fill_alpha=0, size=30, color='black'), precedence=-1, doc=""" Style of the start marker indicating the first boundary point. Default is a triangle that can be rotated by setting the 'angle' keyword.""") # GUI controllable parameters node_size = param.Integer(default=10, bounds=(1,200), doc="Size of nodes used to mark the boundary.") edge_width = param.Integer(default=2, bounds=(1,50), doc="Width of the boundary edges") xres = param.Integer(default=50, bounds=(2, None), doc=""" X resolution of the generated grid""") yres = param.Integer(default=50, bounds=(2, None), doc=""" Y resolution of the generated grid""") generate_mesh = param.Event(doc='Event that runs mesh generation') hide_mesh = param.Event(doc='Event that clears displayed mesh') insert_points = param.Event( doc='Event that inserts a new node into an edge selected with the Tap tool') _columns = ['color', 'polarity', 'x', 'y'] def __init__(self, data=None, **params): data_params = {} if data is None else {k:v for k,v in data.items() if k not in self._columns} params = dict(data_params, **params) data = {k:[] for k in self._columns} if (data is None) else data super().__init__(**params) def install_handle(plot, element): "Handle needed to make the draw_tool available in the JS callback" plot.handles['draw_tool'] = plot.state.tools[-1] node_style = dict(self.node_style, tools=['tap'], color=hv.dim('polarity'), fill_alpha=hv.dim('polarity').categorize({'0':0, '+':1, '-':1 }), show_legend=False, hooks=[install_handle]) # PointDraw Stream that enables the PointDraw Bokeh tool self._node_stream = hv.streams.PointDraw(data=data, num_objects=self.max_nodes, empty_value = '+') # Nodes is a DynamicMap returning hv.Points along the boundary self.nodes = hv.DynamicMap(self.points, streams=[self._node_stream, self.param.insert_points, self.param.node_size]).opts(**node_style) # DynamicMap drawing the boundary as a hv.Path element self.boundary_dmap = hv.DynamicMap(self._boundary, streams=[self._node_stream, hv.streams.Selection1D()]) # DynamicMap placing the start indicator self.start_marker = hv.DynamicMap(self._start_marker, streams=[self._node_stream] ).opts(**self.start_indicator_style) # Initial, empty mesh self.qmesh = hv.QuadMesh((np.zeros((2,2)), np.zeros((2,2)), np.zeros((2,2)))) self._selected_edge_index = None @classmethod def from_geopandas(cls, df): """ Classmethod that allows a GridEditor to be initialized from a boundary geopandas DataFrame (such as the one available from the .boundary property). """ if len(df) == 0: return GridEditor() allowed = [el for el in cls._columns if el != 'geometry'] color_vals = {v:k for k,v in cls.polarity_value.items()} data = {k:list(v) for k,v in df.to_dict(orient='list').items() if k in allowed} data['color'] = [color_vals[p] for p in data['polarity']] data['polarity'] = data['color'] return GridEditor(data) @property def boundary(self): "Property returning the boundary GeoDataFrame" exclude = ['color', 'fill_alpha'] data = self._node_stream.data polarity = [self.polarity_value[c] for c in data['color']] df_data = {c:[el for el in v] for c,v in data.items() if c not in exclude} df_data['polarity'] = polarity df = pd.DataFrame(df_data) return geopandas.GeoDataFrame(df, geometry=geopandas.points_from_xy(df.x, df.y)) @property def data(self, exclude=['name', 'fill_alpha', 'grid']): """ Propery exposing serializable state of the editor that can be passed into the GridEditor constructor to restore that state """ data = {k:[el for el in v] for k,v in self._node_stream.data.items()} param_data = {p:getattr(self, p) for p in self.param} data.update(param_data) return {k:v for k,v in data.items() if k not in exclude} def _ready(self): "Predicate method indicating current readiness for mesh generation" data = self._node_stream.data if len(data['x']) > 3: summed = sum(self.polarity_value[el] for el in data['color']) return (summed == 4) else: return False @pn.depends('ready', watch=True) def _check_readiness(self): "Callback used to disable generate mesh button till ready" for widget in self.widgets: if isinstance(widget, pn.widgets.button.Button) and (widget.name == 'Generate mesh'): button = widget break button.disabled = not self.ready @pn.depends('_node_stream.data') def _geojson(self): "Callback to generate GeoJSON file when download button is clicked" boundary = self.boundary bio = BytesIO() if len(boundary) != 0: boundary.to_file(bio, driver='GeoJSON') bio.seek(0) return bio # DynamicMap callbacks def points(self, data, insert_points, node_size): "DynamicMap callback returns Points representing boundary nodes" new_data = {'x': data['x'], 'y': data['y'], 'polarity' : np.array(data['color'], dtype='U1')} if insert_points and len(self._selected_edge_index)==1: point_index = self._selected_edge_index[0] + 1 sx, ex = new_data['x'][point_index-1], new_data['x'][point_index] sy, ey = new_data['y'][point_index-1], new_data['y'][point_index] new_data['x'] = np.insert(new_data['x'], point_index, (sx+ex) / 2.) new_data['y'] = np.insert(new_data['y'], point_index, (sy+ey) / 2.) new_data['polarity'] = np.insert(new_data['polarity'], point_index, '+') return hv.Points(new_data, vdims=['polarity']).opts(size=node_size) def _generate_mesh(self, generate_mesh=False, hide_mesh=False): "Callback returning generated QuadMesh element" if not self.ready: return self.qmesh.opts(fill_alpha=0, line_alpha=0) elif hide_mesh or (not generate_mesh): return self.qmesh.opts(fill_alpha=0, line_alpha=0) if self.ready: gdf = self.boundary kwargs = dict(shape=(self.xres, self.yres), ul_idx=self.ul_idx) if self.focus is not None: kwargs['focus'] = self.focus self.grid = pgg.Gridgen(gdf.geometry.x, gdf.geometry.y, gdf.polarity, **kwargs) xdim, ydim = self.grid.x.shape zs = np.ones((xdim-1, ydim-1)) self.qmesh = hv.QuadMesh((np.array(self.grid.x), np.array(self.grid.y), zs)) return self.qmesh.opts(**self.mesh_style, fill_alpha=0) def _boundary(self, data, index): "Callback drawing Path element defining boundary" self._selected_edge_index = index xs, ys = data['x'], data['y'] lines = [] for i in range(len(xs)-1): s, e = i, (i+1) lines.append([(xs[s], ys[s]), (xs[e], ys[e])]) self.ready = self._ready() return hv.Path(lines).opts(**self.edge_style) def _start_marker(self, data): "Callback to draw the start marker" if len(data['x']) == 0: return hv.Points(None) return hv.Points((data['x'][0], data['y'][0])) def _background(self, background): """ Callback that allows the background to be switched between tile sources, a custom background element or None for no background """ elements = [] if background != 'None': elements = [TILE_SOURCES[background].opts(global_extent=True, alpha=1)] elif background == 'None': if self.custom_background: elements = [self.custom_background] else: elements = [TILE_SOURCES[list(TILE_SOURCES.keys())[0]].opts(alpha=0)] return hv.Overlay(elements) def view(self): "Main entry point for using the GridEditor after construction" self.polarity_link = PolaritySwap(self.nodes) param_stream = hv.streams.Params(self, ['generate_mesh', 'hide_mesh'], transient=True) elements = [hv.DynamicMap(self._background, streams=[self.param.background]), self.boundary_dmap.apply.opts(line_width=self.param.edge_width), self.start_marker, self.nodes, hv.DynamicMap(self._generate_mesh, streams=[param_stream])] hvobj = hv.Overlay(elements).collate() self.widgets = pn.Param(self.param, widgets={'edge_select_mode': pn.widgets.Toggle}) obj = pn.Row(pn.Column(self.widgets, pn.widgets.FileDownload(callback=self._geojson, filename='boundary.geojson')), hvobj.opts(width=self.width, height=self.height)) self.param.trigger('ready') return obj
class TextToSpeech(Utterance, Widget): """ The `TextToSpeech` widget wraps the HTML5 SpeechSynthesis API See https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis Reference: https://panel.holoviz.org/reference/widgets/TextToSpeech.html :Example: >>> TextToSpeech(name="Speech Synthesis", value="Data apps are nice") """ auto_speak = param.Boolean(default=True, doc=""" Whether or not to automatically speak when the value changes.""") cancel = param.Event(doc=""" Removes all utterances from the utterance queue.""") pause = param.Event(doc=""" Puts the TextToSpeak object into a paused state.""") resume = param.Event(doc=""" Puts the TextToSpeak object into a non-paused state: resumes it if it was already paused.""") paused = param.Boolean(readonly=True, doc=""" A Boolean that returns true if the TextToSpeak object is in a paused state.""") pending = param.Boolean(readonly=True, doc=""" A Boolean that returns true if the utterance queue contains as-yet-unspoken utterances.""") speak = param.Event(doc=""" Speak. I.e. send a new Utterance to the browser""") speaking = param.Boolean(readonly=True, doc=""" A Boolean that returns true if an utterance is currently in the process of being spoken — even if TextToSpeak is in a paused state.""") voices = param.List(readonly=True, doc=""" Returns a list of Voice objects representing all the available voices on the current device.""") _voices = param.List() _widget_type = _BkTextToSpeech _rename = { "auto_speak": None, "lang": None, "pitch": None, "rate": None, "speak": None, "value": None, "voice": None, "voices": None, "volume": None, "_voices": "voices", } def _process_param_change(self, msg): speak = msg.get('speak') or ('value' in msg and self.auto_speak) msg = super()._process_param_change(msg) if speak: msg['speak'] = self.to_dict() return msg @param.depends("_voices", watch=True) def _update_voices(self): voices = [] for _voice in self._voices: # pylint: disable=not-an-iterable voice = Voice(**_voice) voices.append(voice) self.voices = voices self.set_voices(self.voices) def __repr__(self, depth=None): # We need to do this because otherwise a error is raised when used in notebook # due to infinite recursion return f"TextToSpeech(name={self.name})" def __str__(self): return f"TextToSpeech(name={self.name})"
class DataFrameSource(Source): tables = param.Dict({}, doc="Dictionary of tables in this Source") updated = param.Event() dropna = param.Boolean( True, doc= 'Remove rows of all NaN when adding / selecting / removing dataframes' ) # def __init__(self, **params): # pass # super().__init__(**params) # if self.df.columns.nlevels == 1: # self.tables = [self.name] # self.multiindex = False # elif self.df.columns.nlevels == 2: # self.multiindex = True # self.tables = [] # todo populate tables for multiindex # else: # raise ValueError("Currently column multiindex beyond two levels is not supported") def remove_df(self, table, name, level): raise NotImplementedError('Removing datafarmes not implemented') self.updated = True def add_df(self, df, table, names=None): """ #Todo method for adding a table to multindex source Parameters ---------- df Returns ------- """ # todo check if df already present, update? target_df = self.tables[table] df = df.copy() if target_df.columns.nlevels != df.columns.nlevels: if isinstance(names, str): names = [names] if len(names) != target_df.columns.nlevels - df.columns.nlevels: raise ValueError( f"Insufficient names provided to match target dataframe multindex level {df.columns.nlevels}" ) if df.columns.nlevels == 1: cols = ((column, ) for column in df.columns) else: cols = df.columns tuples = tuple((*names, *tup) for tup in cols) new_index = pd.MultiIndex.from_tuples( tuples, names=target_df.columns.names) df.columns = new_index new_df = pd.concat([target_df, df], axis=1) if self.dropna: new_df = new_df.dropna(how='all') self.tables[table] = new_df #todo check for row indices self.updated = True def get(self, table, **query): df = self.tables[table] # This means querying a field with the same name as higher-levels columns is not possible while df.columns.nlevels > 1: selected_col = query.pop(df.columns.names[0], False) if selected_col: df = df[selected_col] if self.dropna: df = df.dropna( how='all' ) # These subsets will have padded NaN rows. Remove? else: break dask = query.pop('__dask', False) df = self._filter_dataframe(df, **query) return df @cached_schema def get_schema(self, table=None): schemas = {} for name in self.tables: if table is not None and name != table: continue df = self.get(name) schemas[name] = get_dataframe_schema(df)['items']['properties'] return schemas if table is None else schemas[table] def get_unique(self, table=None, field=None, **query): """Get unique values for specified tables and fields""" print('deprecation candidate') unique_values = {} for name in self.tables: if table is not None and name != table: continue df = self.get(name, **query) if field is not None: unique_values[name] = df[field].unique() else: unique_values[name] = { field_name: df[field_name].unique() for field_name in df.columns } return unique_values if table is None else unique_values[table]
class Button(_ClickButton): """ The `Button` widget allows triggering events when the button is clicked. The Button provides a `value` parameter, which will toggle from `False` to `True` while the click event is being processed It also provides an additional `clicks` parameter, that can be watched to subscribe to click events. Reference: https://panel.holoviz.org/reference/widgets/Button.html#widgets-gallery-button :Example: >>> pn.widgets.Button(name='Click me', button_type='primary') """ clicks = param.Integer(default=0, doc=""" Number of clicks (can be listened to)""") value = param.Event(doc=""" Toggles from False to True while the event is being processed.""") _rename = {'clicks': None, 'name': 'label', 'value': None} _target_transforms = {'event:button_click': None, 'value': None} _widget_type = _BkButton @property def _linkable_params(self): return super()._linkable_params + ['value'] def jslink(self, target, code=None, args=None, bidirectional=False, **links): links = { 'event:' + self._event if p == 'value' else p: v for p, v in links.items() } super().jslink(target, code, args, bidirectional, **links) jslink.__doc__ = Widget.jslink.__doc__ def _process_event(self, event): self.param.trigger('value') self.clicks += 1 def on_click(self, callback): """ Register a callback to be executed when the `Button` is clicked. The callback is given an `Event` argument declaring the number of clicks Arguments --------- callback: (callable) The function to run on click events. Must accept a positional `Event` argument """ self.param.watch(callback, 'clicks', onlychanged=False)