def proofValidity(self, val=False, desc="valid"): validity= widgets.Valid( value=val, description=str(desc), style = self.style ) return validity
def on_field_filled(self, *args): for i, value in enumerate(self.part_info.participant_info_name): if self.part_info.participant_info_name.get( self.participant[i].description )[0] > self.participant[i].value: widgets.Valid(value=False, description='Der eingegebene Wert ist zu tief', style={'description_width': 'initial'}) elif self.part_info.participant_info_name.get( self.participant[i].description )[1] < self.participant[i].value: widgets.Valid(value=False, description='Der eingegebene Wert ist zu hoch', style={'description_width': 'initial'}) else: widgets.Valid(value=True, description='', style={'description_width': 'initial'})
def valid(boolVal): # If the boolean is True if boolVal == True: valid_widget = widgets.Valid( value=boolVal, description='Valid!', ) display(valid_widget) print("Congrats!!") return True # If the boolean is false elif boolVal == False: valid_widget = widgets.Valid( value=boolVal, description='', ) display(valid_widget) print("Try Again!")
def __init__(self, *args): """ Constructs a Trial object. """ inc = args[0].validate() ex = args[1].validate() wizard_page = args[3] ## Validate In-/Exclusion criteria ## if wizard_page == 4: if inc and ex == True: self.validator = widgets.Valid( value=True, description='Subject ist geeignet für die Studie', style={'description_width': 'initial'}) else: self.validator = widgets.Valid( value=False, description='Subject ist nicht geeignet für die Studie', style={'description_width': 'initial'}) self.wizard_page = 100
def __init__(self, on_true=None, on_false=None, on_true_output=None, on_false_output=None, \ on_true_overwrite_previous_output=True, on_false_overwrite_previous_output=True, on_true_feedback=False, \ on_false_feedback=False, run=True, layout={'width':'max-content'}, indent=False, on_true_action_kws={}, on_false_action_kws={}, *args, **kwargs): super().__init__(on_true=on_true, on_false=on_false, on_true_output=on_true_output, on_false_output=on_false_output, \ on_true_overwrite_previous_output=on_true_overwrite_previous_output, on_true_action_kws=on_true_action_kws, on_false_overwrite_previous_output=on_false_overwrite_previous_output, on_true_feedback=on_true_feedback, \ on_false_feedback=on_false_feedback, on_false_action_kws=on_false_action_kws) self.widget = widgets.Valid(layout=layout, indent=indent, *args, **kwargs) if run: self.run()
def old_initiate(self): tab_children = [] ########################### # data 1 box d1_vbox_childs = [] ## ### d1_button_next = widgets.Button(description='next measurement') d1_button_prev = widgets.Button(description='prev measurement') d1_button_next.on_click(self.on_d1_botton_next) d1_button_prev.on_click(self.on_d1_botton_prev) d1_box_h_1 = widgets.HBox([d1_button_prev, d1_button_next]) ### d1_vbox_childs.append(d1_box_h_1) ## ### d1_text_path = widgets.Text(placeholder='path name', disabled=False) self.d1_text_path = d1_text_path d1_vbox_childs.append(d1_text_path) ## d1_vbox = widgets.VBox(d1_vbox_childs) tab_children.append({'element': d1_vbox, 'title': 'iMet'}) ############################ # data 2 box d2_vbox_childs = [] ## ### d2_button_next = widgets.Button(description='next measurement') d2_button_prev = widgets.Button(description='prev measurement') self.d2_dropdown_fnames = widgets.Dropdown( options=[ 1 ], #[i.name for i in self.controller.data.dataset2.path2data_list], value=1, #self.controller.data.dataset2.path2active.name, # description='N', disabled=False, ) d2_button_next.on_click(self.on_d2_botton_next) d2_button_prev.on_click(self.on_d2_botton_prev) self.d2_dropdown_fnames.observe(self.on_change_d2_dropdown_fnames) d2_box_h_1 = widgets.HBox( [d2_button_prev, d2_button_next, self.d2_dropdown_fnames]) ### d2_vbox_childs.append(d2_box_h_1) ## ### # text field showing the path d2_text_path = widgets.Text(placeholder='path name', disabled=False) self.d2_text_path = d2_text_path d2_vbox_childs.append(d2_text_path) ## d2_vbox = widgets.VBox(d2_vbox_childs) tab_children.append({'element': d2_vbox, 'title': 'POPS'}) # others box # Tab tab = widgets.Tab([child['element'] for child in tab_children]) for e, child in enumerate(tab_children): tab.set_title(e, child['title']) # accordeon self.accordeon_assigned = widgets.Valid( value=False, description='bound?', ) self.dropdown_popssn = widgets.Dropdown( options=['00', '14', '18'], # value='2', description='popssn', disabled=False, ) self.inttext_deltat = widgets.IntText(value=0, description='deltat', disabled=False) self.inttext_deltat.observe(self.on_inttext_deltat) self.dropdown_gps_bar_bad = widgets.Dropdown( options=[ 'gps', 'baro', 'bad', 'bad_but_usable_gps', 'bad_but_usable_baro' ], value='gps', description='which alt to use:', disabled=False, ) self.button_bind_measurements = widgets.ToggleButton( description='bind/unbind measurements') # button_bind_measurements.on_click(self.deprecated_on_button_bind_measurements) self.button_bind_measurements.observe(self.on_button_bind_measurements) accordon_box = widgets.VBox([ self.accordeon_assigned, self.dropdown_popssn, self.inttext_deltat, self.dropdown_gps_bar_bad, self.button_bind_measurements ]) accordion_children = [accordon_box] accordion = widgets.Accordion(children=accordion_children) accordion.set_title(0, 'do_stuff') # messages self.messages = widgets.Textarea('\n'.join(self.controller._message), layout={'width': '100%'}) # message_box = widgets.HBox([self.messages]) # OverVbox overVbox = widgets.VBox([tab, accordion, self.messages]) display(overVbox) #################### self.update_d1() self.update_d2() self.update_accordeon()
def plot_interactive_mapper_graph(pipeline, data, layout='kamada_kawai', layout_dim=2, color_variable=None, node_color_statistic=None, color_by_columns_dropdown=False, plotly_kwargs=None): """Plotting function for interactive Mapper graphs. Parameters ---------- pipeline : :class:`~gtda.mapper.pipeline.MapperPipeline` object Mapper pipeline to act on to data. data : array-like of shape (n_samples, n_features) Data used to generate the Mapper graph. Can be a pandas dataframe. layout : None, str or callable, optional, default: ``'kamada-kawai'`` Layout algorithm for the graph. Can be any accepted value for the ``layout`` parameter in the :meth:`layout` method of :class:`igraph.Graph`. [1]_ layout_dim : int, default: ``2`` The number of dimensions for the layout. Can be 2 or 3. color_variable : object or None, optional, default: ``None`` Specifies which quantity is to be used for node coloring. 1. If a numpy ndarray or pandas dataframe, `color_variable` must have the same length as `data` and is interpreted as a quantity of interest according to which node of the Mapper graph is to be colored (see `node_color_statistic`). 2. If ``None`` then equivalent to passing `data`. 3. If an object implementing :meth:`transform` or :meth:`fit_transform`, e.g. a scikit-learn estimator or pipeline, it is applied to `data` to generate the quantity of interest. 4. If an index or string, or list of indices / strings, equivalent to selecting a column or subset of columns from `data`. node_color_statistic :None, callable, or ndarray of shape (n_nodes,) or \ (n_nodes, 1), optional, default: ``None`` Specifies how to determine the colors of each node. If a numpy array, it must have the same length as the number of nodes in the Mapper graph, and its values are used directly for node coloring, ignoring `color_variable`. Otherwise, it can be a callable object which is used to obtain a summary statistic, within each Mapper node, of the quantity specified by `color_variable`. The default value ``None`` is equivalent to passing ``numpy.mean``. color_by_columns_dropdown : bool, optional, default: ``False`` If ``True``, a dropdown widget is generated which allows the user to color Mapper nodes according to any column in `data`. plotly_kwargs : dict, optional, default: ``None`` Keyword arguments to configure the Plotly Figure. Returns ------- box : :class:`ipywidgets.VBox` object A box containing the following widgets: parameters of the clustering algorithm, parameters for the covering scheme, a Mapper graph arising from those parameters, a validation box, and logs. References ---------- .. [1] `igraph.Graph.layout <https://igraph.org/python/doc/igraph.Graph-class.html#layout>`_ documentation. """ # clone pipeline to avoid side effects from in-place parameter changes pipe = clone(pipeline) if node_color_statistic is not None: _node_color_statistic = node_color_statistic else: _node_color_statistic = np.mean def get_widgets_per_param(param, value): if isinstance(value, float): return (param, widgets.FloatText( value=value, step=0.05, description=param.split('__')[1], continuous_update=False, disabled=False )) elif isinstance(value, int): return (param, widgets.IntText( value=value, step=1, description=param.split('__')[1], continuous_update=False, disabled=False )) elif isinstance(value, str): return (param, widgets.Text( value=value, description=param.split('__')[1], continuous_update=False, disabled=False )) else: return None def update_figure(figure, edge_trace, node_trace, layout_dim): figure.data[0].x = edge_trace.x figure.data[0].y = edge_trace.y figure.data[1].x = node_trace.x figure.data[1].y = node_trace.y if layout_dim == 3: figure.data[0].z = edge_trace.z figure.data[1].z = node_trace.z figure.data[1].marker.size = node_trace.marker.size figure.data[1].marker.color = node_trace.marker.color figure.data[1].marker.cmin = node_trace.marker.cmin figure.data[1].marker.cmax = node_trace.marker.cmax figure.data[1].marker.sizeref = node_trace.marker.sizeref figure.data[1].hoverlabel = node_trace.hoverlabel figure.data[1].hovertext = node_trace.hovertext def on_parameter_change(change): handler.clear_logs() try: for param, value in cover_params.items(): if isinstance(value, (int, float, str)): pipe.set_params( **{param: cover_params_widgets[param].value}) for param, value in cluster_params.items(): if isinstance(value, (int, float, str)): pipe.set_params( **{param: cluster_params_widgets[param].value}) logger.info("Updating figure ...") with fig.batch_update(): (node_trace, edge_trace, node_elements, node_colors, plot_options) = _calculate_graph_data( pipe, data, layout, layout_dim, color_variable, _node_color_statistic, plotly_kwargs ) update_figure(fig, edge_trace, node_trace, layout_dim) # Update color by column buttons is_data_dataframe = hasattr(data, 'columns') if color_by_columns_dropdown: column_color_buttons = _get_column_color_buttons( data, is_data_dataframe, node_elements, node_colors, plot_options['node_trace_marker_colorscale']) else: column_color_buttons = None button_height = 1.1 fig.update_layout( updatemenus=[ go.layout.Updatemenu( buttons=column_color_buttons, direction="down", pad={"r": 10, "t": 10}, showactive=True, x=0.11, xanchor='left', y=button_height, yanchor="top" ), ]) valid.value = True except Exception: exception_data = traceback.format_exc().splitlines() logger.exception(exception_data[-1]) valid.value = False def observe_widgets(params, widgets): for param, value in params.items(): if isinstance(value, (int, float, str)): widgets[param].observe(on_parameter_change, names='value') # define output widget to capture logs out = widgets.Output() @out.capture() def click_box(change): if logs_box.value: out.clear_output() handler.show_logs() else: out.clear_output() # initialise logging logger = logging.getLogger(__name__) handler = OutputWidgetHandler() handler.setFormatter(logging.Formatter( '%(asctime)s - [%(levelname)s] %(message)s')) logger.addHandler(handler) logger.setLevel(logging.INFO) # initialise cover and cluster dictionaries of parameters and widgets cover_params = dict(filter(lambda x: x[0].startswith('cover'), pipe.get_mapper_params().items())) cover_params_widgets = dict( filter(None, map(lambda x: get_widgets_per_param(*x), cover_params.items()))) cluster_params = dict(filter(lambda x: x[0].startswith('clusterer'), pipe.get_mapper_params().items())) cluster_params_widgets = dict( filter(None, map(lambda x: get_widgets_per_param(*x), cluster_params.items()))) # initialise widgets for validating input parameters of pipeline valid = widgets.Valid( value=True, description='Valid parameters', style={'description_width': '100px'}, ) # initialise widget for showing the logs logs_box = widgets.Checkbox( description='Show logs: ', value=False, indent=False ) # initialise figure with initial pipeline and config if plotly_kwargs is None: plotly_kwargs = dict() fig = plot_static_mapper_graph( pipe, data, layout, layout_dim, color_variable, _node_color_statistic, color_by_columns_dropdown, plotly_kwargs, clone_pipeline=False) observe_widgets(cover_params, cover_params_widgets) observe_widgets(cluster_params, cluster_params_widgets) logs_box.observe(click_box, names='value') # define containers for input widgets container_cover = widgets.HBox( children=list(cover_params_widgets.values())) container_cluster_layout = Layout(display='flex', flex_flow='row wrap') container_cluster = widgets.HBox( children=list(cluster_params_widgets.values()), layout=container_cluster_layout) box = widgets.VBox( [container_cover, container_cluster, fig, valid, logs_box, out]) return box
def _create(self): default_layout = widgets.Layout(width='auto', height='auto') self.wg['organization'] = widgets.Text(value='', description='Organization', layout=default_layout) self.wg['filter'] = widgets.Text( description='Filter', placeholder='Repository names must contain', layout=default_layout) self.wg['filename'] = widgets.Text(value='exercice.py', description='Filename', layout=default_layout) self.wg['request_url'] = widgets.Text( description='Request URL', value= f'https://raw.githubusercontent.com/{self.wg["organization"].value}/%RepositoryName%/master/{self.wg["filename"].value}', layout=default_layout, disabled=True) self.wg['get_files'] = widgets.Button(description='Fetch submissions') self.wg['get_files_status'] = widgets.Valid(value=True, description='Ready', layout=default_layout) self.wg['previous_button'] = widgets.Button(description='Previous') self.wg['next_button'] = widgets.Button(description='Next') self.wg['open_in_browser'] = widgets.Button( description='Open in GitHub', layout=default_layout) self.wg['open_file'] = widgets.Checkbox(description='File only', layout=default_layout) self.wg['repository_select'] = widgets.Dropdown( description='Select', layout=widgets.Layout(width='600px')) self.wg['max_preview_lines'] = widgets.IntText( value=100, disabled=False, layout=widgets.Layout(width='50px')) self.wg['preview_lines_range'] = widgets.IntRangeSlider( value=[0, 20], min=0, max=self.wg['max_preview_lines'].value, step=1, description='Lines range:', continuous_update=True, orientation='horizontal', readout=True, readout_format='d', layout=widgets.Layout(width='500px')) self.wg['repository_grading'] = widgets.HTML( layout=widgets.Layout(width='auto', height='auto', border='solid 1px', padding='2px 10px 2px 10px')) html_layout = widgets.Layout(width='auto', height='auto', padding='20px 100px 0px 20px') self.wg['file_preview_stats'] = widgets.HTML(layout=html_layout) self.wg['file_preview'] = widgets.HTML(layout=html_layout) self.wg['file_view_stats'] = widgets.HTML(layout=html_layout) self.wg['file_view'] = widgets.HTML(layout=html_layout) file_preview_box = widgets.HBox( (self.wg['file_preview'], self.wg['file_preview_stats'])) file_view_box = widgets.HBox( (self.wg['file_view'], self.wg['file_view_stats'])) lines_range_box = widgets.HBox( (self.wg['preview_lines_range'], self.wg['max_preview_lines'])) self.wg['accordion'] = widgets.Accordion(children=[ widgets.VBox((lines_range_box, file_preview_box)), file_view_box ]) self.wg['accordion'].set_title(0, 'Preview') self.wg['accordion'].set_title(1, 'File')
def plot(self, color_data=None, color_features=None, node_color_statistic=None, layout="kamada_kawai", layout_dim=2, n_sig_figs=3, node_scale=12, plotly_params=None): """ Produce the interactive Mapper widget. Parameters ---------- color_data : array-like of length n_samples, or None, optional, \ default: ``None`` Data to be used to construct node colors in the Mapper graph (according to `color_features` and `node_color_statistic`). Must have the same length as `data`. ``None`` is the same as passing ``numpy.arange(len(data))``. color_features : object or None, optional, default: ``None`` Specifies one or more feature of interest from `color_data` to be used, together with `node_color_statistic`, to determine node colors. 1. ``None`` is equivalent to passing `color_data`. 2. If an object implementing :meth:`transform` or :meth:`fit_transform`, or a callable, it is applied to `color_data` to generate the features of interest. 3. If an index or string, or list of indices/strings, it is equivalent to selecting a column or subset of columns from `color_data`. node_color_statistic : None or callable, optional, default: ``None`` If a callable, node colors will be computed as summary statistics from the feature array ``y`` determined by `color_data` and `color_features`. Let ``y`` have ``n`` columns (note: 1d feature arrays are converted to column vectors). Then, for a node representing a list ``I`` of row indices, there will be ``n`` colors, each computed as ``node_color_statistic(y[I, i])`` for ``i`` between ``0`` and ``n``. layout : None, str or callable, optional, default: ``"kamada-kawai"`` Layout algorithm for the graph. Can be any accepted value for the ``layout`` parameter in the :meth:`layout` method of :class:`igraph.Graph` [1]_. layout_dim : int, default: ``2`` The number of dimensions for the layout. Can be 2 or 3. n_sig_figs : int or None, optional, default: ``3`` If not ``None``, number of significant figures to which to round node summary statistics. If ``None``, no rounding is performed. node_scale : int or float, optional, default: ``12`` Sets the scale factor used to determine the rendered size of the nodes. Increase for larger nodes. Implements a formula in the `Plotly documentation \ <plotly.com/python/bubble-charts/#scaling-the-size-of-bubble-charts>`_. plotly_params : dict or None, optional, default: ``None`` Custom parameters to configure the plotly figure. Allowed keys are ``"node_trace"``, ``"edge_trace"`` and ``"layout"``, and the corresponding values should be dictionaries containing keyword arguments as would be fed to the :meth:`update_traces` and :meth:`update_layout` methods of :class:`plotly.graph_objects.Figure`. Returns ------- box : :class:`ipywidgets.VBox` object A box containing the following widgets: parameters of the clustering algorithm, parameters for the covering scheme, a Mapper graph arising from those parameters, a validation box, and logs. """ # Clone pipeline to avoid side effects from in-place parameter changes if self.clone_pipeline: self._pipeline = clone(self.pipeline) else: self._pipeline = self.pipeline def get_widgets_per_param(params): for key, value in params.items(): style = {'description_width': 'initial'} description = key.split("__")[1] if "__" in key else key if isinstance(value, float): yield (key, widgets.FloatText(value=value, step=0.05, description=description, continuous_update=False, disabled=False, layout=Layout(width="90%"), style=style)) elif isinstance(value, bool): yield (key, widgets.ToggleButton(value=value, description=description, disabled=False, layout=Layout(width="90%"), style=style)) elif isinstance(value, int): yield (key, widgets.IntText(value=value, step=1, description=description, continuous_update=False, disabled=False, layout=Layout(width="90%"), style=style)) elif isinstance(value, str): yield (key, widgets.Text(value=value, description=description, continuous_update=False, disabled=False, layout=Layout(width="90%"), style=style)) def on_parameter_change(change): handler.clear_logs() try: for param, value in cover_params.items(): if isinstance(value, (int, float, str)): self._pipeline.set_params( **{param: cover_params_widgets[param].value}) for param, value in cluster_params.items(): if isinstance(value, (int, float, str)): self._pipeline.set_params( **{param: cluster_params_widgets[param].value}) for param, value in nerve_params.items(): if isinstance(value, (int, bool)): self._pipeline.set_params( **{param: nerve_params_widgets[param].value}) logger.info("Updating figure...") with self._figure.batch_update(): self._graph = self._pipeline.fit_transform(self.data) (edge_trace, node_trace, self._node_colors_color_features) = \ _calculate_graph_data( self._graph, self._color_data_transformed, node_color_statistic, layout, layout_dim, n_sig_figs, node_scale ) if colorscale_for_hoverlabel is not None: min_col, max_col = \ np.min(self._node_colors_color_features[:, 0]), \ np.max(self._node_colors_color_features[:, 0]) hoverlabel_bgcolor = _get_colors_for_vals( self._node_colors_color_features[:, 0], min_col, max_col, colorscale_for_hoverlabel) self._figure.update_traces( hoverlabel_bgcolor=hoverlabel_bgcolor, selector={"name": "node_trace"}) self._figure.update_traces( x=node_trace.x, y=node_trace.y, marker_color=node_trace.marker.color, marker_size=node_trace.marker.size, marker_sizeref=node_trace.marker.sizeref, hovertext=node_trace.hovertext, **({ "z": node_trace.z } if layout_dim == 3 else dict()), selector={"name": "node_trace"}) self._figure.update_traces( x=edge_trace.x, y=edge_trace.y, **({ "z": edge_trace.z } if layout_dim == 3 else dict()), selector={"name": "edge_trace"}) # Update color by column buttons if relevant if self._node_colors_color_features.shape[1] > 1: hovertext_color_features = node_trace.hovertext column_color_buttons = _get_column_color_buttons( self._node_colors_color_features, hovertext_color_features, colorscale_for_hoverlabel, n_sig_figs, column_names_dropdown) button_height = 1.1 self._figure.update_layout(updatemenus=[ go.layout.Updatemenu(buttons=column_color_buttons, direction="down", pad={ "r": 10, "t": 10 }, showactive=True, x=0.11, xanchor="left", y=button_height, yanchor="top") ]) valid.value = True except Exception: exception_data = traceback.format_exc().splitlines() logger.exception(exception_data[-1]) valid.value = False def observe_widgets(params, widgets): for param, value in params.items(): if isinstance(value, (int, float, str)): widgets[param].observe(on_parameter_change, names="value") # Define output widget to capture logs out = widgets.Output() @out.capture() def click_box(change): if logs_box.value: out.clear_output() handler.show_logs() else: out.clear_output() # Initialise logging logger = logging.getLogger(__name__) handler = OutputWidgetHandler() handler.setFormatter( logging.Formatter("%(asctime)s - [%(levelname)s] %(message)s")) logger.addHandler(handler) logger.setLevel(logging.INFO) # Initialise cover, cluster and nerve dictionaries of parameters and # widgets mapper_params_items = self._pipeline.get_mapper_params().items() cover_params = { key: value for key, value in mapper_params_items if key.startswith("cover__") } cover_params_widgets = dict(get_widgets_per_param(cover_params)) cluster_params = { key: value for key, value in mapper_params_items if key.startswith("clusterer__") } cluster_params_widgets = dict(get_widgets_per_param(cluster_params)) nerve_params = { key: value for key, value in mapper_params_items if key in ["min_intersection", "contract_nodes"] } nerve_params_widgets = dict(get_widgets_per_param(nerve_params)) # Initialise widgets for validating input parameters of pipeline valid = widgets.Valid( value=True, description="Valid parameters", style={"description_width": "100px"}, ) # Initialise widget for showing the logs logs_box = widgets.Checkbox(description="Show logs: ", value=False, indent=False) # Initialise figure with initial pipeline and config self._graph = self._pipeline.fit_transform(self.data) (self._color_data_transformed, column_names_dropdown, node_color_statistic) = \ _validate_color_kwargs(self._graph, self.data, color_data, color_features, node_color_statistic, interactive=True) edge_trace, node_trace, self._node_colors_color_features = \ _calculate_graph_data( self._graph, self._color_data_transformed, node_color_statistic, layout, layout_dim, n_sig_figs, node_scale ) self._figure = _produce_static_figure(edge_trace, node_trace, self._node_colors_color_features, column_names_dropdown, layout_dim, n_sig_figs, plotly_params) colorscale_for_hoverlabel = None if layout_dim == 3: # In plot_static_mapper_graph, hoverlabel bgcolors are set to white # if something goes wrong in computing them according to the # colorscale. is_bgcolor_not_white = \ self._figure.data[1].hoverlabel.bgcolor != "white" user_hoverlabel_bgcolor = False if plotly_params: if "node_trace" in plotly_params: if "hoverlabel_bgcolor" in plotly_params["node_trace"]: user_hoverlabel_bgcolor = True if is_bgcolor_not_white and not user_hoverlabel_bgcolor: colorscale_for_hoverlabel = \ self._figure.data[1].marker.colorscale observe_widgets(cover_params, cover_params_widgets) observe_widgets(cluster_params, cluster_params_widgets) observe_widgets(nerve_params, nerve_params_widgets) logs_box.observe(click_box, names="value") # Define containers for input widgets cover_title = HTML(value="<b>Cover parameters</b>") container_cover = widgets.VBox(children=[cover_title] + list(cover_params_widgets.values())) container_cover.layout.align_items = 'center' cluster_title = HTML(value="<b>Clusterer parameters</b>") container_cluster = widgets.VBox( children=[cluster_title] + list(cluster_params_widgets.values()), ) container_cluster.layout.align_items = 'center' nerve_title = HTML(value="<b>Nerve parameters</b>") container_nerve = widgets.VBox(children=[nerve_title] + list(nerve_params_widgets.values()), ) container_nerve.layout.align_items = 'center' container_parameters = widgets.HBox( children=[container_cover, container_cluster, container_nerve]) box = widgets.VBox( [container_parameters, self._figure, valid, logs_box, out]) return box
def create_interactive_network(pipeline, data, node_pos=None, node_color=None, columns_to_color=None, plotly_kwargs=None, summary_stat=np.mean): """ Parameters ---------- pipeline : MapperPipeline The nerve of the refined pullback cover. Nodes correspond to cluster sets, and an edge exists between two nodes if they share at least one point in common. data : ndarray, shape (n_samples, n_features) The point cloud data used to generate the nerve. node_pos : igraph.Graph.layout or ndarray, shape (n_nodes, n_dims) Encodes the layout of the graph according to a layout algorithm or pre-defined array of coordinates in an n-dimensional space. node_color : ndarray, shape (n_nodes,) The numerical values to color each node of the graph by. columns_to_color : dict, optional, default: ``None`` Key-value pairs (column_name, column_index) to specify which columns of :attr:`data` to color the graph by. Generates a dropdown widget to select the columns to color by. plotly_kwargs : dict, optional, default: ``None`` Keyword arguments to configure the Plotly Figure. summary_stat : callable, default ``np.mean`` Summary statistic to apply to the elements in each node of the topological graph. """ # TODO could abstract away common patterns in get_cover_params_widgets and # get_cluster_params_widgets # TODO allow dimension to be passed as either 2 or 3 as an arg or kwarg # clone input pipeline to catch scenarios where user selects invalid # configuration of parameters and re-executes cell in Jupyter notebook pipe = clone(pipeline) def get_cover_params_widgets(param, value): if isinstance(value, float): return (param, widgets.FloatSlider( value=value, step=0.05, min=0.05, max=1.0, description=param.split('__')[1], disabled=False )) elif isinstance(value, int): return (param, widgets.IntSlider( value=value, min=1, max=100, step=1, description=param.split('__')[1], disabled=False )) else: return None def get_cluster_params_widgets(param, value): if isinstance(value, float): return (param, widgets.FloatText( value=value, step=0.1, description=param.split('__')[1], disabled=False )) elif isinstance(value, int): return (param, widgets.IntText( value=value, step=1, description=param.split('__')[1], disabled=False )) elif isinstance(value, str): return (param, widgets.Text( value=value, description=param.split('__')[1], disabled=False )) else: return None def update_figure(old_figure, new_figure): # TODO could this be abstracted to node and edge traces and metadata # information without need for creating a full new figure object old_figure.data[0].x = new_figure.data[0].x old_figure.data[0].y = new_figure.data[0].y old_figure.data[1].x = new_figure.data[1].x old_figure.data[1].y = new_figure.data[1].y old_figure.data[1].marker.size = new_figure.data[1].marker.size old_figure.data[1].marker.color = new_figure.data[1].marker.color old_figure.data[1].marker.sizeref = new_figure.data[1].marker.sizeref def get_figure(pipe, data, node_pos, node_color, columns_to_color, summary_stat): graph = pipe.fit_transform(data) node_elements = graph['node_metadata']['node_elements'] if node_pos is None: node_pos = graph.layout('kamada_kawai') if node_color is None: node_color = get_node_summary(node_elements, data, summary_stat=summary_stat) return create_network_2d(graph, data, node_pos, node_color, columns_to_color, plotly_kwargs) def response_numeric(change): # TODO: remove hardcoding of keys and mimic what is done with clusterer handler.clear_logs() try: pipe.set_mapper_params( cover__n_intervals=cover_params_widgets['cover__n_intervals'] .value) pipe.set_mapper_params( cover__overlap_frac=cover_params_widgets['cover__overlap_frac'] .value) for param, value in cluster_params.items(): if isinstance(value, (int, float)): pipe.set_mapper_params( **{param: cluster_params_widgets[param].value} ) # TODO check this alternative: # # num_params = {param: value for param, value in # cluster_params.items() # if isinstance(value, (int, float))} # # pipe.set_mapper_params( # **{param: cluster_params_widgets[param].value for param in # num_params} # ) new_fig = get_figure(pipe, data, node_pos, node_color, columns_to_color, summary_stat) logger.info("Updating figure ...") with fig.batch_update(): update_figure(fig, new_fig) valid.value = True except Exception: exception_data = traceback.format_exc().splitlines() logger.exception(exception_data[-1]) valid.value = False def response_text(text): handler.clear_logs() try: for param, value in cluster_params.items(): if isinstance(value, str): pipe.set_mapper_params( **{param: cluster_params_widgets[param].value} ) new_fig = get_figure(pipe, data, node_pos, node_color, columns_to_color, summary_stat) logger.info("Updating figure ...") with fig.batch_update(): update_figure(fig, new_fig) valid.value = True except Exception: exception_data = traceback.format_exc().splitlines() logger.exception(exception_data[-1]) valid.value = False def observe_numeric_widgets(params, widgets): for param, value in params.items(): if isinstance(value, (int, float)): widgets[param].observe(response_numeric, names='value') # define output widget to capture logs out = widgets.Output() @out.capture() def click_box(change): if logs_box.value: out.clear_output() handler.show_logs() else: out.clear_output() # initialise logging logger = logging.getLogger(__name__) handler = OutputWidgetHandler() handler.setFormatter(logging.Formatter( '%(asctime)s - [%(levelname)s] %(message)s')) logger.addHandler(handler) logger.setLevel(logging.INFO) # initialise cover and cluster dictionaries of parameters and widgets cover_params = dict(filter(lambda x: x[0].startswith('cover'), pipe.get_mapper_params().items())) cover_params_widgets = dict( filter(None, map(lambda x: get_cover_params_widgets(*x), cover_params.items()))) cluster_params = dict(filter(lambda x: x[0].startswith('clusterer'), pipe.get_mapper_params().items())) cluster_params_widgets = dict( filter(None, map(lambda x: get_cluster_params_widgets(*x), cluster_params.items()))) # create button for text inputs submit_button = widgets.Button(description="Submit") submit_button.on_click(response_text) # initialise widgets for validating input parameters of pipeline valid = widgets.Valid( value=True, description='Valid parameters', style={'description_width': '100px'}, ) # initialise widget for showing the logs logs_box = widgets.Checkbox( description='Show logs: ', value=False, indent=False ) # initialise figure with initial pipeline and config if plotly_kwargs is None: plotly_kwargs = dict() fig = get_figure(pipe, data, node_pos, node_color, columns_to_color, summary_stat) observe_numeric_widgets(cover_params, cover_params_widgets) observe_numeric_widgets(cluster_params, cluster_params_widgets) logs_box.observe(click_box, names='value') # define containers for input widgets container_cover = widgets.HBox(children=list( cover_params_widgets.values())) container_cluster_text = widgets.HBox( children=list(v for k, v in cluster_params_widgets.items() if isinstance(cluster_params[k], str)) + [submit_button]) container_cluster_layout = Layout(display='flex', flex_flow='row wrap') container_cluster_numeric = widgets.HBox( children=list(v for k, v in cluster_params_widgets.items() if isinstance(cluster_params[k], (int, float)) ), layout=container_cluster_layout ) box = widgets.VBox([container_cover, container_cluster_text, container_cluster_numeric, fig, valid, logs_box]) display(box, out)
def plot_interactive_mapper_graph(pipeline, data, layout="kamada_kawai", layout_dim=2, color_variable=None, node_color_statistic=None, clone_pipeline=True, color_by_columns_dropdown=False, n_sig_figs=3, node_scale=12, plotly_params=None): """Plotting function for interactive Mapper graphs. Provides functionality to interactively update parameters from the cover and clustering steps defined in `pipeline`. Nodes are colored according to `color_variable` and `node_color_statistic`. By default, the hovertext on each node displays a globally unique ID for the node, the number of data points associated with the node, and the summary statistic which determines its color. Parameters ---------- pipeline : :class:`~gtda.mapper.pipeline.MapperPipeline` object Mapper pipeline to act on to data. data : array-like of shape (n_samples, n_features) Data used to generate the Mapper graph. Can be a pandas dataframe. layout : None, str or callable, optional, default: ``"kamada-kawai"`` Layout algorithm for the graph. Can be any accepted value for the ``layout`` parameter in the :meth:`layout` method of :class:`igraph.Graph`. [1]_ layout_dim : int, default: ``2`` The number of dimensions for the layout. Can be 2 or 3. color_variable : object or None, optional, default: ``None`` Specifies a feature of interest to be used, together with `node_color_statistic`, to determine node colors. 1. If a numpy array or pandas dataframe, it must have the same length as `data`. 2. ``None`` is equivalent to passing `data`. 3. If an object implementing :meth:`transform` or :meth:`fit_transform`, it is applied to `data` to generate the feature of interest. 4. If an index or string, or list of indices/strings, it is equivalent to selecting a column or subset of columns from `data`. node_color_statistic : callable or None, optional, default: ``None`` If a callable, node colors will be computed as summary statistics from the feature array ``Y`` determined by `color_variable` – specifically, the color of a node representing the entries of `data` whose row indices are in ``I`` will be ``node_color_statistic(Y[I])``. ``None`` is equivalent to passing :func:`numpy.mean`. color_by_columns_dropdown : bool, optional, default: ``False`` If ``True``, a dropdown widget is generated which allows the user to color Mapper nodes according to any column in `data` (still using `node_color_statistic`) in addition to `color_variable`. clone_pipeline : bool, optional, default: ``True`` If ``True``, the input `pipeline` is cloned before computing the Mapper graph to prevent unexpected side effects from in-place parameter updates. n_sig_figs : int or None, optional, default: ``3`` If not ``None``, number of significant figures to which to round node summary statistics. If ``None``, no rounding is performed. node_scale : int or float, optional, default: ``12`` Sets the scale factor used to determine the rendered size of the nodes. Increase for larger nodes. Implements a formula in the `Plotly documentation \ <plotly.com/python/bubble-charts/#scaling-the-size-of-bubble-charts>`_. plotly_params : dict or None, optional, default: ``None`` Custom parameters to configure the plotly figure. Allowed keys are ``"node_trace"``, ``"edge_trace"`` and ``"layout"``, and the corresponding values should be dictionaries containing keyword arguments as would be fed to the :meth:`update_traces` and :meth:`update_layout` methods of :class:`plotly.graph_objects.Figure`. Returns ------- box : :class:`ipywidgets.VBox` object A box containing the following widgets: parameters of the clustering algorithm, parameters for the covering scheme, a Mapper graph arising from those parameters, a validation box, and logs. See also -------- :func:`~gtda.mapper.visualization.plot_static_mapper_graph`, :func:`~gtda.mapper.pipeline.make_mapper_pipeline` References ---------- .. [1] `igraph.Graph.layout <https://igraph.org/python/doc/igraph.Graph-class.html#layout>`_ documentation. """ # Clone pipeline to avoid side effects from in-place parameter changes _pipeline = clone(pipeline) if clone_pipeline else pipeline _node_color_statistic = node_color_statistic or np.mean def get_widgets_per_param(param, value): if isinstance(value, float): return (param, widgets.FloatText(value=value, step=0.05, description=param.split("__")[1], continuous_update=False, disabled=False)) elif isinstance(value, int): return (param, widgets.IntText(value=value, step=1, description=param.split("__")[1], continuous_update=False, disabled=False)) elif isinstance(value, str): return (param, widgets.Text(value=value, description=param.split("__")[1], continuous_update=False, disabled=False)) else: return None def on_parameter_change(change): handler.clear_logs() try: for param, value in cover_params.items(): if isinstance(value, (int, float, str)): _pipeline.set_params( **{param: cover_params_widgets[param].value}) for param, value in cluster_params.items(): if isinstance(value, (int, float, str)): _pipeline.set_params( **{param: cluster_params_widgets[param].value}) logger.info("Updating figure...") with fig.batch_update(): (edge_trace, node_trace, node_elements, node_colors_color_variable) = _calculate_graph_data( _pipeline, data, is_data_dataframe, layout, layout_dim, color_variable, _node_color_statistic, n_sig_figs, node_scale) if colorscale_for_hoverlabel is not None: node_colors_color_variable = np.asarray( node_colors_color_variable) min_col = np.min(node_colors_color_variable) max_col = np.max(node_colors_color_variable) hoverlabel_bgcolor = _get_colors_for_vals( node_colors_color_variable, min_col, max_col, colorscale_for_hoverlabel) fig.update_traces(hoverlabel_bgcolor=hoverlabel_bgcolor, selector={"name": "node_trace"}) fig.update_traces(x=node_trace.x, y=node_trace.y, marker_color=node_trace.marker.color, marker_size=node_trace.marker.size, marker_sizeref=node_trace.marker.sizeref, hovertext=node_trace.hovertext, **({ "z": node_trace.z } if layout_dim == 3 else dict()), selector={"name": "node_trace"}) fig.update_traces(x=edge_trace.x, y=edge_trace.y, **({ "z": edge_trace.z } if layout_dim == 3 else dict()), selector={"name": "edge_trace"}) # Update color by column buttons if color_by_columns_dropdown: hovertext_color_variable = node_trace.hovertext column_color_buttons = _get_column_color_buttons( data, is_data_dataframe, node_elements, node_colors_color_variable, _node_color_statistic, hovertext_color_variable, colorscale_for_hoverlabel, n_sig_figs) # Avoid recomputing hoverlabel bgcolor for top button if colorscale_for_hoverlabel is not None: column_color_buttons[0]["args"][0][ "hoverlabel.bgcolor"] = [None, hoverlabel_bgcolor] else: column_color_buttons = None button_height = 1.1 fig.update_layout(updatemenus=[ go.layout.Updatemenu(buttons=column_color_buttons, direction="down", pad={ "r": 10, "t": 10 }, showactive=True, x=0.11, xanchor="left", y=button_height, yanchor="top"), ]) valid.value = True except Exception: exception_data = traceback.format_exc().splitlines() logger.exception(exception_data[-1]) valid.value = False def observe_widgets(params, widgets): for param, value in params.items(): if isinstance(value, (int, float, str)): widgets[param].observe(on_parameter_change, names="value") # Define output widget to capture logs out = widgets.Output() @out.capture() def click_box(change): if logs_box.value: out.clear_output() handler.show_logs() else: out.clear_output() # Initialise logging logger = logging.getLogger(__name__) handler = OutputWidgetHandler() handler.setFormatter( logging.Formatter("%(asctime)s - [%(levelname)s] %(message)s")) logger.addHandler(handler) logger.setLevel(logging.INFO) # Initialise cover and cluster dictionaries of parameters and widgets cover_params = dict( filter(lambda x: x[0].startswith("cover"), _pipeline.get_mapper_params().items())) cover_params_widgets = dict( filter(None, map(lambda x: get_widgets_per_param(*x), cover_params.items()))) cluster_params = dict( filter(lambda x: x[0].startswith("clusterer"), _pipeline.get_mapper_params().items())) cluster_params_widgets = dict( filter( None, map(lambda x: get_widgets_per_param(*x), cluster_params.items()))) # Initialise widgets for validating input parameters of pipeline valid = widgets.Valid( value=True, description="Valid parameters", style={"description_width": "100px"}, ) # Initialise widget for showing the logs logs_box = widgets.Checkbox(description="Show logs: ", value=False, indent=False) # Initialise figure with initial pipeline and config fig = plot_static_mapper_graph( _pipeline, data, layout=layout, layout_dim=layout_dim, color_variable=color_variable, node_color_statistic=_node_color_statistic, color_by_columns_dropdown=color_by_columns_dropdown, clone_pipeline=False, n_sig_figs=n_sig_figs, node_scale=node_scale, plotly_params=plotly_params) # Store variables for later updates is_data_dataframe = hasattr(data, "columns") colorscale_for_hoverlabel = None if layout_dim == 3: # In plot_static_mapper_graph, hoverlabel bgcolors are set to white if # something goes wrong computing them according to the colorscale. is_bgcolor_not_white = fig.data[1].hoverlabel.bgcolor != "white" user_hoverlabel_bgcolor = False if plotly_params: if "node_trace" in plotly_params: if "hoverlabel_bgcolor" in plotly_params["node_trace"]: user_hoverlabel_bgcolor = True if is_bgcolor_not_white and not user_hoverlabel_bgcolor: colorscale_for_hoverlabel = fig.data[1].marker.colorscale observe_widgets(cover_params, cover_params_widgets) observe_widgets(cluster_params, cluster_params_widgets) logs_box.observe(click_box, names="value") # Define containers for input widgets container_cover = widgets.HBox( children=list(cover_params_widgets.values())) container_cluster_layout = Layout(display="flex", flex_flow="row wrap") container_cluster = widgets.HBox(children=list( cluster_params_widgets.values()), layout=container_cluster_layout) box = widgets.VBox( [container_cover, container_cluster, fig, valid, logs_box, out]) return box
def addValidMark(self, name, value): self.pushControl(name, widgets.Valid( value=value, description=name, ))