Ejemplo n.º 1
0
 def create_direction_toggle(self) -> Toggle:
     direction = self.plot.glyph.direction
     active = self.DIRECTION_TOGGLE_STATE[direction]
     toggle = Toggle(
         label=direction,
         button_type="primary",
         active=active,
         margin=(24, 0, 0, 5),
         width=100,
     )
     toggle.on_change("active", partial(self.handle_direction_change,
                                        toggle))
     return toggle
Ejemplo n.º 2
0
def _create_restart_button(
    doc,
    database,
    session_data,
    start_params,
    updating_options,
):
    """Create the button that restarts the convergence plots.

    Args:
        doc (bokeh.Document)
        database (sqlalchemy.MetaData): Bound metadata object.
        session_data (dict): dictionary with the last retrieved row id
        start_params (pd.DataFrame): See :ref:`params`
        updating_options (dict): Specification how to update the plotting data.
            It contains rollover, update_frequency, update_chunk, jump and stride.

    Returns:
        bokeh.layouts.Row

    """
    # (Re)start convergence plot button
    restart_button = Toggle(
        active=False,
        label="Start Updating",
        button_type="danger",
        width=200,
        height=30,
        name="restart_button",
    )
    restart_callback = partial(
        reset_and_start_convergence,
        session_data=session_data,
        doc=doc,
        database=database,
        button=restart_button,
        start_params=start_params,
        updating_options=updating_options,
    )
    restart_button.on_change("active", restart_callback)
    return restart_button
Ejemplo n.º 3
0
    gamma_slider.value = float(gamma_slider.value)


desc = Div(text=open(join(dirname(__file__), "error_plot.html")).read(),
           sizing_mode="stretch_width")

lay = layout([[desc], [eps, eps_slider], [small_delta, small_delta_slider],
              [big_delta, big_delta_slider], [gamma, gamma_slider], [geo_text],
              [err_percent_btn, enable_1exp_btn, enable_refine_btn],
              [p_1exp, p_error_stat], [redo_1exp_btn]])

controls = [eps, small_delta, big_delta, gamma]
for control in controls:
    control.on_change('value', lambda attr, old, new: update_1exp())

err_percent_btn.on_change('active', on_err_percent_btn_changed)

redo_1exp_btn.on_click(update_1exp)

enable_1exp_btn.on_change('active', lambda attr, old, new: update_1exp())
sliders = [eps_slider, small_delta_slider, big_delta_slider, gamma_slider]
for slider in sliders:
    slider.on_change('value', lambda attr, old, new: on_slider_change())

textinputs = [eps, small_delta, big_delta, gamma]
for ti in textinputs:
    ti.on_change('value', lambda attr, old, new: on_textinput_change())

# put the button and plot in a layout and add to the document
curdoc().add_root(lay)
curdoc().title = "Empirical error"
Ejemplo n.º 4
0
    style={
        'background-color': '#c8cfd6',
        'outline': 'black solid thin',
        'text-align': 'center'
    },
    height=70,
    align="center",
    sizing_mode="stretch_width")

# Make a toggle to cycle through the dates
button = Button(label='► Play', height=30)
button.on_click(animate)

# Make a toggle for changing the map to linear
tog_lin = Toggle(label='Lin Map', active=False, height=30)
tog_lin.on_change('active', change_var)

tog_res = Toggle(label='Hi Res', active=False, height=30)
tog_res.on_change('active', change_src)

rb_who_jhu = RadioButtonGroup(labels=['WHO', 'JHU'], active=0, height=30)
rb_who_jhu.on_change('active', change_src)

rb_cases_deaths = RadioButtonGroup(labels=['Cases', 'Deaths'],
                                   active=0,
                                   height=30)
rb_cases_deaths.on_change('active', change_var)

rb_abs_rel = RadioButtonGroup(labels=['Per Region', 'Per 100k'],
                              active=0,
                              height=30)
Ejemplo n.º 5
0
class PlotSettings:
    """This class contains widgets and logic that are common to all settings modals.
    """
    default_values = {
        "grid_lines": True,
        "plot_outline": False,
        "custom_plot_dimensions": False,
        "plot_height": 600,
        "plot_width": 600,
        "text_size": 11,
        "point_size": 8,
        "point_shape": "circle",
        "point_color": COLORS_PALETTE[0],  # Blue
        "multi_point_color": [COLORS_PALETTE[0]],  # Blue
        "text_thickness": False,
        "axes_thickness": False,
        "line_thickness": False,
        "line_color": COLORS_PALETTE[7],  # Gray
        "multi_line_color": [COLORS_PALETTE[0]],  # Blue
        "fill_color": COLORS_PALETTE[-2],  # Light Turquoise
        "show_legend": True,
        "legend_position": "top_right",
    }

    def __init__(
        self,
        title: str,
        plot: Figure,
        state: AppState,
        included_settings: Optional[List[str]] = None,
        default_values: Optional[Dict[str, Any]] = None,
    ) -> None:
        """Initializes a PlotSettings instance.

        Args:
            title (str): The title of the plot settings modal.
            plot (Figure): The plot to apply the settings to.
            state (AppState): Bokeh application state.
            included_settings (Optional[List[str]], optional): A list of setting IDs to include in the settings modal.
                The order in which the settings widgets are rendered matches the order they appear in this list.
                Defaults to None.
            default_values (Optional[Dict[str, Any]], optional): A dictionary that maps settings to
                their default values. For example, {"show_legend": False}. Defaults to None.
            configure_jinja_env (bool, optional): Whether to configure the jinja environment or not. Defaults to True.
        """

        self._title = title
        self._plot = plot
        self._state = state
        self._included_settings = included_settings or BASE_SETTINGS
        self._plot_tool_description = "Plot Settings"

        # Update settings' default values
        if default_values:
            self.default_values.update(default_values)

        # Initialize settings' widgets
        self._init_included_widgets()

        # Create a dummy widget to allow invoking a backend callback (python) from the frontend (javascript)
        self._backend_callback_invoker = Toggle()
        self._backend_callback_invoker.on_change("active",
                                                 self._update_widgets_values)

        # Add the settings tool to the plot
        self._settings_plot_tool = CustomAction(
            description=self._plot_tool_description,
            icon=os.path.join(BASE_DIR, "../assets/img/settings-icon.png"),
            callback=self._get_settings_button_click_js_callback(),
        )
        self._plot.add_tools(self._settings_plot_tool)

        # "Apply" button
        self._apply_dialog_btn = Button(
            label="Apply",
            width=75,
            css_classes=["apply-plot-settings"],
            name="apply_btn",
        )
        self._apply_dialog_btn.on_click(self.on_apply_dialog)

        # "Cancel" button
        self._cancel_dialog_btn = Button(
            label="Cancel",
            width=75,
            css_classes=["cancel-plot-settings"],
            name="cancel_btn",
        )
        self._cancel_dialog_btn.on_click(self._on_cancel_dialog)

        # "Reset" button
        # Note! This button is an icon button that uses google fonts.
        # the "label" property holds the name of the icon that is used.
        # More info can be found in "https://google.github.io/material-design-icons".
        self._reset_btn = Button(
            label="restart_alt",
            width=42,
            css_classes=["reset-plot-settings"],
            name="reset_btn",
        )
        self._reset_btn.on_click(self._on_reset_settings)

        # Layout the widgets
        self.layout = Column(
            *(self._get_setting_widget(setting_id)
              for setting_id in self._included_settings),
            self._apply_dialog_btn,
            self._cancel_dialog_btn,
            self._reset_btn,
        )

        # Set plot properties' initial values
        self._init_plot_settings_values()

        self._configure_jinja_environment()

    @property
    def _plot_settings_state(self) -> Dict[str, Any]:
        state_cookie = self._state["plot_settings_state"] or {}

        return {
            setting_id: state_cookie.get(setting_id,
                                         self.default_values.get(setting_id))
            for setting_id in self._included_settings
        }

    @_plot_settings_state.setter
    def _plot_settings_state(self, value: Dict[str, Any]):
        self._state["plot_settings_state"] = value

    @property
    def _grid_lines(self) -> bool:
        return all(grid.visible for grid in self._plot.grid)

    @_grid_lines.setter
    def _grid_lines(self, value: bool):
        self._plot.grid[0].visible = value
        self._plot.grid[1].visible = value

    @property
    def _plot_outline(self) -> bool:
        return bool(self._plot.outline_line_alpha)

    @_plot_outline.setter
    def _plot_outline(self, value: bool):
        if value:
            axes_width = AXES_BOLD_WIDTH if self._get_setting_widget_value(
                "axes_thickness") else AXES_NORMAL_WIDTH
            outline_width = axes_width if "axes_thickness" in self._included_settings else AXES_NORMAL_WIDTH
            self._plot.outline_line_dash = "solid"
            self._plot.outline_line_color = "black"
            self._plot.outline_line_alpha = 1
            self._plot.outline_line_width = outline_width
        else:
            self._plot.outline_line_alpha = 0

    @property
    def _custom_plot_dimensions(self) -> bool:
        return self._plot.sizing_mode is None

    @_custom_plot_dimensions.setter
    def _custom_plot_dimensions(self, value: bool):
        self._plot.sizing_mode = None if value else "scale_width"
        self._plot.aspect_ratio = "auto" if value else 1.5

    @property
    def _plot_height(self) -> Optional[int]:
        return PLOT_DIMENSION_STEP * round(
            self._plot.outer_height / PLOT_DIMENSION_STEP)

    @_plot_height.setter
    def _plot_height(self, value: Optional[int]):
        self._plot.height_policy = "auto"
        self._plot.height = value

    @property
    def _plot_width(self) -> Optional[int]:
        return PLOT_DIMENSION_STEP * round(
            self._plot.outer_width / PLOT_DIMENSION_STEP)

    @_plot_width.setter
    def _plot_width(self, value: Optional[int]):
        self._plot.width_policy = "auto"
        self._plot.width = value

    @property
    def _text_size(self) -> float:
        font_size_str = self._plot.xaxis.major_label_text_font_size

        # Extracts the numerical value of the font-size out of the font-size string (e.g "12px").
        return float(re.split(r"[px|pt]", font_size_str, 1)[0])

    @_text_size.setter
    def _text_size(self, value: float):
        self._plot.xaxis.major_label_text_font_size = f"{value}px"
        self._plot.yaxis.major_label_text_font_size = f"{value}px"
        self._plot.xaxis.axis_label_text_font_size = f"{value}px"
        self._plot.yaxis.axis_label_text_font_size = f"{value}px"

    @property
    def _point_size(self) -> float:
        return self._plot.renderers[0].glyph.size

    @_point_size.setter
    def _point_size(self, value: float):
        self._plot.renderers[0].glyph.size = value

    @property
    def _point_shape(self) -> str:
        glyph = self._plot.renderers[0].glyph
        glyph_class_name = type(glyph).__name__.lower()
        return getattr(glyph, "marker", glyph_class_name)

    @_point_shape.setter
    def _point_shape(self, value: str):
        # Fetch the glyph renderer's "create" method
        create_glyph = getattr(self._plot, value)

        # Get glyphs kwargs
        glyph_kwargs = self._get_glyph_kwargs()

        fill_alpha = 0 if value.endswith(("_dot", "_cross", "_y", "_x")) else 1

        for kwargs in glyph_kwargs:
            # The "Circle" glyph has no "marker" attribute,
            # hence it should be removed from kwargs.
            if value == "circle":
                kwargs.pop("marker", None)

            kwargs["fill_alpha"] = fill_alpha

            # Create the glyph renderer
            create_glyph(**kwargs)

        # remove previous renderers
        self._plot.renderers = self._plot.renderers[len(glyph_kwargs):]

        # Update legend
        if getattr(self._plot, "legend"):
            for renderer, legend_item in zip(self._plot.renderers,
                                             self._plot.legend.items):
                legend_item.renderers = [renderer]

    @property
    def _point_color(self) -> str:
        return self._plot.renderers[0].glyph.fill_color

    @_point_color.setter
    def _point_color(self, value: str):
        for renderer in self._plot.renderers:
            renderer.glyph.line_color = value
            renderer.glyph.fill_color = value

    @property
    def _text_thickness(self) -> bool:
        return self._plot.xaxis.major_label_text_font_style == "bold" and \
            self._plot.xaxis.axis_label_text_font_style == "bold" and \
            self._plot.yaxis.major_label_text_font_style == "bold" and \
            self._plot.yaxis.axis_label_text_font_style == "bold"

    @_text_thickness.setter
    def _text_thickness(self, value: bool):
        label_style = "bold" if value else "normal"
        self._plot.xaxis.major_label_text_font_style = label_style
        self._plot.yaxis.major_label_text_font_style = label_style
        self._plot.xaxis.axis_label_text_font_style = label_style
        self._plot.yaxis.axis_label_text_font_style = label_style

    @property
    def _axes_thickness(self) -> bool:
        return all(
            getattr(axis, attr) == AXES_BOLD_WIDTH for axis in self._plot.axis
            for attr in AXES_WIDTH_ATTRIBUTES)

    @_axes_thickness.setter
    def _axes_thickness(self, value: bool):
        axis_width = AXES_BOLD_WIDTH if value else AXES_NORMAL_WIDTH
        kwargs = {attr: axis_width for attr in AXES_WIDTH_ATTRIBUTES}
        for axis in self._plot.axis:
            axis.update(**kwargs)

    @property
    def _line_thickness(self) -> bool:
        renderer = self._plot.renderers[0]
        return renderer.glyph.line_width == LINE_BOLD_WIDTH and \
            renderer.hover_glyph.line_width == LINE_BOLD_WIDTH

    @_line_thickness.setter
    def _line_thickness(self, value: bool):
        line_width = LINE_BOLD_WIDTH if value else LINE_NORMAL_WIDTH
        renderer = self._plot.renderers[0]
        renderer.glyph.line_width = line_width
        renderer.hover_glyph.line_width = line_width

    @property
    def _line_color(self) -> str:
        return self._plot.renderers[0].glyph.line_color

    @_line_color.setter
    def _line_color(self, value: str):
        for renderer in self._plot.renderers:
            renderer.glyph.line_color = value

            if hasattr(renderer.hover_glyph, "line_color"):
                renderer.hover_glyph.line_color = self._find_darker_shade(
                    value)

    @property
    def _multi_line_color(self) -> List[str]:
        renderers_num = len(self._plot.renderers)

        if renderers_num == 1:
            renderer = self._plot.renderers[0]
            color_field_name = renderer.glyph.line_color
            return [*{*renderer.data_source.data[color_field_name]}]
        else:
            return [
                renderer.glyph.line_color for renderer in self._plot.renderers
            ]

    @_multi_line_color.setter
    def _multi_line_color(self, value: List[str]):
        renderers_num = len(self._plot.renderers)
        if renderers_num == 1:
            self._set_colors_by_field(value)

        else:
            colors = self._fill_missing_colors(value,
                                               len(self._plot.renderers))

            for color, renderer in zip(colors, self._plot.renderers):
                renderer.glyph.line_color = color

                if getattr(renderer, "hover_glyph"):
                    renderer.hover_glyph.line_color = self._find_darker_shade(
                        color)

    @property
    def _multi_point_color(self) -> List[str]:
        renderers_num = len(self._plot.renderers)

        if renderers_num == 1:
            renderer = self._plot.renderers[0]
            color_field_name = renderer.glyph.fill_color
            return [*{*renderer.data_source.data[color_field_name]}]
        else:
            return [
                renderer.glyph.fill_color for renderer in self._plot.renderers
            ]

    @_multi_point_color.setter
    def _multi_point_color(self, value: List[str]):
        renderers_num = len(self._plot.renderers)

        if renderers_num == 1:
            self._set_colors_by_field(value)

        else:
            colors = self._fill_missing_colors(value,
                                               len(self._plot.renderers))

            for color, renderer in zip(colors, self._plot.renderers):
                renderer.glyph.fill_color = color
                renderer.glyph.line_color = color

                if getattr(renderer, "hover_glyph"):
                    darker_shade = self._find_darker_shade(color)
                    renderer.hover_glyph.fill_color = darker_shade
                    renderer.hover_glyph.line_color = darker_shade

    @property
    def _fill_color(self) -> str:
        return self._plot.renderers[0].glyph.fill_color

    @_fill_color.setter
    def _fill_color(self, value: str):
        for renderer in self._plot.renderers:
            if hasattr(renderer.glyph, "fill_color"):
                renderer.glyph.fill_color = value

            if hasattr(renderer.hover_glyph, "fill_color"):
                renderer.hover_glyph.fill_color = self._find_darker_shade(
                    value)

    @property
    def _show_legend(self) -> bool:
        return self._plot.legend.visible

    @_show_legend.setter
    def _show_legend(self, value: bool):
        self._plot.legend.visible = value

    @property
    def _legend_position(self) -> str:
        return self._plot.legend.location

    @_legend_position.setter
    def _legend_position(self, value: str):
        self._plot.legend.location = value

    def _init_included_widgets(self):
        """Initializes Bokeh widgets based on included settings.
        """
        for setting_id in self._included_settings:
            widget_class, kwargs = SETTINGS[setting_id]

            if widget_class.__name__ == "CheckboxGroup":
                kwargs["css_classes"] = ["custom_checkbox"]

            setting_widget = widget_class(**kwargs)
            setting_widget.name = setting_id

            if setting_id == "custom_plot_dimensions":
                setting_widget.on_change("active",
                                         self._toggle_plot_dimensions)

            setattr(self, f"_{setting_id}_widget", setting_widget)

    def _get_settings_button_click_js_callback(self) -> CustomJS:
        """Returns a javascript callback to run when the settings tool is clicked.

        Returns:
            CustomJS: "on_click" Javascript callback.
        """

        args = {"backend_callback_invoker": self._backend_callback_invoker}
        code = f"""
        // Add "data" attributes to the "Settings" tool to allow toggling the modal.
        $(".bk-toolbar-button-custom-action[title='{self._plot_tool_description}']").attr("data-toggle", "modal")
            .attr("data-target", "#plot-settings-modal")

        // Add "data" attribute to both the "Apply" and "Cancel" buttons (in the modal) to allow closing the modal.
        $(".apply-plot-settings > .bk-btn-group > .bk-btn").attr("data-dismiss", "modal")
        $(".cancel-plot-settings > .bk-btn-group > .bk-btn").attr("data-dismiss", "modal")

        // Add a tooltip to the "Reset" button (in the modal).
        $(".reset-plot-settings > .bk-btn-group > .bk-btn").attr("data-toggle", "tooltip")
            .attr("title", "Reset to Default")

        // Add an icon for the "Reset" button.
        $(".reset-plot-settings > .bk-btn-group > .bk-btn:not([class*='material-icons'])").addClass("material-icons")
        $(".reset-plot-settings > .bk-btn-group > .bk-btn").removeClass("bk bk-btn-default")

        // Invoke backend callback
        backend_callback_invoker.active = !backend_callback_invoker.active
        """
        return CustomJS(args=args, code=code)

    def on_apply_dialog(self, new_plot: Optional[Figure] = None):
        """Modifies the plot based on the settings configurations.

        This method can be triggered either by clicking the "Apply" button in the settings modal
        or by another component that replaces the existing plot with a new one and needs to apply the settings on
        the newly created plot. For example, updating the "Parameter to Display" filter in the "Histogram" dashboard
        causes a creation of a new Figure instance. In such case, the histogram component can use this function
        in order to apply the plot settings on the newly created plot.

        Args:
            new_plot (Optional[Figure], optional): The newly created Figure instance. Defaults to None.
        """
        if isinstance(new_plot, Figure):
            self._plot = new_plot
            self._plot.add_tools(self._settings_plot_tool)

        state = {}

        for setting_id in self._included_settings:
            value = self._get_setting_widget_value(setting_id)

            # Update the plot based on the applied setting
            self._set_setting_property(setting_id, value)

            state[setting_id] = value

        # Save the state as a cookie
        self._plot_settings_state = state

    def _on_cancel_dialog(self):
        for setting_id in self._included_settings:
            setting_value = self._plot_settings_state[setting_id]
            self._set_setting_widget_value(setting_id, setting_value)

    def _on_reset_settings(self):
        for setting_id in self._included_settings:
            setting_value = self.default_values[setting_id]
            self._set_setting_widget_value(setting_id, setting_value)

    def _configure_jinja_environment(self):
        """Configures the Bokeh template's environment.

        These configurations:
            1. Allow the Bokeh template that is used to render the app to import/include the settings modal macros
                by adding their path to Jinja's search path.
            2. Add template variables that are necessary for running the settings modal macros.
        """

        PLOT_SETTINGS_MACROS_PATH = os.path.join(
            BASE_DIR, "../templates/plot_settings_modal")
        doc = curdoc()

        # Add the settings modal macros directory to the Jinja environment search path.
        # This allows importing/including the settings modal macros in the Bokeh app template.
        doc.template.environment.loader.searchpath.append(
            PLOT_SETTINGS_MACROS_PATH)

        # Add template variables. These variables are necessary for generating the settings modal HTML.
        doc.template_variables["plot_settings_title"] = self._title
        doc.template_variables["plot_settings_layout"] = [
            self._get_setting_widget(setting_id).name
            for setting_id in self._included_settings
        ]

    def _get_setting_widget(self, setting_id: str) -> Any:
        """Returns the corresponding Bokeh widget of a given setting.

        Args:
            setting_id (str): ID of a setting.

        Returns:
            Any: The setting's corresponding Bokeh widget.
        """
        return getattr(self, f"_{setting_id}_widget", None)

    def _get_setting_widget_value(self, setting_id: str) -> Any:
        """Returns the value of the corresponding Bokeh widget of a given setting.

        Args:
            setting_id (str): ID of a setting.

        Returns:
            Any: The value of the setting's corresponding Bokeh widget.
        """
        setting_widget = getattr(self, f"_{setting_id}_widget", None)

        if setting_widget is None:
            raise ValueError(
                f"The setting '{setting_id}' has no corresponding widget")

        if type(setting_widget).__name__ == "CheckboxGroup":
            return bool(setting_widget.active)
        else:
            return setting_widget.value

    def _set_setting_property(self, setting_id: str, value: Any):
        """Sets the value of the corresponding class property of a given setting.

        Args:
            setting_id (str): ID of the setting.
            value (Any): Value to set to the corresponding class property.
        """
        setattr(self, f"_{setting_id}", value)

    def _set_setting_widget_value(self, setting_id: str, value: Any):
        """Sets the value of the corresponding widget of a given setting.

        Args:
            setting_id (str): ID of the setting.
            value (Any): Value to set to the corresponding widget.
        """
        setting_widget = self._get_setting_widget(setting_id)

        if type(setting_widget).__name__ == "CheckboxGroup":
            setting_widget.active = [0] if value else []
        else:
            setting_widget.value = value

    def _get_setting_property(self, setting_id: str) -> Any:
        return getattr(self, f"_{setting_id}")

    def _get_glyph_kwargs(self) -> List[Dict[str, Any]]:
        """Returns the keyword arguments of each of the plot's renderers.

        Returns:
            List[Dict[str, Any]]: A list of renderers' keyword arguments.
        """
        if len(self._plot.renderers) == 0:
            return []

        return [{
            **renderer.glyph._property_values,
            "source": renderer.data_source,
            "visible": renderer.visible,
        } for renderer in self._plot.renderers]

    def _init_plot_settings_values(self):
        """Initializes the settings values.

        The initial values are set to be equal to the last saved state (loaded by the AppState).
        If there is no saved state, they are default to the "default_values" class property.
        """
        for setting_id, value in self._plot_settings_state.items():
            self._set_setting_property(setting_id, value)
            self._set_setting_widget_value(setting_id, value)

    @staticmethod
    def _find_darker_shade(color: str) -> str:
        """Finds a darker shade for a given hex color.

        Args:
            color (str): A valid hex color (e.g "#4F4F4F").

        Returns:
            str: A darker hex color.
        """
        MIN = 0
        MAX = 255
        DELTA = 30
        hex_color = color.lstrip("#")

        # For each of the RGB parts, find a darker shade by decreasing the value (by "DELTA") and
        # concatenate the results back together. Each result is being clipped to (MIN, MAX)
        # in order to keep it valid.
        return "#" + "".join("{:02x}".format(
            np.clip(int(hex_color[i:i + 2], 16) - DELTA, MIN, MAX))
                             for i in (0, 2, 4))

    def _set_colors_by_field(self, colors: List[str]):
        """Sets plot colors by modifying a data source field.

        This method assumes that the plot's data is grouped using Bokeh's
        "automatic grouping" feature (more info here: https://docs.bokeh.org/en/latest/docs/user_guide/annotations.html#automatic-grouping-browser-side)
        rather than having multiple renderers.

        Args:
            colors (List[str]): A list of colors that were chosen by the user in the settings modal.
        """  # noqa: E501

        renderer = self._plot.renderers[0]
        source = renderer.data_source
        data = source.data
        color_field_name = getattr(renderer, "fill_color",
                                   renderer.glyph.line_color)

        # The field that the data is grouped by
        grouped_by = self._plot.legend.items[0].label["field"]

        groups = {*data[grouped_by]}
        groups_colors = self._fill_missing_colors(colors, len(groups))

        # Set color for each group
        criteria = [
            np.array(data[grouped_by]) == group_name for group_name in groups
        ]
        data[color_field_name] = np.select(criteria, groups_colors)

    def _fill_missing_colors(self, chosen_colors: List[str],
                             groups_num: int) -> List[str]:
        """Fills missing colors.

        In case there are more groups to be colored than colors that were chosen by the user,
        the function will add the missing colors.

        Args:
            chosen_colors (List[str]): Colors that were chosen by the user.
            groups_num (int): Groups to be colored.

        Returns:
            List[str]: A list with the same number of colors as the number of groups.
        """
        # There are enough colors to color all the groups.
        if len(chosen_colors) >= groups_num:
            return chosen_colors[:groups_num]

        # Colors that were not chosen by the user
        not_chosen_colors = [
            color for color in COLORS_PALETTE if color not in chosen_colors
        ]

        # Creating an infinite pool of colors (repetitive) while giving precedence to colors
        # that were not chosen by the user.
        colors_pool = itertools.cycle(not_chosen_colors + chosen_colors)

        # Number of missing colors
        missing_colors_num = groups_num - len(chosen_colors)

        return chosen_colors + [
            next(colors_pool) for _ in range(missing_colors_num)
        ]

    def _update_widgets_values(self, attr, old, new):
        """Updates the widgets' values based on the current state of the plot.
        """
        for setting_id in self._included_settings:
            setting_value = self._get_setting_property(setting_id)
            self._set_setting_widget_value(setting_id, setting_value)

    def _toggle_plot_dimensions(self, attr, old, new):
        """Enables/disables the "plot height" and "plot width" widgets.
        """
        is_disabled = False if new else True
        self._get_setting_widget("plot_height").disabled = is_disabled
        self._get_setting_widget("plot_width").disabled = is_disabled
Ejemplo n.º 6
0
def monitoring_app(doc, database_name, session_data):
    """Create plots showing the development of the criterion and parameters.

    Options are loaded from the database. Supported options are:
        - rollover (int): How many iterations to keep before discarding.

    Args:
        doc (bokeh.Document): Argument required by bokeh.
        database_name (str): Short and unique name of the database.
        session_data (dict): Infos to be passed between and within apps.
            Keys of this app's entry are:
            - last_retrieved (int): last iteration currently in the ColumnDataSource.
            - database_path (str or pathlib.Path)
            - callbacks (dict): dictionary to be populated with callbacks.

    """
    database = load_database(session_data["database_path"])
    start_params = read_scalar_field(database, "start_params")
    dash_options = read_scalar_field(database, "dash_options")
    rollover = dash_options["rollover"]

    tables = ["criterion_history", "params_history"]
    criterion_history, params_history = _create_bokeh_data_sources(
        database=database, tables=tables
    )
    session_data["last_retrieved"] = 1

    # create initial bokeh elements without callbacks
    initial_convergence_plots = _create_initial_convergence_plots(
        criterion_history=criterion_history,
        params_history=params_history,
        start_params=start_params,
    )

    activation_button = Toggle(
        active=False,
        label="Start Updating from Database",
        button_type="danger",
        width=50,
        height=30,
        name="activation_button",
    )

    # add elements to bokeh Document
    bokeh_convergence_elements = [Row(activation_button)] + initial_convergence_plots
    convergence_tab = Panel(
        child=Column(*bokeh_convergence_elements), title="Convergence Tab"
    )
    tabs = Tabs(tabs=[convergence_tab])
    doc.add_root(tabs)

    # add callbacks
    activation_callback = partial(
        _activation_callback,
        button=activation_button,
        doc=doc,
        database=database,
        session_data=session_data,
        rollover=rollover,
        tables=tables,
    )
    activation_button.on_change("active", activation_callback)
Ejemplo n.º 7
0
        else:
            other_source.data = empty_dict

        if 1 in button_group.active:
            speed_source.data = gen_dict(speed)
        else:
            speed_source.data = empty_dict

        if 2 in button_group.active:
            drunk_source.data = gen_dict(drunk)
        else:
            drunk_source.data = empty_dict


slider.on_change('value', update_hour)
toggle.on_change('active', update_hour)
button_group.on_change('active', update_hour)

callback = CustomJS(args=dict(other=other_circles,
                              speed=speed_circles,
                              drunk=drunk_circles),
                    code="""
        other.glyph.radius = { value: cb_obj.get('value')*125 }
        other.data_source.trigger('change')

        speed.glyph.radius = { value: cb_obj.get('value')*125 }
        speed.data_source.trigger('change')

        drunk.glyph.radius = { value: cb_obj.get('value')*125 }
        drunk.data_source.trigger('change')
""")
Ejemplo n.º 8
0
def update_clustering(querier, bokeh_layout, bokeh_doc, data, clustering, cluster_indices, representatives):

    plot_width = int(800 / len(clustering))
    plot_height = int(plot_width / 2)

    plots = []
    cols = []

    for c, c_idxs, cluster_representatives in zip(clustering, cluster_indices, representatives):

        cur_data = data[c_idxs, :]

        x_range = 0, cur_data.shape[1]
        y_range = cur_data[1:].min(), cur_data[1:].max()

        cluster_plot = figure(plot_width=plot_width, plot_height=plot_height, x_range=x_range, y_range=y_range,
                              toolbar_location=None)
        cluster_plot.toolbar.logo = None

        # actually make the plot
        canvas = datashader.Canvas(x_range=x_range, y_range=y_range,
                                   plot_height=plot_height, plot_width=plot_width)

        # reformat the data into an appropriate DataFrame
        dfs = []
        split = pd.DataFrame({'x': [np.nan]})
        # for i in range(len(data)-1):
        for i in range(len(cur_data)):
            x = list(range(cur_data.shape[1]))
            y = cur_data[i]
            df3 = pd.DataFrame({'x': x, 'y': y})
            dfs.append(df3)
            dfs.append(split)
        df2 = pd.concat(dfs, ignore_index=True)

        agg = canvas.line(df2, 'x', 'y', datashader.count())
        img = datashader.transfer_functions.shade(agg, how='eq_hist')

        cluster_plot.image_rgba(image=[img.data], x=x_range[0], y=y_range[0], dw=x_range[1] - x_range[0],
                                dh=y_range[1] - y_range[0])

        for i, repr_idx in enumerate(cluster_representatives):
            cluster_plot.line('x', 'y', source=dict(x=range(data.shape[1]), y=data[repr_idx, :]), line_width=2, line_color=colors[i % len(colors)])

        plots.append(cluster_plot)

        button = Toggle(label="This cluster is pure.", active=c.is_pure)
        button.on_change("active", partial(cluster_is_pure, {"cluster" : c}))

        button2 = Toggle(label="This cluster is pure and complete.", active=c.is_finished)
        button2.on_change("active", partial(cluster_is_finished, {"cluster" : c}))

        cols.append(column(cluster_plot,button,button2))

    topdiv = Div(
        text="<font size=\"15\"> <b>COBRAS<sup>TS</sup></b> </font>  <br><font size=\"2\">  # queries answered: " + str(
            querier.n_queries) + "</font>", css_classes=['top_title_div'],
        width=500, height=100)
    bokeh_layout.children[0].children[0] = column(topdiv)

    bokeh_layout.children[3] = row(cols)

    finished_indicating = Button(label="Show me more queries!",button_type="danger")
    finished_indicating.on_click(partial(remove_cluster_indicators_callback, querier=querier, bokeh_layout=bokeh_layout, bokeh_doc=bokeh_doc))

    bokeh_layout.children[4] = row(finished_indicating)

    bokeh_layout.children[1].children[1].children[1] = Div(text="""Click 'Show me more queries!' below to continue querying""", width=400, height=100)
Ejemplo n.º 9
0
size_range_slider.on_change('value', alpha_size)


def toggle_small_handler(attr, old, new):
    print(toggle_small.active)
    my_slider_handler()


def toggle_hexes_handler(attr, old, new):
    print(toggle_hexes.active)
    my_slider_handler()


toggle_small = Toggle(label="Toggle Points", button_type="success")
toggle_small.on_change("active", toggle_small_handler)

toggle_hexes = Toggle(label="Toggle Hexes", button_type="success")
toggle_hexes.on_change("active", toggle_hexes_handler)

CID = list(set(df['customer_id'].astype('str')))
print('CID', len(CID))
select_cid = Select(title="Select Customer IDs:", value="foo", options=CID)

global pre

pre = Div(
    text=
    '<h4 style="border-top: 2px solid #778899;width: 1600px"><br><b style="color:slategray">High Utilization Areas</b><br>'
    + '<b style="color:slategray">Filtered Idle spots: </b>' + str(len(df)) +
    '</h4>',
Ejemplo n.º 10
0
        else:
            other_source.data = empty_dict

        if 1 in button_group.active:
            speed_source.data = gen_dict(speed)
        else:
            speed_source.data = empty_dict

        if 2 in button_group.active:
            drunk_source.data = gen_dict(drunk)
        else:
            drunk_source.data = empty_dict


slider.on_change('value', update_hour)
toggle.on_change('active', update_hour)
button_group.on_change('active', update_hour)

callback = CustomJS(args=dict(other=other_circles, speed=speed_circles, drunk=drunk_circles), code="""
        other.glyph.radius = { value: cb_obj.get('value')*125 }
        other.data_source.trigger('change')

        speed.glyph.radius = { value: cb_obj.get('value')*125 }
        speed.data_source.trigger('change')

        drunk.glyph.radius = { value: cb_obj.get('value')*125 }
        drunk.data_source.trigger('change')
""")

size_slider = Slider(title="Dot Size", start=1, end=100, orientation="horizontal",
                value=100, step=1, callback=callback, callback_policy="mouseup")
Ejemplo n.º 11
0
def _create_button_row(
    doc,
    database,
    session_data,
    start_params,
    updating_options,
):
    """Create a row with two buttons, one for (re)starting and one for scale switching.

    Args:
        doc (bokeh.Document)
        database (sqlalchemy.MetaData): Bound metadata object.
        session_data (dict): dictionary with the last retrieved rowid
        start_params (pd.DataFrame): See :ref:`params`
        updating_options (dict): Specification how to update the plotting data.
            It contains rollover, update_frequency, update_chunk, jump and stride.

    Returns:
        bokeh.layouts.Row

    """
    # (Re)start convergence plot button
    activation_button = Toggle(
        active=False,
        label="Start Updating",
        button_type="danger",
        width=200,
        height=30,
        name="activation_button",
    )
    partialed_activation_callback = partial(
        activation_callback,
        button=activation_button,
        doc=doc,
        database=database,
        session_data=session_data,
        tables=["criterion_history", "params_history"],
        start_params=start_params,
        updating_options=updating_options,
    )
    activation_button.on_change("active", partialed_activation_callback)

    # switch between linear and logscale button
    logscale_button = Toggle(
        active=False,
        label="Show criterion plot on a logarithmic scale",
        button_type="default",
        width=200,
        height=30,
        name="logscale_button",
    )
    partialed_logscale_callback = partial(
        logscale_callback,
        button=logscale_button,
        doc=doc,
    )
    logscale_button.on_change("active", partialed_logscale_callback)

    button_row = Row(children=[activation_button, logscale_button],
                     name="button_row")
    return button_row
Ejemplo n.º 12
0
                     tools="save,reset",
                     toolbar_location="below")

plot_figure.scatter('x', 'y', source=source, size=10)

columns = [
    TableColumn(field="x", title="x"),
    TableColumn(field="y", title="y")
]
data_table = DataTable(
    source=source,
    columns=columns,
    index_position=None,
    autosize_mode="fit_columns",
    editable=True,
)

toggle = Toggle()


def cb(attr, old, new):
    columns[0].visible = toggle.active


toggle.on_change('active', cb)

layout = row(plot_figure, data_table, toggle)

curdoc().add_root(layout)
curdoc().title = "Data-Table Bokeh Server"