Source code for pandas_visual_analysis.layout

import typing
import uuid

from ipywidgets import widgets

from pandas_visual_analysis.data_source import DataSource, SelectionType
from pandas_visual_analysis.utils.config import Config
from pandas_visual_analysis.widgets import BaseWidget
from pandas_visual_analysis.widgets.registry import WidgetClassRegistry
from pandas_visual_analysis.utils.util import text_color
import pandas_visual_analysis.utils.validation as validate


[docs]class AnalysisLayout: """ The AnalysisLayout class determines which widgets should be displayed in the analysis and defines their position and size. """ root_widget = None predefined_layouts = { "default": [["BrushSummary", "Scatter"], ["Histogram", "Scatter"]] } def __init__( self, layout: typing.Union[str, typing.List[typing.List[str]]], row_height: typing.Union[int, typing.List[int]], data_source: DataSource, *args, **kwargs ): """ :param layout: Layout specification name or explicit definition of widget names in rows. :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. :param data_source: The :class:`pandas_visual_analysis.data_source.DataSource` object passed to the widgets in that layout. """ super().__init__(*args, **kwargs) self._id = uuid.uuid4().hex if isinstance(layout, str): if layout not in set(self.predefined_layouts.keys()): raise ValueError( "The specified layout is invalid. Only these values are accepted: %s" % str(self.predefined_layouts.keys()) ) self.layout_spec = self.predefined_layouts[layout] elif ( isinstance(layout, list) and all(isinstance(x, list) for x in layout) and all(isinstance(x, str) for row in layout for x in row) ): self.layout_spec = layout valid_widgets = WidgetClassRegistry().widget_set if any([el not in valid_widgets for row in self.layout_spec for el in row]): raise ValueError( "Some widget names are not valid. " "Only the following widgets can be included in a layout specification: %s" % (str(valid_widgets)) ) else: raise TypeError( "The layout specification either has to be a string or a list of list of strings." ) validate.validate_row_height(row_height, self.layout_spec) self.data_source = data_source self.row_height = row_height self.selection_type_widget = widgets.ToggleButtons( options=[("Standard", "std"), ("Additive", "add"), ("Subtractive", "sub")], description="Selection Type:", disabled=False, button_style="", # 'success', 'info', 'warning', 'danger' or '' tooltips=[ "Replaces selection", "Adds selected points to selection", "Removes selected points from selection", ], style={"description_width": "initial"}, ) self.selection_type_widget.add_class("layout-" + self._id) self.selection_type_widget.observe(self._selection_type_changed, "value")
[docs] 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 _selection_type_changed(self, change): value = change["new"] if value == "std": self.data_source.selection_type = SelectionType.STANDARD elif value == "add": self.data_source.selection_type = SelectionType.ADDITIVE elif value == "sub": self.data_source.selection_type = SelectionType.SUBTRACTIVE