def _get_figure_widget(self): config = Config() trace = go.Parcats( dimensions=[{ "label": col, "values": self.data_source.data[col] } for col in self.selected_columns], line=dict( color=config.color_scale[1][1], colorscale=config.color_scale, cmin=0, cmax=1, shape="hspline", ), ) figure_widget = go.FigureWidget( data=[trace], layout=go.Layout( margin=dict(l=20, r=20, b=20, t=20, pad=5), autosize=True, showlegend=False, ), ) figure_widget.data[0].on_click(self.on_selection) return trace, figure_widget
def _get_figure_widget(self): config = Config() trace = go.Box( y=self.data_source.data[self.column_select.value], boxmean="sd", boxpoints=self.box_point_select.value, jitter=0.5, pointpos=-1.8, hoverinfo="skip", marker={"color": "rgb(%d,%d,%d)" % config.select_color}, selected={ "marker": { "color": "rgb(%d,%d,%d)" % config.select_color, "opacity": config.alpha, } }, unselected={ "marker": { "color": "rgb(%d,%d,%d)" % config.deselect_color, "opacity": config.alpha / 2, } }, showlegend=False, ) figure_widget = go.FigureWidget( data=[trace], layout=go.Layout( dragmode="select", margin=dict(l=15, r=15, b=15, t=15, pad=2), xaxis=dict(zeroline=False, showticklabels=False), ), ) return trace, figure_widget
def _get_scatter(self): config = Config() return go.Scatter( x=self.data_source.data[self.x_selection.value], y=self.data_source.data[self.y_selection.value], opacity=config.alpha, mode="markers", marker={"color": "rgb(%d,%d,%d)" % config.deselect_color}, selected={"marker": {"color": "rgb(%d,%d,%d)" % config.select_color}}, unselected={"marker": {"opacity": config.alpha / 2}}, showlegend=False, )
def populated_config(): config = Config() config.alpha = 0.75 config.select_color = (0, 0, 0) config.deselect_color = (0, 0, 0) config.color_scale = [ [0, "rgb(%d,%d,%d)" % config.deselect_color], [1, "rgb(%d,%d,%d)" % config.select_color], ]
def _get_par_coords(self) -> go.Parcoords: config = Config() self.trace: go.Parcoords = go.Parcoords( line=dict( color=config.color_scale[1][1], colorscale=config.color_scale, cmin=0, cmax=1, ), dimensions=[ self._get_dimension_dict(col) for col in self.selected_columns ], ) return self.trace
def build(self) -> widgets.Widget: """ Generates widgets from layout and returns the root widget for this layout. Rows are in a VBox while plots in the rows are in HBox widgets. :return: self.root_widget """ wcr = WidgetClassRegistry() rows = [self.selection_type_widget ] # first row is the selection type widget for r, row in enumerate(self.layout_spec): row_widgets = [] if isinstance(self.row_height, int): current_row_height = self.row_height else: # list current_row_height = self.row_height[r] for i, widget_name in enumerate(row): widget_cls: BaseWidget.__class__ = wcr.get_widget_class( widget_name) widget = widget_cls(self.data_source, r, i, 1.0 / len(row), current_row_height) row_widgets.append(widget.build()) h_box = widgets.HBox(row_widgets) rows.append(h_box) # workaround to include arbitrary css bg_color: typing.Tuple[int, int, int] = Config().select_color css = "<style>" css += ".jupyter-widgets { border-radius : 5px ; }" css += ".layout-{} * .jupyter-button.mod-active {{ background-color : rgb{}; color: rgb{}; }}".format( self._id, bg_color, text_color(bg_color) ) # add layout-xxx... class to buttons since this color is specific to this layout css += ".jupyter.button, .widget-toggle-button {border-radius : 5px; }" css += ".widget-dropdown > select {border-radius : 5px; }" css += ( ".jupyter-widgets::-webkit-scrollbar-track { border-radius: 4px; background-color: #F5F5F5; " "-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.15); }") css += ".jupyter-widgets::-webkit-scrollbar { width: 7px; background-color: #F5F5F5; }" css += ".jupyter-widgets::-webkit-scrollbar-thumb {{ border-radius: 4px; background-color: rgb{col}; }}".format( col=(170, 170, 170)) css += "</style>" rows.append(widgets.HTML(css)) self.root_widget = widgets.VBox( rows, layout=widgets.Layout(margin="20px 0px 10px 0px")) return self.root_widget
def _get_histograms(self): col = self.column_select.value config = Config() fig = go.Figure(layout=go.Layout( margin=dict(l=5, r=5, b=5, t=5, pad=2))) fig.add_trace( go.Histogram( x=self.data[col], opacity=max(config.alpha, 0.75), marker={"color": "rgb(%d,%d,%d)" % config.deselect_color}, selected={ "marker": { "color": "rgb(%d,%d,%d)" % config.deselect_color } }, unselected={"marker": { "opacity": 0.4 }}, hoverinfo="skip", histnorm="", bingroup=1, )) fig.add_trace( go.Histogram( x=self.brushed_data[col], opacity=1.0, # mode='markers', marker={"color": "rgb(%d,%d,%d)" % config.select_color}, selected={ "marker": { "color": "rgb(%d,%d,%d)" % config.select_color } }, unselected={"marker": { "opacity": 1.0 }}, hoverinfo="skip", histnorm="", bingroup=1, )) fig.update_layout(barmode="overlay", showlegend=False, dragmode="select") return fig
def apply_size_constraints(self, widget): """ Adds styling to a widget to control the height, width, margin, padding and border. Sets min and max_width to conform to the relative size of the widget, minus the margin. Sets min and max_width to conform to the max_height parameter. Adds a margin to the widget and also a padding with the same size. :param widget: An IPython widget to which the layout should be applied. :return: The same widget with updated layout. """ with widget.hold_trait_notifications(): margin = 5 num_widgets_in_row = int(round(1 / self.relative_size)) - 1 size_mod = ( 3 * num_widgets_in_row ) # because a border is added to each widget we have to subtract that (2px) + 1px for safety widget.layout.min_width = ( "calc(" + str(self.relative_size * 100) + "%" + " - %dpx)" % (margin + size_mod) ) widget.layout.max_width = ( "calc(" + str(self.relative_size * 100) + "%" + " - %dpx)" % (margin + size_mod) ) widget.layout.max_height = "%dpx" % self.max_height widget.layout.min_height = "%dpx" % self.max_height widget.layout.margin = "%dpx %dpx %dpx %dpx" % ( margin, margin, margin, margin, ) widget.layout.padding = "%dpx %dpx %dpx %dpx" % ( margin, margin, margin, margin, ) widget.layout.border = "2px solid rgb(%d,%d,%d)" % Config().select_color return widget
def __init__( self, data: typing.Union[DataFrame, DataSource], layout: typing.Union[str, typing.List[typing.List[str]]] = "default", categorical_columns: typing.Union[typing.List[str], None] = None, row_height: typing.Union[int, typing.List[int]] = 400, sample: typing.Union[float, int, None] = None, select_color: typing.Union[str, typing.Tuple[int, int, int]] = "#323EEC", deselect_color: typing.Union[str, typing.Tuple[int, int, int]] = "#8A8C93", alpha: float = 0.75, seed: typing.Union[int, None] = None, ): """ :param data: A pandas.DataFrame object or a :class:`DataSource`. :param layout: Layout specification name or explicit definition of widget names in rows. Those columns have to include all columns of the DataFrame which have type `object`, `str`, `bool` or `category`. This means it can only add columns which do not have the aforementioned types. Defaults to 'default'. :param categorical_columns: If given, specifies which columns are to be interpreted as categorical. Defaults to None. :param row_height: Height in pixels each row should have. If given an integer, each row has the height specified by that value, if given a list of integers, each value in the list specifies the height of the corresponding row. Defaults to 400. :param sample: Int or float value specifying if the DataFrame should be sub-sampled. When an int is given, the DataFrame will be limited to that number of rows given by the value. When a float is given, the DataFrame will include the fraction of rows given by the value. Defaults to None. :param select_color: RGB tuple or hex color specifying the color display selected data points. Values in the tuple have to be between 0 and 255 inclusive or a hex string that converts to such RGB values. Defaults to '#323EEC'. :param deselect_color: RGB tuple or hex color specifying the color display deselected data points. Values in the tuple have to be between 0 and 255 inclusive or a hex string that converts to such RGB values. Defaults to '#8A8C93'. :param alpha: Opacity of data points when applicable ranging from 0.0 to 1.0 inclusive. Defaults to 0.75. :param seed: Random seed used for sampling the data. Values can be any integer between 0 and 2**32 - 1 inclusive or None. Defaults to None. """ super().__init__() validate.validate_data(data) validate.validate_alpha(alpha) validate.validate_color(select_color) validate.validate_color(deselect_color) if isinstance(select_color, str): self.select_color: typing.Tuple[int, int, int] = hex_to_rgb(select_color) elif isinstance(select_color, tuple): self.select_color: typing.Tuple[int, int, int] = select_color if isinstance(deselect_color, str): self.deselect_color: typing.Tuple[int, int, int] = hex_to_rgb( deselect_color ) elif isinstance(deselect_color, tuple): self.deselect_color: typing.Tuple[int, int, int] = deselect_color self.alpha = alpha self.color_scale = [ [0, "rgb(%d,%d,%d)" % self.deselect_color], [1, "rgb(%d,%d,%d)" % self.select_color], ] config = Config() config["alpha"] = self.alpha config["select_color"] = self.select_color config["deselect_color"] = self.deselect_color config["color_scale"] = self.color_scale if isinstance(data, DataFrame): self.data_source = DataSource( df=data, categorical_columns=categorical_columns, sample=sample, seed=seed, ) elif isinstance(data, DataSource): self.data_source = data self.layout = AnalysisLayout( layout=layout, row_height=row_height, data_source=self.data_source ) if self.data_source.few_num_cols and len(self._check_numerical_plots()) != 0: warnings.warn( "The passed DataFrame only has %d NUMERICAL column, which is insufficient for some plots " "like Parallel Coordinates. These plots will not be displayed." % len(self.data_source.numerical_columns) ) if self.data_source.few_cat_cols and len(self._check_categorical_plots()) != 0: warnings.warn( "The passed DataFrame only has %d CATEGORICAL column, which is insufficient for some plots " "like Parallel Categories. These plots will not be displayed." % len(self.data_source.numerical_columns) )
def test_config_store_and_get(): config = Config() val = [1, 3] config["val"] = val assert config["val"] is val
def test_config_singleton(): config1 = Config() config2 = Config() assert config1 is config2
def test_config_attribute_error(): config = Config() val = "hello" with pytest.raises(ValueError): config.keys = val
def test_config_attributes(): config = Config() val = "hello" config.val = val assert config.val is val
def populated_config(): config = Config() config.alpha = 0.75 config.select_color = (0, 0, 0) config.deselect_color = (0, 0, 0)