def test_displays_button_type(self, typ, bokeh_model_page): button = Toggle(button_type=typ, css_classes=["foo"]) page = bokeh_model_page(button) button = page.driver.find_element_by_css_selector('.foo .bk-btn') assert typ in button.get_attribute('class')
def test_js_on_click_executes(self, bokeh_model_page): button = Toggle(css_classes=['foo']) button.js_on_click(CustomJS(code=RECORD("value", "cb_obj.active"))) page = bokeh_model_page(button) button = page.driver.find_element_by_css_selector('.foo .bk-btn') button.click() results = page.results assert results == {'value': True} button = page.driver.find_element_by_css_selector('.foo .bk-btn') button.click() results = page.results assert results == {'value': False} button = page.driver.find_element_by_css_selector('.foo .bk-btn') button.click() results = page.results assert results == {'value': True} assert page.has_no_console_errors()
def modify_doc(doc): source = ColumnDataSource(dict(x=[1, 2], y=[1, 1])) plot = Plot(plot_height=400, plot_width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) plot.add_glyph(source, Circle(x='x', y='y', size=20)) plot.add_tools(CustomAction(callback=CustomJS(args=dict(s=source), code=RECORD("data", "s.data")))) button = Toggle(css_classes=['foo']) def cb(value): if value: source.data=dict(x=[10, 20], y=[10, 10]) else: source.data=dict(x=[100, 200], y=[100, 100]) button.on_click(cb) doc.add_root(column(button, plot))
def plotPairs(fileName, bokehPlaceholderId='bokehContent'): source = ColumnDataSource(data={'count':[], 'index':[]}) minCount = TextInput(value="20", title="Minimum number of trips per pair:") checkbox_group = CheckboxGroup(labels=["Include trips from a station to itself."]) ShowButton = Toggle(label="Show", type="success") model = dict(source=source, checkbox = checkbox_group, minCount = minCount) plot = Figure(title="Accumulative Number Of Trips Over Top Station Pairs", x_axis_label='Top Station Pairs in Decreasing Order', y_axis_label='Accumulative Number of Trips', plot_width=1200, plot_height=400) plot.ray(x = [0], y=[90], line_color='red', angle=0.0, name='90%', length = 0, line_width=2) plot.ray(x = [0], y=[90], line_color='red', angle=180.0, angle_units = 'deg', length = 0, line_width=2) plot.line(x = 'index', y ='count', source=source, line_width=2, line_alpha=0.6) callback = CustomJS(args=model, code=""" var selectSelf = checkbox.get('active').length; var minCountVal = minCount.get('value'); var xmlhttp; xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == XMLHttpRequest.DONE ) { if(xmlhttp.status == 200){ var data = source.get('data'); var result = JSON.parse(xmlhttp.responseText); data['count'] = result.count; data['index'] = result.index; source.trigger('change'); alert('triggered'); } else if(xmlhttp.status == 400) { alert(400); } else { alert(xmlhttp.status); } } }; var params = {self:selectSelf, min:minCountVal}; url = "/pairsdist?" + jQuery.param( params ); xmlhttp.open("GET", url, true); xmlhttp.send(); """) ShowButton.callback = callback layout = vform(checkbox_group, minCount, ShowButton, plot) script, div = components(layout) html = readHtmlFile(fileName) html = insertScriptIntoHeader(html, script) html = appendElementContent(html, div, "div", "bokehContent") return html
file_selection_button.on_click(load_file) files_selector_spacer = Spacer(width=10) group_selection_button = Button(label="Select Directory", button_type="primary", width=140) group_selection_button.on_click(load_directory_group) update_files_button = Button(label="Update Files", button_type="default", width=50) update_files_button.on_click(reload_all_files) auto_update_toggle_button = Toggle(label="Auto Update", button_type="default", width=50, active=True) auto_update_toggle_button.on_click(toggle_auto_update) unload_file_button = Button(label="Unload", button_type="danger", width=50) unload_file_button.on_click(unload_file) # files selection box files_selector = Select(title="Files:", options=[""]) files_selector.on_change('value', change_data_selector) # data selection box data_selector = MultiSelect(title="Data:", options=[], size=12) data_selector.on_change('value', select_data) # x axis selection box
plot_r2.add_layout(trial_type_b_corr) # Set up widgets data_title = Div(text="<b>Correlation Settings</b>", style={'font-size': '100%'}, width=200, height=30) corr_a_widget = Slider(title="Trial Type A Correlation", value=corr_a, start=-1, end=1, step=0.1) corr_b_widget = Slider(title="Trial Type B Correlation", value=corr_b, start=-1, end=1, step=0.1) noise_widget = Slider(title="noise", value=0, start=0, end=0.1, step=0.01) widgets = [data_title, corr_a_widget, corr_b_widget, noise_widget] beta_a_toggle = Toggle(label="Show A Betas", button_type="success", active=True) beta_b_toggle = Toggle(label="Show B Betas", button_type="success", active=True) for y0, y1, plot, beta_key, in zip([-0.2, 0.04], [0, 0.08], [plot_r1, plot_r2], ['betas_r1', 'betas_r2']): for idx, (onsets, betas, toggle, source) in enumerate(zip([onsets_a, onsets_b], [betas_a, betas_b], [beta_a_toggle, beta_b_toggle], [source_la, source_lb])): y0s = y0 * len(onsets) y1s = y1 * len(onsets) lbls = LabelSet(x='onsets', y=0.35, text=beta_key, source=source,
from bokeh.layouts import layout from bokeh.models import BoxAnnotation, Toggle from bokeh.plotting import figure output_file("styling_visible_annotation_with_interaction.html") p = figure(plot_width=600, plot_height=200, tools='') p.line([1, 2, 3], [1, 2, 1], line_color="blue") pink_line = p.line([1, 2, 3], [2, 1, 2], line_color="pink") green_box = BoxAnnotation(left=1.5, right=2.5, fill_color='green', fill_alpha=0.1) p.add_layout(green_box) # Use js_link to connect button active property to glyph visible property toggle1 = Toggle(label="Green Box", button_type="success", active=True) toggle1.js_link('active', green_box, 'visible') toggle2 = Toggle(label="Pink Line", button_type="success", active=True) toggle2.js_link('active', pink_line, 'visible') show(layout([p], [toggle1, toggle2])) ####### # dimensions from bokeh.plotting import figure, output_file, show output_file("dimensions.html")
def aggregate_plot(tb): """ Function for creating a bokeh plot that shows aggregate tax liabilities for each year the TaxBrain instance was run Parameters ---------- tb: An instance of the TaxBrain object Returns ------- Bokeh figure """ # Pull aggregate data by year and transpose it for plotting varlist = ["iitax", "payrolltax", "combined"] base_data = tb.multi_var_table(varlist, "base").transpose() base_data["calc"] = "Base" reform_data = tb.multi_var_table(varlist, "reform").transpose() reform_data["calc"] = "Reform" base_cds = ColumnDataSource(base_data) reform_cds = ColumnDataSource(reform_data) num_ticks = len(base_data) del base_data, reform_data fig = figure(title="Aggregate Tax Liability by Year", width=700, height=500, tools="save") ii_base = fig.line(x="index", y="iitax", line_width=4, line_color="#12719e", legend="Income Tax - Base", source=base_cds) ii_reform = fig.line(x="index", y="iitax", line_width=4, line_color="#73bfe2", legend="Income Tax - Reform", source=reform_cds) proll_base = fig.line(x="index", y="payrolltax", line_width=4, line_color="#408941", legend="Payroll Tax - Base", source=base_cds) proll_reform = fig.line(x="index", y="payrolltax", line_width=4, line_color="#98cf90", legend="Payroll Tax - Reform", source=reform_cds) comb_base = fig.line(x="index", y="combined", line_width=4, line_color="#a4201d", legend="Combined - Base", source=base_cds) comb_reform = fig.line(x="index", y="combined", line_width=4, line_color="#e9807d", legend="Combined - Reform", source=reform_cds) # format figure fig.legend.location = "top_left" fig.yaxis.formatter = NumeralTickFormatter(format="$0.00a") fig.yaxis.axis_label = "Aggregate Tax Liability" fig.xaxis.minor_tick_line_color = None fig.xaxis[0].ticker.desired_num_ticks = num_ticks # Add hover tool tool_str = """ <p><b>@calc - {}</b></p> <p>${}</p> """ ii_hover = HoverTool(tooltips=tool_str.format("Individual Income Tax", "@iitax{0,0}"), renderers=[ii_base, ii_reform]) proll_hover = HoverTool(tooltips=tool_str.format("Payroll Tax", "@payrolltax{0,0}"), renderers=[proll_base, proll_reform]) combined_hover = HoverTool(tooltips=tool_str.format( "Combined Tax", "@combined{0,0}"), renderers=[comb_base, comb_reform]) fig.add_tools(ii_hover, proll_hover, combined_hover) # toggle which lines are shown plot_js = """ object1.visible = toggle.active object2.visible = toggle.active object3.visible = toggle.active """ base_callback = CustomJS.from_coffeescript(code=plot_js, args={}) base_toggle = Toggle(label="Base", button_type="primary", callback=base_callback, active=True) base_callback.args = { "toggle": base_toggle, "object1": ii_base, "object2": proll_base, "object3": comb_base } reform_callback = CustomJS.from_coffeescript(code=plot_js, args={}) reform_toggle = Toggle(label="Reform", button_type="primary", callback=reform_callback, active=True) reform_callback.args = { "toggle": reform_toggle, "object1": ii_reform, "object2": proll_reform, "object3": comb_reform } fig_layout = layout([fig], [base_toggle, reform_toggle]) # Components needed to embed the figure data = json_item(fig_layout) outputs = { "media_type": "bokeh", "title": "", "data": data, } return outputs
def plot_benchmarks_as_lines(title, *bm, transform=None, line_title_transform=None, logx=True, logy=True): import bokeh from bokeh.plotting import figure, output_file, show from bokeh.palettes import Dark2_5 as palette from bokeh.layouts import row, column from bokeh.models import (Legend, LegendItem, CheckboxGroup, CustomJS, Div, RadioGroup, Toggle, ColumnDataSource, DataTable, TableColumn) from bokeh.models.markers import marker_types # ids = entry_ids(*bm, transform=transform) colors = itertools.cycle(palette) markers = itertools.cycle(marker_types) p = figure( title=title, x_axis_type="log" if logx else "linear", y_axis_type="log" if logy else "linear", #background_fill_color="#fafafa", plot_width=1000, x_axis_label="Number of pixels", y_axis_label="Throughput (MB/s)", ) p.toolbar.autohide = True #p.toolbar.active_inspect = [hover_tool, crosshair_tool] p.toolbar.active_drag = "auto" p.toolbar.active_scroll = "auto" # def dft(v): return v if v else (lambda n: n) tr = dft(transform) lttr = dft(line_title_transform) # for results in bm: x = [ids[name] for name in results.names] y = [bps / 1e6 for bps in results.bytes_per_second] c = next(colors) marker = next(markers) next(markers) # advance two line_name = lttr(results.first) #legends.append(LegendItem(name=c, label=line_name)) p.scatter(x, y, marker=marker, size=8, color=c, legend_label=line_name) p.line( x, y, color=c, alpha=0.9, #muted_color=c, muted_alpha=0.05, legend_label=line_name) p.legend.click_policy = "hide" p.legend.label_text_font_size = "10px" # def input_title(title): return Div(text=f"<h3>{title}</h3>") inputs = [] first = bm[0].first.meta for k, g in first.checkbox_groups().items(): cb = CheckboxGroup(labels=[str(v) for v in g], active=[i for i in range(len(g))], inline=True) inputs.append(input_title(k)) inputs.append(cb) # # https://github.com/bokeh/bokeh/blob/branch-2.3/examples/app/export_csv/main.py x_axis_values = [f"{m.num_pixels}px" for m in bm[0].meta] table_sources = [] for i, px in enumerate(x_axis_values): c = ColumnDataSource( data={ 'name': [nth(results.filtered_names, i) for results in bm], 'bytes_per_second': [nth(results.bytes_per_second, i) for results in bm], 'items_per_second': [nth(results.items_per_second, i) for results in bm], 'cpu_time': [nth(results.real_time, i) for results in bm], 'real_time': [nth(results.real_time, i) for results in bm], 'iterations': [nth(results.iterations, i) for results in bm], 'threads': [nth(results.threads, i) for results in bm], }) table_sources.append(c) selected_x_index = 8 # FIXME (currently 2000 pixels) table_source = copy.deepcopy(table_sources[selected_x_index]) relvalues = Toggle(label="Table: Relative values") px_title = input_title("Table: number of pixels") px_radiogroup = RadioGroup(labels=x_axis_values, active=selected_x_index) table_inputs = [relvalues, px_title, px_radiogroup] # table_cols = [ TableColumn(field='name', title='Name'), TableColumn(field='bytes_per_second', title='Bytes/second'), TableColumn(field='items_per_second', title='Items/second'), TableColumn(field='cpu_time', title='CPU time'), TableColumn(field='real_time', title='Real time'), TableColumn(field='iterations', title='Iterations'), TableColumn(field='threads', title='Threads'), ] data_table = DataTable(source=table_source, columns=table_cols, width=1200) callback = CustomJS(args=dict(radiogroup=px_radiogroup, source=table_source, table=table_sources), code=""" console.log(`active=${radiogroup.active}`); /*source.data=table[radiogroup.active];*/ var nrows = source.data['name'].length; var ts = table[radiogroup.active].data; var names = ["name", "bytes_per_second", "items_per_second", "cpu_time", "real_time", "iterations", "threads"]; var ncols = names.length; console.log(`names=${names} nrows=${nrows} ncols=${ncols}`); for(var i = 0; i < nrows; i++) { for(var j = 0; j < ncols; ++j) { var name = names[j]; /*console.log(`i=${i} j=${j} name=${name}`);*/ source.data[name][i] = ts[name][i]; } } source.change.emit(); """) px_radiogroup.js_on_change('active', callback) # lambda attr, old, new: log(f"attr={attr} old={old} new={new} active={px_radiogroup.active}")) # layout = column(row(column(*inputs), p), row(column(*table_inputs), data_table)) show(layout)
p.add_tools( HoverTool(renderers=[points_render], tooltips=[('Var1', '@var1'), ('Var2', '@var2'), ('Var3', '@var3')])) filter_list = {} for var in ['var1', 'var2', 'var3']: min_ = 0 max_ = 100 slider = RangeSlider(start=min_, end=max_, step=0.1, value=(min_, max_), title=f'{var} range') toggle = Toggle(label="Inactive", button_type="danger", aspect_ratio=3) toggle.js_on_click(toggle_callback(toggle)) filter_list[var] = Filter(var, slider, toggle) def update_plot(attrname, old, new): mask = [True] * len(gdf) for key, filter in filter_list.items(): if filter.toggle_.active: mask = mask & (gdf[key] >= filter.slider_.value[0]) & ( gdf[key] <= filter.slider_.value[1]) test_view.filters[0] = BooleanFilter(booleans=mask) for _, filter in filter_list.items(): filter.slider_.on_change('value', update_plot)
'mpoly': mpoly, 'ext_datafiles': ext_datafiles, 'line': line, }, code=""" console.log('select: value=' + this.value, this.toString()) line.glyph.y.field = this.value line.data_source.change.emit() """)) """ # %% Make a button ________________________________________________________________________________ """ buttons = {} buttons['test'] = Toggle(label="Test", visible=True, button_type='primary') # buttons['test'].js_on_change( 'active', CustomJS(args={'p': p}, code=""" if (cb_obj.active == false){ console.log(p.x_range) console.log(p.x_range.follow) console.log(p.x_range.start) console.log(p.x_range.end) p.x_range.setv({'start':-1.4e7,'end':-7.4e6}) p.change.emit() } else{ console.log(p.x_range) console.log(p.x_range.follow)
extension = '.txt' elif str(new) == '2': extension = '.npy' LABELS = [".csv", ".txt", ".npy"] extensiones_rg = RadioGroup(labels=LABELS, active=0) extensiones_rg.on_change('active', callback_extension) #BOTON INICIAR def activador(active): global manual manual = not manual act = Toggle(label="Manual/Automático", button_type="success") act.on_click(activador) #BOTON GUARDAR def guardador(active): global guardar guardar = not guardar boton_guardar = Toggle(label="Guardar", button_type="success") boton_guardar.on_click(guardador) # TEXT INPUT def Kp_callback(attr, old, new): global Kp Kp = new
def __make_daybyday_interactive_timeline( df: pd.DataFrame, *, geo_df: geopandas.GeoDataFrame, value_col: str, transform_df_func: Callable[[pd.DataFrame], pd.DataFrame] = None, stage: Union[DiseaseStage, Literal[Select.ALL]] = Select.ALL, count: Union[Counting, Literal[Select.ALL]] = Select.ALL, out_file_basename: str, subplot_title_prefix: str, plot_aspect_ratio: float = None, cmap=None, n_cbar_buckets: int = None, n_buckets_btwn_major_ticks: int = None, n_minor_ticks_btwn_major_ticks: int = None, per_capita_denominator: int = None, x_range: Tuple[float, float], y_range: Tuple[float, float], min_visible_y_range: float, should_make_video: bool, ) -> InfoForAutoload: """Create the bokeh interactive timeline plot(s) This function takes the given DataFrame, which must contain COVID data for locations on different dates, and a GeoDataFrame, which contains the long/lat coords for those locations, and creates an interactive choropleth of the COVID data over time. :param df: The COVID data DataFrame :type df: pd.DataFrame :param geo_df: The geometry GeoDataFrame for the locations in `df` :type geo_df: geopandas.GeoDataFrame :param value_col: The column of `df` containing the values to plot in the choropleth; should be something like "Case_Counts" or "Case_Diff_From_Prev_Day" :type value_col: str :param stage: The DiseaseStage to plot, defaults to Select.ALL. If ALL, then all stages are plotted and are stacked vertically. :type stage: Union[DiseaseStage, Literal[Select.ALL]], optional :param count: The Counting to plot, defaults to Select.ALL. If ALL, then all count types are plotted and are stacked horizontally. :type count: Union[Counting, Literal[Select.ALL]], optional :param out_file_basename: The basename of the file to save the interactive plots to (there are two components, the JS script and the HTML <div>) :type out_file_basename: str :param subplot_title_prefix: What the first part of the subplot title should be; probably a function of `value_col` (if value_col is "Case_Counts" then this param might be "Cases" or "# of Cases") :type subplot_title_prefix: str :param x_range: The range of the x-axis as (min, max) :type x_range: Tuple[float, float] :param y_range: The range of the y-axis as (min, max) :type y_range: Tuple[float, float] :param min_visible_y_range: The minimum height (in axis units) of the y-axis; it will not be possible to zoom in farther than this on the choropleth. :type min_visible_y_range: float :param should_make_video: Optionally run through the timeline day by day, capture a screenshot for each day, and then stitch the screenshots into a video. The video shows the same info as the interactive plots, but not interactively. This easily takes 20x as long as just making the graphs themselves, so use with caution. :type should_make_video: bool :param transform_df_func: This function expects data in a certain format, and does a bunch of preprocessing (expected to be common) before plotting. This gives you a chance to do any customization on the postprocessed df before it's plotted. Defaults to None, in which case no additional transformation is performed. :type transform_df_func: Callable[[pd.DataFrame], pd.DataFrame], optional :param plot_aspect_ratio: The aspect ratio of the plot as width/height; if set, the aspect ratio will be fixed to this. Defaults to None, in which case the aspect ratio is determined from the x_range and y_range arguments :type plot_aspect_ratio: float, optional :param cmap: The colormap to use as either a matplotlib-compatible colormap or a list of hex strings (e.g., ["#ae8f1c", ...]). Defaults to None in which case a reasonable default is used. :type cmap: Matplotlib-compatible colormap or List[str], optional :param n_cbar_buckets: How many colorbar buckets to use. Has little effect if the colormap is continuous, but always works in conjunction with n_buckets_btwn_major_ticks to determine the number of major ticks. Defaults to 6. :type n_cbar_buckets: int, optional :param n_buckets_btwn_major_ticks: How many buckets are to lie between colorbar major ticks, determining how many major ticks are drawn. Defaults to 1. :type n_buckets_btwn_major_ticks: int, optional :param n_minor_ticks_btwn_major_ticks: How many minor ticks to draw between colorbar major ticks. Defaults to 8 (which means each pair of major ticks has 10 ticks total). :type n_minor_ticks_btwn_major_ticks: int, optional :param per_capita_denominator: When describing per-capita numbers, what to use as the denominator (e.g., cases per 100,000 people). If None, it is automatically computed per plot to be appropriately scaled for the data. :type per_capita_denominator: int, optional :raises ValueError: [description] :return: The two pieces of info required to make a Bokeh autoloading HTML+JS plot: the HTML div to be inserted somewhere in the HTML body, and the JS file that will load the plot into that div. :rtype: InfoForAutoload """ Counting.verify(count, allow_select=True) DiseaseStage.verify(stage, allow_select=True) # The date as a string, so that bokeh can use it as a column name STRING_DATE_COL = "String_Date_" # A column whose sole purpose is to be a (the same) date associated with each # location FAKE_DATE_COL = "Fake_Date_" # The column we'll actually use for the colors; it's computed from value_col COLOR_COL = "Color_" # Under no circumstances may you change this date format # It's not just a pretty date representation; it actually has to match up with the # date strings computed in JS DATE_FMT = r"%Y-%m-%d" ID_COLS = [ REGION_NAME_COL, Columns.DATE, Columns.STAGE, Columns.COUNT_TYPE, ] if cmap is None: cmap = cmocean.cm.matter if n_cbar_buckets is None: n_cbar_buckets = 6 if n_buckets_btwn_major_ticks is None: n_buckets_btwn_major_ticks = 1 if n_minor_ticks_btwn_major_ticks is None: n_minor_ticks_btwn_major_ticks = 8 n_cbar_major_ticks = n_cbar_buckets // n_buckets_btwn_major_ticks + 1 try: color_list = [ # Convert matplotlib colormap to bokeh (list of hex strings) # https://stackoverflow.com/a/49934218 RGB(*rgb).to_hex() for i, rgb in enumerate((255 * cmap(range(256))).astype("int")) ] except TypeError: color_list = cmap color_list: List[BokehColor] if stage is Select.ALL: stage_list = list(DiseaseStage) else: stage_list = [stage] if count is Select.ALL: count_list = list(Counting) else: count_list = [count] stage_list: List[DiseaseStage] count_list: List[Counting] stage_count_list: List[Tuple[DiseaseStage, Counting]] = list( itertools.product(stage_list, count_list)) df = df.copy() # Unadjust dates (see SaveFormats._adjust_dates) normalized_dates = df[Columns.DATE].dt.normalize() is_at_midnight = df[Columns.DATE] == normalized_dates df.loc[is_at_midnight, Columns.DATE] -= pd.Timedelta(days=1) df.loc[~is_at_midnight, Columns.DATE] = normalized_dates[~is_at_midnight] min_date, max_date = df[Columns.DATE].agg(["min", "max"]) dates: List[pd.Timestamp] = pd.date_range(start=min_date, end=max_date, freq="D") max_date_str = max_date.strftime(DATE_FMT) # Get day-by-day case diffs per location, date, stage, count-type # Make sure data exists for every date for every state so that the entire country is # plotted each day; fill missing data with 0 (missing really *is* as good as 0) # enums will be replaced by their name (kind of important) id_cols_product: pd.MultiIndex = pd.MultiIndex.from_product( [ df[REGION_NAME_COL].unique(), dates, [s.name for s in DiseaseStage], [c.name for c in Counting], ], names=ID_COLS, ) df = (id_cols_product.to_frame(index=False).merge( df, how="left", on=ID_COLS, ).sort_values(ID_COLS)) df[STRING_DATE_COL] = df[Columns.DATE].dt.strftime(DATE_FMT) df[Columns.CASE_COUNT] = df[Columns.CASE_COUNT].fillna(0) if transform_df_func is not None: df = transform_df_func(df) df = geo_df.merge(df, how="inner", on=REGION_NAME_COL)[[ REGION_NAME_COL, Columns.DATE, STRING_DATE_COL, Columns.STAGE, Columns.COUNT_TYPE, value_col, ]] dates: List[pd.Timestamp] = [ pd.Timestamp(d) for d in df[Columns.DATE].unique() ] values_mins_maxs = (df[df[value_col] > 0].groupby( [Columns.STAGE, Columns.COUNT_TYPE])[value_col].agg(["min", "max"])) vmins: pd.Series = values_mins_maxs["min"] vmaxs: pd.Series = values_mins_maxs["max"] pow10s_series: pd.Series = vmaxs.map( lambda x: int(10**(-np.floor(np.log10(x))))) # _pow_10s_series_dict = {} # for stage in DiseaseStage: # _pow_10s_series_dict.update( # { # (stage.name, Counting.TOTAL_CASES.name): 100000, # (stage.name, Counting.PER_CAPITA.name): 10000, # } # ) # pow10s_series = pd.Series(_pow_10s_series_dict) vmins: dict = vmins.to_dict() vmaxs: dict = vmaxs.to_dict() for stage in DiseaseStage: _value_key = (stage.name, Counting.PER_CAPITA.name) if per_capita_denominator is None: _max_pow10 = pow10s_series.loc[(slice(None), Counting.PER_CAPITA.name)].max() else: _max_pow10 = per_capita_denominator vmins[_value_key] *= _max_pow10 vmaxs[_value_key] *= _max_pow10 pow10s_series[_value_key] = _max_pow10 percap_pow10s: pd.Series = df.apply( lambda row: pow10s_series[ (row[Columns.STAGE], row[Columns.COUNT_TYPE])], axis=1, ) _per_cap_rows = df[Columns.COUNT_TYPE] == Counting.PER_CAPITA.name df.loc[_per_cap_rows, value_col] *= percap_pow10s.loc[_per_cap_rows] # Ideally we wouldn't have to pivot, and we could do a JIT join of state longs/lats # after filtering the data. Unfortunately this is not possible, and a long data # format leads to duplication of the very large long/lat lists; pivoting is how we # avoid that. (This seems to be one downside of bokeh when compared to plotly) df = (df.pivot_table( index=[REGION_NAME_COL, Columns.STAGE, Columns.COUNT_TYPE], columns=STRING_DATE_COL, values=value_col, aggfunc="first", ).reset_index().merge( geo_df[[REGION_NAME_COL, LONG_COL, LAT_COL]], how="inner", on=REGION_NAME_COL, )) # All three oclumns are just initial values; they'll change with the date slider df[value_col] = df[max_date_str] df[FAKE_DATE_COL] = max_date_str df[COLOR_COL] = np.where(df[value_col] > 0, df[value_col], "NaN") # Technically takes a df but we don't need the index bokeh_data_source = ColumnDataSource( {k: v.tolist() for k, v in df.to_dict(orient="series").items()}) filters = [[ GroupFilter(column_name=Columns.STAGE, group=stage.name), GroupFilter(column_name=Columns.COUNT_TYPE, group=count.name), ] for stage, count in stage_count_list] figures = [] for subplot_index, (stage, count) in enumerate(stage_count_list): # fig = bplotting.figure() # ax: plt.Axes = fig.add_subplot( # len(stage_list), len(count_list), subplot_index # ) # # Add timestamp to top right axis # if subplot_index == 2: # ax.text( # 1.25, # Coords are arbitrary magic numbers # 1.23, # f"Last updated {NOW_STR}", # horizontalalignment="right", # fontsize="small", # transform=ax.transAxes, # ) view = CDSView(source=bokeh_data_source, filters=filters[subplot_index]) vmin = vmins[(stage.name, count.name)] vmax = vmaxs[(stage.name, count.name)] # Compute and set axes titles if stage is DiseaseStage.CONFIRMED: fig_stage_name = "Cases" elif stage is DiseaseStage.DEATH: fig_stage_name = "Deaths" else: raise ValueError fig_title_components: List[str] = [] if subplot_title_prefix is not None: fig_title_components.append(subplot_title_prefix) fig_title_components.append(fig_stage_name) if count is Counting.PER_CAPITA: _per_cap_denom = pow10s_series[(stage.name, count.name)] fig_title_components.append(f"Per {_per_cap_denom:,d} people") formatter = PrintfTickFormatter(format=r"%2.3f") label_standoff = 12 tooltip_fmt = "{0.000}" else: formatter = NumeralTickFormatter(format="0.0a") label_standoff = 10 tooltip_fmt = "{0}" color_mapper = LogColorMapper( color_list, low=vmin, high=vmax, nan_color="#f2f2f2", ) fig_title = " ".join(fig_title_components) if plot_aspect_ratio is None: if x_range is None or y_range is None: raise ValueError("Must provide both `x_range` and `y_range`" + " when `plot_aspect_ratio` is None") plot_aspect_ratio = (x_range[1] - x_range[0]) / (y_range[1] - y_range[0]) # Create figure object p = bplotting.figure( title=fig_title, title_location="above", tools=[ HoverTool( tooltips=[ ("Date", f"@{{{FAKE_DATE_COL}}}"), ("State", f"@{{{REGION_NAME_COL}}}"), ("Count", f"@{{{value_col}}}{tooltip_fmt}"), ], toggleable=False, ), PanTool(), BoxZoomTool(match_aspect=True), ZoomInTool(), ZoomOutTool(), ResetTool(), ], active_drag=None, aspect_ratio=plot_aspect_ratio, output_backend="webgl", lod_factor=4, lod_interval=400, lod_threshold=1000, lod_timeout=300, ) p.xgrid.grid_line_color = None p.ygrid.grid_line_color = None # Finally, add the actual choropleth data we care about p.patches( LONG_COL, LAT_COL, source=bokeh_data_source, view=view, fill_color={ "field": COLOR_COL, "transform": color_mapper }, line_color="black", line_width=0.25, fill_alpha=1, ) # Add evenly spaced ticks and their labels to the colorbar # First major, then minor # Adapted from https://stackoverflow.com/a/50314773 bucket_size = (vmax / vmin)**(1 / n_cbar_buckets) tick_dist = bucket_size**n_buckets_btwn_major_ticks # Simple log scale math major_tick_locs = ( vmin * (tick_dist**np.arange(0, n_cbar_major_ticks)) # * (bucket_size ** 0.5) # Use this if centering ticks on buckets ) # Get minor locs by linearly interpolating between major ticks minor_tick_locs = [] for major_tick_index, this_major_tick in enumerate( major_tick_locs[:-1]): next_major_tick = major_tick_locs[major_tick_index + 1] # Get minor ticks as numbers in range [this_major_tick, next_major_tick] # and exclude the major ticks themselves (once we've used them to # compute the minor tick locs) minor_tick_locs.extend( np.linspace( this_major_tick, next_major_tick, n_minor_ticks_btwn_major_ticks + 2, )[1:-1]) color_bar = ColorBar( color_mapper=color_mapper, ticker=FixedTicker(ticks=major_tick_locs, minor_ticks=minor_tick_locs), formatter=formatter, label_standoff=label_standoff, major_tick_out=0, major_tick_in=13, major_tick_line_color="white", major_tick_line_width=1, minor_tick_out=0, minor_tick_in=5, minor_tick_line_color="white", minor_tick_line_width=1, location=(0, 0), border_line_color=None, bar_line_color=None, orientation="vertical", ) p.add_layout(color_bar, "right") p.hover.point_policy = "follow_mouse" # Bokeh axes (and most other things) are splattable p.axis.visible = False figures.append(p) # Make all figs pan and zoom together by setting their axes equal to each other # Also fix the plots' aspect ratios figs_iter = iter(np.ravel(figures)) anchor_fig = next(figs_iter) if x_range is not None and y_range is not None: data_aspect_ratio = (x_range[1] - x_range[0]) / (y_range[1] - y_range[0]) else: data_aspect_ratio = plot_aspect_ratio if x_range is not None: anchor_fig.x_range = Range1d( *x_range, bounds="auto", min_interval=min_visible_y_range * data_aspect_ratio, ) if y_range is not None: anchor_fig.y_range = Range1d(*y_range, bounds="auto", min_interval=min_visible_y_range) for fig in figs_iter: fig.x_range = anchor_fig.x_range fig.y_range = anchor_fig.y_range # 2x2 grid (for now) gp = gridplot( figures, ncols=len(count_list), sizing_mode="scale_both", toolbar_location="above", ) plot_layout = [gp] # Ok, pause # Now we're going into a whole other thing: we're doing all the JS logic behind a # date slider that changes which date is shown on the graphs. The structure of the # data is one column per date, one row per location, and a few extra columns to # store the data the graph will use. When we adjust the date of the slider, we copy # the relevant column of the df into the columns the graphs are looking at. # That's the easy part; the hard part is handling the "play button" functionality, # whereby the user can click one button and the date slider will periodically # advance itself. That requires a fair bit of logic to schedule and cancel the # timers and make it all feel right. # Create unique ID for the JS playback info object for this plot (since it'll be on # the webpage with other plots, and their playback info isn't shared) _THIS_PLOT_ID = uuid.uuid4().hex __TIMER = "'timer'" __IS_ACTIVE = "'isActive'" __SELECTED_INDEX = "'selectedIndex'" __BASE_INTERVAL_MS = "'BASE_INTERVAL'" # Time (in MS) btwn frames when speed==1 __TIMER_START_DATE = "'startDate'" __TIMER_ELAPSED_TIME_MS = "'elapsedTimeMS'" __TIMER_ELAPSED_TIME_PROPORTION = "'elapsedTimeProportion'" __SPEEDS_KEY = "'SPEEDS'" __PLAYBACK_INFO = f"window._playbackInfo_{_THIS_PLOT_ID}" _PBI_TIMER = f"{__PLAYBACK_INFO}[{__TIMER}]" _PBI_IS_ACTIVE = f"{__PLAYBACK_INFO}[{__IS_ACTIVE}]" _PBI_SELECTED_INDEX = f"{__PLAYBACK_INFO}[{__SELECTED_INDEX}]" _PBI_TIMER_START_DATE = f"{__PLAYBACK_INFO}[{__TIMER_START_DATE}]" _PBI_TIMER_ELAPSED_TIME_MS = f"{__PLAYBACK_INFO}[{__TIMER_ELAPSED_TIME_MS}]" _PBI_TIMER_ELAPSED_TIME_PROPORTION = ( f"{__PLAYBACK_INFO}[{__TIMER_ELAPSED_TIME_PROPORTION}]") _PBI_BASE_INTERVAL = f"{__PLAYBACK_INFO}[{__BASE_INTERVAL_MS}]" _PBI_SPEEDS = f"{__PLAYBACK_INFO}[{__SPEEDS_KEY}]" _PBI_CURR_INTERVAL_MS = ( f"{_PBI_BASE_INTERVAL} / {_PBI_SPEEDS}[{_PBI_SELECTED_INDEX}]") _SPEED_OPTIONS = [0.25, 0.5, 1.0, 2.0] _DEFAULT_SPEED = 1.0 _DEFAULT_SELECTED_INDEX = _SPEED_OPTIONS.index(_DEFAULT_SPEED) _SETUP_WINDOW_PLAYBACK_INFO = f""" if (typeof({__PLAYBACK_INFO}) === 'undefined') {{ {__PLAYBACK_INFO} = {{ {__TIMER}: null, {__IS_ACTIVE}: false, {__SELECTED_INDEX}: {_DEFAULT_SELECTED_INDEX}, {__TIMER_START_DATE}: null, {__TIMER_ELAPSED_TIME_MS}: 0, {__TIMER_ELAPSED_TIME_PROPORTION}: 0, {__BASE_INTERVAL_MS}: 1000, {__SPEEDS_KEY}: {_SPEED_OPTIONS} }}; }} """ _DEFFUN_INCR_DATE = f""" // See this link for why this works (it's an undocumented feature?) // https://discourse.bokeh.org/t/5254 // Tl;dr we need this to automatically update the hover as the play button plays // Without this, the hover tooltip only updates when we jiggle the mouse // slightly let prev_val = null; source.inspect.connect(v => prev_val = v); function updateDate() {{ {_PBI_TIMER_START_DATE} = new Date(); {_PBI_TIMER_ELAPSED_TIME_MS} = 0 if (dateSlider.value < maxDate) {{ dateSlider.value += 86400000; }} if (dateSlider.value >= maxDate) {{ console.log(dateSlider.value, maxDate) console.log('reached end') clearInterval({_PBI_TIMER}); {_PBI_IS_ACTIVE} = false; playPauseButton.active = false; playPauseButton.change.emit(); playPauseButton.label = 'Restart'; }} dateSlider.change.emit(); // This is pt. 2 of the prev_val/inspect stuff above if (prev_val !== null) {{ source.inspect.emit(prev_val); }} }} """ _DO_START_TIMER = f""" function startLoopTimer() {{ updateDate(); if ({_PBI_IS_ACTIVE}) {{ {_PBI_TIMER} = setInterval(updateDate, {_PBI_CURR_INTERVAL_MS}) }} }} {_PBI_TIMER_START_DATE} = new Date(); // Should never be <0 or >1 but I am being very defensive here const proportionRemaining = 1 - ( {_PBI_TIMER_ELAPSED_TIME_PROPORTION} <= 0 ? 0 : {_PBI_TIMER_ELAPSED_TIME_PROPORTION} >= 1 ? 1 : {_PBI_TIMER_ELAPSED_TIME_PROPORTION} ); const remainingTimeMS = ( {_PBI_CURR_INTERVAL_MS} * proportionRemaining ); const initialInterval = ( {_PBI_TIMER_ELAPSED_TIME_MS} === 0 ? 0 : remainingTimeMS ); {_PBI_TIMER} = setTimeout( startLoopTimer, initialInterval ); """ _DO_STOP_TIMER = f""" const now = new Date(); {_PBI_TIMER_ELAPSED_TIME_MS} += ( now.getTime() - {_PBI_TIMER_START_DATE}.getTime() ); {_PBI_TIMER_ELAPSED_TIME_PROPORTION} = ( {_PBI_TIMER_ELAPSED_TIME_MS} / {_PBI_CURR_INTERVAL_MS} ); clearInterval({_PBI_TIMER}); """ update_on_date_change_callback = CustomJS( args={"source": bokeh_data_source}, code=f""" {_SETUP_WINDOW_PLAYBACK_INFO} const sliderValue = cb_obj.value; const sliderDate = new Date(sliderValue) // Ugh, actually requiring the date to be YYYY-MM-DD (matching DATE_FMT) const dateStr = sliderDate.toISOString().split('T')[0] const data = source.data; {_PBI_TIMER_ELAPSED_TIME_MS} = 0 if (typeof(data[dateStr]) !== 'undefined') {{ data['{value_col}'] = data[dateStr] const valueCol = data['{value_col}']; const colorCol = data['{COLOR_COL}']; const fakeDateCol = data['{FAKE_DATE_COL}'] for (var i = 0; i < data['{value_col}'].length; i++) {{ const value = valueCol[i] if (value == 0) {{ colorCol[i] = 'NaN'; }} else {{ colorCol[i] = value; }} fakeDateCol[i] = dateStr; }} source.change.emit(); }} """, ) # Taking day-over-day diffs means the min slider day is one more than the min data # date (might be off by 1 if not using day over diffs but in practice not an issue) min_slider_date = min_date + pd.Timedelta(days=1) date_slider = DateSlider( start=min_slider_date, end=max_date, value=max_date, step=1, sizing_mode="stretch_width", width_policy="fit", ) date_slider.js_on_change("value", update_on_date_change_callback) play_pause_button = Toggle( label="Start playing", button_type="success", active=False, sizing_mode="stretch_width", ) animate_playback_callback = CustomJS( args={ "source": bokeh_data_source, "dateSlider": date_slider, "playPauseButton": play_pause_button, "maxDate": max_date, "minDate": min_slider_date, }, code=f""" {_SETUP_WINDOW_PLAYBACK_INFO} {_DEFFUN_INCR_DATE} if (dateSlider.value >= maxDate) {{ if (playPauseButton.active) {{ dateSlider.value = minDate; dateSlider.change.emit(); // Hack to get timer to wait after date slider wraps; any positive // number works but the smaller the better {_PBI_TIMER_ELAPSED_TIME_MS} = 1; }} }} const active = cb_obj.active; {_PBI_IS_ACTIVE} = active; if (active) {{ playPauseButton.label = 'Playing – Click/tap to pause' {_DO_START_TIMER} }} else {{ playPauseButton.label = 'Paused – Click/tap to play' {_DO_STOP_TIMER} }} """, ) play_pause_button.js_on_click(animate_playback_callback) change_playback_speed_callback = CustomJS( args={ "source": bokeh_data_source, "dateSlider": date_slider, "playPauseButton": play_pause_button, "maxDate": max_date, }, code=f""" {_SETUP_WINDOW_PLAYBACK_INFO} {_DEFFUN_INCR_DATE} // Must stop timer before handling changing the speed, as stopping the timer // saves values based on the current (unchaged) speed selection if ({_PBI_TIMER} !== null) {{ {_DO_STOP_TIMER} }} const selectedIndex = cb_obj.active; {_PBI_SELECTED_INDEX} = selectedIndex; if ({_PBI_IS_ACTIVE}) {{ {_DO_START_TIMER} }} else {{ {_PBI_TIMER_ELAPSED_TIME_MS} = 0 }} console.log({__PLAYBACK_INFO}) """, ) playback_speed_radio = RadioButtonGroup( labels=[f"{speed:.2g}x speed" for speed in _SPEED_OPTIONS], active=_DEFAULT_SELECTED_INDEX, sizing_mode="stretch_width", ) playback_speed_radio.js_on_click(change_playback_speed_callback) plot_layout.append( layout_column( [ date_slider, layout_row( [play_pause_button, playback_speed_radio], height_policy="min", ), ], width_policy="fit", height_policy="min", )) plot_layout = layout_column(plot_layout, sizing_mode="scale_both") # grid = gridplot(figures, ncols=len(count_list), sizing_mode="stretch_both") # Create the autoloading bokeh plot info (HTML + JS) js_path = str(Path(out_file_basename + "_autoload").with_suffix(".js")) tag_html_path = str( Path(out_file_basename + "_div_tag").with_suffix(".html")) js_code, tag_code = autoload_static(plot_layout, CDN, js_path) tag_uuid = re.search(r'id="([^"]+)"', tag_code).group(1) tag_code = re.sub(r'src="([^"]+)"', f'src="\\1?uuid={tag_uuid}"', tag_code) with open(Paths.DOCS / js_path, "w") as f_js, open(Paths.DOCS / tag_html_path, "w") as f_html: f_js.write(js_code) f_html.write(tag_code) # Create the video by creating stills of the graphs for each date and then stitching # the images into a video if should_make_video: save_dir: Path = PNG_SAVE_ROOT_DIR / out_file_basename save_dir.mkdir(parents=True, exist_ok=True) STILL_WIDTH = 1500 STILL_HEIGHT = int(np.ceil(STILL_WIDTH / plot_aspect_ratio) * 1.05) # Unclear why *1.05 is necessary gp.height = STILL_HEIGHT gp.width = STILL_WIDTH gp.sizing_mode = "fixed" orig_title = anchor_fig.title.text for date in dates: date_str = date.strftime(DATE_FMT) anchor_fig.title = Title(text=f"{orig_title} {date_str}") for p in figures: p.title = Title(text=p.title.text, text_font_size="20px") # Just a reimplementation of the JS code in the date slider's callback data = bokeh_data_source.data data[value_col] = data[date_str] for i, value in enumerate(data[value_col]): if value == 0: data[COLOR_COL][i] = "NaN" else: data[COLOR_COL][i] = value data[FAKE_DATE_COL][i] = date_str save_path: Path = (save_dir / date_str).with_suffix(".png") export_png(gp, filename=save_path) resize_to_even_dims(save_path, pad_bottom=0.08) if date == max(dates): poster_path: Path = ( PNG_SAVE_ROOT_DIR / (out_file_basename + "_poster")).with_suffix(".png") poster_path.write_bytes(save_path.read_bytes()) make_video(save_dir, out_file_basename, 0.9) print(f"Did interactive {out_file_basename}") return (js_code, tag_code)
v0_input_x.on_change('value', particle_speed_x) ## Create slider to select v0-y v0_input_y = Slider(title=u"v\u2092-y", value=-2.0, start=-5.0, end=5.0, step=0.5) v0_input_y.on_change('value', particle_speed_y) ## Create reset button reset_button = Button(label="Reset", button_type="success") reset_button.on_click(reset_situation) ## Create pause button pause_button = Toggle(label="Pause", button_type="success") pause_button.on_click(pause) ## Create re-initialise button reinit_button = Button(label="Re-initialise", button_type="success") reinit_button.on_click(BackToInitial) ## Create play button play_button = Button(label="Play", button_type="success") play_button.on_click(play) ## Create choice of referential button Referential_button = RadioGroup( labels=["Reference frame: Room", "Reference frame: Disk"], active=0) Referential_button.on_change('active', chooseRef)
CheckboxButtonGroup, DatePicker, Dropdown, RadioButtonGroup, TextInput, Toggle, ) from bokeh.plotting import save menu = [("Item 1", "1"), ("Item 2", "2"), ("Item 3", "3")] layout = column( Button(label="Default Button 1", button_type="default"), Button(label="Primary Button 2", button_type="primary"), Button(label="Success Button 3", button_type="success"), Toggle(label="Default Toggle 1", button_type="default"), Toggle(label="Primary Toggle 2", button_type="primary"), Toggle(label="Success Toggle 3", button_type="success"), Dropdown(label="Default Dropdown 1", button_type="default", menu=menu), Dropdown(label="Primary Dropdown 2", button_type="primary", menu=menu), Dropdown(label="Success Dropdown 3", button_type="success", menu=menu), CheckboxButtonGroup( labels=["Checkbox Option 1", "Checkbox Option 2", "Checkbox Option 3"], button_type="default", active=[0, 1]), CheckboxButtonGroup( labels=["Checkbox Option 4", "Checkbox Option 5", "Checkbox Option 6"], button_type="primary", active=[1, 2]), CheckboxButtonGroup( labels=["Checkbox Option 7", "Checkbox Option 8", "Checkbox Option 9"],
def plotHistogram(fileName, initData, stations, dateRange, bokehPlaceholderId='bokehContent'): data = {'xs':[initData['bins']], 'ys':[initData['values']],'ss':[1,2], 'es':[3,4] }#ss and es are for test purposes we'll add other values of the controlles e.g. age, usertype, Gender coming fetshed from initdata source = ColumnDataSource(data=data) stations.insert(0, "All") selectSS = Select(title="Start Station:", value="All", options=stations) selectES = Select(title="End Station:", value="All", options=stations) selectUT = Select(title="User Type:", value="All", options=["All", "Subscriber", "Customer"]) selectGender = Select(title="Gender:", value="All", options=["All", "Male", "Female"]) sliderAge = Slider(start=8, end=100, value=30, step=5, title="Age") startDP = DatePicker(title="Start Date:", min_date=dateRange[0] ,max_date=dateRange[1], value=dateRange[0]) endDP = DatePicker(title="End Date:", min_date=dateRange[0] ,max_date=dateRange[1], value=dateRange[1]) binSize = TextInput(value="15", title="Bin Size (Days):") AddButton = Toggle(label="Add", type="success") DeleteButton = Toggle(label="delete", type="success") columns = [TableColumn(field="ss", title="Start Station"),TableColumn(field="es", title="End Station")]# add other columns contains values of other controllers data_table = DataTable(source=source, columns=columns, width=650, height=300) model = dict(source=source, selectSS = selectSS, selectES = selectES, startDP = startDP, endDP = endDP, binSize = binSize,selectUT=selectUT,selectGender=selectGender,sliderAge=sliderAge) plot = Figure(plot_width=650, plot_height=400, x_axis_type="datetime") plot.multi_line('xs', 'ys', source=source, line_width='width', line_alpha=0.6, line_color='color') callback = CustomJS(args=model, code=""" //alert("callback"); var startStation = selectSS.get('value'); var endStation = selectES.get('value'); var startDate = startDP.get('value'); if ( typeof(startDate) !== "number") startDate = startDate.getTime(); var endDate = endDP.get('value'); if ( typeof(endDate) !== "number") endDate = endDate.getTime(); var binSize = binSize.get('value'); //alert(startStation + " " + endStation + " " + startDate + " " + endDate + " " + binSize); var xmlhttp; xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == XMLHttpRequest.DONE ) { if(xmlhttp.status == 200){ var data = source.get('data'); var result = JSON.parse(xmlhttp.responseText); var temp=[]; for(var date in result.x) { temp.push(new Date(result.x[date])); } data['xs'].push(temp); data['ys'].push(result.y); source.trigger('change'); } else if(xmlhttp.status == 400) { alert(400); } else { alert(xmlhttp.status); } } }; var params = {ss:startStation, es:endStation, sd:startDate, ed:endDate, bs: binSize}; url = "/histogram?" + jQuery.param( params ); xmlhttp.open("GET", url, true); xmlhttp.send(); """) AddButton.callback = callback #DeleteButton.on_click(callback1) layout1 = vform (startDP,endDP,binSize) layout2 = vform(plot,DeleteButton,data_table) layout3 = vform(selectSS, selectES,selectUT,selectGender,sliderAge,AddButton) layout = hplot(layout1,layout2,layout3) script, div = components(layout) html = readHtmlFile(fileName) html = insertScriptIntoHeader(html, script) html = appendElementContent(html, div, "div", "bokehContent") return html
def widgets(): from bokeh.io import show from bokeh.models import Select, CheckboxButtonGroup, Button, CheckboxGroup, ColorPicker, Dropdown, \ FileInput, MultiSelect, RadioButtonGroup, RadioGroup, Slider, RangeSlider, TextAreaInput, TextInput, Toggle, \ Paragraph, PreText, Div put_text('Button') button = Button(label="Foo", button_type="success") show(button) put_text('CheckboxButtonGroup') checkbox_button_group = CheckboxButtonGroup( labels=["Option 1", "Option 2", "Option 3"], active=[0, 1]) show(checkbox_button_group) put_text('CheckboxGroup') checkbox_group = CheckboxGroup(labels=["Option 1", "Option 2", "Option 3"], active=[0, 1]) show(checkbox_group) put_text('ColorPicker') color_picker = ColorPicker(color="#ff4466", title="Choose color:", width=200) show(color_picker) put_text('Dropdown') menu = [("Item 1", "item_1"), ("Item 2", "item_2"), None, ("Item 3", "item_3")] dropdown = Dropdown(label="Dropdown button", button_type="warning", menu=menu) show(dropdown) put_text('FileInput') file_input = FileInput() show(file_input) put_text('MultiSelect') multi_select = MultiSelect(title="Option:", value=["foo", "quux"], options=[("foo", "Foo"), ("bar", "BAR"), ("baz", "bAz"), ("quux", "quux")]) show(multi_select) put_text('RadioButtonGroup') radio_button_group = RadioButtonGroup( labels=["Option 1", "Option 2", "Option 3"], active=0) show(radio_button_group) put_text('RadioGroup') radio_group = RadioGroup(labels=["Option 1", "Option 2", "Option 3"], active=0) show(radio_group) put_text('Select') select = Select(title="Option:", value="foo", options=["foo", "bar", "baz", "quux"]) show(select) put_text('Slider') slider = Slider(start=0, end=10, value=1, step=.1, title="Stuff") show(slider) put_text('RangeSlider') range_slider = RangeSlider(start=0, end=10, value=(1, 9), step=.1, title="Stuff") show(range_slider) put_text('TextAreaInput') text_input = TextAreaInput(value="default", rows=6, title="Label:") show(text_input) put_text('TextInput') text_input = TextInput(value="default", title="Label:") show(text_input) put_text('Toggle') toggle = Toggle(label="Foo", button_type="success") show(toggle) put_text('Div') div = Div( text= """Your <a href="https://en.wikipedia.org/wiki/HTML">HTML</a>-supported text is initialized with the <b>text</b> argument. The remaining div arguments are <b>width</b> and <b>height</b>. For this example, those values are <i>200</i> and <i>100</i> respectively.""", width=200, height=100) show(div) put_text('Paragraph') p = Paragraph( text="""Your text is initialized with the 'text' argument. The remaining Paragraph arguments are 'width' and 'height'. For this example, those values are 200 and 100 respectively.""", width=200, height=100) show(p) put_text('PreText') pre = PreText(text="""Your text is initialized with the 'text' argument. The remaining Paragraph arguments are 'width' and 'height'. For this example, those values are 500 and 100 respectively.""", width=500, height=100) show(pre)
def plotHistogram(fileName, initData, stations, dateRange, bokehPlaceholderId='bokehContent'): MAX_AGE = 150 #The initial data data = { 'xs':[initData['bins']], 'ys':[initData['values']], 'ss':["All"], 'es':["All"], 'ut':["All"], 'g':["All"], 'a':[MAX_AGE], 'color':["blue"], 'line_width':[2] } source = ColumnDataSource(data=data) stations.insert(0, "All") selectSS = Select(title="Start Station:", value="All", options=stations) selectES = Select(title="End Station:", value="All", options=stations) selectUT = Select(title="User Type:", value="All", options=["All", "Subscriber", "Customer"]) selectGender = Select(title="Gender:", value="All", options=["All", "Male", "Female", "Unknown"]) sliderAge = Slider(start=0, end=MAX_AGE, value=0, step=1, title="Age LEQ") startDP = DatePicker(title="Start Date:", min_date=dateRange[0] ,max_date=dateRange[1], value=dateRange[0]) endDP = DatePicker(title="End Date:", min_date=dateRange[0] ,max_date=dateRange[1], value=dateRange[1]) binSize = TextInput(value="15", title="Bin Size (Days):") AddButton = Toggle(label="Add", type="success") DeleteButton = Toggle(label="Delete Selected", type="success") columns = [ TableColumn(field="ss", title="SS"), TableColumn(field="es", title="ES"), TableColumn(field="ut", title="User Type"), TableColumn(field="a", title="Age LEQ"), TableColumn(field="g", title="Sex") ] data_table = DataTable(source=source, columns=columns, width=400, row_headers=False, selectable='checkbox') model = dict(source=source, selectSS = selectSS, selectES = selectES, startDP = startDP, endDP = endDP, binSize = binSize,selectUT=selectUT,selectGender=selectGender,sliderAge=sliderAge, dt = data_table) addCallback = CustomJS(args=model, code=""" //alert("callback"); var startStation = selectSS.get('value'); var endStation = selectES.get('value'); var startDate = startDP.get('value'); if ( typeof(startDate) !== "number") startDate = startDate.getTime(); var endDate = endDP.get('value'); if ( typeof(endDate) !== "number") endDate = endDate.getTime(); var binSize = binSize.get('value'); var gender = selectGender.get('value'); var userType = selectUT.get('value'); var age = sliderAge.get('value'); //alert(age); //alert(startStation + " " + endStation + " " + startDate + " " + endDate + " " + binSize); var xmlhttp; xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == XMLHttpRequest.DONE ) { if(xmlhttp.status == 200){ var data = source.get('data'); var result = JSON.parse(xmlhttp.responseText); var temp=[]; for(var date in result.x) { temp.push(new Date(result.x[date])); } data['xs'].push(temp); data['ys'].push(result.y); data['ss'].push(startStation); data['es'].push(endStation); data['ut'].push(userType); data['g'].push(gender); data['a'].push(age); data['color'].push('blue'); data['line_width'].push(2); source.trigger('change'); dt.trigger('change'); } else if(xmlhttp.status == 400) { alert(400); } else { alert(xmlhttp.status); } } }; var params = {ss:startStation, es:endStation, sd:startDate, ed:endDate, bs: binSize, g:gender, ut:userType, age:age}; url = "/histogram?" + jQuery.param( params ); xmlhttp.open("GET", url, true); xmlhttp.send(); """) deleteCallBack = CustomJS(args=dict(source=source, dt= data_table), code=""" var indices = source.get('selected')['1d'].indices; if(indices.length != 0){ indices.sort(); var data = source.get('data'); var counter = 0; var i = 0; var key = 0; var index = 0; for(i in indices) { index = indices[i]; index -= counter; for(key in data) { data[key].splice(index, 1); } counter += 1; } source.trigger('change'); dt.trigger('change'); } """) AddButton.callback = addCallback DeleteButton.callback = deleteCallBack; plot = Figure(title="Number Of Trips Over Time", x_axis_label='Time', y_axis_label='Number of trips', plot_width=750, plot_height=400, x_axis_type="datetime") plot.multi_line('xs', 'ys', source=source, line_width='line_width', line_alpha=0.9, line_color='color') l2 = vform(plot, hplot(startDP, endDP), binSize) l3 = vform(selectSS, selectES,selectUT,selectGender,sliderAge, hplot(AddButton, DeleteButton), data_table) layout = hplot(l2, l3) script, div = components(layout) html = readHtmlFile(fileName) html = insertScriptIntoHeader(html, script) html = appendElementContent(html, div, "div", "bokehContent") return html
title="Error Plots", tools="crosshair,pan,reset,save,wheel_zoom", x_range=[start_m - 0.5, end_m + 0.5], y_range=[0, 2000], ) error_land_data = ColumnDataSource(data=dict( x=[m], y=[utils.compute_error(x, y, m)], )) error_plot.line('x', 'y', source=error_land_data, color='red') # Set up widgets set_m = Slider(title='m', value=m, start=start_m, step=0.1, end=end_m) animate_button = Button(label='► Play', button_type='primary') cluttered_button = Toggle(label='Filter points', button_type='primary', width=200) button_draw_error = Toggle(label='Draw Errors', button_type='primary', width=200) reset_button = Button(label='Reset errors') # Setup callbacks def change_m(attr, old, new): new_m = set_m.value x_line = np.arange(-1, 5.1, 0.1) y_line = new_m * x_line line_data.data = dict(x=x_line, y=y_line)
def create(palm): connected = False current_message = None stream_t = 0 doc = curdoc() # Streaked and reference waveforms plot waveform_plot = Plot( title=Title(text="eTOF waveforms"), x_range=DataRange1d(), y_range=DataRange1d(), plot_height=PLOT_CANVAS_HEIGHT, plot_width=PLOT_CANVAS_WIDTH, toolbar_location="right", ) # ---- tools waveform_plot.toolbar.logo = None waveform_plot.add_tools(PanTool(), BoxZoomTool(), WheelZoomTool(), ResetTool()) # ---- axes waveform_plot.add_layout(LinearAxis(axis_label="Photon energy, eV"), place="below") waveform_plot.add_layout(LinearAxis(axis_label="Intensity", major_label_orientation="vertical"), place="left") # ---- grid lines waveform_plot.add_layout(Grid(dimension=0, ticker=BasicTicker())) waveform_plot.add_layout(Grid(dimension=1, ticker=BasicTicker())) # ---- line glyphs waveform_source = ColumnDataSource( dict(x_str=[], y_str=[], x_ref=[], y_ref=[])) waveform_ref_line = waveform_plot.add_glyph( waveform_source, Line(x="x_ref", y="y_ref", line_color="blue")) waveform_str_line = waveform_plot.add_glyph( waveform_source, Line(x="x_str", y="y_str", line_color="red")) # ---- legend waveform_plot.add_layout( Legend(items=[("reference", [waveform_ref_line]), ("streaked", [waveform_str_line])])) waveform_plot.legend.click_policy = "hide" # Cross-correlation plot xcorr_plot = Plot( title=Title(text="Waveforms cross-correlation"), x_range=DataRange1d(), y_range=DataRange1d(), plot_height=PLOT_CANVAS_HEIGHT, plot_width=PLOT_CANVAS_WIDTH, toolbar_location="right", ) # ---- tools xcorr_plot.toolbar.logo = None xcorr_plot.add_tools(PanTool(), BoxZoomTool(), WheelZoomTool(), ResetTool()) # ---- axes xcorr_plot.add_layout(LinearAxis(axis_label="Energy shift, eV"), place="below") xcorr_plot.add_layout(LinearAxis(axis_label="Cross-correlation", major_label_orientation="vertical"), place="left") # ---- grid lines xcorr_plot.add_layout(Grid(dimension=0, ticker=BasicTicker())) xcorr_plot.add_layout(Grid(dimension=1, ticker=BasicTicker())) # ---- line glyphs xcorr_source = ColumnDataSource(dict(lags=[], xcorr=[])) xcorr_plot.add_glyph(xcorr_source, Line(x="lags", y="xcorr", line_color="purple")) # ---- vertical span xcorr_center_span = Span(location=0, dimension="height") xcorr_plot.add_layout(xcorr_center_span) # Delays plot pulse_delay_plot = Plot( title=Title(text="Pulse delays"), x_range=DataRange1d(), y_range=DataRange1d(), plot_height=PLOT_CANVAS_HEIGHT, plot_width=PLOT_CANVAS_WIDTH, toolbar_location="right", ) # ---- tools pulse_delay_plot.toolbar.logo = None pulse_delay_plot.add_tools(PanTool(), BoxZoomTool(), WheelZoomTool(), ResetTool()) # ---- axes pulse_delay_plot.add_layout(LinearAxis(axis_label="Pulse number"), place="below") pulse_delay_plot.add_layout( LinearAxis(axis_label="Pulse delay (uncalib), eV", major_label_orientation="vertical"), place="left", ) # ---- grid lines pulse_delay_plot.add_layout(Grid(dimension=0, ticker=BasicTicker())) pulse_delay_plot.add_layout(Grid(dimension=1, ticker=BasicTicker())) # ---- line glyphs pulse_delay_source = ColumnDataSource(dict(x=[], y=[])) pulse_delay_plot.add_glyph(pulse_delay_source, Line(x="x", y="y", line_color="steelblue")) # Pulse lengths plot pulse_length_plot = Plot( title=Title(text="Pulse lengths"), x_range=DataRange1d(), y_range=DataRange1d(), plot_height=PLOT_CANVAS_HEIGHT, plot_width=PLOT_CANVAS_WIDTH, toolbar_location="right", ) # ---- tools pulse_length_plot.toolbar.logo = None pulse_length_plot.add_tools(PanTool(), BoxZoomTool(), WheelZoomTool(), ResetTool()) # ---- axes pulse_length_plot.add_layout(LinearAxis(axis_label="Pulse number"), place="below") pulse_length_plot.add_layout( LinearAxis(axis_label="Pulse length (uncalib), eV", major_label_orientation="vertical"), place="left", ) # ---- grid lines pulse_length_plot.add_layout(Grid(dimension=0, ticker=BasicTicker())) pulse_length_plot.add_layout(Grid(dimension=1, ticker=BasicTicker())) # ---- line glyphs pulse_length_source = ColumnDataSource(dict(x=[], y=[])) pulse_length_plot.add_glyph(pulse_length_source, Line(x="x", y="y", line_color="steelblue")) # Image buffer slider def buffer_slider_callback(_attr, _old, new): message = receiver.data_buffer[new] doc.add_next_tick_callback(partial(update, message=message)) buffer_slider = Slider( start=0, end=59, value=0, step=1, title="Buffered Image", callback_policy="throttle", callback_throttle=500, ) buffer_slider.on_change("value", buffer_slider_callback) # Connect toggle button def connect_toggle_callback(state): nonlocal connected if state: connected = True connect_toggle.label = "Connecting" connect_toggle.button_type = "default" else: connected = False connect_toggle.label = "Connect" connect_toggle.button_type = "default" connect_toggle = Toggle(label="Connect", button_type="default", width=250) connect_toggle.on_click(connect_toggle_callback) # Intensity stream reset button def reset_button_callback(): nonlocal stream_t stream_t = 1 # keep the latest point in order to prevent full axis reset reset_button = Button(label="Reset", button_type="default", width=250) reset_button.on_click(reset_button_callback) # Stream update coroutine async def update(message): nonlocal stream_t if connected and receiver.state == "receiving": y_ref = message[receiver.reference].value[np.newaxis, :] y_str = message[receiver.streaked].value[np.newaxis, :] delay, length, debug_data = palm.process({ "0": y_ref, "1": y_str }, debug=True) prep_data, lags, corr_res_uncut, _ = debug_data waveform_source.data.update( x_str=palm.energy_range, y_str=prep_data["1"][0, :], x_ref=palm.energy_range, y_ref=prep_data["0"][0, :], ) xcorr_source.data.update(lags=lags, xcorr=corr_res_uncut[0, :]) xcorr_center_span.location = delay[0] pulse_delay_source.stream({ "x": [stream_t], "y": [delay] }, rollover=120) pulse_length_source.stream({ "x": [stream_t], "y": [length] }, rollover=120) stream_t += 1 # Periodic callback to fetch data from receiver async def internal_periodic_callback(): nonlocal current_message if waveform_plot.inner_width is None: # wait for the initialization to finish, thus skip this periodic callback return if connected: if receiver.state == "polling": connect_toggle.label = "Polling" connect_toggle.button_type = "warning" elif receiver.state == "stopped": connect_toggle.label = "Not available" connect_toggle.button_type = "danger" elif receiver.state == "receiving": connect_toggle.label = "Receiving" connect_toggle.button_type = "success" # Set slider to the right-most position if len(receiver.data_buffer) > 1: buffer_slider.end = len(receiver.data_buffer) - 1 buffer_slider.value = len(receiver.data_buffer) - 1 if receiver.data_buffer: current_message = receiver.data_buffer[-1] doc.add_next_tick_callback(partial(update, message=current_message)) doc.add_periodic_callback(internal_periodic_callback, 1000) # assemble tab_layout = column( row( column(waveform_plot, xcorr_plot), Spacer(width=30), column(buffer_slider, row(connect_toggle, reset_button)), ), row(pulse_delay_plot, Spacer(width=10), pulse_length_plot), ) return Panel(child=tab_layout, title="Stream")
buttons = { 'restart': Button(label='Restart', align='center', width=165), 'stop': Button(label='Stop', align='center', button_type='danger', width=165) } buttons['restart'].on_click(restart) buttons['stop'].js_on_click(CustomJS(code='window.close()')) buttons['stop'].on_click(lambda: sys.exit()) toggles = { 'pause': Toggle(label='Pause', align='center', button_type='success', width=165) } toggles['pause'].on_click(partial(callback_pause, ref=toggles['pause'])) selectors = { 'activation': { 'Activate': lambda x: (x**3 + 50 * x**2 + 10 * x) / 1000 }, 'optimizer': { 'Activate': optimizers.SGD() }, 'eta_modifier': { 'Activate': 1 } }
# linear regression object regr = linear_model.LinearRegression() # fit linear model regr.fit(X, Y) # make predictions pred = regr.predict(X) # plot with regression line regr_plot = figure(plot_width=500, plot_height=300) regr_plot.scatter(x, y, size=10) regr_line = regr_plot.line(x, pred.flatten(), line_color='red') toggle_button = Toggle(label='line of best fit', button_type='success', active=True) toggle_button.js_link('active', regr_line, 'visible') show(layout([regr_plot], [toggle_button])) Loading BokehJS ... In [87]: # slider.js_link? Interactive Widgets with ipywidgets In [88]: seattle_weather['year'] = pd.DatetimeIndex(seattle_weather['date']).year seattle_weather.tail() Out[88]: date precipitation temp_max temp_min wind weather year 1456 2015-12-27 8.6 4.4 1.7 2.9 fog 2015 1457 2015-12-28 1.5 5.0 1.7 1.3 fog 2015 1458 2015-12-29 0.0 7.2 0.6 2.6 fog 2015
y_offset="y_off", text="text", text_font_size="64px", text_align="center", ) grid.add_glyph(letter_src, letters) # Set up widgets time_slider = Slider(start=1, end=10, value=3, step=1, title="Duration (minutes)") special_toggle = Toggle( label="""Include bonus die? \n [He, An, In, Er, Th, Qu]""", button_type="warning", ) angle_toggle = Toggle(label="Rotate dice in random direction?", button_type="primary") shuffle_button = Button(label="Shake!", button_type="primary") start_button = Button(label="Start timer", button_type="success") stop_button = Button(label="Stop timer", button_type="danger") timer = Div( text=f"""Timer: <br> 0:00""", style={ "font-size": "400%", "color": "black", "text-align": "center" }, )
def update(): # Compute new y values: y y = np.sin(x) + np.random.random(N) # Update the ColumnDataSource data dictionary source.data = {'x': x, 'y': y} # Add the update callback to the button button.on_click(update) # Create layout and add to current document layout = column(widgetbox(button), plot) curdoc().add_root(layout) # Import CheckboxGroup, RadioGroup, Toggle from bokeh.models from bokeh.models import CheckboxGroup, RadioGroup, Toggle # Add a Toggle: toggle toggle = Toggle(button_type="success", label="Toggle button") # Add a CheckboxGroup: checkbox checkbox = CheckboxGroup(labels=['Option 1', 'Option 2', 'Option 3']) # Add a RadioGroup: radio radio = RadioGroup(labels=['Option 1', 'Option 2', 'Option 3']) # Add widgetbox(toggle, checkbox, radio) to the current document curdoc().add_root(widgetbox(toggle, checkbox, radio))
# Setup color bar mapper = LinearColorMapper(palette=colors, low=-1, high=1) color_bar = ColorBar(color_mapper=mapper, location=(0, 0)) p3.add_layout(color_bar, 'right') #Spinner GUI spinner = Spinner(title="Size", low=0, high=4, step=0.1, value=1, width=80) #spinner.js_link('value', points.glyph, 'radius') #Dropdown menu GUI menu = [("Item 1", "item_1"), ("Item 2", "item_2"), None, ("Item 3", "item_3")] dropdown = Dropdown(label="Dropdown button", button_type="warning", menu=menu) dropdown.js_on_event("menu_item_click", CustomJS(code="console.log('dropdown: ' + this.item, this.toString())")) #Toggle button GUI toggle = Toggle(label="Button", button_type="success") toggle.js_on_click(CustomJS(code=""" console.log('toggle: active=' + this.active, this.toString()) """)) OPTIONS = ["Attribute1", "Attribute2", "Attribute3", "Attribute4"] multi_choice = MultiChoice(value=["foo", "baz"], options=OPTIONS) multi_choice.js_on_change("value", CustomJS(code=""" console.log('multi_choice: value=' + this.value, this.toString()) """)) #GUI Left column controls = [dropdown, spinner, toggle] inputs = column(*controls, sizing_mode='fixed', height=250, width=350) l1 = layout([[inputs, p1]], sizing_mode='fixed', height=600, width=150) l2 = layout([[inputs, p2]], sizing_mode='fixed', height=600, width=150) l3 = layout([[inputs, p3]], sizing_mode='fixed', height=600, width=150)
def sky_plot(db_file, pointings): ''' plot pointings in pulsar catalog pointed to by db_file. plot pointings in database retrieved in pointings parameter. THIS IS A DIRTY IMPLEMENTATION. WE SHOULD CLEAN THIS UP. ''' raj, decj = load_psr_pos(db_file) source = ColumnDataSource(data=dict(x=raj, y=decj)) # unpack pointings taken from database dates, ras, decs = zip(*pointings) cover_raj, cover_decj = get_sky_cover_area(ras, decs, 2.5 / 60.0) cover_raj *= np.pi / 180. cover_decj *= np.pi / 180. projects = ['P2030'] * len(cover_decj) source2 = ColumnDataSource(data=dict( x=cover_raj.tolist(), y=cover_decj.tolist(), project=projects, date=dates, )) TOOLS = "pan,wheel_zoom,reset,hover,hover,save" plot = figure(x_range=(-1, 7), y_range=(-2, 2), tools=TOOLS, toolbar_location="left", title="ANTF Catalogue", x_axis_label='RAJ (rad)', y_axis_label='DECJ (rad)') box_color = "navy" box_alpha = 0.4 starplot = plot.asterisk('x', 'y', source=source, size=5) cover_box = plot.patches('x', 'y', source=source2, line_width=3, color=box_color, fill_alpha=box_alpha) pulsar_tooltips = [ ("RA, DEC : ", "($x, $y)"), ] obs_tooltips = [ ("Project", "@project"), ("Date", "@date"), ("Time", "@time"), ("Observer", "@name"), ("RA, DEC : ", "($x, $y)"), ] hover = plot.select(dict(type=HoverTool)) hover[0].renderers = [starplot] hover[0].tooltips = pulsar_tooltips hover[1].renderers = [cover_box] hover[1].tooltips = obs_tooltips code2 = '''\ object.visible = toggle.active ''' callback_star = CustomJS.from_coffeescript(code=code2, args={}) toggle1 = Toggle(label="Pulsars", button_type="success", callback=callback_star) callback_star.args = {'toggle': toggle1, 'object': starplot} callback_box = CustomJS.from_coffeescript(code=code2, args={}) toggle2 = Toggle(label="Observation Scans", button_type="success", callback=callback_box) callback_box.args = {'toggle': toggle2, 'object': cover_box} return [plot, toggle1, toggle2]
class Dashboard: """Explorepy dashboard class""" def __init__(self, explore=None, mode='signal'): """ Args: stream_processor (explorepy.stream_processor.StreamProcessor): Stream processor object """ self.explore = explore self.stream_processor = self.explore.stream_processor self.n_chan = self.stream_processor.device_info['adc_mask'].count(1) self.y_unit = DEFAULT_SCALE self.offsets = np.arange(1, self.n_chan + 1)[:, np.newaxis].astype(float) self.chan_key_list = [ CHAN_LIST[i] for i, mask in enumerate( reversed(self.stream_processor.device_info['adc_mask'])) if mask == 1 ] self.exg_mode = 'EEG' self.rr_estimator = None self.win_length = WIN_LENGTH self.mode = mode self.exg_fs = self.stream_processor.device_info['sampling_rate'] # Init ExG data source exg_temp = np.zeros((self.n_chan, 2)) exg_temp[:, 0] = self.offsets[:, 0] exg_temp[:, 1] = np.nan init_data = dict(zip(self.chan_key_list, exg_temp)) self._exg_source_orig = ColumnDataSource(data=init_data) init_data['t'] = np.array([0., 0.]) self._exg_source_ds = ColumnDataSource( data=init_data) # Downsampled ExG data for visualization purposes # Init ECG R-peak source init_data = dict( zip(['r_peak', 't'], [ np.array([None], dtype=np.double), np.array([None], dtype=np.double) ])) self._r_peak_source = ColumnDataSource(data=init_data) # Init marker source init_data = dict( zip(['marker', 't'], [ np.array([None], dtype=np.double), np.array([None], dtype=np.double) ])) self._marker_source = ColumnDataSource(data=init_data) # Init ORN data source init_data = dict(zip(ORN_LIST, np.zeros((9, 1)))) init_data['t'] = [0.] self._orn_source = ColumnDataSource(data=init_data) # Init table sources self._heart_rate_source = ColumnDataSource(data={'heart_rate': ['NA']}) self._firmware_source = ColumnDataSource( data={ 'firmware_version': [self.stream_processor.device_info['firmware_version']] }) self._battery_source = ColumnDataSource(data={'battery': ['NA']}) self.temperature_source = ColumnDataSource( data={'temperature': ['NA']}) self.light_source = ColumnDataSource(data={'light': ['NA']}) self.battery_percent_list = [] self.server = None # Init fft data source init_data = dict(zip(self.chan_key_list, np.zeros((self.n_chan, 1)))) init_data['f'] = np.array([0.]) self.fft_source = ColumnDataSource(data=init_data) # Init impedance measurement source init_data = { 'channel': self.chan_key_list, 'impedance': ['NA' for i in range(self.n_chan)], 'row': ['1' for i in range(self.n_chan)], 'color': ['black' for i in range(self.n_chan)] } self.imp_source = ColumnDataSource(data=init_data) # Init timer source self._timer_source = ColumnDataSource(data={'timer': ['00:00:00']}) def start_server(self): """Start bokeh server""" validate(False) self.server = Server({'/': self._init_doc}, num_procs=1) self.server.start() def start_loop(self): """Start io loop and show the dashboard""" self.server.io_loop.add_callback(self.server.show, "/") self.server.io_loop.start() def exg_callback(self, packet): """ Update ExG data in the visualization Args: packet (explorepy.packet.EEG): Received ExG packet """ time_vector, exg = packet.get_data(self.exg_fs) self._exg_source_orig.stream(dict(zip(self.chan_key_list, exg)), rollover=int(self.exg_fs * self.win_length)) # Downsampling exg = exg[:, ::int(self.exg_fs / EXG_VIS_SRATE)] time_vector = time_vector[::int(self.exg_fs / EXG_VIS_SRATE)] # Update ExG unit exg = self.offsets + exg / self.y_unit new_data = dict(zip(self.chan_key_list, exg)) new_data['t'] = time_vector self.doc.add_next_tick_callback( partial(self._update_exg, new_data=new_data)) def orn_callback(self, packet): """Update orientation data Args: packet (explorepy.packet.Orientation): Orientation packet """ if self.tabs.active != 1: return timestamp, orn_data = packet.get_data() new_data = dict(zip(ORN_LIST, np.array(orn_data)[:, np.newaxis])) new_data['t'] = timestamp self.doc.add_next_tick_callback( partial(self._update_orn, new_data=new_data)) def info_callback(self, packet): """Update device information in the dashboard Args: packet (explorepy.packet.Environment): Environment/DeviceInfo packet """ new_info = packet.get_data() for key in new_info.keys(): data = {key: new_info[key]} if key == 'firmware_version': self.doc.add_next_tick_callback( partial(self._update_fw_version, new_data=data)) elif key == 'battery': self.battery_percent_list.append(new_info[key][0]) if len(self.battery_percent_list) > BATTERY_N_MOVING_AVERAGE: del self.battery_percent_list[0] value = int(np.mean(self.battery_percent_list) / 5) * 5 if value < 1: value = 1 self.doc.add_next_tick_callback( partial(self._update_battery, new_data={key: [value]})) elif key == 'temperature': self.doc.add_next_tick_callback( partial(self._update_temperature, new_data=data)) elif key == 'light': data[key] = [int(data[key][0])] self.doc.add_next_tick_callback( partial(self._update_light, new_data=data)) else: print("Warning: There is no field named: " + key) def marker_callback(self, packet): """Update markers Args: packet (explorepy.packet.EventMarker): Event marker packet """ if self.mode == "impedance": return timestamp, _ = packet.get_data() new_data = dict( zip(['marker', 't', 'code'], [ np.array([0.01, self.n_chan + 0.99, None], dtype=np.double), np.array([timestamp[0], timestamp[0], None], dtype=np.double) ])) self.doc.add_next_tick_callback( partial(self._update_marker, new_data=new_data)) def impedance_callback(self, packet): """Update impedances Args: packet (explorepy.packet.EEG): ExG packet """ if self.mode == "impedance": imp = packet.get_impedances() color = [] imp_status = [] for value in imp: if value > 500: color.append("black") imp_status.append("Open") elif value > 100: color.append("red") imp_status.append(str(round(value, 0)) + " K\u03A9") elif value > 50: color.append("orange") imp_status.append(str(round(value, 0)) + " K\u03A9") elif value > 10: color.append("yellow") imp_status.append(str(round(value, 0)) + " K\u03A9") elif value > 5: imp_status.append(str(round(value, 0)) + " K\u03A9") color.append("green") else: color.append("green") imp_status.append( "<5K\u03A9" ) # As the ADS is not precise in low values. data = { "impedance": imp_status, 'channel': self.chan_key_list, 'row': ['1' for i in range(self.n_chan)], 'color': color } self.doc.add_next_tick_callback( partial(self._update_imp, new_data=data)) else: raise RuntimeError( "Trying to compute impedances while the dashboard is not in Impedance mode!" ) @gen.coroutine @without_property_validation def _update_exg(self, new_data): self._exg_source_ds.stream(new_data, rollover=int(2 * EXG_VIS_SRATE * WIN_LENGTH)) @gen.coroutine @without_property_validation def _update_orn(self, new_data): self._orn_source.stream(new_data, rollover=int(2 * WIN_LENGTH * ORN_SRATE)) @gen.coroutine @without_property_validation def _update_fw_version(self, new_data): self._firmware_source.stream(new_data, rollover=1) @gen.coroutine @without_property_validation def _update_battery(self, new_data): self._battery_source.stream(new_data, rollover=1) @gen.coroutine @without_property_validation def _update_temperature(self, new_data): self.temperature_source.stream(new_data, rollover=1) @gen.coroutine @without_property_validation def _update_light(self, new_data): self.light_source.stream(new_data, rollover=1) @gen.coroutine @without_property_validation def _update_marker(self, new_data): self._marker_source.stream(new_data=new_data, rollover=100) @gen.coroutine @without_property_validation def _update_imp(self, new_data): self.imp_source.stream(new_data, rollover=self.n_chan) @gen.coroutine @without_property_validation def _update_fft(self): """ Update spectral frequency analysis plot""" # Check if the tab is active and if EEG mode is active if (self.tabs.active != 2) or (self.exg_mode != 'EEG'): return exg_data = np.array( [self._exg_source_orig.data[key] for key in self.chan_key_list]) if exg_data.shape[1] < self.exg_fs * 5: return fft_content, freq = get_fft(exg_data, self.exg_fs) data = dict(zip(self.chan_key_list, fft_content)) data['f'] = freq self.fft_source.data = data @gen.coroutine @without_property_validation def _update_heart_rate(self): """Detect R-peaks and update the plot and heart rate""" if self.exg_mode == 'EEG': self._heart_rate_source.stream({'heart_rate': ['NA']}, rollover=1) return if CHAN_LIST[0] not in self.chan_key_list: print( 'WARNING: Heart rate estimation works only when channel 1 is enabled.' ) return if self.rr_estimator is None: self.rr_estimator = HeartRateEstimator(fs=self.exg_fs) # Init R-peaks plot self.exg_plot.circle(x='t', y='r_peak', source=self._r_peak_source, fill_color="red", size=8) ecg_data = ( np.array(self._exg_source_ds.data['Ch1'])[-2 * EXG_VIS_SRATE:] - self.offsets[0]) * self.y_unit time_vector = np.array(self._exg_source_ds.data['t'])[-2 * EXG_VIS_SRATE:] # Check if the peak2peak value is bigger than threshold if (np.ptp(ecg_data) < V_TH[0]) or (np.ptp(ecg_data) > V_TH[1]): print( "WARNING: P2P value larger or less than threshold. Cannot compute heart rate!" ) return peaks_time, peaks_val = self.rr_estimator.estimate( ecg_data, time_vector) peaks_val = (np.array(peaks_val) / self.y_unit) + self.offsets[0] if peaks_time: data = dict(zip(['r_peak', 't'], [peaks_val, peaks_time])) self._r_peak_source.stream(data, rollover=50) # Update heart rate cell estimated_heart_rate = self.rr_estimator.heart_rate data = {'heart_rate': [estimated_heart_rate]} self._heart_rate_source.stream(data, rollover=1) @gen.coroutine @without_property_validation def _change_scale(self, attr, old, new): """Change y-scale of ExG plot""" new, old = SCALE_MENU[new], SCALE_MENU[old] old_unit = 10**(-old) self.y_unit = 10**(-new) for chan, value in self._exg_source_ds.data.items(): if chan in self.chan_key_list: temp_offset = self.offsets[self.chan_key_list.index(chan)] self._exg_source_ds.data[chan] = (value - temp_offset) * ( old_unit / self.y_unit) + temp_offset self._r_peak_source.data['r_peak'] = (np.array(self._r_peak_source.data['r_peak']) - self.offsets[0]) * \ (old_unit / self.y_unit) + self.offsets[0] @gen.coroutine @without_property_validation def _change_t_range(self, attr, old, new): """Change time range""" self._set_t_range(TIME_RANGE_MENU[new]) @gen.coroutine def _change_mode(self, attr, old, new): """Set EEG or ECG mode""" self.exg_mode = new def _init_doc(self, doc): self.doc = doc self.doc.title = "Explore Dashboard" with open( os.path.join(os.path.dirname(__file__), 'templates', 'index.html')) as f: index_template = Template(f.read()) doc.template = index_template self.doc.theme = Theme( os.path.join(os.path.dirname(__file__), 'theme.yaml')) self._init_plots() m_widgetbox = self._init_controls() # Create tabs if self.mode == "signal": exg_tab = Panel(child=self.exg_plot, title="ExG Signal") orn_tab = Panel(child=column( [self.acc_plot, self.gyro_plot, self.mag_plot]), title="Orientation") fft_tab = Panel(child=self.fft_plot, title="Spectral analysis") self.tabs = Tabs(tabs=[exg_tab, orn_tab, fft_tab], width=600) self.recorder_widget = self._init_recorder() elif self.mode == "impedance": imp_tab = Panel(child=self.imp_plot, title="Impedance") self.tabs = Tabs(tabs=[imp_tab], width=600) banner = Div( text= """<font size="5.5">Explorepy Dashboard</font> <a href="https://www.mentalab.co"><img src= "https://images.squarespace-cdn.com/content/5428308ae4b0701411ea8aaf/1505653866447-R24N86G5X1HFZCD7KBWS/ Mentalab%2C+Name+copy.png?format=1500w&content-type=image%2Fpng" alt="Mentalab" width="225" height="39">""", width=1500, height=50, css_classes=["banner"], align='center') if self.mode == 'signal': self.doc.add_root( column( banner, Spacer(width=600, height=20), row([ column(m_widgetbox, self.recorder_widget), Spacer(width=25, height=500), self.tabs, Spacer(width=700, height=600) ]))) elif self.mode == 'impedance': self.doc.add_root( column( banner, Spacer(width=600, height=20), row([m_widgetbox, Spacer(width=25, height=500), self.tabs]))) self.doc.add_periodic_callback(self._update_fft, 2000) self.doc.add_periodic_callback(self._update_heart_rate, 2000) if self.stream_processor: self.stream_processor.subscribe(topic=TOPICS.filtered_ExG, callback=self.exg_callback) self.stream_processor.subscribe(topic=TOPICS.raw_orn, callback=self.orn_callback) self.stream_processor.subscribe(topic=TOPICS.device_info, callback=self.info_callback) self.stream_processor.subscribe(topic=TOPICS.marker, callback=self.marker_callback) self.stream_processor.subscribe(topic=TOPICS.env, callback=self.info_callback) self.stream_processor.subscribe(topic=TOPICS.imp, callback=self.impedance_callback) def _init_plots(self): """Initialize all plots in the dashboard""" self.exg_plot = figure(y_range=(0.01, self.n_chan + 1 - 0.01), y_axis_label='Voltage', x_axis_label='Time (s)', title="ExG signal", plot_height=600, plot_width=1270, y_minor_ticks=int(10), tools=[ResetTool()], active_scroll=None, active_drag=None, active_inspect=None, active_tap=None) self.mag_plot = figure(y_axis_label='Magnetometer [mgauss/LSB]', x_axis_label='Time (s)', plot_height=230, plot_width=1270, tools=[ResetTool()], active_scroll=None, active_drag=None, active_inspect=None, active_tap=None) self.acc_plot = figure(y_axis_label='Accelerometer [mg/LSB]', plot_height=190, plot_width=1270, tools=[ResetTool()], active_scroll=None, active_drag=None, active_inspect=None, active_tap=None) self.acc_plot.xaxis.visible = False self.gyro_plot = figure(y_axis_label='Gyroscope [mdps/LSB]', plot_height=190, plot_width=1270, tools=[ResetTool()], active_scroll=None, active_drag=None, active_inspect=None, active_tap=None) self.gyro_plot.xaxis.visible = False self.fft_plot = figure(y_axis_label='Amplitude (uV)', x_axis_label='Frequency (Hz)', title="FFT", x_range=(0, 70), plot_height=600, plot_width=1270, y_axis_type="log") self.imp_plot = self._init_imp_plot() # Set yaxis properties self.exg_plot.yaxis.ticker = SingleIntervalTicker(interval=1, num_minor_ticks=0) # Initial plot line for i in range(self.n_chan): self.exg_plot.line(x='t', y=self.chan_key_list[i], source=self._exg_source_ds, line_width=1.0, alpha=.9, line_color="#42C4F7") self.fft_plot.line(x='f', y=self.chan_key_list[i], source=self.fft_source, legend_label=self.chan_key_list[i] + " ", line_width=1.0, alpha=.9, line_color=FFT_COLORS[i]) self.fft_plot.yaxis.axis_label_text_font_style = 'normal' self.exg_plot.line(x='t', y='marker', source=self._marker_source, line_width=1, alpha=.8, line_color='#7AB904', line_dash="4 4") for i in range(3): self.acc_plot.line(x='t', y=ORN_LIST[i], source=self._orn_source, legend_label=ORN_LIST[i] + " ", line_width=1.5, line_color=LINE_COLORS[i], alpha=.9) self.gyro_plot.line(x='t', y=ORN_LIST[i + 3], source=self._orn_source, legend_label=ORN_LIST[i + 3] + " ", line_width=1.5, line_color=LINE_COLORS[i], alpha=.9) self.mag_plot.line(x='t', y=ORN_LIST[i + 6], source=self._orn_source, legend_label=ORN_LIST[i + 6] + " ", line_width=1.5, line_color=LINE_COLORS[i], alpha=.9) # Set x_range self.plot_list = [ self.exg_plot, self.acc_plot, self.gyro_plot, self.mag_plot ] self._set_t_range(WIN_LENGTH) # Set the formatting of yaxis ticks' labels self.exg_plot.yaxis.major_label_overrides = dict( zip(range(1, self.n_chan + 1), self.chan_key_list)) for plot in self.plot_list: plot.toolbar.autohide = True plot.yaxis.axis_label_text_font_style = 'normal' if len(plot.legend) != 0: plot.legend.location = "bottom_left" plot.legend.orientation = "horizontal" plot.legend.padding = 2 def _init_imp_plot(self): plot = figure(plot_width=600, plot_height=200, x_range=self.chan_key_list[0:self.n_chan], y_range=[str(1)], toolbar_location=None) plot.circle(x='channel', y="row", radius=.3, source=self.imp_source, fill_alpha=0.6, color="color", line_color='color', line_width=2) text_props = { "source": self.imp_source, "text_align": "center", "text_color": "white", "text_baseline": "middle", "text_font": "helvetica", "text_font_style": "bold" } x = dodge("channel", -0.1, range=plot.x_range) plot.text(x=x, y=dodge('row', -.4, range=plot.y_range), text="impedance", **text_props).glyph.text_font_size = "10pt" plot.text(x=x, y=dodge('row', -.3, range=plot.y_range), text="channel", **text_props).glyph.text_font_size = "12pt" plot.outline_line_color = None plot.grid.grid_line_color = None plot.axis.axis_line_color = None plot.axis.major_tick_line_color = None plot.axis.major_label_standoff = 0 plot.axis.visible = False return plot def _init_controls(self): """Initialize all controls in the dashboard""" # EEG/ECG Radio button self.mode_control = widgets.Select(title="Signal", value='EEG', options=MODE_LIST, width=210) self.mode_control.on_change('value', self._change_mode) self.t_range = widgets.Select(title="Time window", value="10 s", options=list(TIME_RANGE_MENU.keys()), width=210) self.t_range.on_change('value', self._change_t_range) self.y_scale = widgets.Select(title="Y-axis Scale", value="1 mV", options=list(SCALE_MENU.keys()), width=210) self.y_scale.on_change('value', self._change_scale) # Create device info tables columns = [ widgets.TableColumn(field='heart_rate', title="Heart Rate (bpm)") ] self.heart_rate = widgets.DataTable(source=self._heart_rate_source, index_position=None, sortable=False, reorderable=False, columns=columns, width=210, height=50) columns = [ widgets.TableColumn(field='firmware_version', title="Firmware Version") ] self.firmware = widgets.DataTable(source=self._firmware_source, index_position=None, sortable=False, reorderable=False, columns=columns, width=210, height=50) columns = [widgets.TableColumn(field='battery', title="Battery (%)")] self.battery = widgets.DataTable(source=self._battery_source, index_position=None, sortable=False, reorderable=False, columns=columns, width=210, height=50) columns = [ widgets.TableColumn(field='temperature', title="Device temperature (C)") ] self.temperature = widgets.DataTable(source=self.temperature_source, index_position=None, sortable=False, reorderable=False, columns=columns, width=210, height=50) columns = [widgets.TableColumn(field='light', title="Light (Lux)")] self.light = widgets.DataTable(source=self.light_source, index_position=None, sortable=False, reorderable=False, columns=columns, width=210, height=50) # Add widgets to the doc widget_box = widgetbox([ Spacer(width=210, height=30), self.mode_control, self.y_scale, self.t_range, self.heart_rate, self.battery, self.temperature, self.firmware ], width=220) return widget_box def _init_recorder(self): self.rec_button = Toggle(label=u"\u25CF Record", button_type="default", active=False, width=210) self.file_name_widget = TextInput(value="test_file", title="File name:", width=210) self.file_type_widget = RadioGroup(labels=["EDF (BDF+)", "CSV"], active=0, width=210) columns = [ widgets.TableColumn( field='timer', title="Record time", formatter=widgets.StringFormatter(text_align='center')) ] self.timer = widgets.DataTable(source=self._timer_source, index_position=None, sortable=False, reorderable=False, header_row=False, columns=columns, width=210, height=50, css_classes=["timer_widget"]) self.rec_button.on_click(self._toggle_rec) return column(Spacer(width=210, height=5), self.file_name_widget, self.file_type_widget, self.rec_button, self.timer) def _toggle_rec(self, active): if active: if self.explore.is_connected: self.explore.record_data( file_name=self.file_name_widget.value, file_type=['edf', 'csv'][self.file_type_widget.active], do_overwrite=True) self.rec_button.label = u"\u25A0 Stop" self.rec_start_time = datetime.now() self.rec_timer_id = self.doc.add_periodic_callback( self._timer_callback, 1000) else: self.rec_button.active = False self.doc.remove_periodic_callback(self.rec_timer_id) self.doc.add_next_tick_callback( partial(self._update_rec_timer, new_data={'timer': '00:00:00'})) else: self.explore.stop_recording() self.rec_button.label = u"\u25CF Record" self.doc.add_next_tick_callback( partial(self._update_rec_timer, new_data={'timer': '00:00:00'})) self.doc.remove_periodic_callback(self.rec_timer_id) def _timer_callback(self): t_delta = (datetime.now() - self.rec_start_time).seconds timer_text = ':'.join([ str(int(t_delta / 3600)).zfill(2), str(int(t_delta / 60) % 60).zfill(2), str(int(t_delta % 60)).zfill(2) ]) data = {'timer': timer_text} self.doc.add_next_tick_callback( partial(self._update_rec_timer, new_data=data)) @gen.coroutine @without_property_validation def _update_rec_timer(self, new_data): self._timer_source.stream(new_data, rollover=1) def _set_t_range(self, t_length): """Change time range of ExG and orientation plots""" for plot in self.plot_list: self.win_length = int(t_length) plot.x_range.follow = "end" plot.x_range.follow_interval = t_length plot.x_range.range_padding = 0. plot.x_range.min_interval = t_length
ticker=CmapAndTicks['ticker'], formatter=CmapAndTicks['formatter'], label_standoff=2, border_line_color=None, location=(0, 0), bar_line_alpha=0.5, major_label_text_align='left', ) # %% Make Buttons for state graph """ # %% Make Buttons for state graph ________________________________________________________________________________ """ buttons = {} buttons['state_covid_data'] = Toggle(label="State Time History Graph", visible=False, button_type='primary') # buttons['state_covid_data'].js_on_change( 'active', CustomJS(args={'p_state_covid': p_state_covid}, code=""" if (cb_obj.active == false){ cb_obj.label = "Show State Time History Graph" p_state_covid.visible = false } else{ cb_obj.label = "Hide State Time History Graph" p_state_covid.visible = true } """))
from bokeh.io import output_file, show from bokeh.models import Toggle output_file("toggle.html") toggle = Toggle(label="Foo", button_type="success") show(toggle)
# Import CheckboxGroup, RadioGroup, Toggle from bokeh.models from bokeh.models import CheckboxGroup, RadioGroup, Toggle from bokeh.io import curdoc from bokeh.plotting import figure from bokeh.layouts import widgetbox # Add a Toggle: toggle toggle = Toggle(label='Toggle button', button_type='success') # Add a CheckboxGroup: checkbox checkbox = CheckboxGroup(labels=['Option 1', 'Option 2', 'Option 3']) # Add a RadioGroup: radio radio = RadioGroup(labels=['Option 1', 'Option 2', 'Option 3']) # Add widgetbox(toggle, checkbox, radio) to the current document curdoc().add_root(widgetbox(toggle, checkbox, radio))
from bokeh.io import output_file, show from bokeh.plotting import figure from bokeh.layouts import layout from bokeh.models import Toggle, BoxAnnotation output_file("styling_visible_annotation_with_interaction.html") p = figure(plot_width=600, plot_height=200, tools='') p.line([1, 2, 3], [1, 2, 1], line_color="blue") pink_line = p.line([1, 2, 3], [2, 1, 2], line_color="pink") green_box = BoxAnnotation(left=1.5, right=2.5, fill_color='green', fill_alpha=0.1) p.add_layout(green_box) # Use js_link to connect button active property to glyph visble property toggle1 = Toggle(label="Green Box", button_type="success", active=True) toggle1.js_link('active', green_box, 'visible') toggle2 = Toggle(label="Pink Line", button_type="success", active=True) toggle2.js_link('active', pink_line, 'visible') show(layout([p], [toggle1, toggle2]))
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"
other_circles = fig.circle(source=other_source, x="x", y="y", radius="r", fill_color="gray", fill_alpha=.3, line_alpha=0, hover_alpha=1, hover_color="yellow", legend="Other") speed_circles = fig.circle(source=speed_source, x="x", y="y", radius="r", fill_color="blue", fill_alpha=.3, line_alpha=0, hover_alpha=1, hover_color="yellow", legend="Speeding") drunk_circles = fig.circle(source=drunk_source, x="x", y="y", radius="r", fill_color="red", fill_alpha=.3, line_alpha=0, hover_alpha=1, hover_color="yellow", legend="Drunk") dot_tooltips = [("Date", "@MONTH/@DAY/@YEAR"), ("Fatalities", "@FATALS"), ("Drunk", "@DRUNK_DR"), ("Speeding", "@SP"), ("Weather", "@WEATHER")] fig.add_tools(HoverTool(renderers=[other_circles, speed_circles, drunk_circles], tooltips=dot_tooltips)) button_group = CheckboxButtonGroup( labels=["Other", "Speeding", "Drunk"], active=[0, 1, 2], width=200) toggle = Toggle(label="Sort by Hour", button_type="default") slider = Slider(title="Hour (Military Time)", start=0, end=23, value=0, step=1) empty_dict = dict( x=np.array([]), y=np.array([]), r=np.array([]), MONTH=np.array([]), DAY=np.array([]), YEAR=np.array([]), FATALS=np.array([]), DRUNK_DR=np.array([]), SP=np.array([]), WEATHER=np.array([]) )
# Import CheckboxGroup, RadioGroup, Toggle from bokeh.models from bokeh.io import curdoc from bokeh.models import CheckboxGroup, RadioGroup, Toggle from bokeh.layouts import widgetbox # Add a Toggle: toggle toggle = Toggle(button_type='success', label='Toggle button') # Add a CheckboxGroup: checkbox checkbox = CheckboxGroup(labels=['Option 1', 'Option 2', 'Option 3']) # Add a RadioGroup: radio radio = RadioGroup(labels=['Option 1', 'Option 2', 'Option 3']) # Add widgetbox(toggle, checkbox, radio) to the current document curdoc().add_root(widgetbox(toggle, checkbox, radio))
def plotDistanceDurationRelation(fileName, dateRange, bokehPlaceholderId='bokehContent'): MAX_AGE = 150 #The initial data data = { 'x':[], 'y':[], 'ss':[], 'ss_lat':[], 'ss_long':[], 'es':[], 'es_lat':[], 'es_long':[], 'distance':[], 'avg_duration':[], 'count':[] } source = ColumnDataSource(data=data) ## Input Widgets ## textInputTopK = TextInput(value="1000", title="Number of Top Results Selected:") textInputMinDistance = TextInput(value="0.3", title="Minimum Distance Between Stations (km):") textInputMinDuration = TextInput(value="1", title="Minimum Trip Duration (min):") selectUT = Select(title="User Type:", value="All", options=["All", "Subscriber", "Customer"]) selectGender = Select(title="Gender:", value="All", options=["All", "Male", "Female", "Unknown"]) sliderAgeGeq = Slider(start=0, end=MAX_AGE, value=0, step=1, title="Age GEQ") sliderAgeLeq = Slider(start=0, end=MAX_AGE, value=0, step=1, title="Age LEQ") startDP = DatePicker(title="Start Date:", min_date=dateRange[0] ,max_date=dateRange[1], value=dateRange[0]) endDP = DatePicker(title="End Date:", min_date=dateRange[0] ,max_date=dateRange[1], value=dateRange[1]) ## Action Widget ## showButton = Toggle(label="Show", type="success") ## Output Widget ## columns = [ TableColumn(field="ss", title="SS"), TableColumn(field="es", title="ES"), TableColumn(field="distance", title="Distance (km)"), TableColumn(field="avg_duration", title="Avg Duration (min)"), TableColumn(field="count", title="#Trips") ] data_table = DataTable(source=source, columns=columns, width=850, row_headers=False, selectable='checkbox') ## Callbacks ## model = dict(source=source,textInputTopK=textInputTopK, textInputMinDuration=textInputMinDuration, textInputMinDistance=textInputMinDistance, startDP = startDP, endDP = endDP, selectUT=selectUT,selectGender=selectGender,sliderAgeLeq=sliderAgeLeq, sliderAgeGeq=sliderAgeGeq, dt = data_table) showCallback = CustomJS(args=model, code=""" //alert("callback"); var topK = textInputTopK.get('value'); var minDuration = textInputMinDuration.get('value'); var minDistance = textInputMinDistance.get('value'); var gender = selectGender.get('value'); var userType = selectUT.get('value'); var startAge = sliderAgeGeq.get('value'); var endAge = sliderAgeLeq.get('value'); var startDate = startDP.get('value'); if ( typeof(startDate) !== "number") startDate = startDate.getTime(); var endDate = endDP.get('value'); if ( typeof(endDate) !== "number") endDate = endDate.getTime(); var xmlhttp; xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == XMLHttpRequest.DONE ) { if(xmlhttp.status == 200){ var data = source.get('data'); var result = JSON.parse(xmlhttp.responseText); data['ss']=result.ss; data['ss_lat']=result.ss_lat; data['ss_long']=result.ss_long; data['es']=result.es; data['es_lat']=result.es_lat; data['es_long']=result.es_long; data['distance']=result.distance; data['avg_duration']=result.avg_duration; data['count']=result.count; source.trigger('change'); dt.trigger('change'); } else if(xmlhttp.status == 400) { alert(400); } else { alert(xmlhttp.status); } } }; var params = {topK:topK, minDuration:minDuration, minDistance:minDistance, gender:gender, userType:userType, startAge:startAge, endAge:endAge, startDate:startDate, endDate:endDate}; url = "/pairsDistanceOverDuration?" + jQuery.param( params ); xmlhttp.open("GET", url, true); xmlhttp.send(); """) showButton.callback = showCallback TOOLS = "box_zoom,reset,pan,wheel_zoom,save, tap, poly_select" plot = Figure(title="Station Pair Distance/Trip Duration Relation", x_axis_label='Distance Between Stations (km)', y_axis_label='Average Trip Duration (min)', plot_width=850, plot_height=400, tools=TOOLS) plot.circle('distance', 'avg_duration', source=source, color="navy", size = 5, alpha = 0.7) l1 = vform(plot, data_table) tab1 = Panel(child = vform(textInputTopK, textInputMinDistance), title="Station Pairs Filters") tab2 = Panel(child = vform(textInputMinDuration, selectUT, selectGender,sliderAgeLeq, sliderAgeGeq, startDP, endDP), title="Trips Filters") tabs = Tabs(tabs=[tab1, tab2]) layout = hplot(vplot(tabs, showButton), l1) script, div = components(layout) html = readHtmlFile(fileName) html = insertScriptIntoHeader(html, script) html = appendElementContent(html, div, "div", "bokehContent") return html
bt = Button(label='Update Plot') bt.on_click(my_slider_handler) 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) from bokeh.tile_providers import CARTODBPOSITRON map_repr = 'mercator' # set up/draw the map p = figure( # x_range=(minlng,maxlng),
def create(): det_data = {} roi_selection = {} upload_div = Div(text="Open .cami file:") def upload_button_callback(_attr, _old, new): with io.StringIO(base64.b64decode(new).decode()) as file: h5meta_list = pyzebra.parse_h5meta(file) file_list = h5meta_list["filelist"] filelist.options = file_list filelist.value = file_list[0] upload_button = FileInput(accept=".cami") upload_button.on_change("value", upload_button_callback) def update_image(index=None): if index is None: index = index_spinner.value current_image = det_data["data"][index] proj_v_line_source.data.update(x=np.arange(0, IMAGE_W) + 0.5, y=np.mean(current_image, axis=0)) proj_h_line_source.data.update(x=np.mean(current_image, axis=1), y=np.arange(0, IMAGE_H) + 0.5) image_source.data.update( h=[np.zeros((1, 1))], k=[np.zeros((1, 1))], l=[np.zeros((1, 1))], ) image_source.data.update(image=[current_image]) if auto_toggle.active: im_max = int(np.max(current_image)) im_min = int(np.min(current_image)) display_min_spinner.value = im_min display_max_spinner.value = im_max image_glyph.color_mapper.low = im_min image_glyph.color_mapper.high = im_max def update_overview_plot(): h5_data = det_data["data"] n_im, n_y, n_x = h5_data.shape overview_x = np.mean(h5_data, axis=1) overview_y = np.mean(h5_data, axis=2) overview_plot_x_image_source.data.update(image=[overview_x], dw=[n_x]) overview_plot_y_image_source.data.update(image=[overview_y], dw=[n_y]) if frame_button_group.active == 0: # Frame overview_plot_x.axis[1].axis_label = "Frame" overview_plot_y.axis[1].axis_label = "Frame" overview_plot_x_image_source.data.update(y=[0], dh=[n_im]) overview_plot_y_image_source.data.update(y=[0], dh=[n_im]) elif frame_button_group.active == 1: # Omega overview_plot_x.axis[1].axis_label = "Omega" overview_plot_y.axis[1].axis_label = "Omega" om = det_data["rot_angle"] om_start = om[0] om_end = (om[-1] - om[0]) * n_im / (n_im - 1) overview_plot_x_image_source.data.update(y=[om_start], dh=[om_end]) overview_plot_y_image_source.data.update(y=[om_start], dh=[om_end]) def filelist_callback(_attr, _old, new): nonlocal det_data det_data = pyzebra.read_detector_data(new) index_spinner.value = 0 index_spinner.high = det_data["data"].shape[0] - 1 update_image(0) update_overview_plot() filelist = Select() filelist.on_change("value", filelist_callback) def index_spinner_callback(_attr, _old, new): update_image(new) index_spinner = Spinner(title="Image index:", value=0, low=0) index_spinner.on_change("value", index_spinner_callback) plot = Plot( x_range=Range1d(0, IMAGE_W, bounds=(0, IMAGE_W)), y_range=Range1d(0, IMAGE_H, bounds=(0, IMAGE_H)), plot_height=IMAGE_H * 3, plot_width=IMAGE_W * 3, toolbar_location="left", ) # ---- tools plot.toolbar.logo = None # ---- axes plot.add_layout(LinearAxis(), place="above") plot.add_layout(LinearAxis(major_label_orientation="vertical"), place="right") # ---- grid lines plot.add_layout(Grid(dimension=0, ticker=BasicTicker())) plot.add_layout(Grid(dimension=1, ticker=BasicTicker())) # ---- rgba image glyph image_source = ColumnDataSource( dict( image=[np.zeros((IMAGE_H, IMAGE_W), dtype="float32")], h=[np.zeros((1, 1))], k=[np.zeros((1, 1))], l=[np.zeros((1, 1))], x=[0], y=[0], dw=[IMAGE_W], dh=[IMAGE_H], )) h_glyph = Image(image="h", x="x", y="y", dw="dw", dh="dh", global_alpha=0) k_glyph = Image(image="k", x="x", y="y", dw="dw", dh="dh", global_alpha=0) l_glyph = Image(image="l", x="x", y="y", dw="dw", dh="dh", global_alpha=0) plot.add_glyph(image_source, h_glyph) plot.add_glyph(image_source, k_glyph) plot.add_glyph(image_source, l_glyph) image_glyph = Image(image="image", x="x", y="y", dw="dw", dh="dh") plot.add_glyph(image_source, image_glyph, name="image_glyph") # ---- projections proj_v = Plot( x_range=plot.x_range, y_range=DataRange1d(), plot_height=200, plot_width=IMAGE_W * 3, toolbar_location=None, ) proj_v.add_layout(LinearAxis(major_label_orientation="vertical"), place="right") proj_v.add_layout(LinearAxis(major_label_text_font_size="0pt"), place="below") proj_v.add_layout(Grid(dimension=0, ticker=BasicTicker())) proj_v.add_layout(Grid(dimension=1, ticker=BasicTicker())) proj_v_line_source = ColumnDataSource(dict(x=[], y=[])) proj_v.add_glyph(proj_v_line_source, Line(x="x", y="y", line_color="steelblue")) proj_h = Plot( x_range=DataRange1d(), y_range=plot.y_range, plot_height=IMAGE_H * 3, plot_width=200, toolbar_location=None, ) proj_h.add_layout(LinearAxis(), place="above") proj_h.add_layout(LinearAxis(major_label_text_font_size="0pt"), place="left") proj_h.add_layout(Grid(dimension=0, ticker=BasicTicker())) proj_h.add_layout(Grid(dimension=1, ticker=BasicTicker())) proj_h_line_source = ColumnDataSource(dict(x=[], y=[])) proj_h.add_glyph(proj_h_line_source, Line(x="x", y="y", line_color="steelblue")) # add tools hovertool = HoverTool(tooltips=[("intensity", "@image"), ("h", "@h"), ("k", "@k"), ("l", "@l")]) box_edit_source = ColumnDataSource(dict(x=[], y=[], width=[], height=[])) box_edit_glyph = Rect(x="x", y="y", width="width", height="height", fill_alpha=0, line_color="red") box_edit_renderer = plot.add_glyph(box_edit_source, box_edit_glyph) boxedittool = BoxEditTool(renderers=[box_edit_renderer], num_objects=1) def box_edit_callback(_attr, _old, new): if new["x"]: h5_data = det_data["data"] x_val = np.arange(h5_data.shape[0]) left = int(np.floor(new["x"][0])) right = int(np.ceil(new["x"][0] + new["width"][0])) bottom = int(np.floor(new["y"][0])) top = int(np.ceil(new["y"][0] + new["height"][0])) y_val = np.sum(h5_data[:, bottom:top, left:right], axis=(1, 2)) else: x_val = [] y_val = [] roi_avg_plot_line_source.data.update(x=x_val, y=y_val) box_edit_source.on_change("data", box_edit_callback) wheelzoomtool = WheelZoomTool(maintain_focus=False) plot.add_tools( PanTool(), BoxZoomTool(), wheelzoomtool, ResetTool(), hovertool, boxedittool, ) plot.toolbar.active_scroll = wheelzoomtool # shared frame range frame_range = DataRange1d() det_x_range = DataRange1d() overview_plot_x = Plot( title=Title(text="Projections on X-axis"), x_range=det_x_range, y_range=frame_range, plot_height=400, plot_width=400, toolbar_location="left", ) # ---- tools wheelzoomtool = WheelZoomTool(maintain_focus=False) overview_plot_x.toolbar.logo = None overview_plot_x.add_tools( PanTool(), BoxZoomTool(), wheelzoomtool, ResetTool(), ) overview_plot_x.toolbar.active_scroll = wheelzoomtool # ---- axes overview_plot_x.add_layout(LinearAxis(axis_label="Coordinate X, pix"), place="below") overview_plot_x.add_layout(LinearAxis(axis_label="Frame", major_label_orientation="vertical"), place="left") # ---- grid lines overview_plot_x.add_layout(Grid(dimension=0, ticker=BasicTicker())) overview_plot_x.add_layout(Grid(dimension=1, ticker=BasicTicker())) # ---- rgba image glyph overview_plot_x_image_source = ColumnDataSource( dict(image=[np.zeros((1, 1), dtype="float32")], x=[0], y=[0], dw=[1], dh=[1])) overview_plot_x_image_glyph = Image(image="image", x="x", y="y", dw="dw", dh="dh") overview_plot_x.add_glyph(overview_plot_x_image_source, overview_plot_x_image_glyph, name="image_glyph") det_y_range = DataRange1d() overview_plot_y = Plot( title=Title(text="Projections on Y-axis"), x_range=det_y_range, y_range=frame_range, plot_height=400, plot_width=400, toolbar_location="left", ) # ---- tools wheelzoomtool = WheelZoomTool(maintain_focus=False) overview_plot_y.toolbar.logo = None overview_plot_y.add_tools( PanTool(), BoxZoomTool(), wheelzoomtool, ResetTool(), ) overview_plot_y.toolbar.active_scroll = wheelzoomtool # ---- axes overview_plot_y.add_layout(LinearAxis(axis_label="Coordinate Y, pix"), place="below") overview_plot_y.add_layout(LinearAxis(axis_label="Frame", major_label_orientation="vertical"), place="left") # ---- grid lines overview_plot_y.add_layout(Grid(dimension=0, ticker=BasicTicker())) overview_plot_y.add_layout(Grid(dimension=1, ticker=BasicTicker())) # ---- rgba image glyph overview_plot_y_image_source = ColumnDataSource( dict(image=[np.zeros((1, 1), dtype="float32")], x=[0], y=[0], dw=[1], dh=[1])) overview_plot_y_image_glyph = Image(image="image", x="x", y="y", dw="dw", dh="dh") overview_plot_y.add_glyph(overview_plot_y_image_source, overview_plot_y_image_glyph, name="image_glyph") def frame_button_group_callback(_active): update_overview_plot() frame_button_group = RadioButtonGroup(labels=["Frames", "Omega"], active=0) frame_button_group.on_click(frame_button_group_callback) roi_avg_plot = Plot( x_range=DataRange1d(), y_range=DataRange1d(), plot_height=IMAGE_H * 3, plot_width=IMAGE_W * 3, toolbar_location="left", ) # ---- tools roi_avg_plot.toolbar.logo = None # ---- axes roi_avg_plot.add_layout(LinearAxis(), place="below") roi_avg_plot.add_layout(LinearAxis(major_label_orientation="vertical"), place="left") # ---- grid lines roi_avg_plot.add_layout(Grid(dimension=0, ticker=BasicTicker())) roi_avg_plot.add_layout(Grid(dimension=1, ticker=BasicTicker())) roi_avg_plot_line_source = ColumnDataSource(dict(x=[], y=[])) roi_avg_plot.add_glyph(roi_avg_plot_line_source, Line(x="x", y="y", line_color="steelblue")) cmap_dict = { "gray": Greys256, "gray_reversed": Greys256[::-1], "plasma": Plasma256, "cividis": Cividis256, } def colormap_callback(_attr, _old, new): image_glyph.color_mapper = LinearColorMapper(palette=cmap_dict[new]) overview_plot_x_image_glyph.color_mapper = LinearColorMapper( palette=cmap_dict[new]) overview_plot_y_image_glyph.color_mapper = LinearColorMapper( palette=cmap_dict[new]) colormap = Select(title="Colormap:", options=list(cmap_dict.keys())) colormap.on_change("value", colormap_callback) colormap.value = "plasma" radio_button_group = RadioButtonGroup(labels=["nb", "nb_bi"], active=0) STEP = 1 # ---- colormap auto toggle button def auto_toggle_callback(state): if state: display_min_spinner.disabled = True display_max_spinner.disabled = True else: display_min_spinner.disabled = False display_max_spinner.disabled = False update_image() auto_toggle = Toggle(label="Auto Range", active=True, button_type="default") auto_toggle.on_click(auto_toggle_callback) # ---- colormap display max value def display_max_spinner_callback(_attr, _old_value, new_value): display_min_spinner.high = new_value - STEP image_glyph.color_mapper.high = new_value display_max_spinner = Spinner( title="Maximal Display Value:", low=0 + STEP, value=1, step=STEP, disabled=auto_toggle.active, ) display_max_spinner.on_change("value", display_max_spinner_callback) # ---- colormap display min value def display_min_spinner_callback(_attr, _old_value, new_value): display_max_spinner.low = new_value + STEP image_glyph.color_mapper.low = new_value display_min_spinner = Spinner( title="Minimal Display Value:", high=1 - STEP, value=0, step=STEP, disabled=auto_toggle.active, ) display_min_spinner.on_change("value", display_min_spinner_callback) def hkl_button_callback(): index = index_spinner.value setup_type = "nb_bi" if radio_button_group.active else "nb" h, k, l = calculate_hkl(det_data, index, setup_type) image_source.data.update(h=[h], k=[k], l=[l]) hkl_button = Button(label="Calculate hkl (slow)") hkl_button.on_click(hkl_button_callback) selection_list = TextAreaInput(rows=7) def selection_button_callback(): nonlocal roi_selection selection = [ int(np.floor(det_x_range.start)), int(np.ceil(det_x_range.end)), int(np.floor(det_y_range.start)), int(np.ceil(det_y_range.end)), int(np.floor(frame_range.start)), int(np.ceil(frame_range.end)), ] filename_id = filelist.value[-8:-4] if filename_id in roi_selection: roi_selection[f"{filename_id}"].append(selection) else: roi_selection[f"{filename_id}"] = [selection] selection_list.value = str(roi_selection) selection_button = Button(label="Add selection") selection_button.on_click(selection_button_callback) # Final layout layout_image = column( gridplot([[proj_v, None], [plot, proj_h]], merge_tools=False), row(index_spinner)) colormap_layout = column(colormap, auto_toggle, display_max_spinner, display_min_spinner) hkl_layout = column(radio_button_group, hkl_button) layout_overview = column( gridplot( [[overview_plot_x, overview_plot_y]], toolbar_options=dict(logo=None), merge_tools=True, ), frame_button_group, ) tab_layout = row( column( upload_div, upload_button, filelist, layout_image, row(colormap_layout, hkl_layout), ), column( roi_avg_plot, layout_overview, row(selection_button, selection_list), ), ) return Panel(child=tab_layout, title="Data Viewer")
counter = 0 def run(new): global p, patches, colors, counter for _ in range(slider.value): counter += 1 data = patches.data_source.data.copy() rates = np.random.uniform(0, 100, size=100).tolist() color = [colors[2 + int(rate / 16.667)] for rate in rates] p.title = 'Algorithms Deployed, Iteration: {}'.format(counter) source.data['rate'] = rates source.data['color'] = color time.sleep(5) toggle = Toggle(label='START') toggle.on_click(run) slider = Slider(name='N iterations to advance', title='N iterations to advance', start=5, end=10000, step=5, value=500) # set up layout toggler = HBox(toggle) inputs = VBox(toggler, slider) # add to document curdoc().add_root(HBox(inputs))
from bokeh.io import show from bokeh.models import CustomJS, Toggle toggle = Toggle(label="Foo", button_type="success") toggle.js_on_event( 'button_click', CustomJS(args=dict(btn=toggle), code=""" console.log('toggle: active=' + btn.active, this.toString()) """)) show(toggle)
TableColumn(field="S", title="Salinity"), TableColumn(field="N", title="Nitrate"), TableColumn(field="O", title="Oxygen"), TableColumn(field="Z", title="Tidal Height") ] data_table = DataTable(source=source, columns=columns, width=300, height=600) def toggle_callback(attr): if tide_toggle.active: # Checked *after* press tide_toggle.label = "Disable Tides" else: tide_toggle.label = "Enable Tides" tide_toggle = Toggle(label="Enable Tides", callback=toggle_ocean) tide_toggle.on_click(toggle_callback) download_button = Button(label="Download data", callback=download_data) go_button = Button(label="Run model")#, callback=check_fish) go_button.on_click(update_plots) # Set up app layout prods = VBox(gas_exchange_slider, productivity_slider) river = VBox(river_flow_slider, river_N_slider) tide_run = HBox(tide_toggle, download_button, go_button) all_settings = VBox(prods, river, tide_run, width=400)
StringEditor, StringFormatter, Switch, TableColumn, Tabs, TextAreaInput, TextInput, Toggle) from bokeh.plotting import figure from bokeh.resources import INLINE from bokeh.sampledata.autompg2 import autompg2 as mpg from bokeh.sampledata.iris import flowers from bokeh.util.browser import view click_button = Button(label="Button still has click event", button_type="success") disabled_button = Button(label="Button (disabled) - still has click event", button_type="primary", disabled=True) toggle = Toggle(label="Toggle button", button_type="success") menu = [("Item 1", "item_1_value"), ("Item 2", "item_2_value"), None, ("Item 3", "item_3_value")] dropdown = Dropdown(label="Dropdown button", button_type="warning", menu=menu) dropdown_split = Dropdown(label="Split button", button_type="danger", menu=menu, split=True) checkbox_group = CheckboxGroup(labels=["Option 1", "Option 2", "Option 3"], active=[0, 1]) radio_group = RadioGroup(labels=["Option 1", "Option 2", "Option 3"], active=0) checkbox_button_group = CheckboxButtonGroup(