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
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
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"
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)
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
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)
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') """)
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)
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>',
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")
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
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"